@nick848/sf-cli 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -250,18 +250,67 @@ async function executeWorkflow(ctx) {
250
250
  lines.push("");
251
251
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/8: \u5F00\u53D1\u5B9E\u73B0 \u2501\u2501\u2501"));
252
252
  lines.push("");
253
- lines.push(chalk9__default.default.yellow(" \u{1F4DD} \u5F00\u53D1\u9636\u6BB5"));
254
- lines.push(chalk9__default.default.gray(" \u8BF7\u8C03\u7528 $frontend-dev \u6267\u884C\u5F00\u53D1\u4EFB\u52A1"));
255
- lines.push(chalk9__default.default.gray(" \u6216\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
256
- return { output: lines.join("\n") };
253
+ lines.push(chalk9__default.default.yellow(" \u{1F680} \u6B63\u5728\u8C03\u7528 AI \u751F\u6210\u4EE3\u7801..."));
254
+ try {
255
+ const developResult = await executeDevelopment(ctx, activeSession);
256
+ if (developResult.success) {
257
+ lines.push(chalk9__default.default.green(" \u2713 \u4EE3\u7801\u751F\u6210\u5B8C\u6210"));
258
+ for (const file of developResult.files) {
259
+ lines.push(chalk9__default.default.gray(` - ${file}`));
260
+ }
261
+ activeSession.implFiles = developResult.files;
262
+ activeSession.phase = "review";
263
+ } else {
264
+ lines.push(chalk9__default.default.red(` \u2717 \u4EE3\u7801\u751F\u6210\u5931\u8D25: ${developResult.error}`));
265
+ lines.push(chalk9__default.default.gray(" \u8BF7\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
266
+ return { output: lines.join("\n") };
267
+ }
268
+ } catch (error) {
269
+ lines.push(chalk9__default.default.red(` \u2717 \u5F00\u53D1\u9636\u6BB5\u51FA\u9519: ${error.message}`));
270
+ lines.push(chalk9__default.default.gray(" \u8BF7\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
271
+ return { output: lines.join("\n") };
272
+ }
257
273
  }
258
274
  if (activeSession.phase === "review") {
259
275
  lines.push("");
260
276
  lines.push(chalk9__default.default.cyan("\u2501\u2501\u2501 \u9636\u6BB5 8/8: \u4EE3\u7801\u5BA1\u6838 \u2501\u2501\u2501"));
261
277
  lines.push("");
262
- lines.push(chalk9__default.default.yellow(" \u{1F50D} \u4EE3\u7801\u5BA1\u6838\u9636\u6BB5"));
263
- lines.push(chalk9__default.default.gray(" \u8BF7\u8C03\u7528 $code-reviewer \u6267\u884C\u4EE3\u7801\u5BA1\u6838"));
264
- lines.push(chalk9__default.default.gray(" \u6216\u8F93\u5165 review \u5B8C\u6210\u5BA1\u6838"));
278
+ lines.push(chalk9__default.default.yellow(" \u{1F50D} \u6B63\u5728\u8FDB\u884C\u4EE3\u7801\u5BA1\u6838..."));
279
+ try {
280
+ const reviewResult = await executeReview(ctx, activeSession);
281
+ if (reviewResult.passed) {
282
+ lines.push(chalk9__default.default.green(" \u2713 \u4EE3\u7801\u5BA1\u6838\u901A\u8FC7"));
283
+ if (reviewResult.suggestions.length > 0) {
284
+ lines.push(chalk9__default.default.gray(" \u5EFA\u8BAE:"));
285
+ for (const s of reviewResult.suggestions.slice(0, 3)) {
286
+ lines.push(chalk9__default.default.gray(` - ${s}`));
287
+ }
288
+ }
289
+ await archiveWorkflow(ctx.options.workingDirectory);
290
+ const summary = activeSession.refinedRequirement;
291
+ activeSession = null;
292
+ lines.push("");
293
+ lines.push(chalk9__default.default.green.bold("\u{1F389} \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210\uFF01"));
294
+ lines.push(chalk9__default.default.gray(`\u9700\u6C42: ${summary.slice(0, 60)}${summary.length > 60 ? "..." : ""}`));
295
+ lines.push("");
296
+ lines.push(chalk9__default.default.cyan("\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41"));
297
+ return { output: lines.join("\n") };
298
+ } else {
299
+ lines.push(chalk9__default.default.red(" \u2717 \u4EE3\u7801\u5BA1\u6838\u672A\u901A\u8FC7"));
300
+ lines.push(chalk9__default.default.gray(" \u95EE\u9898:"));
301
+ for (const issue of reviewResult.issues.slice(0, 5)) {
302
+ lines.push(chalk9__default.default.red(` - ${issue}`));
303
+ }
304
+ lines.push("");
305
+ lines.push(chalk9__default.default.yellow(" \u8BF7\u4FEE\u590D\u95EE\u9898\u540E\u8F93\u5165 y \u91CD\u65B0\u5BA1\u6838"));
306
+ lines.push(chalk9__default.default.gray(" \u6216\u8F93\u5165 n \u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5"));
307
+ return { output: lines.join("\n") };
308
+ }
309
+ } catch (error) {
310
+ lines.push(chalk9__default.default.red(` \u2717 \u5BA1\u6838\u9636\u6BB5\u51FA\u9519: ${error.message}`));
311
+ lines.push(chalk9__default.default.gray(" \u8F93\u5165 pass \u5F3A\u5236\u901A\u8FC7\uFF0C\u6216 fix \u4FEE\u590D\u95EE\u9898"));
312
+ return { output: lines.join("\n") };
313
+ }
265
314
  }
266
315
  return { output: lines.join("\n") };
267
316
  } catch (error) {
@@ -344,19 +393,25 @@ async function handleWorkflowInput(input, ctx) {
344
393
  }
345
394
  }
346
395
  if (activeSession.phase === "review") {
347
- if (trimmed === "review" || trimmed === "\u5BA1\u6838" || trimmed === "pass" || trimmed === "\u901A\u8FC7") {
396
+ if (trimmed === "pass" || trimmed === "\u901A\u8FC7" || trimmed === "\u5F3A\u5236\u901A\u8FC7") {
348
397
  await archiveWorkflow(ctx.options.workingDirectory);
349
398
  const summary = activeSession.refinedRequirement;
350
399
  activeSession = null;
351
400
  return {
352
401
  output: chalk9__default.default.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210") + chalk9__default.default.gray(`
353
- \u9700\u6C42: ${summary.slice(0, 60)}...`) + chalk9__default.default.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
402
+ \u9700\u6C42: ${summary.slice(0, 60)}${summary.length > 60 ? "..." : ""}`) + chalk9__default.default.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
354
403
  };
355
404
  }
356
- if (trimmed === "fail" || trimmed === "\u5931\u8D25" || trimmed === "reject" || trimmed === "\u62D2\u7EDD") {
405
+ if (trimmed === "y" || trimmed === "yes" || trimmed === "\u91CD\u65B0\u5BA1\u6838" || trimmed === "retry") {
406
+ return executeWorkflow(ctx);
407
+ }
408
+ if (trimmed === "n" || trimmed === "no" || trimmed === "\u56DE\u9000" || trimmed === "rollback") {
357
409
  activeSession.phase = "spec";
410
+ return executeWorkflow(ctx);
411
+ }
412
+ if (trimmed === "fix" || trimmed === "\u4FEE\u590D") {
358
413
  return {
359
- output: chalk9__default.default.yellow("\u21A9\uFE0F \u5BA1\u6838\u672A\u901A\u8FC7\uFF0C\u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5") + chalk9__default.default.gray("\n\u8BF7\u91CD\u65B0\u786E\u8BA4\u6216\u4FEE\u6539\u89C4\u683C") + chalk9__default.default.yellow("\n\n\u4F7F\u7528 y \u786E\u8BA4\uFF0Cn \u91CD\u65B0\u751F\u6210")
414
+ output: chalk9__default.default.yellow("\u{1F4DD} \u8BF7\u624B\u52A8\u4FEE\u590D\u4EE3\u7801\u95EE\u9898") + chalk9__default.default.gray("\n\u4FEE\u590D\u5B8C\u6210\u540E\u8F93\u5165 y \u91CD\u65B0\u5BA1\u6838") + chalk9__default.default.gray("\n\u6216\u8F93\u5165 n \u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5")
360
415
  };
361
416
  }
362
417
  }
@@ -478,6 +533,207 @@ function getCategoryLabel(category) {
478
533
  };
479
534
  return labels[category] || category;
480
535
  }
536
+ async function executeDevelopment(ctx, session) {
537
+ const workingDir = ctx.options.workingDirectory;
538
+ const files = [];
539
+ try {
540
+ const systemPrompt = buildDevelopmentPrompt(session);
541
+ const messages = [
542
+ {
543
+ role: "system",
544
+ content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002
545
+
546
+ \u8981\u6C42\uFF1A
547
+ 1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
548
+ 2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
549
+ 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
550
+ 4. \u4EE3\u7801\u8981\u6709\u9002\u5F53\u7684\u6CE8\u91CA
551
+ 5. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
552
+
553
+ \u9879\u76EE\u4FE1\u606F\uFF1A
554
+ - \u540D\u79F0: ${session.context?.name}
555
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
556
+ - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
557
+
558
+ ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
559
+ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
560
+ },
561
+ {
562
+ role: "user",
563
+ content: systemPrompt
564
+ }
565
+ ];
566
+ const response = await ctx.modelService.sendMessage(messages, {
567
+ temperature: 0.3,
568
+ maxTokens: 8e3,
569
+ agent: "frontend-dev"
570
+ });
571
+ const codeBlocks = parseCodeBlocks(response.content);
572
+ for (const block of codeBlocks) {
573
+ const filePath = path4__namespace.join(workingDir, block.filename);
574
+ const dir = path4__namespace.dirname(filePath);
575
+ await fs4__namespace.mkdir(dir, { recursive: true });
576
+ await fs4__namespace.writeFile(filePath, block.code, "utf-8");
577
+ files.push(block.filename);
578
+ }
579
+ if (files.length === 0) {
580
+ const implDir = path4__namespace.join(workingDir, "src", "features");
581
+ await fs4__namespace.mkdir(implDir, { recursive: true });
582
+ const featureName = session.specItems[0]?.title || "feature";
583
+ const fileName = `${featureName.replace(/[^a-zA-Z0-9]/g, "_")}.ts`;
584
+ const filePath = path4__namespace.join(implDir, fileName);
585
+ const stubCode = `/**
586
+ * ${session.requirement}
587
+ *
588
+ * TODO: \u6B64\u6587\u4EF6\u7531 AI \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u6839\u636E\u9700\u6C42\u5B8C\u5584\u5B9E\u73B0
589
+ */
590
+
591
+ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
592
+ // TODO: \u5B9E\u73B0\u529F\u80FD
593
+ console.log('${featureName} - \u5F85\u5B9E\u73B0');
594
+ }
595
+ `;
596
+ await fs4__namespace.writeFile(filePath, stubCode, "utf-8");
597
+ files.push(`src/features/${fileName}`);
598
+ }
599
+ return { success: true, files };
600
+ } catch (error) {
601
+ return {
602
+ success: false,
603
+ files: [],
604
+ error: error.message
605
+ };
606
+ }
607
+ }
608
+ function buildDevelopmentPrompt(session) {
609
+ const lines = [];
610
+ lines.push("## \u9700\u6C42\u63CF\u8FF0");
611
+ lines.push(session.refinedRequirement);
612
+ lines.push("");
613
+ lines.push("## BDD \u573A\u666F");
614
+ for (const scenario of session.bddScenarios) {
615
+ lines.push(`### ${scenario.feature}`);
616
+ for (const s of scenario.scenarios) {
617
+ lines.push(`- ${s.name}`);
618
+ }
619
+ }
620
+ lines.push("");
621
+ lines.push("## \u4EFB\u52A1\u5217\u8868");
622
+ for (const item of session.specItems) {
623
+ lines.push(`- [${item.id}] ${item.title}: ${item.description}`);
624
+ }
625
+ lines.push("");
626
+ lines.push("\u8BF7\u6839\u636E\u4EE5\u4E0A\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002");
627
+ return lines.join("\n");
628
+ }
629
+ function parseCodeBlocks(content) {
630
+ const blocks = [];
631
+ const regex = /```(\S+)\n([\s\S]*?)```/g;
632
+ let match;
633
+ while ((match = regex.exec(content)) !== null) {
634
+ const filename = match[1];
635
+ const code = match[2].trim();
636
+ if (["text", "json", "markdown", "md"].includes(filename.toLowerCase())) {
637
+ continue;
638
+ }
639
+ blocks.push({ filename, code });
640
+ }
641
+ return blocks;
642
+ }
643
+ async function executeReview(ctx, session) {
644
+ const workingDir = ctx.options.workingDirectory;
645
+ const issues = [];
646
+ const suggestions = [];
647
+ try {
648
+ const codeContents = [];
649
+ for (const file of session.implFiles) {
650
+ try {
651
+ const content = await fs4__namespace.readFile(path4__namespace.join(workingDir, file), "utf-8");
652
+ codeContents.push(`// ${file}
653
+ ${content}`);
654
+ } catch {
655
+ }
656
+ }
657
+ const testContents = [];
658
+ for (const file of session.testFiles) {
659
+ try {
660
+ const content = await fs4__namespace.readFile(path4__namespace.join(workingDir, file), "utf-8");
661
+ testContents.push(`// ${file}
662
+ ${content}`);
663
+ } catch {
664
+ }
665
+ }
666
+ const messages = [
667
+ {
668
+ role: "system",
669
+ content: `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u4EE3\u7801\u5BA1\u6838\u4E13\u5BB6\u3002\u8BF7\u5BA1\u6838\u4EE5\u4E0B\u4EE3\u7801\u5B9E\u73B0\u662F\u5426\u7B26\u5408\u9700\u6C42\u89C4\u683C\u3002
670
+
671
+ \u5BA1\u6838\u6807\u51C6\uFF1A
672
+ 1. \u4EE3\u7801\u662F\u5426\u6B63\u786E\u5B9E\u73B0\u4E86\u9700\u6C42\u89C4\u683C\u4E2D\u7684\u529F\u80FD
673
+ 2. \u4EE3\u7801\u8D28\u91CF\uFF08\u53EF\u8BFB\u6027\u3001\u53EF\u7EF4\u62A4\u6027\uFF09
674
+ 3. \u6F5C\u5728\u7684 bug \u548C\u8FB9\u754C\u60C5\u51B5\u5904\u7406
675
+ 4. \u4EE3\u7801\u98CE\u683C\u662F\u5426\u7B26\u5408\u9879\u76EE\u89C4\u8303
676
+ 5. \u6D4B\u8BD5\u8986\u76D6\u662F\u5426\u5145\u5206
677
+
678
+ \u8FD4\u56DE\u683C\u5F0F\uFF1A
679
+ - \u5982\u679C\u901A\u8FC7\u5BA1\u6838\uFF0C\u8FD4\u56DE "\u5BA1\u6838\u901A\u8FC7" \u5E76\u5217\u51FA\u6539\u8FDB\u5EFA\u8BAE
680
+ - \u5982\u679C\u4E0D\u901A\u8FC7\uFF0C\u5217\u51FA\u5177\u4F53\u95EE\u9898
681
+
682
+ \u9879\u76EE\u4FE1\u606F\uFF1A
683
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
684
+ - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
685
+
686
+ ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
687
+ ${session.context.devStandards.slice(0, 1e3)}` : ""}`
688
+ },
689
+ {
690
+ role: "user",
691
+ content: `## \u9700\u6C42\u89C4\u683C
692
+ ${session.refinedRequirement}
693
+
694
+ ## \u4EFB\u52A1\u5217\u8868
695
+ ${session.specItems.map((item) => `- [${item.id}] ${item.title}`).join("\n")}
696
+
697
+ ## \u5B9E\u73B0\u4EE3\u7801
698
+ ${codeContents.join("\n\n") || "\uFF08\u65E0\u4EE3\u7801\u6587\u4EF6\uFF09"}
699
+
700
+ ## \u6D4B\u8BD5\u4EE3\u7801
701
+ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
702
+
703
+ \u8BF7\u5BA1\u6838\u4EE5\u4E0A\u4EE3\u7801\u5B9E\u73B0\u3002`
704
+ }
705
+ ];
706
+ const response = await ctx.modelService.sendMessage(messages, {
707
+ temperature: 0.2,
708
+ maxTokens: 2e3,
709
+ agent: "code-reviewer"
710
+ });
711
+ const result = response.content;
712
+ const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
713
+ const issueMatches = result.match(/问题[::]\s*([\s\S]*?)(?=建议|$)/i);
714
+ if (issueMatches) {
715
+ const issueList = issueMatches[1].split(/[\n-]/).filter((s) => s.trim());
716
+ issues.push(...issueList.map((s) => s.trim()).filter((s) => s));
717
+ }
718
+ const suggestionMatches = result.match(/建议[::]\s*([\s\S]*?)$/i);
719
+ if (suggestionMatches) {
720
+ const suggestionList = suggestionMatches[1].split(/[\n-]/).filter((s) => s.trim());
721
+ suggestions.push(...suggestionList.map((s) => s.trim()).filter((s) => s));
722
+ }
723
+ if (issues.length === 0 && !passed) {
724
+ const lines = result.split("\n").filter((l) => l.includes("\u95EE\u9898") || l.includes("\u9519\u8BEF") || l.includes("\u7F3A\u9677"));
725
+ issues.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
726
+ }
727
+ if (suggestions.length === 0) {
728
+ const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
729
+ suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
730
+ }
731
+ return { passed, issues, suggestions };
732
+ } catch (error) {
733
+ suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
734
+ return { passed: true, issues, suggestions };
735
+ }
736
+ }
481
737
  async function readProjectContext(workingDir) {
482
738
  const context = {
483
739
  name: path4__namespace.basename(workingDir),