@devflow-core/dsh-devflow 0.6.1 → 0.7.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/README.md CHANGED
@@ -33,7 +33,7 @@ npx @deepseek-ai/dsh plugin --profile web add @devflow-core/dsh-devflow
33
33
 
34
34
  - **devflow-\* 权威覆盖**:目标文件与包内源文件字节相同 → 跳过;不同 → 覆盖。
35
35
  - 源不再包含的 devflow-\* 残留(升级后旧文件)→ 删除(prune)。
36
- - **非 devflow 资产永不触碰**:用户自装的 skill(如 `atlassian`、`rtk`)、
36
+ - **非 devflow 资产永不触碰**:用户自装的 skill(如 `atlassian`)、
37
37
  自定义命令/脚本一律保留,不删除不覆盖。
38
38
  - **preset 目录完全由插件管理**:`~/.dsh/.agent-presets/devflow-2/` 整目录归
39
39
  插件所有,目录内非包内文件(含手动备份如 `agent.cordis.yml.bak-*`)会在
package/RELEASE.md CHANGED
@@ -101,7 +101,7 @@ npx @deepseek-ai/dsh plugin --profile web update @devflow-core/dsh-devflow
101
101
  - 字节相同的文件跳过(不覆盖用户 mtime)
102
102
  - 不同的文件覆盖(devflow-* 权威)
103
103
  - 源不再包含的 devflow-* 残留 prune
104
- - 非 devflow 资产(atlassian、rtk 等)永不触碰
104
+ - 非 devflow 资产(atlassian 等)永不触碰
105
105
  - `~/.dsh/.agent-presets/devflow-2/` 整目录归插件管理,目录内非包内文件会被清理
106
106
 
107
107
  ## 常见错误对照表
@@ -1,20 +1,21 @@
1
1
  # DevFlow 2.0 — DeepSeek Harness Agent Preset
2
2
 
3
3
  A DSH agent preset that anchors the FIRST model request on the Minimal
4
- surface, then promotes into a full DevFlow session: Code Mode (PTC) on the
5
- wire, the complete DevFlow persona, and the DevFlow lifecycle guaranteed.
4
+ surface, then promotes into a full DevFlow session: the native tool surface,
5
+ the complete DevFlow persona, and the DevFlow lifecycle guaranteed.
6
6
 
7
7
  ## What it is
8
8
 
9
9
  - `agent.cordis.yml` — two-phase composition (adapted from the `liangshen`
10
10
  preset). Phase 1 shows only the one-line Minimal persona
11
- ("You are a helpful software engineer assistant."), persistent
11
+ ("You are a helpful software engineer assistant."),
12
12
  `bash` + `str_replace_editor`, no runtime contexts, no injected pre-step
13
- messages, and a 1024 output budget. The anchor gate (first minimal-like
14
- reasoning block, 4-step fallback, or first-response release) then promotes
15
- the session: the wire switches to Code Mode (a single `run_code` tool), the
16
- full DevFlow persona is restored, and workspace instructions (AGENTS.md
17
- digest) plus the skill catalog are deferred one step.
13
+ messages, and since 2026-09-10 — no output-token cap. The anchor gate
14
+ (first minimal-like reasoning block, 4-step fallback, or first-response
15
+ release) then promotes the session: the wire switches to the deployment
16
+ default presentation (`native` 33 tool schemas, no `run_code`, no generated
17
+ SDK section), the full DevFlow persona is restored, and workspace
18
+ instructions (AGENTS.md digest) plus the skill catalog are deferred one step.
18
19
  - `tool-bootstrap.mjs` — the two-phase bootstrap plugin (from
19
20
  `xiaobright/dsh-anchored-standard`, MIT, extended by `dsh-liangshen`),
20
21
  with a `phase1Persona` swap so the DevFlow persona only appears after
@@ -49,18 +50,48 @@ cp dsh/agent-presets/devflow-2/NOTICE ~/.dsh/.agent-presets/devfl
49
50
  Start a new session in the web UI and pick **DevFlow 2.0** in the
50
51
  preset picker. The roster may need a refresh or restart to show a newly added
51
52
  preset. Existing sessions keep their old phase; the two-phase behavior applies
52
- to new sessions.
53
+ to new sessions. The same holds for the 2026-09-10 prompt-budget changes: a
54
+ running session keeps the policy it mounted with, so start a new session to
55
+ pick them up.
53
56
 
54
57
  ## Caveats
55
58
 
56
59
  - `~/.dsh/.agent-presets/` is plugin-maintained: a DSH plugin upgrade may
57
60
  regenerate it. Re-run `npm run install:user -- --home ~/.dsh --write --force`
58
61
  (or re-copy) after upgrades.
