maskweaver 0.8.2 → 0.8.4

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.
Files changed (36) hide show
  1. package/README.ko.md +64 -47
  2. package/README.md +63 -46
  3. package/assets/commands/weave-craft.md +26 -398
  4. package/assets/commands/weave-flow.md +13 -30
  5. package/assets/commands/weave-help.md +4 -4
  6. package/assets/commands/weave-prepare.md +1 -1
  7. package/assets/commands/weave-refine-plan.md +0 -1
  8. package/assets/commands/weave-status.md +2 -7
  9. package/dist/plugin/tools/slashcommand.js +6 -6
  10. package/dist/plugin/tools/slashcommand.js.map +1 -1
  11. package/dist/plugin/tools/weave.d.ts +0 -2
  12. package/dist/plugin/tools/weave.d.ts.map +1 -1
  13. package/dist/plugin/tools/weave.js +316 -826
  14. package/dist/plugin/tools/weave.js.map +1 -1
  15. package/dist/shared/config.d.ts +14 -0
  16. package/dist/shared/config.d.ts.map +1 -1
  17. package/dist/shared/config.js +7 -0
  18. package/dist/shared/config.js.map +1 -1
  19. package/dist/shared-context/test/squad.test.js +2 -2
  20. package/dist/shared-context/test/squad.test.js.map +1 -1
  21. package/dist/shared-context/test/storage.test.js +3 -3
  22. package/dist/shared-context/test/storage.test.js.map +1 -1
  23. package/dist/shared-context/test/task.test.js +6 -6
  24. package/dist/shared-context/test/task.test.js.map +1 -1
  25. package/dist/weave/gdc.d.ts +59 -0
  26. package/dist/weave/gdc.d.ts.map +1 -0
  27. package/dist/weave/gdc.js +221 -0
  28. package/dist/weave/gdc.js.map +1 -0
  29. package/dist/weave/stages/execute.d.ts.map +1 -1
  30. package/dist/weave/stages/execute.js +6 -7
  31. package/dist/weave/stages/execute.js.map +1 -1
  32. package/dist/weave/stages/research.d.ts +1 -1
  33. package/dist/weave/stages/research.d.ts.map +1 -1
  34. package/dist/weave/stages/research.js +225 -5
  35. package/dist/weave/stages/research.js.map +1 -1
  36. package/package.json +8 -2
@@ -20,10 +20,10 @@ import { generateStatusReport, handleUserResponse } from '../../weave/stages/han
20
20
  import { getPhaseManager } from '../../weave/phase-manager.js';
21
21
  import { recommendVerificationCommands, formatRecommendedCommandsAsBash } from '../../weave/verification/index.js';
22
22
  import { createWeaveWorktree, listWeaveWorktrees, resolveWeaveWorktree, removeWeaveWorktree } from '../../weave/worktree.js';
23
- import { ensureGitRepo, stageAllChanges, listStagedFiles, hasStagedChanges, getWorkingTreeStatus, commitStagedChanges } from '../../weave/git.js';
23
+ import { ensureGitRepo, stageAllChanges, listStagedFiles, hasStagedChanges, commitStagedChanges } from '../../weave/git.js';
24
24
  import { scanFilesForSecrets, loadSecretScanConfig, shouldBlockOnFindings, formatSecretScanReport } from '../../weave/security/secret-scan.js';
25
25
  import { searchTroubleshooting, recordTroubleshooting, GlobalKnowledge } from '../../weave/knowledge/global.js';
26
- import { getOrchestrator } from '../../weave/orchestrator.js';
26
+ import { getEffectiveGdcConfig, runGdcMachineCommand, countGdcCheckIssues, } from '../../weave/gdc.js';
27
27
  // ============================================================================
28
28
  // Tool Factory
29
29
  // ============================================================================
@@ -38,8 +38,8 @@ Commands:
38
38
  - prepare [docsPath]: Create research + spec + plan with defaults (auto-splits oversized plans)
39
39
  - refine-plan: Apply annotation notes to active plan
40
40
  - approve-plan: Mark plan reviewed/approved before implementation
41
- - flow [docsPath]: One-command path (prepare -> approve-plan gate -> craft auto-loop + auto finalize)
42
- - craft [phaseId]: Execute next phase automatically if omitted (includes auto task loop + goal check + auto finalize)
41
+ - flow [docsPath]: One-command path (prepare -> auto-approve -> craft -> verify -> finalize)
42
+ - craft [phaseId]: Prepare execution context for a phase (phase auto-select if omitted)
43
43
  - status: View overall progress
44
44
  - worktree: Manage git worktrees for parallel work
45
45
  - verify: Run build/test verification for current worktree
@@ -99,10 +99,8 @@ Examples:
99
99
  .describe('Stage all changes before commit (default: false)'),
100
100
  commitMessage: z.string().optional()
101
101
  .describe('Commit message (optional)'),
102
- taskId: z.string().optional()
103
- .describe('Preferred task ID when resuming craft auto-loop'),
104
102
  verify: z.boolean().optional()
105
- .describe('Run verification as part of craft auto-loop (default: true)'),
103
+ .describe('Run verification (for verify/flow paths)'),
106
104
  error: z.string().optional()
107
105
  .describe('Error message to search solutions for (for troubleshoot command)'),
108
106
  solution: z.string().optional()
@@ -295,6 +293,174 @@ function formatRefinePlanResult(title, outcome) {
295
293
  }
296
294
  return lines.join('\n');
297
295
  }
