@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 +185 -65
- package/dist/skills/reef-commit/SKILL.md +1 -1
- package/dist/skills/reef-gen-backend/SKILL.md.tmpl +10 -4
- package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +10 -4
- package/dist/skills/reef-scope/SKILL.md +1 -3
- package/dist/skills/reef-start/SKILL.md.tmpl +1 -0
- package/dist/skills/reef-start/references/stage-4-implementation.md +9 -5
- package/dist/skills/reef-start/references/superpowers-gate.md +3 -0
- package/dist/skills/sweep-init/SKILL.md +5 -11
- package/dist/skills/sweep-plan/SKILL.md.tmpl +2 -13
- package/dist/skills/sweep-run/SKILL.md +28 -31
- package/dist/skills/sweep-run/scripts/env-manager.mjs +30 -50
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +58 -1
- package/package.json +2 -2
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(
|
|
1191
|
-
return /^\.|this\b/.test(
|
|
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(
|
|
1196
|
-
return
|
|
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(
|
|
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:
|
|
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),
|
|
2589
|
+
var params = this.setupFullMustacheParams(decorator, program2, void 0), path24 = decorator.path;
|
|
2590
2590
|
this.useDecorators = true;
|
|
2591
|
-
this.opcode("registerDecorator", params.length,
|
|
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
|
|
2656
|
-
this.opcode("getContext",
|
|
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
|
-
|
|
2660
|
-
this.accept(
|
|
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
|
|
2665
|
-
|
|
2666
|
-
this.accept(
|
|
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),
|
|
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
|
-
|
|
2677
|
-
|
|
2678
|
-
this.accept(
|
|
2679
|
-
this.opcode("invokeHelper", params.length,
|
|
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(
|
|
2683
|
-
this.addDepth(
|
|
2684
|
-
this.opcode("getContext",
|
|
2685
|
-
var 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,
|
|
2687
|
+
this.opcode("lookupBlockParam", blockParamId, path24.parts);
|
|
2688
2688
|
} else if (!name) {
|
|
2689
2689
|
this.opcode("pushContext");
|
|
2690
|
-
} else if (
|
|
2690
|
+
} else if (path24.data) {
|
|
2691
2691
|
this.options.data = true;
|
|
2692
|
-
this.opcode("lookupData",
|
|
2692
|
+
this.opcode("lookupData", path24.depth, path24.parts, path24.strict);
|
|
2693
2693
|
} else {
|
|
2694
|
-
this.opcode("lookupOnContext",
|
|
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
|
|
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
|
-
|
|
3050
|
+
path24 = url.path;
|
|
3051
3051
|
}
|
|
3052
|
-
var isAbsolute = exports2.isAbsolute(
|
|
3053
|
-
var parts =
|
|
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
|
-
|
|
3071
|
-
if (
|
|
3072
|
-
|
|
3070
|
+
path24 = parts.join("/");
|
|
3071
|
+
if (path24 === "") {
|
|
3072
|
+
path24 = isAbsolute ? "/" : ".";
|
|
3073
3073
|
}
|
|
3074
3074
|
if (url) {
|
|
3075
|
-
url.path =
|
|
3075
|
+
url.path = path24;
|
|
3076
3076
|
return urlGenerate(url);
|
|
3077
3077
|
}
|
|
3078
|
-
return
|
|
3078
|
+
return path24;
|
|
3079
3079
|
}
|
|
3080
3080
|
exports2.normalize = normalize;
|
|
3081
|
-
function
|
|
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 =
|
|
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 =
|
|
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
|
|
5862
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
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
|
|
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
|
|
6357
|
-
groupId:
|
|
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 =
|
|
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
|
|
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
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 检查(不推荐):
|
|
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`
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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`
|
|
97
|
-
- **THEN** 提示"当前项目已初始化为 Sweep 测试项目。如需重新初始化,请删除 `sweep.e2eProjectPath`
|
|
96
|
+
- **WHEN** `sweep.e2eProjectPath` 已设置
|
|
97
|
+
- **THEN** 提示"当前项目已初始化为 Sweep 测试项目。如需重新初始化,请删除 `sweep.e2eProjectPath` 配置后重试。"并退出
|
|
98
98
|
|
|
99
|
-
#### 1.3
|
|
99
|
+
#### 1.3 未初始化 → 继续
|
|
100
100
|
|
|
101
|
-
- **WHEN** `
|
|
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`
|
|
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**
|
|
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**
|
|
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
|
-
|
|
129
|
+
请选择:全部执行 / 输入序号(如 1,3) / 输入模块名
|
|
130
|
+
```
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- **
|
|
135
|
-
- **
|
|
136
|
-
|
|
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`
|
|
221
|
-
- `--env test` → 从 `settings.json` 的 `sweep.environments.test`
|
|
222
|
-
- 不传参 → 使用 `settings.json` 的 `sweep.environments.default
|
|
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`(指定的环境在
|
|
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
|
-
*
|
|
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(
|
|
136
|
+
export function getDefaultEnv() {
|
|
134
137
|
const dsEnvs = readDeepstormEnvironments();
|
|
135
138
|
if (dsEnvs && dsEnvs.default) {
|
|
136
139
|
return dsEnvs.default;
|
|
137
140
|
}
|
|
138
|
-
return
|
|
141
|
+
return 'test';
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
/**
|
|
142
|
-
* List all available environments.
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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(
|
|
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
|
|
166
|
-
|
|
167
|
-
if (
|
|
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:
|
|
170
|
-
key:
|
|
171
|
-
url:
|
|
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
|
|
194
|
-
|
|
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
|
-
|
|
202
|
-
const
|
|
203
|
-
const
|
|
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
|
|
267
|
-
const
|
|
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 —
|
|
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.
|
|
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.
|
|
19
|
+
"@deepstorm/pilot": "^0.9.2"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.0.0",
|