@godv61/dsh-task-engine 0.23.9 → 0.25.0
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/.acceptance.mjs +151 -0
- package/.assessment-batch1.mjs +423 -0
- package/.e2e-presets.mjs +230 -0
- package/.enforce-test.mjs +139 -0
- package/.evidence-test.mjs +119 -0
- package/.filter-test.mjs +93 -0
- package/.freeze-test.mjs +199 -0
- package/.hook-consistency.mjs +60 -0
- package/.hook-test.mjs +8 -3
- package/.p0-test.mjs +141 -50
- package/.revision-test.mjs +114 -0
- package/.roundtrip-test.mjs +63 -0
- package/.workflow-test.mjs +94 -65
- package/README.md +4 -2
- package/defaults/eng.json +45 -6
- package/docs/BRIEF-FOR-REVIEW.md +163 -0
- package/docs/CHANGELOG.md +96 -0
- package/docs/configuration.md +31 -6
- package/hooks/commit-msg +162 -65
- package/lib/client.js +638 -431
- package/lib/client.js.map +3 -3
- package/lib/controller.d.ts +18 -1
- package/lib/controller.js +64 -5
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.js +363 -47
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +368 -6
- package/lib/engine.js +328 -13
- package/lib/engine.js.map +1 -1
- package/lib/hook.js +14 -4
- package/lib/hook.js.map +1 -1
- package/lib/skill-audit.d.ts +14 -3
- package/lib/skill-audit.js +71 -4
- package/lib/skill-audit.js.map +1 -1
- package/lib/workflows.d.ts +75 -12
- package/lib/workflows.js +213 -77
- package/lib/workflows.js.map +1 -1
- package/package.json +14 -4
- package/scripts/verify-package.mjs +1 -1
package/hooks/commit-msg
CHANGED
|
@@ -33,8 +33,9 @@ var import_node_path = __toESM(require("node:path"), 1);
|
|
|
33
33
|
function artifactsForStage(stage, config2) {
|
|
34
34
|
return config2.artifacts.filter((artifact) => artifact.stage === stage);
|
|
35
35
|
}
|
|
36
|
-
function todosBlockers(state2) {
|
|
36
|
+
function todosBlockers(state2, config2) {
|
|
37
37
|
if (state2.items.length === 0) return ["no implementation items"];
|
|
38
|
+
const depth = config2?.review_depth ?? "two-stage";
|
|
38
39
|
const blockers = [];
|
|
39
40
|
for (const item of state2.items) {
|
|
40
41
|
if (item.status !== "done") {
|
|
@@ -42,11 +43,13 @@ function todosBlockers(state2) {
|
|
|
42
43
|
continue;
|
|
43
44
|
}
|
|
44
45
|
if (item.review === void 0) {
|
|
45
|
-
blockers.push(`item "${item.id}" is done but has no
|
|
46
|
+
blockers.push(`item "${item.id}" is done but has no review`);
|
|
46
47
|
continue;
|
|
47
48
|
}
|
|
48
|
-
if (item.review.spec
|
|
49
|
-
if (item.review.quality
|
|
49
|
+
if (item.review.spec?.outcome !== "pass") blockers.push(`item "${item.id}" spec review is not pass`);
|
|
50
|
+
if (depth === "two-stage" && item.review.quality?.outcome !== "pass") {
|
|
51
|
+
blockers.push(`item "${item.id}" quality review is not pass`);
|
|
52
|
+
}
|
|
50
53
|
}
|
|
51
54
|
return blockers;
|
|
52
55
|
}
|
|
@@ -57,7 +60,7 @@ function guardSatisfied(guard, state2, config2) {
|
|
|
57
60
|
case "solution_confirmation":
|
|
58
61
|
return state2.solution_confirmed;
|
|
59
62
|
case "todos_done":
|
|
60
|
-
return todosBlockers(state2).length === 0;
|
|
63
|
+
return todosBlockers(state2, config2).length === 0;
|
|
61
64
|
case "verified": {
|
|
62
65
|
if (!state2.verification.passed) return false;
|
|
63
66
|
if (config2.high_risk_requires_verification && state2.risk_level === "high_risk") {
|
|
@@ -87,11 +90,45 @@ function validateCommitMessage(message2, config2) {
|
|
|
87
90
|
errors: [`commit summary "${message2}" does not match ${config2.commit.message_hint}`]
|
|
88
91
|
};
|
|
89
92
|
}
|
|
93
|
+
function verificationHeldStages(config2) {
|
|
94
|
+
const held = /* @__PURE__ */ new Set();
|
|
95
|
+
const queue = config2.transitions.filter((transition) => (transition.requires ?? []).includes("verified")).map((transition) => transition.to);
|
|
96
|
+
while (queue.length > 0) {
|
|
97
|
+
const stage = queue.shift();
|
|
98
|
+
if (held.has(stage)) continue;
|
|
99
|
+
held.add(stage);
|
|
100
|
+
for (const transition of config2.transitions) {
|
|
101
|
+
if (transition.from === stage && !held.has(transition.to)) queue.push(transition.to);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return held;
|
|
105
|
+
}
|
|
106
|
+
function verificationBlockers(state2, config2) {
|
|
107
|
+
if (state2.execution_version !== 1) return [];
|
|
108
|
+
if (!verificationHeldStages(config2).has(state2.stage)) return [];
|
|
109
|
+
const blockers = [];
|
|
110
|
+
if (!state2.verification.passed) {
|
|
111
|
+
blockers.push("this flow requires a passing verification at this stage, and the current verification is not passing");
|
|
112
|
+
}
|
|
113
|
+
if (config2.high_risk_requires_verification && state2.risk_level === "high_risk") {
|
|
114
|
+
const receipt = state2.verification.receipt;
|
|
115
|
+
if (receipt === void 0) {
|
|
116
|
+
blockers.push("a high-risk task needs a real command receipt, and none is recorded");
|
|
117
|
+
} else if (receipt.exit_code !== 0 || receipt.timed_out || receipt.aborted) {
|
|
118
|
+
blockers.push("a high-risk task needs a command that exited 0 without timing out or aborting");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return blockers;
|
|
122
|
+
}
|
|
90
123
|
function commitCheckpoint(state2, config2) {
|
|
91
124
|
const { policy, checkpoints } = config2.commit;
|
|
92
125
|
if (policy === "manual") {
|
|
93
126
|
return { allowed: false, reason: "commit policy is manual \u2014 await an explicit user instruction" };
|
|
94
127
|
}
|
|
128
|
+
const verification = verificationBlockers(state2, config2);
|
|
129
|
+
if (verification.length > 0) {
|
|
130
|
+
return { allowed: false, reason: verification.join("; ") };
|
|
131
|
+
}
|
|
95
132
|
if (checkpoints.includes(state2.stage)) {
|
|
96
133
|
const ready = config2.transitions.filter((transition) => transition.from === state2.stage).every((transition) => (transition.requires ?? []).every((g) => guardSatisfied(g, state2, config2)));
|
|
97
134
|
if (!ready) {
|
|
@@ -118,6 +155,10 @@ function checkFileScope(state2, committing, config2) {
|
|
|
118
155
|
}
|
|
119
156
|
|
|
120
157
|
// src/workflows.ts
|
|
158
|
+
function bundled(skill, ...rules) {
|
|
159
|
+
const ref = (name) => ({ source: "bundled", name });
|
|
160
|
+
return { skill: ref(skill), rules: rules.map(ref) };
|
|
161
|
+
}
|
|
121
162
|
var STANDARD = {
|
|
122
163
|
stages: ["\u9700\u6C42\u8BC4\u5BA1", "\u8BBE\u8BA1", "\u5F00\u53D1", "\u4EA4\u4ED8", "\u4EE3\u7801\u5BA1\u6838", "\u5B8C\u6210"],
|
|
123
164
|
start_stage: "\u9700\u6C42\u8BC4\u5BA1",
|
|
@@ -128,25 +169,34 @@ var STANDARD = {
|
|
|
128
169
|
{ from: "\u4EA4\u4ED8", to: "\u4EE3\u7801\u5BA1\u6838", requires: ["verified"] },
|
|
129
170
|
{ from: "\u4EE3\u7801\u5BA1\u6838", to: "\u5B8C\u6210", requires: ["review_passed", "artifacts_present"] }
|
|
130
171
|
],
|
|
172
|
+
// No artifacts, no commit policy, no skills: the skeleton carries none of them.
|
|
173
|
+
artifacts: [],
|
|
174
|
+
commit: { policy: "task", message_pattern: "", message_hint: "\u65E0\u683C\u5F0F\u8981\u6C42", checkpoints: ["\u4EE3\u7801\u5BA1\u6838"], file_scope: true },
|
|
175
|
+
high_risk_requires_verification: true
|
|
176
|
+
};
|
|
177
|
+
var STANDARD_RECOMMENDATION = {
|
|
178
|
+
commit: {
|
|
179
|
+
// Only the TEXT conventions. Policy, checkpoints and scope stay on the
|
|
180
|
+
// skeleton: they decide when a gate fires, not how a message reads.
|
|
181
|
+
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
182
|
+
message_hint: "\u3010<task_id>\u3011\u3010TASK/T1\u3011\u8BF4\u660E \u2014\u2014 \u7B2C\u4E00\u6BB5\u586B\u672C\u4EFB\u52A1 id\uFF08\u5982 GREET-001\uFF09\uFF0C\u5199\u7ED3\u679C\u4E0D\u5199\u7A7A\u6CDB\u52A8\u4F5C"
|
|
183
|
+
},
|
|
131
184
|
artifacts: [
|
|
132
185
|
{ stage: "\u9700\u6C42\u8BC4\u5BA1", id: "requirement", name: "\u9700\u6C42\u8BF4\u660E", fields: ["scope", "acceptance_criteria"] },
|
|
133
186
|
{ stage: "\u8BBE\u8BA1", id: "design", name: "\u8BBE\u8BA1\u6587\u6863", fields: ["approach", "risks", "impact"] },
|
|
134
187
|
{ stage: "\u4EE3\u7801\u5BA1\u6838", id: "review", name: "\u8BC4\u5BA1\u8BB0\u5F55", fields: ["conclusion", "issues"] }
|
|
135
188
|
],
|
|
136
|
-
commit: {
|
|
137
|
-
policy: "task",
|
|
138
|
-
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
139
|
-
message_hint: "\u3010<task_id>\u3011\u3010TASK/T1\u3011\u8BF4\u660E \u2014\u2014 \u7B2C\u4E00\u6BB5\u586B\u672C\u4EFB\u52A1 id\uFF08\u5982 GREET-001\uFF09\uFF0C\u5199\u7ED3\u679C\u4E0D\u5199\u7A7A\u6CDB\u52A8\u4F5C",
|
|
140
|
-
checkpoints: ["\u4EE3\u7801\u5BA1\u6838"],
|
|
141
|
-
file_scope: true
|
|
142
|
-
},
|
|
143
|
-
high_risk_requires_verification: true,
|
|
144
189
|
stage_bindings: {
|
|
145
|
-
"\u9700\u6C42\u8BC4\u5BA1": { skills: ["requirement-analysis"
|
|
146
|
-
"\u8BBE\u8BA1": { skills: ["solution-design"] },
|
|
147
|
-
"\u5F00\u53D1": { skills: ["code-implement"
|
|
148
|
-
"\u4EA4\u4ED8": { skills: ["code-verify"]
|
|
149
|
-
"\u4EE3\u7801\u5BA1\u6838": {
|
|
190
|
+
"\u9700\u6C42\u8BC4\u5BA1": { skills: [bundled("requirement-analysis", "security-redlines")] },
|
|
191
|
+
"\u8BBE\u8BA1": { skills: [bundled("solution-design")] },
|
|
192
|
+
"\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
|
|
193
|
+
"\u4EA4\u4ED8": { skills: [bundled("code-verify")] },
|
|
194
|
+
"\u4EE3\u7801\u5BA1\u6838": {
|
|
195
|
+
skills: [
|
|
196
|
+
bundled("code-review", "coding-conventions", "security-redlines"),
|
|
197
|
+
bundled("code-commit", "commit-conventions")
|
|
198
|
+
]
|
|
199
|
+
}
|
|
150
200
|
}
|
|
151
201
|
};
|
|
152
202
|
var AGILE = {
|
|
@@ -155,24 +205,43 @@ var AGILE = {
|
|
|
155
205
|
transitions: [
|
|
156
206
|
{ from: "\u9700\u6C42", to: "\u5F00\u53D1", requires: ["requirement_confirmation"] },
|
|
157
207
|
{ from: "\u5F00\u53D1", to: "\u4EA4\u4ED8", requires: ["todos_done"] },
|
|
208
|
+
// Entering 审查 is unconditional. A review guard on this edge would be
|
|
209
|
+
// circular — it would demand the verdict BEFORE the stage that produces it —
|
|
210
|
+
// and it would also block the commit at 交付, since a commit requires its
|
|
211
|
+
// stage's outgoing guards. What 审查 requires is declared as completion_guards
|
|
212
|
+
// instead, because a terminal stage has no outgoing edge to carry it.
|
|
158
213
|
{ from: "\u4EA4\u4ED8", to: "\u5BA1\u67E5", requires: [] }
|
|
159
214
|
],
|
|
160
|
-
artifacts: [
|
|
161
|
-
|
|
162
|
-
|
|
215
|
+
artifacts: [],
|
|
216
|
+
commit: { policy: "item", message_pattern: "", message_hint: "\u65E0\u683C\u5F0F\u8981\u6C42", checkpoints: ["\u4EA4\u4ED8"], file_scope: true },
|
|
217
|
+
high_risk_requires_verification: false,
|
|
218
|
+
// 审查 is terminal and IS the review, so there is no outgoing edge to carry
|
|
219
|
+
// the guard. Declaring it here is what makes a blocked review prevent
|
|
220
|
+
// completion; without it the stage could be reached and finished with the
|
|
221
|
+
// review failing, which is what the assessment found.
|
|
222
|
+
completion_guards: ["review_passed"]
|
|
223
|
+
};
|
|
224
|
+
var AGILE_RECOMMENDATION = {
|
|
163
225
|
commit: {
|
|
164
|
-
policy:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
226
|
+
// `item` policy emits two label shapes: `T<n>` mid-flow and `TASK` at the
|
|
227
|
+
// closing checkpoint. The pattern accepts BOTH, so the label the engine hands
|
|
228
|
+
// the model always satisfies the rule that validates it.
|
|
229
|
+
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
230
|
+
message_hint: "\u3010<task_id>\u3011\u3010TASK/T1\u3011\u8BF4\u660E \u2014\u2014 \u7B2C\u4E00\u6BB5\u586B\u672C\u4EFB\u52A1 id\uFF1B\u5B9E\u65BD\u9879\u63D0\u4EA4\u7528 T1\u3001T2\uFF0C\u6536\u5C3E\u63D0\u4EA4\u7528 TASK"
|
|
169
231
|
},
|
|
170
|
-
|
|
232
|
+
artifacts: [
|
|
233
|
+
{ stage: "\u9700\u6C42", id: "requirement", name: "\u9700\u6C42\u8BF4\u660E", fields: ["scope"] },
|
|
234
|
+
// The 审查 stage binds code-review, whose instructions write this artifact with
|
|
235
|
+
// `record artifact=review`. Declaring it keeps that binding executable: the
|
|
236
|
+
// engine refuses an undeclared artifact, so a bound skill pointing at one would
|
|
237
|
+
// be a contract the recommendation made impossible to satisfy.
|
|
238
|
+
{ stage: "\u5BA1\u67E5", id: "review", name: "\u8BC4\u5BA1\u8BB0\u5F55", fields: ["conclusion", "issues"] }
|
|
239
|
+
],
|
|
171
240
|
stage_bindings: {
|
|
172
|
-
"\u9700\u6C42": { skills: ["requirement-analysis"] },
|
|
173
|
-
"\u5F00\u53D1": { skills: ["code-implement"
|
|
174
|
-
"\u4EA4\u4ED8": { skills: ["code-verify", "code-commit"
|
|
175
|
-
"\u5BA1\u67E5": { skills: ["code-review"] }
|
|
241
|
+
"\u9700\u6C42": { skills: [bundled("requirement-analysis", "security-redlines")] },
|
|
242
|
+
"\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
|
|
243
|
+
"\u4EA4\u4ED8": { skills: [bundled("code-verify"), bundled("code-commit", "commit-conventions")] },
|
|
244
|
+
"\u5BA1\u67E5": { skills: [bundled("code-review", "coding-conventions", "security-redlines")] }
|
|
176
245
|
}
|
|
177
246
|
};
|
|
178
247
|
var MINIMAL = {
|
|
@@ -182,17 +251,17 @@ var MINIMAL = {
|
|
|
182
251
|
{ from: "\u5F00\u53D1", to: "\u4EA4\u4ED8", requires: ["todos_done"] }
|
|
183
252
|
],
|
|
184
253
|
artifacts: [],
|
|
185
|
-
commit: {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
254
|
+
commit: { policy: "task", message_pattern: "", message_hint: "\u65E0\u683C\u5F0F\u8981\u6C42", checkpoints: ["\u4EA4\u4ED8"], file_scope: false },
|
|
255
|
+
high_risk_requires_verification: false
|
|
256
|
+
};
|
|
257
|
+
var MINIMAL_RECOMMENDATION = {
|
|
258
|
+
// A fast-change flow checks each change once, not twice under two headings a
|
|
259
|
+
// short change rarely distinguishes. The item still has to be reviewed — what
|
|
260
|
+
// drops is the duplicated verdict, which is where the weight actually was.
|
|
261
|
+
review_depth: "single",
|
|
193
262
|
stage_bindings: {
|
|
194
|
-
"\u5F00\u53D1": { skills: ["code-implement"] },
|
|
195
|
-
"\u4EA4\u4ED8": { skills: ["code-commit"
|
|
263
|
+
"\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
|
|
264
|
+
"\u4EA4\u4ED8": { skills: [bundled("code-commit", "commit-conventions")] }
|
|
196
265
|
}
|
|
197
266
|
};
|
|
198
267
|
function deriveCapabilities(config2) {
|
|
@@ -204,40 +273,61 @@ function deriveCapabilities(config2) {
|
|
|
204
273
|
if (config2.commit.file_scope) caps.push("file_scope");
|
|
205
274
|
return caps;
|
|
206
275
|
}
|
|
207
|
-
function preset(id, version, label, description, config2) {
|
|
208
|
-
return {
|
|
276
|
+
function preset(id, version, label, description, config2, recommendation) {
|
|
277
|
+
return {
|
|
278
|
+
id,
|
|
279
|
+
version,
|
|
280
|
+
label,
|
|
281
|
+
description,
|
|
282
|
+
capabilities: deriveCapabilities(config2),
|
|
283
|
+
config: config2,
|
|
284
|
+
// exactOptionalPropertyTypes: omit rather than assign undefined.
|
|
285
|
+
...recommendation !== void 0 ? { recommendation } : {}
|
|
286
|
+
};
|
|
209
287
|
}
|
|
210
288
|
var FLOW_PRESETS = {
|
|
211
|
-
standard: preset("standard",
|
|
212
|
-
|
|
213
|
-
|
|
289
|
+
standard: preset("standard", 3, "\u5B8C\u6574\u7814\u53D1", "\u65B0\u529F\u80FD\u3001\u67B6\u6784\u6216\u8DE8\u6A21\u5757\u6539\u52A8\u3001\u9AD8\u98CE\u9669\u4EFB\u52A1\uFF1A\u9700\u6C42\u786E\u8BA4 \u2192 \u65B9\u6848\u786E\u8BA4 \u2192 \u5B9E\u73B0 \u2192 \u9A8C\u8BC1 \u2192 \u5BA1\u6838 \u2192 \u63D0\u4EA4", STANDARD, STANDARD_RECOMMENDATION),
|
|
290
|
+
// Version 4 for agile: 交付 → 审查 now requires review_passed, where it previously
|
|
291
|
+
// required nothing. That is a gate-semantics change, so a task created before it
|
|
292
|
+
// keeps its frozen version 3 config and behaves exactly as it did.
|
|
293
|
+
//
|
|
294
|
+
// Version 3 across all three: the preset is now a bare skeleton and its former
|
|
295
|
+
// built-in bindings, commit rule and artifacts are a separate recommendation a
|
|
296
|
+
// user adopts explicitly. Tasks created before this keep their frozen config and
|
|
297
|
+
// are unaffected; a project's existing config is likewise left exactly as it is.
|
|
298
|
+
agile: preset("agile", 4, "\u65E5\u5E38\u8FED\u4EE3", "\u76EE\u6807\u660E\u786E\u7684\u5E38\u89C4\u529F\u80FD\u4E0E\u7F3A\u9677\u4FEE\u590D\uFF1A\u76EE\u6807\u4E0E\u9A8C\u6536 \u2192 \u5B9E\u73B0 \u2192 \u9A8C\u6536\u4E0E\u5BA1\u67E5 \u2192 \u63D0\u4EA4", AGILE, AGILE_RECOMMENDATION),
|
|
299
|
+
minimal: preset("minimal", 3, "\u5FEB\u901F\u4FEE\u6539", "\u5C40\u90E8\u3001\u4F4E\u98CE\u9669\u3001\u65B9\u6848\u660E\u786E\u7684\u6539\u52A8\uFF1A\u4FEE\u6539 \u2192 \u68C0\u67E5\u4E0E\u63D0\u4EA4\uFF0C\u6BCF\u9879\u4E00\u6B21\u68C0\u67E5", MINIMAL, MINIMAL_RECOMMENDATION)
|
|
214
300
|
};
|
|
215
301
|
function dedupe(names) {
|
|
216
302
|
return [...new Set(names)];
|
|
217
303
|
}
|
|
218
|
-
function
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
304
|
+
function applyProjectConfig(base, project) {
|
|
305
|
+
if (project === void 0) return base;
|
|
306
|
+
const next = { ...base };
|
|
307
|
+
if (project.stage_bindings !== void 0) {
|
|
308
|
+
const bindings = {};
|
|
309
|
+
for (const [stage, binding] of Object.entries(project.stage_bindings)) {
|
|
310
|
+
const entry = {};
|
|
311
|
+
if ((binding.skills ?? []).length > 0) entry.skills = [...binding.skills];
|
|
312
|
+
const legacy = dedupe(binding.legacy_rules ?? []);
|
|
313
|
+
if (legacy.length > 0) entry.legacy_rules = legacy;
|
|
314
|
+
bindings[stage] = entry;
|
|
315
|
+
}
|
|
316
|
+
next.stage_bindings = bindings;
|
|
231
317
|
}
|
|
232
|
-
if (
|
|
233
|
-
|
|
318
|
+
if (project.commit !== void 0) next.commit = project.commit;
|
|
319
|
+
if (project.artifacts !== void 0) next.artifacts = project.artifacts;
|
|
320
|
+
if (project.review_depth !== void 0) next.review_depth = project.review_depth;
|
|
321
|
+
if (project.commit_required !== void 0) next.commit_required = project.commit_required;
|
|
322
|
+
return next;
|
|
234
323
|
}
|
|
235
|
-
function resolveFlow(flow,
|
|
324
|
+
function resolveFlow(flow, project) {
|
|
236
325
|
const preset2 = FLOW_PRESETS[flow];
|
|
237
326
|
if (preset2 === void 0) {
|
|
238
327
|
return { ok: false, code: "UNKNOWN_FLOW", flow, knownFlows: Object.keys(FLOW_PRESETS) };
|
|
239
328
|
}
|
|
240
|
-
|
|
329
|
+
const { flow: _ignored, ...rest } = project ?? { flow };
|
|
330
|
+
return { ok: true, config: applyProjectConfig(preset2.config, { flow, ...rest }), preset: preset2 };
|
|
241
331
|
}
|
|
242
332
|
|
|
243
333
|
// src/snapshot.ts
|
|
@@ -392,8 +482,15 @@ function loadWorkflow(parsed) {
|
|
|
392
482
|
if (typeof flow !== "string" || flow.trim() === "") {
|
|
393
483
|
refuse('\u9879\u76EE .dsh/eng.json \u7F3A "flow" \u5B57\u6BB5\u2014\u2014\u8BBE\u4E3A standard|agile|minimal\uFF0C\u6216\u5220\u9664\u8BE5\u6587\u4EF6');
|
|
394
484
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
485
|
+
const project = {
|
|
486
|
+
flow,
|
|
487
|
+
...typeof parsed.stage_bindings === "object" && parsed.stage_bindings !== null && !Array.isArray(parsed.stage_bindings) ? { stage_bindings: parsed.stage_bindings } : {},
|
|
488
|
+
...typeof parsed.commit === "object" && parsed.commit !== null && !Array.isArray(parsed.commit) ? { commit: parsed.commit } : {},
|
|
489
|
+
...Array.isArray(parsed.artifacts) ? { artifacts: parsed.artifacts } : {},
|
|
490
|
+
...typeof parsed.review_depth === "string" ? { review_depth: parsed.review_depth } : {},
|
|
491
|
+
...typeof parsed.commit_required === "boolean" ? { commit_required: parsed.commit_required } : {}
|
|
492
|
+
};
|
|
493
|
+
const resolved = resolveFlow(flow, project);
|
|
397
494
|
if (!resolved.ok) {
|
|
398
495
|
refuse(`\u672A\u77E5\u6D41\u7A0B "${resolved.flow}"\uFF08\u5DF2\u77E5\u6D41\u7A0B\uFF1A${resolved.knownFlows.join("\u3001")}\uFF09`);
|
|
399
496
|
}
|