@godv61/dsh-task-engine 0.24.0 → 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 +150 -88
- package/.assessment-batch1.mjs +32 -12
- package/.e2e-presets.mjs +40 -5
- package/.enforce-test.mjs +139 -0
- package/.evidence-test.mjs +119 -0
- package/.filter-test.mjs +93 -0
- package/.freeze-test.mjs +110 -1
- package/.hook-test.mjs +8 -3
- package/.p0-test.mjs +141 -57
- package/.revision-test.mjs +17 -2
- package/.roundtrip-test.mjs +63 -0
- package/.workflow-test.mjs +31 -4
- package/README.md +4 -2
- package/docs/CHANGELOG.md +37 -0
- package/docs/configuration.md +3 -3
- package/hooks/commit-msg +93 -75
- package/lib/client.js +218 -81
- package/lib/client.js.map +1 -1
- package/lib/controller.d.ts +10 -2
- package/lib/controller.js +27 -2
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.js +94 -20
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +90 -0
- package/lib/engine.js +64 -0
- 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 +66 -3
- package/lib/skill-audit.js.map +1 -1
- package/lib/workflows.d.ts +75 -12
- package/lib/workflows.js +172 -96
- package/lib/workflows.js.map +1 -1
- package/package.json +9 -5
- package/scripts/verify-package.mjs +1 -1
package/lib/client.js
CHANGED
|
@@ -4557,6 +4557,14 @@ var workbenchTheme = `
|
|
|
4557
4557
|
.te-binding-tools .te-input { flex:1; width:100%; min-width:120px; padding:8px 10px; font-size:13px; }
|
|
4558
4558
|
.te-binding-filter { display:flex; align-items:center; gap:5px; font-size:12px; white-space:nowrap; cursor:pointer; }
|
|
4559
4559
|
.te-binding-list { max-height:280px; overflow-y:auto; overscroll-behavior:contain; display:flex; flex-direction:column; gap:4px; scrollbar-gutter:stable; }
|
|
4560
|
+
/* Search + filter row. The catalog grows with every install, so finding one skill
|
|
4561
|
+
has to be a lookup rather than a scroll. */
|
|
4562
|
+
.te-binding-controls { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:0 0 6px; }
|
|
4563
|
+
.te-binding-search { flex:1; min-width:120px; padding:6px 9px; font-size:12px; color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l2); border-radius:7px; }
|
|
4564
|
+
.te-binding-search:focus-visible { outline:2px solid var(--dsw-alias-state-business-primary); outline-offset:1px; }
|
|
4565
|
+
/* Only the open skill's rules render, and inside a bounded region: expanding a
|
|
4566
|
+
second skill must not push the first off screen or grow the page without limit. */
|
|
4567
|
+
.te-rule-list { display:flex; flex-direction:column; gap:4px; margin-top:6px; padding:8px; max-height:240px; overflow-y:auto; overscroll-behavior:contain; border:1px solid var(--dsw-alias-border-l2); border-radius:8px; background:var(--dsw-alias-bg-layer-1); scrollbar-gutter:stable; }
|
|
4560
4568
|
.te-binding-option { display:flex; gap:9px; align-items:flex-start; padding:9px; border:1px solid transparent; border-radius:8px; cursor:pointer; }
|
|
4561
4569
|
.te-binding-option:hover { background:var(--dsw-alias-bg-layer-2); }
|
|
4562
4570
|
.te-binding-option.is-selected { background:var(--dsw-alias-bg-layer-2); border-color:var(--dsw-alias-border-l2); }
|
|
@@ -4695,6 +4703,10 @@ var styles = {
|
|
|
4695
4703
|
},
|
|
4696
4704
|
// ── flow preset picker ────────────────────────────────────────────────────
|
|
4697
4705
|
flowGrid: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(230px, 1fr))", gap: 10 },
|
|
4706
|
+
// The adoption row is deliberately separated from the flow picker: choosing a
|
|
4707
|
+
// skeleton and adopting a recommended setup are different decisions, and the
|
|
4708
|
+
// button is the only thing that writes method into the user's config.
|
|
4709
|
+
adoptRow: { display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", marginBottom: 4 },
|
|
4698
4710
|
flowOption: {
|
|
4699
4711
|
display: "flex",
|
|
4700
4712
|
flexDirection: "column",
|
|
@@ -4871,19 +4883,23 @@ var STANDARD = {
|
|
|
4871
4883
|
{ from: "\u4EA4\u4ED8", to: "\u4EE3\u7801\u5BA1\u6838", requires: ["verified"] },
|
|
4872
4884
|
{ from: "\u4EE3\u7801\u5BA1\u6838", to: "\u5B8C\u6210", requires: ["review_passed", "artifacts_present"] }
|
|
4873
4885
|
],
|
|
4886
|
+
// No artifacts, no commit policy, no skills: the skeleton carries none of them.
|
|
4887
|
+
artifacts: [],
|
|
4888
|
+
commit: { policy: "task", message_pattern: "", message_hint: "\u65E0\u683C\u5F0F\u8981\u6C42", checkpoints: ["\u4EE3\u7801\u5BA1\u6838"], file_scope: true },
|
|
4889
|
+
high_risk_requires_verification: true
|
|
4890
|
+
};
|
|
4891
|
+
var STANDARD_RECOMMENDATION = {
|
|
4892
|
+
commit: {
|
|
4893
|
+
// Only the TEXT conventions. Policy, checkpoints and scope stay on the
|
|
4894
|
+
// skeleton: they decide when a gate fires, not how a message reads.
|
|
4895
|
+
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
4896
|
+
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"
|
|
4897
|
+
},
|
|
4874
4898
|
artifacts: [
|
|
4875
4899
|
{ stage: "\u9700\u6C42\u8BC4\u5BA1", id: "requirement", name: "\u9700\u6C42\u8BF4\u660E", fields: ["scope", "acceptance_criteria"] },
|
|
4876
4900
|
{ stage: "\u8BBE\u8BA1", id: "design", name: "\u8BBE\u8BA1\u6587\u6863", fields: ["approach", "risks", "impact"] },
|
|
4877
4901
|
{ stage: "\u4EE3\u7801\u5BA1\u6838", id: "review", name: "\u8BC4\u5BA1\u8BB0\u5F55", fields: ["conclusion", "issues"] }
|
|
4878
4902
|
],
|
|
4879
|
-
commit: {
|
|
4880
|
-
policy: "task",
|
|
4881
|
-
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
4882
|
-
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",
|
|
4883
|
-
checkpoints: ["\u4EE3\u7801\u5BA1\u6838"],
|
|
4884
|
-
file_scope: true
|
|
4885
|
-
},
|
|
4886
|
-
high_risk_requires_verification: true,
|
|
4887
4903
|
stage_bindings: {
|
|
4888
4904
|
"\u9700\u6C42\u8BC4\u5BA1": { skills: [bundled("requirement-analysis", "security-redlines")] },
|
|
4889
4905
|
"\u8BBE\u8BA1": { skills: [bundled("solution-design")] },
|
|
@@ -4903,30 +4919,38 @@ var AGILE = {
|
|
|
4903
4919
|
transitions: [
|
|
4904
4920
|
{ from: "\u9700\u6C42", to: "\u5F00\u53D1", requires: ["requirement_confirmation"] },
|
|
4905
4921
|
{ from: "\u5F00\u53D1", to: "\u4EA4\u4ED8", requires: ["todos_done"] },
|
|
4922
|
+
// Entering 审查 is unconditional. A review guard on this edge would be
|
|
4923
|
+
// circular — it would demand the verdict BEFORE the stage that produces it —
|
|
4924
|
+
// and it would also block the commit at 交付, since a commit requires its
|
|
4925
|
+
// stage's outgoing guards. What 审查 requires is declared as completion_guards
|
|
4926
|
+
// instead, because a terminal stage has no outgoing edge to carry it.
|
|
4906
4927
|
{ from: "\u4EA4\u4ED8", to: "\u5BA1\u67E5", requires: [] }
|
|
4907
4928
|
],
|
|
4929
|
+
artifacts: [],
|
|
4930
|
+
commit: { policy: "item", message_pattern: "", message_hint: "\u65E0\u683C\u5F0F\u8981\u6C42", checkpoints: ["\u4EA4\u4ED8"], file_scope: true },
|
|
4931
|
+
high_risk_requires_verification: false,
|
|
4932
|
+
// 审查 is terminal and IS the review, so there is no outgoing edge to carry
|
|
4933
|
+
// the guard. Declaring it here is what makes a blocked review prevent
|
|
4934
|
+
// completion; without it the stage could be reached and finished with the
|
|
4935
|
+
// review failing, which is what the assessment found.
|
|
4936
|
+
completion_guards: ["review_passed"]
|
|
4937
|
+
};
|
|
4938
|
+
var AGILE_RECOMMENDATION = {
|
|
4939
|
+
commit: {
|
|
4940
|
+
// `item` policy emits two label shapes: `T<n>` mid-flow and `TASK` at the
|
|
4941
|
+
// closing checkpoint. The pattern accepts BOTH, so the label the engine hands
|
|
4942
|
+
// the model always satisfies the rule that validates it.
|
|
4943
|
+
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
4944
|
+
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"
|
|
4945
|
+
},
|
|
4908
4946
|
artifacts: [
|
|
4909
4947
|
{ stage: "\u9700\u6C42", id: "requirement", name: "\u9700\u6C42\u8BF4\u660E", fields: ["scope"] },
|
|
4910
4948
|
// The 审查 stage binds code-review, whose instructions write this artifact with
|
|
4911
|
-
// `record artifact=review`. Declaring it
|
|
4912
|
-
//
|
|
4913
|
-
//
|
|
4949
|
+
// `record artifact=review`. Declaring it keeps that binding executable: the
|
|
4950
|
+
// engine refuses an undeclared artifact, so a bound skill pointing at one would
|
|
4951
|
+
// be a contract the recommendation made impossible to satisfy.
|
|
4914
4952
|
{ stage: "\u5BA1\u67E5", id: "review", name: "\u8BC4\u5BA1\u8BB0\u5F55", fields: ["conclusion", "issues"] }
|
|
4915
4953
|
],
|
|
4916
|
-
commit: {
|
|
4917
|
-
// `item` policy emits two label shapes: `T<n>` for a mid-flow item commit and
|
|
4918
|
-
// `TASK` for the closing commit at the checkpoint. The pattern below accepts
|
|
4919
|
-
// BOTH, so the label the engine hands the model always satisfies the preset's
|
|
4920
|
-
// own rule. Previously it accepted only `T\d+` while the checkpoint stage
|
|
4921
|
-
// returned `TASK`, which made the closing commit impossible to phrase: the
|
|
4922
|
-
// status line and the validator disagreed about the contract.
|
|
4923
|
-
policy: "item",
|
|
4924
|
-
message_pattern: "^\u3010(\\S+)\u3011\u3010(?:TASK|T\\d+)\u3011.+",
|
|
4925
|
-
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",
|
|
4926
|
-
checkpoints: ["\u4EA4\u4ED8"],
|
|
4927
|
-
file_scope: true
|
|
4928
|
-
},
|
|
4929
|
-
high_risk_requires_verification: false,
|
|
4930
4954
|
stage_bindings: {
|
|
4931
4955
|
"\u9700\u6C42": { skills: [bundled("requirement-analysis", "security-redlines")] },
|
|
4932
4956
|
"\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
|
|
@@ -4941,18 +4965,13 @@ var MINIMAL = {
|
|
|
4941
4965
|
{ from: "\u5F00\u53D1", to: "\u4EA4\u4ED8", requires: ["todos_done"] }
|
|
4942
4966
|
],
|
|
4943
4967
|
artifacts: [],
|
|
4944
|
-
commit: {
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
checkpoints: ["\u4EA4\u4ED8"],
|
|
4949
|
-
file_scope: false
|
|
4950
|
-
},
|
|
4951
|
-
high_risk_requires_verification: false,
|
|
4968
|
+
commit: { policy: "task", message_pattern: "", message_hint: "\u65E0\u683C\u5F0F\u8981\u6C42", checkpoints: ["\u4EA4\u4ED8"], file_scope: false },
|
|
4969
|
+
high_risk_requires_verification: false
|
|
4970
|
+
};
|
|
4971
|
+
var MINIMAL_RECOMMENDATION = {
|
|
4952
4972
|
// A fast-change flow checks each change once, not twice under two headings a
|
|
4953
4973
|
// short change rarely distinguishes. The item still has to be reviewed — what
|
|
4954
|
-
// drops is the duplicated verdict, which is where the weight actually was
|
|
4955
|
-
// flow used to carry the same per-item audit burden as the full one.
|
|
4974
|
+
// drops is the duplicated verdict, which is where the weight actually was.
|
|
4956
4975
|
review_depth: "single",
|
|
4957
4976
|
stage_bindings: {
|
|
4958
4977
|
"\u5F00\u53D1": { skills: [bundled("code-implement", "coding-conventions", "security-redlines")] },
|
|
@@ -4968,54 +4987,81 @@ function deriveCapabilities(config) {
|
|
|
4968
4987
|
if (config.commit.file_scope) caps.push("file_scope");
|
|
4969
4988
|
return caps;
|
|
4970
4989
|
}
|
|
4971
|
-
function preset(id, version, label2, description, config) {
|
|
4972
|
-
return {
|
|
4990
|
+
function preset(id, version, label2, description, config, recommendation) {
|
|
4991
|
+
return {
|
|
4992
|
+
id,
|
|
4993
|
+
version,
|
|
4994
|
+
label: label2,
|
|
4995
|
+
description,
|
|
4996
|
+
capabilities: deriveCapabilities(config),
|
|
4997
|
+
config,
|
|
4998
|
+
// exactOptionalPropertyTypes: omit rather than assign undefined.
|
|
4999
|
+
...recommendation !== void 0 ? { recommendation } : {}
|
|
5000
|
+
};
|
|
4973
5001
|
}
|
|
4974
5002
|
var FLOW_PRESETS = {
|
|
4975
|
-
standard: preset("standard",
|
|
4976
|
-
// Version
|
|
4977
|
-
//
|
|
4978
|
-
//
|
|
4979
|
-
|
|
4980
|
-
// Version
|
|
4981
|
-
//
|
|
4982
|
-
//
|
|
4983
|
-
//
|
|
4984
|
-
|
|
5003
|
+
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),
|
|
5004
|
+
// Version 4 for agile: 交付 → 审查 now requires review_passed, where it previously
|
|
5005
|
+
// required nothing. That is a gate-semantics change, so a task created before it
|
|
5006
|
+
// keeps its frozen version 3 config and behaves exactly as it did.
|
|
5007
|
+
//
|
|
5008
|
+
// Version 3 across all three: the preset is now a bare skeleton and its former
|
|
5009
|
+
// built-in bindings, commit rule and artifacts are a separate recommendation a
|
|
5010
|
+
// user adopts explicitly. Tasks created before this keep their frozen config and
|
|
5011
|
+
// are unaffected; a project's existing config is likewise left exactly as it is.
|
|
5012
|
+
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),
|
|
5013
|
+
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)
|
|
4985
5014
|
};
|
|
4986
5015
|
function dedupe(names) {
|
|
4987
5016
|
return [...new Set(names)];
|
|
4988
5017
|
}
|
|
4989
|
-
function
|
|
4990
|
-
if (
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
const
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5018
|
+
function applyProjectConfig(base, project) {
|
|
5019
|
+
if (project === void 0) return base;
|
|
5020
|
+
const next = { ...base };
|
|
5021
|
+
if (project.stage_bindings !== void 0) {
|
|
5022
|
+
const bindings = {};
|
|
5023
|
+
for (const [stage, binding] of Object.entries(project.stage_bindings)) {
|
|
5024
|
+
const entry = {};
|
|
5025
|
+
if ((binding.skills ?? []).length > 0) entry.skills = [...binding.skills];
|
|
5026
|
+
const legacy = dedupe(binding.legacy_rules ?? []);
|
|
5027
|
+
if (legacy.length > 0) entry.legacy_rules = legacy;
|
|
5028
|
+
bindings[stage] = entry;
|
|
5029
|
+
}
|
|
5030
|
+
next.stage_bindings = bindings;
|
|
5031
|
+
}
|
|
5032
|
+
if (project.commit !== void 0) next.commit = project.commit;
|
|
5033
|
+
if (project.artifacts !== void 0) next.artifacts = project.artifacts;
|
|
5034
|
+
if (project.review_depth !== void 0) next.review_depth = project.review_depth;
|
|
5035
|
+
if (project.commit_required !== void 0) next.commit_required = project.commit_required;
|
|
5036
|
+
return next;
|
|
5037
|
+
}
|
|
5038
|
+
function adoptRecommendation(presetId, current) {
|
|
5039
|
+
const preset2 = FLOW_PRESETS[presetId];
|
|
5040
|
+
if (preset2 === void 0) return void 0;
|
|
5041
|
+
const rec = preset2.recommendation;
|
|
5042
|
+
if (rec === void 0) return { flow: presetId, ...current };
|
|
5043
|
+
const commit = {
|
|
5044
|
+
...preset2.config.commit,
|
|
5045
|
+
...rec.commit ?? {},
|
|
5046
|
+
...current?.commit ?? {}
|
|
5047
|
+
};
|
|
5048
|
+
const config = {
|
|
5049
|
+
flow: presetId,
|
|
5050
|
+
stage_bindings: current?.stage_bindings ?? rec.stage_bindings ?? {},
|
|
5051
|
+
commit,
|
|
5052
|
+
artifacts: current?.artifacts ?? rec.artifacts ?? preset2.config.artifacts,
|
|
5053
|
+
...current?.review_depth !== void 0 ? { review_depth: current.review_depth } : rec.review_depth !== void 0 ? { review_depth: rec.review_depth } : {},
|
|
5054
|
+
...current?.commit_required !== void 0 ? { commit_required: current.commit_required } : {}
|
|
5055
|
+
};
|
|
5056
|
+
return config;
|
|
5012
5057
|
}
|
|
5013
|
-
function resolveFlow(flow,
|
|
5058
|
+
function resolveFlow(flow, project) {
|
|
5014
5059
|
const preset2 = FLOW_PRESETS[flow];
|
|
5015
5060
|
if (preset2 === void 0) {
|
|
5016
5061
|
return { ok: false, code: "UNKNOWN_FLOW", flow, knownFlows: Object.keys(FLOW_PRESETS) };
|
|
5017
5062
|
}
|
|
5018
|
-
|
|
5063
|
+
const { flow: _ignored, ...rest } = project ?? { flow };
|
|
5064
|
+
return { ok: true, config: applyProjectConfig(preset2.config, { flow, ...rest }), preset: preset2 };
|
|
5019
5065
|
}
|
|
5020
5066
|
|
|
5021
5067
|
// src/client/shared.ts
|
|
@@ -5031,13 +5077,16 @@ function refFromText(text2) {
|
|
|
5031
5077
|
return parseResourceRef(text2) ?? { source: "bundled", name: text2 };
|
|
5032
5078
|
}
|
|
5033
5079
|
function resolvedConfig(flow, stageBindings) {
|
|
5034
|
-
const resolved = resolveFlow(flow, { stage_bindings: stageBindings });
|
|
5080
|
+
const resolved = resolveFlow(flow, { flow, stage_bindings: stageBindings });
|
|
5035
5081
|
return resolved.ok ? resolved.config : FLOW_PRESETS.standard.config;
|
|
5036
5082
|
}
|
|
5037
5083
|
function TaskEngineSection(props) {
|
|
5038
5084
|
const { remote, workspace } = props;
|
|
5039
5085
|
const [flow, setFlow] = (0, import_react.useState)("standard");
|
|
5040
5086
|
const [stageBindings, setStageBindings] = (0, import_react.useState)({});
|
|
5087
|
+
const [commitRule, setCommitRule] = (0, import_react.useState)(void 0);
|
|
5088
|
+
const [artifacts, setArtifacts] = (0, import_react.useState)(void 0);
|
|
5089
|
+
const [reviewDepth, setReviewDepth] = (0, import_react.useState)(void 0);
|
|
5041
5090
|
const [source, setSource] = (0, import_react.useState)("default");
|
|
5042
5091
|
const [savedAt, setSavedAt] = (0, import_react.useState)("");
|
|
5043
5092
|
const [dirty, setDirty] = (0, import_react.useState)(false);
|
|
@@ -5059,6 +5108,9 @@ function TaskEngineSection(props) {
|
|
|
5059
5108
|
if (result.ok) {
|
|
5060
5109
|
setFlow(result.value.flow);
|
|
5061
5110
|
setStageBindings(result.value.config.stage_bindings ?? {});
|
|
5111
|
+
setCommitRule(result.value.config.commit);
|
|
5112
|
+
setArtifacts(result.value.config.artifacts);
|
|
5113
|
+
setReviewDepth(result.value.config.review_depth);
|
|
5062
5114
|
setSource(result.value.source);
|
|
5063
5115
|
setConfigProblems(result.value.problems);
|
|
5064
5116
|
setSavedAt("");
|
|
@@ -5098,7 +5150,17 @@ function TaskEngineSection(props) {
|
|
|
5098
5150
|
if (id === flow) return;
|
|
5099
5151
|
if (dirty && !window.confirm("\u5F53\u524D\u6D41\u7A0B\u914D\u7F6E\u6709\u672A\u4FDD\u5B58\u7684\u4FEE\u6539\uFF0C\u5207\u6362\u6D41\u7A0B\u4F1A\u4E22\u5F03\u5B83\u4EEC\u3002\u7EE7\u7EED\u5207\u6362\uFF1F")) return;
|
|
5100
5152
|
setFlow(id);
|
|
5101
|
-
|
|
5153
|
+
setDirty(true);
|
|
5154
|
+
setSavedAt("");
|
|
5155
|
+
};
|
|
5156
|
+
const adopt = () => {
|
|
5157
|
+
const adopted = adoptRecommendation(flow);
|
|
5158
|
+
if (adopted === void 0) return;
|
|
5159
|
+
if (dirty && !window.confirm("\u91C7\u7528\u63A8\u8350\u914D\u7F6E\u4F1A\u66FF\u6362\u5F53\u524D\u7684\u6280\u80FD\u4E0E\u89C4\u5219\u8BBE\u7F6E\u3002\u7EE7\u7EED\uFF1F")) return;
|
|
5160
|
+
setStageBindings({ ...adopted.stage_bindings ?? {} });
|
|
5161
|
+
setCommitRule(adopted.commit);
|
|
5162
|
+
setArtifacts(adopted.artifacts);
|
|
5163
|
+
setReviewDepth(adopted.review_depth);
|
|
5102
5164
|
setDirty(true);
|
|
5103
5165
|
setSavedAt("");
|
|
5104
5166
|
};
|
|
@@ -5124,7 +5186,15 @@ function TaskEngineSection(props) {
|
|
|
5124
5186
|
] }),
|
|
5125
5187
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.head, children: [
|
|
5126
5188
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: styles.title, children: "\u5DE5\u7A0B\u6D41\u7A0B\u914D\u7F6E" }),
|
|
5127
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.muted, children: "\
|
|
5189
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.muted, children: "\u6D41\u7A0B\u51B3\u5B9A\u5DE5\u4F5C\u600E\u4E48\u6D41\u8F6C\uFF08\u9636\u6BB5\u4E0E\u95E8\u7981\uFF09\uFF1B\u6280\u80FD\u3001\u89C4\u5219\u3001\u63D0\u4EA4\u683C\u5F0F\u548C\u4EA7\u7269\u5B57\u6BB5\u7531\u4F60\u81EA\u5DF1\u914D\u7F6E\u3002 \u9700\u8981\u4E00\u4EFD\u73B0\u6210\u7684\u8D77\u70B9\u65F6\uFF0C\u53EF\u4EE5\u300C\u91C7\u7528\u63A8\u8350\u914D\u7F6E\u300D\u2014\u2014\u91C7\u7528\u540E\u8FD9\u4E9B\u5C31\u5B8C\u5168\u5C5E\u4E8E\u4F60\uFF0C\u53EF\u968F\u610F\u4FEE\u6539\u6216\u5220\u9664\u3002" })
|
|
5190
|
+
] }),
|
|
5191
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.adoptRow, children: [
|
|
5192
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dsh_client_ui_primitives.Button, { size: "sm", onClick: adopt, children: [
|
|
5193
|
+
"\u91C7\u7528\u300C",
|
|
5194
|
+
FLOW_PRESETS[flow]?.label ?? flow,
|
|
5195
|
+
"\u300D\u7684\u63A8\u8350\u914D\u7F6E"
|
|
5196
|
+
] }),
|
|
5197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.muted, children: "\u5199\u5165\u63A8\u8350\u6280\u80FD\u3001\u63D0\u4EA4\u4FE1\u606F\u683C\u5F0F\u4E0E\u4EA7\u7269\u5B57\u6BB5\u3002\u4EC5\u5728\u4F60\u70B9\u51FB\u65F6\u53D1\u751F\u4E00\u6B21\uFF1B\u5220\u9664\u540E\u4E0D\u4F1A\u81EA\u52A8\u8865\u56DE\u3002" })
|
|
5128
5198
|
] }),
|
|
5129
5199
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.sourceRow, children: [
|
|
5130
5200
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: source === "project" ? "done" : source === "invalid" ? "warning" : "ongoing" }),
|
|
@@ -5182,7 +5252,7 @@ function TaskEngineSection(props) {
|
|
|
5182
5252
|
disabled: problems.length > 0 || saving,
|
|
5183
5253
|
onClick: () => {
|
|
5184
5254
|
setSaving(true);
|
|
5185
|
-
void save(remote, flow, stageBindings, workspace, setSavedAt, setSource).then((ok) => {
|
|
5255
|
+
void save(remote, { flow, stage_bindings: stageBindings, ...commitRule !== void 0 ? { commit: commitRule } : {}, ...artifacts !== void 0 ? { artifacts } : {}, ...reviewDepth !== void 0 ? { review_depth: reviewDepth } : {} }, workspace, setSavedAt, setSource).then((ok) => {
|
|
5186
5256
|
if (ok) {
|
|
5187
5257
|
setConfigProblems([]);
|
|
5188
5258
|
setDirty(false);
|
|
@@ -5234,10 +5304,36 @@ function SectionCard({ icon, title, hint, children }) {
|
|
|
5234
5304
|
function BindingEditor({ stages, defaults, stageBindings, setStageBindings, skills, rules }) {
|
|
5235
5305
|
const [stage, setStage] = (0, import_react.useState)(stages[0] ?? "");
|
|
5236
5306
|
const [expanded, setExpanded] = (0, import_react.useState)(null);
|
|
5307
|
+
const [query, setQuery] = (0, import_react.useState)("");
|
|
5308
|
+
const [onlySelected, setOnlySelected] = (0, import_react.useState)(false);
|
|
5309
|
+
const [ruleQuery, setRuleQuery] = (0, import_react.useState)("");
|
|
5310
|
+
const [ruleOnlySelected, setRuleOnlySelected] = (0, import_react.useState)(false);
|
|
5237
5311
|
const currentStage = stages.includes(stage) ? stage : stages[0] ?? "";
|
|
5238
5312
|
const bindingFor = (name) => stageBindings[name] ?? {};
|
|
5239
5313
|
const presetSkills = (name) => defaults[name]?.skills ?? [];
|
|
5240
5314
|
const isPresetSkill = (name, raw) => presetSkills(name).some((entry) => formatResourceRef(entry.skill) === raw);
|
|
5315
|
+
const visibleSkills = () => {
|
|
5316
|
+
const bound = bindingFor(currentStage).skills ?? [];
|
|
5317
|
+
const boundKeys = new Set(bound.map((entry) => formatResourceRef(entry.skill)));
|
|
5318
|
+
const needle = query.trim().toLowerCase();
|
|
5319
|
+
return skills.filter((entry) => {
|
|
5320
|
+
const raw = formatResourceRef(entry.ref);
|
|
5321
|
+
const inForce = boundKeys.has(raw) || isPresetSkill(currentStage, raw);
|
|
5322
|
+
if (onlySelected && !inForce) return false;
|
|
5323
|
+
if (needle === "") return true;
|
|
5324
|
+
return inForce || entry.name.toLowerCase().includes(needle) || entry.description.toLowerCase().includes(needle) || entry.sourceLabel.toLowerCase().includes(needle);
|
|
5325
|
+
});
|
|
5326
|
+
};
|
|
5327
|
+
const visibleRulesFor = (bound) => {
|
|
5328
|
+
const chosen = new Set(bound.rules.map((rule) => formatResourceRef(rule)));
|
|
5329
|
+
const needle = ruleQuery.trim().toLowerCase();
|
|
5330
|
+
return rules.filter((rule) => {
|
|
5331
|
+
const raw = formatResourceRef(rule.ref);
|
|
5332
|
+
if (ruleOnlySelected && !chosen.has(raw)) return false;
|
|
5333
|
+
if (needle === "") return true;
|
|
5334
|
+
return chosen.has(raw) || rule.name.toLowerCase().includes(needle) || rule.sourceLabel.toLowerCase().includes(needle);
|
|
5335
|
+
});
|
|
5336
|
+
};
|
|
5241
5337
|
const setSkills = (name, next) => {
|
|
5242
5338
|
setStageBindings({ ...stageBindings, [name]: { ...bindingFor(name), skills: next } });
|
|
5243
5339
|
};
|
|
@@ -5284,12 +5380,32 @@ function BindingEditor({ stages, defaults, stageBindings, setStageBindings, skil
|
|
|
5284
5380
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-count", children: [
|
|
5285
5381
|
"\u5DF2\u9009 ",
|
|
5286
5382
|
boundSkills.length,
|
|
5287
|
-
presetCount > 0 ? `\uFF08\u542B ${presetCount} \u9879\u9884\u8BBE\uFF09` : ""
|
|
5383
|
+
presetCount > 0 ? `\uFF08\u542B ${presetCount} \u9879\u9884\u8BBE\uFF09` : "",
|
|
5384
|
+
visibleSkills().length !== skills.length ? ` \xB7 \u663E\u793A ${visibleSkills().length}/${skills.length}` : ""
|
|
5385
|
+
] })
|
|
5386
|
+
] }),
|
|
5387
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "te-binding-controls", children: [
|
|
5388
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5389
|
+
"input",
|
|
5390
|
+
{
|
|
5391
|
+
className: "te-binding-search",
|
|
5392
|
+
type: "search",
|
|
5393
|
+
value: query,
|
|
5394
|
+
placeholder: "\u641C\u7D22\u6280\u80FD\u540D\u6216\u8BF4\u660E\u2026",
|
|
5395
|
+
"aria-label": "\u641C\u7D22\u6280\u80FD",
|
|
5396
|
+
onChange: (event) => setQuery(event.target.value)
|
|
5397
|
+
}
|
|
5398
|
+
),
|
|
5399
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "te-binding-filter", children: [
|
|
5400
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: onlySelected, onChange: () => setOnlySelected((value) => !value) }),
|
|
5401
|
+
" ",
|
|
5402
|
+
"\u4EC5\u770B\u5DF2\u9009"
|
|
5288
5403
|
] })
|
|
5289
5404
|
] }),
|
|
5290
5405
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "te-binding-list", role: "group", "aria-label": "\u6280\u80FD\u5217\u8868", tabIndex: 0, children: [
|
|
5291
5406
|
skills.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "te-binding-empty", children: "\u6682\u65E0\u6280\u80FD\uFF0C\u8BF7\u5728\u4E0A\u65B9\u201C\u6280\u80FD\u201D\u9875\u6DFB\u52A0\u3002" }) : null,
|
|
5292
|
-
skills.
|
|
5407
|
+
skills.length > 0 && visibleSkills().length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "te-binding-empty", children: "\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD\u3002" }) : null,
|
|
5408
|
+
visibleSkills().map((entry) => {
|
|
5293
5409
|
const raw = formatResourceRef(entry.ref);
|
|
5294
5410
|
const bound = boundSkills.find((candidate) => formatResourceRef(candidate.skill) === raw);
|
|
5295
5411
|
const preset2 = isPresetSkill(currentStage, raw);
|
|
@@ -5317,8 +5433,29 @@ function BindingEditor({ stages, defaults, stageBindings, setStageBindings, skil
|
|
|
5317
5433
|
] }),
|
|
5318
5434
|
bound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "te-link", onClick: () => setExpanded(open ? null : raw), children: open ? "\u6536\u8D77\u89C4\u5219" : "\u89C4\u5219\u8BBE\u7F6E" }) : null,
|
|
5319
5435
|
open && bound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-rule-list", children: [
|
|
5436
|
+
ruleQuery !== "" || ruleOnlySelected ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "te-binding-meta", children: [
|
|
5437
|
+
rules.length,
|
|
5438
|
+
" \u6761\u53EF\u7528\u89C4\u5219"
|
|
5439
|
+
] }),
|
|
5440
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
5441
|
+
"input",
|
|
5442
|
+
{
|
|
5443
|
+
className: "te-binding-search",
|
|
5444
|
+
type: "search",
|
|
5445
|
+
value: ruleQuery,
|
|
5446
|
+
placeholder: "\u8FC7\u6EE4\u89C4\u5219\u2026",
|
|
5447
|
+
"aria-label": `\u8FC7\u6EE4 ${entry.name} \u7684\u89C4\u5219`,
|
|
5448
|
+
onChange: (event) => setRuleQuery(event.target.value)
|
|
5449
|
+
}
|
|
5450
|
+
),
|
|
5451
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "te-binding-filter", children: [
|
|
5452
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: ruleOnlySelected, onChange: () => setRuleOnlySelected((value) => !value) }),
|
|
5453
|
+
" ",
|
|
5454
|
+
"\u4EC5\u770B\u5DF2\u9009"
|
|
5455
|
+
] }),
|
|
5320
5456
|
rules.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "te-binding-meta", children: "\u89C4\u5219\u5E93\u4E3A\u7A7A\u3002" }) : null,
|
|
5321
|
-
rules.
|
|
5457
|
+
rules.length > 0 && visibleRulesFor(bound).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "te-binding-meta", children: "\u6CA1\u6709\u5339\u914D\u7684\u89C4\u5219\u3002" }) : null,
|
|
5458
|
+
visibleRulesFor(bound).map((rule) => {
|
|
5322
5459
|
const ruleRaw = formatResourceRef(rule.ref);
|
|
5323
5460
|
const on = bound.rules.some((candidate) => formatResourceRef(candidate) === ruleRaw);
|
|
5324
5461
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "te-binding-filter", children: [
|
|
@@ -5340,9 +5477,9 @@ function BindingEditor({ stages, defaults, stageBindings, setStageBindings, skil
|
|
|
5340
5477
|
] }) }, currentStage)
|
|
5341
5478
|
] });
|
|
5342
5479
|
}
|
|
5343
|
-
async function save(remote,
|
|
5480
|
+
async function save(remote, config, workspace, setSavedAt, setSource) {
|
|
5344
5481
|
try {
|
|
5345
|
-
const result = await remote.write({ path: workspace,
|
|
5482
|
+
const result = await remote.write({ path: workspace, ...config });
|
|
5346
5483
|
if (!result.ok) {
|
|
5347
5484
|
setSavedAt("\u4FDD\u5B58\u5931\u8D25\uFF1A" + describeError(result.error));
|
|
5348
5485
|
return false;
|