@deepstorm/cli 0.9.0 → 0.9.2

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"));
@@ -6351,10 +6351,11 @@ async function promptContinueSetup(targetDir, projectName, registry2) {
6351
6351
  }
6352
6352
  function buildContext(opts) {
6353
6353
  const projectName = opts.projectName || "project";
6354
+ const packageName = projectName.replace(/[_-]/g, "").toLowerCase();
6354
6355
  return {
6355
6356
  projectName,
6356
- packageName: projectName.replace(/[_-]/g, "").toLowerCase(),
6357
- groupId: "com.example",
6357
+ packageName,
6358
+ groupId: `com.${packageName}`,
6358
6359
  frontend: opts.frontend || false,
6359
6360
  backend: opts.backend || false,
6360
6361
  uiLib: opts.uiLib || false,
@@ -6822,7 +6823,7 @@ tasks.withType<Test> {
6822
6823
  useJUnitPlatform()
6823
6824
  }
6824
6825
  `);
6825
- const appPackage = `${ctx.groupId}.${ctx.packageName}`.replace(/[.-]/g, ".");
6826
+ const appPackage = ctx.groupId.replace(/[.-]/g, ".");
6826
6827
  const appDir = `src/main/java/${appPackage.replace(/\./g, "/")}`;
6827
6828
  ensureDir2(path2.join(projectDir, appDir));
6828
6829
  writeTemplate(projectDir, `${appDir}/Application.java`, `package ${appPackage};
@@ -6917,6 +6918,8 @@ logging:
6917
6918
  ${ctx.packageName}: DEBUG
6918
6919
  `);
6919
6920
  if (hasLiquibase) {
6921
+ const now = /* @__PURE__ */ new Date();
6922
+ const ts = now.toISOString().replace(/\D/g, "").slice(0, 12);
6920
6923
  ensureDir2(path2.join(projectDir, "src/main/resources/db/changelog"));
6921
6924
  writeTemplate(projectDir, "src/main/resources/db/changelog/db.changelog-master.xml", `<?xml version="1.0" encoding="UTF-8"?>
6922
6925
  <databaseChangeLog
@@ -6925,7 +6928,7 @@ logging:
6925
6928
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
6926
6929
  http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
6927
6930
 
6928
- <changeSet id="001" author="deepstorm">
6931
+ <changeSet id="${ts}-001" author="deepstorm">
6929
6932
  <createTable tableName="flyway_schema_history">
6930
6933
  <column name="version" type="VARCHAR(50)"/>
6931
6934
  </createTable>
@@ -9383,6 +9386,7 @@ function registerTemplateCommand(program2, registry2) {
9383
9386
 
9384
9387
  // src/commands/update.ts
9385
9388
  var fs24 = __toESM(require("node:fs"));
9389
+ var path20 = __toESM(require("node:path"));
9386
9390
  var import_node_child_process2 = require("node:child_process");
9387
9391
 
9388
9392
  // src/utils/version.ts
@@ -9814,23 +9818,139 @@ async function updateCLI(fetchFn) {
9814
9818
  return result;
9815
9819
  }
9816
9820
  function getInstalledSkillIds(targetDir) {
9817
- const settingsPath = getDeepStormConfigPath(targetDir);
9821
+ const config = readDeepStormConfig(targetDir);
9822
+ if (!config) return [];
9823
+ const ids = config.installedSkills;
9824
+ return Array.isArray(ids) ? ids : [];
9825
+ }
9826
+ function migrateOldDataSources(targetDir) {
9827
+ const migrated = [];
9818
9828
  try {
9819
- const raw = fs24.readFileSync(settingsPath, "utf-8");
9820
- const config = JSON.parse(raw);
9821
- return config.installedSkills ?? [];
9822
- } catch {
9823
- return [];
9829
+ const claudeSettingsPath = path20.join(targetDir, ".claude", "settings.json");
9830
+ if (fs24.existsSync(claudeSettingsPath)) {
9831
+ const raw = fs24.readFileSync(claudeSettingsPath, "utf-8");
9832
+ const claudeConfig = JSON.parse(raw);
9833
+ if (claudeConfig.deepstorm && typeof claudeConfig.deepstorm === "object" && Object.keys(claudeConfig.deepstorm).length > 0) {
9834
+ writeDeepStormConfig(targetDir, claudeConfig.deepstorm);
9835
+ delete claudeConfig.deepstorm;
9836
+ fs24.writeFileSync(claudeSettingsPath, JSON.stringify(claudeConfig, null, 2) + "\n", "utf-8");
9837
+ migrated.push(".claude/settings.json \u2192 .deepstorm/settings.json");
9838
+ console.log(" \u2714 \u5DF2\u8FC1\u79FB .claude/settings.json \u4E2D\u7684 DeepStorm \u914D\u7F6E");
9839
+ }
9840
+ }
9841
+ } catch (err) {
9842
+ console.log(` \u26A0 deepstorm \u914D\u7F6E\u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9843
+ }
9844
+ try {
9845
+ const sweepInitPath = path20.join(targetDir, ".sweep-init");
9846
+ if (fs24.existsSync(sweepInitPath)) {
9847
+ const existing = readDeepStormConfig(targetDir);
9848
+ const hasE2ePath = existing && existing.sweep?.e2eProjectPath;
9849
+ if (!hasE2ePath) {
9850
+ writeDeepStormConfig(targetDir, { sweep: { e2eProjectPath: "." } });
9851
+ migrated.push(".sweep-init \u2192 sweep.e2eProjectPath");
9852
+ console.log(' \u2714 \u5DF2\u8FC1\u79FB .sweep-init \u2192 sweep.e2eProjectPath = "."');
9853
+ }
9854
+ fs24.rmSync(sweepInitPath, { force: true });
9855
+ if (hasE2ePath) {
9856
+ console.log(" \u2139 .sweep-init \u5DF2\u5220\u9664\uFF08sweep.e2eProjectPath \u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\uFF09");
9857
+ }
9858
+ }
9859
+ } catch (err) {
9860
+ console.log(` \u26A0 .sweep-init \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9861
+ }
9862
+ try {
9863
+ const envPath = path20.join(targetDir, ".env");
9864
+ if (fs24.existsSync(envPath)) {
9865
+ const raw = fs24.readFileSync(envPath, "utf-8");
9866
+ const lines = raw.split("\n");
9867
+ const baseUrlVars = {};
9868
+ let defaultEnv = "test";
9869
+ let hasDefaultEnv = false;
9870
+ const remainingLines = [];
9871
+ for (const line of lines) {
9872
+ const trimmed = line.trim();
9873
+ const baseMatch = trimmed.match(/^BASE_URL_(\w+)=(.+)$/);
9874
+ if (baseMatch) {
9875
+ const envName = baseMatch[1].toLowerCase();
9876
+ let url = baseMatch[2].trim();
9877
+ if (url.startsWith('"') && url.endsWith('"') || url.startsWith("'") && url.endsWith("'")) {
9878
+ url = url.slice(1, -1);
9879
+ }
9880
+ baseUrlVars[envName] = url;
9881
+ continue;
9882
+ }
9883
+ const defaultMatch = trimmed.match(/^DEFAULT_ENV=(.+)$/);
9884
+ if (defaultMatch) {
9885
+ hasDefaultEnv = true;
9886
+ defaultEnv = defaultMatch[1].trim();
9887
+ continue;
9888
+ }
9889
+ remainingLines.push(line);
9890
+ }
9891
+ if (Object.keys(baseUrlVars).length > 0) {
9892
+ const environments = {};
9893
+ for (const [envName, url] of Object.entries(baseUrlVars)) {
9894
+ environments[envName] = { baseUrl: url };
9895
+ }
9896
+ environments.default = defaultEnv;
9897
+ const existing = readDeepStormConfig(targetDir);
9898
+ const hasEnvs = existing && existing.sweep?.environments;
9899
+ if (!hasEnvs) {
9900
+ writeDeepStormConfig(targetDir, { sweep: { environments } });
9901
+ migrated.push(".env BASE_URL \u2192 sweep.environments");
9902
+ console.log(` \u2714 \u5DF2\u8FC1\u79FB .env \u4E2D ${Object.keys(baseUrlVars).length} \u4E2A\u73AF\u5883\u914D\u7F6E`);
9903
+ }
9904
+ const cleaned = remainingLines.join("\n").trimEnd();
9905
+ if (cleaned === "") {
9906
+ 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");
9907
+ } else if (hasDefaultEnv || Object.keys(baseUrlVars).length > 0) {
9908
+ fs24.writeFileSync(envPath, cleaned + "\n", "utf-8");
9909
+ }
9910
+ }
9911
+ }
9912
+ } catch (err) {
9913
+ console.log(` \u26A0 .env baseURL \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9824
9914
  }
9915
+ try {
9916
+ const scopeConfigPath = path20.join(targetDir, ".deepstorm", "scope-config.json");
9917
+ if (fs24.existsSync(scopeConfigPath)) {
9918
+ const raw = fs24.readFileSync(scopeConfigPath, "utf-8");
9919
+ const scopeData = JSON.parse(raw);
9920
+ const scope = {};
9921
+ if ("enabled" in scopeData) scope.enabled = scopeData.enabled;
9922
+ if ("ciEnabled" in scopeData) scope.ciEnabled = scopeData.ciEnabled;
9923
+ if ("domains" in scopeData) scope.domains = scopeData.domains;
9924
+ const existing = readDeepStormConfig(targetDir);
9925
+ const hasReefScope = existing && existing.reef?.scope;
9926
+ if (!hasReefScope) {
9927
+ writeDeepStormConfig(targetDir, { reef: { scope } });
9928
+ migrated.push("scope-config.json \u2192 reef.scope");
9929
+ console.log(" \u2714 \u5DF2\u8FC1\u79FB .deepstorm/scope-config.json \u2192 reef.scope");
9930
+ }
9931
+ fs24.rmSync(scopeConfigPath, { force: true });
9932
+ if (hasReefScope) {
9933
+ console.log(" \u2139 scope-config.json \u5DF2\u5220\u9664\uFF08reef.scope \u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\uFF09");
9934
+ }
9935
+ }
9936
+ } catch (err) {
9937
+ console.log(` \u26A0 scope-config.json \u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
9938
+ }
9939
+ return { migrated };
9825
9940
  }
9826
9941
  function registerUpdateCommand(program2) {
9827
9942
  const cliDir2 = __dirname;
9828
9943
  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());
9944
+ const targetDir = process.cwd();
9945
+ const { migrated } = migrateOldDataSources(targetDir);
9946
+ if (migrated.length > 0) {
9947
+ console.log(`\u2714 \u5DF2\u5B8C\u6210 ${migrated.length} \u9879\u65E7\u6570\u636E\u6E90\u8FC1\u79FB`);
9948
+ }
9949
+ const installedIds = getInstalledSkillIds(targetDir);
9830
9950
  if (installedIds.length === 0) {
9831
9951
  console.log("\u672A\u68C0\u6D4B\u5230\u5DF2\u5B89\u88C5\u7684 skill\uFF0C\u8DF3\u8FC7\u540C\u6B65");
9832
9952
  } else {
9833
- upgradeTemplates(cliDir2, process.cwd(), installedIds);
9953
+ upgradeTemplates(cliDir2, targetDir, installedIds);
9834
9954
  }
9835
9955
  await updateCLI();
9836
9956
  });
@@ -9844,7 +9964,7 @@ function registerPilotCommands(program2) {
9844
9964
 
9845
9965
  // src/commands/doctor.ts
9846
9966
  var fs25 = __toESM(require("node:fs"));
9847
- var path20 = __toESM(require("node:path"));
9967
+ var path21 = __toESM(require("node:path"));
9848
9968
  function getInstalledSkills(targetDir) {
9849
9969
  const settingsPath = getDeepStormConfigPath(targetDir);
9850
9970
  try {
@@ -9862,7 +9982,7 @@ function runDoctor(targetDir) {
9862
9982
  checks.push({
9863
9983
  name: "CLI \u7248\u672C",
9864
9984
  status: "pass",
9865
- message: `v${version}\uFF08\u8FD0\u884C deepstorm update --check \u68C0\u67E5\u6700\u65B0\u7248\u672C\uFF09`
9985
+ message: `v${version}\uFF08\u8FD0\u884C deepstorm update \u68C0\u67E5\u6700\u65B0\u7248\u672C\uFF09`
9866
9986
  });
9867
9987
  let hasDeepStorm = false;
9868
9988
  if (!fs25.existsSync(settingsPath)) {
@@ -9896,15 +10016,15 @@ function runDoctor(targetDir) {
9896
10016
  });
9897
10017
  }
9898
10018
  }
9899
- const skillsDir = path20.join(targetDir, ".claude", "skills");
10019
+ const skillsDir = path21.join(targetDir, ".claude", "skills");
9900
10020
  if (fs25.existsSync(skillsDir)) {
9901
10021
  const skillDirs = fs25.readdirSync(skillsDir).filter(
9902
- (n) => fs25.statSync(path20.join(skillsDir, n)).isDirectory()
10022
+ (n) => fs25.statSync(path21.join(skillsDir, n)).isDirectory()
9903
10023
  );
9904
10024
  let validCount = 0;
9905
10025
  let invalidCount = 0;
9906
10026
  for (const skillName of skillDirs) {
9907
- const skillMd = path20.join(skillsDir, skillName, "SKILL.md");
10027
+ const skillMd = path21.join(skillsDir, skillName, "SKILL.md");
9908
10028
  if (!fs25.existsSync(skillMd)) {
9909
10029
  invalidCount++;
9910
10030
  continue;
@@ -9939,7 +10059,7 @@ function runDoctor(targetDir) {
9939
10059
  const installedSkills = getInstalledSkills(targetDir);
9940
10060
  const missingSkills = [];
9941
10061
  for (const skillId of installedSkills) {
9942
- if (!fs25.existsSync(path20.join(skillsDir, skillId))) {
10062
+ if (!fs25.existsSync(path21.join(skillsDir, skillId))) {
9943
10063
  missingSkills.push(skillId);
9944
10064
  }
9945
10065
  }
@@ -9963,7 +10083,7 @@ function runDoctor(targetDir) {
9963
10083
  message: ".claude/skills/ \u4E0D\u5B58\u5728"
9964
10084
  });
9965
10085
  }
9966
- const mcpPath = path20.join(targetDir, ".mcp.json");
10086
+ const mcpPath = path21.join(targetDir, ".mcp.json");
9967
10087
  if (fs25.existsSync(mcpPath)) {
9968
10088
  try {
9969
10089
  const mcp = JSON.parse(fs25.readFileSync(mcpPath, "utf-8"));
@@ -10085,7 +10205,7 @@ function printDoctorReport(report) {
10085
10205
 
10086
10206
  // src/commands/uninstall.ts
10087
10207
  var fs26 = __toESM(require("node:fs"));
10088
- var path21 = __toESM(require("node:path"));
10208
+ var path22 = __toESM(require("node:path"));
10089
10209
  var import_prompts4 = require("@clack/prompts");
10090
10210
  async function uninstallDeepStorm(targetDir) {
10091
10211
  const settingsPath = getDeepStormConfigPath(targetDir);
@@ -10105,7 +10225,7 @@ async function uninstallDeepStorm(targetDir) {
10105
10225
  }
10106
10226
  cleanInstalled(targetDir);
10107
10227
  resetConfig(targetDir);
10108
- const templatesDir = path21.join(targetDir, ".deepstorm", "templates");
10228
+ const templatesDir = path22.join(targetDir, ".deepstorm", "templates");
10109
10229
  if (fs26.existsSync(templatesDir)) {
10110
10230
  const shouldDelete = await (0, import_prompts4.confirm)({
10111
10231
  message: "\u662F\u5426\u5220\u9664 .deepstorm/templates/ \u76EE\u5F55\uFF1F",
@@ -10125,7 +10245,7 @@ async function uninstallDeepStorm(targetDir) {
10125
10245
  var program = new import_commander.Command();
10126
10246
  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
10247
  function loadRegistry() {
10128
- const registryPath = path22.join(__dirname, "registry.json");
10248
+ const registryPath = path23.join(__dirname, "registry.json");
10129
10249
  try {
10130
10250
  const raw = fs27.readFileSync(registryPath, "utf-8");
10131
10251
  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
@@ -30,9 +30,15 @@ git diff "$FORK_POINT"..HEAD --diff-filter=M --name-only
30
30
 
31
31
  **规则**:不凭空写新文件。先读一个真实存在的同类文件,理解模式后再动手。写新模块时参考已有模块的完整实现。
32
32
 
33
- ### 2. 查阅规范
33
+ ### 2. 🔴 加载编码规范(硬性门禁)
34
34
 
35
- 加载 `reef:reef-style-backend` 技能,阅读 `quick-reference.md` 和必要示例。
35
+ > **必须先通过 Skill tool 加载 `reef:reef-style-backend`,阅读 `quick-reference.md` 了解核心编码规范,并根据当前变更类型阅读对应的维度规范文件(如 `spring-boot.md`、`hibernate.md`、`fastapi-quick-reference.md`、`pytest-testing.md` 等)。完成后方可进入后续代码编写步骤。未加载 code-style 技能不得编写代码,否则视为违反工作流纪律。**
36
+
37
+ 加载完成后,AI MUST 输出以下声明:
38
+
39
+ ```
40
+ ✅ [CODE-STYLE] 已加载后端编码规范(quick-reference + 必要维度规范),所有新增/修改代码将遵循项目编码规范。
41
+ ```
36
42
 
37
43
  涉及库/框架 API 用法时,使用 context7 获取最新文档:`resolve-library-id` → `query-docs`。
38
44
 
@@ -83,6 +89,6 @@ ruff check . && mypy . && python -m pytest
83
89
  ```
84
90
  {{/if}}
85
91
 
86
- ### {{#if reef.backend.java.dbMigration.styleRef}}6{{else}}{{#if reef.backend.python.dbMigration.styleRef}}6{{else}}5{{/if}}{{/if}}. 提交前自检
92
+ ### {{#if reef.backend.java.dbMigration.styleRef}}6{{else}}{{#if reef.backend.python.dbMigration.styleRef}}6{{else}}5{{/if}}{{/if}}. 🔴 提交前自检(后置检查)
87
93
 
88
- 加载 `reef:reef-style-backend` 技能,逐项检查所有规范要求。**未通过不得提交。**
94
+ 重新加载 `reef:reef-style-backend` 技能,逐项对照所有规范要求检查本变更中的每处代码修改。**检查未通过不得提交。**
@@ -31,9 +31,15 @@ git diff "$FORK_POINT"..HEAD --diff-filter=M --name-only
31
31
 
32
32
  **规则**:不凭空写新文件。先读一个真实存在的同类文件,理解模式后再动手。
33
33
 
34
- ### 2. 查阅规范
34
+ ### 2. 🔴 加载编码规范(硬性门禁)
35
35
 
36
- 加载 `reef:reef-style-frontend` 技能,阅读 `quick-reference.md` 和必要示例。
36
+ > **必须先通过 Skill tool 加载 `reef:reef-style-frontend`,阅读 `quick-reference.md` 了解核心编码规范,并根据当前变更类型阅读对应的维度规范文件(框架规范、UI 组件库规范、CSS 方案、TypeScript 配置、测试框架等)。完成后方可进入后续代码编写步骤。未加载 code-style 技能不得编写代码,否则视为违反工作流纪律。**
37
+
38
+ 加载完成后,AI MUST 输出以下声明:
39
+
40
+ ```
41
+ ✅ [CODE-STYLE] 已加载前端编码规范(quick-reference + 必要维度规范),所有新增/修改代码将遵循项目编码规范。
42
+ ```
37
43
 
38
44
  涉及库/框架 API 用法时,使用 context7 获取最新文档:`resolve-library-id` → `query-docs`。
39
45
 
@@ -79,6 +85,6 @@ git diff "$FORK_POINT"..HEAD --diff-filter=M --name-only
79
85
  {{reef.frontend.framework.formatCmd}} lint && {{reef.frontend.framework.formatCmd}} typecheck && {{reef.frontend.framework.formatCmd}} test
80
86
  ```
81
87
 
82
- ### {{#if reef.frontend.framework.sourcePath}}6{{else}}5{{/if}}. 提交前自检
88
+ ### {{#if reef.frontend.framework.sourcePath}}6{{else}}5{{/if}}. 🔴 提交前自检(后置检查)
83
89
 
84
- 加载 `reef:reef-style-frontend` 技能,逐项检查所有规范要求。**未通过不得提交。**
90
+ 重新加载 `reef:reef-style-frontend` 技能,逐项对照所有规范要求检查本变更中的每处代码修改。**检查未通过不得提交。**
@@ -89,8 +89,6 @@ bash packages/reef/hooks/reef-scope-split.sh
89
89
  - `ciEnabled`: CI 门禁开关(独立控制)
90
90
  - `domains`: 项目业务领域列表(空 = AI 自由分类,非空 = AI 对齐到这些领域)
91
91
 
92
- > **注意:** 旧版 `.deepstorm/scope-config.json` 已不再使用。如果存在该文件,运行 `deepstorm scope setup` 会自动迁移到 `settings.json`。
93
-
94
92
  ## 使用流程
95
93
 
96
94
  1. **安装**: 运行一次 `reef-scope-setup.sh install`
@@ -128,7 +126,7 @@ scope-check:
128
126
  ## 常见问题
129
127
 
130
128
  **Q: 我不想在所有项目上都启用这个检查?**
131
- A: 每个项目独立安装和配置,`scope-config.json` 中的 `enabled: false` 可关闭。
129
+ A: 每个项目独立安装和配置,settings.json `reef.scope.enabled: false` 可关闭。
132
130
 
133
131
  **Q: 我改了文档也要被阻断吗?**
134
132
  A: 不会。文档(markdown、README 等)变更会被归类为 `documentation` 领域,不计入多领域阻断判断。
@@ -377,6 +377,7 @@ openspec instructions tasks --change "$CHANGE" --json
377
377
  ### 流程
378
378
 
379
379
  1. **调用 Skill 工具** — 根据 tasks.md 的任务范围,加载可能适用的 superpowers:
380
+ - `reef:reef-style-backend` 或 `reef:reef-style-frontend`(如果 tasks 涉及后端/前端**代码改动**,**必须**加载对应 code-style 技能,未加载不得进入阶段四)
380
381
  - `superpowers:test-driven-development`(如果 tasks 涉及 TypeScript/Java/Python 等代码改动)
381
382
  - `superpowers:verification-before-completion`
382
383
  - 根据 tasks.md 内容判断是否适用其他 superpowers
@@ -36,11 +36,14 @@ PLAN_FILE="docs/superpowers/plans/$(date +%Y-%m-%d)-$CHANGE.md"
36
36
  适用于:文档修改、配置文件调整、SKILL.md 流程修改、测试框架搭建、简单重构(测试覆盖充分)
37
37
 
38
38
  ```
39
- 1. 直接实现代码变更
40
- 2. 执行后置验证:buildlint → test
41
- 3. 如果验证失败修复 重新验证 通过后才标完成
42
- 4. 如果发现复杂度超预期 → 暂停并向用户建议升级为 tdd mode
43
- 5. 标记 task 为完成
39
+ 1. 🔴 确认 code-style 已加载 — 检查 `reef:reef-style-backend` 或 `reef:reef-style-frontend` 是否已通过 Skill tool 加载
40
+ - 已加载进入步骤 2
41
+ - 未加载暂停并先通过 Skill tool 加载对应技能,阅读 quick-reference.md 后再继续
42
+ 2. 直接实现代码变更
43
+ 3. 执行后置验证:build lint → test
44
+ 4. 如果验证失败 → 修复 → 重新验证 → 通过后才标完成
45
+ 5. 如果发现复杂度超预期 → 暂停并向用户建议升级为 tdd mode
46
+ 6. ✅ 标记 task 为完成
44
47
  ```
45
48
 
46
49
  > **⚠️ Plan mode 风险预警:** plan mode 不要求前置测试,但**后置验证不可跳过**。如果 build/lint/test 任何一步失败,该 task **不得**被标记为完成。
@@ -50,6 +53,7 @@ PLAN_FILE="docs/superpowers/plans/$(date +%Y-%m-%d)-$CHANGE.md"
50
53
  适用于:新增业务逻辑、Bug 修复、权限/安全变更、资金/幂等性变更、状态机/并发逻辑
51
54
 
52
55
  **🔴 RED — 先写测试**
56
+ - 先确认 code-style 已加载:检查 `reef:reef-style-backend` 或 `reef:reef-style-frontend` 是否已通过 Skill tool 加载,未加载则先加载
53
57
  - 根据 spec 的 Scenario 编写单元测试
54
58
  - 运行测试,确认失败(红)
55
59
  - 如果测试意外通过了,说明测试写的太弱,需改进
@@ -16,6 +16,7 @@
16
16
  | 技能 | 类型 | 对本变更的要求 |
17
17
  |------|------|---------------|
18
18
  | {skill-name} | {类型} | {要求描述} |
19
+ | `reef:reef-style-backend` / `reef:reef-style-frontend` | 🟢 **Code Style** | 所有新增/修改的代码必须遵循项目编码规范,加载后阅读 `quick-reference.md` 和必要维度规范 |
19
20
 
20
21
  ### Plan Mode 纪律确认
21
22
 
@@ -39,6 +40,7 @@
39
40
  | 技能 | 类型 | 对本变更的要求 |
40
41
  |------|------|---------------|
41
42
  | test-driven-development | 🔴 **Rigid** | 每个代码行为改动必须先写测试、看失败、再写实现 |
43
+ | `reef:reef-style-backend` / `reef:reef-style-frontend` | 🟢 **Code Style** | 所有新增/修改的代码必须遵循项目编码规范,加载后阅读 `quick-reference.md` 和必要维度规范 |
42
44
 
43
45
  ### TDD 纪律确认
44
46
 
@@ -57,6 +59,7 @@
57
59
  - [ ] 📝 **实现计划已生成**(superpowers:writing-plans 完成,存于 `docs/superpowers/plans/`)
58
60
  - [ ] 用户已审阅并批准所有 SDD 文档及实现计划
59
61
  - [ ] 🔍 **Superpowers 技能已加载**(Skill 工具已调用)
62
+ - [ ] 🎨 **Code-Style 技能已加载**(`reef:reef-style-backend`/`reef:reef-style-frontend` 已通过 Skill 工具加载并阅读规范)
60
63
  - [ ] 🚨 **Rigid 纪律已向用户声明并获得确认**
61
64
  - [ ] Path A 检查项:Git 分支已从 main 岔出
62
65
  - [ ] Path B 检查项:OpenSpec change 已创建,brainstorming 文件已产出
@@ -93,18 +93,12 @@ E2E_PATH=$(cat .deepstorm/settings.json 2>/dev/null | grep -o '"e2eProjectPath"[
93
93
 
94
94
  #### 1.2 已初始化 → 退出
95
95
 
96
- - **WHEN** `sweep.e2eProjectPath` 已设置(或旧 `.sweep-init` 文件存在)
97
- - **THEN** 提示"当前项目已初始化为 Sweep 测试项目。如需重新初始化,请删除 `sweep.e2eProjectPath` 配置或 `.sweep-init` 文件后重试。"并退出
96
+ - **WHEN** `sweep.e2eProjectPath` 已设置
97
+ - **THEN** 提示"当前项目已初始化为 Sweep 测试项目。如需重新初始化,请删除 `sweep.e2eProjectPath` 配置后重试。"并退出
98
98
 
99
- #### 1.3 向后兼容:旧 .sweep-init 文件检测
99
+ #### 1.3 未初始化 继续
100
100
 
101
- - **WHEN** `settings.json` 中 `sweep.e2eProjectPath` 未设置,但 `.sweep-init` 文件存在
102
- - **THEN** 提示"ℹ️ 检测到旧版 .sweep-init 标记。建议删除该文件后重新运行 /sweep-init 以使用新版配置。"
103
- - **THEN** 提示已初始化,退出
104
-
105
- #### 1.4 未初始化 → 继续
106
-
107
- - **WHEN** 以上均不满足
101
+ - **WHEN** `sweep.e2eProjectPath` 未设置
108
102
  - **THEN** 继续执行初始化流程
109
103
 
110
104
  ---
@@ -375,7 +369,7 @@ fi
375
369
 
376
370
  - [ ] 已读取框架配置(`deepstorm.sweep.e2eFramework`)
377
371
  - [ ] 步骤 0A:已选择目标目录(`TARGET_DIR` 已确定)
378
- - [ ] 步骤 1:检查通过(`e2eProjectPath` 未设置,且 `.sweep-init` 不存在)
372
+ - [ ] 步骤 1:检查通过(`e2eProjectPath` 未设置)
379
373
  - [ ] 项目目录结构已创建(`{TARGET_DIR}/flows/`、`{TARGET_DIR}/flows/reports/`、`{TARGET_DIR}/scripts/`)
380
374
  - [ ] package.json 已写入(含对应框架的依赖)
381
375
  - [ ] 框架配置文件已生成(如 playwright.config.ts)
@@ -90,21 +90,10 @@ fi
90
90
  fi
91
91
  ```
92
92
 
93
- #### 1.3 配置不存在 → 向后兼容检查
93
+ #### 1.3 配置不存在 → 报错退出
94
94
 
95
95
  - **WHEN** `E2E_PATH` 为空
96
- - **THEN** 检查旧 `.sweep-init` 文件是否存在(从当前目录和 DEEPSTORM_DIR 向上查找):
97
- ```bash
98
- if [ -f ".sweep-init" ]; then
99
- echo "ℹ️ 检测到旧版 .sweep-init 标记。建议运行 /sweep-init 刷新配置。"
100
- # 继续执行(兼容旧项目)
101
- elif [ -n "$DEEPSTORM_DIR" ] && [ -f "$DEEPSTORM_DIR/.sweep-init" ]; then
102
- echo "ℹ️ 检测到旧版 .sweep-init 标记(项目根: $DEEPSTORM_DIR)。建议运行 /sweep-init 刷新配置。"
103
- else
104
- echo "❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"
105
- exit 1
106
- fi
107
- ```
96
+ - **THEN** 提示"❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"并退出
108
97
 
109
98
  ### 步骤 2:获取测试需求上下文(MCP 动态适配)
110
99
 
@@ -24,8 +24,6 @@ deepstorm:
24
24
  /sweep-run flows/login.flow.md # 单文件
25
25
  /sweep-run flows/login.flow.md --flow L02 # 单 Flow
26
26
  /sweep-run --env staging # 指定测试环境
27
- /sweep-run --text # 强制使用文本输入模式选择(不启动 TTY checkbox)
28
- /sweep-run --all --text # 全量 + 文本模式
29
27
  ```
30
28
 
31
29
  ### 执行模式说明
@@ -102,21 +100,10 @@ fi
102
100
  fi
103
101
  ```
104
102
 
105
- #### 1.2 配置不存在 → 向后兼容检查
103
+ #### 1.2 配置不存在 → 报错退出
106
104
 
107
105
  - **WHEN** `E2E_PATH` 为空
108
- - **THEN** 检查旧 `.sweep-init` 文件是否存在(从当前目录和 DEEPSTORM_DIR 向上查找):
109
- ```bash
110
- if [ -f ".sweep-init" ]; then
111
- echo "ℹ️ 检测到旧版 .sweep-init 标记。建议运行 /sweep-init 刷新配置。"
112
- # 继续执行(兼容旧项目)
113
- elif [ -n "$DEEPSTORM_DIR" ] && [ -f "$DEEPSTORM_DIR/.sweep-init" ]; then
114
- echo "ℹ️ 检测到旧版 .sweep-init 标记(项目根: $DEEPSTORM_DIR)。建议运行 /sweep-init 刷新配置。"
115
- else
116
- echo "❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"
117
- exit 1
118
- fi
119
- ```
106
+ - **THEN** 提示"❌ 未检测到 E2E 项目。请先运行 /sweep-init 初始化。"并退出
120
107
 
121
108
  ---
122
109
 
@@ -124,18 +111,30 @@ fi
124
111
 
125
112
  根据用户输入参数或交互选择,确定要执行的 .flow.md 文件列表。
126
113
 
127
- #### 2.1 交互模式(无参数)
114
+ #### 2.1 交互模式(无参数) — 对话式选择
115
+
116
+ 不使用 `@inquirer/checkbox` 交互选择器(在 Claude Code 环境中 TTY/TUI 不可靠),改为**对话式选择**:
117
+
118
+ 1. 运行 `node scripts/flow-selector.mjs --list` 获取可用 .flow.md 文件列表
119
+ 2. 检查输出是否为有效 JSON(含 `files[]`),若脚本报错则提示用户后退出
120
+ 3. 在消息中直接列出可用文件供用户选择,参考格式:
121
+
122
+ ```markdown
123
+ 📋 可用测试模块:
124
+
125
+ 1. user-system/register(3 个用例:L01-L03)
126
+ 2. user-system/login(2 个用例:L01-L02)
127
+ 3. tasks/crud(4 个用例:T01-T04)
128
128
 
129
- **必须运行** `node scripts/flow-selector.mjs` 进行选择。
129
+ 请选择:全部执行 / 输入序号(如 1,3) / 输入模块名
130
+ ```
130
131
 
131
- 1. 先检查 `scripts/flow-selector.mjs` 是否存在
132
- 2. 运行 `node scripts/flow-selector.mjs`
133
- 3. 脚本自动检测 TTY 可用性:
134
- - **TTY 可用** 展示 `@inquirer/checkbox` 勾选列表(空格勾选、回车确认、a=全选)
135
- - **TTY 不可用** 自动回退到 **readline 文本输入模式**(序号选择、逗号分隔、输入 a 全选)
136
- - 也可通过 `--text` 强制文本模式、`--tui` 强制 checkbox 模式
137
- 4. 读取 `.sweep-selection.json` 获取选中结果
138
- 5. 列出将要执行的文件,让用户确认后开始执行(**以下为示例格式,实际内容根据项目动态生成**)
132
+ 4. 等待用户在聊天中回复选择
133
+ 5. 根据用户选择,直接写入 `.sweep-selection.json`:
134
+ - **全部执行** `{"type":"all"}`
135
+ - **指定文件全部 Flow** → `{"type":"selection","files":[{"file":"/abs/path/to/file.flow.md","flows":[],"all":true}]}`
136
+ - **指定文件的特定 Flow** → `{"type":"selection","files":[{"file":"/abs/path/to/file.flow.md","flows":["L01"],"all":false}]}`
137
+ 6. 输出选中文件的总览,确认后继续执行(**以下为示例格式,实际内容根据项目动态生成**):
139
138
 
140
139
  ```
141
140
  选中的模块:user-system, tasks
@@ -158,8 +157,6 @@ fi
158
157
  | `--env {env}` | 切换目标环境 | `/sweep-run --all --env staging` |
159
158
  | `--browser` | 打开浏览器窗口逐步骤调试 | `/sweep-run --all --browser` |
160
159
  | `--no-parallel` | 使用批量但不并行 | `/sweep-run --all --no-parallel` |
161
- | `--text` | 强制文本输入模式选择(跳过 TTY checkbox) | `/sweep-run --text` |
162
- | `--tui` | 强制 TUI checkbox 模式 | `/sweep-run --tui` |
163
160
 
164
161
  #### 2.3 文件不存在处理
165
162
 
@@ -217,9 +214,9 @@ node scripts/env-manager.mjs --env staging
217
214
  ]}
218
215
  ```
219
216
 
220
- - `--env staging` → 从 `settings.json` 的 `sweep.environments.staging` 读取(回退到 `.env`)
221
- - `--env test` → 从 `settings.json` 的 `sweep.environments.test` 读取(回退到 `.env`)
222
- - 不传参 → 使用 `settings.json` 的 `sweep.environments.default`(回退到 `.env` 的 `DEFAULT_ENV`),未设置则默认 `test`
217
+ - `--env staging` → 从 `settings.json` 的 `sweep.environments.staging` 读取
218
+ - `--env test` → 从 `settings.json` 的 `sweep.environments.test` 读取
219
+ - 不传参 → 使用 `settings.json` 的 `sweep.environments.default`,未设置则默认 `test`
223
220
 
224
221
  #### 4.2 设置环境变量
225
222
 
@@ -234,7 +231,7 @@ BASE_URL=$(node scripts/env-manager.mjs --env test | node -pe "JSON.parse(requir
234
231
 
235
232
  #### 4.3 环境不存在处理
236
233
 
237
- 如果 `baseUrl` 为 `null`(指定的环境在 `.env` 中找不到对应配置):
234
+ 如果 `baseUrl` 为 `null`(指定的环境在 settings.json 中找不到对应配置):
238
235
 
239
236
  - 提示"未找到 [{env}] 环境的 baseURL 配置"
240
237
  - 列出 `availableEnvs` 中已有的环境
@@ -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 语句
@@ -126,49 +130,35 @@ export function readDotEnv() {
126
130
  }
127
131
 
128
132
  /**
129
- * Get the default environment name.
130
- * Priority: settings.json > .env
133
+ * Get the default environment name from .deepstorm/settings.json.
131
134
  * Falls back to 'test' if not configured.
132
135
  */
133
- export function getDefaultEnv(envMap) {
136
+ export function getDefaultEnv() {
134
137
  const dsEnvs = readDeepstormEnvironments();
135
138
  if (dsEnvs && dsEnvs.default) {
136
139
  return dsEnvs.default;
137
140
  }
138
- return envMap.DEFAULT_ENV || 'test';
141
+ return 'test';
139
142
  }
140
143
 
141
144
  /**
142
- * List all available environments.
143
- * Priority: settings.json > .env
144
- * 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.
145
149
  */
146
- export function listEnvs(envMap) {
150
+ export function listEnvs() {
147
151
  const dsEnvs = readDeepstormEnvironments();
148
- if (dsEnvs) {
149
- const envs = [];
150
- for (const [name, config] of Object.entries(dsEnvs)) {
151
- if (name === 'default') continue;
152
- if (config && config.baseUrl) {
153
- envs.push({
154
- name: name.toLowerCase(),
155
- key: `BASE_URL_${name.toUpperCase()}`,
156
- url: config.baseUrl,
157
- });
158
- }
159
- }
160
- return envs;
161
- }
152
+ if (!dsEnvs) return [];
162
153
 
163
- // Fallback to .env-based envMap
164
154
  const envs = [];
165
- for (const key of Object.keys(envMap)) {
166
- const match = key.match(/^BASE_URL_(.+)$/);
167
- if (match) {
155
+ for (const [name, config] of Object.entries(dsEnvs)) {
156
+ if (name === 'default') continue;
157
+ if (config && config.baseUrl) {
168
158
  envs.push({
169
- name: match[1].toLowerCase(),
170
- key: key,
171
- url: envMap[key],
159
+ name: name.toLowerCase(),
160
+ key: `BASE_URL_${name.toUpperCase()}`,
161
+ url: config.baseUrl,
172
162
  });
173
163
  }
174
164
  }
@@ -176,36 +166,27 @@ export function listEnvs(envMap) {
176
166
  }
177
167
 
178
168
  /**
179
- * Resolve target environment config.
180
- *
181
- * Priority: settings.json > .env
169
+ * Resolve target environment config from .deepstorm/settings.json.
182
170
  *
183
171
  * @param {string} [envName] - Target environment name (e.g. 'staging', 'test')
184
172
  * @returns {{ env: string, baseUrl: string|null, availableEnvs: Array<{name:string,url:string}> }}
185
173
  *
186
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.
187
176
  * If the environment is not found, baseUrl is null and availableEnvs lists alternatives.
188
177
  */
189
178
  export function resolveEnv(envName) {
190
- // Try settings.json first
191
179
  const dsEnvs = readDeepstormEnvironments();
192
- if (dsEnvs) {
193
- const { envMap, defaultEnv } = deepstormEnvsToEnvMap(dsEnvs);
194
- const name = envName || defaultEnv;
195
- const availableEnvs = listEnvs(envMap);
196
- const baseUrlKey = `BASE_URL_${name.toUpperCase()}`;
197
- const baseUrl = envMap[baseUrlKey] || null;
198
- return { env: name, baseUrl, availableEnvs };
180
+ if (!dsEnvs) {
181
+ const name = envName || 'test';
182
+ return { env: name, baseUrl: null, availableEnvs: [] };
199
183
  }
200
184
 
201
- // Fallback to .env
202
- const envMap = readDotEnv();
203
- const name = envName || getDefaultEnv(envMap);
204
- const availableEnvs = listEnvs(envMap);
205
-
185
+ const { envMap, defaultEnv } = deepstormEnvsToEnvMap(dsEnvs);
186
+ const name = envName || defaultEnv;
187
+ const availableEnvs = listEnvs();
206
188
  const baseUrlKey = `BASE_URL_${name.toUpperCase()}`;
207
189
  const baseUrl = envMap[baseUrlKey] || null;
208
-
209
190
  return { env: name, baseUrl, availableEnvs };
210
191
  }
211
192
 
@@ -263,9 +244,8 @@ if (process.argv[1] === import.meta.filename) {
263
244
  const args = process.argv.slice(2);
264
245
 
265
246
  if (args.includes('--list')) {
266
- const envMap = readDotEnv();
267
- const envs = listEnvs(envMap);
268
- const defaultEnv = getDefaultEnv(envMap);
247
+ const envs = listEnvs();
248
+ const defaultEnv = getDefaultEnv();
269
249
  console.log(JSON.stringify({ default: defaultEnv, environments: envs }));
270
250
  process.exit(0);
271
251
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Sweep Flow Selector — 基于 @inquirer/checkbox 的层级选择工具
4
+ * Sweep Flow Selector — 交互/非交互式选择工具
5
5
  *
6
6
  * 支持选择粒度:
7
7
  * 1. 模块级别(eg. user-system)
@@ -13,6 +13,7 @@
13
13
  * 将选中结果写入 .sweep-selection.json 并输出到 stdout。
14
14
  *
15
15
  * 使用方式:
16
+ * node scripts/flow-selector.mjs --list # 非交互:列出可用文件(JSON)
16
17
  * node scripts/flow-selector.mjs # 自动检测 TTY(checkbox 或文本回退)
17
18
  * node scripts/flow-selector.mjs --tui # 强制 TUI checkbox 模式
18
19
  * node scripts/flow-selector.mjs --text # 强制文本输入模式
@@ -142,6 +143,45 @@ function scanFlowFiles(dir) {
142
143
  return result;
143
144
  }
144
145
 
146
+ // ── 非交互模式:构建文件列表 JSON ─────────────────────────
147
+
148
+ function buildFileList(baseFlowsDir) {
149
+ const topologyPath = join(baseFlowsDir, 'topology.yaml');
150
+ const files = [];
151
+
152
+ if (existsSync(topologyPath)) {
153
+ const yaml = readFileSync(topologyPath, 'utf-8');
154
+ const modules = parseTopology(yaml);
155
+
156
+ function walkModules(mods, prefix = '') {
157
+ for (const mod of mods) {
158
+ const path = prefix ? `${prefix}/${mod.name}` : mod.name;
159
+ if (mod.children && mod.children.length > 0) {
160
+ walkModules(mod.children, path);
161
+ } else {
162
+ const flowFile = resolveFlowFile(path, baseFlowsDir);
163
+ if (flowFile) {
164
+ const content = readFileSync(flowFile, 'utf-8');
165
+ const flows = parseFlows(content);
166
+ files.push({ file: flowFile, relative: path, flows });
167
+ }
168
+ }
169
+ }
170
+ }
171
+ walkModules(modules);
172
+ } else {
173
+ const scanned = scanFlowFiles(baseFlowsDir);
174
+ for (const file of scanned) {
175
+ const content = readFileSync(file, 'utf-8');
176
+ const flows = parseFlows(content);
177
+ const rel = relative(baseFlowsDir, file);
178
+ files.push({ file, relative: rel, flows });
179
+ }
180
+ }
181
+
182
+ return { files, totalFiles: files.length, totalFlows: files.reduce((s, f) => s + f.flows.length, 0) };
183
+ }
184
+
145
185
  // ── 构建 Choice 树 ──────────────────────────────────────────
146
186
 
147
187
  function buildChoicesWithFlows(modules, baseFlowsDir, prefix = '') {
@@ -341,6 +381,22 @@ async function main() {
341
381
  const cliArgs = process.argv.slice(2);
342
382
  const forceText = cliArgs.includes('--text');
343
383
  const forceTui = cliArgs.includes('--tui');
384
+ const listMode = cliArgs.includes('--list');
385
+
386
+ // ── 非交互模式:直接输出可用文件列表 JSON ──────────────
387
+ if (listMode) {
388
+ if (!existsSync(BASE_FLOWS_DIR)) {
389
+ console.error(JSON.stringify({ error: 'flows/ 目录不存在' }));
390
+ process.exit(1);
391
+ }
392
+ const list = buildFileList(BASE_FLOWS_DIR);
393
+ if (list.files.length === 0) {
394
+ console.error(JSON.stringify({ error: '没有找到任何 .flow.md 文件' }));
395
+ process.exit(1);
396
+ }
397
+ console.log(JSON.stringify(list, null, 2));
398
+ return;
399
+ }
344
400
 
345
401
  try {
346
402
  let modules = [];
@@ -471,6 +527,7 @@ export {
471
527
  buildChoicesWithFlows,
472
528
  normalizeSelection,
473
529
  buildFlatFileChoices,
530
+ buildFileList,
474
531
  askTextSelection,
475
532
  isTtyAvailable,
476
533
  getCheckbox,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstorm/cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
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.9.0"
19
+ "@deepstorm/pilot": "^0.9.2"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.0.0",