@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.mjs CHANGED
@@ -225,18 +225,67 @@ async function executeWorkflow(ctx) {
225
225
  lines.push("");
226
226
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 7/8: \u5F00\u53D1\u5B9E\u73B0 \u2501\u2501\u2501"));
227
227
  lines.push("");
228
- lines.push(chalk9.yellow(" \u{1F4DD} \u5F00\u53D1\u9636\u6BB5"));
229
- lines.push(chalk9.gray(" \u8BF7\u8C03\u7528 $frontend-dev \u6267\u884C\u5F00\u53D1\u4EFB\u52A1"));
230
- lines.push(chalk9.gray(" \u6216\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
231
- return { output: lines.join("\n") };
228
+ lines.push(chalk9.yellow(" \u{1F680} \u6B63\u5728\u8C03\u7528 AI \u751F\u6210\u4EE3\u7801..."));
229
+ try {
230
+ const developResult = await executeDevelopment(ctx, activeSession);
231
+ if (developResult.success) {
232
+ lines.push(chalk9.green(" \u2713 \u4EE3\u7801\u751F\u6210\u5B8C\u6210"));
233
+ for (const file of developResult.files) {
234
+ lines.push(chalk9.gray(` - ${file}`));
235
+ }
236
+ activeSession.implFiles = developResult.files;
237
+ activeSession.phase = "review";
238
+ } else {
239
+ lines.push(chalk9.red(` \u2717 \u4EE3\u7801\u751F\u6210\u5931\u8D25: ${developResult.error}`));
240
+ lines.push(chalk9.gray(" \u8BF7\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
241
+ return { output: lines.join("\n") };
242
+ }
243
+ } catch (error) {
244
+ lines.push(chalk9.red(` \u2717 \u5F00\u53D1\u9636\u6BB5\u51FA\u9519: ${error.message}`));
245
+ lines.push(chalk9.gray(" \u8BF7\u624B\u52A8\u5B9E\u73B0\u4EE3\u7801\u540E\u8F93\u5165 continue \u7EE7\u7EED"));
246
+ return { output: lines.join("\n") };
247
+ }
232
248
  }
233
249
  if (activeSession.phase === "review") {
234
250
  lines.push("");
235
251
  lines.push(chalk9.cyan("\u2501\u2501\u2501 \u9636\u6BB5 8/8: \u4EE3\u7801\u5BA1\u6838 \u2501\u2501\u2501"));
236
252
  lines.push("");
237
- lines.push(chalk9.yellow(" \u{1F50D} \u4EE3\u7801\u5BA1\u6838\u9636\u6BB5"));
238
- lines.push(chalk9.gray(" \u8BF7\u8C03\u7528 $code-reviewer \u6267\u884C\u4EE3\u7801\u5BA1\u6838"));
239
- lines.push(chalk9.gray(" \u6216\u8F93\u5165 review \u5B8C\u6210\u5BA1\u6838"));
253
+ lines.push(chalk9.yellow(" \u{1F50D} \u6B63\u5728\u8FDB\u884C\u4EE3\u7801\u5BA1\u6838..."));
254
+ try {
255
+ const reviewResult = await executeReview(ctx, activeSession);
256
+ if (reviewResult.passed) {
257
+ lines.push(chalk9.green(" \u2713 \u4EE3\u7801\u5BA1\u6838\u901A\u8FC7"));
258
+ if (reviewResult.suggestions.length > 0) {
259
+ lines.push(chalk9.gray(" \u5EFA\u8BAE:"));
260
+ for (const s of reviewResult.suggestions.slice(0, 3)) {
261
+ lines.push(chalk9.gray(` - ${s}`));
262
+ }
263
+ }
264
+ await archiveWorkflow(ctx.options.workingDirectory);
265
+ const summary = activeSession.refinedRequirement;
266
+ activeSession = null;
267
+ lines.push("");
268
+ lines.push(chalk9.green.bold("\u{1F389} \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210\uFF01"));
269
+ lines.push(chalk9.gray(`\u9700\u6C42: ${summary.slice(0, 60)}${summary.length > 60 ? "..." : ""}`));
270
+ lines.push("");
271
+ lines.push(chalk9.cyan("\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41"));
272
+ return { output: lines.join("\n") };
273
+ } else {
274
+ lines.push(chalk9.red(" \u2717 \u4EE3\u7801\u5BA1\u6838\u672A\u901A\u8FC7"));
275
+ lines.push(chalk9.gray(" \u95EE\u9898:"));
276
+ for (const issue of reviewResult.issues.slice(0, 5)) {
277
+ lines.push(chalk9.red(` - ${issue}`));
278
+ }
279
+ lines.push("");
280
+ lines.push(chalk9.yellow(" \u8BF7\u4FEE\u590D\u95EE\u9898\u540E\u8F93\u5165 y \u91CD\u65B0\u5BA1\u6838"));
281
+ lines.push(chalk9.gray(" \u6216\u8F93\u5165 n \u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5"));
282
+ return { output: lines.join("\n") };
283
+ }
284
+ } catch (error) {
285
+ lines.push(chalk9.red(` \u2717 \u5BA1\u6838\u9636\u6BB5\u51FA\u9519: ${error.message}`));
286
+ lines.push(chalk9.gray(" \u8F93\u5165 pass \u5F3A\u5236\u901A\u8FC7\uFF0C\u6216 fix \u4FEE\u590D\u95EE\u9898"));
287
+ return { output: lines.join("\n") };
288
+ }
240
289
  }
241
290
  return { output: lines.join("\n") };
242
291
  } catch (error) {
@@ -319,19 +368,25 @@ async function handleWorkflowInput(input, ctx) {
319
368
  }
320
369
  }
321
370
  if (activeSession.phase === "review") {
322
- if (trimmed === "review" || trimmed === "\u5BA1\u6838" || trimmed === "pass" || trimmed === "\u901A\u8FC7") {
371
+ if (trimmed === "pass" || trimmed === "\u901A\u8FC7" || trimmed === "\u5F3A\u5236\u901A\u8FC7") {
323
372
  await archiveWorkflow(ctx.options.workingDirectory);
324
373
  const summary = activeSession.refinedRequirement;
325
374
  activeSession = null;
326
375
  return {
327
376
  output: chalk9.green("\u2713 \u5DE5\u4F5C\u6D41\u5DF2\u5B8C\u6210") + chalk9.gray(`
328
- \u9700\u6C42: ${summary.slice(0, 60)}...`) + chalk9.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
377
+ \u9700\u6C42: ${summary.slice(0, 60)}${summary.length > 60 ? "..." : ""}`) + chalk9.cyan("\n\n\u4F7F\u7528 /new <\u9700\u6C42> \u5F00\u59CB\u65B0\u7684\u5DE5\u4F5C\u6D41")
329
378
  };
330
379
  }
331
- if (trimmed === "fail" || trimmed === "\u5931\u8D25" || trimmed === "reject" || trimmed === "\u62D2\u7EDD") {
380
+ if (trimmed === "y" || trimmed === "yes" || trimmed === "\u91CD\u65B0\u5BA1\u6838" || trimmed === "retry") {
381
+ return executeWorkflow(ctx);
382
+ }
383
+ if (trimmed === "n" || trimmed === "no" || trimmed === "\u56DE\u9000" || trimmed === "rollback") {
332
384
  activeSession.phase = "spec";
385
+ return executeWorkflow(ctx);
386
+ }
387
+ if (trimmed === "fix" || trimmed === "\u4FEE\u590D") {
333
388
  return {
334
- output: chalk9.yellow("\u21A9\uFE0F \u5BA1\u6838\u672A\u901A\u8FC7\uFF0C\u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5") + chalk9.gray("\n\u8BF7\u91CD\u65B0\u786E\u8BA4\u6216\u4FEE\u6539\u89C4\u683C") + chalk9.yellow("\n\n\u4F7F\u7528 y \u786E\u8BA4\uFF0Cn \u91CD\u65B0\u751F\u6210")
389
+ output: chalk9.yellow("\u{1F4DD} \u8BF7\u624B\u52A8\u4FEE\u590D\u4EE3\u7801\u95EE\u9898") + chalk9.gray("\n\u4FEE\u590D\u5B8C\u6210\u540E\u8F93\u5165 y \u91CD\u65B0\u5BA1\u6838") + chalk9.gray("\n\u6216\u8F93\u5165 n \u56DE\u9000\u5230\u89C4\u683C\u9636\u6BB5")
335
390
  };
336
391
  }
337
392
  }
@@ -453,6 +508,207 @@ function getCategoryLabel(category) {
453
508
  };
454
509
  return labels[category] || category;
455
510
  }
511
+ async function executeDevelopment(ctx, session) {
512
+ const workingDir = ctx.options.workingDirectory;
513
+ const files = [];
514
+ try {
515
+ const systemPrompt = buildDevelopmentPrompt(session);
516
+ const messages = [
517
+ {
518
+ role: "system",
519
+ 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
520
+
521
+ \u8981\u6C42\uFF1A
522
+ 1. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
523
+ 2. \u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u4EE3\u7801\u98CE\u683C\u548C\u89C4\u8303
524
+ 3. \u4F7F\u7528\u9879\u76EE\u6307\u5B9A\u7684\u6280\u672F\u6808
525
+ 4. \u4EE3\u7801\u8981\u6709\u9002\u5F53\u7684\u6CE8\u91CA
526
+ 5. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
527
+
528
+ \u9879\u76EE\u4FE1\u606F\uFF1A
529
+ - \u540D\u79F0: ${session.context?.name}
530
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
531
+ - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
532
+
533
+ ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
534
+ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
535
+ },
536
+ {
537
+ role: "user",
538
+ content: systemPrompt
539
+ }
540
+ ];
541
+ const response = await ctx.modelService.sendMessage(messages, {
542
+ temperature: 0.3,
543
+ maxTokens: 8e3,
544
+ agent: "frontend-dev"
545
+ });
546
+ const codeBlocks = parseCodeBlocks(response.content);
547
+ for (const block of codeBlocks) {
548
+ const filePath = path5.join(workingDir, block.filename);
549
+ const dir = path5.dirname(filePath);
550
+ await fs4.mkdir(dir, { recursive: true });
551
+ await fs4.writeFile(filePath, block.code, "utf-8");
552
+ files.push(block.filename);
553
+ }
554
+ if (files.length === 0) {
555
+ const implDir = path5.join(workingDir, "src", "features");
556
+ await fs4.mkdir(implDir, { recursive: true });
557
+ const featureName = session.specItems[0]?.title || "feature";
558
+ const fileName = `${featureName.replace(/[^a-zA-Z0-9]/g, "_")}.ts`;
559
+ const filePath = path5.join(implDir, fileName);
560
+ const stubCode = `/**
561
+ * ${session.requirement}
562
+ *
563
+ * TODO: \u6B64\u6587\u4EF6\u7531 AI \u81EA\u52A8\u751F\u6210\uFF0C\u8BF7\u6839\u636E\u9700\u6C42\u5B8C\u5584\u5B9E\u73B0
564
+ */
565
+
566
+ export function ${featureName.replace(/[^a-zA-Z0-9]/g, "")}() {
567
+ // TODO: \u5B9E\u73B0\u529F\u80FD
568
+ console.log('${featureName} - \u5F85\u5B9E\u73B0');
569
+ }
570
+ `;
571
+ await fs4.writeFile(filePath, stubCode, "utf-8");
572
+ files.push(`src/features/${fileName}`);
573
+ }
574
+ return { success: true, files };
575
+ } catch (error) {
576
+ return {
577
+ success: false,
578
+ files: [],
579
+ error: error.message
580
+ };
581
+ }
582
+ }
583
+ function buildDevelopmentPrompt(session) {
584
+ const lines = [];
585
+ lines.push("## \u9700\u6C42\u63CF\u8FF0");
586
+ lines.push(session.refinedRequirement);
587
+ lines.push("");
588
+ lines.push("## BDD \u573A\u666F");
589
+ for (const scenario of session.bddScenarios) {
590
+ lines.push(`### ${scenario.feature}`);
591
+ for (const s of scenario.scenarios) {
592
+ lines.push(`- ${s.name}`);
593
+ }
594
+ }
595
+ lines.push("");
596
+ lines.push("## \u4EFB\u52A1\u5217\u8868");
597
+ for (const item of session.specItems) {
598
+ lines.push(`- [${item.id}] ${item.title}: ${item.description}`);
599
+ }
600
+ lines.push("");
601
+ lines.push("\u8BF7\u6839\u636E\u4EE5\u4E0A\u9700\u6C42\u89C4\u683C\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002");
602
+ return lines.join("\n");
603
+ }
604
+ function parseCodeBlocks(content) {
605
+ const blocks = [];
606
+ const regex = /```(\S+)\n([\s\S]*?)```/g;
607
+ let match;
608
+ while ((match = regex.exec(content)) !== null) {
609
+ const filename = match[1];
610
+ const code = match[2].trim();
611
+ if (["text", "json", "markdown", "md"].includes(filename.toLowerCase())) {
612
+ continue;
613
+ }
614
+ blocks.push({ filename, code });
615
+ }
616
+ return blocks;
617
+ }
618
+ async function executeReview(ctx, session) {
619
+ const workingDir = ctx.options.workingDirectory;
620
+ const issues = [];
621
+ const suggestions = [];
622
+ try {
623
+ const codeContents = [];
624
+ for (const file of session.implFiles) {
625
+ try {
626
+ const content = await fs4.readFile(path5.join(workingDir, file), "utf-8");
627
+ codeContents.push(`// ${file}
628
+ ${content}`);
629
+ } catch {
630
+ }
631
+ }
632
+ const testContents = [];
633
+ for (const file of session.testFiles) {
634
+ try {
635
+ const content = await fs4.readFile(path5.join(workingDir, file), "utf-8");
636
+ testContents.push(`// ${file}
637
+ ${content}`);
638
+ } catch {
639
+ }
640
+ }
641
+ const messages = [
642
+ {
643
+ role: "system",
644
+ 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
645
+
646
+ \u5BA1\u6838\u6807\u51C6\uFF1A
647
+ 1. \u4EE3\u7801\u662F\u5426\u6B63\u786E\u5B9E\u73B0\u4E86\u9700\u6C42\u89C4\u683C\u4E2D\u7684\u529F\u80FD
648
+ 2. \u4EE3\u7801\u8D28\u91CF\uFF08\u53EF\u8BFB\u6027\u3001\u53EF\u7EF4\u62A4\u6027\uFF09
649
+ 3. \u6F5C\u5728\u7684 bug \u548C\u8FB9\u754C\u60C5\u51B5\u5904\u7406
650
+ 4. \u4EE3\u7801\u98CE\u683C\u662F\u5426\u7B26\u5408\u9879\u76EE\u89C4\u8303
651
+ 5. \u6D4B\u8BD5\u8986\u76D6\u662F\u5426\u5145\u5206
652
+
653
+ \u8FD4\u56DE\u683C\u5F0F\uFF1A
654
+ - \u5982\u679C\u901A\u8FC7\u5BA1\u6838\uFF0C\u8FD4\u56DE "\u5BA1\u6838\u901A\u8FC7" \u5E76\u5217\u51FA\u6539\u8FDB\u5EFA\u8BAE
655
+ - \u5982\u679C\u4E0D\u901A\u8FC7\uFF0C\u5217\u51FA\u5177\u4F53\u95EE\u9898
656
+
657
+ \u9879\u76EE\u4FE1\u606F\uFF1A
658
+ - \u6846\u67B6: ${session.context?.framework || "\u672A\u6307\u5B9A"}
659
+ - \u6280\u672F\u6808: ${session.context?.techStack.join(", ") || "\u672A\u6307\u5B9A"}
660
+
661
+ ${session.context?.devStandards ? `\u5F00\u53D1\u89C4\u8303\uFF1A
662
+ ${session.context.devStandards.slice(0, 1e3)}` : ""}`
663
+ },
664
+ {
665
+ role: "user",
666
+ content: `## \u9700\u6C42\u89C4\u683C
667
+ ${session.refinedRequirement}
668
+
669
+ ## \u4EFB\u52A1\u5217\u8868
670
+ ${session.specItems.map((item) => `- [${item.id}] ${item.title}`).join("\n")}
671
+
672
+ ## \u5B9E\u73B0\u4EE3\u7801
673
+ ${codeContents.join("\n\n") || "\uFF08\u65E0\u4EE3\u7801\u6587\u4EF6\uFF09"}
674
+
675
+ ## \u6D4B\u8BD5\u4EE3\u7801
676
+ ${testContents.join("\n\n") || "\uFF08\u65E0\u6D4B\u8BD5\u6587\u4EF6\uFF09"}
677
+
678
+ \u8BF7\u5BA1\u6838\u4EE5\u4E0A\u4EE3\u7801\u5B9E\u73B0\u3002`
679
+ }
680
+ ];
681
+ const response = await ctx.modelService.sendMessage(messages, {
682
+ temperature: 0.2,
683
+ maxTokens: 2e3,
684
+ agent: "code-reviewer"
685
+ });
686
+ const result = response.content;
687
+ const passed = result.includes("\u5BA1\u6838\u901A\u8FC7") || result.includes("\u901A\u8FC7") || !result.includes("\u4E0D\u901A\u8FC7");
688
+ const issueMatches = result.match(/问题[::]\s*([\s\S]*?)(?=建议|$)/i);
689
+ if (issueMatches) {
690
+ const issueList = issueMatches[1].split(/[\n-]/).filter((s) => s.trim());
691
+ issues.push(...issueList.map((s) => s.trim()).filter((s) => s));
692
+ }
693
+ const suggestionMatches = result.match(/建议[::]\s*([\s\S]*?)$/i);
694
+ if (suggestionMatches) {
695
+ const suggestionList = suggestionMatches[1].split(/[\n-]/).filter((s) => s.trim());
696
+ suggestions.push(...suggestionList.map((s) => s.trim()).filter((s) => s));
697
+ }
698
+ if (issues.length === 0 && !passed) {
699
+ const lines = result.split("\n").filter((l) => l.includes("\u95EE\u9898") || l.includes("\u9519\u8BEF") || l.includes("\u7F3A\u9677"));
700
+ issues.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
701
+ }
702
+ if (suggestions.length === 0) {
703
+ const lines = result.split("\n").filter((l) => l.includes("\u5EFA\u8BAE") || l.includes("\u6539\u8FDB") || l.includes("\u4F18\u5316"));
704
+ suggestions.push(...lines.map((l) => l.replace(/^[-*]\s*/, "").trim()).filter((l) => l));
705
+ }
706
+ return { passed, issues, suggestions };
707
+ } catch (error) {
708
+ suggestions.push(`\u5BA1\u6838\u8FC7\u7A0B\u51FA\u9519: ${error.message}`);
709
+ return { passed: true, issues, suggestions };
710
+ }
711
+ }
456
712
  async function readProjectContext(workingDir) {
457
713
  const context = {
458
714
  name: path5.basename(workingDir),