59
- - Phase 1 uses the persistent (PTY-backed) Minimal `bash` shell. On Windows
60
- builds where DSH's PTY backend is unavailable, phase-1 `bash` calls fail but
61
- `str_replace_editor` still works, and the first-response release /
62
- composition-drift guard keep the session usable (Code Mode `run_code` does
63
- not need the PTY).
62
+ - Phase 1 uses the Minimal `bash`: the persistent PTY-backed shell on
63
+ linux/darwin, or the stateless Git-Bash-backed `custom-bash.mjs` on Windows,
64
+ where the PTY backend is unavailable. `str_replace_editor` is the phase-1
65
+ editor on both paths, and the first-response release / composition-drift
66
+ guard keep the session usable if either bootstrap tool is missing.
67
+
68
+ ## Changes 2026-09-10 (prompt budget)
69
+
70
+ Three independent defects were measured on real session logs and fixed:
71
+
72
+ - **Phase-1 output cap removed.** `bootstrapMaxTokens: 1024` capped every
73
+ pre-promotion request, and output tokens INCLUDE reasoning tokens: under
74
+ `reasoningEffort: max` the first request was hard-truncated at 1024
75
+ (`finish_reason=length`, reasoning-only blocks with no text and no tool call),
76
+ which the web UI reports as "已达到输出 token 上限 / 回答被截断". The cap was
77
+ the anchor's forcing function for the short We-need block, not a safety limit;
78
+ the gate still promotes through its other three paths.
79
+ - **`promotedPresentation: code` → `native`.** `code` does NOT mean "PTC only".
80
+ In `@deepseek-ai/dsh-tools`, `presentAs(mode)` attaches the collapse + SDK
81
+ sections for every mode except `native`, while `wireSchemas()` narrows the
82
+ wire to `run_code` only for `ptc`; `code` falls through to the default branch.
83
+ `code` therefore behaves as `both`: native schemas AND `run_code` AND the
84
+ generated SDK section — measured at 33,805 + 35,333 chars, i.e. one 36-tool
85
+ catalog paid for twice. `native` keeps the tools and drops the duplicate
86
+ (~-48% prompt per request). Use `ptc` instead to keep PTC batching at about
87
+ the same saving, at the cost of routing every call through a program.
88
+ - **Three never-used tool families disabled** (0 calls across 30 recorded
89
+ sessions): `tool-jobs` (`job_kill`/`job_list`/`job_output`, 1,508 B + a
90
+ 384-char guidance section that the same plugin contributes), the `workflow`
91
+ rows (4,067 B — the largest single schema), and `tool-ralph` (845 B). Each is
92
+ commented out in place with its restore note. The `subagent` family is kept
93
+ (it has real calls); `herdr_*` is kept because this deployment installs
94
+ `@deepseek-ai/dsh-tool-herdr` deliberately via `~/.dsh/cordis.patch.yml`.
64
95
 
65
96
  ## Customize
66
97
 
@@ -4,13 +4,18 @@
4
4
  #
5
5
  # TWO-PHASE PERSONA: the FIRST model request sees only the one-line Minimal
6
6
  # persona ("You are a helpful software engineer assistant."), the minimal tool
7
- # pair (persistent `bash` + `str_replace_editor`), NO runtime contexts and NO
7
+ # pair (`bash` + `str_replace_editor`), NO runtime contexts and NO
8
8
  # auto-injected pre-step messages. After the anchor gate passes (first
9
9
  # minimal-like reasoning block, 4-step fallback, or first-response release) the
10
- # session promotes: the wire switches to Code Mode (PTC — a single `run_code`
11
- # tool), the FULL DevFlow persona below is restored (with {{model}}/{{cwd}}),
12
- # and workspace instructions (AGENTS.md digest) + the skill catalog are
13
- # deferred one step, then stay current.
10
+ # session promotes: the wire switches to the deployment default presentation
11
+ # (`native` all tool schemas, no `run_code`, no generated SDK section), the
12
+ # FULL DevFlow persona below is restored (with {{model}}/{{cwd}}), and
13
+ # workspace instructions (AGENTS.md digest) + the skill catalog are deferred
14
+ # one step, then stay current.
15
+ #
16
+ # The pre-promotion requests carry NO output-token cap (2026-09-10): the
17
+ # former `bootstrapMaxTokens: 1024` truncated the first request under
18
+ # `reasoningEffort: max`, because output tokens include reasoning tokens.
14
19
  #
15
20
  # This row MUST stay the FIRST row: the pre-step strip relies on waterfall
16
21
  # reverse-registration order so request #1 stays minimal-exact.
@@ -24,12 +29,33 @@
24
29
  anchorGate: true
25
30
  maxBootstrapSteps: 4
26
31
  promoteAfterFirstResponse: true
