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.
- package/README.ko.md +64 -47
- package/README.md +63 -46
- package/assets/commands/weave-craft.md +26 -398
- package/assets/commands/weave-flow.md +13 -30
- package/assets/commands/weave-help.md +4 -4
- package/assets/commands/weave-prepare.md +1 -1
- package/assets/commands/weave-refine-plan.md +0 -1
- package/assets/commands/weave-status.md +2 -7
- package/dist/plugin/tools/slashcommand.js +6 -6
- package/dist/plugin/tools/slashcommand.js.map +1 -1
- package/dist/plugin/tools/weave.d.ts +0 -2
- package/dist/plugin/tools/weave.d.ts.map +1 -1
- package/dist/plugin/tools/weave.js +316 -826
- package/dist/plugin/tools/weave.js.map +1 -1
- package/dist/shared/config.d.ts +14 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +7 -0
- package/dist/shared/config.js.map +1 -1
- package/dist/shared-context/test/squad.test.js +2 -2
- package/dist/shared-context/test/squad.test.js.map +1 -1
- package/dist/shared-context/test/storage.test.js +3 -3
- package/dist/shared-context/test/storage.test.js.map +1 -1
- package/dist/shared-context/test/task.test.js +6 -6
- package/dist/shared-context/test/task.test.js.map +1 -1
- package/dist/weave/gdc.d.ts +59 -0
- package/dist/weave/gdc.d.ts.map +1 -0
- package/dist/weave/gdc.js +221 -0
- package/dist/weave/gdc.js.map +1 -0
- package/dist/weave/stages/execute.d.ts.map +1 -1
- package/dist/weave/stages/execute.js +6 -7
- package/dist/weave/stages/execute.js.map +1 -1
- package/dist/weave/stages/research.d.ts +1 -1
- package/dist/weave/stages/research.d.ts.map +1 -1
- package/dist/weave/stages/research.js +225 -5
- package/dist/weave/stages/research.js.map +1 -1
- 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,
|
|
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 {
|
|
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
|
|
42
|
-
- craft [phaseId]:
|
|
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
|
|
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('
|
|
646
|
-
lines.push(`
|
|
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: '
|
|
819
|
+
trigger: 'plan_gate_failed_bypassed',
|
|
650
820
|
pattern: `Phase ${resolvedPhaseId} failed plan gate checks: ${planGate.failedLabels.join(', ')}`,
|
|
651
|
-
rule: '
|
|
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
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
693
|
-
|
|
864
|
+
const gdcGate = await runGdcVerifyGate(basePath);
|
|
865
|
+
if (gdcGate.applied && gdcGate.report) {
|
|
866
|
+
lines.push(gdcGate.report);
|
|
867
|
+
lines.push('');
|
|
694
868
|
}
|
|
695
|
-
|
|
696
|
-
|
|
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
|
|
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('###
|
|
989
|
+
lines.push('### Next Steps');
|
|
823
990
|
lines.push('');
|
|
824
|
-
lines.push(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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]\` |
|
|
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
|
|
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 #
|
|
1848
|
-
weave craft #
|
|
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
|
-
? '
|
|
1863
|
-
: '
|
|
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
|
|
1516
|
+
label: 'Test coverage exists',
|
|
1868
1517
|
passed: /\btest\b|테스트/.test(taskText),
|
|
1869
1518
|
},
|
|
1870
1519
|
{
|
|
1871
|
-
label: 'Verification/build
|
|
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
|
-
'-
|
|
2107
|
-
'- Verify behavior before marking
|
|
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
|
|
1613
|
+
// Non-fatal: lesson capture should not block workflow execution.
|
|
2124
1614
|
}
|
|
2125
1615
|
}
|
|
2126
1616
|
function resolveUnderBase(basePath, inputPath) {
|