296
+ function pickLogTail(text, maxLength = 180) {
297
+ if (!text)
298
+ return '';
299
+ const lines = text.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
300
+ if (lines.length === 0)
301
+ return '';
302
+ return sanitizeLessonText(lines[lines.length - 1], maxLength);
303
+ }
304
+ function formatGdcCommandLine(label, result) {
305
+ const parts = [];
306
+ parts.push(result.exitCode === 0 ? 'PASS' : `FAIL(exit=${result.exitCode})`);
307
+ if (result.timedOut)
308
+ parts.push('timeout');
309
+ if (result.transportError)
310
+ parts.push(sanitizeLessonText(result.transportError, 140));
311
+ if (result.parseError)
312
+ parts.push(`parse=${sanitizeLessonText(result.parseError, 120)}`);
313
+ const detail = pickLogTail(result.stderr || result.stdout);
314
+ if (detail)
315
+ parts.push(`detail=${detail}`);
316
+ return `- ${label}: ${parts.join(' | ')}`;
317
+ }
318
+ async function runGdcVerifyGate(basePath) {
319
+ const gdc = getEffectiveGdcConfig(basePath);
320
+ if (!gdc.enabled) {
321
+ return {
322
+ applied: false,
323
+ passed: true,
324
+ report: '',
325
+ };
326
+ }
327
+ const lines = [];
328
+ lines.push('### GDC Pre-Verify Gate');
329
+ lines.push('');
330
+ lines.push(`- mode: ${gdc.strictVerify ? 'strict' : 'lenient'}`);
331
+ lines.push(`- binary: \`${gdc.binPath}\``);
332
+ const syncResult = await runGdcMachineCommand({
333
+ basePath,
334
+ command: 'sync',
335
+ config: gdc,
336
+ });
337
+ lines.push(formatGdcCommandLine('sync', syncResult));
338
+ const syncInfraFailure = Boolean(syncResult.transportError)
339
+ || (syncResult.exitCode !== 0 && syncResult.exitCode !== 2);
340
+ const syncMachineParseFailure = Boolean(syncResult.parseError && syncResult.exitCode === 0);
341
+ if (syncInfraFailure || syncMachineParseFailure) {
342
+ if (gdc.strictVerify) {
343
+ lines.push('');
344
+ lines.push('❌ Strict mode blocks verification when GDC sync cannot be trusted.');
345
+ return {
346
+ applied: true,
347
+ passed: false,
348
+ failedAt: 'GDC Sync',
349
+ report: lines.join('\n'),
350
+ };
351
+ }
352
+ lines.push('');
353
+ lines.push('⚠️ Proceeding without strict GDC sync gate (lenient mode).');
354
+ return {
355
+ applied: true,
356
+ passed: true,
357
+ report: lines.join('\n'),
358
+ };
359
+ }
360
+ const checkResult = await runGdcMachineCommand({
361
+ basePath,
362
+ command: 'check',
363
+ config: gdc,
364
+ });
365
+ lines.push(formatGdcCommandLine('check', checkResult));
366
+ if (checkResult.exitCode === 2) {
367
+ lines.push('');
368
+ lines.push('❌ Blocking GDC check issues detected (exit code 2).');
369
+ return {
370
+ applied: true,
371
+ passed: false,
372
+ failedAt: 'GDC Check',
373
+ report: lines.join('\n'),
374
+ };
375
+ }
376
+ const checkInfraFailure = Boolean(checkResult.transportError)
377
+ || (checkResult.exitCode !== 0 && checkResult.exitCode !== 2);
378
+ if (checkInfraFailure) {
379
+ if (gdc.strictVerify) {
380
+ lines.push('');
381
+ lines.push('❌ Strict mode blocks verification when GDC check fails to run.');
382
+ return {
383
+ applied: true,
384
+ passed: false,
385
+ failedAt: 'GDC Check',
386
+ report: lines.join('\n'),
387
+ };
388
+ }
389
+ lines.push('');
390
+ lines.push('⚠️ GDC check execution failed, but lenient mode allows verification to continue.');
391
+ return {
392
+ applied: true,
393
+ passed: true,
394
+ report: lines.join('\n'),
395
+ };
396
+ }
397
+ const counts = countGdcCheckIssues(checkResult.data);
398
+ lines.push(`- check summary: errors=${counts.errors}, warnings=${counts.warnings}, info=${counts.infos}, issues=${counts.issueCount}`);
399
+ if (counts.errors > 0) {
400
+ lines.push('');
401
+ lines.push('❌ Blocking GDC check errors found. Resolve them before build/test verification.');
402
+ return {
403
+ applied: true,
404
+ passed: false,
405
+ failedAt: 'GDC Check',
406
+ report: lines.join('\n'),
407
+ };
408
+ }
409
+ if (checkResult.parseError) {
410
+ if (gdc.strictVerify) {
411
+ lines.push('');
412
+ lines.push('❌ Strict mode requires parseable GDC machine output.');
413
+ return {
414
+ applied: true,
415
+ passed: false,
416
+ failedAt: 'GDC Check Parse',
417
+ report: lines.join('\n'),
418
+ };
419
+ }
420
+ lines.push('');
421
+ lines.push('⚠️ GDC check output could not be parsed; continuing in lenient mode.');
422
+ }
423
+ lines.push('');
424
+ lines.push('✅ GDC gate passed.');
425
+ return {
426
+ applied: true,
427
+ passed: true,
428
+ report: lines.join('\n'),
429
+ };
430
+ }
431
+ async function runGdcPrepareSync(basePath) {
432
+ const gdc = getEffectiveGdcConfig(basePath);
433
+ if (!gdc.enabled || !gdc.autoSyncOnPrepare) {
434
+ return {
435
+ applied: false,
436
+ report: '',
437
+ };
438
+ }
439
+ const lines = [];
440
+ lines.push('### GDC Prepare Sync');
441
+ lines.push('');
442
+ lines.push(`- binary: \`${gdc.binPath}\``);
443
+ const syncResult = await runGdcMachineCommand({
444
+ basePath,
445
+ command: 'sync',
446
+ config: gdc,
447
+ });
448
+ lines.push(formatGdcCommandLine('sync', syncResult));
449
+ const syncInfraFailure = Boolean(syncResult.transportError)
450
+ || (syncResult.exitCode !== 0 && syncResult.exitCode !== 2);
451
+ const syncMachineParseFailure = Boolean(syncResult.parseError && syncResult.exitCode === 0);
452
+ lines.push('');
453
+ if (syncInfraFailure || syncMachineParseFailure) {
454
+ lines.push('⚠️ GDC sync failed during prepare; continuing without blocking.');
455
+ }
456
+ else {
457
+ lines.push('✅ GDC sync completed before research/plan generation.');
458
+ }
459
+ return {
460
+ applied: true,
461
+ report: lines.join('\n'),
462
+ };
463
+ }
298
464
  async function handleResearch(args, basePath) {
299
465
  const { docsPath, projectName } = args;
300
466
  if (!docsPath) {
@@ -524,6 +690,7 @@ async function handlePrepare(args, basePath) {
524
690
  }
525
691
  const resolvedDocsPath = resolveUnderBase(basePath, docsPath);
526
692
  const intakeResult = await intake({ docsPath: resolvedDocsPath });
693
+ const gdcPrepareSync = await runGdcPrepareSync(basePath);
527
694
  const researchResult = await writeResearchReport({
528
695
  docsPath: resolvedDocsPath,
529
696
  intake: intakeResult,
@@ -555,6 +722,10 @@ async function handlePrepare(args, basePath) {
555
722
  });
556
723
  const lines = [];
557
724
  lines.push('## ✅ Weave Prepare 완료\n');
725
+ if (gdcPrepareSync.applied && gdcPrepareSync.report) {
726
+ lines.push(gdcPrepareSync.report);
727
+ lines.push('');
728
+ }
558
729
  lines.push(researchResult.summary);
559
730
  lines.push('');
560
731
  lines.push(specResult.summary);
@@ -642,38 +813,36 @@ async function handleFlow(args, basePath) {
642
813
  }
643
814
  if (!planGate.passed) {
644
815
  lines.push('');
645
- lines.push('Flow paused: plan gate failed. Re-plan before implementation.');
646
- lines.push(`Run: \`weave command=craft phaseId="${resolvedPhaseId}"\``);
647
- lines.push('Inspect: `weave command=status`');
816
+ lines.push('⚠️ Plan gate failed, but flow continues in one-shot mode.');
817
+ lines.push(`Missing checks: ${planGate.failedLabels.join(', ')}`);
648
818
  await appendWorkflowLesson(basePath, {
649
- trigger: 'plan_gate_failed',
819
+ trigger: 'plan_gate_failed_bypassed',
650
820
  pattern: `Phase ${resolvedPhaseId} failed plan gate checks: ${planGate.failedLabels.join(', ')}`,
651
- rule: 'Before implementation, ensure phase tasks include implementation, tests, and verification coverage.',
821
+ rule: 'Flow one-shot mode bypassed plan gate. Revisit plan quality after this run.',
652
822
  });
653
- await syncWorkflowArtifacts(basePath, manager, {
654
- phaseId: resolvedPhaseId,
655
- reviewLines: [
656
- `Flow paused at plan gate for ${resolvedPhaseId}.`,
657
- `Missing checks: ${planGate.failedLabels.join(', ')}`,
658
- ],
659
- });
660
- return lines.join('\n');
661
823
  }
662
824
  lines.push('');
663
825
  lines.push('### Plan Approval');
664
826
  lines.push('');
665
827
  if (!plan.planApproved) {
666
- const approvalMessage = formatPlanApprovalRequired(basePath, plan);
667
- lines.push(approvalMessage);
668
- await syncWorkflowArtifacts(basePath, manager, {
669
- phaseId: resolvedPhaseId,
670
- reviewLines: [
671
- `Flow paused before implementation: plan approval required for ${resolvedPhaseId}.`,
672
- ],
673
- });
674
- return lines.join('\n');
828
+ const approvalResult = await handleApprovePlan({
829
+ planReview: 'Auto-approved by weave flow',
830
+ applyNotes: false,
831
+ }, basePath);
832
+ if (approvalResult.startsWith('Error:')) {
833
+ await syncWorkflowArtifacts(basePath, manager, {
834
+ phaseId: resolvedPhaseId,
835
+ reviewLines: [
836
+ `Flow failed at auto-approval for ${resolvedPhaseId}.`,
837
+ ],
838
+ });
839
+ return approvalResult;
840
+ }
841
+ lines.push(approvalResult);
842
+ }
843
+ else {
844
+ lines.push('- PASS: plan approved for implementation');
675
845
  }
676
- lines.push('- PASS: plan approved for implementation');
677
846
  const craftResult = await handleCraft({
678
847
  phaseId: resolvedPhaseId,
679
848
  projectType: args.projectType,
@@ -685,44 +854,70 @@ async function handleFlow(args, basePath) {
685
854
  lines.push('### 2) Craft');
686
855
  lines.push('');
687
856
  lines.push(craftResult);
857
+ lines.push('');
858
+ lines.push('### 3) Verify');
859
+ lines.push('');
688
860
  const reviewLines = [
689
861
  `Flow executed for ${resolvedPhaseId}.`,
690
- `Plan gate: PASS (${planGate.nonTrivial ? 'non-trivial' : 'simple'} scope).`,
862
+ `Plan gate: ${planGate.passed ? 'PASS' : 'BYPASS'} (${planGate.nonTrivial ? 'non-trivial' : 'simple'} scope).`,
691
863
  ];
692
- if (craftResult.includes('All tasks done')) {
693
- reviewLines.push(`All tasks done for ${resolvedPhaseId}; phase finalization handled automatically in craft.`);
864
+ const gdcGate = await runGdcVerifyGate(basePath);
865
+ if (gdcGate.applied && gdcGate.report) {
866
+ lines.push(gdcGate.report);
867
+ lines.push('');
694
868
  }
695
- else {
696
- reviewLines.push(`Craft auto-loop paused for ${resolvedPhaseId}; rerun craft after implementation updates.`);
869
+ if (!gdcGate.passed) {
870
+ lines.push(`❌ Verification failed at: ${gdcGate.failedAt || 'GDC Gate'}`);
871
+ reviewLines.push(`Flow stopped at GDC gate for ${resolvedPhaseId}: ${gdcGate.failedAt || 'GDC Gate'}.`);
872
+ await syncWorkflowArtifacts(basePath, manager, {
873
+ phaseId: resolvedPhaseId,
874
+ reviewLines,
875
+ });
876
+ return lines.join('\n');
697
877
  }
878
+ const verification = await runAIVerification({
879
+ projectType: args.projectType || 'unknown',
880
+ projectPath: basePath,
881
+ enablePlaywright: false,
882
+ enableScreenshots: false,
883
+ mode: args.verifyMode || 'quick',
884
+ });
885
+ const verificationReport = generateVerificationReport(verification.results);
886
+ lines.push(verificationReport);
887
+ reviewLines.push(`Craft prepared execution context for ${resolvedPhaseId}.`);
888
+ reviewLines.push('Phase implementation/verification proceeds outside the legacy auto loop.');
889
+ if (gdcGate.applied) {
890
+ reviewLines.push(`GDC pre-verify gate passed for ${resolvedPhaseId}.`);
891
+ }
892
+ if (!verification.passed) {
893
+ lines.push('');
894
+ lines.push(`❌ Verification failed at: ${verification.failedAt || 'unknown'}`);
895
+ lines.push('Fix the issues and rerun `weave command=flow` or `weave command=verify`.');
896
+ reviewLines.push(`Flow stopped at verification for ${resolvedPhaseId}: ${verification.failedAt || 'unknown'}.`);
897
+ await syncWorkflowArtifacts(basePath, manager, {
898
+ phaseId: resolvedPhaseId,
899
+ reviewLines,
900
+ });
901
+ return lines.join('\n');
902
+ }
903
+ lines.push('');
904
+ lines.push('### 4) Finalize');
905
+ lines.push('');
906
+ const finalizeResult = await handleApprove({
907
+ phaseId: resolvedPhaseId,
908
+ projectType: args.projectType,
909
+ skipVerify: true,
910
+ source: 'command',
911
+ }, basePath);
912
+ lines.push(finalizeResult);
913
+ reviewLines.push(`Flow verification passed for ${resolvedPhaseId}.`);
914
+ reviewLines.push(`Flow finalized ${resolvedPhaseId}.`);
698
915
  await syncWorkflowArtifacts(basePath, manager, {
699
916
  phaseId: resolvedPhaseId,
700
917
  reviewLines,
701
918
  });
702
919
  return lines.join('\n');
703
920
  }
704
- function derivePhaseIdFromTaskId(taskId) {
705
- if (!taskId)
706
- return undefined;
707
- const m = /^([Pp]\d+)-T\d+$/.exec(taskId.trim());
708
- if (!m)
709
- return undefined;
710
- return m[1].toUpperCase();
711
- }
712
- const taskStartSnapshots = new Map();
713
- function getTaskSnapshotKey(basePath, phaseId, taskId) {
714
- return `${path.resolve(basePath)}::${phaseId}::${taskId}`;
715
- }
716
- async function captureTaskSnapshot(basePath, phaseId, taskId) {
717
- try {
718
- await ensureGitRepo(basePath);
719
- const status = await getWorkingTreeStatus(basePath);
720
- taskStartSnapshots.set(getTaskSnapshotKey(basePath, phaseId, taskId), status.trim());
721
- }
722
- catch {
723
- // Non-git workspace: skip snapshots.
724
- }
725
- }
726
921
  async function handleCraft(args, basePath) {
727
922
  const { phaseId, projectType } = args;
728
923
  let resolvedPhaseId = phaseId;
@@ -770,7 +965,7 @@ async function handleCraft(args, basePath) {
770
965
  }
771
966
  // Prepare execution plan (validates, marks in_progress, generates plan)
772
967
  const events = [];
773
- const { plan: executionPlan, phase } = await preparePhaseExecution({
968
+ const { plan: executionPlan } = await preparePhaseExecution({
774
969
  phaseId: resolvedPhaseId,
775
970
  projectType,
776
971
  onEvent: (event) => events.push(event),
@@ -778,11 +973,8 @@ async function handleCraft(args, basePath) {
778
973
  });
779
974
  // Format the execution plan as markdown for the Mask Weaver
780
975
  const planMarkdown = formatExecutionPlan(executionPlan);
781
- // Add a small "what to do next" block so users don't bounce between craft/task.
782
976
  const manager = getPhaseManager(basePath);
783
977
  await manager.loadPlan();
784
- const refreshed = manager.getPhase(resolvedPhaseId);
785
- const next = refreshed ? findNextActionableTask(refreshed) : null;
786
978
  const lines = [];
787
979
  if (recoveryPrefix) {
788
980
  lines.push(recoveryPrefix.trimEnd());
@@ -793,98 +985,20 @@ async function handleCraft(args, basePath) {
793
985
  lines.push('');
794
986
  }
795
987
  lines.push(planMarkdown);
796
- if (refreshed) {
797
- lines.push('');
798
- lines.push('---');
799
- lines.push('');
800
- lines.push(formatPhaseTasksTable(refreshed));
801
- lines.push('');
802
- if (next) {
803
- lines.push(`Next: \`${next.id}\` (${next.status}) — ${next.name}`);
804
- lines.push(`Continue: \`weave command=craft phaseId="${resolvedPhaseId}" taskId="${next.id}"\``);
805
- }
806
- else {
807
- lines.push(`All tasks done for ${resolvedPhaseId}. Final goal check + auto finalize will run in this craft execution.`);
808
- }
809
- }
810
- const autoResult = await handleTask({
811
- phaseId: resolvedPhaseId,
812
- taskAction: 'auto',
813
- taskId: args.taskId || next?.id,
814
- verify: args.verify,
815
- verifyMode: args.verifyMode,
816
- commit: args.commit,
817
- stageAll: args.stageAll,
818
- commitMessage: args.commitMessage,
819
- projectType,
820
- }, basePath);
821
988
  lines.push('');
822
- lines.push('### Auto Loop');
989
+ lines.push('### Next Steps');
823
990
  lines.push('');
824
- lines.push(autoResult);
825
- const autoCompleted = autoResult.includes(`All tasks done for ${resolvedPhaseId}`);
826
- if (autoCompleted) {
827
- await manager.loadPlan();
828
- const finalizedPhase = manager.getPhase(resolvedPhaseId);
829
- if (finalizedPhase) {
830
- const goalCheck = evaluatePhaseGoal(finalizedPhase);
831
- lines.push('');
832
- lines.push('### Final Goal Check');
833
- lines.push('');
834
- lines.push(`Phase goal (done_when): ${finalizedPhase.doneWhen || '(missing)'}`);
835
- for (const check of goalCheck.checks) {
836
- lines.push(`- ${check.passed ? 'PASS' : 'FAIL'}: ${check.label}`);
837
- }
838
- if (!goalCheck.passed) {
839
- const metadataFailures = goalCheck.failedLabels.filter(label => label.includes('done_when') || label.includes('checklist'));
840
- if (metadataFailures.length > 0) {
841
- lines.push('');
842
- lines.push('🛑 Final goal check failed due to phase metadata gaps.');
843
- lines.push('Update the phase definition, then re-approve the plan before continuing.');
844
- lines.push('Suggested path:');
845
- lines.push('- `weave command=refine-plan`');
846
- lines.push('- `weave command=approve-plan`');
847
- lines.push(`- \`weave command=craft phaseId="${resolvedPhaseId}"\``);
848
- return lines.join('\n');
849
- }
850
- const followupTask = await ensureGoalCheckFollowupTask(manager, resolvedPhaseId, goalCheck.failedLabels);
851
- lines.push('');
852
- lines.push('⚠️ Final goal check failed. Craft loop is re-entered with a follow-up task.');
853
- if (followupTask) {
854
- lines.push(`Created follow-up task: \`${followupTask.id}\` — ${followupTask.name}`);
855
- }
856
- const reentryResult = await handleTask({
857
- phaseId: resolvedPhaseId,
858
- taskAction: 'auto',
859
- taskId: followupTask?.id,
860
- verify: args.verify,
861
- verifyMode: args.verifyMode,
862
- commit: args.commit,
863
- stageAll: args.stageAll,
864
- commitMessage: args.commitMessage,
865
- projectType,
866
- }, basePath);
867
- lines.push('');
868
- lines.push('### Auto Loop (Re-entry)');
869
- lines.push('');
870
- lines.push(reentryResult);
871
- }
872
- else {
873
- const finalizeResult = await handleApprove({
874
- phaseId: resolvedPhaseId,
875
- projectType,
876
- skipVerify: args.skipVerify,
877
- verifyMode: 'full',
878
- source: 'craft',
879
- }, basePath);
880
- lines.push('');
881
- lines.push('### Auto Finalize');
882
- lines.push('');
883
- lines.push(finalizeResult);
884
- }
885
- }
886
- }
887
- // Return the plan + current auto-loop execution result.
991
+ lines.push('- Implement/delegate the phase work using the execution plan above.');
992
+ lines.push('- Run verification: `weave command=verify` (or your project test/build commands).');
993
+ lines.push(`- Finalize the phase when ready: \`weave command=approve-plan phaseId="${resolvedPhaseId}"\``);
994
+ lines.push('- Check overall progress anytime: `weave command=status`.');
995
+ await syncWorkflowArtifacts(basePath, manager, {
996
+ phaseId: resolvedPhaseId,
997
+ reviewLines: [
998
+ `Craft prepared execution context for ${resolvedPhaseId}.`,
999
+ 'Legacy auto loop has been removed; proceed with implementation + verification, then approve.',
1000
+ ],
1001
+ });
888
1002
  return lines.join('\n');
889
1003
  }
890
1004
  function generateDefaultPhaseTasks(phase) {
@@ -911,36 +1025,6 @@ function generateDefaultPhaseTasks(phase) {
911
1025
  },
912
1026
  ];
913
1027
  }
914
- function formatPhaseTasksTable(phase) {
915
- const lines = [];
916
- lines.push(`## Phase ${phase.id}: Tasks`);
917
- lines.push('');
918
- if (!phase.tasks || phase.tasks.length === 0) {
919
- lines.push('(no tasks)');
920
- return lines.join('\n');
921
- }
922
- lines.push('| ID | Status | Retries | Task |');
923
- lines.push('|----|--------|--------|------|');
924
- for (const t of phase.tasks) {
925
- const retries = `${t.retryCount || 0}/${t.maxRetries || 5}`;
926
- lines.push(`| ${t.id} | ${t.status} | ${retries} | ${t.name} |`);
927
- }
928
- return lines.join('\n');
929
- }
930
- function findNextActionableTask(phase) {
931
- if (!phase.tasks || phase.tasks.length === 0)
932
- return null;
933
- const inProgress = phase.tasks.find((t) => t.status === 'in_progress');
934
- if (inProgress)
935
- return inProgress;
936
- const failed = phase.tasks.find((t) => t.status === 'failed' && (t.retryCount || 0) < (t.maxRetries || 5));
937
- if (failed)
938
- return failed;
939
- const pending = phase.tasks.find((t) => t.status === 'pending');
940
- if (pending)
941
- return pending;
942
- return null;
943
- }
944
1028
  async function handleStatus(basePath) {
945
1029
  const manager = getPhaseManager(basePath);
946
1030
  const activePlan = await manager.loadPlan();
@@ -1074,472 +1158,18 @@ async function handleWorktree(args, basePath) {
1074
1158
  }
1075
1159
  }
1076
1160
  }
1077
- async function handleTask(args, basePath) {
1078
- const manager = getPhaseManager(basePath);
1079
- const plan = await manager.loadPlan();
1080
- if (!plan) {
1081
- return 'Error: No active plan. Run `weave design docs/` (or `weave prepare docs/`) first.';
1082
- }
1083
- const derivedPhaseId = derivePhaseIdFromTaskId(args.taskId);
1084
- const phaseId = args.phaseId || derivedPhaseId || plan.currentPhase || manager.getNextPhase()?.id;
1085
- if (!phaseId) {
1086
- return 'Error: phaseId not resolved. Start with `weave craft` to auto-select a phase.';
1087
- }
1088
- const phase = manager.getPhase(phaseId);
1089
- if (!phase) {
1090
- return `Error: Phase not found: ${phaseId}`;
1091
- }
1092
- const action = args.taskAction || 'list';
1093
- if (['start', 'pass', 'retry', 'auto'].includes(action) && !plan.planApproved) {
1094
- return formatPlanApprovalRequired(basePath, plan);
1095
- }
1096
- const currentPhase = () => manager.getPhase(phaseId) || phase;
1097
- const findTask = (taskId) => {
1098
- if (!taskId)
1099
- return null;
1100
- return currentPhase().tasks.find(t => t.id === taskId) || null;
1101
- };
1102
- const nextActionable = () => {
1103
- const tasks = currentPhase().tasks;
1104
- const inProgress = tasks.find(t => t.status === 'in_progress');
1105
- if (inProgress)
1106
- return inProgress;
1107
- const failed = tasks.find(t => t.status === 'failed' && (t.retryCount || 0) < (t.maxRetries || 5));
1108
- if (failed)
1109
- return failed;
1110
- const pending = tasks.find(t => t.status === 'pending');
1111
- if (pending)
1112
- return pending;
1113
- return null;
1114
- };
1115
- const formatTasksTable = () => {
1116
- const p = currentPhase();
1117
- const lines = [];
1118
- lines.push(`## Phase ${p.id}: Tasks`);
1119
- lines.push('');
1120
- if (p.tasks.length === 0) {
1121
- lines.push('(no tasks)');
1122
- return lines.join('\n');
1123
- }
1124
- lines.push('| ID | Status | Retries | Task |');
1125
- lines.push('|----|--------|--------|------|');
1126
- for (const t of p.tasks) {
1127
- const retries = `${t.retryCount || 0}/${t.maxRetries || 5}`;
1128
- lines.push(`| ${t.id} | ${t.status} | ${retries} | ${t.name} |`);
1129
- }
1130
- return lines.join('\n');
1131
- };
1132
- const finalize = async (message, reviewLines) => {
1133
- await syncWorkflowArtifacts(basePath, manager, {
1134
- phaseId,
1135
- reviewLines,
1136
- });
1137
- return message;
1138
- };
1139
- const passTask = async (task) => {
1140
- // Guardrail: don't mark task as passed when nothing changed.
1141
- const snapshotKey = getTaskSnapshotKey(basePath, phaseId, task.id);
1142
- try {
1143
- await ensureGitRepo(basePath);
1144
- const currentStatus = (await getWorkingTreeStatus(basePath)).trim();
1145
- const baselineStatus = taskStartSnapshots.get(snapshotKey);
1146
- if (baselineStatus === undefined) {
1147
- taskStartSnapshots.set(snapshotKey, currentStatus);
1148
- await manager.updateTaskStatus(phaseId, task.id, 'in_progress', {
1149
- lastError: undefined,
1150
- });
1151
- return {
1152
- ok: false,
1153
- reason: 'no_changes',
1154
- body: [
1155
- `⏸ Baseline captured for task: ${task.id}`,
1156
- 'No implementation delta to verify yet.',
1157
- 'Implement code changes (or delegate), then rerun `weave craft`.',
1158
- ].join('\n'),
1159
- };
1160
- }
1161
- if (currentStatus === baselineStatus) {
1162
- await manager.updateTaskStatus(phaseId, task.id, 'in_progress', {
1163
- lastError: undefined,
1164
- });
1165
- return {
1166
- ok: false,
1167
- reason: 'no_changes',
1168
- body: [
1169
- `⏸ No implementation delta detected for task: ${task.id}`,
1170
- 'Task remains in_progress.',
1171
- 'Implement code changes (or delegate), then rerun `weave craft`.',
1172
- ].join('\n'),
1173
- };
1174
- }
1175
- }
1176
- catch {
1177
- // Non-git workspace: skip change detection guard.
1178
- }
1179
- const shouldVerify = args.verify ?? true;
1180
- const verifyMode = args.verifyMode || 'quick';
1181
- const projectType = args.projectType || 'unknown';
1182
- const commit = !!args.commit;
1183
- // Mark in progress while we verify/commit
1184
- await manager.updateTaskStatus(phaseId, task.id, 'in_progress');
1185
- let verificationReport = '';
1186
- if (shouldVerify) {
1187
- const verification = await runAIVerification({
1188
- projectType,
1189
- projectPath: basePath,
1190
- enablePlaywright: false,
1191
- enableScreenshots: false,
1192
- mode: verifyMode,
1193
- });
1194
- verificationReport = generateVerificationReport(verification.results);
1195
- if (!verification.passed) {
1196
- await manager.updateTaskStatus(phaseId, task.id, 'failed', {
1197
- lastError: `Verification failed at: ${verification.failedAt || 'unknown'}`,
1198
- });
1199
- return {
1200
- ok: false,
1201
- reason: 'verification_failed',
1202
- body: [
1203
- verificationReport,
1204
- '',
1205
- `❌ Verification failed at: ${verification.failedAt || 'unknown'}`,
1206
- '',
1207
- `Task marked failed: ${task.id}`,
1208
- ].join('\n'),
1209
- };
1210
- }
1211
- }
1212
- let commitBlock = null;
1213
- if (commit) {
1214
- try {
1215
- await ensureGitRepo(basePath);
1216
- if (args.stageAll) {
1217
- await stageAllChanges(basePath);
1218
- }
1219
- else {
1220
- const hasStaged = await hasStagedChanges(basePath);
1221
- if (!hasStaged) {
1222
- await manager.updateTaskStatus(phaseId, task.id, 'failed', {
1223
- lastError: 'No staged changes to commit',
1224
- });
1225
- return {
1226
- ok: false,
1227
- reason: 'no_staged_changes',
1228
- body: [
1229
- verificationReport,
1230
- '',
1231
- '❌ No staged changes to commit.',
1232
- 'Stage files first, or use `stageAll=true`.',
1233
- ].filter(Boolean).join('\n'),
1234
- };
1235
- }
1236
- }
1237
- const stagedFiles = await listStagedFiles(basePath);
1238
- const secretCfg = loadSecretScanConfig(basePath);
1239
- const findings = scanFilesForSecrets({ projectPath: basePath, files: stagedFiles, config: secretCfg });
1240
- if (findings.length > 0 && shouldBlockOnFindings(findings, secretCfg)) {
1241
- await manager.updateTaskStatus(phaseId, task.id, 'failed', {
1242
- lastError: 'Secret scan blocked commit',
1243
- });
1244
- return {
1245
- ok: false,
1246
- reason: 'secret_scan_blocked',
1247
- body: [
1248
- verificationReport,
1249
- '',
1250
- formatSecretScanReport(findings),
1251
- ].filter(Boolean).join('\n'),
1252
- };
1253
- }
1254
- const secretWarning = findings.length > 0 ? formatSecretScanReport(findings) : null;
1255
- const defaultMsg = `${phaseId}/${task.id}: ${task.name}`;
1256
- const msg = (args.commitMessage && args.commitMessage.trim().length > 0)
1257
- ? args.commitMessage.trim()
1258
- : defaultMsg;
1259
- const commitRes = await commitStagedChanges(basePath, msg);
1260
- const commitOutput = [commitRes.stdout, commitRes.stderr].filter(Boolean).join('\n').trim();
1261
- commitBlock = [
1262
- secretWarning ? secretWarning : '',
1263
- '✅ Commit created.',
1264
- commitOutput ? ['```', commitOutput, '```'].join('\n') : '',
1265
- ].filter(Boolean).join('\n');
1266
- }
1267
- catch (e) {
1268
- const msg = e instanceof Error ? e.message : String(e);
1269
- await manager.updateTaskStatus(phaseId, task.id, 'failed', {
1270
- lastError: `Commit failed: ${msg}`,
1271
- });
1272
- return {
1273
- ok: false,
1274
- reason: 'commit_failed',
1275
- body: [
1276
- verificationReport,
1277
- '',
1278
- `❌ Commit failed: ${msg}`,
1279
- ].filter(Boolean).join('\n'),
1280
- };
1281
- }
1282
- }
1283
- await manager.updateTaskStatus(phaseId, task.id, 'passed');
1284
- taskStartSnapshots.delete(snapshotKey);
1285
- return {
1286
- ok: true,
1287
- body: [
1288
- shouldVerify ? verificationReport : '',
1289
- shouldVerify ? '' : '',
1290
- `✅ Task passed: ${task.id} — ${task.name}`,
1291
- commitBlock ? '' : '',
1292
- commitBlock ? commitBlock : '',
1293
- ].filter(Boolean).join('\n'),
1294
- };
1295
- };
1296
- switch (action) {
1297
- case 'list': {
1298
- const lines = [formatTasksTable()];
1299
- const next = nextActionable();
1300
- if (next) {
1301
- lines.push('');
1302
- lines.push(`Next: \`${next.id}\` (${next.status}) — ${next.name}`);
1303
- lines.push(`Continue: \`weave command=craft phaseId="${phaseId}" taskId="${next.id}"\``);
1304
- }
1305
- return lines.join('\n');
1306
- }
1307
- case 'next': {
1308
- const next = nextActionable();
1309
- if (!next) {
1310
- return `All tasks are done for ${phaseId}. Rerun \`weave craft\` to run final goal check + auto finalize.`;
1311
- }
1312
- return [
1313
- `## Next Task for ${phaseId}`,
1314
- '',
1315
- `- ID: \`${next.id}\``,
1316
- `- Status: \`${next.status}\``,
1317
- `- Task: ${next.name}`,
1318
- '',
1319
- `Continue: \`weave command=craft phaseId="${phaseId}" taskId="${next.id}"\``,
1320
- ].join('\n');
1321
- }
1322
- case 'start': {
1323
- const task = findTask(args.taskId);
1324
- if (!task) {
1325
- return 'Error: taskId is required and must exist in this phase.';
1326
- }
1327
- await manager.updateTaskStatus(phaseId, task.id, 'in_progress');
1328
- await captureTaskSnapshot(basePath, phaseId, task.id);
1329
- return finalize([
1330
- `✅ Task started: ${task.id} — ${task.name}`,
1331
- 'Note: start only updates task state. Code changes are done by implementation work (you/agent), then pass/auto verifies.',
1332
- ].join('\n'), [
1333
- `Task started: ${task.id}`,
1334
- ]);
1335
- }
1336
- case 'retry': {
1337
- const task = findTask(args.taskId);
1338
- if (!task) {
1339
- return 'Error: taskId is required and must exist in this phase.';
1340
- }
1341
- await manager.updateTaskStatus(phaseId, task.id, 'in_progress', { lastError: undefined });
1342
- await captureTaskSnapshot(basePath, phaseId, task.id);
1343
- return finalize(`🔄 Task retry: ${task.id} — ${task.name}`, [
1344
- `Task retried: ${task.id}`,
1345
- ]);
1346
- }
1347
- case 'fail': {
1348
- const task = findTask(args.taskId);
1349
- if (!task) {
1350
- return 'Error: taskId is required and must exist in this phase.';
1351
- }
1352
- const err = (args.taskError || '').trim();
1353
- await manager.updateTaskStatus(phaseId, task.id, 'failed', { lastError: err || 'failed' });
1354
- const lines = [];
1355
- lines.push(`❌ Task failed: ${task.id} — ${task.name}`);
1356
- if (err) {
1357
- lines.push('');
1358
- lines.push('Error:');
1359
- lines.push('```');
1360
- lines.push(err.slice(0, 2000));
1361
- lines.push('```');
1362
- }
1363
- if (err) {
1364
- try {
1365
- const orch = getOrchestrator();
1366
- const suggestedMask = orch.selectMaskForError(err);
1367
- lines.push('');
1368
- lines.push(`Suggested mask: \`${suggestedMask}\``);
1369
- }
1370
- catch {
1371
- // ignore
1372
- }
1373
- try {
1374
- const solutions = await searchTroubleshooting(err, {
1375
- projectType: args.projectType,
1376
- limit: 3,
1377
- });
1378
- if (solutions.length > 0) {
1379
- lines.push('');
1380
- lines.push('Hints (Global Knowledge):');
1381
- for (const s of solutions) {
1382
- lines.push(`- ${s.entry.solution}`);
1383
- }
1384
- }
1385
- }
1386
- catch {
1387
- // ignore
1388
- }
1389
- }
1390
- lines.push('');
1391
- lines.push(`Retry: \`weave command=craft phaseId="${phaseId}" taskId="${task.id}"\``);
1392
- if (err) {
1393
- await appendWorkflowLesson(basePath, {
1394
- trigger: `task_fail:${task.id}`,
1395
- pattern: sanitizeLessonText(err),
1396
- rule: 'Capture concrete failure context, then retry only after implementation changes.',
1397
- });
1398
- }
1399
- return finalize(lines.join('\n'), [
1400
- `Task failed: ${task.id}`,
1401
- err ? `Error captured for ${task.id}.` : `Failure recorded without error details for ${task.id}.`,
1402
- ]);
1403
- }
1404
- case 'pass': {
1405
- const task = findTask(args.taskId);
1406
- if (!task) {
1407
- return 'Error: taskId is required and must exist in this phase.';
1408
- }
1409
- const result = await passTask(task);
1410
- if (!result.ok) {
1411
- if (result.reason === 'verification_failed') {
1412
- await appendWorkflowLesson(basePath, {
1413
- trigger: `task_pass_verification_failed:${task.id}`,
1414
- pattern: sanitizeLessonText(result.body),
1415
- rule: 'Do not mark task done until verification passes. Fix root cause and rerun pass.',
1416
- });
1417
- }
1418
- return finalize(result.body, [
1419
- `Task pass blocked: ${task.id} (${result.reason || 'unknown'}).`,
1420
- ]);
1421
- }
1422
- const next = nextActionable();
1423
- return finalize([
1424
- result.body,
1425
- next ? '' : '',
1426
- next ? `Next: \`${next.id}\` (${next.status}) — ${next.name}` : `All tasks done for ${phaseId}. Rerun \`weave craft\` to run final goal check + auto finalize.`,
1427
- ].filter(Boolean).join('\n'), [
1428
- `Task passed: ${task.id}`,
1429
- next ? `Next actionable task: ${next.id}.` : `All tasks passed for ${phaseId}.`,
1430
- ]);
1431
- }
1432
- case 'auto': {
1433
- if (currentPhase().tasks.length === 0) {
1434
- return finalize(`No tasks found for ${phaseId}. Run \`weave craft ${phaseId}\` to seed/generate tasks.`, [
1435
- `Auto loop paused: no tasks in ${phaseId}.`,
1436
- ]);
1437
- }
1438
- const lines = [];
1439
- lines.push(`## Auto Task Loop: ${phaseId}`);
1440
- lines.push('');
1441
- const maxSteps = Math.max(10, currentPhase().tasks.length * 3);
1442
- let steps = 0;
1443
- let preferredTaskId = args.taskId;
1444
- while (steps < maxSteps) {
1445
- const preferred = preferredTaskId ? findTask(preferredTaskId) : null;
1446
- preferredTaskId = undefined;
1447
- const next = (preferred && preferred.status !== 'passed') ? preferred : nextActionable();
1448
- if (!next) {
1449
- lines.push(`✅ All tasks done for ${phaseId}.`);
1450
- lines.push('Proceeding to final goal check in craft.');
1451
- return finalize(lines.join('\n'), [
1452
- `Auto loop completed: all tasks done for ${phaseId}.`,
1453
- ]);
1454
- }
1455
- steps += 1;
1456
- const previousStatus = next.status;
1457
- if (previousStatus !== 'in_progress') {
1458
- const patch = next.status === 'failed' ? { lastError: undefined } : undefined;
1459
- await manager.updateTaskStatus(phaseId, next.id, 'in_progress', patch);
1460
- await captureTaskSnapshot(basePath, phaseId, next.id);
1461
- if (previousStatus === 'failed') {
1462
- lines.push(`🔄 Retrying: ${next.id} — ${next.name}`);
1463
- }
1464
- else {
1465
- lines.push(`▶ Started: ${next.id} — ${next.name}`);
1466
- }
1467
- lines.push('Implement/delegate this task first, then rerun `weave craft`.');
1468
- lines.push(`Continue: \`weave command=craft phaseId="${phaseId}" taskId="${next.id}"\``);
1469
- return finalize(lines.join('\n'), [
1470
- `Auto loop waiting for implementation on ${next.id}.`,
1471
- ]);
1472
- }
1473
- lines.push(`▶ Verifying: ${next.id} — ${next.name}`);
1474
- const task = findTask(next.id);
1475
- if (!task) {
1476
- lines.push(`❌ Task disappeared: ${next.id}`);
1477
- return finalize(lines.join('\n'), [
1478
- `Auto loop error: task disappeared (${next.id}).`,
1479
- ]);
1480
- }
1481
- const result = await passTask(task);
1482
- lines.push(result.body);
1483
- lines.push('');
1484
- if (!result.ok) {
1485
- if (result.reason === 'no_changes') {
1486
- lines.push(`⏸ Waiting for implementation changes on: ${task.id}`);
1487
- lines.push(`Resume after code changes: \`weave command=craft phaseId="${phaseId}" taskId="${task.id}"\``);
1488
- return finalize(lines.join('\n'), [
1489
- `Auto loop waiting for code changes on ${task.id}.`,
1490
- ]);
1491
- }
1492
- const failedTask = findTask(task.id);
1493
- const retries = failedTask?.retryCount || 0;
1494
- const maxRetries = failedTask?.maxRetries || 5;
1495
- const replanThreshold = Math.min(maxRetries, DEFAULT_REPLAN_THRESHOLD);
1496
- if (retries >= replanThreshold) {
1497
- const replan = await autoReplanFailedTask(manager, phaseId, task.id, {
1498
- reason: failedTask?.lastError || result.body,
1499
- });
1500
- if (replan && replan.newTaskIds.length > 0) {
1501
- lines.push(`🧭 Auto re-plan created ${replan.newTaskIds.length} subtasks from ${task.id}.`);
1502
- lines.push(`Subtasks: ${replan.newTaskIds.map(id => `\`${id}\``).join(', ')}`);
1503
- lines.push('Continuing auto loop with the first replanned subtask.');
1504
- await appendWorkflowLesson(basePath, {
1505
- trigger: `task_auto_replan:${task.id}`,
1506
- pattern: `Task ${task.id} hit ${retries} failures in auto loop.`,
1507
- rule: 'When repeated failures occur, split the task into smaller subtasks and continue iteratively.',
1508
- });
1509
- preferredTaskId = replan.newTaskIds[0];
1510
- continue;
1511
- }
1512
- lines.push(`🛑 Re-plan triggered: ${task.id} failed ${retries} times, but subtask generation failed.`);
1513
- lines.push('Stop and refine the plan before retrying the same implementation path.');
1514
- lines.push(`Inspect: \`weave command=status\``);
1515
- lines.push(`Refresh strategy: \`weave command=craft phaseId="${phaseId}"\``);
1516
- await appendWorkflowLesson(basePath, {
1517
- trigger: `task_auto_replan_failed:${task.id}`,
1518
- pattern: `Task ${task.id} failed ${retries} times and automatic re-plan generation failed.`,
1519
- rule: 'If automatic re-plan fails, perform manual plan refinement before retry.',
1520
- });
1521
- return finalize(lines.join('\n'), [
1522
- `Auto loop re-plan fallback for ${task.id} after ${retries} failures.`,
1523
- ]);
1524
- }
1525
- lines.push(`⏸ Stopped at failed task: ${task.id}`);
1526
- lines.push(`Fix code, then rerun: \`weave command=craft phaseId="${phaseId}" taskId="${task.id}"\``);
1527
- return finalize(lines.join('\n'), [
1528
- `Auto loop stopped at failed task ${task.id}.`,
1529
- ]);
1530
- }
1531
- }
1532
- lines.push('⚠️ Auto loop stopped at safety limit.');
1533
- lines.push(`Inspect with: \`weave command=craft phaseId="${phaseId}"\``);
1534
- return finalize(lines.join('\n'), [
1535
- `Auto loop safety stop in ${phaseId}.`,
1536
- ]);
1537
- }
1538
- }
1539
- }
1540
1161
  async function handleVerify(args, basePath) {
1541
1162
  const projectType = args.projectType || 'unknown';
1542
1163
  const mode = args.verifyMode || 'full';
1164
+ const gdcGate = await runGdcVerifyGate(basePath);
1165
+ const sections = [];
1166
+ if (gdcGate.applied && gdcGate.report) {
1167
+ sections.push(gdcGate.report);
1168
+ }
1169
+ if (!gdcGate.passed) {
1170
+ sections.push(`❌ Verification failed at: ${gdcGate.failedAt || 'GDC Gate'}`);
1171
+ return sections.join('\n\n');
1172
+ }
1543
1173
  const verification = await runAIVerification({
1544
1174
  projectType,
1545
1175
  projectPath: basePath,
@@ -1548,26 +1178,27 @@ async function handleVerify(args, basePath) {
1548
1178
  mode,
1549
1179
  });
1550
1180
  const report = generateVerificationReport(verification.results);
1181
+ sections.push(report);
1551
1182
  if (verification.results.length === 0) {
1552
- return [
1553
- report,
1183
+ sections.push([
1554
1184
  '',
1555
1185
  '> No verification commands detected for this project.',
1556
1186
  '> Provide scripts/tools (package.json, go.mod, Cargo.toml, pyproject.toml, *.sln) or pass projectType hint.',
1557
- ].join('\n');
1187
+ ].join('\n'));
1188
+ return sections.join('\n\n');
1558
1189
  }
1559
1190
  if (!verification.passed) {
1560
- return [
1561
- report,
1191
+ sections.push([
1562
1192
  '',
1563
1193
  `❌ Verification failed at: ${verification.failedAt || 'unknown'}`,
1564
- ].join('\n');
1194
+ ].join('\n'));
1195
+ return sections.join('\n\n');
1565
1196
  }
1566
- return [
1567
- report,
1197
+ sections.push([
1568
1198
  '',
1569
1199
  '✅ Verification passed.',
1570
- ].join('\n');
1200
+ ].join('\n'));
1201
+ return sections.join('\n\n');
1571
1202
  }
1572
1203
  async function handleTroubleshoot(args) {
1573
1204
  const { error, projectType } = args;
@@ -1656,6 +1287,24 @@ async function handleApprove(args, basePath) {
1656
1287
  return message;
1657
1288
  };
1658
1289
  if (!skipVerify) {
1290
+ const reportSections = [];
1291
+ const gdcGate = await runGdcVerifyGate(basePath);
1292
+ if (gdcGate.applied && gdcGate.report) {
1293
+ reportSections.push(gdcGate.report);
1294
+ }
1295
+ if (!gdcGate.passed) {
1296
+ return finalizeApprove([
1297
+ ...reportSections,
1298
+ `❌ Verification failed at: ${gdcGate.failedAt || 'GDC Gate'}`,
1299
+ '',
1300
+ invokedByCraft
1301
+ ? 'Fix the failures and rerun `weave craft`.'
1302
+ : 'Fix the failures and re-run `weave craft`.',
1303
+ 'You can also run: `weave command=verify`',
1304
+ ].filter(Boolean).join('\n\n'), [
1305
+ `Finalization blocked at GDC gate for ${resolvedPhaseId}: ${gdcGate.failedAt || 'GDC Gate'}.`,
1306
+ ]);
1307
+ }
1659
1308
  const verification = await runAIVerification({
1660
1309
  projectType: projectType || 'unknown',
1661
1310
  projectPath: basePath,
@@ -1663,7 +1312,8 @@ async function handleApprove(args, basePath) {
1663
1312
  enableScreenshots: false,
1664
1313
  mode: verifyMode || 'full',
1665
1314
  });
1666
- const report = generateVerificationReport(verification.results);
1315
+ reportSections.push(generateVerificationReport(verification.results));
1316
+ const report = reportSections.join('\n\n');
1667
1317
  if (!verification.passed) {
1668
1318
  return finalizeApprove([
1669
1319
  report,
@@ -1820,9 +1470,9 @@ To check installed version:
1820
1470
  | \`weave prepare [docs]\` | Create spec + phase plan (auto-splits oversized plans) |
1821
1471
  | \`weave refine-plan\` | Apply structured plan-note directives to active plan |
1822
1472
  | \`weave approve-plan\` | Mark plan approved before implementation |
1823
- | \`weave flow [docs]\` | One-command path (prepare -> approve-plan gate -> craft auto-loop + auto finalize) |
1473
+ | \`weave flow [docs]\` | One-command path (prepare -> auto-approve -> craft -> verify -> finalize) |
1824
1474
  | \`weave design [docs]\` | Analyze requirements and create phase plan (auto-splits oversized plans) |
1825
- | \`weave craft [id]\` | Execute a phase with auto task loop + goal check + auto finalize |
1475
+ | \`weave craft [id]\` | Prepare execution context for a phase |
1826
1476
  | \`weave status\` | View progress |
1827
1477
  | \`weave worktree ...\` | Manage git worktrees for parallel work |
1828
1478
  | \`weave verify\` | Run build/test verification for current worktree |
@@ -1833,7 +1483,7 @@ To check installed version:
1833
1483
 
1834
1484
  ### Key Features
1835
1485
 
1836
- - **Mask Auto-Selection**: Automatically applies expert masks per task
1486
+ - **Mask Auto-Selection**: Automatically applies expert masks for phase execution
1837
1487
  - **Global Knowledge Sharing**: Cross-project troubleshooting experience
1838
1488
  - **Auto-Verification**: Build + Self-Verify Loop
1839
1489
  - **YAML Auto-Repair**: Automatically detects and fixes corrupted plan files
@@ -1844,12 +1494,11 @@ To check installed version:
1844
1494
  weave prepare docs/ # Research + spec + plan
1845
1495
  weave refine-plan # Apply plan-notes directives (optional)
1846
1496
  weave approve-plan # Explicit approval gate
1847
- weave flow # Continue active plan with craft auto-loop
1848
- weave craft # Resume from current phase/task and auto-finalize on completion
1497
+ weave flow # One-shot: prepare/approve/craft/verify/finalize
1498
+ weave craft # Prepare current phase execution context
1849
1499
  \`\`\`
1850
1500
  `;
1851
1501
  }
1852
- const DEFAULT_REPLAN_THRESHOLD = 2;
1853
1502
  function evaluatePlanGate(phase) {
1854
1503
  const tasks = phase.tasks || [];
1855
1504
  const nonTrivial = tasks.length >= 3 || (phase.estimatedHours || 0) >= 4;
@@ -1859,16 +1508,16 @@ function evaluatePlanGate(phase) {
1859
1508
  const checks = [
1860
1509
  {
1861
1510
  label: nonTrivial
1862
- ? 'Task granularity (>=3 executable tasks for non-trivial work)'
1863
- : 'Task granularity (>=1 executable task)',
1511
+ ? 'Plan granularity (>=3 executable items for non-trivial work)'
1512
+ : 'Plan granularity (>=1 executable item)',
1864
1513
  passed: tasks.length >= (nonTrivial ? 3 : 1),
1865
1514
  },
1866
1515
  {
1867
- label: 'Test coverage task exists',
1516
+ label: 'Test coverage exists',
1868
1517
  passed: /\btest\b|테스트/.test(taskText),
1869
1518
  },
1870
1519
  {
1871
- label: 'Verification/build task exists',
1520
+ label: 'Verification/build coverage exists',
1872
1521
  passed: /\bverify\b|검증|\bbuild\b|\btypecheck\b/.test(taskText),
1873
1522
  },
1874
1523
  {
@@ -1884,155 +1533,6 @@ function evaluatePlanGate(phase) {
1884
1533
  failedLabels,
1885
1534
  };
1886
1535
  }
1887
- function evaluatePhaseGoal(phase) {
1888
- const tasks = phase.tasks || [];
1889
- const doneWhen = (phase.doneWhen || '').trim();
1890
- const checklist = phase.checklist || [];
1891
- const taskText = tasks
1892
- .map(task => `${task.name} ${task.testCase || ''}`.toLowerCase())
1893
- .join('\n');
1894
- const doneWhenTokens = (doneWhen.toLowerCase().match(/[a-z0-9가-힣]{3,}/g) || [])
1895
- .slice(0, 8);
1896
- const doneWhenTrace = doneWhenTokens.length === 0
1897
- ? doneWhen.length > 0
1898
- : doneWhenTokens.some(token => taskText.includes(token));
1899
- const checks = [
1900
- {
1901
- label: 'Phase done_when is defined',
1902
- passed: doneWhen.length > 0,
1903
- },
1904
- {
1905
- label: 'All phase tasks are passed',
1906
- passed: tasks.length > 0 && tasks.every(task => task.status === 'passed'),
1907
- },
1908
- {
1909
- label: 'Phase checklist is defined',
1910
- passed: checklist.length > 0,
1911
- },
1912
- {
1913
- label: 'Task descriptions are traceable to done_when',
1914
- passed: doneWhenTrace,
1915
- },
1916
- ];
1917
- const failedLabels = checks.filter(check => !check.passed).map(check => check.label);
1918
- return {
1919
- passed: failedLabels.length === 0,
1920
- checks,
1921
- failedLabels,
1922
- };
1923
- }
1924
- async function ensureGoalCheckFollowupTask(manager, phaseId, failedLabels) {
1925
- const plan = await manager.loadPlan();
1926
- if (!plan)
1927
- return null;
1928
- const phase = plan.phases.find(p => p.id === phaseId);
1929
- if (!phase)
1930
- return null;
1931
- const existing = phase.tasks.find(task => task.name.includes('[goal-check]') &&
1932
- (task.status === 'pending' || task.status === 'in_progress'));
1933
- if (existing) {
1934
- return { id: existing.id, name: existing.name };
1935
- }
1936
- const taskId = `${phaseId}-T${getNextTaskNumber(phase.tasks)}`;
1937
- const taskName = `[goal-check] ${phase.name} 목표 정합성 보강`;
1938
- const testCase = failedLabels.length > 0
1939
- ? `final goal checks pass: ${failedLabels.join('; ')}`
1940
- : 'phase done_when and checklist are satisfied';
1941
- await manager.addTasks(phaseId, [
1942
- {
1943
- id: taskId,
1944
- name: taskName,
1945
- testCase,
1946
- maxRetries: 2,
1947
- },
1948
- ]);
1949
- return { id: taskId, name: taskName };
1950
- }
1951
- async function autoReplanFailedTask(manager, phaseId, taskId, options) {
1952
- const plan = await manager.loadPlan();
1953
- if (!plan)
1954
- return null;
1955
- const phase = plan.phases.find(p => p.id === phaseId);
1956
- if (!phase || !phase.tasks || phase.tasks.length === 0)
1957
- return null;
1958
- const taskIndex = phase.tasks.findIndex(task => task.id === taskId);
1959
- if (taskIndex < 0)
1960
- return null;
1961
- const sourceTask = phase.tasks[taskIndex];
1962
- const retryCount = sourceTask.retryCount || 0;
1963
- if (retryCount <= 0)
1964
- return null;
1965
- // Freeze the repeatedly failing task so craft auto-loop can move on to replanned subtasks.
1966
- sourceTask.status = 'failed';
1967
- sourceTask.maxRetries = Math.max(1, retryCount);
1968
- sourceTask.retryCount = Math.max(retryCount, sourceTask.maxRetries);
1969
- sourceTask.lastError = mergeTaskError(sourceTask.lastError, options.reason);
1970
- const reasonSummary = sanitizeLessonText(options.reason || sourceTask.lastError || 'unknown failure', 120);
1971
- const baseTaskName = sourceTask.name;
1972
- const firstIdNumber = getNextTaskNumber(phase.tasks);
1973
- const subtasks = [
1974
- {
1975
- id: `${phaseId}-T${firstIdNumber}`,
1976
- name: `${baseTaskName} [re-plan] 원인 분해`,
1977
- status: 'pending',
1978
- retryCount: 0,
1979
- maxRetries: 2,
1980
- testCase: `반복 실패 원인을 좁힌다: ${reasonSummary}`,
1981
- lastError: undefined,
1982
- maskUsed: undefined,
1983
- },
1984
- {
1985
- id: `${phaseId}-T${firstIdNumber + 1}`,
1986
- name: `${baseTaskName} [re-plan] 최소 수정 구현`,
1987
- status: 'pending',
1988
- retryCount: 0,
1989
- maxRetries: 3,
1990
- testCase: sourceTask.testCase || '핵심 동작이 정상 동작한다',
1991
- lastError: undefined,
1992
- maskUsed: undefined,
1993
- },
1994
- {
1995
- id: `${phaseId}-T${firstIdNumber + 2}`,
1996
- name: `${baseTaskName} [re-plan] 검증 및 회귀 확인`,
1997
- status: 'pending',
1998
- retryCount: 0,
1999
- maxRetries: 2,
2000
- testCase: '관련 테스트와 검증이 통과한다',
2001
- lastError: undefined,
2002
- maskUsed: undefined,
2003
- },
2004
- ];
2005
- phase.tasks.splice(taskIndex + 1, 0, ...subtasks);
2006
- await manager.savePlan(plan);
2007
- return {
2008
- sourceTaskId: sourceTask.id,
2009
- newTaskIds: subtasks.map(task => task.id),
2010
- };
2011
- }
2012
- function getNextTaskNumber(tasks) {
2013
- let max = 0;
2014
- for (const task of tasks) {
2015
- const match = /-T(\d+)$/i.exec(task.id);
2016
- if (!match)
2017
- continue;
2018
- const value = Number.parseInt(match[1], 10);
2019
- if (Number.isFinite(value) && value > max) {
2020
- max = value;
2021
- }
2022
- }
2023
- return max + 1;
2024
- }
2025
- function mergeTaskError(existing, reason) {
2026
- const normalizedReason = reason ? sanitizeLessonText(reason, 180) : '';
2027
- const base = existing ? existing.trim() : '';
2028
- if (!normalizedReason)
2029
- return base || undefined;
2030
- if (!base)
2031
- return `Auto re-plan reason: ${normalizedReason}`;
2032
- if (base.includes(normalizedReason))
2033
- return base;
2034
- return `${base} | Auto re-plan reason: ${normalizedReason}`;
2035
- }
2036
1536
  async function syncWorkflowArtifacts(basePath, manager, options) {
2037
1537
  try {
2038
1538
  const plan = await manager.loadPlan();
@@ -2057,16 +1557,6 @@ async function syncWorkflowArtifacts(basePath, manager, options) {
2057
1557
  for (const phase of plan.phases) {
2058
1558
  const phaseDone = phase.status === 'completed';
2059
1559
  lines.push(`- [${phaseDone ? 'x' : ' '}] ${phase.id} ${phase.name} (${phase.status})`);
2060
- const tasks = phase.tasks || [];
2061
- if (tasks.length === 0) {
2062
- lines.push(' - [ ] (no tasks)');
2063
- continue;
2064
- }
2065
- for (const task of tasks) {
2066
- const taskDone = task.status === 'passed';
2067
- const retries = `${task.retryCount || 0}/${task.maxRetries || 5}`;
2068
- lines.push(` - [${taskDone ? 'x' : ' '}] ${task.id} ${task.name} (${task.status}, retries ${retries})`);
2069
- }
2070
1560
  }
2071
1561
  lines.push('');
2072
1562
  lines.push('## Review');
@@ -2103,8 +1593,8 @@ async function appendWorkflowLesson(basePath, lesson) {
2103
1593
  '# Lessons',
2104
1594
  '',
2105
1595
  '## Rules',
2106
- '- Stop and re-plan when the same task fails repeatedly.',
2107
- '- Verify behavior before marking work done.',
1596
+ '- Re-plan when implementation repeatedly stalls or fails.',
1597
+ '- Verify behavior before marking a phase complete.',
2108
1598
  '- Capture failure patterns as explicit prevention rules.',
2109
1599
  '',
2110
1600
  ].join('\n');
@@ -2120,7 +1610,7 @@ async function appendWorkflowLesson(basePath, lesson) {
2120
1610
  await writeFile(lessonsPath, `${current.trimEnd()}\n\n${entry}`, 'utf-8');
2121
1611
  }
2122
1612
  catch {
2123
- // Non-fatal: lesson capture should not block task execution.
1613
+ // Non-fatal: lesson capture should not block workflow execution.
2124
1614
  }
2125
1615
  }
2126
1616
  function resolveUnderBase(basePath, inputPath) {