27
- bootstrapMaxTokens: 1024
32
+ # DISABLED 2026-09-10: the phase-1 output cap collides with
33
+ # `reasoningEffort: max`. Output tokens INCLUDE reasoning tokens, so a
34
+ # 1024 cap hard-truncates the first request (finish_reason=length) and the
35
+ # web UI reports "已达到输出 token 上限 / 回答被截断". The cap is the
36
+ # anchor's forcing function for the short We-need block, not a safety
37
+ # limit: without it the gate still promotes via the anchor heuristic, the
38
+ # `maxBootstrapSteps` fallback, or `promoteAfterFirstResponse`.
39
+ # Re-enable only alongside a reasoningEffort low enough to fit under it.
40
+ # bootstrapMaxTokens: 1024
28
41
  # Core work set after a compaction, before re-promotion.
29
42
  compactionTools: [read, write, edit, glob, grep, todo_write, ask_user_question]
30
43
  deferredSources: [agent-instructions, skill-catalog]
31
44
  deferredGraceSteps: 1
32
- promotedPresentation: code
45
+ # native (changed 2026-09-10, was `code`): `code` does NOT mean "PTC only".
46
+ # In @deepseek-ai/dsh-tools, presentAs(mode) attaches the collapse + SDK
47
+ # sections for every mode except `native`, while wireSchemas() narrows the
48
+ # wire to `run_code` ONLY for `ptc`; `code` falls through to the default
49
+ # branch, so it behaves as `both` — native schemas AND run_code AND the
50
+ # generated SDK section. Measured on a real session: 36 tool schemas
51
+ # (33,805 chars) plus a 35,333-char SDK section in the system prompt, i.e.
52
+ # one 36-tool catalog paid for twice (76,968 chars/request). `native` keeps
53
+ # the same 36 native tools and drops the duplicate SDK section
54
+ # (~40,065 chars/request, -48%). Trade-off: `run_code` disappears.
55
+ # Use `ptc` instead if the PTC batching surface is wanted: it saves about
56
+ # the same amount (drops the 33,805-char schema array instead) but forces
57
+ # every tool call through a program.
58
+ promotedPresentation: native
33
59
  # Aligned with liangshen 0.2.8 (issue #388): after promotion, replace the
34
60
  # full-text AGENTS.md injection with one non-imperative reference hint.
35
61
  instructionHint: true
@@ -157,8 +183,11 @@
157
183
  # registry is keyed by owning agent anyway. With the persistent shell, long
158
184
  # commands run through `&`; these controls still expose whatever the host
159
185
  # registry tracks.
160
- - id: tool-jobs
161
- name: '@deepseek-ai/dsh-tool-jobs'
186
+ # DISABLED 2026-09-10 (prompt-budget trim): `job_kill` / `job_list` /
187
+ # `job_output` were never called in 30 recorded sessions. Uncomment to restore
188
+ # background-job control. Long commands still work through `&` in the shell.
189
+ # - id: tool-jobs
190
+ # name: '@deepseek-ai/dsh-tool-jobs'
162
191
 
163
192
  # ── skills ──────────────────────────────────────────────────────────────────
164
193
 
@@ -307,19 +336,25 @@
307
336
  enableRunInBackground: false
308
337
  maxDepth: provider-managed
309
338
 
310
- - id: workflow-worker-thread
311
- name: '@deepseek-ai/dsh-workflow-worker-thread'
312
- config:
313
- provider: spawn
314
-
315
- - id: tool-workflow
316
- name: '@deepseek-ai/dsh-tool-workflow'
317
-
318
- - id: tool-ralph
319
- name: '@deepseek-ai/dsh-tool-ralph'
320
- config:
321
- subagentProvider: spawn
322
- maxRounds: 64
339
+ # DISABLED 2026-09-10 (prompt-budget trim): the `workflow` schema is the
340
+ # single largest tool description in this catalog (4,067 chars) and was
341
+ # never called in 30 recorded sessions. Uncomment both rows to restore
342
+ # large multi-agent orchestration.
343
+ # - id: workflow-worker-thread
344
+ # name: '@deepseek-ai/dsh-workflow-worker-thread'
345
+ # config:
346
+ # provider: spawn
347
+ #
348
+ # - id: tool-workflow
349
+ # name: '@deepseek-ai/dsh-tool-workflow'
350
+
351
+ # DISABLED 2026-09-10 (prompt-budget trim): `ralph` was never called in 30
352
+ # recorded sessions. Uncomment to restore fresh-agent Ralph loops.
353
+ # - id: tool-ralph
354
+ # name: '@deepseek-ai/dsh-tool-ralph'
355
+ # config:
356
+ # subagentProvider: spawn
357
+ # maxRounds: 64
323
358
 
324
359
  # ── remaining model-facing rows ─────────────────────────────────────────────
325
360
 
@@ -1,3 +1,3 @@
1
1
  name: DevFlow 2.0
