@lumenflow/cli 1.1.0 → 1.3.2
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/dist/__tests__/cli-entry-point.test.js +50 -0
- package/dist/__tests__/cli-subprocess.test.js +64 -0
- package/dist/cli-entry-point.js +46 -0
- package/dist/gates.js +102 -39
- package/dist/init.js +241 -195
- package/dist/initiative-add-wu.js +2 -1
- package/dist/initiative-create.js +5 -8
- package/dist/initiative-edit.js +3 -3
- package/dist/initiative-list.js +2 -1
- package/dist/initiative-status.js +2 -1
- package/dist/wu-claim.js +297 -110
- package/dist/wu-cleanup.js +129 -57
- package/dist/wu-create.js +197 -122
- package/dist/wu-deps.js +2 -1
- package/dist/wu-done.js +46 -14
- package/dist/wu-edit.js +152 -61
- package/dist/wu-infer-lane.js +5 -4
- package/dist/wu-preflight.js +2 -1
- package/dist/wu-prune.js +12 -3
- package/dist/wu-repair.js +2 -1
- package/dist/wu-spawn.js +79 -159
- package/dist/wu-unlock-lane.js +6 -1
- package/dist/wu-validate.js +2 -1
- package/package.json +14 -14
- package/dist/gates.d.ts +0 -41
- package/dist/gates.d.ts.map +0 -1
- package/dist/gates.js.map +0 -1
- package/dist/initiative-add-wu.d.ts +0 -22
- package/dist/initiative-add-wu.d.ts.map +0 -1
- package/dist/initiative-add-wu.js.map +0 -1
- package/dist/initiative-create.d.ts +0 -28
- package/dist/initiative-create.d.ts.map +0 -1
- package/dist/initiative-create.js.map +0 -1
- package/dist/initiative-edit.d.ts +0 -34
- package/dist/initiative-edit.d.ts.map +0 -1
- package/dist/initiative-edit.js.map +0 -1
- package/dist/initiative-list.d.ts +0 -12
- package/dist/initiative-list.d.ts.map +0 -1
- package/dist/initiative-list.js.map +0 -1
- package/dist/initiative-status.d.ts +0 -11
- package/dist/initiative-status.d.ts.map +0 -1
- package/dist/initiative-status.js.map +0 -1
- package/dist/mem-checkpoint.d.ts +0 -16
- package/dist/mem-checkpoint.d.ts.map +0 -1
- package/dist/mem-checkpoint.js.map +0 -1
- package/dist/mem-cleanup.d.ts +0 -29
- package/dist/mem-cleanup.d.ts.map +0 -1
- package/dist/mem-cleanup.js.map +0 -1
- package/dist/mem-create.d.ts +0 -17
- package/dist/mem-create.d.ts.map +0 -1
- package/dist/mem-create.js.map +0 -1
- package/dist/mem-inbox.d.ts +0 -35
- package/dist/mem-inbox.d.ts.map +0 -1
- package/dist/mem-inbox.js.map +0 -1
- package/dist/mem-init.d.ts +0 -15
- package/dist/mem-init.d.ts.map +0 -1
- package/dist/mem-init.js.map +0 -1
- package/dist/mem-ready.d.ts +0 -16
- package/dist/mem-ready.d.ts.map +0 -1
- package/dist/mem-ready.js.map +0 -1
- package/dist/mem-signal.d.ts +0 -16
- package/dist/mem-signal.d.ts.map +0 -1
- package/dist/mem-signal.js.map +0 -1
- package/dist/mem-start.d.ts +0 -16
- package/dist/mem-start.d.ts.map +0 -1
- package/dist/mem-start.js.map +0 -1
- package/dist/mem-summarize.d.ts +0 -22
- package/dist/mem-summarize.d.ts.map +0 -1
- package/dist/mem-summarize.js.map +0 -1
- package/dist/mem-triage.d.ts +0 -22
- package/dist/mem-triage.d.ts.map +0 -1
- package/dist/mem-triage.js.map +0 -1
- package/dist/spawn-list.d.ts +0 -16
- package/dist/spawn-list.d.ts.map +0 -1
- package/dist/spawn-list.js.map +0 -1
- package/dist/wu-block.d.ts +0 -16
- package/dist/wu-block.d.ts.map +0 -1
- package/dist/wu-block.js.map +0 -1
- package/dist/wu-claim.d.ts +0 -32
- package/dist/wu-claim.d.ts.map +0 -1
- package/dist/wu-claim.js.map +0 -1
- package/dist/wu-cleanup.d.ts +0 -17
- package/dist/wu-cleanup.d.ts.map +0 -1
- package/dist/wu-cleanup.js.map +0 -1
- package/dist/wu-create.d.ts +0 -38
- package/dist/wu-create.d.ts.map +0 -1
- package/dist/wu-create.js.map +0 -1
- package/dist/wu-deps.d.ts +0 -13
- package/dist/wu-deps.d.ts.map +0 -1
- package/dist/wu-deps.js.map +0 -1
- package/dist/wu-done.d.ts +0 -153
- package/dist/wu-done.d.ts.map +0 -1
- package/dist/wu-done.js.map +0 -1
- package/dist/wu-edit.d.ts +0 -29
- package/dist/wu-edit.d.ts.map +0 -1
- package/dist/wu-edit.js.map +0 -1
- package/dist/wu-infer-lane.d.ts +0 -17
- package/dist/wu-infer-lane.d.ts.map +0 -1
- package/dist/wu-infer-lane.js.map +0 -1
- package/dist/wu-preflight.d.ts +0 -47
- package/dist/wu-preflight.d.ts.map +0 -1
- package/dist/wu-preflight.js.map +0 -1
- package/dist/wu-prune.d.ts +0 -16
- package/dist/wu-prune.d.ts.map +0 -1
- package/dist/wu-prune.js.map +0 -1
- package/dist/wu-repair.d.ts +0 -60
- package/dist/wu-repair.d.ts.map +0 -1
- package/dist/wu-repair.js.map +0 -1
- package/dist/wu-spawn-completion.d.ts +0 -10
- package/dist/wu-spawn.d.ts +0 -168
- package/dist/wu-spawn.d.ts.map +0 -1
- package/dist/wu-spawn.js.map +0 -1
- package/dist/wu-unblock.d.ts +0 -16
- package/dist/wu-unblock.d.ts.map +0 -1
- package/dist/wu-unblock.js.map +0 -1
- package/dist/wu-validate.d.ts +0 -16
- package/dist/wu-validate.d.ts.map +0 -1
- package/dist/wu-validate.js.map +0 -1
package/dist/wu-claim.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Full migration to thin shim pending @lumenflow/core CLI export implementation.
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
18
|
-
import { access, readFile, writeFile } from 'node:fs/promises';
|
|
18
|
+
import { access, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { isOrphanWorktree } from '@lumenflow/core/dist/orphan-detector.js';
|
|
21
21
|
// WU-1352: Use centralized YAML functions from wu-yaml.mjs
|
|
@@ -32,14 +32,15 @@ import { getGitForCwd, createGitForPath } from '@lumenflow/core/dist/git-adapter
|
|
|
32
32
|
import { die } from '@lumenflow/core/dist/error-handler.js';
|
|
33
33
|
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/dist/arg-parser.js';
|
|
34
34
|
import { WU_PATHS, getStateStoreDirFromBacklog } from '@lumenflow/core/dist/wu-paths.js';
|
|
35
|
-
import { BRANCHES, REMOTES, WU_STATUS, CLAIMED_MODES, STATUS_SECTIONS, PATTERNS, toKebab, LOG_PREFIX, MICRO_WORKTREE_OPERATIONS, COMMIT_FORMATS, EMOJI, FILE_SYSTEM, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
35
|
+
import { BRANCHES, REMOTES, WU_STATUS, CLAIMED_MODES, STATUS_SECTIONS, PATTERNS, toKebab, LOG_PREFIX, GIT_REFS, MICRO_WORKTREE_OPERATIONS, COMMIT_FORMATS, EMOJI, FILE_SYSTEM, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
|
|
36
36
|
import { withMicroWorktree } from '@lumenflow/core/dist/micro-worktree.js';
|
|
37
|
-
import { ensureOnMain } from '@lumenflow/core/dist/wu-helpers.js';
|
|
37
|
+
import { ensureOnMain, ensureMainUpToDate } from '@lumenflow/core/dist/wu-helpers.js';
|
|
38
38
|
import { emitWUFlowEvent } from '@lumenflow/core/dist/telemetry.js';
|
|
39
39
|
import { checkLaneForOrphanDoneWU, repairWUInconsistency, } from '@lumenflow/core/dist/wu-consistency-checker.js';
|
|
40
40
|
import { emitMandatoryAgentAdvisory } from '@lumenflow/core/dist/orchestration-advisory-loader.js';
|
|
41
41
|
import { validateWU, generateAutoApproval } from '@lumenflow/core/dist/wu-schema.js';
|
|
42
42
|
import { startSessionForWU } from '@lumenflow/agent/dist/auto-session-integration.js';
|
|
43
|
+
import { getConfig } from '@lumenflow/core/dist/lumenflow-config.js';
|
|
43
44
|
import { detectFixableIssues, applyFixes, autoFixWUYaml, formatIssues, } from '@lumenflow/core/dist/wu-yaml-fixer.js';
|
|
44
45
|
import { validateSpecCompleteness } from '@lumenflow/core/dist/wu-done-validators.js';
|
|
45
46
|
import { getAssignedEmail } from '@lumenflow/core/dist/wu-claim-helpers.js';
|
|
@@ -164,7 +165,7 @@ function validateYAMLSchema(WU_PATH, doc, args) {
|
|
|
164
165
|
}
|
|
165
166
|
// WU-1576: validateBacklogConsistency removed - repair now happens inside micro-worktree
|
|
166
167
|
// See claimWorktreeMode() execute function for the new location
|
|
167
|
-
async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null) {
|
|
168
|
+
async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktreePath = null, sessionId = null, gitAdapter = null) {
|
|
168
169
|
// Check file exists
|
|
169
170
|
try {
|
|
170
171
|
await access(WU_PATH);
|
|
@@ -223,16 +224,16 @@ async function updateWUYaml(WU_PATH, id, lane, claimedMode = 'worktree', worktre
|
|
|
223
224
|
if (worktreePath) {
|
|
224
225
|
doc.worktree_path = worktreePath;
|
|
225
226
|
}
|
|
227
|
+
const git = gitAdapter || getGitForCwd();
|
|
226
228
|
// WU-1423: Record owner using validated email (no silent username fallback)
|
|
227
229
|
// Fallback chain: git config user.email > GIT_AUTHOR_EMAIL > error
|
|
228
230
|
// WU-1427: getAssignedEmail is now async to properly await gitAdapter.getConfigValue
|
|
229
|
-
doc.assigned_to = await getAssignedEmail(
|
|
231
|
+
doc.assigned_to = await getAssignedEmail(git);
|
|
230
232
|
// Record claim timestamp for duration tracking (WU-637)
|
|
231
233
|
doc.claimed_at = new Date().toISOString();
|
|
232
234
|
// WU-1382: Store baseline main SHA for parallel agent detection
|
|
233
235
|
// wu:done will compare against this to detect if other WUs were merged during work
|
|
234
|
-
|
|
235
|
-
doc.baseline_main_sha = await git.getCommitHash('origin/main');
|
|
236
|
+
doc.baseline_main_sha = await git.getCommitHash(GIT_REFS.ORIGIN_MAIN);
|
|
236
237
|
// WU-1438: Store agent session ID for tracking
|
|
237
238
|
if (sessionId) {
|
|
238
239
|
doc.session_id = sessionId;
|
|
@@ -357,6 +358,90 @@ export function getWorktreeCommitFiles(wuId) {
|
|
|
357
358
|
// These generated files cause merge conflicts when main advances
|
|
358
359
|
];
|
|
359
360
|
}
|
|
361
|
+
function parseStagedChangeLine(line) {
|
|
362
|
+
const parts = line.trim().split(/\s+/);
|
|
363
|
+
const status = parts[0];
|
|
364
|
+
if (!status)
|
|
365
|
+
return null;
|
|
366
|
+
if (status.startsWith('R') || status.startsWith('C')) {
|
|
367
|
+
return { status, from: parts[1], filePath: parts[2] };
|
|
368
|
+
}
|
|
369
|
+
return { status, filePath: parts.slice(1).join(' ') };
|
|
370
|
+
}
|
|
371
|
+
async function getStagedChanges() {
|
|
372
|
+
const diff = await getGitForCwd().raw(['diff', '--cached', '--name-status']);
|
|
373
|
+
if (!diff.trim())
|
|
374
|
+
return [];
|
|
375
|
+
return diff
|
|
376
|
+
.split(STRING_LITERALS.NEWLINE)
|
|
377
|
+
.filter(Boolean)
|
|
378
|
+
.map(parseStagedChangeLine)
|
|
379
|
+
.filter(Boolean);
|
|
380
|
+
}
|
|
381
|
+
async function applyStagedChangesToMicroWorktree(worktreePath, stagedChanges) {
|
|
382
|
+
for (const change of stagedChanges) {
|
|
383
|
+
const filePath = change.filePath;
|
|
384
|
+
if (!filePath)
|
|
385
|
+
continue;
|
|
386
|
+
const targetPath = path.join(worktreePath, filePath);
|
|
387
|
+
if (change.status.startsWith('D')) {
|
|
388
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const sourcePath = path.join(process.cwd(), filePath);
|
|
392
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool applies staged files
|
|
393
|
+
const contents = await readFile(sourcePath, { encoding: FILE_SYSTEM.UTF8 });
|
|
394
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
395
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool applies staged files
|
|
396
|
+
await writeFile(targetPath, contents, { encoding: FILE_SYSTEM.UTF8 });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Update canonical claim state on origin/main using push-only micro-worktree.
|
|
401
|
+
* Ensures canonical state stays global while local main remains unchanged.
|
|
402
|
+
*/
|
|
403
|
+
async function applyCanonicalClaimUpdate(ctx, sessionId) {
|
|
404
|
+
const { args, id, laneK, worktree, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, fixableIssues, stagedChanges, } = ctx;
|
|
405
|
+
const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
|
|
406
|
+
const worktreePathForYaml = claimedMode === CLAIMED_MODES.BRANCH_ONLY ? null : path.resolve(worktree);
|
|
407
|
+
let updatedTitle = '';
|
|
408
|
+
const filesToCommit = args.noAuto && stagedChanges.length > 0
|
|
409
|
+
? stagedChanges.map((change) => change.filePath).filter(Boolean)
|
|
410
|
+
: [WU_PATHS.WU(id), WU_PATHS.STATUS(), WU_PATHS.BACKLOG(), '.beacon/state/wu-events.jsonl'];
|
|
411
|
+
console.log(`${PREFIX} Updating canonical claim state (push-only)...`);
|
|
412
|
+
await withMicroWorktree({
|
|
413
|
+
operation: MICRO_WORKTREE_OPERATIONS.WU_CLAIM,
|
|
414
|
+
id,
|
|
415
|
+
logPrefix: PREFIX,
|
|
416
|
+
pushOnly: true,
|
|
417
|
+
execute: async ({ worktreePath }) => {
|
|
418
|
+
const microWUPath = path.join(worktreePath, WU_PATH);
|
|
419
|
+
const microStatusPath = path.join(worktreePath, STATUS_PATH);
|
|
420
|
+
const microBacklogPath = path.join(worktreePath, BACKLOG_PATH);
|
|
421
|
+
if (args.noAuto) {
|
|
422
|
+
await applyStagedChangesToMicroWorktree(worktreePath, stagedChanges);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
if (fixableIssues && fixableIssues.length > 0) {
|
|
426
|
+
console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
|
|
427
|
+
autoFixWUYaml(microWUPath);
|
|
428
|
+
console.log(`${PREFIX} YAML fixes applied successfully`);
|
|
429
|
+
}
|
|
430
|
+
const microGit = createGitForPath(worktreePath);
|
|
431
|
+
updatedTitle =
|
|
432
|
+
(await updateWUYaml(microWUPath, id, args.lane, claimedMode, worktreePathForYaml, sessionId, microGit)) || updatedTitle;
|
|
433
|
+
await addOrReplaceInProgressStatus(microStatusPath, id, updatedTitle);
|
|
434
|
+
await removeFromReadyAndAddToInProgressBacklog(microBacklogPath, id, updatedTitle, args.lane);
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
commitMessage: commitMsg,
|
|
438
|
+
files: filesToCommit,
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
console.log(`${PREFIX} Canonical claim state updated on origin/main`);
|
|
443
|
+
return updatedTitle;
|
|
444
|
+
}
|
|
360
445
|
async function readWUTitle(id) {
|
|
361
446
|
const p = WU_PATHS.WU(id);
|
|
362
447
|
// Check file exists
|
|
@@ -617,51 +702,44 @@ async function validateBranchOnlyMode(STATUS_PATH, id) {
|
|
|
617
702
|
* Execute branch-only mode claim workflow
|
|
618
703
|
*/
|
|
619
704
|
async function claimBranchOnlyMode(ctx) {
|
|
620
|
-
const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode } = ctx;
|
|
621
|
-
//
|
|
622
|
-
let sessionId = null;
|
|
705
|
+
const { args, id, laneK, title, branch, WU_PATH, STATUS_PATH, BACKLOG_PATH, claimedMode, sessionId, updatedTitle, } = ctx;
|
|
706
|
+
// Create branch and switch to it from origin/main (avoids local main mutation)
|
|
623
707
|
try {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
708
|
+
await getGitForCwd().createBranch(branch, `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
die(`Canonical claim state may be updated, but branch creation failed.\n\n` +
|
|
712
|
+
`Error: ${error.message}\n\n` +
|
|
713
|
+
`Recovery:\n` +
|
|
714
|
+
` 1. Run: git fetch ${REMOTES.ORIGIN} ${BRANCHES.MAIN}\n` +
|
|
715
|
+
` 2. Retry: pnpm wu:claim --id ${id} --lane "${args.lane}"\n` +
|
|
716
|
+
` 3. If needed, delete local branch: git branch -D ${branch}`);
|
|
717
|
+
}
|
|
718
|
+
let finalTitle = updatedTitle || title;
|
|
719
|
+
const msg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
|
|
720
|
+
if (args.noPush) {
|
|
721
|
+
if (args.noAuto) {
|
|
722
|
+
await ensureCleanOrClaimOnlyWhenNoAuto();
|
|
631
723
|
}
|
|
632
724
|
else {
|
|
633
|
-
|
|
725
|
+
finalTitle =
|
|
726
|
+
(await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId)) || finalTitle;
|
|
727
|
+
await addOrReplaceInProgressStatus(STATUS_PATH, id, finalTitle);
|
|
728
|
+
await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, finalTitle, args.lane);
|
|
729
|
+
await getGitForCwd().add(`${JSON.stringify(WU_PATH)} ${JSON.stringify(STATUS_PATH)} ${JSON.stringify(BACKLOG_PATH)}`);
|
|
634
730
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
// Non-blocking: session start failure should not block claim
|
|
638
|
-
console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
|
|
639
|
-
}
|
|
640
|
-
// Create branch and switch to it (LEGACY - for constrained environments only)
|
|
641
|
-
await getGitForCwd().createBranch(branch, BRANCHES.MAIN);
|
|
642
|
-
// Update metadata in branch-only mode (on main checkout)
|
|
643
|
-
let updatedTitle = title;
|
|
644
|
-
if (args.noAuto) {
|
|
645
|
-
await ensureCleanOrClaimOnlyWhenNoAuto();
|
|
731
|
+
await getGitForCwd().commit(msg);
|
|
732
|
+
console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
|
|
646
733
|
}
|
|
647
734
|
else {
|
|
648
|
-
|
|
649
|
-
(await updateWUYaml(WU_PATH, id, args.lane, claimedMode, null, sessionId)) || title;
|
|
650
|
-
await addOrReplaceInProgressStatus(STATUS_PATH, id, updatedTitle);
|
|
651
|
-
await removeFromReadyAndAddToInProgressBacklog(BACKLOG_PATH, id, updatedTitle, args.lane);
|
|
652
|
-
await getGitForCwd().add(`${JSON.stringify(WU_PATH)} ${JSON.stringify(STATUS_PATH)} ${JSON.stringify(BACKLOG_PATH)}`);
|
|
735
|
+
await getGitForCwd().push(REMOTES.ORIGIN, branch, { setUpstream: true });
|
|
653
736
|
}
|
|
654
|
-
// Commit and push
|
|
655
|
-
const msg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
|
|
656
|
-
await getGitForCwd().commit(msg);
|
|
657
|
-
await getGitForCwd().push(REMOTES.ORIGIN, branch);
|
|
658
737
|
// Summary
|
|
659
738
|
console.log(`\n${PREFIX} Claim recorded in Branch-Only mode.`);
|
|
660
|
-
console.log(`- WU: ${id}${
|
|
739
|
+
console.log(`- WU: ${id}${finalTitle ? ` — ${finalTitle}` : ''}`);
|
|
661
740
|
console.log(`- Lane: ${args.lane}`);
|
|
662
741
|
console.log(`- Mode: Branch-Only (no worktree)`);
|
|
663
|
-
console.log(`- Commit: ${msg}`);
|
|
664
|
-
console.log(`- Branch: ${branch}`);
|
|
742
|
+
console.log(`${args.noPush ? `- Commit: ${msg}` : `- Branch: ${branch}`}`);
|
|
665
743
|
console.log('\n⚠️ LIMITATION: Branch-Only mode does not support parallel WUs (WIP=1 across ALL lanes)');
|
|
666
744
|
console.log('Next: work on this branch in the main checkout.');
|
|
667
745
|
// WU-1360: Print next-steps checklist to prevent common mistakes
|
|
@@ -706,97 +784,95 @@ async function claimBranchOnlyMode(ctx) {
|
|
|
706
784
|
*/
|
|
707
785
|
async function claimWorktreeMode(ctx) {
|
|
708
786
|
const { args, id, laneK, title, branch, worktree, WU_PATH, BACKLOG_PATH, claimedMode, fixableIssues, // Fixable issues from pre-flight validation
|
|
709
|
-
|
|
787
|
+
sessionId, updatedTitle, stagedChanges, } = ctx;
|
|
710
788
|
const originalCwd = process.cwd();
|
|
711
789
|
const worktreePath = path.resolve(worktree);
|
|
712
|
-
let
|
|
790
|
+
let finalTitle = updatedTitle || title;
|
|
713
791
|
const commitMsg = COMMIT_FORMATS.CLAIM(id.toLowerCase(), laneK);
|
|
714
|
-
// WU-1438: Start agent session BEFORE metadata update to include session_id in YAML
|
|
715
|
-
let sessionId = null;
|
|
716
|
-
try {
|
|
717
|
-
const sessionResult = await startSessionForWU({
|
|
718
|
-
wuId: id,
|
|
719
|
-
tier: 2,
|
|
720
|
-
});
|
|
721
|
-
sessionId = sessionResult.sessionId;
|
|
722
|
-
if (sessionResult.alreadyActive) {
|
|
723
|
-
console.log(`${PREFIX} Agent session already active (${sessionId.slice(0, 8)}...)`);
|
|
724
|
-
}
|
|
725
|
-
else {
|
|
726
|
-
console.log(`${PREFIX} ${EMOJI.SUCCESS} Agent session started (${sessionId.slice(0, 8)}...)`);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
catch (err) {
|
|
730
|
-
// Non-blocking: session start failure should not block claim
|
|
731
|
-
console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
|
|
732
|
-
}
|
|
733
792
|
// WU-1741: Step 1 - Create work worktree+branch from main
|
|
734
793
|
// Branch creation IS the coordination lock (git prevents duplicate branch names)
|
|
735
794
|
console.log(`${PREFIX} Creating worktree (branch = coordination lock)...`);
|
|
736
|
-
|
|
795
|
+
const startPoint = args.noPush ? BRANCHES.MAIN : `${REMOTES.ORIGIN}/${BRANCHES.MAIN}`;
|
|
796
|
+
await getGitForCwd().worktreeAdd(worktree, branch, startPoint);
|
|
737
797
|
console.log(`${PREFIX} ${EMOJI.SUCCESS} Worktree created at ${worktree}`);
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
//
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
798
|
+
if (!args.noPush) {
|
|
799
|
+
const wtGit = createGitForPath(worktreePath);
|
|
800
|
+
await wtGit.push(REMOTES.ORIGIN, branch, { setUpstream: true });
|
|
801
|
+
}
|
|
802
|
+
if (args.noPush) {
|
|
803
|
+
// Local-only claim (air-gapped) - update metadata inside worktree branch
|
|
804
|
+
if (args.noAuto) {
|
|
805
|
+
await applyStagedChangesToMicroWorktree(worktreePath, stagedChanges);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
const wtWUPath = path.join(worktreePath, WU_PATH);
|
|
809
|
+
const wtBacklogPath = path.join(worktreePath, BACKLOG_PATH);
|
|
810
|
+
if (fixableIssues && fixableIssues.length > 0) {
|
|
811
|
+
console.log(`${PREFIX} Applying ${fixableIssues.length} YAML fix(es)...`);
|
|
812
|
+
autoFixWUYaml(wtWUPath);
|
|
813
|
+
console.log(`${PREFIX} YAML fixes applied successfully`);
|
|
814
|
+
}
|
|
815
|
+
finalTitle =
|
|
816
|
+
(await updateWUYaml(wtWUPath, id, args.lane, claimedMode, worktree, sessionId)) ||
|
|
817
|
+
finalTitle;
|
|
818
|
+
// WU-1746: Only append claim event to state store - don't regenerate backlog.md/status.md
|
|
819
|
+
const wtStateDir = getStateStoreDirFromBacklog(wtBacklogPath);
|
|
820
|
+
await appendClaimEventOnly(wtStateDir, id, finalTitle, args.lane);
|
|
748
821
|
}
|
|
749
|
-
// Update metadata files in worktree (WU-1438: include session_id)
|
|
750
|
-
updatedTitle =
|
|
751
|
-
(await updateWUYaml(wtWUPath, id, args.lane, claimedMode, worktree, sessionId)) || title;
|
|
752
|
-
// WU-1746: Only append claim event to state store - don't regenerate backlog.md/status.md
|
|
753
|
-
// These generated files cause merge conflicts when committed to worktrees
|
|
754
|
-
const wtStateDir = getStateStoreDirFromBacklog(wtBacklogPath);
|
|
755
|
-
await appendClaimEventOnly(wtStateDir, id, updatedTitle, args.lane);
|
|
756
|
-
// WU-1741: Step 3 - Commit metadata in worktree (NOT on main)
|
|
757
|
-
// This commit stays on the lane branch until wu:done merges to main
|
|
758
822
|
console.log(`${PREFIX} Committing claim metadata in worktree...`);
|
|
759
823
|
const wtGit = createGitForPath(worktreePath);
|
|
760
|
-
// WU-1746: Use getWorktreeCommitFiles which excludes backlog.md and status.md
|
|
761
824
|
const filesToCommit = getWorktreeCommitFiles(id);
|
|
762
825
|
await wtGit.add(filesToCommit);
|
|
763
826
|
await wtGit.commit(commitMsg);
|
|
764
827
|
console.log(`${PREFIX} ${EMOJI.SUCCESS} Claim committed: ${commitMsg}`);
|
|
828
|
+
console.warn(`${PREFIX} Warning: --no-push enabled. Claim is local-only and NOT visible to other agents.`);
|
|
765
829
|
}
|
|
766
|
-
// WU-
|
|
767
|
-
//
|
|
768
|
-
|
|
769
|
-
if (
|
|
770
|
-
|
|
830
|
+
// WU-1023: Auto-setup worktree dependencies
|
|
831
|
+
// By default, run pnpm install to ensure all dependencies are built (including CLI dist)
|
|
832
|
+
// Use --skip-setup to use symlink-only approach for faster claims when deps are already built
|
|
833
|
+
if (args.skipSetup) {
|
|
834
|
+
// WU-1443: Symlink-only mode for fast claims
|
|
835
|
+
// WU-2238: Pass mainRepoPath to detect broken worktree-path symlinks
|
|
836
|
+
const symlinkResult = symlinkNodeModules(worktreePath, console, originalCwd);
|
|
837
|
+
if (symlinkResult.created) {
|
|
838
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked (--skip-setup mode)`);
|
|
839
|
+
}
|
|
840
|
+
else if (symlinkResult.refused) {
|
|
841
|
+
console.warn(`${PREFIX} Warning: symlink refused: ${symlinkResult.reason}`);
|
|
842
|
+
console.warn(`${PREFIX} Run 'pnpm install' manually in the worktree`);
|
|
843
|
+
}
|
|
844
|
+
// WU-1579: Auto-symlink nested package node_modules for turbo typecheck
|
|
845
|
+
if (!symlinkResult.refused) {
|
|
846
|
+
const nestedResult = symlinkNestedNodeModules(worktreePath, originalCwd);
|
|
847
|
+
if (nestedResult.created > 0) {
|
|
848
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
771
851
|
}
|
|
772
|
-
else
|
|
773
|
-
// WU-
|
|
774
|
-
//
|
|
775
|
-
console.log(`${PREFIX}
|
|
852
|
+
else {
|
|
853
|
+
// WU-1023: Full setup mode (default) - run pnpm install with progress indicator
|
|
854
|
+
// This ensures CLI dist is built and all dependencies are properly resolved
|
|
855
|
+
console.log(`${PREFIX} Installing worktree dependencies (this may take a moment)...`);
|
|
776
856
|
try {
|
|
777
857
|
const { execSync } = await import('node:child_process');
|
|
778
858
|
execSync('pnpm install --frozen-lockfile', {
|
|
779
859
|
cwd: worktreePath,
|
|
780
|
-
stdio: 'inherit',
|
|
781
|
-
timeout:
|
|
860
|
+
stdio: 'inherit', // Shows progress output from pnpm
|
|
861
|
+
timeout: 300000, // 5 minute timeout for full install
|
|
782
862
|
});
|
|
783
|
-
console.log(`${PREFIX} ${EMOJI.SUCCESS}
|
|
863
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} Worktree dependencies installed`);
|
|
784
864
|
}
|
|
785
865
|
catch (installError) {
|
|
866
|
+
// Non-fatal: warn but don't block claim
|
|
786
867
|
console.warn(`${PREFIX} Warning: pnpm install failed: ${installError.message}`);
|
|
787
868
|
console.warn(`${PREFIX} You may need to run 'pnpm install' manually in the worktree`);
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
// WU-2238: Skip nested symlinks if root symlink was refused (pnpm install handles them)
|
|
792
|
-
if (!symlinkResult.refused) {
|
|
793
|
-
const nestedResult = symlinkNestedNodeModules(worktreePath, originalCwd);
|
|
794
|
-
if (nestedResult.created > 0) {
|
|
795
|
-
console.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
|
|
869
|
+
// Fall back to symlink approach so worktree is at least somewhat usable
|
|
870
|
+
console.log(`${PREFIX} Falling back to symlink approach...`);
|
|
871
|
+
applyFallbackSymlinks(worktreePath, originalCwd, console);
|
|
796
872
|
}
|
|
797
873
|
}
|
|
798
874
|
console.log(`${PREFIX} Claim recorded in worktree`);
|
|
799
|
-
console.log(`- WU: ${id}${
|
|
875
|
+
console.log(`- WU: ${id}${finalTitle ? ` — ${finalTitle}` : ''}`);
|
|
800
876
|
console.log(`- Lane: ${args.lane}`);
|
|
801
877
|
console.log(`- Worktree: ${worktreePath}`);
|
|
802
878
|
console.log(`- Branch: ${branch}`);
|
|
@@ -827,9 +903,44 @@ async function claimWorktreeMode(ctx) {
|
|
|
827
903
|
const wuDoc = parseYAML(wuContent);
|
|
828
904
|
const codePaths = wuDoc.code_paths || [];
|
|
829
905
|
emitMandatoryAgentAdvisory(codePaths, id);
|
|
906
|
+
// WU-1047: Emit agent-only project defaults from config
|
|
907
|
+
const config = getConfig();
|
|
908
|
+
printProjectDefaults(config?.agents?.methodology);
|
|
830
909
|
// WU-1763: Print lifecycle nudge with tips for tool adoption
|
|
831
910
|
printLifecycleNudge(id);
|
|
832
911
|
}
|
|
912
|
+
/**
|
|
913
|
+
* WU-1047: Format Project Defaults section (agent-only).
|
|
914
|
+
*
|
|
915
|
+
* @param {object} methodology - Methodology defaults config
|
|
916
|
+
* @returns {string} Formatted output or empty string if disabled
|
|
917
|
+
*/
|
|
918
|
+
export function formatProjectDefaults(methodology) {
|
|
919
|
+
if (!methodology || methodology.enabled === false)
|
|
920
|
+
return '';
|
|
921
|
+
const enforcement = methodology.enforcement || 'required';
|
|
922
|
+
const principles = Array.isArray(methodology.principles) ? methodology.principles : [];
|
|
923
|
+
const lines = [
|
|
924
|
+
`${PREFIX} 🧭 Project Defaults (agent-only)`,
|
|
925
|
+
` Enforcement: ${enforcement}`,
|
|
926
|
+
` Principles: ${principles.length > 0 ? principles.join(', ') : 'None'}`,
|
|
927
|
+
];
|
|
928
|
+
if (methodology.notes) {
|
|
929
|
+
lines.push(` Notes: ${methodology.notes}`);
|
|
930
|
+
}
|
|
931
|
+
return `\n${lines.join('\n')}`;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* WU-1047: Print Project Defaults section (agent-only).
|
|
935
|
+
*
|
|
936
|
+
* @param {object} methodology - Methodology defaults config
|
|
937
|
+
*/
|
|
938
|
+
export function printProjectDefaults(methodology) {
|
|
939
|
+
const output = formatProjectDefaults(methodology);
|
|
940
|
+
if (output) {
|
|
941
|
+
console.log(output);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
833
944
|
/**
|
|
834
945
|
* WU-1763: Print a single concise tips line to improve tool adoption.
|
|
835
946
|
* Non-blocking, single-line output to avoid flooding the console.
|
|
@@ -840,6 +951,27 @@ export function printLifecycleNudge(_id) {
|
|
|
840
951
|
// Single line, concise, actionable
|
|
841
952
|
console.log(`\n${PREFIX} 💡 Tip: pnpm session:recommend for context tier, mem:ready for pending work, pnpm file:*/git:* for audited wrappers`);
|
|
842
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* WU-1029: Apply symlink fallback (root + nested node_modules) after install failure.
|
|
956
|
+
*
|
|
957
|
+
* @param {string} worktreePath - Worktree path
|
|
958
|
+
* @param {string} mainRepoPath - Main repo path
|
|
959
|
+
* @param {Console} logger - Logger (console-compatible)
|
|
960
|
+
*/
|
|
961
|
+
export function applyFallbackSymlinks(worktreePath, mainRepoPath, logger = console) {
|
|
962
|
+
const symlinkResult = symlinkNodeModules(worktreePath, logger, mainRepoPath);
|
|
963
|
+
if (symlinkResult.created) {
|
|
964
|
+
logger.log(`${PREFIX} ${EMOJI.SUCCESS} node_modules symlinked as fallback`);
|
|
965
|
+
}
|
|
966
|
+
let nestedResult = null;
|
|
967
|
+
if (!symlinkResult.refused) {
|
|
968
|
+
nestedResult = symlinkNestedNodeModules(worktreePath, mainRepoPath);
|
|
969
|
+
if (nestedResult.created > 0) {
|
|
970
|
+
logger.log(`${PREFIX} ${EMOJI.SUCCESS} ${nestedResult.created} nested node_modules symlinked for typecheck`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
return { symlinkResult, nestedResult };
|
|
974
|
+
}
|
|
843
975
|
/**
|
|
844
976
|
* WU-2411: Handle --resume flag for agent handoff
|
|
845
977
|
*
|
|
@@ -929,6 +1061,8 @@ async function main() {
|
|
|
929
1061
|
WU_OPTIONS.reason,
|
|
930
1062
|
WU_OPTIONS.allowIncomplete,
|
|
931
1063
|
WU_OPTIONS.resume, // WU-2411: Agent handoff flag
|
|
1064
|
+
WU_OPTIONS.skipSetup, // WU-1023: Skip auto-setup for fast claims
|
|
1065
|
+
WU_OPTIONS.noPush, // Skip pushing claim state/branch (air-gapped)
|
|
932
1066
|
],
|
|
933
1067
|
required: ['id', 'lane'],
|
|
934
1068
|
allowPositionalId: true,
|
|
@@ -954,9 +1088,19 @@ async function main() {
|
|
|
954
1088
|
` 3. Use --no-auto if you already staged claim edits manually`);
|
|
955
1089
|
}
|
|
956
1090
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1091
|
+
let stagedChanges = [];
|
|
1092
|
+
if (args.noAuto) {
|
|
1093
|
+
await ensureCleanOrClaimOnlyWhenNoAuto();
|
|
1094
|
+
stagedChanges = await getStagedChanges();
|
|
1095
|
+
}
|
|
1096
|
+
// WU-1361: Fetch latest remote before validation (no local main mutation)
|
|
1097
|
+
if (!args.noPush) {
|
|
1098
|
+
await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
1099
|
+
await ensureMainUpToDate(getGitForCwd(), 'wu:claim');
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
console.warn(`${PREFIX} Warning: --no-push enabled. Skipping origin/main sync; local state may be stale.`);
|
|
1103
|
+
}
|
|
960
1104
|
const WU_PATH = WU_PATHS.WU(id);
|
|
961
1105
|
const STATUS_PATH = WU_PATHS.STATUS();
|
|
962
1106
|
const BACKLOG_PATH = WU_PATHS.BACKLOG();
|
|
@@ -1050,7 +1194,18 @@ async function main() {
|
|
|
1050
1194
|
if (args.branchOnly) {
|
|
1051
1195
|
await validateBranchOnlyMode(STATUS_PATH, id);
|
|
1052
1196
|
}
|
|
1053
|
-
// Check if branch already exists (prevents duplicate claims)
|
|
1197
|
+
// Check if remote branch already exists (prevents duplicate global claims)
|
|
1198
|
+
if (!args.noPush) {
|
|
1199
|
+
const remoteExists = await getGitForCwd().remoteBranchExists(REMOTES.ORIGIN, branch);
|
|
1200
|
+
if (remoteExists) {
|
|
1201
|
+
die(`Remote branch ${REMOTES.ORIGIN}/${branch} already exists. WU may already be claimed.\n\n` +
|
|
1202
|
+
`Options:\n` +
|
|
1203
|
+
` 1. Coordinate with the owning agent or wait for completion\n` +
|
|
1204
|
+
` 2. Choose a different WU\n` +
|
|
1205
|
+
` 3. Use --no-push for local-only claims (offline)`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
// Check if branch already exists locally (prevents duplicate claims)
|
|
1054
1209
|
const branchAlreadyExists = await getGitForCwd().branchExists(branch);
|
|
1055
1210
|
if (branchAlreadyExists) {
|
|
1056
1211
|
die(`Branch ${branch} already exists. WU may already be claimed.\n\n` +
|
|
@@ -1075,8 +1230,27 @@ async function main() {
|
|
|
1075
1230
|
`Manual cleanup: rm -rf ${absoluteWorktreePath}`);
|
|
1076
1231
|
}
|
|
1077
1232
|
}
|
|
1233
|
+
// WU-1438: Start agent session BEFORE metadata update to include session_id in YAML
|
|
1234
|
+
let sessionId = null;
|
|
1235
|
+
try {
|
|
1236
|
+
const sessionResult = await startSessionForWU({
|
|
1237
|
+
wuId: id,
|
|
1238
|
+
tier: 2,
|
|
1239
|
+
});
|
|
1240
|
+
sessionId = sessionResult.sessionId;
|
|
1241
|
+
if (sessionResult.alreadyActive) {
|
|
1242
|
+
console.log(`${PREFIX} Agent session already active (${sessionId.slice(0, 8)}...)`);
|
|
1243
|
+
}
|
|
1244
|
+
else {
|
|
1245
|
+
console.log(`${PREFIX} ${EMOJI.SUCCESS} Agent session started (${sessionId.slice(0, 8)}...)`);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
catch (err) {
|
|
1249
|
+
// Non-blocking: session start failure should not block claim
|
|
1250
|
+
console.warn(`${PREFIX} Warning: Could not start agent session: ${err.message}`);
|
|
1251
|
+
}
|
|
1078
1252
|
// Execute claim workflow
|
|
1079
|
-
const
|
|
1253
|
+
const baseCtx = {
|
|
1080
1254
|
args,
|
|
1081
1255
|
id,
|
|
1082
1256
|
laneK,
|
|
@@ -1088,6 +1262,18 @@ async function main() {
|
|
|
1088
1262
|
BACKLOG_PATH,
|
|
1089
1263
|
claimedMode,
|
|
1090
1264
|
fixableIssues, // WU-1361: Pass fixable issues for worktree application
|
|
1265
|
+
stagedChanges,
|
|
1266
|
+
};
|
|
1267
|
+
let updatedTitle = title;
|
|
1268
|
+
if (!args.noPush) {
|
|
1269
|
+
updatedTitle = (await applyCanonicalClaimUpdate(baseCtx, sessionId)) || updatedTitle;
|
|
1270
|
+
// Refresh origin/main after push-only update so worktrees start from canonical state
|
|
1271
|
+
await getGitForCwd().fetch(REMOTES.ORIGIN, BRANCHES.MAIN);
|
|
1272
|
+
}
|
|
1273
|
+
const ctx = {
|
|
1274
|
+
...baseCtx,
|
|
1275
|
+
sessionId,
|
|
1276
|
+
updatedTitle,
|
|
1091
1277
|
};
|
|
1092
1278
|
if (args.branchOnly) {
|
|
1093
1279
|
await claimBranchOnlyMode(ctx);
|
|
@@ -1112,6 +1298,7 @@ async function main() {
|
|
|
1112
1298
|
}
|
|
1113
1299
|
// Guard main() for testability (WU-1366)
|
|
1114
1300
|
import { fileURLToPath } from 'node:url';
|
|
1301
|
+
import { runCLI } from './cli-entry-point.js';
|
|
1115
1302
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
1116
|
-
main
|
|
1303
|
+
runCLI(main);
|
|
1117
1304
|
}
|