@brainervirus/workit-core 1.0.9 → 1.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-core",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "private": false,
5
5
  "description": "Workit shared core — task, policy, evidence, review, decision, worker, and writer state for agentic coding workflows",
6
6
  "keywords": [
@@ -22,9 +22,21 @@ selects requirements; do not wait for a rule that can only exist after assess.
22
22
  needed, owner, and next action. Keep the plan against the existing system.
23
23
  3. If policy separately requires a durable specification, record that behavior
24
24
  agreement; otherwise do not invent a spec. A plan without a spec is valid.
25
- 4. Update the shared task progress at meaningful boundaries. Reassess when facts,
25
+ 4. Present before asking: show durable artifacts as a complete digest plus the
26
+ exact path and inline plans as their content. Never ask the user to approve
27
+ something they have not seen, and keep the question to one short scoped
28
+ sentence.
29
+ 5. Execute an approved plan continuously: one atomic commit per task after its
30
+ checks pass, no "continue?" prompts. Stop only for a new product decision, a
31
+ failed safety or verification gate, a conflicting concurrent edit, or missing
32
+ authority.
33
+ 6. Record the plan's commit list once through the plan-scoped action approval
34
+ (`git.commit` with `plan_steps` and `plan_branch`) so each listed commit runs
35
+ without a new question; an unlisted message, a branch change, or any change to
36
+ the plan needs a fresh exact approval.
37
+ 7. Update the shared task progress at meaningful boundaries. Reassess when facts,
26
38
  dependencies, or scope change; preserve unresolved blockers and decisions.
27
- 5. On steering (new instructions mid-task): apply `workit-steer` — park state
39
+ 8. On steering (new instructions mid-task): apply `workit-steer` — park state
28
40
  verbatim, classify same-task / new-task / quick-question, handle, re-anchor.
29
41
 
30
42
  Use shared task/progress and evidence operations. Do not create a universal
@@ -432,7 +432,15 @@ export const verifyDecisionContentAtRoot = (
432
432
  return failure("invalid_input", "document references require a byte digest");
433
433
  const target = path.resolve(root, reference.path);
434
434
  if (target !== root && !target.startsWith(`${root}${path.sep}`))
435
- return failure("invalid_input", "document reference escapes checkout");
435
+ return failure(
436
+ "invalid_input",
437
+ "document reference escapes checkout; cite it as an external file:// reference instead",
438
+ {
439
+ fields: [
440
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` },
441
+ ],
442
+ },
443
+ );
436
444
  try {
437
445
  const relative = path.relative(root, target);
438
446
  let current = root;
@@ -442,7 +450,18 @@ export const verifyDecisionContentAtRoot = (
442
450
  const stat = fs.lstatSync(current);
443
451
  const real = fs.realpathSync(current);
444
452
  if (real !== root && !real.startsWith(`${root}${path.sep}`))
445
- return failure("invalid_input", "document reference escapes checkout");
453
+ return failure(
454
+ "invalid_input",
455
+ "document reference escapes checkout; cite it as an external file:// reference instead",
456
+ {
457
+ fields: [
458
+ {
459
+ path: "contentRefs.path",
460
+ reason: `${reference.path} resolves outside the checkout`,
461
+ },
462
+ ],
463
+ },
464
+ );
446
465
  if (stat.isSymbolicLink())
447
466
  return failure("invalid_input", "document reference uses a symlink");
448
467
  if (current === target && !stat.isFile())
@@ -268,6 +268,18 @@ export const ensureBaseBranch = (cwd: string, base: string): { ok: boolean; erro
268
268
  return fastForwardBase(cwd, base);
269
269
  };
270
270
 
271
+ export type BranchSetupResult =
272
+ | {
273
+ action: "setup";
274
+ ok: true;
275
+ branch: string;
276
+ previous_branch: string;
277
+ stash_ref: string | null;
278
+ manifest: string;
279
+ }
280
+ | { action: "reapply_stash"; ok: true }
281
+ | { error: string; phase: "preflight" | "post_mutation" };
282
+
271
283
  // Port of scripts/branch/setup-branch.sh
272
284
  export const branchSetup = ({
273
285
  action,
@@ -283,12 +295,13 @@ export const branchSetup = ({
283
295
  stash?: string;
284
296
  workspace_root: string;
285
297
  log?: (message: string) => void;
286
- }) => {
298
+ }): BranchSetupResult => {
287
299
  const cwd = path.resolve(workspace_root);
288
300
  const exec = (args: string[]): string =>
289
301
  execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
290
302
  const current = gitContext(cwd).branch;
291
- if (!current || current === "unknown") return { error: "not in a git repository" };
303
+ if (!current || current === "unknown")
304
+ return { error: "not in a git repository", phase: "preflight" };
292
305
  const sdd = sdd_dir ?? "docs";
293
306
  const manifestPath = path.isAbsolute(sdd)
294
307
  ? path.join(sdd, "manifest.json")
@@ -297,7 +310,6 @@ export const branchSetup = ({
297
310
  const dir = path.dirname(manifestPath);
298
311
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o755 });
299
312
  };
300
- ensureManifestDir();
301
313
  const readManifest = (): Record<string, unknown> => {
302
314
  try {
303
315
  return JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -315,13 +327,16 @@ export const branchSetup = ({
315
327
  if (action === "reapply_stash") {
316
328
  const manifest = readManifest();
317
329
  const ref = manifest.stash_ref;
318
- if (!ref) return { error: "no stash_ref in manifest" };
330
+ if (!ref) return { error: "no stash_ref in manifest", phase: "preflight" };
319
331
  journal(`pre-pop: ${String(ref)}`);
320
332
  try {
321
333
  exec(["stash", "pop", String(ref)]);
322
334
  } catch (error) {
323
335
  journal("pop: failed");
324
- return { error: error instanceof Error ? error.message : "stash pop failed" };
336
+ return {
337
+ error: error instanceof Error ? error.message : "stash pop failed",
338
+ phase: "post_mutation",
339
+ };
325
340
  }
326
341
  journal("pop: ok");
327
342
  delete manifest.stash_ref;
@@ -331,10 +346,17 @@ export const branchSetup = ({
331
346
  }
332
347
 
333
348
  const target = target_branch ?? "";
334
- if (!target) return { error: "target branch required" };
335
- if (isProtected(cwd, target)) return { error: `protected branch ${target}` };
349
+ if (!target)
350
+ return {
351
+ error: "target_branch (the working branch to create or switch to) is required for setup",
352
+ phase: "preflight",
353
+ };
354
+ if (isProtected(cwd, target)) return { error: `protected branch ${target}`, phase: "preflight" };
336
355
  if (!allowedBranch(cwd, target))
337
- return { error: `target branch ${target} is not allowed by the branch policy` };
356
+ return {
357
+ error: `target branch ${target} is not allowed by the branch policy`,
358
+ phase: "preflight",
359
+ };
338
360
 
339
361
  // CA-02: resolve the base up front so an unresolvable base fails before
340
362
  // any mutation. The origin/<base> validation runs after the stash gate
@@ -348,7 +370,7 @@ export const branchSetup = ({
348
370
  }
349
371
  if (!targetExists) {
350
372
  const baseResolved = baseBranch(cwd);
351
- if ("error" in baseResolved) return { error: baseResolved.error };
373
+ if ("error" in baseResolved) return { error: baseResolved.error, phase: "preflight" };
352
374
  base = baseResolved.base;
353
375
  }
354
376
  journal(`entry: current=${current} target=${target} base=${base ?? "-"}`);
@@ -356,7 +378,10 @@ export const branchSetup = ({
356
378
  let stash_ref: string | undefined;
357
379
  // Best-effort restore; if the pop itself fails, the caller's error gains a
358
380
  // suffix pointing at the stash so stranded work stays discoverable.
359
- const failAfterStash = (message: string): { error: string } => {
381
+ const failAfterStash = (
382
+ message: string,
383
+ phase: "preflight" | "post_mutation" = "post_mutation",
384
+ ): { error: string; phase: "preflight" | "post_mutation" } => {
360
385
  let suffix = "";
361
386
  if (stash_ref) {
362
387
  journal(`pre-pop: ${stash_ref}`);
@@ -369,7 +394,7 @@ export const branchSetup = ({
369
394
  suffix = " (changes preserved in stash)";
370
395
  }
371
396
  }
372
- return { error: `${message}${suffix}` };
397
+ return { error: `${message}${suffix}`, phase: stash_ref ? "post_mutation" : phase };
373
398
  };
374
399
  if (current !== target) {
375
400
  const dirty = Boolean(gitContext(cwd).status_short.trim());
@@ -377,6 +402,7 @@ export const branchSetup = ({
377
402
  return {
378
403
  error:
379
404
  "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes",
405
+ phase: "preflight",
380
406
  };
381
407
  }
382
408
  // CA-02: validate origin/<base> before ANY mutation (no snapshot, no
@@ -386,14 +412,18 @@ export const branchSetup = ({
386
412
  let validatedBase: string | undefined;
387
413
  if (!targetExists && base !== undefined) {
388
414
  const ready = originBaseReady(cwd, base);
389
- if (!ready.ok) return { error: ready.error ?? "ensure-base-branch failed" };
415
+ if (!ready.ok)
416
+ return { error: ready.error ?? "ensure-base-branch failed", phase: "preflight" };
390
417
  validatedBase = base;
391
418
  }
392
419
  if (dirty) {
393
420
  try {
394
421
  exec(["stash", "push", "-u", "-m", `workit: pre-checkout ${target}`]);
395
422
  } catch (error) {
396
- return { error: error instanceof Error ? error.message : "stash push failed" };
423
+ return {
424
+ error: error instanceof Error ? error.message : "stash push failed",
425
+ phase: "post_mutation",
426
+ };
397
427
  }
398
428
  stash_ref = "stash@{0}";
399
429
  journal(`stash push: ${stash_ref}`);
@@ -406,13 +436,14 @@ export const branchSetup = ({
406
436
  if (/worktree/i.test(message)) {
407
437
  return failAfterStash(
408
438
  `branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)`,
439
+ "preflight",
409
440
  );
410
441
  }
411
442
  try {
412
443
  let effectiveBase = base;
413
444
  if (effectiveBase === undefined) {
414
445
  const lateResolved = baseBranch(cwd);
415
- if ("error" in lateResolved) return failAfterStash(lateResolved.error);
446
+ if ("error" in lateResolved) return failAfterStash(lateResolved.error, "preflight");
416
447
  effectiveBase = lateResolved.base;
417
448
  }
418
449
  // Pre-validated above when the target was missing: only the
@@ -236,6 +236,125 @@ const safePath = (root: string, value: string): string | null => {
236
236
  }
237
237
  };
238
238
 
239
+ /** Read-only baseline bound into a branch-setup proposal: the exact remote
240
+ * commit the base currently points at, without moving any local ref. */
241
+ const branchCreationBaseline = (
242
+ root: string,
243
+ target: string,
244
+ base: string,
245
+ ): { base_branch: string; target_exists: boolean; remote_base: string | null } => {
246
+ const exists =
247
+ gitValue(root, ["rev-parse", "--verify", "--quiet", `refs/heads/${target}`]) !== null;
248
+ if (exists) return { base_branch: base, target_exists: true, remote_base: null };
249
+ const remote = run(root, ["ls-remote", "origin", `refs/heads/${base}`]);
250
+ const sha = remote.exitCode === 0 ? remote.stdout.trim().split(/\s+/)[0] : "";
251
+ return {
252
+ base_branch: base,
253
+ target_exists: false,
254
+ remote_base: /^[0-9a-f]{40}$/.test(sha ?? "") ? (sha as string) : null,
255
+ };
256
+ };
257
+
258
+ const shortSha = (value: unknown): string =>
259
+ typeof value === "string" && value ? value.slice(0, 8) : "unknown";
260
+
261
+ const isProtectedBranch = (root: string, branch: string): boolean =>
262
+ resolveBranchPolicyFor(root).protected.has(branch.toLowerCase());
263
+
264
+ /** Concise, user-facing approval text for one resolved action. The exact
265
+ * descriptor stays machine-facing; this is what the native question shows. */
266
+ export const actionProposalQuestion = (
267
+ request: ExternalActionRequest,
268
+ descriptorPayload: unknown,
269
+ ): { presented: string; approvedText: string } => {
270
+ const payload = (descriptorPayload ?? {}) as Record<string, unknown>;
271
+ const resolved = (payload.resolved ?? {}) as Record<string, unknown>;
272
+ const payloadTarget = String(payload.target_branch ?? "");
273
+ if (request.operation === "git.commit" && Array.isArray(payload.plan_steps)) {
274
+ const steps = payload.plan_steps as string[];
275
+ return {
276
+ presented: `Workit decision: action — Approve committing the plan's ${steps.length} listed tasks on \`${String(payload.plan_branch ?? "")}\`?`,
277
+ approvedText: `Approve the ${steps.length} listed plan commits.`,
278
+ };
279
+ }
280
+ switch (request.operation) {
281
+ case "git.branch_setup": {
282
+ if (request.payload.action === "reapply_stash")
283
+ return {
284
+ presented:
285
+ "Workit decision: action — Reapply the pre-checkout stash recorded in the branch manifest?",
286
+ approvedText: "Reapply the recorded stash.",
287
+ };
288
+ const target = String(payload.target_branch ?? "");
289
+ // A dirty tree binds stash: the approval carries the stash through,
290
+ // so the question names it instead of failing later at preflight.
291
+ const stashes = payload.stash === "yes" && resolved.dirty === true;
292
+ if (resolved.target_exists === true)
293
+ return stashes
294
+ ? {
295
+ presented: `Workit decision: action — Stash the dirty working tree and switch to branch \`${target}\`?`,
296
+ approvedText: `Stash and switch to \`${target}\`.`,
297
+ }
298
+ : {
299
+ presented: `Workit decision: action — Switch to branch \`${target}\`?`,
300
+ approvedText: `Switch to \`${target}\`.`,
301
+ };
302
+ const base = String(resolved.base_branch ?? "the base branch");
303
+ return stashes
304
+ ? {
305
+ presented: `Workit decision: action — Stash the dirty working tree and create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
306
+ approvedText: `Stash and create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`,
307
+ }
308
+ : {
309
+ presented: `Workit decision: action — Create branch \`${target}\` from \`${base}\` (currently ${shortSha(resolved.remote_base)})?`,
310
+ approvedText: `Create \`${target}\` from \`${base}\` at ${shortSha(resolved.remote_base)}.`,
311
+ };
312
+ }
313
+ case "git.commit": {
314
+ const paths = Array.isArray(resolved.paths) ? (resolved.paths as string[]) : [];
315
+ return {
316
+ presented: `Workit decision: action — Commit ${paths.length} staged file(s) as "${String(request.payload.message)}"?`,
317
+ approvedText: `Commit as "${String(request.payload.message)}".`,
318
+ };
319
+ }
320
+ case "git.push":
321
+ return {
322
+ presented: `Workit decision: action — Push branch \`${String(payload.branch ?? "")}\` to origin?`,
323
+ approvedText: `Push \`${String(payload.branch ?? "")}\` to origin.`,
324
+ };
325
+ case "hosting.pull_request":
326
+ return {
327
+ presented: `Workit decision: action — Open a PR from \`${String(resolved.source_branch ?? "")}\` to \`${payloadTarget}\` titled "${String(request.payload.title)}"?`,
328
+ approvedText: `Open the PR: ${String(resolved.source_branch ?? "")} → ${payloadTarget}, "${String(request.payload.title)}".`,
329
+ };
330
+ case "changelog.apply":
331
+ return {
332
+ presented: `Workit decision: action — Apply changelog changes at \`${String(payload.path ?? "CHANGELOG.md")}\`?`,
333
+ approvedText: `Apply changelog changes at ${String(payload.path ?? "CHANGELOG.md")}.`,
334
+ };
335
+ case "youtrack.update":
336
+ return {
337
+ presented: `Workit decision: action — Update YouTrack issue ${String(request.payload.issueId)}${request.payload.minutes ? ` and log ${String(request.payload.minutes)}m` : ""}?`,
338
+ approvedText: `Update YouTrack issue ${String(request.payload.issueId)}.`,
339
+ };
340
+ case "youtrack.time":
341
+ return {
342
+ presented: `Workit decision: action — Log ${String(request.payload.minutes)}m to YouTrack issue ${String(request.payload.issueId)}?`,
343
+ approvedText: `Log time to YouTrack issue ${String(request.payload.issueId)}.`,
344
+ };
345
+ case "youtrack.meeting":
346
+ return {
347
+ presented: `Workit decision: action — Log meeting time to YouTrack issue ${String(request.payload.issueId)}?`,
348
+ approvedText: `Log meeting time to YouTrack issue ${String(request.payload.issueId)}.`,
349
+ };
350
+ default:
351
+ return {
352
+ presented: `Workit decision: action — Approve ${request.operation}?`,
353
+ approvedText: `Approve ${request.operation}.`,
354
+ };
355
+ }
356
+ };
357
+
239
358
  export type ResolvedExternalAction = {
240
359
  request: ExternalActionRequest;
241
360
  descriptorPayload: unknown;
@@ -697,23 +816,41 @@ export const resolveExternalActionRequest = (
697
816
  case "git.branch_setup": {
698
817
  const sdd_dir = safePath(root, request.payload.sdd_dir ?? "docs");
699
818
  if (!sdd_dir) return failure("invalid_input", "sdd_dir must stay inside the workspace");
819
+ const action = request.payload.action ?? "setup";
700
820
  const policy = resolveBranchPolicyFor(root);
701
- const target_branch = request.payload.target_branch ?? policy.defaultTargetBranch;
821
+ const target_branch = request.payload.target_branch;
822
+ if (action !== "reapply_stash" && !target_branch)
823
+ return failure(
824
+ "invalid_input",
825
+ "target_branch (the working branch to create or switch to) is required for setup",
826
+ {
827
+ outcome: "not_started",
828
+ fields: [{ path: "target_branch", reason: "required for action=setup" }],
829
+ },
830
+ );
702
831
  const head = gitValue(root, ["rev-parse", "HEAD"]);
703
832
  if (!head) return failure("storage_error", "current Git commit could not be resolved");
833
+ const dirty = Boolean((gitRaw(root, ["status", "--short"]) ?? "").trim());
834
+ const creation =
835
+ action === "reapply_stash" || !target_branch
836
+ ? { base_branch: policy.defaultTargetBranch }
837
+ : branchCreationBaseline(root, target_branch, policy.defaultTargetBranch);
704
838
  const normalized = {
705
839
  operation: request.operation,
706
840
  payload: {
707
841
  ...request.payload,
708
- action: request.payload.action ?? "setup",
842
+ action,
709
843
  sdd_dir,
710
844
  stash: request.payload.stash ?? "no",
711
- target_branch,
845
+ ...(target_branch ? { target_branch } : {}),
712
846
  },
713
847
  } as ExternalActionRequest;
714
848
  return success(null, null, {
715
849
  request: normalized,
716
- descriptorPayload: { ...normalized.payload, resolved: { head, target_branch } },
850
+ descriptorPayload: {
851
+ ...normalized.payload,
852
+ resolved: { head, dirty, ...creation },
853
+ },
717
854
  });
718
855
  }
719
856
  case "git.push": {
@@ -729,7 +866,46 @@ export const resolveExternalActionRequest = (
729
866
  : failure("invalid_input", "git push requires a resolvable current branch");
730
867
  }
731
868
  case "git.commit": {
869
+ const planSteps = request.payload.plan_steps;
870
+ const planBranch = request.payload.plan_branch;
732
871
  const head = gitValue(root, ["rev-parse", "HEAD"]);
872
+ if (!head) return failure("storage_error", "Git state could not be resolved");
873
+ const branch = gitValue(root, ["branch", "--show-current"]);
874
+ if (planSteps !== undefined || planBranch !== undefined) {
875
+ if (
876
+ !Array.isArray(planSteps) ||
877
+ planSteps.some((step) => typeof step !== "string" || !step.trim()) ||
878
+ new Set(planSteps).size !== planSteps.length ||
879
+ planSteps.length === 0 ||
880
+ planSteps.length > 32
881
+ )
882
+ return failure(
883
+ "invalid_input",
884
+ "plan_steps must be 1-32 unique non-empty commit messages",
885
+ {
886
+ outcome: "not_started",
887
+ fields: [{ path: "plan_steps", reason: "unique strings" }],
888
+ },
889
+ );
890
+ if (typeof planBranch !== "string" || !planBranch.trim() || planBranch !== branch)
891
+ return failure("invalid_input", "plan_branch must name the current working branch", {
892
+ outcome: "not_started",
893
+ fields: [{ path: "plan_branch", reason: "current branch" }],
894
+ });
895
+ if (isProtectedBranch(root, planBranch))
896
+ return failure("invalid_input", "plan_branch must not be a protected branch", {
897
+ outcome: "not_started",
898
+ fields: [{ path: "plan_branch", reason: "protected branch" }],
899
+ });
900
+ return success(null, null, {
901
+ request,
902
+ descriptorPayload: {
903
+ plan_steps: planSteps,
904
+ plan_branch: planBranch,
905
+ resolved: { head, branch, steps: planSteps },
906
+ },
907
+ });
908
+ }
733
909
  const staged = gitRaw(root, [
734
910
  "diff",
735
911
  "--cached",
@@ -740,7 +916,13 @@ export const resolveExternalActionRequest = (
740
916
  "HEAD",
741
917
  ]);
742
918
  const paths = stagedPaths(root);
743
- if (!head || staged === null || paths === null)
919
+ const message = request.payload.message;
920
+ if (typeof message !== "string" || !message)
921
+ return failure("invalid_input", "git.commit requires a commit message", {
922
+ outcome: "not_started",
923
+ fields: [{ path: "message", reason: "required unless plan_steps is provided" }],
924
+ });
925
+ if (staged === null || paths === null)
744
926
  return failure("storage_error", "Git state could not be resolved");
745
927
  let policy: { preset: string; pattern?: string };
746
928
  try {
@@ -757,7 +939,7 @@ export const resolveExternalActionRequest = (
757
939
  const detected = detectCommitFlavor((log ?? "").split("\n").filter(Boolean));
758
940
  flavor = detected.flavor ?? "conventional";
759
941
  }
760
- if (!matchCommitFlavor(request.payload.message, flavor as CommitFlavor, policy.pattern))
942
+ if (!matchCommitFlavor(message, flavor as CommitFlavor, policy.pattern))
761
943
  return failure(
762
944
  "invalid_input",
763
945
  `Commit message does not match the ${flavor} flavor (commitPolicy.preset)`,
@@ -766,7 +948,7 @@ export const resolveExternalActionRequest = (
766
948
  request,
767
949
  descriptorPayload: {
768
950
  ...request.payload,
769
- resolved: { head, staged: sha256(staged), paths },
951
+ resolved: { head, branch, staged: sha256(staged), paths },
770
952
  },
771
953
  });
772
954
  }
@@ -985,10 +1167,24 @@ export const executeConcreteExternalAction = async (
985
1167
  sdd_dir: request.payload.sdd_dir ?? "docs",
986
1168
  workspace_root: root,
987
1169
  });
988
- return result.error ? unknown(request.operation) : success(null, null, result);
1170
+ if ("error" in result) {
1171
+ if (result.phase === "preflight")
1172
+ return failure("invalid_input", result.error, {
1173
+ outcome: "not_started",
1174
+ operation: request.operation,
1175
+ });
1176
+ return unknown(request.operation);
1177
+ }
1178
+ return success(null, null, result);
989
1179
  }
990
1180
  case "git.commit": {
991
- const result = run(root, ["commit", "-m", request.payload.message]);
1181
+ const message = request.payload.message;
1182
+ if (typeof message !== "string" || !message)
1183
+ return failure("invalid_input", "git.commit requires a commit message", {
1184
+ outcome: "not_started",
1185
+ fields: [{ path: "message", reason: "required" }],
1186
+ });
1187
+ const result = run(root, ["commit", "-m", message]);
992
1188
  return result.exitCode === 0
993
1189
  ? success(null, null, { stdout: result.stdout.trim() })
994
1190
  : unknown(request.operation);