wendkeep 0.76.9 → 0.78.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 +39 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/docs/en/commands/changes-and-verification.md +8 -1
- package/docs/en/commands/verify.md +22 -5
- package/docs/en/commands/worktrees.md +80 -32
- package/docs/pt-BR/commands/changes-and-verification.md +8 -1
- package/docs/pt-BR/commands/verify.md +22 -6
- package/docs/pt-BR/commands/worktrees.md +80 -31
- package/hooks/change-core.mjs +2 -1
- package/hooks/harness-doctor.mjs +51 -1
- package/hooks/spec-core.mjs +26 -8
- package/package.json +2 -2
- package/packages/cli/src/index.mjs +1 -1
- package/packages/harness/src/sensors-core.mjs +57 -3
- package/packages/vault/src/evidence-envelope.mjs +73 -0
- package/packages/vault/src/index.mjs +1 -0
- package/packages/vault/src/memory-handoff.mjs +46 -5
- package/packages/vault/src/vault-path-safety.mjs +11 -0
- package/schema/wendkeep.evidence-envelope-v2.schema.json +92 -0
- package/src/change.mjs +93 -10
- package/src/evidence-envelope.mjs +288 -0
- package/src/skills-seed.mjs +11 -5
- package/src/verify.mjs +85 -22
- package/src/worktree-cleanup.mjs +712 -0
- package/src/worktree.mjs +130 -19
package/src/worktree.mjs
CHANGED
|
@@ -10,6 +10,13 @@ import {
|
|
|
10
10
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
11
11
|
|
|
12
12
|
import { resolveProjectVault } from './project-vault.mjs';
|
|
13
|
+
import {
|
|
14
|
+
cleanupMergedWorktrees,
|
|
15
|
+
diagnoseManagedWorktreeCleanups,
|
|
16
|
+
finishManagedWorktree,
|
|
17
|
+
pruneManagedWorktrees,
|
|
18
|
+
removeManagedWorktree,
|
|
19
|
+
} from './worktree-cleanup.mjs';
|
|
13
20
|
import { getLocale } from '../packages/vault/src/locale.mjs';
|
|
14
21
|
import {
|
|
15
22
|
discoverWorktreeRepository,
|
|
@@ -150,12 +157,29 @@ function vscodeWorktreeTasks() {
|
|
|
150
157
|
args: ['--no-install', 'wendkeep', 'worktree', 'open', '${input:wendkeepWorktreeSlug}'],
|
|
151
158
|
problemMatcher: [],
|
|
152
159
|
},
|
|
160
|
+
{
|
|
161
|
+
label: 'WendKeep: Finish merged worktree',
|
|
162
|
+
type: 'process',
|
|
163
|
+
command: 'npx',
|
|
164
|
+
args: [
|
|
165
|
+
'--no-install', 'wendkeep', 'worktree', 'finish',
|
|
166
|
+
'${input:wendkeepWorktreeSlug}', '--pr', '${input:wendkeepPullRequest}', '--open-main',
|
|
167
|
+
],
|
|
168
|
+
problemMatcher: [],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
inputs: [
|
|
172
|
+
{
|
|
173
|
+
id: 'wendkeepWorktreeSlug',
|
|
174
|
+
type: 'promptString',
|
|
175
|
+
description: 'Managed worktree slug',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'wendkeepPullRequest',
|
|
179
|
+
type: 'promptString',
|
|
180
|
+
description: 'Merged pull request number or URL',
|
|
181
|
+
},
|
|
153
182
|
],
|
|
154
|
-
inputs: [{
|
|
155
|
-
id: 'wendkeepWorktreeSlug',
|
|
156
|
-
type: 'promptString',
|
|
157
|
-
description: 'Managed worktree slug',
|
|
158
|
-
}],
|
|
159
183
|
};
|
|
160
184
|
}
|
|
161
185
|
|
|
@@ -312,18 +336,26 @@ export function diagnoseManagedWorktrees({ startDir = process.cwd(), spawn = spa
|
|
|
312
336
|
throw error;
|
|
313
337
|
}
|
|
314
338
|
const issues = listed.worktrees
|
|
315
|
-
.filter((entry) =>
|
|
339
|
+
.filter((entry) => (
|
|
340
|
+
!['ready', 'cleaning', 'cleanup-failed', 'cleaned'].includes(entry.state)
|
|
341
|
+
|| (entry.state === 'ready' && !entry.binding.healthy)
|
|
342
|
+
))
|
|
316
343
|
.map((entry) => ({
|
|
317
344
|
slug: entry.slug,
|
|
318
345
|
state: entry.state,
|
|
319
346
|
errorCode: entry.errorCode || entry.binding.errorCode || 'WENDKEEP_WORKTREE_UNHEALTHY',
|
|
320
347
|
repair: entry.recovery || `wendkeep worktree status ${entry.slug}`,
|
|
321
348
|
}));
|
|
322
|
-
|
|
349
|
+
const cleanup = diagnoseManagedWorktreeCleanups({ startDir, spawn });
|
|
350
|
+
return { initialized: true, issues: [...issues, ...cleanup.issues] };
|
|
323
351
|
}
|
|
324
352
|
|
|
325
|
-
const WORKTREE_VALUE_OPTIONS = new Set([
|
|
326
|
-
|
|
353
|
+
const WORKTREE_VALUE_OPTIONS = new Set([
|
|
354
|
+
'--base', '--branch', '--open', '--editor', '--project', '--pr', '--reason',
|
|
355
|
+
]);
|
|
356
|
+
const WORKTREE_FLAG_OPTIONS = new Set([
|
|
357
|
+
'--json', '--delete-remote', '--open-main', '--merged', '--dry-run', '--apply',
|
|
358
|
+
]);
|
|
327
359
|
|
|
328
360
|
function parseWorktreeArgv(argv) {
|
|
329
361
|
const values = new Map();
|
|
@@ -360,10 +392,14 @@ function parseWorktreeArgv(argv) {
|
|
|
360
392
|
|
|
361
393
|
function assertCommandOptions(command, parsed) {
|
|
362
394
|
const allowed = {
|
|
363
|
-
create: new Set(['--base', '--branch', '--open', '--project']),
|
|
364
|
-
list: new Set(['--project']),
|
|
365
|
-
status: new Set(['--project']),
|
|
366
|
-
open: new Set(['--editor', '--project']),
|
|
395
|
+
create: new Set(['--base', '--branch', '--open', '--project', '--json']),
|
|
396
|
+
list: new Set(['--project', '--json']),
|
|
397
|
+
status: new Set(['--project', '--json']),
|
|
398
|
+
open: new Set(['--editor', '--project', '--json']),
|
|
399
|
+
finish: new Set(['--pr', '--project', '--delete-remote', '--open-main', '--json']),
|
|
400
|
+
cleanup: new Set(['--project', '--merged', '--dry-run', '--apply', '--json']),
|
|
401
|
+
remove: new Set(['--reason', '--project', '--json']),
|
|
402
|
+
prune: new Set(['--project', '--dry-run', '--apply', '--json']),
|
|
367
403
|
}[command];
|
|
368
404
|
if (!allowed) return;
|
|
369
405
|
for (const name of parsed.values.keys()) {
|
|
@@ -371,6 +407,11 @@ function assertCommandOptions(command, parsed) {
|
|
|
371
407
|
throw worktreeError('WENDKEEP_WORKTREE_USAGE', `${name} não é válido para worktree ${command}.`);
|
|
372
408
|
}
|
|
373
409
|
}
|
|
410
|
+
for (const name of parsed.flags) {
|
|
411
|
+
if (!allowed.has(name)) {
|
|
412
|
+
throw worktreeError('WENDKEEP_WORKTREE_USAGE', `${name} não é válido para worktree ${command}.`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
374
415
|
}
|
|
375
416
|
|
|
376
417
|
const ERROR_TEXT = {
|
|
@@ -401,6 +442,15 @@ const ERROR_TEXT = {
|
|
|
401
442
|
WENDKEEP_WORKTREE_EDITOR_UNSUPPORTED: 'Unsupported worktree editor.',
|
|
402
443
|
WENDKEEP_WORKTREE_NOT_FOUND: 'Managed worktree was not found.',
|
|
403
444
|
WENDKEEP_WORKTREE_NOT_READY: 'Managed worktree is not ready.',
|
|
445
|
+
WENDKEEP_WORKTREE_PR_INVALID: 'Pull Request reference is invalid or missing.',
|
|
446
|
+
WENDKEEP_WORKTREE_PR_UNAVAILABLE: 'Pull Request proof is unavailable.',
|
|
447
|
+
WENDKEEP_WORKTREE_PR_NOT_MERGED: 'Pull Request is not merged.',
|
|
448
|
+
WENDKEEP_WORKTREE_PR_MISMATCH: 'Pull Request does not match the managed branch.',
|
|
449
|
+
WENDKEEP_WORKTREE_PR_MERGE_UNREACHABLE: 'Pull Request merge commit is not reachable from the local base.',
|
|
450
|
+
WENDKEEP_WORKTREE_CLEANUP_BUSY: 'Worktree cleanup is already running.',
|
|
451
|
+
WENDKEEP_WORKTREE_REASON_REQUIRED: 'Worktree removal requires a reason.',
|
|
452
|
+
WENDKEEP_WORKTREE_REMOTE_UNAVAILABLE: 'Remote branch state is unavailable.',
|
|
453
|
+
WENDKEEP_WORKTREE_REMOTE_DIVERGED: 'Remote branch diverged from the proven head.',
|
|
404
454
|
WENDKEEP_WORKTREE_USAGE: 'Invalid worktree command usage.',
|
|
405
455
|
},
|
|
406
456
|
'pt-BR': {
|
|
@@ -430,6 +480,15 @@ const ERROR_TEXT = {
|
|
|
430
480
|
WENDKEEP_WORKTREE_EDITOR_UNSUPPORTED: 'Editor de worktree não suportado.',
|
|
431
481
|
WENDKEEP_WORKTREE_NOT_FOUND: 'Worktree gerenciada não encontrada.',
|
|
432
482
|
WENDKEEP_WORKTREE_NOT_READY: 'Worktree gerenciada ainda não está pronta.',
|
|
483
|
+
WENDKEEP_WORKTREE_PR_INVALID: 'Referência de Pull Request inválida ou ausente.',
|
|
484
|
+
WENDKEEP_WORKTREE_PR_UNAVAILABLE: 'A prova do Pull Request está indisponível.',
|
|
485
|
+
WENDKEEP_WORKTREE_PR_NOT_MERGED: 'O Pull Request não está merged.',
|
|
486
|
+
WENDKEEP_WORKTREE_PR_MISMATCH: 'O Pull Request não corresponde à branch gerenciada.',
|
|
487
|
+
WENDKEEP_WORKTREE_PR_MERGE_UNREACHABLE: 'O merge commit do Pull Request não está alcançável pela base local.',
|
|
488
|
+
WENDKEEP_WORKTREE_CLEANUP_BUSY: 'A limpeza da worktree já está em andamento.',
|
|
489
|
+
WENDKEEP_WORKTREE_REASON_REQUIRED: 'A remoção da worktree exige um motivo.',
|
|
490
|
+
WENDKEEP_WORKTREE_REMOTE_UNAVAILABLE: 'O estado da branch remota está indisponível.',
|
|
491
|
+
WENDKEEP_WORKTREE_REMOTE_DIVERGED: 'A branch remota divergiu do head comprovado.',
|
|
433
492
|
WENDKEEP_WORKTREE_USAGE: 'Uso inválido do comando worktree.',
|
|
434
493
|
},
|
|
435
494
|
};
|
|
@@ -464,17 +523,23 @@ function writeWorktreeResult(payload, { json, locale, action }) {
|
|
|
464
523
|
process.stdout.write(`${JSON.stringify({ ok: true, ...payload })}\n`);
|
|
465
524
|
return;
|
|
466
525
|
}
|
|
467
|
-
const subject = payload.worktree?.slug
|
|
526
|
+
const subject = payload.worktree?.slug
|
|
527
|
+
|| payload.receipt?.slug
|
|
528
|
+
|| `${payload.worktrees?.length ?? payload.actions?.length ?? 0}`;
|
|
468
529
|
const messages = locale === 'en'
|
|
469
|
-
? { create: `worktree created: ${subject}`, list: `managed worktrees: ${subject}`, status: `worktree status: ${subject}`, open: `worktree opened: ${subject}` }
|
|
470
|
-
: { create: `worktree criada: ${subject}`, list: `worktrees gerenciadas: ${subject}`, status: `status da worktree: ${subject}`, open: `worktree aberta: ${subject}` };
|
|
530
|
+
? { create: `worktree created: ${subject}`, list: `managed worktrees: ${subject}`, status: `worktree status: ${subject}`, open: `worktree opened: ${subject}`, finish: `worktree finished: ${subject}`, cleanup: `merged worktree cleanup: ${subject}`, remove: `worktree removed: ${subject}`, prune: `worktree prune: ${subject}` }
|
|
531
|
+
: { create: `worktree criada: ${subject}`, list: `worktrees gerenciadas: ${subject}`, status: `status da worktree: ${subject}`, open: `worktree aberta: ${subject}`, finish: `worktree finalizada: ${subject}`, cleanup: `limpeza de worktrees merged: ${subject}`, remove: `worktree removida: ${subject}`, prune: `prune de worktrees: ${subject}` };
|
|
471
532
|
const details = action === 'list'
|
|
472
533
|
? payload.worktrees.map(renderWorktreeLine)
|
|
473
|
-
: (action === 'status'
|
|
534
|
+
: (action === 'status'
|
|
535
|
+
? [renderWorktreeLine(payload.worktree)]
|
|
536
|
+
: (payload.actions || []).map((item) => [
|
|
537
|
+
`slug=${item.slug || ''}`, `path=${item.path || ''}`, `outcome=${item.outcome || item.action || ''}`,
|
|
538
|
+
].join(' ')));
|
|
474
539
|
process.stdout.write(`${messages[action]}${details.length ? `\n${details.join('\n')}` : ''}\n`);
|
|
475
540
|
}
|
|
476
541
|
|
|
477
|
-
export function runWorktree(argv = []) {
|
|
542
|
+
export async function runWorktree(argv = [], dependencies = {}) {
|
|
478
543
|
let parsed;
|
|
479
544
|
let locale = 'pt-BR';
|
|
480
545
|
try {
|
|
@@ -484,6 +549,9 @@ export function runWorktree(argv = []) {
|
|
|
484
549
|
const startDir = parsed.values.get('--project') || process.cwd();
|
|
485
550
|
locale = commandLocale(startDir);
|
|
486
551
|
const json = parsed.flags.has('--json');
|
|
552
|
+
if (parsed.flags.has('--dry-run') && parsed.flags.has('--apply')) {
|
|
553
|
+
throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'Use apenas --dry-run ou --apply.');
|
|
554
|
+
}
|
|
487
555
|
if (command === 'create') {
|
|
488
556
|
if (positionals.length !== 1) throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'create requer <slug>.');
|
|
489
557
|
const worktree = createManagedWorktree({
|
|
@@ -518,7 +586,50 @@ export function runWorktree(argv = []) {
|
|
|
518
586
|
writeWorktreeResult({ worktree }, { json, locale, action: 'open' });
|
|
519
587
|
return 0;
|
|
520
588
|
}
|
|
521
|
-
|
|
589
|
+
if (command === 'finish') {
|
|
590
|
+
if (positionals.length !== 1) throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'finish requer <slug>.');
|
|
591
|
+
const operation = dependencies.finishManagedWorktree || finishManagedWorktree;
|
|
592
|
+
const result = await operation({
|
|
593
|
+
startDir,
|
|
594
|
+
slug: positionals[0],
|
|
595
|
+
pullRequest: parsed.values.get('--pr') || '',
|
|
596
|
+
deleteRemote: parsed.flags.has('--delete-remote'),
|
|
597
|
+
});
|
|
598
|
+
if (parsed.flags.has('--open-main')) {
|
|
599
|
+
const main = discoverWorktreeRepository({ startDir }).mainWorktree;
|
|
600
|
+
const openMain = dependencies.openMain
|
|
601
|
+
|| ((path) => openWorktreePath(path, 'vscode', spawnSync));
|
|
602
|
+
openMain(main);
|
|
603
|
+
}
|
|
604
|
+
writeWorktreeResult(result, { json, locale, action: 'finish' });
|
|
605
|
+
return 0;
|
|
606
|
+
}
|
|
607
|
+
if (command === 'cleanup') {
|
|
608
|
+
if (positionals.length || !parsed.flags.has('--merged')) {
|
|
609
|
+
throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'cleanup exige --merged e não aceita slug.');
|
|
610
|
+
}
|
|
611
|
+
const operation = dependencies.cleanupMergedWorktrees || cleanupMergedWorktrees;
|
|
612
|
+
const result = await operation({ startDir, apply: parsed.flags.has('--apply') });
|
|
613
|
+
writeWorktreeResult(result, { json, locale, action: 'cleanup' });
|
|
614
|
+
return result.actions.some((item) => item.outcome === 'blocked') ? 2 : 0;
|
|
615
|
+
}
|
|
616
|
+
if (command === 'remove') {
|
|
617
|
+
if (positionals.length !== 1) throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'remove requer <slug>.');
|
|
618
|
+
const operation = dependencies.removeManagedWorktree || removeManagedWorktree;
|
|
619
|
+
const result = await operation({
|
|
620
|
+
startDir, slug: positionals[0], reason: parsed.values.get('--reason') || '',
|
|
621
|
+
});
|
|
622
|
+
writeWorktreeResult(result, { json, locale, action: 'remove' });
|
|
623
|
+
return 0;
|
|
624
|
+
}
|
|
625
|
+
if (command === 'prune') {
|
|
626
|
+
if (positionals.length) throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'prune não aceita slug.');
|
|
627
|
+
const operation = dependencies.pruneManagedWorktrees || pruneManagedWorktrees;
|
|
628
|
+
const result = await operation({ startDir, apply: parsed.flags.has('--apply') });
|
|
629
|
+
writeWorktreeResult(result, { json, locale, action: 'prune' });
|
|
630
|
+
return 0;
|
|
631
|
+
}
|
|
632
|
+
throw worktreeError('WENDKEEP_WORKTREE_USAGE', 'Use create, list, status, open, finish, cleanup, remove ou prune.');
|
|
522
633
|
} catch (error) {
|
|
523
634
|
process.stderr.write(`${renderWorktreeError(error, locale)}\n`);
|
|
524
635
|
return 2;
|