@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/lib/skill-audit.js
CHANGED
|
@@ -36,18 +36,85 @@ export function obligationStages(state, workflow) {
|
|
|
36
36
|
&& !workflow.transitions.some(edge => edge.from === t.to)).map(t => t.to);
|
|
37
37
|
return [...new Set([state.stage, ...terminalTargets])];
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Whether a skill must supply a command receipt, given what it declared.
|
|
41
|
+
*
|
|
42
|
+
* A binding may declare how it proves execution. A requirement or design skill
|
|
43
|
+
* produces a document, and demanding a "real validation command" from it forced an
|
|
44
|
+
* irrelevant command to satisfy a gate — the evidence existed, just in another
|
|
45
|
+
* form. Core skills keep their exemption because their native artifact,
|
|
46
|
+
* verification, review and commit gates already carry the proof.
|
|
47
|
+
* @param name - the bound skill's name.
|
|
48
|
+
* @param evidence - the declared evidence kind, if the binding named one.
|
|
49
|
+
* @returns true when a command receipt is what this binding owes.
|
|
50
|
+
*/
|
|
51
|
+
export function needsSkillReceipt(name, evidence) {
|
|
52
|
+
if (evidence === 'none' || evidence === 'artifact' || evidence === 'review' || evidence === 'manual')
|
|
53
|
+
return false;
|
|
54
|
+
if (evidence === 'command')
|
|
55
|
+
return true;
|
|
41
56
|
return !CORE_SKILLS.has(name);
|
|
42
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether a skill's declared evidence is present for this stage.
|
|
60
|
+
*
|
|
61
|
+
* Each kind is satisfied by the record that actually carries that proof, so a
|
|
62
|
+
* document-producing skill is checked against a recorded artifact and a judging
|
|
63
|
+
* skill against a review verdict, rather than all of them against a shell receipt.
|
|
64
|
+
* @param kind - the declared evidence kind.
|
|
65
|
+
* @param state - the task state.
|
|
66
|
+
* @param stage - the stage being checked.
|
|
67
|
+
* @param name - the skill name, which keys `skill_results`.
|
|
68
|
+
* @param workflow - the effective workflow, for artifact declarations.
|
|
69
|
+
* @returns the blocker text when the evidence is absent, or an empty array.
|
|
70
|
+
*/
|
|
71
|
+
function evidenceBlockers(kind, state, stage, name, workflow) {
|
|
72
|
+
switch (kind) {
|
|
73
|
+
case 'none':
|
|
74
|
+
return [];
|
|
75
|
+
case 'artifact': {
|
|
76
|
+
// Any artifact recorded at this stage proves the skill produced its document.
|
|
77
|
+
// Requiring a specific id would guess which one, and the workflow already
|
|
78
|
+
// declares what each stage must record.
|
|
79
|
+
const declared = workflow.artifacts.filter(artifact => artifact.stage === stage);
|
|
80
|
+
if (declared.length === 0)
|
|
81
|
+
return [];
|
|
82
|
+
const recorded = state.artifacts ?? {};
|
|
83
|
+
const present = declared.some(artifact => recorded[artifact.id] !== undefined);
|
|
84
|
+
return present ? [] : [`${stage}: "${name}" declares artifact evidence, but no artifact is recorded at this stage`];
|
|
85
|
+
}
|
|
86
|
+
case 'review':
|
|
87
|
+
return state.review?.outcome === 'pass'
|
|
88
|
+
? []
|
|
89
|
+
: [`${stage}: "${name}" declares review evidence, but no passing review is recorded`];
|
|
90
|
+
case 'manual':
|
|
91
|
+
// A human statement is recorded as evidence text on the skill_result; the
|
|
92
|
+
// engine cannot verify the judgement itself, only that it was made explicitly.
|
|
93
|
+
return (state.skill_results?.[stage]?.[name]?.evidence ?? []).some(value => value.trim() !== '')
|
|
94
|
+
? []
|
|
95
|
+
: [`${stage}: "${name}" declares manual evidence and needs an explicit recorded statement`];
|
|
96
|
+
case 'command':
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
43
100
|
export function skillBlockers(state, workflow, session) {
|
|
44
101
|
if (state.execution_version !== 1)
|
|
45
102
|
return [];
|
|
46
103
|
const loaded = loadedSkills(session);
|
|
47
|
-
return obligationStages(state, workflow).flatMap(stage => (workflow.stage_bindings?.[stage]?.skills ?? []).flatMap(
|
|
104
|
+
return obligationStages(state, workflow).flatMap(stage => (workflow.stage_bindings?.[stage]?.skills ?? []).flatMap(entry => {
|
|
105
|
+
// A binding names its skill with a source layer; the loaded set and the
|
|
106
|
+
// skill_result keys are keyed by name, because DSH's skill tool is addressed
|
|
107
|
+
// by name. The source decides which layer to resolve, not which key to use.
|
|
108
|
+
const name = entry.skill.name;
|
|
48
109
|
if (!loaded.has(name))
|
|
49
110
|
return [`${stage}: load skill "${name}" with the skill tool before leaving this stage`];
|
|
50
|
-
|
|
111
|
+
// A declared non-command kind is checked against its own record rather than
|
|
112
|
+
// falling through to the command requirement, which would demand evidence of a
|
|
113
|
+
// kind this binding never asked for.
|
|
114
|
+
if (entry.evidence !== undefined && entry.evidence !== 'command') {
|
|
115
|
+
return evidenceBlockers(entry.evidence, state, stage, name, workflow);
|
|
116
|
+
}
|
|
117
|
+
if (!needsSkillReceipt(name, entry.evidence))
|
|
51
118
|
return [];
|
|
52
119
|
const result = state.skill_results?.[stage]?.[name];
|
|
53
120
|
if (!result || !result.receipt || result.receipt.exit_code !== 0 || result.receipt.aborted || result.receipt.timed_out
|
package/lib/skill-audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-audit.js","sourceRoot":"","sources":["../src/skill-audit.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC,CAAA;AAEvJ,2FAA2F;AAC3F,MAAM,UAAU,YAAY,CAAC,OAAsB;IACjD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAA;IACvC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,IAA8D,CAAA;YACjF,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,SAAQ;YACnD,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAuB,CAAA;gBACrE,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;oBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;YACtE,CAAC;YAAC,MAAM,CAAC,CAAC,mEAAmE,CAAC,CAAC;QACjF,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAgH,CAAA;YACnI,IAAI,IAAI,CAAC,KAAK;gBAAE,SAAQ;YACxB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,CAAC;gBAChD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU;oBAAE,SAAQ;gBAChF,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;gBACxC,IAAI,IAAI;oBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,CAAA;YAC9C,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,gBAAgB,CAAC,KAAgB,EAAE,QAAwB;IACzE,MAAM,eAAe,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,KAAK;WAC1E,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC3E,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;AACxD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"skill-audit.js","sourceRoot":"","sources":["../src/skill-audit.ts"],"names":[],"mappings":"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"}
|
package/lib/workflows.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Built-in
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Built-in workflow SKELETONS.
|
|
3
|
+
*
|
|
4
|
+
* A preset states how work MOVES: the stage graph and the guards on each edge.
|
|
5
|
+
* It deliberately carries nothing about HOW to do the work, because a preset that
|
|
6
|
+
* also baked in skills, a commit format and artifact fields would make every
|
|
7
|
+
* project follow the shipped method whether or not that suits it.
|
|
8
|
+
*
|
|
9
|
+
* One reasonable engineering setup per skeleton is offered separately as a
|
|
10
|
+
* {@link FlowRecommendation}, and enters a project only when a user adopts it
|
|
11
|
+
* explicitly. After adoption those values are the user's own config: edited,
|
|
12
|
+
* extended or removed, with nothing merged back by a later version.
|
|
9
13
|
*
|
|
10
14
|
* @module dsh-task-engine/workflows
|
|
11
15
|
*/
|
|
12
|
-
import type { StageBinding, WorkflowConfig } from './engine.ts';
|
|
16
|
+
import type { ArtifactDef, CommitRule, ReviewDepth, StageBinding, WorkflowConfig } from './engine.ts';
|
|
13
17
|
/** A preset workflow a project can select by id. */
|
|
14
18
|
export interface FlowOption {
|
|
15
19
|
id: string;
|
|
@@ -26,6 +30,42 @@ export interface FlowPreset extends FlowOption {
|
|
|
26
30
|
capabilities: WorkflowCapability[];
|
|
27
31
|
/** The complete engine config this preset resolves to. */
|
|
28
32
|
config: WorkflowConfig;
|
|
33
|
+
/**
|
|
34
|
+
* An optional ready-made engineering setup a user may ADOPT into their own
|
|
35
|
+
* config: stage bindings, commit format and artifact fields reflecting one
|
|
36
|
+
* reasonable way of working.
|
|
37
|
+
*
|
|
38
|
+
* Separate from `config` on purpose. The skeleton states how the work moves;
|
|
39
|
+
* the recommendation states one opinion about how to do it. A preset that also
|
|
40
|
+
* imposed its recommendation would make every project follow the shipped method
|
|
41
|
+
* whether or not that suits it — the opposite of letting the user decide.
|
|
42
|
+
* Nothing here takes effect until a user adopts it, and once adopted the values
|
|
43
|
+
* are theirs: edited, extended, or removed like any other config, never
|
|
44
|
+
* silently merged back.
|
|
45
|
+
*/
|
|
46
|
+
recommendation?: FlowRecommendation;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One ready-made engineering setup, offered for explicit adoption.
|
|
50
|
+
*
|
|
51
|
+
* Every field is optional because a preset may recommend a commit format without
|
|
52
|
+
* recommending skills; an adopter receives only what is present.
|
|
53
|
+
*/
|
|
54
|
+
export interface FlowRecommendation {
|
|
55
|
+
/** Stage bindings to write on adoption. */
|
|
56
|
+
stage_bindings?: Record<string, StageBinding>;
|
|
57
|
+
/**
|
|
58
|
+
* The commit TEXT conventions to write on adoption.
|
|
59
|
+
*
|
|
60
|
+
* Partial on purpose: a recommendation supplies only the writing convention —
|
|
61
|
+
* the message pattern and hint. WHEN a commit gate fires and whether the file
|
|
62
|
+
* scope is enforced are flow control and stay on the skeleton.
|
|
63
|
+
*/
|
|
64
|
+
commit?: Partial<CommitRule>;
|
|
65
|
+
/** Artifact declarations to write on adoption. */
|
|
66
|
+
artifacts?: ArtifactDef[];
|
|
67
|
+
/** Per-item review depth to write on adoption. */
|
|
68
|
+
review_depth?: ReviewDepth;
|
|
29
69
|
}
|
|
30
70
|
/** The selectable preset workflows (shown in the workbench flow picker). */
|
|
31
71
|
export declare const FLOW_OPTIONS: readonly FlowOption[];
|
|
@@ -55,6 +95,31 @@ export interface KnownFlow {
|
|
|
55
95
|
preset: FlowPreset;
|
|
56
96
|
}
|
|
57
97
|
export type FlowResolve = KnownFlow | UnknownFlow;
|
|
98
|
+
/**
|
|
99
|
+
* Write a preset's recommendation into a config, for a user who asked for it.
|
|
100
|
+
*
|
|
101
|
+
* This is the ADOPTION step. It is a deliberate, one-time act: the returned config
|
|
102
|
+
* is the user's own from then on, and nothing re-applies or re-merges the
|
|
103
|
+
* recommendation later, so a value the user deletes stays deleted.
|
|
104
|
+
* @param presetId - the preset whose recommendation to adopt.
|
|
105
|
+
* @param current - the user's current config, if any values should be kept.
|
|
106
|
+
* @returns the config with the recommendation applied, or undefined for an unknown preset.
|
|
107
|
+
*/
|
|
108
|
+
export declare function adoptRecommendation(presetId: string, current?: ProjectConfig): ProjectConfig | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* A project's own configuration, as stored in `.dsh/eng.json`.
|
|
111
|
+
*
|
|
112
|
+
* Every field beyond `flow` is optional and is the user's to set. A field that is
|
|
113
|
+
* absent means "not configured", never "use the built-in value".
|
|
114
|
+
*/
|
|
115
|
+
export interface ProjectConfig {
|
|
116
|
+
flow: string;
|
|
117
|
+
stage_bindings?: Record<string, StageBinding>;
|
|
118
|
+
commit?: CommitRule;
|
|
119
|
+
artifacts?: ArtifactDef[];
|
|
120
|
+
review_depth?: ReviewDepth;
|
|
121
|
+
commit_required?: boolean;
|
|
122
|
+
}
|
|
58
123
|
/**
|
|
59
124
|
* Resolve a preset workflow plus a project's stage-binding additions into the
|
|
60
125
|
* complete engine config. Unknown `flow` ids fail closed with an explicit
|
|
@@ -62,8 +127,6 @@ export type FlowResolve = KnownFlow | UnknownFlow;
|
|
|
62
127
|
* project that names a missing or stale preset stops instead of running the
|
|
63
128
|
* wrong gate.
|
|
64
129
|
* @param flow - workflow id from the project's `.dsh/eng.json`.
|
|
65
|
-
* @param
|
|
130
|
+
* @param project - the project's own config, applied verbatim over the skeleton.
|
|
66
131
|
*/
|
|
67
|
-
export declare function resolveFlow(flow: string,
|
|
68
|
-
stage_bindings?: Record<string, StageBinding>;
|
|
69
|
-
}): FlowResolve;
|
|
132
|
+
export declare function resolveFlow(flow: string, project?: ProjectConfig): FlowResolve;
|
package/lib/workflows.js
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Built-in
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Built-in workflow SKELETONS.
|
|
3
|
+
*
|
|
4
|
+
* A preset states how work MOVES: the stage graph and the guards on each edge.
|
|
5
|
+
* It deliberately carries nothing about HOW to do the work, because a preset that
|
|
6
|
+
* also baked in skills, a commit format and artifact fields would make every
|
|
7
|
+
* project follow the shipped method whether or not that suits it.
|
|
8
|
+
*
|
|
9
|
+
* One reasonable engineering setup per skeleton is offered separately as a
|
|
10
|
+
* {@link FlowRecommendation}, and enters a project only when a user adopts it
|
|
11
|
+
* explicitly. After adoption those values are the user's own config: edited,
|
|
12
|
+
* extended or removed, with nothing merged back by a later version.
|
|
9
13
|
*
|
|
10
14
|
* @module dsh-task-engine/workflows
|
|
11
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* Build one skill binding from a bare name plus its bundled rules.
|
|
18
|
+
*
|
|
19
|
+
* The preset's own bindings are all bundled resources, so the `bundled:` prefix
|
|
20
|
+
* is applied here rather than repeated at every call site. Project and user
|
|
21
|
+
* resources enter through a project's config, where their source is explicit.
|
|
22
|
+
* @param skill - the bundled skill name.
|
|
23
|
+
* @param rules - bundled rule names that belong to this skill.
|
|
24
|
+
* @returns the binding.
|
|
25
|
+
*/
|
|
26
|
+
function bundled(skill, ...rules) {
|
|
27
|
+
const ref = (name) => ({ source: 'bundled', name });
|
|
28
|
+
return { skill: ref(skill), rules: rules.map(ref) };
|
|
29
|
+
}
|
|
12
30
|
/** The selectable preset workflows (shown in the workbench flow picker). */
|
|
13
31
|
export const FLOW_OPTIONS = [
|
|
14
|
-
{ id: 'standard', label: '
|
|
15
|
-
{ id: 'agile', label: '
|
|
16
|
-
{ id: 'minimal', label: '
|
|
32
|
+
{ id: 'standard', label: '完整研发', description: '新功能、架构或跨模块改动、高风险任务:需求确认 → 方案确认 → 实现 → 验证 → 审核 → 提交' },
|
|
33
|
+
{ id: 'agile', label: '日常迭代', description: '目标明确的常规功能与缺陷修复:目标与验收 → 实现 → 验收与审查 → 提交' },
|
|
34
|
+
{ id: 'minimal', label: '快速修改', description: '局部、低风险、方案明确的改动:修改 → 检查与提交' },
|
|
17
35
|
];
|
|
18
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Standard review-gated delivery.
|
|
38
|
+
*
|
|
39
|
+
* The skeleton states how work MOVES: which stages exist, what may follow what,
|
|
40
|
+
* and which confirmations or evidence an edge demands. Everything about HOW to do
|
|
41
|
+
* the work — which skills run, what a commit message must look like, which
|
|
42
|
+
* artifacts a stage records — lives in {@link STANDARD_RECOMMENDATION} and enters
|
|
43
|
+
* a project only when the user adopts it.
|
|
44
|
+
*/
|
|
19
45
|
const STANDARD = {
|
|
20
46
|
stages: ['需求评审', '设计', '开发', '交付', '代码审核', '完成'],
|
|
21
47
|
start_stage: '需求评审',
|
|
@@ -26,55 +52,92 @@ const STANDARD = {
|
|
|
26
52
|
{ from: '交付', to: '代码审核', requires: ['verified'] },
|
|
27
53
|
{ from: '代码审核', to: '完成', requires: ['review_passed', 'artifacts_present'] },
|
|
28
54
|
],
|
|
55
|
+
// No artifacts, no commit policy, no skills: the skeleton carries none of them.
|
|
56
|
+
artifacts: [],
|
|
57
|
+
commit: { policy: 'task', message_pattern: '', message_hint: '无格式要求', checkpoints: ['代码审核'], file_scope: true },
|
|
58
|
+
high_risk_requires_verification: true,
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* One ready-made setup for the standard flow, offered for explicit adoption.
|
|
62
|
+
*
|
|
63
|
+
* This is a recommendation, not a default: it reflects one reasonable way to work
|
|
64
|
+
* and nothing here takes effect until a user adopts it. After adoption these
|
|
65
|
+
* values are ordinary user config — edited, extended or removed — and no later
|
|
66
|
+
* version merges anything back in.
|
|
67
|
+
*/
|
|
68
|
+
const STANDARD_RECOMMENDATION = {
|
|
69
|
+
commit: {
|
|
70
|
+
// Only the TEXT conventions. Policy, checkpoints and scope stay on the
|
|
71
|
+
// skeleton: they decide when a gate fires, not how a message reads.
|
|
72
|
+
message_pattern: '^【(\\S+)】【(?:TASK|T\\d+)】.+',
|
|
73
|
+
message_hint: '【<task_id>】【TASK/T1】说明 —— 第一段填本任务 id(如 GREET-001),写结果不写空泛动作',
|
|
74
|
+
},
|
|
29
75
|
artifacts: [
|
|
30
76
|
{ stage: '需求评审', id: 'requirement', name: '需求说明', fields: ['scope', 'acceptance_criteria'] },
|
|
31
77
|
{ stage: '设计', id: 'design', name: '设计文档', fields: ['approach', 'risks', 'impact'] },
|
|
32
78
|
{ stage: '代码审核', id: 'review', name: '评审记录', fields: ['conclusion', 'issues'] },
|
|
33
79
|
],
|
|
34
|
-
commit: {
|
|
35
|
-
policy: 'task',
|
|
36
|
-
message_pattern: '^【(\\S+)】【(?:TASK|T\\d+)】.+',
|
|
37
|
-
message_hint: '【<task_id>】【TASK/T1】说明 —— 第一段填本任务 id(如 GREET-001),写结果不写空泛动作',
|
|
38
|
-
checkpoints: ['代码审核'],
|
|
39
|
-
file_scope: true,
|
|
40
|
-
},
|
|
41
|
-
high_risk_requires_verification: true,
|
|
42
80
|
stage_bindings: {
|
|
43
|
-
'需求评审': { skills: ['requirement-analysis'
|
|
44
|
-
'设计': { skills: ['solution-design'] },
|
|
45
|
-
'开发': { skills: ['code-implement'
|
|
46
|
-
'交付': { skills: ['code-verify']
|
|
47
|
-
'代码审核': {
|
|
81
|
+
'需求评审': { skills: [bundled('requirement-analysis', 'security-redlines')] },
|
|
82
|
+
'设计': { skills: [bundled('solution-design')] },
|
|
83
|
+
'开发': { skills: [bundled('code-implement', 'coding-conventions', 'security-redlines')] },
|
|
84
|
+
'交付': { skills: [bundled('code-verify')] },
|
|
85
|
+
'代码审核': {
|
|
86
|
+
skills: [
|
|
87
|
+
bundled('code-review', 'coding-conventions', 'security-redlines'),
|
|
88
|
+
bundled('code-commit', 'commit-conventions'),
|
|
89
|
+
],
|
|
90
|
+
},
|
|
48
91
|
},
|
|
49
92
|
};
|
|
50
|
-
/** Lighter four-stage flow
|
|
93
|
+
/** Lighter four-stage flow: the skeleton only. */
|
|
51
94
|
const AGILE = {
|
|
52
95
|
stages: ['需求', '开发', '交付', '审查'],
|
|
53
96
|
start_stage: '需求',
|
|
54
97
|
transitions: [
|
|
55
98
|
{ from: '需求', to: '开发', requires: ['requirement_confirmation'] },
|
|
56
99
|
{ from: '开发', to: '交付', requires: ['todos_done'] },
|
|
100
|
+
// Entering 审查 is unconditional. A review guard on this edge would be
|
|
101
|
+
// circular — it would demand the verdict BEFORE the stage that produces it —
|
|
102
|
+
// and it would also block the commit at 交付, since a commit requires its
|
|
103
|
+
// stage's outgoing guards. What 审查 requires is declared as completion_guards
|
|
104
|
+
// instead, because a terminal stage has no outgoing edge to carry it.
|
|
57
105
|
{ from: '交付', to: '审查', requires: [] },
|
|
58
106
|
],
|
|
107
|
+
artifacts: [],
|
|
108
|
+
commit: { policy: 'item', message_pattern: '', message_hint: '无格式要求', checkpoints: ['交付'], file_scope: true },
|
|
109
|
+
high_risk_requires_verification: false,
|
|
110
|
+
// 审查 is terminal and IS the review, so there is no outgoing edge to carry
|
|
111
|
+
// the guard. Declaring it here is what makes a blocked review prevent
|
|
112
|
+
// completion; without it the stage could be reached and finished with the
|
|
113
|
+
// review failing, which is what the assessment found.
|
|
114
|
+
completion_guards: ['review_passed'],
|
|
115
|
+
};
|
|
116
|
+
/** One ready-made setup for the agile flow, offered for explicit adoption. */
|
|
117
|
+
const AGILE_RECOMMENDATION = {
|
|
118
|
+
commit: {
|
|
119
|
+
// `item` policy emits two label shapes: `T<n>` mid-flow and `TASK` at the
|
|
120
|
+
// closing checkpoint. The pattern accepts BOTH, so the label the engine hands
|
|
121
|
+
// the model always satisfies the rule that validates it.
|
|
122
|
+
message_pattern: '^【(\\S+)】【(?:TASK|T\\d+)】.+',
|
|
123
|
+
message_hint: '【<task_id>】【TASK/T1】说明 —— 第一段填本任务 id;实施项提交用 T1、T2,收尾提交用 TASK',
|
|
124
|
+
},
|
|
59
125
|
artifacts: [
|
|
60
126
|
{ stage: '需求', id: 'requirement', name: '需求说明', fields: ['scope'] },
|
|
127
|
+
// The 审查 stage binds code-review, whose instructions write this artifact with
|
|
128
|
+
// `record artifact=review`. Declaring it keeps that binding executable: the
|
|
129
|
+
// engine refuses an undeclared artifact, so a bound skill pointing at one would
|
|
130
|
+
// be a contract the recommendation made impossible to satisfy.
|
|
131
|
+
{ stage: '审查', id: 'review', name: '评审记录', fields: ['conclusion', 'issues'] },
|
|
61
132
|
],
|
|
62
|
-
commit: {
|
|
63
|
-
policy: 'item',
|
|
64
|
-
message_pattern: '^【(\\S+)】【T\\d+】.+',
|
|
65
|
-
message_hint: '【<task_id>】【T1】说明 —— 第一段填本任务 id',
|
|
66
|
-
checkpoints: ['交付'],
|
|
67
|
-
file_scope: true,
|
|
68
|
-
},
|
|
69
|
-
high_risk_requires_verification: false,
|
|
70
133
|
stage_bindings: {
|
|
71
|
-
'需求': { skills: ['requirement-analysis'] },
|
|
72
|
-
'开发': { skills: ['code-implement'
|
|
73
|
-
'交付': { skills: ['code-verify', 'code-commit'
|
|
74
|
-
'审查': { skills: ['code-review'] },
|
|
134
|
+
'需求': { skills: [bundled('requirement-analysis', 'security-redlines')] },
|
|
135
|
+
'开发': { skills: [bundled('code-implement', 'coding-conventions', 'security-redlines')] },
|
|
136
|
+
'交付': { skills: [bundled('code-verify'), bundled('code-commit', 'commit-conventions')] },
|
|
137
|
+
'审查': { skills: [bundled('code-review', 'coding-conventions', 'security-redlines')] },
|
|
75
138
|
},
|
|
76
139
|
};
|
|
77
|
-
/** Minimal two-stage flow:
|
|
140
|
+
/** Minimal two-stage flow: the skeleton only. */
|
|
78
141
|
const MINIMAL = {
|
|
79
142
|
stages: ['开发', '交付'],
|
|
80
143
|
start_stage: '开发',
|
|
@@ -82,17 +145,18 @@ const MINIMAL = {
|
|
|
82
145
|
{ from: '开发', to: '交付', requires: ['todos_done'] },
|
|
83
146
|
],
|
|
84
147
|
artifacts: [],
|
|
85
|
-
commit: {
|
|
86
|
-
policy: 'task',
|
|
87
|
-
message_pattern: '',
|
|
88
|
-
message_hint: '无格式要求',
|
|
89
|
-
checkpoints: ['交付'],
|
|
90
|
-
file_scope: false,
|
|
91
|
-
},
|
|
148
|
+
commit: { policy: 'task', message_pattern: '', message_hint: '无格式要求', checkpoints: ['交付'], file_scope: false },
|
|
92
149
|
high_risk_requires_verification: false,
|
|
150
|
+
};
|
|
151
|
+
/** One ready-made setup for the minimal flow, offered for explicit adoption. */
|
|
152
|
+
const MINIMAL_RECOMMENDATION = {
|
|
153
|
+
// A fast-change flow checks each change once, not twice under two headings a
|
|
154
|
+
// short change rarely distinguishes. The item still has to be reviewed — what
|
|
155
|
+
// drops is the duplicated verdict, which is where the weight actually was.
|
|
156
|
+
review_depth: 'single',
|
|
93
157
|
stage_bindings: {
|
|
94
|
-
'开发': { skills: ['code-implement'] },
|
|
95
|
-
'交付': { skills: ['code-commit'
|
|
158
|
+
'开发': { skills: [bundled('code-implement', 'coding-conventions', 'security-redlines')] },
|
|
159
|
+
'交付': { skills: [bundled('code-commit', 'commit-conventions')] },
|
|
96
160
|
},
|
|
97
161
|
};
|
|
98
162
|
/**
|
|
@@ -112,13 +176,27 @@ function deriveCapabilities(config) {
|
|
|
112
176
|
caps.push('file_scope');
|
|
113
177
|
return caps;
|
|
114
178
|
}
|
|
115
|
-
function preset(id, version, label, description, config) {
|
|
116
|
-
return {
|
|
179
|
+
function preset(id, version, label, description, config, recommendation) {
|
|
180
|
+
return {
|
|
181
|
+
id, version, label, description,
|
|
182
|
+
capabilities: deriveCapabilities(config),
|
|
183
|
+
config,
|
|
184
|
+
// exactOptionalPropertyTypes: omit rather than assign undefined.
|
|
185
|
+
...(recommendation !== undefined ? { recommendation } : {}),
|
|
186
|
+
};
|
|
117
187
|
}
|
|
118
188
|
export const FLOW_PRESETS = {
|
|
119
|
-
standard: preset('standard',
|
|
120
|
-
|
|
121
|
-
|
|
189
|
+
standard: preset('standard', 3, '完整研发', '新功能、架构或跨模块改动、高风险任务:需求确认 → 方案确认 → 实现 → 验证 → 审核 → 提交', STANDARD, STANDARD_RECOMMENDATION),
|
|
190
|
+
// Version 4 for agile: 交付 → 审查 now requires review_passed, where it previously
|
|
191
|
+
// required nothing. That is a gate-semantics change, so a task created before it
|
|
192
|
+
// keeps its frozen version 3 config and behaves exactly as it did.
|
|
193
|
+
//
|
|
194
|
+
// Version 3 across all three: the preset is now a bare skeleton and its former
|
|
195
|
+
// built-in bindings, commit rule and artifacts are a separate recommendation a
|
|
196
|
+
// user adopts explicitly. Tasks created before this keep their frozen config and
|
|
197
|
+
// are unaffected; a project's existing config is likewise left exactly as it is.
|
|
198
|
+
agile: preset('agile', 4, '日常迭代', '目标明确的常规功能与缺陷修复:目标与验收 → 实现 → 验收与审查 → 提交', AGILE, AGILE_RECOMMENDATION),
|
|
199
|
+
minimal: preset('minimal', 3, '快速修改', '局部、低风险、方案明确的改动:修改 → 检查与提交,每项一次检查', MINIMAL, MINIMAL_RECOMMENDATION),
|
|
122
200
|
};
|
|
123
201
|
/** Whether `flow` names a built-in workflow. */
|
|
124
202
|
export function isKnownFlow(flow) {
|
|
@@ -147,31 +225,88 @@ function dedupe(names) {
|
|
|
147
225
|
return [...new Set(names)];
|
|
148
226
|
}
|
|
149
227
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
228
|
+
* Apply a project's own config over a preset skeleton.
|
|
229
|
+
*
|
|
230
|
+
* The project's values are taken as given. There is nothing to merge against any
|
|
231
|
+
* more: the skeleton carries no skills, no commit rule, no artifacts and no
|
|
232
|
+
* review depth, so whatever the user configured IS the configuration. Earlier
|
|
233
|
+
* versions merged a project's skills into the preset's built-in set, which had two
|
|
234
|
+
* consequences this removes — a built-in skill could never be dropped, and an
|
|
235
|
+
* attempt to give a built-in skill a different rule list was discarded because the
|
|
236
|
+
* skill reference already existed.
|
|
237
|
+
*
|
|
238
|
+
* `stage_bindings` entirely replaces the skeleton's (empty) map; a stage the user
|
|
239
|
+
* omits simply has no bindings, which is a legitimate state rather than a gap to
|
|
240
|
+
* be filled from a recommendation.
|
|
154
241
|
*/
|
|
155
|
-
function
|
|
156
|
-
if (
|
|
242
|
+
function applyProjectConfig(base, project) {
|
|
243
|
+
if (project === undefined)
|
|
157
244
|
return base;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
245
|
+
const next = { ...base };
|
|
246
|
+
if (project.stage_bindings !== undefined) {
|
|
247
|
+
const bindings = {};
|
|
248
|
+
for (const [stage, binding] of Object.entries(project.stage_bindings)) {
|
|
249
|
+
const entry = {};
|
|
250
|
+
if ((binding.skills ?? []).length > 0)
|
|
251
|
+
entry.skills = [...binding.skills];
|
|
252
|
+
const legacy = dedupe(binding.legacy_rules ?? []);
|
|
253
|
+
if (legacy.length > 0)
|
|
254
|
+
entry.legacy_rules = legacy;
|
|
255
|
+
bindings[stage] = entry;
|
|
256
|
+
}
|
|
257
|
+
next.stage_bindings = bindings;
|
|
171
258
|
}
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
259
|
+
if (project.commit !== undefined)
|
|
260
|
+
next.commit = project.commit;
|
|
261
|
+
if (project.artifacts !== undefined)
|
|
262
|
+
next.artifacts = project.artifacts;
|
|
263
|
+
if (project.review_depth !== undefined)
|
|
264
|
+
next.review_depth = project.review_depth;
|
|
265
|
+
if (project.commit_required !== undefined)
|
|
266
|
+
next.commit_required = project.commit_required;
|
|
267
|
+
return next;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Write a preset's recommendation into a config, for a user who asked for it.
|
|
271
|
+
*
|
|
272
|
+
* This is the ADOPTION step. It is a deliberate, one-time act: the returned config
|
|
273
|
+
* is the user's own from then on, and nothing re-applies or re-merges the
|
|
274
|
+
* recommendation later, so a value the user deletes stays deleted.
|
|
275
|
+
* @param presetId - the preset whose recommendation to adopt.
|
|
276
|
+
* @param current - the user's current config, if any values should be kept.
|
|
277
|
+
* @returns the config with the recommendation applied, or undefined for an unknown preset.
|
|
278
|
+
*/
|
|
279
|
+
export function adoptRecommendation(presetId, current) {
|
|
280
|
+
const preset = FLOW_PRESETS[presetId];
|
|
281
|
+
if (preset === undefined)
|
|
282
|
+
return undefined;
|
|
283
|
+
const rec = preset.recommendation;
|
|
284
|
+
if (rec === undefined)
|
|
285
|
+
return { flow: presetId, ...current };
|
|
286
|
+
// The recommendation supplies a starting point; anything the user already set
|
|
287
|
+
// wins, so adopting never overwrites an existing decision.
|
|
288
|
+
//
|
|
289
|
+
// The commit rule is assembled from BOTH halves, because they live in different
|
|
290
|
+
// places by design: the skeleton owns when a gate fires and whether the scope is
|
|
291
|
+
// protected, the recommendation owns how a message reads. Writing the
|
|
292
|
+
// recommendation's half alone would produce a config with no checkpoints and no
|
|
293
|
+
// scope protection — the flow control would silently disappear on adoption.
|
|
294
|
+
const commit = {
|
|
295
|
+
...preset.config.commit,
|
|
296
|
+
...(rec.commit ?? {}),
|
|
297
|
+
...(current?.commit ?? {}),
|
|
298
|
+
};
|
|
299
|
+
const config = {
|
|
300
|
+
flow: presetId,
|
|
301
|
+
stage_bindings: current?.stage_bindings ?? rec.stage_bindings ?? {},
|
|
302
|
+
commit,
|
|
303
|
+
artifacts: current?.artifacts ?? rec.artifacts ?? preset.config.artifacts,
|
|
304
|
+
...(current?.review_depth !== undefined
|
|
305
|
+
? { review_depth: current.review_depth }
|
|
306
|
+
: (rec.review_depth !== undefined ? { review_depth: rec.review_depth } : {})),
|
|
307
|
+
...(current?.commit_required !== undefined ? { commit_required: current.commit_required } : {}),
|
|
308
|
+
};
|
|
309
|
+
return config;
|
|
175
310
|
}
|
|
176
311
|
/**
|
|
177
312
|
* Resolve a preset workflow plus a project's stage-binding additions into the
|
|
@@ -180,13 +315,14 @@ function mergeBindings(base, override) {
|
|
|
180
315
|
* project that names a missing or stale preset stops instead of running the
|
|
181
316
|
* wrong gate.
|
|
182
317
|
* @param flow - workflow id from the project's `.dsh/eng.json`.
|
|
183
|
-
* @param
|
|
318
|
+
* @param project - the project's own config, applied verbatim over the skeleton.
|
|
184
319
|
*/
|
|
185
|
-
export function resolveFlow(flow,
|
|
320
|
+
export function resolveFlow(flow, project) {
|
|
186
321
|
const preset = FLOW_PRESETS[flow];
|
|
187
322
|
if (preset === undefined) {
|
|
188
323
|
return { ok: false, code: 'UNKNOWN_FLOW', flow, knownFlows: Object.keys(FLOW_PRESETS) };
|
|
189
324
|
}
|
|
190
|
-
|
|
325
|
+
const { flow: _ignored, ...rest } = project ?? { flow };
|
|
326
|
+
return { ok: true, config: applyProjectConfig(preset.config, { flow, ...rest }), preset };
|
|
191
327
|
}
|
|
192
328
|
//# sourceMappingURL=workflows.js.map
|