wendkeep 0.78.0 → 0.79.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/src/worktree.mjs CHANGED
@@ -26,10 +26,35 @@ import {
26
26
  worktreeIdentity,
27
27
  } from '../packages/vault/src/worktree-metadata.mjs';
28
28
 
29
+ function sanitizeCliText(value) {
30
+ return String(value || '')
31
+ .replace(/\b(?:ghp|github_pat|npm_|sk-|xox[baprs]-)[A-Za-z0-9_\-]+/gi, '[redacted-token]')
32
+ .replace(/\b(?:token|authorization|bearer|password|secret|api[_-]?key)\s*[=:]\s*[^\s,;]+/gi, '[redacted-token]')
33
+ .replace(/\bBearer\s+[^\s,;]+/gi, '[redacted-token]')
34
+ .replace(/[A-Za-z]:\\[^\r\n"'`;|&]+/g, '[redacted-path]')
35
+ .replace(/(?:^|\s)\/[^\s"'`;|&]+/g, ' [redacted-path]')
36
+ .replace(/[\r\n\t]+/g, ' ')
37
+ .replace(/[;&|`$<>(){}[\]]/g, ' ')
38
+ .replace(/\s{2,}/g, ' ')
39
+ .trim()
40
+ .slice(0, 240);
41
+ }
42
+
43
+ function sanitizeCliValue(value) {
44
+ if (typeof value === 'string') return sanitizeCliText(value);
45
+ if (Array.isArray(value)) return value.map((item) => sanitizeCliValue(item));
46
+ if (value && typeof value === 'object') {
47
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
48
+ sanitizeCliText(key), sanitizeCliValue(item),
49
+ ]));
50
+ }
51
+ return value;
52
+ }
53
+
29
54
  function worktreeError(code, message, details = {}) {
30
- const error = new Error(message);
55
+ const error = new Error(sanitizeCliText(message));
31
56
  error.code = code;
32
- Object.assign(error, details);
57
+ Object.assign(error, sanitizeCliValue(details));
33
58
  return error;
34
59
  }
35
60
 
@@ -446,11 +471,19 @@ const ERROR_TEXT = {
446
471
  WENDKEEP_WORKTREE_PR_UNAVAILABLE: 'Pull Request proof is unavailable.',
447
472
  WENDKEEP_WORKTREE_PR_NOT_MERGED: 'Pull Request is not merged.',
448
473
  WENDKEEP_WORKTREE_PR_MISMATCH: 'Pull Request does not match the managed branch.',
474
+ WENDKEEP_WORKTREE_PR_HEAD_MISMATCH: 'Pull Request head does not match the managed head.',
449
475
  WENDKEEP_WORKTREE_PR_MERGE_UNREACHABLE: 'Pull Request merge commit is not reachable from the local base.',
476
+ WENDKEEP_WORKTREE_CONTEXT_MISMATCH: 'An active context belongs to another project or repository.',
450
477
  WENDKEEP_WORKTREE_CLEANUP_BUSY: 'Worktree cleanup is already running.',
478
+ WENDKEEP_WORKTREE_BRANCH_UNPROVEN: 'The managed branch head could not be proven.',
451
479
  WENDKEEP_WORKTREE_REASON_REQUIRED: 'Worktree removal requires a reason.',
452
480
  WENDKEEP_WORKTREE_REMOTE_UNAVAILABLE: 'Remote branch state is unavailable.',
453
481
  WENDKEEP_WORKTREE_REMOTE_DIVERGED: 'Remote branch diverged from the proven head.',
482
+ WENDKEEP_RECEIPT_LEDGER_CORRUPT: 'The cleanup receipt ledger is corrupt.',
483
+ WENDKEEP_RECEIPT_LEDGER_TRUNCATED: 'The cleanup receipt ledger is truncated.',
484
+ WENDKEEP_RECEIPT_LEDGER_BUSY: 'The cleanup receipt ledger is busy.',
485
+ WENDKEEP_RECEIPT_LEDGER_CONFLICT: 'The cleanup receipt ledger has a conflict.',
486
+ WENDKEEP_PROVENANCE_GATE_BLOCKED: 'Cleanup receipt provenance gate blocked the operation.',
454
487
  WENDKEEP_WORKTREE_USAGE: 'Invalid worktree command usage.',
455
488
  },
456
489
  'pt-BR': {
@@ -484,11 +517,19 @@ const ERROR_TEXT = {
484
517
  WENDKEEP_WORKTREE_PR_UNAVAILABLE: 'A prova do Pull Request está indisponível.',
485
518
  WENDKEEP_WORKTREE_PR_NOT_MERGED: 'O Pull Request não está merged.',
486
519
  WENDKEEP_WORKTREE_PR_MISMATCH: 'O Pull Request não corresponde à branch gerenciada.',
520
+ WENDKEEP_WORKTREE_PR_HEAD_MISMATCH: 'O head do Pull Request não corresponde ao head gerenciado.',
487
521
  WENDKEEP_WORKTREE_PR_MERGE_UNREACHABLE: 'O merge commit do Pull Request não está alcançável pela base local.',
522
+ WENDKEEP_WORKTREE_CONTEXT_MISMATCH: 'Um contexto ativo pertence a outro projeto ou repositório.',
488
523
  WENDKEEP_WORKTREE_CLEANUP_BUSY: 'A limpeza da worktree já está em andamento.',
524
+ WENDKEEP_WORKTREE_BRANCH_UNPROVEN: 'Não foi possível provar o head da branch gerenciada.',
489
525
  WENDKEEP_WORKTREE_REASON_REQUIRED: 'A remoção da worktree exige um motivo.',
490
526
  WENDKEEP_WORKTREE_REMOTE_UNAVAILABLE: 'O estado da branch remota está indisponível.',
491
527
  WENDKEEP_WORKTREE_REMOTE_DIVERGED: 'A branch remota divergiu do head comprovado.',
528
+ WENDKEEP_RECEIPT_LEDGER_CORRUPT: 'O ledger de receipts de cleanup está corrompido.',
529
+ WENDKEEP_RECEIPT_LEDGER_TRUNCATED: 'O ledger de receipts de cleanup está truncado.',
530
+ WENDKEEP_RECEIPT_LEDGER_BUSY: 'O ledger de receipts de cleanup está ocupado.',
531
+ WENDKEEP_RECEIPT_LEDGER_CONFLICT: 'O ledger de receipts de cleanup está em conflito.',
532
+ WENDKEEP_PROVENANCE_GATE_BLOCKED: 'O gate de proveniência do receipt de cleanup bloqueou a operação.',
492
533
  WENDKEEP_WORKTREE_USAGE: 'Uso inválido do comando worktree.',
493
534
  },
494
535
  };
@@ -498,9 +539,54 @@ function commandLocale(startDir) {
498
539
  catch { return 'pt-BR'; }
499
540
  }
500
541
 
501
- function renderWorktreeError(error, locale) {
542
+ function renderWorktreeError(error, locale, { json = false, operation = '' } = {}) {
502
543
  const code = error?.code || 'WENDKEEP_WORKTREE_FAILED';
503
- return `${code}: ${ERROR_TEXT[locale]?.[code] || ERROR_TEXT[locale]?.WENDKEEP_WORKTREE_FAILED}`;
544
+ const safeCode = sanitizeCliText(code) || 'WENDKEEP_WORKTREE_FAILED';
545
+ const localized = ERROR_TEXT[locale]?.[safeCode]
546
+ || ERROR_TEXT[locale]?.WENDKEEP_WORKTREE_FAILED;
547
+ // The renderer receives the CLI command as the authoritative operation
548
+ // label. Cleanup errors also carry their internal operation name for
549
+ // structured diagnostics; exposing that name here would make `finish`,
550
+ // `remove`, and `prune` indistinguishable in the CLI contract.
551
+ const safeOperation = sanitizeCliText(operation || error?.operation || 'worktree') || 'worktree';
552
+ const operationId = sanitizeCliText(error?.operationId || error?.operation_id);
553
+ const state = sanitizeCliText(error?.state);
554
+ const blockers = sanitizeCliValue(error?.blockers || error?.blocker);
555
+ const firstBlocker = Array.isArray(blockers) ? blockers[0] : blockers;
556
+ const blocker = sanitizeCliText(
557
+ error?.blockerCode
558
+ || firstBlocker?.code
559
+ || firstBlocker?.blocker
560
+ || (typeof firstBlocker === 'string' ? firstBlocker : ''),
561
+ );
562
+ const expected = sanitizeCliValue(error?.expected ?? firstBlocker?.expected ?? null);
563
+ const observed = sanitizeCliValue(error?.observed ?? firstBlocker?.observed ?? null);
564
+ const recovery = sanitizeCliText(error?.recovery || error?.repair);
565
+ if (json) {
566
+ return JSON.stringify({
567
+ ok: false,
568
+ code: safeCode,
569
+ error: localized,
570
+ operation: safeOperation,
571
+ ...(operationId ? { operationId, operation_id: operationId } : {}),
572
+ ...(state ? { state } : {}),
573
+ blocker: blocker || null,
574
+ ...(blockers ? { blockers } : {}),
575
+ expected,
576
+ observed,
577
+ ...(recovery ? { recovery } : {}),
578
+ });
579
+ }
580
+ const details = [
581
+ `operation=${safeOperation}`,
582
+ operationId && `operation_id=${operationId}`,
583
+ state && `state=${state}`,
584
+ blocker && `blocker=${blocker}`,
585
+ expected && `expected=${sanitizeCliText(JSON.stringify(expected))}`,
586
+ observed && `observed=${sanitizeCliText(JSON.stringify(observed))}`,
587
+ recovery && `recovery=${recovery}`,
588
+ ].filter(Boolean);
589
+ return `${safeCode}: ${localized}${details.length ? ` (${details.join(' ')})` : ''}`;
504
590
  }
505
591
 
506
592
  function renderWorktreeLine(worktree) {
@@ -542,6 +628,7 @@ function writeWorktreeResult(payload, { json, locale, action }) {
542
628
  export async function runWorktree(argv = [], dependencies = {}) {
543
629
  let parsed;
544
630
  let locale = 'pt-BR';
631
+ const jsonRequested = argv.some((arg) => arg === '--json');
545
632
  try {
546
633
  parsed = parseWorktreeArgv(argv);
547
634
  const [command, ...positionals] = parsed.positional;
@@ -631,7 +718,9 @@ export async function runWorktree(argv = [], dependencies = {}) {
631
718
  }
632
719
  throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'Use create, list, status, open, finish, cleanup, remove ou prune.');
633
720
  } catch (error) {
634
- process.stderr.write(`${renderWorktreeError(error, locale)}\n`);
721
+ const json = Boolean(parsed?.flags?.has('--json') || jsonRequested);
722
+ const operation = parsed?.positional?.[0] || 'worktree';
723
+ process.stderr.write(`${renderWorktreeError(error, locale, { json, operation })}\n`);
635
724
  return 2;
636
725
  }
637
726
  }