@deepstorm/cli 0.8.0 → 0.9.1

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.js CHANGED
@@ -1187,13 +1187,13 @@ var require_ast = __commonJS({
1187
1187
  helperExpression: function helperExpression(node) {
1188
1188
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
1189
1189
  },
1190
- scopedId: function scopedId(path23) {
1191
- return /^\.|this\b/.test(path23.original);
1190
+ scopedId: function scopedId(path24) {
1191
+ return /^\.|this\b/.test(path24.original);
1192
1192
  },
1193
1193
  // an ID is simple if it only has one part, and that part is not
1194
1194
  // `..` or `this`.
1195
- simpleId: function simpleId(path23) {
1196
- return path23.parts.length === 1 && !AST.helpers.scopedId(path23) && !path23.depth;
1195
+ simpleId: function simpleId(path24) {
1196
+ return path24.parts.length === 1 && !AST.helpers.scopedId(path24) && !path24.depth;
1197
1197
  }
1198
1198
  }
1199
1199
  };
@@ -2263,12 +2263,12 @@ var require_helpers2 = __commonJS({
2263
2263
  loc
2264
2264
  };
2265
2265
  }
2266
- function prepareMustache(path23, params, hash, open, strip, locInfo) {
2266
+ function prepareMustache(path24, params, hash, open, strip, locInfo) {
2267
2267
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
2268
2268
  var decorator = /\*/.test(open);
2269
2269
  return {
2270
2270
  type: decorator ? "Decorator" : "MustacheStatement",
2271
- path: path23,
2271
+ path: path24,
2272
2272
  params,
2273
2273
  hash,
2274
2274
  escaped,
@@ -2586,9 +2586,9 @@ var require_compiler = __commonJS({
2586
2586
  },
2587
2587
  DecoratorBlock: function DecoratorBlock(decorator) {
2588
2588
  var program2 = decorator.program && this.compileProgram(decorator.program);
2589
- var params = this.setupFullMustacheParams(decorator, program2, void 0), path23 = decorator.path;
2589
+ var params = this.setupFullMustacheParams(decorator, program2, void 0), path24 = decorator.path;
2590
2590
  this.useDecorators = true;
2591
- this.opcode("registerDecorator", params.length, path23.original);
2591
+ this.opcode("registerDecorator", params.length, path24.original);
2592
2592
  },
2593
2593
  PartialStatement: function PartialStatement(partial) {
2594
2594
  this.usePartial = true;
@@ -2652,46 +2652,46 @@ var require_compiler = __commonJS({
2652
2652
  }
2653
2653
  },
2654
2654
  ambiguousSexpr: function ambiguousSexpr(sexpr, program2, inverse) {
2655
- var path23 = sexpr.path, name = path23.parts[0], isBlock = program2 != null || inverse != null;
2656
- this.opcode("getContext", path23.depth);
2655
+ var path24 = sexpr.path, name = path24.parts[0], isBlock = program2 != null || inverse != null;
2656
+ this.opcode("getContext", path24.depth);
2657
2657
  this.opcode("pushProgram", program2);
2658
2658
  this.opcode("pushProgram", inverse);
2659
- path23.strict = true;
2660
- this.accept(path23);
2659
+ path24.strict = true;
2660
+ this.accept(path24);
2661
2661
  this.opcode("invokeAmbiguous", name, isBlock);
2662
2662
  },
2663
2663
  simpleSexpr: function simpleSexpr(sexpr) {
2664
- var path23 = sexpr.path;
2665
- path23.strict = true;
2666
- this.accept(path23);
2664
+ var path24 = sexpr.path;
2665
+ path24.strict = true;
2666
+ this.accept(path24);
2667
2667
  this.opcode("resolvePossibleLambda");
2668
2668
  },
2669
2669
  helperSexpr: function helperSexpr(sexpr, program2, inverse) {
2670
- var params = this.setupFullMustacheParams(sexpr, program2, inverse), path23 = sexpr.path, name = path23.parts[0];
2670
+ var params = this.setupFullMustacheParams(sexpr, program2, inverse), path24 = sexpr.path, name = path24.parts[0];
2671
2671
  if (this.options.knownHelpers[name]) {
2672
2672
  this.opcode("invokeKnownHelper", params.length, name);
2673
2673
  } else if (this.options.knownHelpersOnly) {
2674
2674
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
2675
2675
  } else {
2676
- path23.strict = true;
2677
- path23.falsy = true;
2678
- this.accept(path23);
2679
- this.opcode("invokeHelper", params.length, path23.original, _ast2["default"].helpers.simpleId(path23));
2676
+ path24.strict = true;
2677
+ path24.falsy = true;
2678
+ this.accept(path24);
2679
+ this.opcode("invokeHelper", params.length, path24.original, _ast2["default"].helpers.simpleId(path24));
2680
2680
  }
2681
2681
  },
2682
- PathExpression: function PathExpression(path23) {
2683
- this.addDepth(path23.depth);
2684
- this.opcode("getContext", path23.depth);
2685
- var name = path23.parts[0], scoped = _ast2["default"].helpers.scopedId(path23), blockParamId = !path23.depth && !scoped && this.blockParamIndex(name);
2682
+ PathExpression: function PathExpression(path24) {
2683
+ this.addDepth(path24.depth);
2684
+ this.opcode("getContext", path24.depth);
2685
+ var name = path24.parts[0], scoped = _ast2["default"].helpers.scopedId(path24), blockParamId = !path24.depth && !scoped && this.blockParamIndex(name);
2686
2686
  if (blockParamId) {
2687
- this.opcode("lookupBlockParam", blockParamId, path23.parts);
2687
+ this.opcode("lookupBlockParam", blockParamId, path24.parts);
2688
2688
  } else if (!name) {
2689
2689
  this.opcode("pushContext");
2690
- } else if (path23.data) {
2690
+ } else if (path24.data) {
2691
2691
  this.options.data = true;
2692
- this.opcode("lookupData", path23.depth, path23.parts, path23.strict);
2692
+ this.opcode("lookupData", path24.depth, path24.parts, path24.strict);
2693
2693
  } else {
2694
- this.opcode("lookupOnContext", path23.parts, path23.falsy, path23.strict, scoped);
2694
+ this.opcode("lookupOnContext", path24.parts, path24.falsy, path24.strict, scoped);
2695
2695
  }
2696
2696
  },
2697
2697
  StringLiteral: function StringLiteral(string) {
@@ -3041,16 +3041,16 @@ var require_util = __commonJS({
3041
3041
  }
3042
3042
  exports2.urlGenerate = urlGenerate;
3043
3043
  function normalize(aPath) {
3044
- var path23 = aPath;
3044
+ var path24 = aPath;
3045
3045
  var url = urlParse(aPath);
3046
3046
  if (url) {
3047
3047
  if (!url.path) {
3048
3048
  return aPath;
3049
3049
  }
3050
- path23 = url.path;
3050
+ path24 = url.path;
3051
3051
  }
3052
- var isAbsolute = exports2.isAbsolute(path23);
3053
- var parts = path23.split(/\/+/);
3052
+ var isAbsolute = exports2.isAbsolute(path24);
3053
+ var parts = path24.split(/\/+/);
3054
3054
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
3055
3055
  part = parts[i];
3056
3056
  if (part === ".") {
@@ -3067,18 +3067,18 @@ var require_util = __commonJS({
3067
3067
  }
3068
3068
  }
3069
3069
  }
3070
- path23 = parts.join("/");
3071
- if (path23 === "") {
3072
- path23 = isAbsolute ? "/" : ".";
3070
+ path24 = parts.join("/");
3071
+ if (path24 === "") {
3072
+ path24 = isAbsolute ? "/" : ".";
3073
3073
  }
3074
3074
  if (url) {
3075
- url.path = path23;
3075
+ url.path = path24;
3076
3076
  return urlGenerate(url);
3077
3077
  }
3078
- return path23;
3078
+ return path24;
3079
3079
  }
3080
3080
  exports2.normalize = normalize;
3081
- function join21(aRoot, aPath) {
3081
+ function join22(aRoot, aPath) {
3082
3082
  if (aRoot === "") {
3083
3083
  aRoot = ".";
3084
3084
  }
@@ -3110,7 +3110,7 @@ var require_util = __commonJS({
3110
3110
  }
3111
3111
  return joined;
3112
3112
  }
3113
- exports2.join = join21;
3113
+ exports2.join = join22;
3114
3114
  exports2.isAbsolute = function(aPath) {
3115
3115
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
3116
3116
  };
@@ -3283,7 +3283,7 @@ var require_util = __commonJS({
3283
3283
  parsed.path = parsed.path.substring(0, index + 1);
3284
3284
  }
3285
3285
  }
3286
- sourceURL = join21(urlGenerate(parsed), sourceURL);
3286
+ sourceURL = join22(urlGenerate(parsed), sourceURL);
3287
3287
  }
3288
3288
  return normalize(sourceURL);
3289
3289
  }
@@ -5858,8 +5858,8 @@ var require_printer = __commonJS({
5858
5858
  return this.accept(sexpr.path) + " " + params + hash;
5859
5859
  };
5860
5860
  PrintVisitor.prototype.PathExpression = function(id) {
5861
- var path23 = id.parts.join("/");
5862
- return (id.data ? "@" : "") + "PATH:" + path23;
5861
+ var path24 = id.parts.join("/");
5862
+ return (id.data ? "@" : "") + "PATH:" + path24;
5863
5863
  };
5864
5864
  PrintVisitor.prototype.StringLiteral = function(string) {
5865
5865
  return '"' + string.value + '"';
@@ -5912,7 +5912,7 @@ var require_lib = __commonJS({
5912
5912
  // src/index.ts
5913
5913
  var import_commander = require("commander");
5914
5914
  var fs27 = __toESM(require("node:fs"));
5915
- var path22 = __toESM(require("node:path"));
5915
+ var path23 = __toESM(require("node:path"));
5916
5916
 
5917
5917
  // src/commands/setup.ts
5918
5918
  var path9 = __toESM(require("node:path"));
@@ -9383,6 +9383,7 @@ function registerTemplateCommand(program2, registry2) {
9383
9383
 
9384
9384
  // src/commands/update.ts
9385
9385
  var fs24 = __toESM(require("node:fs"));
9386
+ var path20 = __toESM(require("node:path"));
9386
9387
  var import_node_child_process2 = require("node:child_process");
9387
9388
 
9388
9389
  // src/utils/version.ts
@@ -9814,23 +9815,139 @@ async function updateCLI(fetchFn) {
9814
9815
  return result;
9815
9816
  }
9816
9817
  function getInstalledSkillIds(targetDir) {
9817
- const settingsPath = getDeepStormConfigPath(targetDir);
9818
+ const config = readDeepStormConfig(targetDir);
9819
+ if (!config) return [];
9820
+ const ids = config.installedSkills;
9821
+ return Array.isArray(ids) ? ids : [];
9822
+ }
9823
+ function migrateOldDataSources(targetDir) {
9824
+ const migrated = [];
9818
9825
  try {
9819
- const raw = fs24.readFileSync(settingsPath, "utf-8");
9820
- const config = JSON.parse(raw);
9821
- return config.installedSkills ?? [];
9822
- } catch {
9823
- return [];
9826
+ const claudeSettingsPath = path20.join(targetDir, ".claude", "settings.json");
9827
+ if (fs24.existsSync(claudeSettingsPath)) {
9828
+ const raw = fs24.readFileSync(claudeSettingsPath, "utf-8");
9829
+ const claudeConfig = JSON.parse(raw);
9830
+ if (claudeConfig.deepstorm && typeof claudeConfig.deepstorm === "object" && Object.keys(claudeConfig.deepstorm).length > 0) {
9831
+ writeDeepStormConfig(targetDir, claudeConfig.deepstorm);
9832
+ delete claudeConfig.deepstorm;
9833
+ fs24.writeFileSync(claudeSettingsPath, JSON.stringify(claudeConfig, null, 2) + "\n", "utf-8");
9834
+ migrated.push(".claude/settings.json \u2192 .deepstorm/settings.json");
9835
+ console.log(" \u2714 \u5DF2\u8FC1\u79FB .claude/settings.json \u4E2D\u7684 DeepStorm \u914D\u7F6E");
9836
+ }
9837
+ }
9838
+ } catch (err) {
9839
+ console.log(` \u26A0 deepstorm \u914D\u7F6E\u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9840
+ }
9841
+ try {
9842
+ const sweepInitPath = path20.join(targetDir, ".sweep-init");
9843
+ if (fs24.existsSync(sweepInitPath)) {
9844
+ const existing = readDeepStormConfig(targetDir);
9845
+ const hasE2ePath = existing && existing.sweep?.e2eProjectPath;
9846
+ if (!hasE2ePath) {
9847
+ writeDeepStormConfig(targetDir, { sweep: { e2eProjectPath: "." } });
9848
+ migrated.push(".sweep-init \u2192 sweep.e2eProjectPath");
9849
+ console.log(' \u2714 \u5DF2\u8FC1\u79FB .sweep-init \u2192 sweep.e2eProjectPath = "."');
9850
+ }
9851
+ fs24.rmSync(sweepInitPath, { force: true });
9852
+ if (hasE2ePath) {
9853
+ console.log(" \u2139 .sweep-init \u5DF2\u5220\u9664\uFF08sweep.e2eProjectPath \u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\uFF09");
9854
+ }
9855
+ }
9856
+ } catch (err) {
9857
+ console.log(` \u26A0 .sweep-init \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9858
+ }
9859
+ try {
9860
+ const envPath = path20.join(targetDir, ".env");
9861
+ if (fs24.existsSync(envPath)) {
9862
+ const raw = fs24.readFileSync(envPath, "utf-8");
9863
+ const lines = raw.split("\n");
9864
+ const baseUrlVars = {};
9865
+ let defaultEnv = "test";
9866
+ let hasDefaultEnv = false;
9867
+ const remainingLines = [];
9868
+ for (const line of lines) {
9869
+ const trimmed = line.trim();
9870
+ const baseMatch = trimmed.match(/^BASE_URL_(\w+)=(.+)$/);
9871
+ if (baseMatch) {
9872
+ const envName = baseMatch[1].toLowerCase();
9873
+ let url = baseMatch[2].trim();
9874
+ if (url.startsWith('"') && url.endsWith('"') || url.startsWith("'") && url.endsWith("'")) {
9875
+ url = url.slice(1, -1);
9876
+ }
9877
+ baseUrlVars[envName] = url;
9878
+ continue;
9879
+ }
9880
+ const defaultMatch = trimmed.match(/^DEFAULT_ENV=(.+)$/);
9881
+ if (defaultMatch) {
9882
+ hasDefaultEnv = true;
9883
+ defaultEnv = defaultMatch[1].trim();
9884
+ continue;
9885
+ }
9886
+ remainingLines.push(line);
9887
+ }
9888
+ if (Object.keys(baseUrlVars).length > 0) {
9889
+ const environments = {};
9890
+ for (const [envName, url] of Object.entries(baseUrlVars)) {
9891
+ environments[envName] = { baseUrl: url };
9892
+ }
9893
+ environments.default = defaultEnv;
9894
+ const existing = readDeepStormConfig(targetDir);
9895
+ const hasEnvs = existing && existing.sweep?.environments;
9896
+ if (!hasEnvs) {
9897
+ writeDeepStormConfig(targetDir, { sweep: { environments } });
9898
+ migrated.push(".env BASE_URL \u2192 sweep.environments");
9899
+ console.log(` \u2714 \u5DF2\u8FC1\u79FB .env \u4E2D ${Object.keys(baseUrlVars).length} \u4E2A\u73AF\u5883\u914D\u7F6E`);
9900
+ }
9901
+ const cleaned = remainingLines.join("\n").trimEnd();
9902
+ if (cleaned === "") {
9903
+ fs24.writeFileSync(envPath, "# \u73AF\u5883 baseURL \u914D\u7F6E\u5DF2\u8FC1\u79FB\u81F3 .deepstorm/settings.json\n# \u8BF7\u52FF\u5728\u6B64\u5904\u91CD\u590D\u5B9A\u4E49 BASE_URL_* \u914D\u7F6E\n", "utf-8");
9904
+ } else if (hasDefaultEnv || Object.keys(baseUrlVars).length > 0) {
9905
+ fs24.writeFileSync(envPath, cleaned + "\n", "utf-8");
9906
+ }
9907
+ }
9908
+ }
9909
+ } catch (err) {
9910
+ console.log(` \u26A0 .env baseURL \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9824
9911
  }
9912
+ try {
9913
+ const scopeConfigPath = path20.join(targetDir, ".deepstorm", "scope-config.json");
9914
+ if (fs24.existsSync(scopeConfigPath)) {
9915
+ const raw = fs24.readFileSync(scopeConfigPath, "utf-8");
9916
+ const scopeData = JSON.parse(raw);
9917
+ const scope = {};
9918
+ if ("enabled" in scopeData) scope.enabled = scopeData.enabled;
9919
+ if ("ciEnabled" in scopeData) scope.ciEnabled = scopeData.ciEnabled;
9920
+ if ("domains" in scopeData) scope.domains = scopeData.domains;
9921
+ const existing = readDeepStormConfig(targetDir);
9922
+ const hasReefScope = existing && existing.reef?.scope;
9923
+ if (!hasReefScope) {
9924
+ writeDeepStormConfig(targetDir, { reef: { scope } });
9925
+ migrated.push("scope-config.json \u2192 reef.scope");
9926
+ console.log(" \u2714 \u5DF2\u8FC1\u79FB .deepstorm/scope-config.json \u2192 reef.scope");
9927
+ }
9928
+ fs24.rmSync(scopeConfigPath, { force: true });
9929
+ if (hasReefScope) {
9930
+ console.log(" \u2139 scope-config.json \u5DF2\u5220\u9664\uFF08reef.scope \u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\uFF09");
9931
+ }
9932
+ }
9933
+ } catch (err) {
9934
+ console.log(` \u26A0 scope-config.json \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9935
+ }
9936
+ return { migrated };
9825
9937
  }
9826
9938
  function registerUpdateCommand(program2) {
9827
9939
  const cliDir2 = __dirname;
9828
9940
  program2.command("update").description("\u68C0\u67E5 CLI \u66F4\u65B0\u5E76\u540C\u6B65\u5DF2\u5B89\u88C5 skill \u7684\u5B98\u65B9\u6700\u65B0\u6A21\u677F").action(async () => {
9829
- const installedIds = getInstalledSkillIds(process.cwd());
9941
+ const targetDir = process.cwd();
9942
+ const { migrated } = migrateOldDataSources(targetDir);
9943
+ if (migrated.length > 0) {
9944
+ console.log(`\u2714 \u5DF2\u5B8C\u6210 ${migrated.length} \u9879\u65E7\u6570\u636E\u6E90\u8FC1\u79FB`);
9945
+ }
9946
+ const installedIds = getInstalledSkillIds(targetDir);
9830
9947
  if (installedIds.length === 0) {
9831
9948
  console.log("\u672A\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5\u7684 skill\uFF0C\u8DF3\u8FC7\u540C\u6B65");
9832
9949
  } else {
9833
- upgradeTemplates(cliDir2, process.cwd(), installedIds);
9950
+ upgradeTemplates(cliDir2, targetDir, installedIds);
9834
9951
  }
9835
9952
  await updateCLI();
9836
9953
  });
@@ -9844,7 +9961,7 @@ function registerPilotCommands(program2) {
9844
9961
 
9845
9962
  // src/commands/doctor.ts
9846
9963
  var fs25 = __toESM(require("node:fs"));
9847
- var path20 = __toESM(require("node:path"));
9964
+ var path21 = __toESM(require("node:path"));
9848
9965
  function getInstalledSkills(targetDir) {
9849
9966
  const settingsPath = getDeepStormConfigPath(targetDir);
9850
9967
  try {
@@ -9896,15 +10013,15 @@ function runDoctor(targetDir) {
9896
10013
  });
9897
10014
  }
9898
10015
  }
9899
- const skillsDir = path20.join(targetDir, ".claude", "skills");
10016
+ const skillsDir = path21.join(targetDir, ".claude", "skills");
9900
10017
  if (fs25.existsSync(skillsDir)) {
9901
10018
  const skillDirs = fs25.readdirSync(skillsDir).filter(
9902
- (n) => fs25.statSync(path20.join(skillsDir, n)).isDirectory()
10019
+ (n) => fs25.statSync(path21.join(skillsDir, n)).isDirectory()
9903
10020
  );
9904
10021
  let validCount = 0;
9905
10022
  let invalidCount = 0;
9906
10023
  for (const skillName of skillDirs) {
9907
- const skillMd = path20.join(skillsDir, skillName, "SKILL.md");
10024
+ const skillMd = path21.join(skillsDir, skillName, "SKILL.md");
9908
10025
  if (!fs25.existsSync(skillMd)) {
9909
10026
  invalidCount++;
9910
10027
  continue;
@@ -9939,7 +10056,7 @@ function runDoctor(targetDir) {
9939
10056
  const installedSkills = getInstalledSkills(targetDir);
9940
10057
  const missingSkills = [];
9941
10058
  for (const skillId of installedSkills) {
9942
- if (!fs25.existsSync(path20.join(skillsDir, skillId))) {
10059
+ if (!fs25.existsSync(path21.join(skillsDir, skillId))) {
9943
10060
  missingSkills.push(skillId);
9944
10061
  }
9945
10062
  }
@@ -9963,7 +10080,7 @@ function runDoctor(targetDir) {
9963
10080
  message: ".claude/skills/ \u4E0D\u5B58\u5728"
9964
10081
  });
9965
10082
  }
9966
- const mcpPath = path20.join(targetDir, ".mcp.json");
10083
+ const mcpPath = path21.join(targetDir, ".mcp.json");
9967
10084
  if (fs25.existsSync(mcpPath)) {
9968
10085
  try {
9969
10086
  const mcp = JSON.parse(fs25.readFileSync(mcpPath, "utf-8"));
@@ -10085,7 +10202,7 @@ function printDoctorReport(report) {
10085
10202
 
10086
10203
  // src/commands/uninstall.ts
10087
10204
  var fs26 = __toESM(require("node:fs"));
10088
- var path21 = __toESM(require("node:path"));
10205
+ var path22 = __toESM(require("node:path"));
10089
10206
  var import_prompts4 = require("@clack/prompts");
10090
10207
  async function uninstallDeepStorm(targetDir) {
10091
10208
  const settingsPath = getDeepStormConfigPath(targetDir);
@@ -10105,7 +10222,7 @@ async function uninstallDeepStorm(targetDir) {
10105
10222
  }
10106
10223
  cleanInstalled(targetDir);
10107
10224
  resetConfig(targetDir);
10108
- const templatesDir = path21.join(targetDir, ".deepstorm", "templates");
10225
+ const templatesDir = path22.join(targetDir, ".deepstorm", "templates");
10109
10226
  if (fs26.existsSync(templatesDir)) {
10110
10227
  const shouldDelete = await (0, import_prompts4.confirm)({
10111
10228
  message: "\u662F\u5426\u5220\u9664 .deepstorm/templates/ \u76EE\u5F55\uFF1F",
@@ -10125,7 +10242,7 @@ async function uninstallDeepStorm(targetDir) {
10125
10242
  var program = new import_commander.Command();
10126
10243
  program.name("deepstorm").description("DeepStorm CLI \u2014 \u4E00\u952E\u914D\u7F6E\u9879\u76EE\u5F00\u53D1\u73AF\u5883").version(getCliVersion(), "-v, --version", "\u8F93\u51FA\u7248\u672C\u53F7");
10127
10244
  function loadRegistry() {
10128
- const registryPath = path22.join(__dirname, "registry.json");
10245
+ const registryPath = path23.join(__dirname, "registry.json");
10129
10246
  try {
10130
10247
  const raw = fs27.readFileSync(registryPath, "utf-8");
10131
10248
  return JSON.parse(raw);
@@ -137,7 +137,7 @@ if [ -f "$SCOPE_HOOK" ]; then
137
137
  bash "$SCOPE_HOOK" || {
138
138
  echo ""
139
139
  echo "提示:如需继续提交,请使用 'reef-scope-split.sh' 拆分分支"
140
- echo "或临时禁用 scope 检查(不推荐):deepstorm/scope-config.json → enabled: false"
140
+ echo "或临时禁用 scope 检查(不推荐):settings.json → reef.scope.enabled: false"
141
141
  exit 1
142
142
  }
143
143
  fi
@@ -71,13 +71,17 @@ bash packages/reef/hooks/reef-scope-split.sh
71
71
 
72
72
  ## 配置文件
73
73
 
74
- 安装后生成 `.deepstorm/scope-config.json`:
74
+ 安装后配置写入 `.deepstorm/settings.json` 的 `reef.scope` 字段:
75
75
 
76
76
  ```json
77
77
  {
78
- "enabled": true,
79
- "ciEnabled": true,
80
- "domains": []
78
+ "reef": {
79
+ "scope": {
80
+ "enabled": true,
81
+ "ciEnabled": true,
82
+ "domains": []
83
+ }
84
+ }
81
85
  }
82
86
  ```
83
87
 
@@ -122,7 +126,7 @@ scope-check:
122
126
  ## 常见问题
123
127
 
124
128
  **Q: 我不想在所有项目上都启用这个检查?**
125
- A: 每个项目独立安装和配置,`scope-config.json` 中的 `enabled: false` 可关闭。
129
+ A: 每个项目独立安装和配置,settings.json `reef.scope.enabled: false` 可关闭。
126
130
 
127
131
  **Q: 我改了文档也要被阻断吗?**
128
132
  A: 不会。文档(markdown、README 等)变更会被归类为 `documentation` 领域,不计入多领域阻断判断。
@@ -8,7 +8,7 @@ deepstorm:
8
8
 
9
9
  # Sweep Setup — E2E 测试项目初始化
10
10
 
11
- 初始化当前目录为 Sweep E2E 测试项目。根据 DeepStorm setup 阶段选择的 E2E 框架(如 Playwright),创建对应的项目骨架。
11
+ 初始化当前目录(或指定子目录)为 Sweep E2E 测试项目。根据 DeepStorm setup 阶段选择的 E2E 框架(如 Playwright),创建对应的项目骨架。
12
12
 
13
13
  > **注意:** setup 是 flow-create 和 flow-run 的硬性前置条件。如果当前目录未初始化,其他 skill 会报错引导执行 `/sweep-init`。
14
14
 
@@ -41,27 +41,73 @@ cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eFramework"[^,]*' | head
41
41
 
42
42
  ---
43
43
 
44
+ ### 步骤 0A:选择生成位置
45
+
46
+ 在执行初始化前,先询问用户的 E2E 测试项目类型,确定文件生成的目标目录。
47
+
48
+ > **注意:** TARGET_DIR 是后续所有文件操作的前缀,在整个初始化流程中使用。
49
+
50
+ #### 0A.1 询问项目类型
51
+
52
+ ```
53
+ 📂 请问你的 E2E 测试是哪种类型?
54
+
55
+ a) 独立项目 → 生成在当前目录
56
+ b) 与其他项目混放 → 生成到子目录
57
+ ```
58
+
59
+ **如果选择 a(独立项目):**
60
+ - 设置 TARGET_DIR 为当前目录(`.`)
61
+ - 继续到步骤 1
62
+
63
+ **如果选择 b(混放项目):**
64
+ - 进一步询问子目录选择:
65
+
66
+ ```
67
+ 请选择目标子目录:
68
+ 1) e2e/ 👈 推荐
69
+ 2) tests/e2e/
70
+ 3) 自定义路径
71
+ ```
72
+
73
+ - 如果选择 3(自定义),AI 询问用户输入路径
74
+ - 验证用户输入的路径:不能为空、不能是绝对路径、不能包含 `..`
75
+ - 设置 TARGET_DIR 为所选路径(如 `"e2e"`、`"tests/e2e"` 等)
76
+ - 如果子目录已存在且非空,输出警告并询问用户是否确认继续
77
+
78
+ #### 0A.2 记录 TARGET_DIR
79
+
80
+ `TARGET_DIR` 变量在后续所有步骤中使用。写入 `.deepstorm/settings.json` 中的 `sweep.e2eProjectPath` 字段将在步骤 8 中完成。
81
+
82
+ ---
83
+
44
84
  ### 步骤 1:检查初始化状态
45
85
 
46
- 检查当前目录是否存在 `.sweep-init` 标记文件。
86
+ 检查当前项目是否已初始化为 E2E 测试项目。从 `.deepstorm/settings.json` 读取 `sweep.e2eProjectPath` 判断。
87
+
88
+ #### 1.1 检查 settings.json
47
89
 
48
- #### 1.1 已初始化 → 退出
90
+ ```bash
91
+ E2E_PATH=$(cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eProjectPath"[^,]*' | head -1 | cut -d'"' -f4)
92
+ ```
49
93
 
50
- - **WHEN** `.sweep-init` 文件已存在
51
- - **THEN** 提示"当前目录已初始化为 Sweep 测试项目。如需重新初始化,请删除 .sweep-init 文件后重试。"并退出
94
+ #### 1.2 已初始化 → 退出
52
95
 
53
- #### 1.2 未初始化 → 继续
96
+ - **WHEN** `sweep.e2eProjectPath` 已设置
97
+ - **THEN** 提示"当前项目已初始化为 Sweep 测试项目。如需重新初始化,请删除 `sweep.e2eProjectPath` 配置后重试。"并退出
54
98
 
55
- - **WHEN** `.sweep-init` 文件不存在
99
+ #### 1.3 未初始化 → 继续
100
+
101
+ - **WHEN** `sweep.e2eProjectPath` 未设置
56
102
  - **THEN** 继续执行初始化流程
57
103
 
58
104
  ---
59
105
 
60
106
  ### 步骤 2:创建项目目录结构
61
107
 
62
- 创建 E2E 测试项目的目录骨架。
108
+ `{TARGET_DIR}` 下创建 E2E 测试项目的目录骨架。
63
109
 
64
- #### 目录结构
110
+ #### 目录结构(当 TARGET_DIR 为 `"."` 时)
65
111
 
66
112
  ```
67
113
  .
@@ -75,19 +121,27 @@ cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eFramework"[^,]*' | head
75
121
  └── tsconfig.json # TypeScript 配置
76
122
  ```
77
123
 
78
- > 如果框架为 `playwright`,还会额外生成 `playwright.config.ts`。
124
+ > 如果 TARGET_DIR 为 `"e2e"`,以上所有文件在 `e2e/` 下生成。如果框架为 `playwright`,还会额外生成 `playwright.config.ts`。
79
125
 
80
126
  #### 执行
81
127
 
82
128
  ```bash
83
- mkdir -p flows flows/reports scripts
129
+ # 计算目标目录:TARGET_DIR "." 时用当前目录,否则用 TARGET_DIR
130
+ if [ "$TARGET_DIR" != "." ]; then
131
+ mkdir -p "$TARGET_DIR"/flows "$TARGET_DIR"/flows/reports "$TARGET_DIR"/scripts
132
+ echo "📂 将在 $TARGET_DIR/ 目录下生成 E2E 测试项目"
133
+ else
134
+ mkdir -p flows flows/reports scripts
135
+ fi
84
136
  ```
85
137
 
86
138
  ---
87
139
 
88
140
  ### 步骤 3:生成项目配置文件(框架感知)
89
141
 
90
- 根据步骤 0 读取的框架配置,生成对应的配置文件。
142
+ 根据步骤 0 读取的框架配置,在 `{TARGET_DIR}` 下生成对应的配置文件。
143
+
144
+ > 所有文件写入路径以 `{TARGET_DIR}` 为前缀。当 `TARGET_DIR` 为 `"."` 时,路径不变。
91
145
 
92
146
  #### 3.1 package.json
93
147
 
@@ -115,7 +169,7 @@ mkdir -p flows flows/reports scripts
115
169
 
116
170
  #### 3.2 框架配置文件
117
171
 
118
- **当框架为 `playwright` 时**,写入 `playwright.config.ts`,包含多环境 baseURL 设置:
172
+ **当框架为 `playwright` 时**,写入 `{TARGET_DIR}/playwright.config.ts`,包含多环境 baseURL 设置:
119
173
 
120
174
  ```ts
121
175
  import { defineConfig } from '@playwright/test';
@@ -164,23 +218,43 @@ export default defineConfig({
164
218
  3. 生产环境(prod)baseURL
165
219
  4. 默认目标环境(test / staging / prod)
166
220
 
167
- #### 4.2 写入 .env
221
+ #### 4.2 写入 settings.json
222
+
223
+ 将多环境配置写入 `.deepstorm/settings.json` 的 `sweep.environments` 字段。**不再生成 `.env` 文件。**
168
224
 
169
- 只写入环境地址配置,不包含 MCP 连接说明(MCP 配置在 `.mcp.json` 中统一管理):
225
+ 使用 Bash `jq` 合并写入(或 AI 读取-修改-写回):
170
226
 
171
227
  ```bash
172
- # .env(gitignored,不提交)
173
- BASE_URL_TEST=http://localhost:3000
174
- BASE_URL_STAGING=https://staging.example.com
175
- BASE_URL_PROD=https://prod.example.com
176
- DEFAULT_ENV=test
228
+ # 先读取现有 settings.json
229
+ SETTINGS=$(cat .deepstorm/settings.json 2>/dev/null || echo "{}")
230
+
231
+ # 构造 environments 配置并写入
232
+ # (使用 jq 或 AI 逐字段修改)
233
+ ```
234
+
235
+ 写入后的 settings.json 结构示例:
236
+
237
+ ```json
238
+ {
239
+ "sweep": {
240
+ "e2eFramework": "playwright",
241
+ "environments": {
242
+ "test": { "baseUrl": "http://localhost:3000" },
243
+ "staging": { "baseUrl": "https://staging.example.com" },
244
+ "prod": { "baseUrl": "https://prod.example.com" },
245
+ "default": "test"
246
+ }
247
+ }
248
+ }
177
249
  ```
178
250
 
251
+ > **注意:** API Key 等敏感信息仍然放在 `.env` 文件(gitignored)中。此处仅迁移多环境 baseURL 配置。
252
+
179
253
  ---
180
254
 
181
255
  ### 步骤 5:创建 topology.yaml 初始模板
182
256
 
183
- 写入功能模块拓扑文件基础结构:
257
+ 写入功能模块拓扑文件基础结构到 `{TARGET_DIR}/flows/topology.yaml`:
184
258
 
185
259
  ```yaml
186
260
  # flows/topology.yaml
@@ -202,7 +276,7 @@ modules:
202
276
 
203
277
  ### 步骤 6:创建 flow-selector.mjs
204
278
 
205
- 使用 Read 读取本 skill 目录下的 `scripts/flow-selector.mjs` 文件,用 Write 写入目标项目的 `scripts/flow-selector.mjs`。
279
+ 使用 Read 读取本 skill 目录下的 `scripts/flow-selector.mjs` 文件,用 Write 写入目标项目的 `{TARGET_DIR}/scripts/flow-selector.mjs`。
206
280
 
207
281
  > **说明:** `flow-selector.mjs` 是基于 `@inquirer/checkbox` 的层级选择工具脚本(供 flow-run skill 调用)。
208
282
  > 它与本 SKILL.md 同属一个 skill,作为 skill 的子文件分开维护,避免内联代码块导致的重复。
@@ -236,16 +310,37 @@ cat .mcp.json 2>/dev/null | grep -c "deepstorm-playwright"
236
310
 
237
311
  ### 步骤 8:完成初始化
238
312
 
239
- #### 8.1 创建初始化标记
313
+ #### 8.1 写入 e2eProjectPath 到 settings.json
314
+
315
+ 将 `sweep.e2eProjectPath` 写入 `.deepstorm/settings.json`。**不再创建 `.sweep-init` 标记文件。**
240
316
 
241
317
  ```bash
242
- echo "sweep-e2e-project" > .sweep-init
318
+ # 值由步骤 0A 确定:
319
+ # 独立项目 → "."
320
+ # 混放项目 → 用户选择的路径(如 "e2e")
321
+ ```
322
+
323
+ 写入后的 settings.json 结构示例:
324
+
325
+ ```json
326
+ {
327
+ "sweep": {
328
+ "e2eFramework": "playwright",
329
+ "e2eProjectPath": "e2e",
330
+ "environments": { ... }
331
+ }
332
+ }
243
333
  ```
244
334
 
245
335
  #### 8.2 安装依赖
246
336
 
247
337
  ```bash
248
- npm install
338
+ # 在目标目录中执行 npm install
339
+ if [ "$TARGET_DIR" != "." ]; then
340
+ cd "$TARGET_DIR" && npm install
341
+ else
342
+ npm install
343
+ fi
249
344
  ```
250
345
 
251
346
  #### 8.3 输出完成信息
@@ -254,15 +349,16 @@ npm install
254
349
  ✅ Sweep E2E 测试项目初始化完成!
255
350
 
256
351
  框架:{所选框架}
352
+ 位置:{TARGET_DIR}
257
353
 
258
354
  项目结构:
259
- flows/ — 测试意图文档目录
260
- flows/reports/ — 执行报告目录
261
- scripts/ — 辅助脚本
262
- (框架配置文件) — {框架名} 配置文件
355
+ {TARGET_DIR}/flows/ — 测试意图文档目录
356
+ {TARGET_DIR}/flows/reports/ — 执行报告目录
357
+ {TARGET_DIR}/scripts/ — 辅助脚本
358
+ (框架配置文件) — {框架名} 配置文件
263
359
 
264
360
  下一步:
265
- 1. 编辑 flows/topology.yaml 添加你的功能模块
361
+ 1. 编辑 {TARGET_DIR}/flows/topology.yaml 添加你的功能模块
266
362
  2. 运行 /sweep-plan 创建测试意图文档
267
363
  3. 运行 /sweep-run 执行测试流程
268
364
  ```
@@ -272,15 +368,16 @@ npm install
272
368
  ## 检查清单
273
369
 
274
370
  - [ ] 已读取框架配置(`deepstorm.sweep.e2eFramework`)
275
- - [ ] `.sweep-init` 不存在 → 继续
276
- - [ ] 项目目录结构已创建(flows/、flows/reports/、scripts/)
371
+ - [ ] 步骤 0A:已选择目标目录(`TARGET_DIR` 已确定)
372
+ - [ ] 步骤 1:检查通过(`e2eProjectPath` 未设置)
373
+ - [ ] 项目目录结构已创建(`{TARGET_DIR}/flows/`、`{TARGET_DIR}/flows/reports/`、`{TARGET_DIR}/scripts/`)
277
374
  - [ ] package.json 已写入(含对应框架的依赖)
278
375
  - [ ] 框架配置文件已生成(如 playwright.config.ts)
279
376
  - [ ] tsconfig.json 已写入
280
377
  - [ ] 用户已输入三个环境的 baseURL
281
- - [ ] .env 已写入(gitignored)
378
+ - [ ] environments 已写入 `.deepstorm/settings.json` 的 `sweep.environments`
282
379
  - [ ] topology.yaml 初始模板已创建
283
380
  - [ ] flow-selector.mjs 已写入
284
381
  - [ ] 已确认 MCP 配置状态(从 `.mcp.json` 读取)
285
- - [ ] .sweep-init 标记文件已创建
286
- - [ ] npm install 已执行
382
+ - [ ] `sweep.e2eProjectPath` 已写入 `.deepstorm/settings.json`
383
+ - [ ] npm install 已在目标目录执行
@@ -17,7 +17,7 @@ mcpCapabilities:
17
17
 
18
18
  ## 前置条件
19
19
 
20
- 运行 `/sweep-plan` 前,请确保当前目录已通过 `/sweep-init` 初始化(存在 `.sweep-init` 标记文件和 `flows/` 目录)。
20
+ 运行 `/sweep-plan` 前,请确保项目已通过 `/sweep-init` 初始化(`.deepstorm/settings.json` 中存在 `sweep.e2eProjectPath` 配置)。
21
21
 
22
22
  ---
23
23
 
@@ -44,10 +44,56 @@ mcpCapabilities:
44
44
 
45
45
  ## 工作流
46
46
 
47
- ### 步骤 1:检查初始化状态
47
+ ### 步骤 1:检查初始化状态与路径导航
48
48
 
49
- - **WHEN** `.sweep-init` 不存在
50
- - **THEN** 提示"当前目录尚未初始化为 Sweep 测试项目。请先运行 /sweep-init 初始化。"并退出
49
+ `.deepstorm/settings.json` 读取 `sweep.e2eProjectPath`,确定 E2E 测试项目的位置,如非根目录则自动切换。支持从子目录向上查找以兼容用户在 E2E 项目子目录中执行的情况。
50
+
51
+ #### 1.1 读取配置(支持向上查找)
52
+
53
+ ```bash
54
+ # 向上查找 .deepstorm/settings.json
55
+ DEEPSTORM_DIR=""
56
+ CUR="$PWD"
57
+ while [ "$CUR" != "/" ]; do
58
+ if [ -f "$CUR/.deepstorm/settings.json" ]; then
59
+ DEEPSTORM_DIR="$CUR"
60
+ break
61
+ fi
62
+ CUR=$(dirname "$CUR")
63
+ done
64
+
65
+ if [ -n "$DEEPSTORM_DIR" ]; then
66
+ E2E_PATH=$(grep -o '"e2eProjectPath"[^,]*' "$DEEPSTORM_DIR/.deepstorm/settings.json" | head -1 | cut -d'"' -f4)
67
+ else
68
+ E2E_PATH=""
69
+ fi
70
+ ```
71
+
72
+ #### 1.2 配置存在 → 路径导航
73
+
74
+ - **WHEN** `E2E_PATH` 不为空
75
+ - **THEN** 判断路径值。注意 `E2E_PATH` 是基于 `DEEPSTORM_DIR`(settings.json 所在目录)的相对路径,若当前在子目录则需拼接:
76
+ ```bash
77
+ if [ "$E2E_PATH" != "." ]; then
78
+ # 若从子目录找到的 settings.json,E2E_PATH 相对于 DEEPSTORM_DIR
79
+ TARGET_DIR="$E2E_PATH"
80
+ if [ -n "$DEEPSTORM_DIR" ] && [ "$DEEPSTORM_DIR" != "$PWD" ]; then
81
+ TARGET_DIR="$DEEPSTORM_DIR/$E2E_PATH"
82
+ fi
83
+ if [ -d "$TARGET_DIR" ]; then
84
+ echo "📂 切换到 E2E 项目目录: $E2E_PATH"
85
+ cd "$TARGET_DIR"
86
+ else
87
+ echo "❌ E2E 项目目录不存在: $E2E_PATH,请重新运行 /sweep-init"
88
+ exit 1
89
+ fi
90
+ fi
91
+ ```
92
+
93
+ #### 1.3 配置不存在 → 报错退出
94
+
95
+ - **WHEN** `E2E_PATH` 为空
96
+ - **THEN** 提示"❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"并退出
51
97
 
52
98
  ### 步骤 2:获取测试需求上下文(MCP 动态适配)
53
99
 
@@ -303,7 +349,7 @@ node scripts/spec-compiler.mjs flows/user-system/login/login.flow.md
303
349
 
304
350
  ## 检查清单
305
351
 
306
- - [ ] 项目已初始化(检查 .sweep-init
352
+ - [ ] 项目已初始化(读取 settings.json → sweep.e2eProjectPath
307
353
  - [ ] 获取到测试需求上下文(Issue / PRD / 描述)
308
354
  - [ ] 确定放置模块位置(topology.yaml)
309
355
  - [ ] 确定文件名
@@ -58,10 +58,54 @@ node scripts/env-manager.mjs --framework
58
58
 
59
59
  ---
60
60
 
61
- ### 步骤 1:检查初始化状态
61
+ ### 步骤 1:检查初始化状态与路径导航
62
62
 
63
- - **WHEN** `.sweep-init` 不存在
64
- - **THEN** 提示"当前目录尚未初始化为 Sweep 测试项目。请先运行 /sweep-init 初始化。"并退出
63
+ `.deepstorm/settings.json` 读取 `sweep.e2eProjectPath`,确定 E2E 测试项目的位置,如非根目录则自动切换。支持从子目录向上查找以兼容用户在 E2E 项目子目录中执行的情况。
64
+
65
+ ```bash
66
+ # 向上查找 .deepstorm/settings.json
67
+ DEEPSTORM_DIR=""
68
+ CUR="$PWD"
69
+ while [ "$CUR" != "/" ]; do
70
+ if [ -f "$CUR/.deepstorm/settings.json" ]; then
71
+ DEEPSTORM_DIR="$CUR"
72
+ break
73
+ fi
74
+ CUR=$(dirname "$CUR")
75
+ done
76
+
77
+ if [ -n "$DEEPSTORM_DIR" ]; then
78
+ E2E_PATH=$(grep -o '"e2eProjectPath"[^,]*' "$DEEPSTORM_DIR/.deepstorm/settings.json" | head -1 | cut -d'"' -f4)
79
+ else
80
+ E2E_PATH=""
81
+ fi
82
+ ```
83
+
84
+ #### 1.1 配置存在 → 路径导航
85
+
86
+ - **WHEN** `E2E_PATH` 不为空
87
+ - **THEN** 判断路径值。注意 `E2E_PATH` 是基于 `DEEPSTORM_DIR`(settings.json 所在目录)的相对路径,若当前在子目录则需拼接:
88
+ ```bash
89
+ if [ "$E2E_PATH" != "." ]; then
90
+ # 若从子目录找到的 settings.json,E2E_PATH 相对于 DEEPSTORM_DIR
91
+ TARGET_DIR="$E2E_PATH"
92
+ if [ -n "$DEEPSTORM_DIR" ] && [ "$DEEPSTORM_DIR" != "$PWD" ]; then
93
+ TARGET_DIR="$DEEPSTORM_DIR/$E2E_PATH"
94
+ fi
95
+ if [ -d "$TARGET_DIR" ]; then
96
+ echo "📂 切换到 E2E 项目目录: $E2E_PATH"
97
+ cd "$TARGET_DIR"
98
+ else
99
+ echo "❌ E2E 项目目录不存在: $E2E_PATH,请重新运行 /sweep-init"
100
+ exit 1
101
+ fi
102
+ fi
103
+ ```
104
+
105
+ #### 1.2 配置不存在 → 报错退出
106
+
107
+ - **WHEN** `E2E_PATH` 为空
108
+ - **THEN** 提示"❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"并退出
65
109
 
66
110
  ---
67
111
 
@@ -162,9 +206,9 @@ node scripts/env-manager.mjs --env staging
162
206
  ]}
163
207
  ```
164
208
 
165
- - `--env staging` → 从 `.env` `BASE_URL_STAGING`
166
- - `--env test` → 从 `.env` `BASE_URL_TEST`
167
- - 不传参 → 使用 `.env` 中的 `DEFAULT_ENV`,未设置则默认 `test`
209
+ - `--env staging` → 从 `settings.json` `sweep.environments.staging` 读取
210
+ - `--env test` → 从 `settings.json` `sweep.environments.test` 读取
211
+ - 不传参 → 使用 `settings.json` `sweep.environments.default`,未设置则默认 `test`
168
212
 
169
213
  #### 4.2 设置环境变量
170
214
 
@@ -179,7 +223,7 @@ BASE_URL=$(node scripts/env-manager.mjs --env test | node -pe "JSON.parse(requir
179
223
 
180
224
  #### 4.3 环境不存在处理
181
225
 
182
- 如果 `baseUrl` 为 `null`(指定的环境在 `.env` 中找不到对应配置):
226
+ 如果 `baseUrl` 为 `null`(指定的环境在 settings.json 中找不到对应配置):
183
227
 
184
228
  - 提示"未找到 [{env}] 环境的 baseURL 配置"
185
229
  - 列出 `availableEnvs` 中已有的环境
@@ -426,7 +470,7 @@ Flow: L01 - 正常登录成功
426
470
  ## 检查清单
427
471
 
428
472
  - [ ] 前置检查:框架配置已读取(`.deepstorm/settings.json` → `sweep.e2eFramework`)
429
- - [ ] 项目已初始化(`.sweep-init` 存在)
473
+ - [ ] 项目已初始化(读取 settings.json → sweep.e2eProjectPath)
430
474
  - [ ] 执行范围已确定(--all / --path / 交互选择)
431
475
  - [ ] 执行模式已选择(hybrid / native / no-parallel / browser)
432
476
  - [ ] MCP 服务按模式正确启动(available → headless / headed / 不启动)
@@ -3,9 +3,13 @@
3
3
  /**
4
4
  * Env Manager — 测试环境配置管理
5
5
  *
6
- * 读取 .env 文件,解析目标环境的 BASE_URL,输出结构化结果。
6
+ * .deepstorm/settings.json 读取目标环境配置,输出结构化结果。
7
7
  * 支持检查 E2E 框架配置和 MCP 可用性。
8
8
  *
9
+ * 所有旧数据源(.env BASE_URL、.sweep-init、scope-config.json 等)
10
+ * 已由 `deepstorm update` 统一迁移到 .deepstorm/settings.json,
11
+ * 本文件无需再做兼容处理。
12
+ *
9
13
  * Usage:
10
14
  * node env-manager.mjs --env test # 解析指定环境
11
15
  * node env-manager.mjs --env test --print # 输出 export 语句
@@ -27,6 +31,65 @@ function getEnvPath() {
27
31
  function getMcpPath() {
28
32
  return resolve(process.cwd(), '.mcp.json');
29
33
  }
34
+ function getDeepstormSettingsPath() {
35
+ return resolve(process.cwd(), '.deepstorm', 'settings.json');
36
+ }
37
+
38
+ // ── settings.json reading ─────────────────────────────────────────
39
+
40
+ /**
41
+ * Read .deepstorm/settings.json and extract environments config.
42
+ * Returns the sweep.environments object or null if not configured.
43
+ */
44
+ export function readDeepstormEnvironments() {
45
+ const settingsPath = getDeepstormSettingsPath();
46
+ if (!existsSync(settingsPath)) return null;
47
+ try {
48
+ const content = readFileSync(settingsPath, 'utf-8');
49
+ const config = JSON.parse(content);
50
+ return config.sweep?.environments || null;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Read .deepstorm/settings.json and extract e2eProjectPath.
58
+ * Returns the project path string or null if not configured.
59
+ */
60
+ export function readE2eProjectPath() {
61
+ const settingsPath = getDeepstormSettingsPath();
62
+ if (!existsSync(settingsPath)) return null;
63
+ try {
64
+ const content = readFileSync(settingsPath, 'utf-8');
65
+ const config = JSON.parse(content);
66
+ return config.sweep?.e2eProjectPath || null;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Convert settings.json environments format to the same structure
74
+ * that listEnvs() returns from .env, for a unified return format.
75
+ *
76
+ * Input: { test: { baseUrl: "..." }, staging: { baseUrl: "..." }, default: "test" }
77
+ * Output: { envMap: { BASE_URL_TEST: "...", BASE_URL_STAGING: "...", DEFAULT_ENV: "test" },
78
+ * defaultEnv: "test" }
79
+ */
80
+ function deepstormEnvsToEnvMap(envs) {
81
+ if (!envs) return { envMap: {}, defaultEnv: 'test' };
82
+ const envMap = {};
83
+ const defaultEnv = envs.default || 'test';
84
+ for (const [name, config] of Object.entries(envs)) {
85
+ if (name === 'default') continue;
86
+ if (config && config.baseUrl) {
87
+ envMap[`BASE_URL_${name.toUpperCase()}`] = config.baseUrl;
88
+ }
89
+ }
90
+ envMap.DEFAULT_ENV = defaultEnv;
91
+ return { envMap, defaultEnv };
92
+ }
30
93
 
31
94
  // ── .env parsing ──────────────────────────────────────────────────
32
95
 
@@ -67,26 +130,35 @@ export function readDotEnv() {
67
130
  }
68
131
 
69
132
  /**
70
- * Get the default environment name.
133
+ * Get the default environment name from .deepstorm/settings.json.
71
134
  * Falls back to 'test' if not configured.
72
135
  */
73
- export function getDefaultEnv(envMap) {
74
- return envMap.DEFAULT_ENV || 'test';
136
+ export function getDefaultEnv() {
137
+ const dsEnvs = readDeepstormEnvironments();
138
+ if (dsEnvs && dsEnvs.default) {
139
+ return dsEnvs.default;
140
+ }
141
+ return 'test';
75
142
  }
76
143
 
77
144
  /**
78
- * List all available environments from .env.
79
- * Detects BASE_URL_{ENV} patterns and returns their names.
145
+ * List all available environments from .deepstorm/settings.json.
146
+ *
147
+ * Reads sweep.environments and returns environment name + URL pairs.
148
+ * Returns empty array if not configured.
80
149
  */
81
- export function listEnvs(envMap) {
150
+ export function listEnvs() {
151
+ const dsEnvs = readDeepstormEnvironments();
152
+ if (!dsEnvs) return [];
153
+
82
154
  const envs = [];
83
- for (const key of Object.keys(envMap)) {
84
- const match = key.match(/^BASE_URL_(.+)$/);
85
- if (match) {
155
+ for (const [name, config] of Object.entries(dsEnvs)) {
156
+ if (name === 'default') continue;
157
+ if (config && config.baseUrl) {
86
158
  envs.push({
87
- name: match[1].toLowerCase(),
88
- key: key,
89
- url: envMap[key],
159
+ name: name.toLowerCase(),
160
+ key: `BASE_URL_${name.toUpperCase()}`,
161
+ url: config.baseUrl,
90
162
  });
91
163
  }
92
164
  }
@@ -94,22 +166,27 @@ export function listEnvs(envMap) {
94
166
  }
95
167
 
96
168
  /**
97
- * Resolve target environment config.
169
+ * Resolve target environment config from .deepstorm/settings.json.
98
170
  *
99
171
  * @param {string} [envName] - Target environment name (e.g. 'staging', 'test')
100
172
  * @returns {{ env: string, baseUrl: string|null, availableEnvs: Array<{name:string,url:string}> }}
101
173
  *
102
- * If envName is omitted, uses DEFAULT_ENV from .env (or 'test').
174
+ * If envName is omitted, uses DEFAULT_ENV (or 'test').
175
+ * If settings.json has no environments config, baseUrl is null and availableEnvs is empty.
103
176
  * If the environment is not found, baseUrl is null and availableEnvs lists alternatives.
104
177
  */
105
178
  export function resolveEnv(envName) {
106
- const envMap = readDotEnv();
107
- const name = envName || getDefaultEnv(envMap);
108
- const availableEnvs = listEnvs(envMap);
179
+ const dsEnvs = readDeepstormEnvironments();
180
+ if (!dsEnvs) {
181
+ const name = envName || 'test';
182
+ return { env: name, baseUrl: null, availableEnvs: [] };
183
+ }
109
184
 
185
+ const { envMap, defaultEnv } = deepstormEnvsToEnvMap(dsEnvs);
186
+ const name = envName || defaultEnv;
187
+ const availableEnvs = listEnvs();
110
188
  const baseUrlKey = `BASE_URL_${name.toUpperCase()}`;
111
189
  const baseUrl = envMap[baseUrlKey] || null;
112
-
113
190
  return { env: name, baseUrl, availableEnvs };
114
191
  }
115
192
 
@@ -167,9 +244,8 @@ if (process.argv[1] === import.meta.filename) {
167
244
  const args = process.argv.slice(2);
168
245
 
169
246
  if (args.includes('--list')) {
170
- const envMap = readDotEnv();
171
- const envs = listEnvs(envMap);
172
- const defaultEnv = getDefaultEnv(envMap);
247
+ const envs = listEnvs();
248
+ const defaultEnv = getDefaultEnv();
173
249
  console.log(JSON.stringify({ default: defaultEnv, environments: envs }));
174
250
  process.exit(0);
175
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstorm/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "DeepStorm CLI — 一键配置项目开发环境",
5
5
  "license": "MIT",
6
6
  "author": "billkang",
@@ -16,7 +16,7 @@
16
16
  "dotenv": "^17.4.2",
17
17
  "handlebars": "^4.7.8",
18
18
  "js-yaml": "^4.1.0",
19
- "@deepstorm/pilot": "^0.8.0"
19
+ "@deepstorm/pilot": "^0.9.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.0.0",