@godv61/dsh-task-engine 0.28.0 → 0.29.1
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/.adaptive-test.mjs +189 -0
- package/.hook-test.mjs +19 -2
- package/.workflow-test.mjs +6 -4
- package/README.md +22 -16
- package/docs/CHANGELOG.md +14 -1
- package/docs/README.md +6 -5
- package/docs/adaptive-workflows.md +72 -0
- package/docs/configuration.md +4 -4
- package/docs/faq.md +15 -7
- package/docs/getting-started.md +8 -5
- package/docs/manual-legacy.html +380 -0
- package/docs/manual.html +126 -380
- package/docs/roadmap.md +7 -8
- package/hooks/commit-msg +39 -22
- package/lib/adaptive.d.ts +15 -0
- package/lib/adaptive.js +54 -0
- package/lib/adaptive.js.map +1 -0
- package/lib/client.js +800 -433
- package/lib/client.js.map +3 -3
- package/lib/controller.d.ts +50 -0
- package/lib/controller.js +132 -0
- package/lib/controller.js.map +1 -1
- package/lib/dev-task.js +413 -18
- package/lib/dev-task.js.map +1 -1
- package/lib/engine.d.ts +20 -0
- package/lib/engine.js +4 -1
- package/lib/engine.js.map +1 -1
- package/lib/hook.js +50 -29
- package/lib/hook.js.map +1 -1
- package/lib/project-init.d.ts +23 -0
- package/lib/project-init.js +94 -0
- package/lib/project-init.js.map +1 -0
- package/lib/skill-audit.js +8 -7
- package/lib/skill-audit.js.map +1 -1
- package/lib/sonar.d.ts +33 -0
- package/lib/sonar.js +80 -0
- package/lib/sonar.js.map +1 -0
- package/package.json +5 -4
- package/preset/enable.mjs +2 -2
- package/preset/persona.md +4 -2
- package/scripts/verify-dsh-compat.mjs +14 -3
- package/scripts/verify-package.mjs +7 -3
- package/skills/architecture-design/SKILL.md +11 -0
- package/skills/code-development/SKILL.md +11 -0
- package/skills/code-review/SKILL.md +11 -0
- package/skills/eng-delivery/SKILL.md +6 -2
- package/skills/requirements-analysis/SKILL.md +11 -0
- package/skills/task-orchestration/SKILL.md +11 -0
- package/skills/test-validation/SKILL.md +11 -0
package/hooks/commit-msg
CHANGED
|
@@ -148,7 +148,7 @@ function commitCheckpoint(state2, config2) {
|
|
|
148
148
|
return { allowed: false, reason: "no commit is due at this stage under the current policy" };
|
|
149
149
|
}
|
|
150
150
|
function isEngineMeta(file) {
|
|
151
|
-
return /^\.dsh\/(task-[^/]+\.json|eng\.json)$/.test(file);
|
|
151
|
+
return /^\.dsh\/(task-[^/]+\.json|eng\.json|meta\.json)$/.test(file);
|
|
152
152
|
}
|
|
153
153
|
function checkFileScope(state2, committing, config2) {
|
|
154
154
|
if (!config2.commit.file_scope) return { ok: true, outside: [], noScope: false };
|
|
@@ -419,6 +419,9 @@ function normalizeTask(task) {
|
|
|
419
419
|
branch: String(task.branch ?? ""),
|
|
420
420
|
work_size: task.work_size ?? "standard",
|
|
421
421
|
risk_level: task.risk_level ?? "standard",
|
|
422
|
+
...task.complexity !== void 0 ? { complexity: task.complexity } : {},
|
|
423
|
+
...task.sonar_policy !== void 0 ? { sonar_policy: task.sonar_policy } : {},
|
|
424
|
+
...task.sonar_audit !== void 0 ? { sonar_audit: task.sonar_audit } : {},
|
|
422
425
|
stage: String(task.stage ?? ""),
|
|
423
426
|
requirement_confirmed: task.requirement_confirmed === true,
|
|
424
427
|
solution_confirmed: task.solution_confirmed === true,
|
|
@@ -501,7 +504,21 @@ function extractTaskId(message2) {
|
|
|
501
504
|
return match ? match[1] : void 0;
|
|
502
505
|
}
|
|
503
506
|
function isEngineMeta2(file) {
|
|
504
|
-
return /^\.dsh\/(task-[^/]+\.json|eng\.json)$/.test(file);
|
|
507
|
+
return /^\.dsh\/(task-[^/]+\.json|eng\.json|meta\.json)$/.test(file);
|
|
508
|
+
}
|
|
509
|
+
function scopeHash(state2, root) {
|
|
510
|
+
const entries2 = [...new Set(state2.files)].filter((file) => !isEngineMeta2(file)).sort().map((file) => {
|
|
511
|
+
const target = import_node_path.default.resolve(root, file);
|
|
512
|
+
const inside = import_node_path.default.relative(root, target);
|
|
513
|
+
if (inside.startsWith("..") || import_node_path.default.isAbsolute(inside)) refuse(`\u4EFB\u52A1\u8303\u56F4\u8DEF\u5F84\u8D8A\u754C: ${file}`);
|
|
514
|
+
try {
|
|
515
|
+
return [file, hashText(import_node_fs.default.readFileSync(target, "utf8"))];
|
|
516
|
+
} catch (error) {
|
|
517
|
+
if (error.code === "ENOENT") return [file, null];
|
|
518
|
+
refuse(`\u65E0\u6CD5\u8BFB\u53D6\u4EFB\u52A1\u6587\u4EF6 ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
return hashText(JSON.stringify(entries2));
|
|
505
522
|
}
|
|
506
523
|
function riskyPaths(entries2) {
|
|
507
524
|
return entries2.filter((entry) => !isEngineMeta2(entry.path)).filter((entry) => DEFAULT_RISK_POLICY.sensitive_paths.some((prefix) => entry.path === prefix || entry.path.startsWith(`${prefix}/`))).map((entry) => entry.path);
|
|
@@ -536,30 +553,11 @@ function loadWorkflow(parsed) {
|
|
|
536
553
|
}
|
|
537
554
|
return resolved.config;
|
|
538
555
|
}
|
|
539
|
-
var cwd = process.cwd();
|
|
540
|
-
var configPath = import_node_path.default.join(cwd, ".dsh", "eng.json");
|
|
541
|
-
var config;
|
|
542
|
-
if (import_node_fs.default.existsSync(configPath)) {
|
|
543
|
-
let raw = "";
|
|
544
|
-
try {
|
|
545
|
-
raw = import_node_fs.default.readFileSync(configPath, "utf8");
|
|
546
|
-
} catch {
|
|
547
|
-
raw = "";
|
|
548
|
-
}
|
|
549
|
-
let parsed;
|
|
550
|
-
try {
|
|
551
|
-
parsed = JSON.parse(raw);
|
|
552
|
-
} catch (error) {
|
|
553
|
-
refuse("invalid JSON in .dsh/eng.json: " + (error instanceof Error ? error.message : String(error)));
|
|
554
|
-
}
|
|
555
|
-
config = loadWorkflow(parsed);
|
|
556
|
-
} else {
|
|
557
|
-
config = loadWorkflow(void 0);
|
|
558
|
-
}
|
|
559
556
|
if (!messageFile) {
|
|
560
557
|
process.exit(0);
|
|
561
558
|
}
|
|
562
559
|
var message = readCommitMessage(messageFile);
|
|
560
|
+
var cwd = process.cwd();
|
|
563
561
|
var tasks = loadTasks();
|
|
564
562
|
var taskId = extractTaskId(message);
|
|
565
563
|
var state;
|
|
@@ -578,8 +576,20 @@ if (!state) {
|
|
|
578
576
|
if (state.flow?.hash !== void 0 && hashConfig(state.flow.config) !== state.flow.hash) {
|
|
579
577
|
refuse(`\u4EFB\u52A1 ${state.id} \u7684\u6D41\u7A0B\u5FEB\u7167 hash \u4E0D\u5339\u914D\u2014\u2014task \u8BB0\u5F55\u5728\u521B\u5EFA\u540E\u88AB\u6539\u52A8\u8FC7\uFF0C\u4FEE\u590D\u6216\u91CD\u5EFA\u4EFB\u52A1\u540E\u518D\u63D0\u4EA4`);
|
|
580
578
|
}
|
|
579
|
+
var config;
|
|
581
580
|
if (state.flow !== void 0 && state.flow.config !== void 0) {
|
|
582
581
|
config = state.flow.config;
|
|
582
|
+
} else {
|
|
583
|
+
const configPath = import_node_path.default.join(cwd, ".dsh", "eng.json");
|
|
584
|
+
if (import_node_fs.default.existsSync(configPath)) {
|
|
585
|
+
let parsed;
|
|
586
|
+
try {
|
|
587
|
+
parsed = JSON.parse(import_node_fs.default.readFileSync(configPath, "utf8"));
|
|
588
|
+
} catch (error) {
|
|
589
|
+
refuse("invalid JSON in .dsh/eng.json: " + (error instanceof Error ? error.message : String(error)));
|
|
590
|
+
}
|
|
591
|
+
config = loadWorkflow(parsed);
|
|
592
|
+
} else config = loadWorkflow(void 0);
|
|
583
593
|
}
|
|
584
594
|
var verdict = validateCommitMessage(message, config);
|
|
585
595
|
if (!verdict.ok) {
|
|
@@ -589,6 +599,13 @@ var checkpoint = commitCheckpoint(state, config);
|
|
|
589
599
|
if (!checkpoint.allowed) {
|
|
590
600
|
refuse((checkpoint.reason ?? "commit checkpoint rejected") + "\uFF08\u5F53\u524D\u9636\u6BB5: " + state.stage + "\uFF09");
|
|
591
601
|
}
|
|
602
|
+
if (state.sonar_policy?.enabled && ["\u4EE3\u7801\u5BA1\u6838", "\u5B8C\u6210"].includes(state.stage)) {
|
|
603
|
+
const audit = state.sonar_audit;
|
|
604
|
+
const lastCommit = state.commits.findLast((entry) => entry.hash !== void 0)?.hash;
|
|
605
|
+
if (!audit || audit.gate !== "OK" || audit.blocking.length > 0 || audit.scope_hash !== scopeHash(state, cwd) || !lastCommit || audit.commit_hash !== lastCommit) {
|
|
606
|
+
refuse("SonarQube \u5BA1\u6838\u672A\u901A\u8FC7\u6216\u5DF2\u8FC7\u671F\u2014\u2014\u5728\u4EE3\u7801\u5BA1\u6838\u9636\u6BB5\u5B8C\u6210\u65B0\u4E00\u6B21 CI \u626B\u63CF\u5E76\u8C03\u7528 dev_task operation=sonar_check");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
592
609
|
var entries = stagedEntries();
|
|
593
610
|
var scope = checkFileScope(state, entries.map((entry) => entry.path), config);
|
|
594
611
|
if (!scope.ok) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Task-scoped engineering flows. The grade selects gates; risk remains separate. */
|
|
2
|
+
import type { WorkflowConfig } from './engine.ts';
|
|
3
|
+
export type Complexity = 'low' | 'medium' | 'high' | 'ultra';
|
|
4
|
+
export type MetaSkill = 'requirements-analysis' | 'architecture-design' | 'task-orchestration' | 'code-development' | 'test-validation' | 'code-review';
|
|
5
|
+
export declare const COMPLEXITY_OPTIONS: readonly {
|
|
6
|
+
id: Complexity;
|
|
7
|
+
label: string;
|
|
8
|
+
guidance: string;
|
|
9
|
+
}[];
|
|
10
|
+
export declare const META_STAGES: Readonly<Record<MetaSkill, string>>;
|
|
11
|
+
export declare const ADAPTIVE_VERSION = 1;
|
|
12
|
+
/** A grade is a per-task snapshot, never a project-wide forced flow. */
|
|
13
|
+
export declare function adaptiveWorkflow(grade: Complexity): WorkflowConfig;
|
|
14
|
+
export declare function isComplexity(value: unknown): value is Complexity;
|
|
15
|
+
export declare function metaForStage(stage: string): MetaSkill | undefined;
|
package/lib/adaptive.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const COMPLEXITY_OPTIONS = [
|
|
2
|
+
{ id: 'low', label: '低', guidance: '边界明确的局部修改;一个实施项即可说明改动。' },
|
|
3
|
+
{ id: 'medium', label: '中', guidance: '常规功能或缺陷修复;先写清需求与验收条件。' },
|
|
4
|
+
{ id: 'high', label: '高', guidance: '跨模块或有先后依赖的需求;按实现顺序编排任务与交接产物。' },
|
|
5
|
+
{ id: 'ultra', label: '超高', guidance: '完整新模块或大范围重构;先明确架构边界,再按依赖顺序实施。' },
|
|
6
|
+
];
|
|
7
|
+
export const META_STAGES = {
|
|
8
|
+
'requirements-analysis': '需求分析',
|
|
9
|
+
'architecture-design': '架构设计',
|
|
10
|
+
'task-orchestration': '任务编排',
|
|
11
|
+
'code-development': '代码开发',
|
|
12
|
+
'test-validation': '测试',
|
|
13
|
+
'code-review': '代码审核',
|
|
14
|
+
};
|
|
15
|
+
export const ADAPTIVE_VERSION = 1;
|
|
16
|
+
const COMMON_ARTIFACTS = [
|
|
17
|
+
{ stage: '需求分析', id: 'requirement', name: '需求与验收条件', fields: ['goal', 'scope', 'acceptance'] },
|
|
18
|
+
{ stage: '架构设计', id: 'architecture', name: '架构边界', fields: ['approach', 'impact', 'tradeoffs'] },
|
|
19
|
+
{ stage: '任务编排', id: 'task-plan', name: '按实现顺序排列的任务', fields: ['steps', 'dependencies', 'handoffs'] },
|
|
20
|
+
];
|
|
21
|
+
/** A grade is a per-task snapshot, never a project-wide forced flow. */
|
|
22
|
+
export function adaptiveWorkflow(grade) {
|
|
23
|
+
const prefix = grade === 'low' ? []
|
|
24
|
+
: grade === 'medium' ? ['需求分析']
|
|
25
|
+
: grade === 'high' ? ['需求分析', '任务编排']
|
|
26
|
+
: ['需求分析', '架构设计', '任务编排'];
|
|
27
|
+
const stages = [...prefix, '代码开发', '测试', '代码审核', '完成'];
|
|
28
|
+
const transitions = stages.slice(0, -1).map((from, index) => {
|
|
29
|
+
const to = stages[index + 1];
|
|
30
|
+
const requires = from === '需求分析' || from === '架构设计' || from === '任务编排'
|
|
31
|
+
? ['artifacts_present']
|
|
32
|
+
: from === '代码开发' ? ['todos_done']
|
|
33
|
+
: from === '测试' ? ['verified']
|
|
34
|
+
: from === '代码审核' ? ['review_passed'] : [];
|
|
35
|
+
return { from, to, requires };
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
stages,
|
|
39
|
+
start_stage: stages[0],
|
|
40
|
+
transitions,
|
|
41
|
+
artifacts: COMMON_ARTIFACTS.filter(artifact => stages.includes(artifact.stage)),
|
|
42
|
+
// The branch must be committed/pushed before its CI Sonar scan can run.
|
|
43
|
+
commit: { policy: 'task', message_pattern: '', message_hint: '无格式要求', checkpoints: ['测试'], file_scope: true },
|
|
44
|
+
high_risk_requires_verification: true,
|
|
45
|
+
review_depth: grade === 'low' ? 'single' : 'two-stage',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function isComplexity(value) {
|
|
49
|
+
return value === 'low' || value === 'medium' || value === 'high' || value === 'ultra';
|
|
50
|
+
}
|
|
51
|
+
export function metaForStage(stage) {
|
|
52
|
+
return Object.entries(META_STAGES).find(([, label]) => label === stage)?.[0];
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=adaptive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive.js","sourceRoot":"","sources":["../src/adaptive.ts"],"names":[],"mappings":"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"}
|