2
- description: DevFlow 开发工作流 + 梁神式两阶段锚定:首个请求只暴露一行 persona(You are a helpful software engineer assistant.)+ 最小工具对(持久 bash + str_replace_editor),无运行时上下文与任何注入;anchorGate 门控(首个 minimal-like 推理块或 4 步兜底)+ 1024 输出预算稳定锚定后,晋升为 Code Mode(单一 run_code),恢复完整 DevFlow 人设并自动激活 devflow-core 生命周期(Brainstorm → Spec/Cut → Plan → Build → Prove)与验证完成闭环;workspace 指令与技能目录延迟一步注入。
2
+ description: DevFlow 开发工作流 + 梁神式两阶段锚定:首个请求只暴露一行 persona(You are a helpful software engineer assistant.)+ 最小工具对(bash + str_replace_editor),无运行时上下文、无任何注入、无输出 token 上限;anchorGate 门控(首个 minimal-like 推理块或 4 步兜底)晋升后切换为 native 工具面(33 个工具,无 run_code、无 SDK 重复表达),恢复完整 DevFlow 人设并自动激活 devflow-core 生命周期(Brainstorm → Spec/Cut → Plan → Build → Prove)与验证完成闭环;workspace 指令与技能目录延迟一步注入。
3
3
  order: 6
@@ -29,10 +29,16 @@
29
29
  * `we` and no `let me`) or the `maxBootstrapSteps` fallback.
30
30
  * `promoteAfterFirstResponse` promotes a tool-less first response once it has
31
31
  * responded, and also releases an anchor-gated session when its first turn
32
- * ends (`turn/end`). With `promotedPresentation: code` the promoted catalog
33
- * is presented as Code Mode (PTC): the wire shows a single `run_code` tool
34
- * backed by the generated SDK, switched at the step boundary so the current
35
- * step's native calls are never interrupted. `deferredSources` and
32
+ * ends (`turn/end`). `promotedPresentation` is passed verbatim to
33
+ * `tools.presentAs(mode)`, so the promoted wire depends on which mode string
34
+ * the preset names: `native` = every tool schema and no SDK section; `ptc` =
35
+ * the `run_code` transport plus the generated SDK; `both`, and any other
36
+ * string — including `code`, which this preset used before 2026-09-10 and
37
+ * which `dsh-tools` falls through to the default branch — = every tool schema
38
+ * AND `run_code` AND the SDK section, i.e. one catalog paid for twice. Only
39
+ * `ptc` narrows `wireSchemas()` to the `run_code` transport. The switch
40
+ * happens at the step boundary so the current step's native calls are never
41
+ * interrupted. `deferredSources` and
36
42
  * `deferredGraceSteps` delay selected injected message kinds (workspace
37
43
  * instructions, skill catalog) for a few steps after promotion.
38
44
  *
@@ -369,20 +369,24 @@ function splitTasksV2(body) {
369
369
  }
370
370
 
371
371
  /** Validate one v2 task: files, change intent, acceptance, proof, and exclusion. */
