@godv61/dsh-task-engine 0.19.2 → 0.22.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/lib/hook.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Commit-msg hook source — the SINGLE source the installed hook is generated from.
3
+ *
4
+ * `build-hook.mjs` bundles this file (plus the pure {@link ../engine} and
5
+ * {@link ../workflows} modules it imports) into a self-contained CommonJS
6
+ * `hooks/commit-msg`. The gate logic therefore lives in exactly one place
7
+ * (`engine.ts` + `workflows.ts`); the hook no longer hand-mirrors it. Edit this
8
+ * file and rebuild — never edit `hooks/commit-msg` directly.
9
+ *
10
+ * @module dsh-task-engine/hook
11
+ */
12
+ export {};
package/lib/hook.js ADDED
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Commit-msg hook source — the SINGLE source the installed hook is generated from.
3
+ *
4
+ * `build-hook.mjs` bundles this file (plus the pure {@link ../engine} and
5
+ * {@link ../workflows} modules it imports) into a self-contained CommonJS
6
+ * `hooks/commit-msg`. The gate logic therefore lives in exactly one place
7
+ * (`engine.ts` + `workflows.ts`); the hook no longer hand-mirrors it. Edit this
8
+ * file and rebuild — never edit `hooks/commit-msg` directly.
9
+ *
10
+ * @module dsh-task-engine/hook
11
+ */
12
+ import { execSync } from 'node:child_process';
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { checkFileScope, commitCheckpoint, validateCommitMessage, } from "./engine.js";
16
+ import { resolveFlow } from "./workflows.js";
17
+ import { hashConfig } from "./snapshot.js";
18
+ import { DEFAULT_RISK_POLICY } from "./project.js";
19
+ const messageFile = process.argv[2];
20
+ function refuse(reason) {
21
+ console.error('');
22
+ console.error('dsh-task-engine 提交门禁拒绝: ' + reason);
23
+ console.error('请先用 dev_task 走完前置阶段(或修正提交消息),再提交。');
24
+ console.error('');
25
+ process.exit(1);
26
+ }
27
+ function readJson(abs) {
28
+ try {
29
+ return JSON.parse(fs.readFileSync(abs, 'utf8'));
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ /**
36
+ * Fill every field missing from a legacy task record so the engine's pure
37
+ * functions (`commitCheckpoint`, `checkFileScope`) never read an absent field.
38
+ */
39
+ function normalizeTask(task) {
40
+ return {
41
+ schema: 1,
42
+ id: String(task.id ?? ''),
43
+ title: String(task.title ?? ''),
44
+ branch: String(task.branch ?? ''),
45
+ work_size: task.work_size ?? 'standard',
46
+ risk_level: task.risk_level ?? 'standard',
47
+ stage: String(task.stage ?? ''),
48
+ requirement_confirmed: task.requirement_confirmed === true,
49
+ solution_confirmed: task.solution_confirmed === true,
50
+ items: Array.isArray(task.items) ? task.items : [],
51
+ verification: task.verification ?? { passed: false, evidence: [] },
52
+ review: task.review ?? { outcome: 'pending' },
53
+ artifacts: task.artifacts ?? {},
54
+ files: Array.isArray(task.files) ? task.files : [],
55
+ commits: Array.isArray(task.commits) ? task.commits : [],
56
+ ...(task.flow !== undefined ? { flow: task.flow } : {}),
57
+ };
58
+ }
59
+ function loadTasks() {
60
+ const dir = path.join(process.cwd(), '.dsh');
61
+ if (!fs.existsSync(dir))
62
+ return [];
63
+ const out = [];
64
+ const names = fs.readdirSync(dir).filter(name => name.startsWith('task-') && name.endsWith('.json'));
65
+ for (const name of names) {
66
+ const json = readJson(path.join(dir, name));
67
+ if (json !== undefined && json.id !== undefined)
68
+ out.push({ file: path.join(dir, name), task: normalizeTask(json) });
69
+ }
70
+ return out;
71
+ }
72
+ function currentBranch() {
73
+ try {
74
+ return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
75
+ }
76
+ catch {
77
+ return '';
78
+ }
79
+ }
80
+ /**
81
+ * The commit's touched paths with their git status (e.g. `M`, `A`, `D`, `T`,
82
+ * `R100`). Read as raw bytes under `quotePath=false` so non-ASCII filenames
83
+ * survive verbatim; `ACMRDT` includes deletions and type changes so every
84
+ * touched path — plus a rename's old and new path — is held to the declared
85
+ * scope. The status is surfaced on refusal for audit.
86
+ */
87
+ function stagedEntries() {
88
+ try {
89
+ const raw = execSync('git -c core.quotePath=false diff --cached --name-status -z --diff-filter=ACMRDT', { encoding: 'utf8' });
90
+ const parts = raw.split('\0');
91
+ const entries = [];
92
+ let i = 0;
93
+ while (i < parts.length) {
94
+ const status = parts[i];
95
+ if (status === undefined || status === '') {
96
+ i++;
97
+ continue;
98
+ }
99
+ if (status.startsWith('R') || status.startsWith('C')) {
100
+ // rename/copy carry `<status><score>\0<oldpath>\0<newpath>`; both paths
101
+ // are in the commit, so both are scope-checked.
102
+ const from = parts[i + 1];
103
+ const to = parts[i + 2];
104
+ if (from !== undefined && from !== '')
105
+ entries.push({ status, path: from });
106
+ if (to !== undefined && to !== '')
107
+ entries.push({ status, path: to });
108
+ i += 3;
109
+ }
110
+ else {
111
+ const path = parts[i + 1];
112
+ if (path !== undefined && path !== '')
113
+ entries.push({ status, path });
114
+ i += 2;
115
+ }
116
+ }
117
+ return entries;
118
+ }
119
+ catch {
120
+ return [];
121
+ }
122
+ }
123
+ function fileMtime(file) {
124
+ try {
125
+ return fs.statSync(file).mtimeMs;
126
+ }
127
+ catch {
128
+ return 0;
129
+ }
130
+ }
131
+ /** Fallback only for pattern-less flows: newest task on the branch, else the sole task. */
132
+ function pickTask(tasks, branch) {
133
+ if (tasks.length === 0)
134
+ return undefined;
135
+ const byBranch = tasks.find(({ task }) => task.branch === branch);
136
+ if (byBranch)
137
+ return byBranch.task;
138
+ if (tasks.length === 1)
139
+ return tasks[0].task;
140
+ const sorted = [...tasks].sort((a, b) => fileMtime(b.file) - fileMtime(a.file));
141
+ return sorted[0].task;
142
+ }
143
+ function readCommitMessage(file) {
144
+ return fs.readFileSync(file, 'utf8')
145
+ .split('\n')
146
+ .filter(line => !line.startsWith('#'))
147
+ .join('\n')
148
+ .trim();
149
+ }
150
+ /**
151
+ * Extract the task id from the leading `【…】` of the summary, independent of the
152
+ * live config (the hook must locate the task before it knows that task's frozen
153
+ * config). Pattern-less flows simply carry no `【…】` here.
154
+ */
155
+ function extractTaskId(message) {
156
+ const match = /^【([^【】\s]+)】/.exec(message);
157
+ return match ? match[1] : undefined;
158
+ }
159
+ /** Engine-owned state files are exempt from scope and risk checks. */
160
+ function isEngineMeta(file) {
161
+ return /^\.dsh\/(task-[^/]+\.json|eng\.json)$/.test(file);
162
+ }
163
+ /** Staged paths that hit the risk policy's sensitive paths (engine files exempt). */
164
+ function riskyPaths(entries) {
165
+ return entries
166
+ .filter(entry => !isEngineMeta(entry.path))
167
+ .filter(entry => DEFAULT_RISK_POLICY.sensitive_paths.some(prefix => entry.path === prefix || entry.path.startsWith(`${prefix}/`)))
168
+ .map(entry => entry.path);
169
+ }
170
+ /** Only a high-risk task that passed a real command receipt may touch sensitive paths. */
171
+ function riskVerified(state) {
172
+ const receipt = state.verification.receipt;
173
+ return state.risk_level === 'high_risk'
174
+ && receipt !== undefined
175
+ && receipt.exit_code === 0
176
+ && !receipt.timed_out
177
+ && !receipt.aborted;
178
+ }
179
+ /** The standard preset, applied when the project has no `.dsh/eng.json` at all. */
180
+ function defaultConfig() {
181
+ const resolved = resolveFlow('standard');
182
+ if (!resolved.ok)
183
+ throw new Error('built-in "standard" preset is missing');
184
+ return resolved.config;
185
+ }
186
+ /**
187
+ * Resolve the project's live workflow — used only as a FALLBACK when a legacy
188
+ * task record carries no frozen snapshot. Mirrors `dev_task` `resolveWorkflow`:
189
+ * no file → standard; missing/unknown `flow` → fail closed (never silent fallback).
190
+ */
191
+ function loadWorkflow(parsed) {
192
+ if (parsed === undefined)
193
+ return defaultConfig();
194
+ const flow = parsed.flow;
195
+ if (typeof flow !== 'string' || flow.trim() === '') {
196
+ refuse('项目 .dsh/eng.json 缺 "flow" 字段——设为 standard|agile|minimal,或删除该文件');
197
+ }
198
+ const override = typeof parsed.stage_bindings === 'object' && parsed.stage_bindings !== null && !Array.isArray(parsed.stage_bindings)
199
+ ? { stage_bindings: parsed.stage_bindings }
200
+ : undefined;
201
+ const resolved = resolveFlow(flow, override);
202
+ if (!resolved.ok) {
203
+ refuse(`未知流程 "${resolved.flow}"(已知流程:${resolved.knownFlows.join('、')})`);
204
+ }
205
+ return resolved.config;
206
+ }
207
+ // --- gather project state (fallback config) ---
208
+ const cwd = process.cwd();
209
+ const configPath = path.join(cwd, '.dsh', 'eng.json');
210
+ let config;
211
+ if (fs.existsSync(configPath)) {
212
+ let raw = '';
213
+ try {
214
+ raw = fs.readFileSync(configPath, 'utf8');
215
+ }
216
+ catch {
217
+ raw = '';
218
+ }
219
+ let parsed;
220
+ try {
221
+ parsed = JSON.parse(raw);
222
+ }
223
+ catch (error) {
224
+ refuse('invalid JSON in .dsh/eng.json: ' + (error instanceof Error ? error.message : String(error)));
225
+ }
226
+ config = loadWorkflow(parsed);
227
+ }
228
+ else {
229
+ config = loadWorkflow(undefined);
230
+ }
231
+ // --- the gate ---
232
+ if (!messageFile) {
233
+ process.exit(0);
234
+ }
235
+ const message = readCommitMessage(messageFile);
236
+ const tasks = loadTasks();
237
+ // Locate the task by id from the summary first; only then read its frozen config.
238
+ const taskId = extractTaskId(message);
239
+ let state;
240
+ if (taskId !== undefined) {
241
+ const found = tasks.find(({ task }) => task.id === taskId);
242
+ if (!found) {
243
+ refuse(`提交消息里的任务 id "${taskId}" 找不到对应的任务记录(.dsh/task-<id>.json)`);
244
+ }
245
+ state = found.task;
246
+ }
247
+ else {
248
+ state = pickTask(tasks, currentBranch());
249
+ }
250
+ if (!state) {
251
+ refuse('没有找到 dev_task 任务记录(.dsh/task-*.json)——请先 dev_task operation=create 建立任务');
252
+ }
253
+ if (state.flow?.hash !== undefined && hashConfig(state.flow.config) !== state.flow.hash) {
254
+ refuse(`任务 ${state.id} 的流程快照 hash 不匹配——task 记录在创建后被改动过,修复或重建任务后再提交`);
255
+ }
256
+ // Check this task against its frozen snapshot, not the live .dsh/eng.json
257
+ // (which may have drifted mid-task). Fall back to the live config only for
258
+ // legacy records that predate the snapshot field.
259
+ if (state.flow !== undefined && state.flow.config !== undefined) {
260
+ config = state.flow.config;
261
+ }
262
+ const verdict = validateCommitMessage(message, config);
263
+ if (!verdict.ok) {
264
+ refuse(verdict.errors?.[0] ?? 'commit summary does not match the configured pattern');
265
+ }
266
+ const checkpoint = commitCheckpoint(state, config);
267
+ if (!checkpoint.allowed) {
268
+ refuse((checkpoint.reason ?? 'commit checkpoint rejected') + '(当前阶段: ' + state.stage + ')');
269
+ }
270
+ const entries = stagedEntries();
271
+ const scope = checkFileScope(state, entries.map(entry => entry.path), config);
272
+ if (!scope.ok) {
273
+ if (scope.noScope) {
274
+ refuse('任务没有声明文件范围(files 为空)——先用 dev_task operation=scope 声明本任务涉及的文件');
275
+ }
276
+ const described = scope.outside.map(outside => {
277
+ const entry = entries.find(candidate => candidate.path === outside);
278
+ return entry === undefined ? outside : `${entry.status}\t${outside}`;
279
+ });
280
+ refuse('提交了任务范围之外的文件: ' + described.join(', '));
281
+ }
282
+ const sensitive = riskyPaths(entries);
283
+ if (sensitive.length > 0 && !riskVerified(state)) {
284
+ refuse('提交触及敏感路径: ' + sensitive.join(', ') +
285
+ ' — 这类变更要求任务声明为 high_risk,且验证必须是真实命令回执(exit 0)');
286
+ }
287
+ process.exit(0);
288
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Project adaptation knowledge shared by the tool plane, the workbench, and
3
+ * the commit hook: language detection, root discovery, default verification
4
+ * commands, governance-file names, and the risk policy.
5
+ *
6
+ * Everything here is a pure function over a narrow {@link FileProbe}, so it
7
+ * unit-tests without a harness and the commit hook can reuse the same tables.
8
+ *
9
+ * @module dsh-task-engine/project
10
+ */
11
+ /**
12
+ * Read one file relative to a directory. `undefined` means missing or unreadable;
13
+ * every caller implements this against its own filesystem (sandboxed `Fs` in the
14
+ * tool plane, `node:fs` in the commit hook).
15
+ */
16
+ export interface FileProbe {
17
+ read(dir: string, relPath: string): Promise<string | undefined>;
18
+ /** Basenames of a directory's children; implementations without listing return `[]`. */
19
+ list?(dir: string, relPath: string): Promise<string[]>;
20
+ }
21
+ export type ProjectType = 'node' | 'java' | 'python' | 'go' | 'rust' | 'unknown';
22
+ /** Default verification command per language; a project can override via `.dsh/eng.json`. */
23
+ export declare const DEFAULT_VERIFY_COMMANDS: Record<Exclude<ProjectType, 'unknown'>, string>;
24
+ /**
25
+ * Governance files that steer AI behavior in this workspace. `init` must not
26
+ * overwrite any of them without an explicit, approved overwrite — they are
27
+ * siblings of `AGENTS.md`, not alternatives it may ignore.
28
+ */
29
+ export declare const GOVERNANCE_FILES: readonly string[];
30
+ /** Directory names inside a project that hold project-level skills / rules. */
31
+ export declare const PROJECT_SKILL_DIR = ".dsh/skills";
32
+ export declare const PROJECT_RULE_DIR = ".dsh/rules";
33
+ /**
34
+ * Risk policy: paths whose change is sensitive, and git operations that are
35
+ * inherently risky regardless of path. The commit hook surfaces these; a task
36
+ * touching them should be verified with a real command receipt.
37
+ */
38
+ export interface RiskPolicy {
39
+ /** Exact relative paths or directory prefixes whose change is sensitive. */
40
+ sensitive_paths: readonly string[];
41
+ /** Git `--name-status` operation letters treated as risky (delete / type change). */
42
+ risky_operations: readonly string[];
43
+ }
44
+ export declare const DEFAULT_RISK_POLICY: RiskPolicy;
45
+ /** All languages detected in `dir` by marker files, in marker declaration order. */
46
+ export declare function detectLanguages(probe: FileProbe, dir: string): Promise<ProjectType[]>;
47
+ /** The single dominant language of `dir`; a mixed tree reports `unknown`. */
48
+ export declare function detectType(probe: FileProbe, dir: string): Promise<ProjectType>;
49
+ /** Default verification command for a language; `unknown` has none. */
50
+ export declare function defaultVerifyCommand(type: ProjectType): string | undefined;
51
+ /**
52
+ * Discover the project root from `startDir` by climbing toward the filesystem
53
+ * root: the first directory containing a git repository, a `.dsh/eng.json`
54
+ * marker, or any language marker file wins. Falls back to `startDir` when
55
+ * nothing is found, so a plain directory still behaves as its own project.
56
+ */
57
+ export declare function detectRoot(probe: FileProbe, startDir: string): Promise<string>;
58
+ /**
59
+ * Governance files actually present in `dir`; `init` uses this to refuse silent
60
+ * overwrites of anything steering other agents (AGENTS.md, CLAUDE.md, …).
61
+ */
62
+ export declare function presentGovernanceFiles(probe: FileProbe, dir: string): Promise<string[]>;
63
+ /** Project-level rule names from `.dsh/rules/*.md` (stem names, sorted). */
64
+ export declare function listProjectRules(probe: FileProbe, dir: string): Promise<string[]>;
65
+ /** Project-level skill names from `.dsh/skills/<name>/SKILL.md` (sorted). */
66
+ export declare function listProjectSkills(probe: FileProbe, dir: string): Promise<string[]>;
package/lib/project.js ADDED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Project adaptation knowledge shared by the tool plane, the workbench, and
3
+ * the commit hook: language detection, root discovery, default verification
4
+ * commands, governance-file names, and the risk policy.
5
+ *
6
+ * Everything here is a pure function over a narrow {@link FileProbe}, so it
7
+ * unit-tests without a harness and the commit hook can reuse the same tables.
8
+ *
9
+ * @module dsh-task-engine/project
10
+ */
11
+ import { dirname } from 'node:path';
12
+ /** Marker files that identify a language stack, checked in declaration order. */
13
+ const TYPE_MARKERS = {
14
+ node: ['package.json'],
15
+ java: ['pom.xml', 'build.gradle', 'build.gradle.kts', 'settings.gradle'],
16
+ python: ['pyproject.toml', 'requirements.txt', 'setup.py'],
17
+ go: ['go.mod'],
18
+ rust: ['Cargo.toml'],
19
+ };
20
+ /** Default verification command per language; a project can override via `.dsh/eng.json`. */
21
+ export const DEFAULT_VERIFY_COMMANDS = {
22
+ node: 'npm test',
23
+ java: 'mvn -q test',
24
+ python: 'python -m pytest',
25
+ go: 'go test ./...',
26
+ rust: 'cargo test',
27
+ };
28
+ /**
29
+ * Governance files that steer AI behavior in this workspace. `init` must not
30
+ * overwrite any of them without an explicit, approved overwrite — they are
31
+ * siblings of `AGENTS.md`, not alternatives it may ignore.
32
+ */
33
+ export const GOVERNANCE_FILES = ['AGENTS.md', 'CLAUDE.md', '.cursorrules'];
34
+ /** Directory names inside a project that hold project-level skills / rules. */
35
+ export const PROJECT_SKILL_DIR = '.dsh/skills';
36
+ export const PROJECT_RULE_DIR = '.dsh/rules';
37
+ export const DEFAULT_RISK_POLICY = {
38
+ // `.dsh/task-*.json` and `.dsh/eng.json` are protected by the snapshot hash and
39
+ // the engine-meta exemption, not by this list; `.dsh/rules` / `.dsh/skills`
40
+ // hold regular project content and stay committable by normal tasks.
41
+ sensitive_paths: ['.git', '.env', '.credentials.yaml', 'credentials', 'secrets'],
42
+ risky_operations: ['D', 'T'],
43
+ };
44
+ /** All marker files across languages, for root discovery. */
45
+ const ALL_MARKERS = Object.values(TYPE_MARKERS).flat();
46
+ /** Git repository markers: a real `.git` directory or a worktree's `.git` file. */
47
+ const GIT_MARKERS = ['.git/HEAD', '.git'];
48
+ /** How many directory levels to climb while hunting for a project root. */
49
+ const MAX_ROOT_CLIMB = 8;
50
+ /** All languages detected in `dir` by marker files, in marker declaration order. */
51
+ export async function detectLanguages(probe, dir) {
52
+ const found = [];
53
+ for (const type of Object.keys(TYPE_MARKERS)) {
54
+ for (const marker of TYPE_MARKERS[type]) {
55
+ if (await probe.read(dir, marker) !== undefined) {
56
+ found.push(type);
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ return found;
62
+ }
63
+ /** The single dominant language of `dir`; a mixed tree reports `unknown`. */
64
+ export async function detectType(probe, dir) {
65
+ const languages = await detectLanguages(probe, dir);
66
+ return languages.length === 1 ? languages[0] : 'unknown';
67
+ }
68
+ /** Default verification command for a language; `unknown` has none. */
69
+ export function defaultVerifyCommand(type) {
70
+ return type === 'unknown' ? undefined : DEFAULT_VERIFY_COMMANDS[type];
71
+ }
72
+ /**
73
+ * Discover the project root from `startDir` by climbing toward the filesystem
74
+ * root: the first directory containing a git repository, a `.dsh/eng.json`
75
+ * marker, or any language marker file wins. Falls back to `startDir` when
76
+ * nothing is found, so a plain directory still behaves as its own project.
77
+ */
78
+ export async function detectRoot(probe, startDir) {
79
+ let current = startDir;
80
+ for (let level = 0; level <= MAX_ROOT_CLIMB; level++) {
81
+ if (await probe.read(current, '.dsh/eng.json') !== undefined)
82
+ return current;
83
+ for (const marker of GIT_MARKERS) {
84
+ if (await probe.read(current, marker) !== undefined)
85
+ return current;
86
+ }
87
+ for (const marker of ALL_MARKERS) {
88
+ if (await probe.read(current, marker) !== undefined)
89
+ return current;
90
+ }
91
+ const parent = dirname(current);
92
+ if (parent === current)
93
+ break;
94
+ current = parent;
95
+ }
96
+ return startDir;
97
+ }
98
+ /**
99
+ * Governance files actually present in `dir`; `init` uses this to refuse silent
100
+ * overwrites of anything steering other agents (AGENTS.md, CLAUDE.md, …).
101
+ */
102
+ export async function presentGovernanceFiles(probe, dir) {
103
+ const present = [];
104
+ for (const file of GOVERNANCE_FILES) {
105
+ if (await probe.read(dir, file) !== undefined)
106
+ present.push(file);
107
+ }
108
+ return present;
109
+ }
110
+ /** Project-level rule names from `.dsh/rules/*.md` (stem names, sorted). */
111
+ export async function listProjectRules(probe, dir) {
112
+ const entries = await probe.list?.(dir, PROJECT_RULE_DIR) ?? [];
113
+ return entries
114
+ .filter(name => name.endsWith('.md'))
115
+ .map(name => name.slice(0, -'.md'.length))
116
+ .sort();
117
+ }
118
+ /** Project-level skill names from `.dsh/skills/<name>/SKILL.md` (sorted). */
119
+ export async function listProjectSkills(probe, dir) {
120
+ const entries = await probe.list?.(dir, PROJECT_SKILL_DIR) ?? [];
121
+ const names = [];
122
+ for (const entry of entries) {
123
+ if (await probe.read(dir, `${PROJECT_SKILL_DIR}/${entry}/SKILL.md`) !== undefined)
124
+ names.push(entry);
125
+ }
126
+ return names.sort();
127
+ }
128
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Frozen-snapshot integrity: the canonical hash of a workflow config.
3
+ *
4
+ * Lives outside {@link ../engine} because the browser client bundles engine
5
+ * (for `validateWorkflow`) but must never pull `node:crypto`; the tool plane
6
+ * and the commit hook import this module directly, both on the Node platform.
7
+ *
8
+ * @module dsh-task-engine/snapshot
9
+ */
10
+ import type { WorkflowConfig } from './engine.ts';
11
+ /**
12
+ * SHA-256 of arbitrary text; the integrity primitive behind both the frozen
13
+ * config hash and the installed-hook integrity check.
14
+ */
15
+ export declare function hashText(text: string): string;
16
+ /**
17
+ * SHA-256 of a workflow config's canonical JSON, the frozen-snapshot integrity
18
+ * stamp. The same JSON object re-serializes byte-identically, so a task record
19
+ * whose `config` was hand-edited after creation no longer matches its `hash`.
20
+ */
21
+ export declare function hashConfig(config: WorkflowConfig): string;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Frozen-snapshot integrity: the canonical hash of a workflow config.
3
+ *
4
+ * Lives outside {@link ../engine} because the browser client bundles engine
5
+ * (for `validateWorkflow`) but must never pull `node:crypto`; the tool plane
6
+ * and the commit hook import this module directly, both on the Node platform.
7
+ *
8
+ * @module dsh-task-engine/snapshot
9
+ */
10
+ import { createHash } from 'node:crypto';
11
+ /**
12
+ * SHA-256 of arbitrary text; the integrity primitive behind both the frozen
13
+ * config hash and the installed-hook integrity check.
14
+ */
15
+ export function hashText(text) {
16
+ return createHash('sha256').update(text).digest('hex');
17
+ }
18
+ /**
19
+ * SHA-256 of a workflow config's canonical JSON, the frozen-snapshot integrity
20
+ * stamp. The same JSON object re-serializes byte-identically, so a task record
21
+ * whose `config` was hand-edited after creation no longer matches its `hash`.
22
+ */
23
+ export function hashConfig(config) {
24
+ return hashText(JSON.stringify(config));
25
+ }
26
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,MAAsB;IAC/C,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAA;AACzC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godv61/dsh-task-engine",
3
- "version": "0.19.2",
3
+ "version": "0.22.0",
4
4
  "description": "Preset-workflow engineering delivery engine for DeepSeek Harness: a `dev_task` tool that hard-gates stage transitions and commit scope/message, per-node skill/rule bindings with progressive disclosure, a workbench to pick a flow preset and mount skills/rules, and preset-scoped activation — the tool and skills activate only for sessions on a preset that names the agent row.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -51,7 +51,7 @@
51
51
  }
52
52
  },
53
53
  "scripts": {
54
- "build": "tsc -p tsconfig.json && node build-client.mjs",
54
+ "build": "tsc -p tsconfig.json && node build-client.mjs && node build-hook.mjs",
55
55
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
56
56
  "test": "node .p0-test.mjs",
57
57
  "prepare": "npm run build"
@@ -7,7 +7,7 @@ description: 交付验证节点:执行目标测试、编译与契约检查,
7
7
 
8
8
  1. `dev_task`(operation=status)读风险等级与验收条件。
9
9
  2. 执行聚焦验证:目标测试、受影响模块编译、契约检查与必要人工步骤,不默认叠加 clean/package 全家桶。
10
- 3. 结果用 `dev_task`(operation=verify, passed=..., evidence=[...]) 记录;high_risk 必须附证据。
10
+ 3. 结果用 `dev_task`(operation=verify)记录:`high_risk` 必须跑真实命令回执(显式传 `command` 指定验收命令;不传时引擎按 `.dsh/eng.json` 的 `verify_command` → 语言默认自动选,但默认命令可能不覆盖验收点,高风险仍建议显式传)。退出码由引擎当客观结论,不得自报 passed;常规风险可用 `passed` + `evidence` 文本。
11
11
  4. 环境受阻时如实记"未编译/未联调";high_risk 核心行为无法验证时不得通过。
12
12
 
13
13
  验证失败不得 advance。