@nick848/sf-cli 1.0.22 → 1.0.23
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/cli/index.js +341 -108
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +338 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +337 -104
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import * as path5 from 'path';
|
|
|
2
2
|
import path5__default from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import chalk9 from 'chalk';
|
|
5
|
-
import * as
|
|
5
|
+
import * as fs9 from 'fs/promises';
|
|
6
6
|
import * as fs10 from 'fs';
|
|
7
7
|
import * as crypto from 'crypto';
|
|
8
8
|
import * as os from 'os';
|
|
@@ -642,24 +642,13 @@ async function executeDevelopment(ctx, session) {
|
|
|
642
642
|
loader.start();
|
|
643
643
|
try {
|
|
644
644
|
const taskPrompt = buildTaskPrompt(session, item, i);
|
|
645
|
+
const structureInfo = session.context?.projectStructure;
|
|
646
|
+
const structureGuide = buildStructureGuide(structureInfo);
|
|
647
|
+
const systemPrompt = buildCodeGenerationSystemPrompt(session, structureGuide);
|
|
645
648
|
const messages = [
|
|
646
649
|
{
|
|
647
650
|
role: "system",
|
|
648
|
-
content:
|
|
649
|
-
|
|
650
|
-
\u26A0\uFE0F \u91CD\u8981\u89C4\u5219\uFF1A
|
|
651
|
-
1. \u53EA\u751F\u6210\u5F53\u524D\u4EFB\u52A1\u76F8\u5173\u7684\u4EE3\u7801\uFF0C\u4E0D\u8981\u751F\u6210\u5176\u4ED6\u4EFB\u52A1\u7684\u4EE3\u7801
|
|
652
|
-
2. \u6280\u672F\u5B9E\u73B0\u5FC5\u987B\u4E25\u683C\u9075\u5FAA\u9879\u76EE\u73B0\u6709\u7684\u5F00\u53D1\u89C4\u8303
|
|
653
|
-
3. \u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801
|
|
654
|
-
4. \u8FD4\u56DE\u683C\u5F0F\uFF1A\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u4EE3\u7801 \`\`\` \u5305\u88F9
|
|
655
|
-
|
|
656
|
-
\u9879\u76EE\u4FE1\u606F\uFF1A
|
|
657
|
-
- \u540D\u79F0: ${session.context?.name}
|
|
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 ? `\u3010\u5F00\u53D1\u89C4\u8303\u3011
|
|
662
|
-
${session.context.devStandards.slice(0, 2e3)}` : ""}`
|
|
651
|
+
content: systemPrompt
|
|
663
652
|
},
|
|
664
653
|
{
|
|
665
654
|
role: "user",
|
|
@@ -679,17 +668,17 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
|
|
|
679
668
|
for (const block of codeBlocks) {
|
|
680
669
|
const filePath = path5.join(workingDir, block.filename);
|
|
681
670
|
const dir = path5.dirname(filePath);
|
|
682
|
-
await
|
|
683
|
-
await
|
|
671
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
672
|
+
await fs9.writeFile(filePath, block.code, "utf-8");
|
|
684
673
|
files.push(block.filename);
|
|
685
674
|
}
|
|
686
675
|
loader.stop(chalk9.green(`${prefix} \u2713 ${item.title.slice(0, 25)} (${codeBlocks.length} \u4E2A\u6587\u4EF6)`));
|
|
687
676
|
} else {
|
|
688
677
|
const implDir = path5.join(workingDir, "src");
|
|
689
|
-
await
|
|
678
|
+
await fs9.mkdir(implDir, { recursive: true });
|
|
690
679
|
const fileName = `${item.title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_")}.ts`;
|
|
691
680
|
const filePath = path5.join(implDir, fileName);
|
|
692
|
-
await
|
|
681
|
+
await fs9.writeFile(filePath, `// TODO: ${item.title}
|
|
693
682
|
// ${item.description}
|
|
694
683
|
`, "utf-8");
|
|
695
684
|
files.push(`src/${fileName}`);
|
|
@@ -712,6 +701,91 @@ ${session.context.devStandards.slice(0, 2e3)}` : ""}`
|
|
|
712
701
|
};
|
|
713
702
|
}
|
|
714
703
|
}
|
|
704
|
+
function buildStructureGuide(structure) {
|
|
705
|
+
if (!structure) {
|
|
706
|
+
return `## \u9879\u76EE\u7ED3\u6784
|
|
707
|
+
- \u7EC4\u4EF6\u653E\u5728 src/components/
|
|
708
|
+
- \u9875\u9762\u653E\u5728 src/pages/
|
|
709
|
+
- \u5DE5\u5177\u51FD\u6570\u653E\u5728 src/utils/`;
|
|
710
|
+
}
|
|
711
|
+
const lines = ["## \u9879\u76EE\u7ED3\u6784\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09"];
|
|
712
|
+
if (structure.hasSrc) {
|
|
713
|
+
lines.push(`- \u6E90\u7801\u76EE\u5F55: ${structure.srcDir}/`);
|
|
714
|
+
}
|
|
715
|
+
if (structure.hasPages) {
|
|
716
|
+
lines.push(`- \u9875\u9762\u76EE\u5F55: ${structure.pagesDir}/ \uFF08\u9875\u9762\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
|
|
717
|
+
} else {
|
|
718
|
+
lines.push(`- \u9875\u9762\u76EE\u5F55: src/pages/ \uFF08\u9875\u9762\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
|
|
719
|
+
}
|
|
720
|
+
if (structure.hasComponents) {
|
|
721
|
+
lines.push(`- \u7EC4\u4EF6\u76EE\u5F55: ${structure.componentsDir}/ \uFF08\u901A\u7528\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
|
|
722
|
+
} else {
|
|
723
|
+
lines.push(`- \u7EC4\u4EF6\u76EE\u5F55: src/components/ \uFF08\u901A\u7528\u7EC4\u4EF6\u653E\u8FD9\u91CC\uFF09`);
|
|
724
|
+
}
|
|
725
|
+
if (structure.hasApi) {
|
|
726
|
+
lines.push(`- API \u76EE\u5F55: src/api/ \u6216 src/services/`);
|
|
727
|
+
}
|
|
728
|
+
if (structure.hasHooks) {
|
|
729
|
+
lines.push(`- Hooks \u76EE\u5F55: src/hooks/`);
|
|
730
|
+
}
|
|
731
|
+
if (structure.hasStore) {
|
|
732
|
+
lines.push(`- \u72B6\u6001\u7BA1\u7406\u76EE\u5F55: src/store/`);
|
|
733
|
+
}
|
|
734
|
+
if (structure.hasUtils) {
|
|
735
|
+
lines.push(`- \u5DE5\u5177\u51FD\u6570\u76EE\u5F55: src/utils/`);
|
|
736
|
+
}
|
|
737
|
+
lines.push("");
|
|
738
|
+
lines.push("\u26A0\uFE0F \u6587\u4EF6\u5FC5\u987B\u653E\u5728\u4E0A\u8FF0\u5BF9\u5E94\u76EE\u5F55\u4E2D\uFF0C\u4E0D\u8981\u968F\u610F\u521B\u5EFA\u65B0\u76EE\u5F55");
|
|
739
|
+
return lines.join("\n");
|
|
740
|
+
}
|
|
741
|
+
function buildCodeGenerationSystemPrompt(session, structureGuide) {
|
|
742
|
+
const framework = session.context?.framework || "React";
|
|
743
|
+
const techStack = session.context?.techStack?.join(", ") || "TypeScript";
|
|
744
|
+
const devStandards = session.context?.devStandards || "";
|
|
745
|
+
const standardsText = devStandards.slice(0, 6e3);
|
|
746
|
+
return `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6839\u636E\u4EFB\u52A1\u63CF\u8FF0\u751F\u6210\u4EE3\u7801\u5B9E\u73B0\u3002
|
|
747
|
+
|
|
748
|
+
## \u26A0\uFE0F \u6838\u5FC3\u89C4\u5219\uFF08\u5FC5\u987B\u9075\u5B88\uFF09
|
|
749
|
+
|
|
750
|
+
1. **\u8BED\u8A00**: \u5FC5\u987B\u4F7F\u7528 TypeScript (.tsx/.ts \u6587\u4EF6)
|
|
751
|
+
2. **\u6846\u67B6**: ${framework}
|
|
752
|
+
3. **\u53EA\u751F\u6210\u5F53\u524D\u4EFB\u52A1\u76F8\u5173\u7684\u4EE3\u7801**\uFF0C\u4E0D\u8981\u751F\u6210\u5176\u4ED6\u4EFB\u52A1\u7684\u4EE3\u7801
|
|
753
|
+
4. **\u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u6B63\u786E**\uFF1A\u4E25\u683C\u6309\u7167\u9879\u76EE\u7ED3\u6784\u653E\u7F6E\u6587\u4EF6
|
|
754
|
+
5. **\u5FC5\u987B\u751F\u6210\u5B8C\u6574\u3001\u53EF\u8FD0\u884C\u7684\u4EE3\u7801**\uFF0C\u4E0D\u8981\u5199 TODO \u6216\u5360\u4F4D\u7B26
|
|
755
|
+
6. **\u5982\u679C\u4EFB\u52A1\u6D89\u53CA\u9875\u9762**\uFF0C\u5FC5\u987B\u751F\u6210\u9875\u9762\u7EC4\u4EF6
|
|
756
|
+
7. **\u5982\u679C\u4EFB\u52A1\u6D89\u53CA UI**\uFF0C\u5FC5\u987B\u751F\u6210\u5BF9\u5E94\u7684\u7EC4\u4EF6\u548C\u6837\u5F0F
|
|
757
|
+
|
|
758
|
+
${structureGuide}
|
|
759
|
+
|
|
760
|
+
## \u9879\u76EE\u4FE1\u606F
|
|
761
|
+
|
|
762
|
+
- \u540D\u79F0: ${session.context?.name || "\u672A\u547D\u540D\u9879\u76EE"}
|
|
763
|
+
- \u6846\u67B6: ${framework}
|
|
764
|
+
- \u6280\u672F\u6808: ${techStack}
|
|
765
|
+
|
|
766
|
+
## \u5F00\u53D1\u89C4\u8303\uFF08\u5FC5\u987B\u9075\u5FAA\uFF09
|
|
767
|
+
|
|
768
|
+
${standardsText || `### \u9ED8\u8BA4\u89C4\u8303
|
|
769
|
+
- \u4F7F\u7528\u51FD\u6570\u5F0F\u7EC4\u4EF6
|
|
770
|
+
- \u4F7F\u7528 TypeScript \u7C7B\u578B\u5B9A\u4E49
|
|
771
|
+
- \u4F7F\u7528 async/await \u5904\u7406\u5F02\u6B65
|
|
772
|
+
- \u53D8\u91CF\u4F7F\u7528 camelCase\uFF0C\u7EC4\u4EF6\u4F7F\u7528 PascalCase
|
|
773
|
+
- \u5BFC\u51FA\u4F7F\u7528 export default \u6216 export const`}
|
|
774
|
+
|
|
775
|
+
## \u8F93\u51FA\u683C\u5F0F
|
|
776
|
+
|
|
777
|
+
\u6BCF\u4E2A\u6587\u4EF6\u7528 \`\`\`filename \u5305\u88F9\uFF0C\u4F8B\u5982\uFF1A
|
|
778
|
+
|
|
779
|
+
\`\`\`src/pages/HomePage.tsx
|
|
780
|
+
// \u4EE3\u7801\u5185\u5BB9
|
|
781
|
+
\`\`\`
|
|
782
|
+
|
|
783
|
+
\`\`\`src/components/Button/Button.tsx
|
|
784
|
+
// \u4EE3\u7801\u5185\u5BB9
|
|
785
|
+
\`\`\`
|
|
786
|
+
|
|
787
|
+
\u73B0\u5728\u8BF7\u6839\u636E\u4EFB\u52A1\u8981\u6C42\u751F\u6210\u4EE3\u7801\u3002`;
|
|
788
|
+
}
|
|
715
789
|
function buildTaskPrompt(session, item, index) {
|
|
716
790
|
const lines = [];
|
|
717
791
|
lines.push(`## \u5F53\u524D\u4EFB\u52A1 (#${index + 1})`);
|
|
@@ -766,7 +840,7 @@ async function executeReview(ctx, session) {
|
|
|
766
840
|
const codeContents = [];
|
|
767
841
|
for (const file of session.implFiles) {
|
|
768
842
|
try {
|
|
769
|
-
const content = await
|
|
843
|
+
const content = await fs9.readFile(path5.join(workingDir, file), "utf-8");
|
|
770
844
|
codeContents.push(`// ${file}
|
|
771
845
|
${content}`);
|
|
772
846
|
} catch {
|
|
@@ -775,7 +849,7 @@ ${content}`);
|
|
|
775
849
|
const testContents = [];
|
|
776
850
|
for (const file of session.testFiles) {
|
|
777
851
|
try {
|
|
778
|
-
const content = await
|
|
852
|
+
const content = await fs9.readFile(path5.join(workingDir, file), "utf-8");
|
|
779
853
|
testContents.push(`// ${file}
|
|
780
854
|
${content}`);
|
|
781
855
|
} catch {
|
|
@@ -869,9 +943,9 @@ async function readProjectContext(workingDir) {
|
|
|
869
943
|
};
|
|
870
944
|
const agentsPath = path5.join(workingDir, "AGENTS.md");
|
|
871
945
|
try {
|
|
872
|
-
const stats = await
|
|
946
|
+
const stats = await fs9.stat(agentsPath);
|
|
873
947
|
if (stats.size <= MAX_FILE_SIZE2) {
|
|
874
|
-
context.agentsMd = await
|
|
948
|
+
context.agentsMd = await fs9.readFile(agentsPath, "utf-8");
|
|
875
949
|
const nameMatch = context.agentsMd.match(/\|\s*项目名称\s*\|\s*([^\s|]+)/);
|
|
876
950
|
if (nameMatch) context.name = nameMatch[1];
|
|
877
951
|
const typeMatch = context.agentsMd.match(/\|\s*项目类型\s*\|\s*([^\s|]+)/);
|
|
@@ -885,9 +959,9 @@ async function readProjectContext(workingDir) {
|
|
|
885
959
|
}
|
|
886
960
|
const configPath = path5.join(workingDir, "openspec", "config.yaml");
|
|
887
961
|
try {
|
|
888
|
-
const stats = await
|
|
962
|
+
const stats = await fs9.stat(configPath);
|
|
889
963
|
if (stats.size <= MAX_FILE_SIZE2) {
|
|
890
|
-
context.configYaml = await
|
|
964
|
+
context.configYaml = await fs9.readFile(configPath, "utf-8");
|
|
891
965
|
const nameMatch = context.configYaml.match(/name:\s*(.+)/);
|
|
892
966
|
if (nameMatch) context.name = nameMatch[1].trim();
|
|
893
967
|
const typeMatch = context.configYaml.match(/type:\s*(.+)/);
|
|
@@ -908,14 +982,173 @@ async function readProjectContext(workingDir) {
|
|
|
908
982
|
}
|
|
909
983
|
const devStandardsPath = path5.join(workingDir, ".sf-cli", "norms", "devstanded.md");
|
|
910
984
|
try {
|
|
911
|
-
const stats = await
|
|
985
|
+
const stats = await fs9.stat(devStandardsPath);
|
|
912
986
|
if (stats.size <= MAX_FILE_SIZE2) {
|
|
913
|
-
context.devStandards = await
|
|
987
|
+
context.devStandards = await fs9.readFile(devStandardsPath, "utf-8");
|
|
914
988
|
}
|
|
915
989
|
} catch {
|
|
916
990
|
}
|
|
991
|
+
if (!context.framework) {
|
|
992
|
+
const detectedFramework = await detectFramework2(workingDir);
|
|
993
|
+
if (detectedFramework) {
|
|
994
|
+
context.framework = detectedFramework;
|
|
995
|
+
context.techStack = [detectedFramework, ...context.techStack.filter((t) => t !== detectedFramework)];
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (!context.devStandards) {
|
|
999
|
+
context.devStandards = await analyzeProjectForStandards(workingDir, context);
|
|
1000
|
+
}
|
|
1001
|
+
context.projectStructure = await analyzeProjectStructure(workingDir);
|
|
917
1002
|
return context;
|
|
918
1003
|
}
|
|
1004
|
+
async function detectFramework2(workingDir) {
|
|
1005
|
+
try {
|
|
1006
|
+
const pkgPath = path5.join(workingDir, "package.json");
|
|
1007
|
+
const pkgContent = await fs9.readFile(pkgPath, "utf-8");
|
|
1008
|
+
const pkg = JSON.parse(pkgContent);
|
|
1009
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1010
|
+
if (deps["react"] || deps["next"]) return "React";
|
|
1011
|
+
if (deps["vue"] || deps["nuxt"]) return "Vue";
|
|
1012
|
+
if (deps["angular"] || deps["@angular/core"]) return "Angular";
|
|
1013
|
+
if (deps["svelte"]) return "Svelte";
|
|
1014
|
+
if (deps["solid-js"]) return "Solid";
|
|
1015
|
+
if (deps["vite"] || deps["webpack"]) return "JavaScript";
|
|
1016
|
+
if (deps["typescript"]) return "TypeScript";
|
|
1017
|
+
return null;
|
|
1018
|
+
} catch {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async function analyzeProjectForStandards(workingDir, context) {
|
|
1023
|
+
const standards = [];
|
|
1024
|
+
standards.push(`# \u9879\u76EE\u5F00\u53D1\u89C4\u8303\uFF08\u81EA\u52A8\u751F\u6210\uFF09
|
|
1025
|
+
> \u9879\u76EE\u540D\u79F0: ${context.name}
|
|
1026
|
+
> \u6846\u67B6: ${context.framework || "\u672A\u68C0\u6D4B\u5230"}
|
|
1027
|
+
> \u6280\u672F\u6808: ${context.techStack.join(", ") || "\u672A\u68C0\u6D4B\u5230"}
|
|
1028
|
+
`);
|
|
1029
|
+
try {
|
|
1030
|
+
const tsConfigPath = path5.join(workingDir, "tsconfig.json");
|
|
1031
|
+
await fs9.access(tsConfigPath);
|
|
1032
|
+
standards.push("\n## \u8BED\u8A00\n- \u4F7F\u7528 TypeScript");
|
|
1033
|
+
} catch {
|
|
1034
|
+
standards.push("\n## \u8BED\u8A00\n- \u4F7F\u7528 JavaScript");
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const srcDir = path5.join(workingDir, "src");
|
|
1038
|
+
const files = await listFilesDeep(srcDir);
|
|
1039
|
+
const hasCss = files.some((f) => f.endsWith(".css"));
|
|
1040
|
+
const hasScss = files.some((f) => f.endsWith(".scss") || f.endsWith(".sass"));
|
|
1041
|
+
const hasLess = files.some((f) => f.endsWith(".less"));
|
|
1042
|
+
const hasStyled = files.some((f) => f.includes(".styled.") || f.includes("styled-components"));
|
|
1043
|
+
if (hasScss) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 SCSS/Sass");
|
|
1044
|
+
else if (hasLess) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 Less");
|
|
1045
|
+
else if (hasStyled) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 styled-components");
|
|
1046
|
+
else if (hasCss) standards.push("\n## \u6837\u5F0F\n- \u4F7F\u7528 CSS");
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
try {
|
|
1050
|
+
const srcDir = path5.join(workingDir, "src");
|
|
1051
|
+
const entries = await fs9.readdir(srcDir, { withFileTypes: true });
|
|
1052
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1053
|
+
if (dirs.includes("components")) standards.push("\n## \u76EE\u5F55\u7ED3\u6784\n- \u7EC4\u4EF6\u653E\u5728 src/components/");
|
|
1054
|
+
if (dirs.includes("pages") || dirs.includes("views")) standards.push("- \u9875\u9762\u653E\u5728 src/pages/ \u6216 src/views/");
|
|
1055
|
+
if (dirs.includes("hooks")) standards.push("- Hooks \u653E\u5728 src/hooks/");
|
|
1056
|
+
if (dirs.includes("utils")) standards.push("- \u5DE5\u5177\u51FD\u6570\u653E\u5728 src/utils/");
|
|
1057
|
+
if (dirs.includes("api") || dirs.includes("services")) standards.push("- API \u8C03\u7528\u653E\u5728 src/api/ \u6216 src/services/");
|
|
1058
|
+
if (dirs.includes("store") || dirs.includes("stores")) standards.push("- \u72B6\u6001\u7BA1\u7406\u653E\u5728 src/store/");
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1061
|
+
standards.push("\n## \u4EE3\u7801\u89C4\u8303");
|
|
1062
|
+
standards.push("- \u4F7F\u7528 ES6+ \u8BED\u6CD5");
|
|
1063
|
+
standards.push("- \u7EC4\u4EF6\u4F7F\u7528\u51FD\u6570\u5F0F\u5199\u6CD5");
|
|
1064
|
+
standards.push("- \u4F7F\u7528 async/await \u5904\u7406\u5F02\u6B65");
|
|
1065
|
+
standards.push("- \u53D8\u91CF\u4F7F\u7528 camelCase\uFF0C\u7EC4\u4EF6\u4F7F\u7528 PascalCase");
|
|
1066
|
+
standards.push("- \u5E38\u91CF\u4F7F\u7528 UPPER_SNAKE_CASE");
|
|
1067
|
+
return standards.join("\n");
|
|
1068
|
+
}
|
|
1069
|
+
async function listFilesDeep(dir, maxDepth = 3) {
|
|
1070
|
+
const files = [];
|
|
1071
|
+
async function scan(currentDir, depth) {
|
|
1072
|
+
if (depth > maxDepth) return;
|
|
1073
|
+
try {
|
|
1074
|
+
const entries = await fs9.readdir(currentDir, { withFileTypes: true });
|
|
1075
|
+
for (const entry of entries) {
|
|
1076
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1077
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
1078
|
+
if (entry.isDirectory()) {
|
|
1079
|
+
await scan(fullPath, depth + 1);
|
|
1080
|
+
} else {
|
|
1081
|
+
files.push(fullPath);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
await scan(dir, 0);
|
|
1088
|
+
return files;
|
|
1089
|
+
}
|
|
1090
|
+
async function analyzeProjectStructure(workingDir) {
|
|
1091
|
+
const structure = {
|
|
1092
|
+
hasSrc: false,
|
|
1093
|
+
hasPages: false,
|
|
1094
|
+
hasComponents: false,
|
|
1095
|
+
hasApi: false,
|
|
1096
|
+
hasHooks: false,
|
|
1097
|
+
hasStore: false,
|
|
1098
|
+
hasUtils: false,
|
|
1099
|
+
srcDir: "src",
|
|
1100
|
+
pagesDir: "",
|
|
1101
|
+
componentsDir: ""
|
|
1102
|
+
};
|
|
1103
|
+
try {
|
|
1104
|
+
const srcDir = path5.join(workingDir, "src");
|
|
1105
|
+
try {
|
|
1106
|
+
const srcStat = await fs9.stat(srcDir);
|
|
1107
|
+
if (srcStat.isDirectory()) {
|
|
1108
|
+
structure.hasSrc = true;
|
|
1109
|
+
structure.srcDir = "src";
|
|
1110
|
+
const srcEntries = await fs9.readdir(srcDir, { withFileTypes: true });
|
|
1111
|
+
const srcDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1112
|
+
if (srcDirs.includes("pages")) {
|
|
1113
|
+
structure.hasPages = true;
|
|
1114
|
+
structure.pagesDir = "src/pages";
|
|
1115
|
+
} else if (srcDirs.includes("views")) {
|
|
1116
|
+
structure.hasPages = true;
|
|
1117
|
+
structure.pagesDir = "src/views";
|
|
1118
|
+
}
|
|
1119
|
+
if (srcDirs.includes("components")) {
|
|
1120
|
+
structure.hasComponents = true;
|
|
1121
|
+
structure.componentsDir = "src/components";
|
|
1122
|
+
}
|
|
1123
|
+
if (srcDirs.includes("api") || srcDirs.includes("services")) {
|
|
1124
|
+
structure.hasApi = true;
|
|
1125
|
+
}
|
|
1126
|
+
if (srcDirs.includes("hooks")) {
|
|
1127
|
+
structure.hasHooks = true;
|
|
1128
|
+
}
|
|
1129
|
+
if (srcDirs.includes("store") || srcDirs.includes("stores")) {
|
|
1130
|
+
structure.hasStore = true;
|
|
1131
|
+
}
|
|
1132
|
+
if (srcDirs.includes("utils")) {
|
|
1133
|
+
structure.hasUtils = true;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
} catch {
|
|
1137
|
+
const rootEntries = await fs9.readdir(workingDir, { withFileTypes: true });
|
|
1138
|
+
const rootDirs = rootEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1139
|
+
if (rootDirs.includes("pages")) {
|
|
1140
|
+
structure.hasPages = true;
|
|
1141
|
+
structure.pagesDir = "pages";
|
|
1142
|
+
}
|
|
1143
|
+
if (rootDirs.includes("components")) {
|
|
1144
|
+
structure.hasComponents = true;
|
|
1145
|
+
structure.componentsDir = "components";
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
} catch {
|
|
1149
|
+
}
|
|
1150
|
+
return structure;
|
|
1151
|
+
}
|
|
919
1152
|
function analyzeComplexity(requirement, context) {
|
|
920
1153
|
let score = 3;
|
|
921
1154
|
if (requirement.length > 100) score += 1;
|
|
@@ -1404,10 +1637,10 @@ ${feedbacks.map((f, i) => `${i + 1}. ${f}`).join("\n")}
|
|
|
1404
1637
|
}
|
|
1405
1638
|
async function saveSpecFile(workingDir, session) {
|
|
1406
1639
|
const specDir = path5.join(workingDir, "openspec", "changes");
|
|
1407
|
-
await
|
|
1640
|
+
await fs9.mkdir(specDir, { recursive: true });
|
|
1408
1641
|
const specPath = path5.join(specDir, `${session.id}-spec.md`);
|
|
1409
1642
|
const content = formatSpecFile(session);
|
|
1410
|
-
await
|
|
1643
|
+
await fs9.writeFile(specPath, content, "utf-8");
|
|
1411
1644
|
return specPath;
|
|
1412
1645
|
}
|
|
1413
1646
|
function formatSpecFile(session) {
|
|
@@ -1492,7 +1725,7 @@ function formatSpecFile(session) {
|
|
|
1492
1725
|
}
|
|
1493
1726
|
async function generateTests(workingDir, session, ctx) {
|
|
1494
1727
|
const testDir = path5.join(workingDir, "tests");
|
|
1495
|
-
await
|
|
1728
|
+
await fs9.mkdir(testDir, { recursive: true });
|
|
1496
1729
|
const testFiles = [];
|
|
1497
1730
|
if (ctx?.modelService) {
|
|
1498
1731
|
for (const scenario of session.bddScenarios) {
|
|
@@ -1502,12 +1735,12 @@ async function generateTests(workingDir, session, ctx) {
|
|
|
1502
1735
|
loader.start();
|
|
1503
1736
|
try {
|
|
1504
1737
|
const content = await generateTestFileWithAI(scenario, session, ctx);
|
|
1505
|
-
await
|
|
1738
|
+
await fs9.writeFile(testPath, content, "utf-8");
|
|
1506
1739
|
testFiles.push(`tests/${testName}.test.ts`);
|
|
1507
1740
|
loader.stop(chalk9.green(` \u2713 \u6D4B\u8BD5\u6587\u4EF6\u5DF2\u751F\u6210`));
|
|
1508
1741
|
} catch {
|
|
1509
1742
|
const content = generateTestFile(scenario, session);
|
|
1510
|
-
await
|
|
1743
|
+
await fs9.writeFile(testPath, content, "utf-8");
|
|
1511
1744
|
testFiles.push(`tests/${testName}.test.ts`);
|
|
1512
1745
|
loader.stop(chalk9.yellow(" \u26A0 \u4F7F\u7528\u57FA\u7840\u6D4B\u8BD5\u6A21\u677F"));
|
|
1513
1746
|
}
|
|
@@ -1517,7 +1750,7 @@ async function generateTests(workingDir, session, ctx) {
|
|
|
1517
1750
|
const testName = scenario.feature.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, "_");
|
|
1518
1751
|
const testPath = path5.join(testDir, `${testName}.test.ts`);
|
|
1519
1752
|
const content = generateTestFile(scenario, session);
|
|
1520
|
-
await
|
|
1753
|
+
await fs9.writeFile(testPath, content, "utf-8");
|
|
1521
1754
|
testFiles.push(`tests/${testName}.test.ts`);
|
|
1522
1755
|
}
|
|
1523
1756
|
}
|
|
@@ -1611,7 +1844,7 @@ function generateTestFile(scenario, session) {
|
|
|
1611
1844
|
async function archiveWorkflow(workingDir) {
|
|
1612
1845
|
if (!activeSession) return;
|
|
1613
1846
|
const archiveDir = path5.join(workingDir, "openspec", "spec");
|
|
1614
|
-
await
|
|
1847
|
+
await fs9.mkdir(archiveDir, { recursive: true });
|
|
1615
1848
|
const archivePath = path5.join(archiveDir, `${activeSession.id}.md`);
|
|
1616
1849
|
const content = `# \u5F52\u6863: ${activeSession.requirement.slice(0, 50)}
|
|
1617
1850
|
|
|
@@ -1638,7 +1871,7 @@ ${activeSession.refinedRequirement}
|
|
|
1638
1871
|
|
|
1639
1872
|
${activeSession.testFiles.map((f) => `- ${f}`).join("\n") || "\u65E0"}
|
|
1640
1873
|
`;
|
|
1641
|
-
await
|
|
1874
|
+
await fs9.writeFile(archivePath, content, "utf-8");
|
|
1642
1875
|
}
|
|
1643
1876
|
function generateSessionId() {
|
|
1644
1877
|
const timestamp = Date.now().toString(36);
|
|
@@ -1914,7 +2147,7 @@ var ConfigManager = class {
|
|
|
1914
2147
|
this.projectPath = projectPath;
|
|
1915
2148
|
this.configPath = path5.join(projectPath, ".sf-cli", "config.json");
|
|
1916
2149
|
try {
|
|
1917
|
-
const content = await
|
|
2150
|
+
const content = await fs9.readFile(this.configPath, "utf-8");
|
|
1918
2151
|
const loaded = JSON.parse(content);
|
|
1919
2152
|
if (loaded.apiKey && loaded.apiKeyEncrypted) {
|
|
1920
2153
|
loaded.apiKey = this.decrypt(loaded.apiKey);
|
|
@@ -1933,8 +2166,8 @@ var ConfigManager = class {
|
|
|
1933
2166
|
configToSave.apiKey = encrypted;
|
|
1934
2167
|
configToSave.apiKeyEncrypted = true;
|
|
1935
2168
|
}
|
|
1936
|
-
await
|
|
1937
|
-
await
|
|
2169
|
+
await fs9.mkdir(path5.dirname(this.configPath), { recursive: true });
|
|
2170
|
+
await fs9.writeFile(
|
|
1938
2171
|
this.configPath,
|
|
1939
2172
|
JSON.stringify(configToSave, null, 2),
|
|
1940
2173
|
"utf-8"
|
|
@@ -3032,8 +3265,8 @@ var ModelService = class {
|
|
|
3032
3265
|
const dailyPath = path5.join(this.statsPath, "daily.json");
|
|
3033
3266
|
const totalPath = path5.join(this.statsPath, "total.json");
|
|
3034
3267
|
const [daily, total] = await Promise.all([
|
|
3035
|
-
|
|
3036
|
-
|
|
3268
|
+
fs9.readFile(dailyPath, "utf-8").catch(() => "{}"),
|
|
3269
|
+
fs9.readFile(totalPath, "utf-8").catch(() => '{"totalInput":0,"totalOutput":0}')
|
|
3037
3270
|
]);
|
|
3038
3271
|
const dailyData = JSON.parse(daily);
|
|
3039
3272
|
const totalData = JSON.parse(total);
|
|
@@ -3046,12 +3279,12 @@ var ModelService = class {
|
|
|
3046
3279
|
async saveStats() {
|
|
3047
3280
|
if (!this.statsPath) return;
|
|
3048
3281
|
try {
|
|
3049
|
-
await
|
|
3282
|
+
await fs9.mkdir(this.statsPath, { recursive: true });
|
|
3050
3283
|
const dailyPath = path5.join(this.statsPath, "daily.json");
|
|
3051
3284
|
const totalPath = path5.join(this.statsPath, "total.json");
|
|
3052
3285
|
await Promise.all([
|
|
3053
|
-
|
|
3054
|
-
|
|
3286
|
+
fs9.writeFile(dailyPath, JSON.stringify(this.stats.byDate, null, 2)),
|
|
3287
|
+
fs9.writeFile(totalPath, JSON.stringify({
|
|
3055
3288
|
totalInput: this.stats.totalInput,
|
|
3056
3289
|
totalOutput: this.stats.totalOutput
|
|
3057
3290
|
}))
|
|
@@ -4760,15 +4993,15 @@ async function loadProjectContext(workingDirectory) {
|
|
|
4760
4993
|
devStandards: ""
|
|
4761
4994
|
};
|
|
4762
4995
|
try {
|
|
4763
|
-
context.agentsMd = await
|
|
4996
|
+
context.agentsMd = await fs9.readFile(path5.join(workingDirectory, "AGENTS.md"), "utf-8");
|
|
4764
4997
|
} catch {
|
|
4765
4998
|
}
|
|
4766
4999
|
try {
|
|
4767
|
-
context.configYaml = await
|
|
5000
|
+
context.configYaml = await fs9.readFile(path5.join(workingDirectory, "openspec", "config.yaml"), "utf-8");
|
|
4768
5001
|
} catch {
|
|
4769
5002
|
}
|
|
4770
5003
|
try {
|
|
4771
|
-
context.devStandards = await
|
|
5004
|
+
context.devStandards = await fs9.readFile(path5.join(workingDirectory, ".sf-cli", "norms", "devstanded.md"), "utf-8");
|
|
4772
5005
|
} catch {
|
|
4773
5006
|
}
|
|
4774
5007
|
return context;
|
|
@@ -5731,19 +5964,19 @@ var WorkflowEngine = class {
|
|
|
5731
5964
|
async loadProjectContext() {
|
|
5732
5965
|
const agentsMdPath = path5.join(this.projectPath, "AGENTS.md");
|
|
5733
5966
|
try {
|
|
5734
|
-
this.projectContext = await
|
|
5967
|
+
this.projectContext = await fs9.readFile(agentsMdPath, "utf-8");
|
|
5735
5968
|
} catch {
|
|
5736
5969
|
this.projectContext = "";
|
|
5737
5970
|
}
|
|
5738
5971
|
const configPath = path5.join(this.openspecPath, "config.yaml");
|
|
5739
5972
|
try {
|
|
5740
|
-
this.projectConfig = await
|
|
5973
|
+
this.projectConfig = await fs9.readFile(configPath, "utf-8");
|
|
5741
5974
|
} catch {
|
|
5742
5975
|
this.projectConfig = "";
|
|
5743
5976
|
}
|
|
5744
5977
|
const devstandedPath = path5.join(this.projectPath, ".sf-cli", "norms", "devstanded.md");
|
|
5745
5978
|
try {
|
|
5746
|
-
this.devStandards = await
|
|
5979
|
+
this.devStandards = await fs9.readFile(devstandedPath, "utf-8");
|
|
5747
5980
|
} catch {
|
|
5748
5981
|
this.devStandards = "";
|
|
5749
5982
|
}
|
|
@@ -5772,7 +6005,7 @@ var WorkflowEngine = class {
|
|
|
5772
6005
|
const specPath = this.getSpecFilePath();
|
|
5773
6006
|
if (!specPath) return false;
|
|
5774
6007
|
try {
|
|
5775
|
-
await
|
|
6008
|
+
await fs9.access(specPath);
|
|
5776
6009
|
return true;
|
|
5777
6010
|
} catch {
|
|
5778
6011
|
return false;
|
|
@@ -6006,11 +6239,11 @@ var WorkflowEngine = class {
|
|
|
6006
6239
|
const workflows = [];
|
|
6007
6240
|
const changesDir = path5.join(this.openspecPath, "changes");
|
|
6008
6241
|
try {
|
|
6009
|
-
const files = await
|
|
6242
|
+
const files = await fs9.readdir(changesDir);
|
|
6010
6243
|
for (const file of files) {
|
|
6011
6244
|
if (!file.endsWith(".md") || file.includes("-spec.md")) continue;
|
|
6012
6245
|
const filePath = path5.join(changesDir, file);
|
|
6013
|
-
const content = await
|
|
6246
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
6014
6247
|
const state = this.parseChangeRecord(content);
|
|
6015
6248
|
if (state && state.status === "running") {
|
|
6016
6249
|
workflows.push(state);
|
|
@@ -6061,7 +6294,7 @@ var WorkflowEngine = class {
|
|
|
6061
6294
|
}
|
|
6062
6295
|
const statePath = path5.join(this.openspecPath, ".workflow-states", `${changeId}.json`);
|
|
6063
6296
|
try {
|
|
6064
|
-
const content = await
|
|
6297
|
+
const content = await fs9.readFile(statePath, "utf-8");
|
|
6065
6298
|
this.state = JSON.parse(content, (key, value) => {
|
|
6066
6299
|
if (key.endsWith("At") && typeof value === "string") {
|
|
6067
6300
|
return new Date(value);
|
|
@@ -6074,7 +6307,7 @@ var WorkflowEngine = class {
|
|
|
6074
6307
|
const changesDir = path5.join(this.openspecPath, "changes");
|
|
6075
6308
|
const changeFile = path5.join(changesDir, `${changeId}.md`);
|
|
6076
6309
|
try {
|
|
6077
|
-
const content = await
|
|
6310
|
+
const content = await fs9.readFile(changeFile, "utf-8");
|
|
6078
6311
|
const parsed = this.parseChangeRecord(content);
|
|
6079
6312
|
if (parsed && parsed.status === "running") {
|
|
6080
6313
|
this.state = parsed;
|
|
@@ -6139,8 +6372,8 @@ var WorkflowEngine = class {
|
|
|
6139
6372
|
const archiveDir = path5.join(changesDir, "archive");
|
|
6140
6373
|
const changeFile = path5.join(changesDir, `${changeId}.md`);
|
|
6141
6374
|
const archiveFile = path5.join(archiveDir, `${changeId}.md`);
|
|
6142
|
-
await
|
|
6143
|
-
await
|
|
6375
|
+
await fs9.mkdir(archiveDir, { recursive: true });
|
|
6376
|
+
await fs9.rename(changeFile, archiveFile).catch(() => {
|
|
6144
6377
|
});
|
|
6145
6378
|
this.state = null;
|
|
6146
6379
|
this.snapshots.clear();
|
|
@@ -6166,15 +6399,15 @@ var WorkflowEngine = class {
|
|
|
6166
6399
|
const archiveDir = path5.join(changesDir, "archive");
|
|
6167
6400
|
const specDir = path5.join(this.openspecPath, "spec");
|
|
6168
6401
|
const statesDir = path5.join(this.openspecPath, ".workflow-states");
|
|
6169
|
-
await
|
|
6170
|
-
await
|
|
6171
|
-
await
|
|
6402
|
+
await fs9.mkdir(archiveDir, { recursive: true });
|
|
6403
|
+
await fs9.mkdir(specDir, { recursive: true });
|
|
6404
|
+
await fs9.mkdir(statesDir, { recursive: true });
|
|
6172
6405
|
}
|
|
6173
6406
|
async restoreState() {
|
|
6174
6407
|
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
6175
6408
|
let activeId = null;
|
|
6176
6409
|
try {
|
|
6177
|
-
const activeContent = await
|
|
6410
|
+
const activeContent = await fs9.readFile(activePath, "utf-8");
|
|
6178
6411
|
const activeData = JSON.parse(activeContent);
|
|
6179
6412
|
activeId = activeData.activeId;
|
|
6180
6413
|
} catch {
|
|
@@ -6182,7 +6415,7 @@ var WorkflowEngine = class {
|
|
|
6182
6415
|
if (activeId) {
|
|
6183
6416
|
const statePath = path5.join(this.openspecPath, ".workflow-states", `${activeId}.json`);
|
|
6184
6417
|
try {
|
|
6185
|
-
const content = await
|
|
6418
|
+
const content = await fs9.readFile(statePath, "utf-8");
|
|
6186
6419
|
this.state = JSON.parse(content, (key, value) => {
|
|
6187
6420
|
if (key.endsWith("At") && typeof value === "string") {
|
|
6188
6421
|
return new Date(value);
|
|
@@ -6195,7 +6428,7 @@ var WorkflowEngine = class {
|
|
|
6195
6428
|
}
|
|
6196
6429
|
const oldStatePath = path5.join(this.openspecPath, ".workflow-state.json");
|
|
6197
6430
|
try {
|
|
6198
|
-
const content = await
|
|
6431
|
+
const content = await fs9.readFile(oldStatePath, "utf-8");
|
|
6199
6432
|
this.state = JSON.parse(content, (key, value) => {
|
|
6200
6433
|
if (key.endsWith("At") && typeof value === "string") {
|
|
6201
6434
|
return new Date(value);
|
|
@@ -6204,7 +6437,7 @@ var WorkflowEngine = class {
|
|
|
6204
6437
|
});
|
|
6205
6438
|
if (this.state) {
|
|
6206
6439
|
await this.saveState();
|
|
6207
|
-
await
|
|
6440
|
+
await fs9.unlink(oldStatePath).catch(() => {
|
|
6208
6441
|
});
|
|
6209
6442
|
}
|
|
6210
6443
|
} catch (e) {
|
|
@@ -6218,16 +6451,16 @@ var WorkflowEngine = class {
|
|
|
6218
6451
|
async saveState() {
|
|
6219
6452
|
if (!this.state) return;
|
|
6220
6453
|
const statesDir = path5.join(this.openspecPath, ".workflow-states");
|
|
6221
|
-
await
|
|
6454
|
+
await fs9.mkdir(statesDir, { recursive: true });
|
|
6222
6455
|
const statePath = path5.join(statesDir, `${this.state.id}.json`);
|
|
6223
|
-
await
|
|
6456
|
+
await fs9.writeFile(statePath, JSON.stringify(this.state, null, 2));
|
|
6224
6457
|
const activePath = path5.join(this.openspecPath, ".workflow-active.json");
|
|
6225
|
-
await
|
|
6458
|
+
await fs9.writeFile(activePath, JSON.stringify({ activeId: this.state.id }, null, 2));
|
|
6226
6459
|
}
|
|
6227
6460
|
async restoreSnapshots() {
|
|
6228
6461
|
const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
|
|
6229
6462
|
try {
|
|
6230
|
-
const content = await
|
|
6463
|
+
const content = await fs9.readFile(snapshotsPath, "utf-8");
|
|
6231
6464
|
const data = JSON.parse(content, (key, value) => {
|
|
6232
6465
|
if (key === "timestamp" && typeof value === "string") {
|
|
6233
6466
|
return new Date(value);
|
|
@@ -6244,7 +6477,7 @@ var WorkflowEngine = class {
|
|
|
6244
6477
|
async saveSnapshots() {
|
|
6245
6478
|
const snapshotsPath = path5.join(this.openspecPath, ".workflow-snapshots.json");
|
|
6246
6479
|
const data = Array.from(this.snapshots.values());
|
|
6247
|
-
await
|
|
6480
|
+
await fs9.writeFile(snapshotsPath, JSON.stringify(data, null, 2));
|
|
6248
6481
|
}
|
|
6249
6482
|
async createSnapshot() {
|
|
6250
6483
|
if (!this.state) return;
|
|
@@ -6265,7 +6498,7 @@ var WorkflowEngine = class {
|
|
|
6265
6498
|
async createChangeRecord() {
|
|
6266
6499
|
if (!this.state) return;
|
|
6267
6500
|
const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
6268
|
-
await
|
|
6501
|
+
await fs9.writeFile(changePath, this.formatChangeRecord());
|
|
6269
6502
|
}
|
|
6270
6503
|
async updateChangeRecord(status) {
|
|
6271
6504
|
if (!this.state) return;
|
|
@@ -6273,7 +6506,7 @@ var WorkflowEngine = class {
|
|
|
6273
6506
|
this.state.status = status;
|
|
6274
6507
|
}
|
|
6275
6508
|
const changePath = path5.join(this.openspecPath, "changes", `${this.state.id}.md`);
|
|
6276
|
-
await
|
|
6509
|
+
await fs9.writeFile(changePath, this.formatChangeRecord());
|
|
6277
6510
|
}
|
|
6278
6511
|
formatChangeRecord() {
|
|
6279
6512
|
if (!this.state) return "";
|
|
@@ -6337,7 +6570,7 @@ ${this.state.steps.map((s) => `- [${s.status === "completed" ? "x" : " "}] ${s.s
|
|
|
6337
6570
|
|
|
6338
6571
|
${this.state.artifacts.map((a) => `- ${a}`).join("\n") || "\u6682\u65E0"}
|
|
6339
6572
|
`;
|
|
6340
|
-
await
|
|
6573
|
+
await fs9.writeFile(specPath, content);
|
|
6341
6574
|
}
|
|
6342
6575
|
};
|
|
6343
6576
|
var ConfirmationRequiredError = class extends Error {
|
|
@@ -6386,11 +6619,11 @@ var NormsManager = class {
|
|
|
6386
6619
|
const files = await this.collectProjectFiles(projectPath);
|
|
6387
6620
|
for (const filePath of files.slice(0, MAX_FILES_TO_SCAN)) {
|
|
6388
6621
|
try {
|
|
6389
|
-
const stats = await
|
|
6622
|
+
const stats = await fs9.stat(filePath);
|
|
6390
6623
|
if (stats.size > MAX_FILE_SIZE) {
|
|
6391
6624
|
continue;
|
|
6392
6625
|
}
|
|
6393
|
-
const content = await
|
|
6626
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
6394
6627
|
const patterns = await this.learnFromFile(filePath, content);
|
|
6395
6628
|
result.patternsFound += patterns.length;
|
|
6396
6629
|
result.filesScanned++;
|
|
@@ -7016,7 +7249,7 @@ ${response}`;
|
|
|
7016
7249
|
const files = [];
|
|
7017
7250
|
async function scan(dir) {
|
|
7018
7251
|
try {
|
|
7019
|
-
const entries = await
|
|
7252
|
+
const entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
7020
7253
|
for (const entry of entries) {
|
|
7021
7254
|
if (entry.isDirectory()) {
|
|
7022
7255
|
if (!IGNORED_DIRS.includes(entry.name)) {
|
|
@@ -7059,8 +7292,8 @@ ${response}`;
|
|
|
7059
7292
|
* 确保规范目录存在
|
|
7060
7293
|
*/
|
|
7061
7294
|
async ensureNormsDir() {
|
|
7062
|
-
await
|
|
7063
|
-
await
|
|
7295
|
+
await fs9.mkdir(this.config.normsDir, { recursive: true });
|
|
7296
|
+
await fs9.mkdir(path5.join(this.config.normsDir, "weekly"), { recursive: true });
|
|
7064
7297
|
}
|
|
7065
7298
|
/**
|
|
7066
7299
|
* 加载已有规范
|
|
@@ -7068,7 +7301,7 @@ ${response}`;
|
|
|
7068
7301
|
async loadStandards() {
|
|
7069
7302
|
const standardsPath = path5.join(this.config.normsDir, "patterns.json");
|
|
7070
7303
|
try {
|
|
7071
|
-
const content = await
|
|
7304
|
+
const content = await fs9.readFile(standardsPath, "utf-8");
|
|
7072
7305
|
const data = JSON.parse(content);
|
|
7073
7306
|
for (const standard of data) {
|
|
7074
7307
|
this.standards.set(standard.id, {
|
|
@@ -7086,7 +7319,7 @@ ${response}`;
|
|
|
7086
7319
|
async loadWeights() {
|
|
7087
7320
|
const weightsPath = path5.join(this.config.normsDir, "weights.json");
|
|
7088
7321
|
try {
|
|
7089
|
-
const content = await
|
|
7322
|
+
const content = await fs9.readFile(weightsPath, "utf-8");
|
|
7090
7323
|
const data = JSON.parse(content);
|
|
7091
7324
|
for (const weight of data) {
|
|
7092
7325
|
this.weights.set(weight.standardId, {
|
|
@@ -7102,7 +7335,7 @@ ${response}`;
|
|
|
7102
7335
|
*/
|
|
7103
7336
|
async saveStandards() {
|
|
7104
7337
|
const standardsPath = path5.join(this.config.normsDir, "patterns.json");
|
|
7105
|
-
await
|
|
7338
|
+
await fs9.writeFile(
|
|
7106
7339
|
standardsPath,
|
|
7107
7340
|
JSON.stringify(Array.from(this.standards.values()), null, 2),
|
|
7108
7341
|
"utf-8"
|
|
@@ -7113,7 +7346,7 @@ ${response}`;
|
|
|
7113
7346
|
*/
|
|
7114
7347
|
async saveWeights() {
|
|
7115
7348
|
const weightsPath = path5.join(this.config.normsDir, "weights.json");
|
|
7116
|
-
await
|
|
7349
|
+
await fs9.writeFile(
|
|
7117
7350
|
weightsPath,
|
|
7118
7351
|
JSON.stringify(Array.from(this.weights.values()), null, 2),
|
|
7119
7352
|
"utf-8"
|
|
@@ -7141,7 +7374,7 @@ ${response}`;
|
|
|
7141
7374
|
*/
|
|
7142
7375
|
async saveWeeklyReport(weekId, report) {
|
|
7143
7376
|
const reportPath = path5.join(this.config.normsDir, "weekly", `${weekId}.json`);
|
|
7144
|
-
await
|
|
7377
|
+
await fs9.writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
7145
7378
|
}
|
|
7146
7379
|
/**
|
|
7147
7380
|
* 更新 devstanded.md
|
|
@@ -7150,7 +7383,7 @@ ${response}`;
|
|
|
7150
7383
|
const standards = this.getEffectiveStandards();
|
|
7151
7384
|
const content = this.generateDevStandedMd(standards);
|
|
7152
7385
|
const mdPath = path5.join(this.config.normsDir, "devstanded.md");
|
|
7153
|
-
await
|
|
7386
|
+
await fs9.writeFile(mdPath, content, "utf-8");
|
|
7154
7387
|
}
|
|
7155
7388
|
/**
|
|
7156
7389
|
* 生成 devstanded.md 内容
|
|
@@ -7542,14 +7775,14 @@ ${summary}`,
|
|
|
7542
7775
|
async persist() {
|
|
7543
7776
|
if (!this.persistPath) return;
|
|
7544
7777
|
const dir = path5.dirname(this.persistPath);
|
|
7545
|
-
await
|
|
7778
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
7546
7779
|
const data = {
|
|
7547
7780
|
messages: this.state.messages,
|
|
7548
7781
|
totalTokens: this.state.totalTokens,
|
|
7549
7782
|
limit: this.state.limit,
|
|
7550
7783
|
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7551
7784
|
};
|
|
7552
|
-
await
|
|
7785
|
+
await fs9.writeFile(this.persistPath, JSON.stringify(data, null, 2), "utf-8");
|
|
7553
7786
|
}
|
|
7554
7787
|
/**
|
|
7555
7788
|
* 加载持久化的上下文
|
|
@@ -7557,7 +7790,7 @@ ${summary}`,
|
|
|
7557
7790
|
async loadPersistedContext() {
|
|
7558
7791
|
if (!this.persistPath) return;
|
|
7559
7792
|
try {
|
|
7560
|
-
const content = await
|
|
7793
|
+
const content = await fs9.readFile(this.persistPath, "utf-8");
|
|
7561
7794
|
const data = JSON.parse(content);
|
|
7562
7795
|
this.state.messages = data.messages || [];
|
|
7563
7796
|
this.state.totalTokens = data.totalTokens || 0;
|
|
@@ -7774,7 +8007,7 @@ async function handleInit(args, ctx) {
|
|
|
7774
8007
|
async function initProject(options = {}, workingDir) {
|
|
7775
8008
|
const cwd = workingDir || process.cwd();
|
|
7776
8009
|
try {
|
|
7777
|
-
const stats = await
|
|
8010
|
+
const stats = await fs9.stat(cwd);
|
|
7778
8011
|
if (!stats.isDirectory()) {
|
|
7779
8012
|
return {
|
|
7780
8013
|
output: chalk9.red(`\u9519\u8BEF: ${cwd} \u4E0D\u662F\u6709\u6548\u76EE\u5F55`)
|
|
@@ -7806,7 +8039,7 @@ async function initProject(options = {}, workingDir) {
|
|
|
7806
8039
|
await normsManager.initialize();
|
|
7807
8040
|
const scanResult = await normsManager.scanProject(cwd);
|
|
7808
8041
|
const agentsContent = generateAgentsMd(projectInfo, scanResult);
|
|
7809
|
-
await
|
|
8042
|
+
await fs9.writeFile(agentsMdPath, agentsContent, "utf-8");
|
|
7810
8043
|
await generateOpenSpecConfig(openspecDir, projectInfo);
|
|
7811
8044
|
return {
|
|
7812
8045
|
output: chalk9.green("\u2713 \u9879\u76EE\u521D\u59CB\u5316\u5B8C\u6210\n") + chalk9.gray(` \u9879\u76EE\u7C7B\u578B: ${projectInfo.type}
|
|
@@ -7847,7 +8080,7 @@ async function createSfCliDirectory(basePath) {
|
|
|
7847
8080
|
"logs"
|
|
7848
8081
|
];
|
|
7849
8082
|
for (const dir of dirs) {
|
|
7850
|
-
await
|
|
8083
|
+
await fs9.mkdir(path5.join(basePath, dir), { recursive: true });
|
|
7851
8084
|
}
|
|
7852
8085
|
const config = {
|
|
7853
8086
|
version: "1.0.0",
|
|
@@ -7855,22 +8088,22 @@ async function createSfCliDirectory(basePath) {
|
|
|
7855
8088
|
yolo: false,
|
|
7856
8089
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7857
8090
|
};
|
|
7858
|
-
await
|
|
8091
|
+
await fs9.writeFile(
|
|
7859
8092
|
path5.join(basePath, "config.json"),
|
|
7860
8093
|
JSON.stringify(config, null, 2),
|
|
7861
8094
|
"utf-8"
|
|
7862
8095
|
);
|
|
7863
|
-
await
|
|
8096
|
+
await fs9.writeFile(
|
|
7864
8097
|
path5.join(basePath, "agents", "registry.json"),
|
|
7865
8098
|
JSON.stringify({ version: "1.0.0", agents: {} }, null, 2),
|
|
7866
8099
|
"utf-8"
|
|
7867
8100
|
);
|
|
7868
|
-
await
|
|
8101
|
+
await fs9.writeFile(
|
|
7869
8102
|
path5.join(basePath, "skills", "registry.json"),
|
|
7870
8103
|
JSON.stringify({ version: "1.0.0", skills: {} }, null, 2),
|
|
7871
8104
|
"utf-8"
|
|
7872
8105
|
);
|
|
7873
|
-
await
|
|
8106
|
+
await fs9.writeFile(
|
|
7874
8107
|
path5.join(basePath, "health", "health.md"),
|
|
7875
8108
|
generateHealthTemplate(),
|
|
7876
8109
|
"utf-8"
|
|
@@ -7880,8 +8113,8 @@ async function createOpenSpecDirectory(basePath) {
|
|
|
7880
8113
|
const changesDir = path5.join(basePath, "changes");
|
|
7881
8114
|
const archiveDir = path5.join(changesDir, "archive");
|
|
7882
8115
|
const specDir = path5.join(basePath, "spec");
|
|
7883
|
-
await
|
|
7884
|
-
await
|
|
8116
|
+
await fs9.mkdir(archiveDir, { recursive: true });
|
|
8117
|
+
await fs9.mkdir(specDir, { recursive: true });
|
|
7885
8118
|
}
|
|
7886
8119
|
async function analyzeProject(cwd) {
|
|
7887
8120
|
const result = {
|
|
@@ -7906,7 +8139,7 @@ async function analyzeProject(cwd) {
|
|
|
7906
8139
|
};
|
|
7907
8140
|
const pkgPath = path5.join(cwd, "package.json");
|
|
7908
8141
|
try {
|
|
7909
|
-
const pkgContent = await
|
|
8142
|
+
const pkgContent = await fs9.readFile(pkgPath, "utf-8");
|
|
7910
8143
|
const pkg = JSON.parse(pkgContent);
|
|
7911
8144
|
result.name = pkg.name || result.name;
|
|
7912
8145
|
result.dependencies = pkg.dependencies || {};
|
|
@@ -7989,7 +8222,7 @@ async function analyzeDirectoryStructure(cwd) {
|
|
|
7989
8222
|
others: []
|
|
7990
8223
|
};
|
|
7991
8224
|
try {
|
|
7992
|
-
const entries = await
|
|
8225
|
+
const entries = await fs9.readdir(cwd, { withFileTypes: true });
|
|
7993
8226
|
for (const entry of entries) {
|
|
7994
8227
|
if (!entry.isDirectory()) continue;
|
|
7995
8228
|
const name = entry.name;
|
|
@@ -8034,7 +8267,7 @@ async function findEntryPoints(cwd) {
|
|
|
8034
8267
|
}
|
|
8035
8268
|
async function saveProjectAnalysis(sfCliDir, analysis) {
|
|
8036
8269
|
const analysisPath = path5.join(sfCliDir, "cache", "analysis", "project-analysis.json");
|
|
8037
|
-
await
|
|
8270
|
+
await fs9.writeFile(
|
|
8038
8271
|
analysisPath,
|
|
8039
8272
|
JSON.stringify(analysis, null, 2),
|
|
8040
8273
|
"utf-8"
|
|
@@ -8075,7 +8308,7 @@ architecture:
|
|
|
8075
8308
|
# \u5F00\u53D1\u89C4\u8303 (\u4ECE\u4EE3\u7801\u4E2D\u5B66\u4E60)
|
|
8076
8309
|
standards: []
|
|
8077
8310
|
`;
|
|
8078
|
-
await
|
|
8311
|
+
await fs9.writeFile(configPath, content, "utf-8");
|
|
8079
8312
|
}
|
|
8080
8313
|
function generateAgentsMd(info, scanResult) {
|
|
8081
8314
|
const techStackList = info.techStack.length > 0 ? info.techStack.map((t) => `| ${t} | \u2713 |`).join("\n") : "| \u5F85\u8BC6\u522B | - |";
|
|
@@ -8197,7 +8430,7 @@ function generateHealthTemplate() {
|
|
|
8197
8430
|
}
|
|
8198
8431
|
async function fileExists(filePath) {
|
|
8199
8432
|
try {
|
|
8200
|
-
await
|
|
8433
|
+
await fs9.access(filePath);
|
|
8201
8434
|
return true;
|
|
8202
8435
|
} catch {
|
|
8203
8436
|
return false;
|
|
@@ -9632,9 +9865,9 @@ async function handleFileReference(filePath, ctx) {
|
|
|
9632
9865
|
const cwd = ctx.options.workingDirectory;
|
|
9633
9866
|
const absolutePath = path5.isAbsolute(filePath) ? filePath : path5.join(cwd, filePath);
|
|
9634
9867
|
try {
|
|
9635
|
-
const stats = await
|
|
9868
|
+
const stats = await fs9.stat(absolutePath);
|
|
9636
9869
|
if (stats.isDirectory()) {
|
|
9637
|
-
const files = await
|
|
9870
|
+
const files = await fs9.readdir(absolutePath);
|
|
9638
9871
|
return {
|
|
9639
9872
|
output: chalk9.cyan(`\u{1F4C1} ${filePath}/`) + chalk9.gray(`
|
|
9640
9873
|
${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9.gray(`
|
|
@@ -9642,7 +9875,7 @@ ${files.slice(0, 20).join("\n")}`) + (files.length > 20 ? chalk9.gray(`
|
|
|
9642
9875
|
contextUsed: 0
|
|
9643
9876
|
};
|
|
9644
9877
|
}
|
|
9645
|
-
const content = await
|
|
9878
|
+
const content = await fs9.readFile(absolutePath, "utf-8");
|
|
9646
9879
|
const lines = content.split("\n");
|
|
9647
9880
|
ctx.contextManager.addMessage({
|
|
9648
9881
|
role: "user",
|
|
@@ -10071,7 +10304,7 @@ var Completer = class {
|
|
|
10071
10304
|
prefix = filePath.slice(lastSlash + 1);
|
|
10072
10305
|
}
|
|
10073
10306
|
try {
|
|
10074
|
-
const entries = await
|
|
10307
|
+
const entries = await fs9.readdir(dirPath, { withFileTypes: true });
|
|
10075
10308
|
const matches = [];
|
|
10076
10309
|
for (const entry of entries) {
|
|
10077
10310
|
if (entry.name.startsWith(prefix)) {
|