372
- function checkTaskV2(task) {
372
+ function checkTaskV2(task, enforceGranularity = true) {
373
373
  const missing = v2TaskFields.filter((field) => !v2FieldPatterns[field].test(task.body));
374
374
  const files = v2FieldBlock(task.body, "Files");
375
375
  const change = v2FieldBlock(task.body, "Change");
376
+ const acceptance = v2FieldBlock(task.body, "Acceptance");
376
377
  const verify = v2FieldBlock(task.body, "Verify");
377
378
  const notDoing = v2FieldBlock(task.body, "Not doing");
378
379
  const fileEntries = parseFileEntries(files);
379
380
  const invalidFiles = fileEntries.filter(({ match }) => !match).map(({ line }) => line);
380
381
  const unlocatedCodeFiles = findUnlocatedCodeFiles(fileEntries);
381
382
  const unresolved = findMatches(task.body, unresolvedPatterns);
382
- const vague = findMatches([change, v2FieldBlock(task.body, "Acceptance"), verify].join("\n"), vaguePatterns);
383
+ const vague = findMatches([change, acceptance, verify].join("\n"), vaguePatterns);
383
384
  // v2 不强制精确改法;只要求 Change 说出可执行意图,具体实现归 Build。
384
385
  const missingChange = !implementationVerbPattern.test(change) || genericMechanicsPattern.test(change);
385
386
  const incompleteVerification = !hasVerificationExpectation(verify);
387
+ // 单结果保守代理:Acceptance 含分号即视为多交付单元;先剔除反引号代码片段,避免命令里的半角分号误报。语义判据("且"连接独立结果、镜像合并)在技能指南,checker 只强制可静态判定的子集。
388
+ const acceptanceText = acceptance.replace(/`[^`]*`/g, "");
389
+ const multiResult = enforceGranularity && /[;;]/.test(acceptanceText);
386
390
 
387
391
  return {
388
392
  number: task.number,
@@ -393,6 +397,7 @@ function checkTaskV2(task) {
393
397
  unlocatedCodeFiles,
394
398
  missingChange,
395
399
  incompleteVerification,
400
+ multiResult,
396
401
  missingNotDoing: !notDoing,
397
402
  ok:
398
403
  missing.length === 0 &&
@@ -402,6 +407,7 @@ function checkTaskV2(task) {
402
407
  unlocatedCodeFiles.length === 0 &&
403
408
  !missingChange &&
404
409
  !incompleteVerification &&
410
+ !multiResult &&
405
411
  Boolean(notDoing)
406
412
  };
407
413
  }
@@ -409,13 +415,15 @@ function checkTaskV2(task) {
409
415
  /** Validate the v2 plan contract: slim header, task rows, Progress table, and Cut subtraction. */
410
416
  function checkPlanV2(body) {
411
417
  const tasks = splitTasksV2(body);
412
- const taskResults = tasks.map(checkTaskV2);
413
- const missingGlobal = v2GlobalFields.filter((field) => !v2FieldPatterns[field].test(body));
414
- const cutBlock = v2FieldBlock(body, "Cut");
415
- const missingRejected = !/Rejected\s*:\s*\S/im.test(cutBlock);
416
418
  const statusMatch = body.match(statusPattern);
417
419
  const status = statusMatch ? statusMatch[1].trim() : "legacy";
418
420
  const invalidStatus = statusMatch ? !validStatuses.includes(status) : false;
421
+ // 粒度代理只在活跃计划(draft/approved/in-progress)上强制;done 计划是历史记录,不重判,避免既有 v2 计划校验回归。
422
+ const enforceGranularity = status !== "done";
423
+ const taskResults = tasks.map((task) => checkTaskV2(task, enforceGranularity));
424
+ const missingGlobal = v2GlobalFields.filter((field) => !v2FieldPatterns[field].test(body));
425
+ const cutBlock = v2FieldBlock(body, "Cut");
426
+ const missingRejected = !/Rejected\s*:\s*\S/im.test(cutBlock);
419
427
  const progress = [...body.matchAll(progressRowPattern)].map((match) => ({
420
428
  number: Number(match[1]),
421
429
  state: match[3],
@@ -806,6 +814,7 @@ function report(body, filePath, json) {
806
814
  ...task.vague.map((match) => `vague ${match}`),
807
815
  ...task.invalidFiles.map((line) => `unclassified file ${line}`),
808
816
  ...task.unlocatedCodeFiles.map((line) => `missing file symbol/anchor ${line}`),
817
+ ...(task.multiResult ? ["Acceptance lists multiple results; split the task or state one observable result"] : []),
809
818
  ...(task.missingChange ? ["Change needs an executable intent verb"] : []),
810
819
  ...(task.missingNotDoing ? ["missing Not doing exclusion"] : []),
811
820
  ...(task.incompleteVerification ? ["Verify needs command/scenario and expected result"] : [])
@@ -1046,7 +1055,7 @@ function selfTest() {
1046
1055
  "Files:",
1047
1056
  "- Modify: scripts/devflow-plan.js | symbol: `checkPlanV2` | validate the slim contract",
1048
1057
  "Change: add v2 field validation and dispatch",
1049
- "Acceptance: v2 plans pass and legacy plans keep passing",
1058
+ "Acceptance: v2 plans pass the slim contract",
1050
1059
  "Verify: run `node scripts/devflow-plan.js --self-test` expect exit 0",
1051
1060
  "Not doing: changing legacy validation",
1052
1061
  "",
@@ -1058,6 +1067,24 @@ function selfTest() {
1058
1067
  ].join("\n");
1059
1068
  if (!detectV2(validV2Plan)) throw new Error("Self-test expected v2 detection to pass");
1060
1069
  if (!checkPlan(validV2Plan).ok) throw new Error("Self-test expected valid v2 plan to pass");
1070
+ const multiResultV2Plan = validV2Plan.replace(
1071
+ "Acceptance: v2 plans pass the slim contract",
1072
+ "Acceptance: v2 plans pass;legacy plans keep passing"
1073
+ );
1074
+ if (checkPlan(multiResultV2Plan).ok) throw new Error("Self-test expected a semicolon-joined Acceptance to fail");
1075
+ const doneMultiResultV2Plan = multiResultV2Plan
1076
+ .replace("Status: approved", "Status: done")
1077
+ .replace("| 1 | Add v2 validation | todo | - |", "| 1 | Add v2 validation | done | run `node scripts/devflow-plan.js --self-test` |");
1078
+ if (!checkPlan(doneMultiResultV2Plan).ok) {
1079
+ throw new Error("Self-test expected a done plan to keep validating without granularity re-judging");
1080
+ }
1081
+ const codeSpanSemicolonV2Plan = validV2Plan.replace(
1082
+ "Acceptance: v2 plans pass the slim contract",
1083
+ "Acceptance: `node -e \"a;b\"` prints one result"
1084
+ );
1085
+ if (!checkPlan(codeSpanSemicolonV2Plan).ok) {
1086
+ throw new Error("Self-test expected a semicolon inside a code span to stay valid");
1087
+ }
1061
1088
  if (checkPlan(validV2Plan.replace(/Rejected:[^\n]*/, "Rejected:")).ok) throw new Error("Self-test expected missing Rejected to fail");
1062
1089
  if (checkPlan(validV2Plan.replace("| 1 | Add v2 validation | todo | - |", "| 1 | Add v2 validation | done | - |")).ok) {
1063
1090
  throw new Error("Self-test expected a done Progress row without evidence to fail");
@@ -1089,7 +1116,7 @@ function selfTest() {
1089
1116
  if (missingArtifact.length === 0) throw new Error("Self-test expected a missing requirement artifact to fail");
1090
1117
 
1091
1118
  console.log("DevFlow plan self-test passed");
1092
- console.log("Checked v2 and legacy plan contracts, Progress evidence, Cut Rejected, code-level fields, precise file locations, verification expectations, documentation-only exception, external-skill declaration, and plan landing guidance");
1119
+ console.log("Checked v2 and legacy plan contracts, Progress evidence, Cut Rejected, single-result Acceptance granularity, code-level fields, precise file locations, verification expectations, documentation-only exception, external-skill declaration, and plan landing guidance");
1093
1120
  }
1094
1121
 
1095
1122
  const args = process.argv.slice(2);
@@ -46,7 +46,7 @@ When saving a plan file, use `docs/plans/YYYY-MM-DD-<short-kebab-name>.md`, reso
46
46
 
47
47
  For multi-step work, tasks must cite the approved source, be small and verifiable, and follow the six-field contract (`Task` / `Files` / `Change` / `Acceptance` / `Verify` / `Not doing`) in `skills/devflow-plan/SKILL.md`. Legacy plans with `Change mechanics` and `Prewalk` stay executable under their own contract.
48
48
 
49
- No unresolved markers. `Change` states the executable intent and boundary; add exact mechanics only when the change crosses a module contract, is irreversible, or touches security or data boundaries. Otherwise Build chooses the smallest implementation and records it as Progress evidence. The verification step keeps its trigger/input, expected result, and command or manual scenario. A task whose files are all documentation paths is documentation-only. No "add tests" without naming the behavior, no "handle edge cases" without naming the edge case, no "similar to Task N" shortcuts.
49
+ No unresolved markers. `Change` states the executable intent and boundary; add exact mechanics only when the change crosses a module contract, is irreversible, touches security or data boundaries, or the mechanism cannot be inferred from the task's named anchors by a different session or model. Otherwise Build chooses the smallest implementation and records it as Progress evidence. The verification step keeps its trigger/input, expected result, and command or manual scenario. A task whose files are all documentation paths is documentation-only. No "add tests" without naming the behavior, no "handle edge cases" without naming the edge case, no "similar to Task N" shortcuts.
50
50
 
51
51
  Close each task by writing back its `## Progress` row: `doing` when starting, `done` with the command and key result when its `Verify` passes. A `done` row without evidence fails the checker. On a legacy plan without a Progress table, report the same evidence in the completion message. When every task is `done`, advance the requirement row in `docs/requirements.md` to `built`.
52
52
 
@@ -18,7 +18,7 @@ Turn an A/B `CUT_PASS`-bounded approved design or confirmed Spec into one review
18
18
  1. Read only source material, code, tests, and conventions relevant to the approved scope. Load `skills/devflow-spec/references/spec-plan-methods.md` and `skills/devflow-plan/references/plan-methods.md` before applying Plan Pack mechanics.
19
19
  2. Map the intended touch set once: list the files and the responsibility each one carries. Reuse existing modules and name the intended file operation.
20
20
  3. Do the bounded investigation needed to write correct tasks. Keep that evidence in the conversation or in a learning card; it is not a plan field.
21
- 4. Split independent deliverables into small, reviewable tasks. Each task carries `Files`, `Change`, `Acceptance`, `Verify`, and `Not doing` so an executor can act on it without reading another task.
21
+ 4. Split by delivery unit: one task = one independently verifiable deliverable and the smallest unit worth a fresh reviewer's gate — split only where a reviewer could meaningfully reject one task while approving its neighbor, and fold setup, configuration, scaffolding, and documentation into the task whose deliverable needs them. Split whenever `Acceptance` needs `且`/`and` to join two independently verifiable results; keep one task when the same rule mirrors across files — mirrored edits are one delivery unit, not one task per file. Each task carries `Files`, `Change`, `Acceptance`, `Verify`, and `Not doing` so an executor can act on it without reading another task.
22
22
  5. Write the plan using the required header, task contract, and `## Progress` table below.
23
23
  6. Self-review Cut fidelity, touch-set coverage, acceptance proof, scope exclusions, and Progress row count against the task count.
24
24
  7. Run `node scripts/devflow-plan.js <plan-file>` when the project-level checker exists. Otherwise resolve the user-level checker according to `core-methods.md` Script Path Resolution.
@@ -49,7 +49,7 @@ Files:
49
49
  - Create: <path> | new file | <responsibility>
50
50
  - Modify: <path> | <symbol or stable anchor> | <responsibility>
51
51
  - Test: <path> | <symbol or stable anchor> | <behavior proved> # only when applicable
52
- Change: <what changes and its boundary; add the smallest mechanics only when the change crosses a module contract, is irreversible, or touches security or data boundaries>
52
+ Change: <what changes and its boundary; add the smallest mechanics (pseudocode, exact replacement, or key fragment) only when the change crosses a module contract, is irreversible, touches security or data boundaries, or the mechanism cannot be inferred from the task's named anchors by a different session or model>
53
53
  Acceptance: <specific observable condition>
54
54
  Verify: <exact command or manual scenario, trigger/input, and expected result>
55
55
  Not doing: <scope excluded by this task>
@@ -77,15 +77,15 @@ Files:
77
77
  - Create: <path> | new file | <responsibility>
78
78
  - Modify: <path> | <symbol or stable anchor> | <responsibility>
79
79
  - Test: <path> | <symbol or stable anchor> | <behavior proved> # only when applicable
80
- Change: <what changes and its boundary; exact mechanics only when the change crosses a module contract, is irreversible, or touches security or data boundaries>
80
+ Change: <what changes and its boundary; exact mechanics (pseudocode, exact replacement, or key fragment) only when the change crosses a module contract, is irreversible, touches security or data boundaries, or the mechanism cannot be inferred from the task's named anchors by a different session or model>
81
81
  Acceptance: <specific observable condition>
82
82
  Verify: <exact command or manual scenario, trigger/input, and expected result>
83
83
  Not doing: <scope excluded by this task>
84
84
  ```
85
85
 
86
- Use only `Create`, `Modify`, and `Test` file-operation labels. `Create` rows use `new file`; every other row names a symbol or stable anchor. `Change` states the executable intent and its boundary in one or two lines; it does not restate current behavior, target behavior, call impact, or interfaces unless the task changes a cross-module contract. The Plan no longer classifies tasks by `Task type`: a task whose files are all documentation paths is documentation-only, and the checker treats it that way.
86
+ Use only `Create`, `Modify`, and `Test` file-operation labels. `Create` rows use `new file`; every other row names a symbol or stable anchor. `Change` states the executable intent and its boundary in one or two lines; it does not restate current behavior, target behavior, call impact, or interfaces unless the task changes a cross-module contract. When the mechanism cannot be inferred from the task's named anchors — the common case when a different session or model executes the plan — `Change` carries the smallest runnable mechanics (pseudocode, exact replacement, or key fragment) so the executor acts without the author's session context. The Plan no longer classifies tasks by `Task type`: a task whose files are all documentation paths is documentation-only, and the checker treats it that way.
87
87
 
88
- Six fields per task is the whole contract: ordering, the touch set, the intent, the acceptance condition, the proof command, and the exclusion. Investigation traces, handoff facts, per-task worklists, architecture, tech stack, spec coverage, and comment locations are owned by other nodes or stay in the conversation. `Prewalk`, `File Structure`, `Interfaces`, `Current behavior`, `Target behavior`, `Change mechanics`, `Call impact`, and `Comments` are not part of the v2 contract; a plan that still carries them is treated as legacy.
88
+ Six fields per task is the whole contract: ordering, the touch set, the intent, the acceptance condition, the proof command, and the exclusion. `Acceptance` states one observable result; a `;`/`;`-joined multi-result acceptance is a split signal and the checker fails it while the plan is active. Plan length has no fixed total line cap: it grows with the number of delivery units while every task keeps the six-field, one-result shape. Investigation traces, handoff facts, per-task worklists, architecture, tech stack, spec coverage, and comment locations are owned by other nodes or stay in the conversation. `Prewalk`, `File Structure`, `Interfaces`, `Current behavior`, `Target behavior`, `Change mechanics`, `Call impact`, and `Comments` are not part of the v2 contract; a plan that still carries them is treated as legacy.
89
89
 
90
90
  Keep one task understandable on its own. Do not use cross-task shorthand, generic test additions, unnamed edge cases, or cleanup entries. Name a test file only when the stated behavior needs one.
91
91
 
@@ -103,6 +103,8 @@ Plan generation does not repeat Cut, perform Build or Prove, prescribe independe
103
103
  | "The checker proves the architecture." | It proves structure only; the author must review scope and design consistency. |
104
104
  | "The plan is approved, so Cut can be skipped." | Plan generation requires an existing `CUT_PASS`; it cannot replace the earlier reuse and scope decision. |
105
105
  | "The task details can broaden the solution." | If a task exceeds the Cut Decision, return the scope-drift facts to `devflow-core`; do not directly enter Build. |
106
+ | "Two results can share one task when they ship together." | Two independently verifiable results are two delivery units; split the task or reduce `Acceptance` to one observable result. |
107
+ | "A different session or model will figure out the how." | If the mechanism cannot be inferred from the task's named anchors, `Change` must carry the smallest runnable mechanics; otherwise the handoff stalls. |
106
108
 
107
109
  ## Verification
108
110
 
@@ -114,6 +116,8 @@ Before leaving this skill, confirm:
114
116
  - [ ] Approved design or saved spec is cited as optional `Source`.
115
117
  - [ ] Header, tasks, and `## Progress` match the v2 contract; each task has six fields and no legacy field.
116
118
  - [ ] Every task is independently understandable and has no unresolved or vague placeholder.
119
+ - [ ] Each task is exactly one delivery unit: no `;`/`;`-joined `Acceptance`, and a mirrored rule was not split per file.
120
+ - [ ] Every task is executable by a different session or model from its six fields plus named anchors: a non-inferable mechanism carries the smallest runnable mechanics.
117
121
  - [ ] Progress row count equals task count; every `done` row carries evidence.
118
122
  - [ ] The checker passed when available.
119
123
  - [ ] The user reviewed the written plan.
@@ -8,6 +8,17 @@ State the intended touch set once, before tasks: the files and the responsibilit
8
8
 
9
9
  The v2 Plan Pack has no `File Structure` table. The per-task `Files` rows are the touch set; a global table would only restate them.
10
10
 
11
+ ## Delivery Unit
12
+
13
+ Split by delivery unit, not by file or by step: one task = one independently verifiable deliverable.
14
+
15
+ - Splitting test: if `Acceptance` needs `且`/`and` to join two independently verifiable results, they are two delivery units — split them.
16
+ - Mirror test: the same rule edited across several files is one delivery unit; do not split it per file.
17
+ - Right-sizing test: a task is the smallest unit worth a fresh reviewer's gate — split only where a reviewer could meaningfully reject one task while approving its neighbor; fold setup, configuration, scaffolding, and documentation into the task whose deliverable needs them.
18
+ - `Acceptance` states one observable result. The checker fails a v2 task whose `Acceptance` contains a `;`/`;` separator outside backtick code spans as the conservative static proxy for this rule; it applies while the plan is active (draft/approved/in-progress), while done plans stay historical records. The semantic tests above still govern mirrored and independently verifiable cases.
19
+ - Handoff density: when the mechanism cannot be inferred from the task's named anchors — a different session or model executes the plan — `Change` carries the smallest runnable mechanics (pseudocode, exact replacement, or key fragment); otherwise it stays intent plus boundary.
20
+ - No fixed total line cap: plan length grows with the number of delivery units; the six-field shape keeps each task slim.
21
+
11
22
  ## Task Rows
12
23
 
13
24
  Each task carries exactly six fields:
@@ -21,7 +32,7 @@ Verify: <command or manual scenario with trigger, input, and expected result>
21
32
  Not doing: <scope excluded by this task>
22
33
  ```
23
34
 
24
- `Change` states the executable intent. Add exact mechanics only when the change crosses a module contract, is irreversible, or touches security or data boundaries. Otherwise the executor chooses the smallest implementation inside the task boundary. This is the deliberate trade: the plan stops pre-deciding every edit, and Build regains bounded implementation authority. That trade is the fix for the bloated-plan problem, not a relaxation of proof.
35
+ `Change` states the executable intent. Add exact mechanics (pseudocode, exact replacement, or key fragment) only when the change crosses a module contract, is irreversible, touches security or data boundaries, or the mechanism cannot be inferred from the task's named anchors by a different session or model. Otherwise the executor chooses the smallest implementation inside the task boundary. This is the deliberate trade: the plan stops pre-deciding every edit, and Build regains bounded implementation authority. That trade is the fix for the bloated-plan problem, not a relaxation of proof.
25
36
 
26
37
  Investigation evidence does not belong in the plan. Keep it in the conversation, or in a `.copilot/cards/` learning card when it is reusable across tasks.
27
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-core/dsh-devflow",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "DevFlow for DeepSeek Harness: devflow-2 agent preset + skills + commands + verification scripts, synced into ~/.dsh on host startup.",
5
5
  "type": "module",
6
6
  "engines": {