sillyspec 3.27.5 → 3.27.6

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.
Files changed (194) hide show
  1. package/.worktrees/8d2d0392/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  2. package/.worktrees/8d2d0392/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  3. package/.worktrees/8d2d0392/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  4. package/.worktrees/8d2d0392/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  5. package/.worktrees/8d2d0392/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  6. package/.worktrees/8d2d0392/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  7. package/.worktrees/8d2d0392/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  8. package/.worktrees/8d2d0392/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  9. package/.worktrees/8d2d0392/.claude/skills/sillyspec-export/SKILL.md +57 -0
  10. package/.worktrees/8d2d0392/.claude/skills/sillyspec-init/SKILL.md +177 -0
  11. package/.worktrees/8d2d0392/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  12. package/.worktrees/8d2d0392/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  13. package/.worktrees/8d2d0392/.claude/skills/sillyspec-propose/SKILL.md +87 -0
  14. package/.worktrees/8d2d0392/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  15. package/.worktrees/8d2d0392/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  16. package/.worktrees/8d2d0392/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  17. package/.worktrees/8d2d0392/.claude/skills/sillyspec-state/SKILL.md +64 -0
  18. package/.worktrees/8d2d0392/.claude/skills/sillyspec-status/SKILL.md +54 -0
  19. package/.worktrees/8d2d0392/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  20. package/.worktrees/8d2d0392/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  21. package/.worktrees/8d2d0392/.claude/skills/verify-per-user/SKILL.md +6 -0
  22. package/.worktrees/8d2d0392/.gitattributes +2 -0
  23. package/.worktrees/8d2d0392/.husky/pre-push +20 -0
  24. package/.worktrees/8d2d0392/README.md +203 -0
  25. package/.worktrees/8d2d0392/SKILL.md +91 -0
  26. package/.worktrees/8d2d0392/architecture-analysis.md +448 -0
  27. package/.worktrees/8d2d0392/bin/sillyspec.js +2 -0
  28. package/.worktrees/8d2d0392/eval/README.md +86 -0
  29. package/.worktrees/8d2d0392/eval/eval.config.json +28 -0
  30. package/.worktrees/8d2d0392/eval/lib/adapters/cli.mjs +63 -0
  31. package/.worktrees/8d2d0392/eval/lib/adapters/dryrun.mjs +27 -0
  32. package/.worktrees/8d2d0392/eval/lib/config.mjs +82 -0
  33. package/.worktrees/8d2d0392/eval/lib/db.mjs +76 -0
  34. package/.worktrees/8d2d0392/eval/lib/report.mjs +120 -0
  35. package/.worktrees/8d2d0392/eval/lib/runner.mjs +157 -0
  36. package/.worktrees/8d2d0392/eval/lib/tasks.mjs +73 -0
  37. package/.worktrees/8d2d0392/eval/lib/util.mjs +107 -0
  38. package/.worktrees/8d2d0392/eval/lib/workdir.mjs +30 -0
  39. package/.worktrees/8d2d0392/eval/run.mjs +83 -0
  40. package/.worktrees/8d2d0392/eval/tasks/001-slugify/files/package.json +6 -0
  41. package/.worktrees/8d2d0392/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  42. package/.worktrees/8d2d0392/eval/tasks/001-slugify/task.json +6 -0
  43. package/.worktrees/8d2d0392/eval/tasks/001-slugify/verify.mjs +28 -0
  44. package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/package.json +6 -0
  45. package/.worktrees/8d2d0392/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  46. package/.worktrees/8d2d0392/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  47. package/.worktrees/8d2d0392/eval/tasks/002-each-day/task.json +6 -0
  48. package/.worktrees/8d2d0392/eval/tasks/002-each-day/verify.mjs +31 -0
  49. package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  50. package/.worktrees/8d2d0392/eval/tasks/003-word-report/files/package.json +6 -0
  51. package/.worktrees/8d2d0392/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  52. package/.worktrees/8d2d0392/eval/tasks/003-word-report/task.json +6 -0
  53. package/.worktrees/8d2d0392/eval/tasks/003-word-report/verify.mjs +38 -0
  54. package/.worktrees/8d2d0392/logo.jpg +0 -0
  55. package/.worktrees/8d2d0392/package-lock.json +977 -0
  56. package/.worktrees/8d2d0392/package.json +41 -0
  57. package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  58. package/.worktrees/8d2d0392/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  59. package/.worktrees/8d2d0392/packages/dashboard/dist/favicon.jpg +0 -0
  60. package/.worktrees/8d2d0392/packages/dashboard/dist/index.html +16 -0
  61. package/.worktrees/8d2d0392/packages/dashboard/dist/logo.jpg +0 -0
  62. package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-dashboard.html +836 -0
  63. package/.worktrees/8d2d0392/packages/dashboard/dist/prototype-overview.html +256 -0
  64. package/.worktrees/8d2d0392/packages/dashboard/package.json +25 -0
  65. package/.worktrees/8d2d0392/packages/dashboard/server/executor.js +95 -0
  66. package/.worktrees/8d2d0392/packages/dashboard/server/index.js +679 -0
  67. package/.worktrees/8d2d0392/packages/dashboard/server/parser.js +526 -0
  68. package/.worktrees/8d2d0392/packages/dashboard/server/watcher.js +357 -0
  69. package/.worktrees/8d2d0392/src/agent-session-log.js +803 -0
  70. package/.worktrees/8d2d0392/src/change-list.js +290 -0
  71. package/.worktrees/8d2d0392/src/change-risk-profile.js +339 -0
  72. package/.worktrees/8d2d0392/src/check-primitives.js +65 -0
  73. package/.worktrees/8d2d0392/src/classify-change.js +109 -0
  74. package/.worktrees/8d2d0392/src/commit-suggest.js +202 -0
  75. package/.worktrees/8d2d0392/src/config-cat.js +88 -0
  76. package/.worktrees/8d2d0392/src/config-schema.js +328 -0
  77. package/.worktrees/8d2d0392/src/constants.js +97 -0
  78. package/.worktrees/8d2d0392/src/contract-matrix.js +533 -0
  79. package/.worktrees/8d2d0392/src/datetime.js +20 -0
  80. package/.worktrees/8d2d0392/src/db-engine.js +55 -0
  81. package/.worktrees/8d2d0392/src/db.js +381 -0
  82. package/.worktrees/8d2d0392/src/decision-distill.js +517 -0
  83. package/.worktrees/8d2d0392/src/dispatch/backends/local-agent.js +155 -0
  84. package/.worktrees/8d2d0392/src/dispatch/backends/sillyhub-mcp.js +245 -0
  85. package/.worktrees/8d2d0392/src/dispatch/probe.js +246 -0
  86. package/.worktrees/8d2d0392/src/dispatch/strategy.js +121 -0
  87. package/.worktrees/8d2d0392/src/docs-check.js +825 -0
  88. package/.worktrees/8d2d0392/src/docs-debt.js +393 -0
  89. package/.worktrees/8d2d0392/src/docs-gate.js +107 -0
  90. package/.worktrees/8d2d0392/src/doctor-diagnostics.js +1099 -0
  91. package/.worktrees/8d2d0392/src/endpoint-extractor.js +409 -0
  92. package/.worktrees/8d2d0392/src/foreign-declared.js +78 -0
  93. package/.worktrees/8d2d0392/src/fs-atomic.js +73 -0
  94. package/.worktrees/8d2d0392/src/git-helper.js +150 -0
  95. package/.worktrees/8d2d0392/src/hooks/claude-pre-tool-use.cjs +130 -0
  96. package/.worktrees/8d2d0392/src/hooks/worktree-guard.js +892 -0
  97. package/.worktrees/8d2d0392/src/index.js +2854 -0
  98. package/.worktrees/8d2d0392/src/init.js +626 -0
  99. package/.worktrees/8d2d0392/src/knowledge-match.js +231 -0
  100. package/.worktrees/8d2d0392/src/local-detect.js +128 -0
  101. package/.worktrees/8d2d0392/src/local-register.js +104 -0
  102. package/.worktrees/8d2d0392/src/machine-interface.js +543 -0
  103. package/.worktrees/8d2d0392/src/migrate.js +127 -0
  104. package/.worktrees/8d2d0392/src/module-impact.js +166 -0
  105. package/.worktrees/8d2d0392/src/module-resolve.js +222 -0
  106. package/.worktrees/8d2d0392/src/modules.js +580 -0
  107. package/.worktrees/8d2d0392/src/plan-adopt-waves.js +126 -0
  108. package/.worktrees/8d2d0392/src/progress/change-registry.js +336 -0
  109. package/.worktrees/8d2d0392/src/progress/consistency-doctor.js +355 -0
  110. package/.worktrees/8d2d0392/src/progress/shared.js +36 -0
  111. package/.worktrees/8d2d0392/src/progress/stage-machine.js +594 -0
  112. package/.worktrees/8d2d0392/src/progress/step-store.js +265 -0
  113. package/.worktrees/8d2d0392/src/progress.js +1304 -0
  114. package/.worktrees/8d2d0392/src/quick-recommend.js +115 -0
  115. package/.worktrees/8d2d0392/src/quicklog.js +883 -0
  116. package/.worktrees/8d2d0392/src/review-tier.js +77 -0
  117. package/.worktrees/8d2d0392/src/run/command.js +1641 -0
  118. package/.worktrees/8d2d0392/src/run/complete-handlers.js +1552 -0
  119. package/.worktrees/8d2d0392/src/run/complete.js +1259 -0
  120. package/.worktrees/8d2d0392/src/run/concurrent-detect.js +156 -0
  121. package/.worktrees/8d2d0392/src/run/gates.js +1094 -0
  122. package/.worktrees/8d2d0392/src/run/multi-repo-context.js +208 -0
  123. package/.worktrees/8d2d0392/src/run/next.js +128 -0
  124. package/.worktrees/8d2d0392/src/run/prompt.js +843 -0
  125. package/.worktrees/8d2d0392/src/run/quick-audit.js +186 -0
  126. package/.worktrees/8d2d0392/src/run/scan-profile.js +269 -0
  127. package/.worktrees/8d2d0392/src/run/shared.js +1562 -0
  128. package/.worktrees/8d2d0392/src/run/stage.js +587 -0
  129. package/.worktrees/8d2d0392/src/run.js +23 -0
  130. package/.worktrees/8d2d0392/src/scan-diff.js +388 -0
  131. package/.worktrees/8d2d0392/src/scan-postcheck.js +524 -0
  132. package/.worktrees/8d2d0392/src/scan-staleness.js +95 -0
  133. package/.worktrees/8d2d0392/src/setup.js +414 -0
  134. package/.worktrees/8d2d0392/src/sillyhub-mcp/client.js +597 -0
  135. package/.worktrees/8d2d0392/src/sillyhub-mcp/config.js +62 -0
  136. package/.worktrees/8d2d0392/src/spec-dir-typo.js +50 -0
  137. package/.worktrees/8d2d0392/src/spec-sync.js +358 -0
  138. package/.worktrees/8d2d0392/src/stage-contract-engine.js +171 -0
  139. package/.worktrees/8d2d0392/src/stage-contract-spec.js +551 -0
  140. package/.worktrees/8d2d0392/src/stage-contract.js +948 -0
  141. package/.worktrees/8d2d0392/src/stage-review.js +597 -0
  142. package/.worktrees/8d2d0392/src/stages/archive.js +199 -0
  143. package/.worktrees/8d2d0392/src/stages/brainstorm-auto.js +285 -0
  144. package/.worktrees/8d2d0392/src/stages/brainstorm.js +624 -0
  145. package/.worktrees/8d2d0392/src/stages/cmd-existence.js +145 -0
  146. package/.worktrees/8d2d0392/src/stages/doctor.js +540 -0
  147. package/.worktrees/8d2d0392/src/stages/execute.js +1277 -0
  148. package/.worktrees/8d2d0392/src/stages/explore.js +43 -0
  149. package/.worktrees/8d2d0392/src/stages/index.js +29 -0
  150. package/.worktrees/8d2d0392/src/stages/knowledge.js +514 -0
  151. package/.worktrees/8d2d0392/src/stages/plan-postcheck.js +1398 -0
  152. package/.worktrees/8d2d0392/src/stages/plan.js +651 -0
  153. package/.worktrees/8d2d0392/src/stages/quick.js +167 -0
  154. package/.worktrees/8d2d0392/src/stages/scan.js +608 -0
  155. package/.worktrees/8d2d0392/src/stages/status.js +64 -0
  156. package/.worktrees/8d2d0392/src/stages/verify.js +246 -0
  157. package/.worktrees/8d2d0392/src/sync.js +1470 -0
  158. package/.worktrees/8d2d0392/src/task-review.js +1498 -0
  159. package/.worktrees/8d2d0392/src/taskcard-placeholders.js +21 -0
  160. package/.worktrees/8d2d0392/src/taskcard.js +217 -0
  161. package/.worktrees/8d2d0392/src/verify-postcheck.js +1718 -0
  162. package/.worktrees/8d2d0392/src/verify-probes.js +327 -0
  163. package/.worktrees/8d2d0392/src/version.js +19 -0
  164. package/.worktrees/8d2d0392/src/workflow.js +600 -0
  165. package/.worktrees/8d2d0392/src/workspace.js +125 -0
  166. package/.worktrees/8d2d0392/src/worktree-apply.js +1611 -0
  167. package/.worktrees/8d2d0392/src/worktree-deps.js +576 -0
  168. package/.worktrees/8d2d0392/src/worktree.js +1838 -0
  169. package/.worktrees/8d2d0392/templates/claude-instruction.md +23 -0
  170. package/.worktrees/8d2d0392/templates/prompts/taskcard-rules.md +29 -0
  171. package/.worktrees/8d2d0392/templates/prompts/testcase-design.md +12 -0
  172. package/.worktrees/8d2d0392/templates/prompts/verify-probes.md +32 -0
  173. package/.worktrees/8d2d0392/templates/workflows/archive-impact.yaml +79 -0
  174. package/.worktrees/8d2d0392/templates/workflows/scan-docs.yaml +132 -0
  175. package/architecture-analysis.md +448 -0
  176. package/package.json +1 -1
  177. package/src/contract-matrix.js +1 -1
  178. package/src/endpoint-extractor.js +41 -52
  179. package/src/foreign-declared.js +78 -1
  180. package/src/index.js +9 -7
  181. package/src/run/command.js +2 -1
  182. package/src/run/gates.js +17 -0
  183. package/src/run/stage.js +8 -5
  184. package/src/stage-contract-spec.js +1 -1
  185. package/src/stages/brainstorm-auto.js +43 -1
  186. package/src/stages/brainstorm.js +2 -0
  187. package/src/stages/plan.js +8 -4
  188. package/src/stages/verify.js +1 -1
  189. package/src/taskcard.js +33 -0
  190. package/src/verify-probes.js +27 -6
  191. package/src/worktree-apply.js +451 -286
  192. package/src/worktree-deps.js +93 -1
  193. package/src/worktree.js +84 -19
  194. package/templates/prompts/taskcard-rules.md +1 -1
@@ -0,0 +1,21 @@
1
+ /**
2
+ * taskcard-placeholders.js — TaskCard 骨架占位符标记清单(叶子模块,无 import)。
3
+ *
4
+ * 坑 taskcard-placeholder-slip(2026-08-24 用户实证:task-03/04/06/07 留成空骨架直到人工
5
+ * 审计才发现——validatePlanFeasibility 只查字段存在性/非空,占位值全过)。标记是
6
+ * buildTaskcardSkeleton 生成的封闭集合,精确匹配零误伤;独立成叶子模块供骨架生成
7
+ * (taskcard.js)与 plan-postcheck 校验两侧同源 import——放 taskcard.js 会与
8
+ * plan-postcheck → taskcard → stages/plan → stages/index 形成 ESM 循环(TDZ 实证)。
9
+ *
10
+ * verify 字段的 `cd frontend && pnpm exec tsc --noEmit` 刻意不收——它可能是真实命令
11
+ * (前端项目 tsc 检查),拦了反而误伤;其余标记不可能出现在已填充卡里。
12
+ */
13
+ export const TASKCARD_PLACEHOLDERS = [
14
+ { field: 'requirement_ids', marker: 'FR-XX' },
15
+ { field: 'decision_ids', marker: 'D-XXX' },
16
+ { field: 'allowed_paths', marker: 'src/example/file.ts' },
17
+ { field: 'goal', marker: '一句话说明这个 task' },
18
+ { field: 'implementation', marker: '具体步骤 1' },
19
+ { field: 'acceptance', marker: '可验证的验收条件 1' },
20
+ { field: 'constraints', marker: '边界约束 1' },
21
+ ]
@@ -0,0 +1,217 @@
1
+ /**
2
+ * sillyspec taskcard — 生成 Windows 安全的 TaskCard 骨架
3
+ *
4
+ * 背景:plan postcheck 任务卡被拒的根因集中在「LLM 子代理手写 frontmatter 不可靠」——
5
+ * CRLF 行尾、漏闭合 ---、缺 constraints 等硬校验字段(校验侧已对 CRLF 容错,但闭合/字段
6
+ * 缺失仍拦)。本命令从源头消灭这类格式错误:骨架由 CLI 用 writeFileSync 直写(字符串
7
+ * 只含 \n,天然 LF),frontmatter 闭合与硬校验 9 字段由代码保证,子代理只需 Edit 填充
8
+ * 占位符,不再手写整卡。
9
+ *
10
+ * 任务编号/标题从 plan.md checkbox 行自动带出(与 plan.js parseTaskNames 同源,
11
+ * 保编号不跳号、名称与 plan.md 一致)。
12
+ */
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
14
+ import { join } from 'path'
15
+ import { git } from './git-helper.js'
16
+ import { nowWallClock } from './datetime.js'
17
+ import { assertSafeChangeName, resolveSpecDir } from './run/shared.js'
18
+ import { parseTaskNames, readTaskRegistryContent } from './stages/plan.js'
19
+ import { parseTaskRegistry } from './stages/execute.js'
20
+
21
+ /**
22
+ * 任务号归一:接受 task-01 / task-1 / 01 / 1,统一为 task-NN(两位补零,与任务卡文件名一致)。
23
+ * @param {string} raw
24
+ * @returns {string} 形如 task-01
25
+ */
26
+ export function normalizeTaskId(raw) {
27
+ const s = String(raw || '').trim()
28
+ const m = s.match(/^(?:task-)?(\d+)$/i)
29
+ if (!m) {
30
+ throw new Error(`任务号格式非法: "${s}"(应为 task-NN 或 NN,可逗号分隔多个)`)
31
+ }
32
+ return `task-${String(m[1]).padStart(2, '0')}`
33
+ }
34
+
35
+ // 占位符标记清单移至叶子模块 taskcard-placeholders.js(本文件与 plan-postcheck 两侧同源;
36
+ // 留在此处会与 plan-postcheck → taskcard → stages/plan → stages/index 形成 ESM 循环 TDZ),
37
+ // 此处 re-export 保持既有 import 兼容(test/taskcard.test.mjs 等直接从本模块取)。
38
+ export { TASKCARD_PLACEHOLDERS } from './taskcard-placeholders.js'
39
+
40
+ /**
41
+ * YAML 标量安全序列化(坑 taskcard-title-backtick-yaml,2026-08-20 实证):plan checkbox 行名
42
+ * 是自由文本,带反引号(如 "`-p` 参数支持")时裸插值进 frontmatter——`` ` `` 与 @ 是 YAML 保留
43
+ * 指示符,js-yaml 直接抛错,契约字段(allowed_paths/constraints 等)随整个 frontmatter 静默丢失,
44
+ * 靠 plan postcheck 才兜住。统一单引号包裹 + 内部 ' 双写(YAML 标准转义),无边界字符要判。
45
+ * @param {string} v
46
+ * @returns {string}
47
+ */
48
+ function yamlScalar(v) {
49
+ const s = String(v ?? '')
50
+ return `'${s.replace(/'/g, "''")}'`
51
+ }
52
+
53
+ /**
54
+ * 生成 TaskCard 骨架字符串(纯函数)。
55
+ *
56
+ * 硬校验 9 字段(plan-postcheck feasibility)+ 规范字段全部就位,占位符由子代理 Edit 填充;
57
+ * 可选字段(provides/expects_from/related_tests)默认不生成——缩小 YAML 出错面,何时加的
58
+ * 判据写在文件尾注释(规则详情见 templates/prompts/taskcard-rules.md)。
59
+ * 字符串只含 \n:writeFileSync 直写不经任何行尾转换,Windows 下天然 LF 安全。
60
+ * title/title_zh/author 经 yamlScalar 转义(自由文本防 YAML 指示符炸解析)。
61
+ *
62
+ * @param {{ taskId: string, title: string, titleZh: string, author: string, now: string,
63
+ * dependsOn?: string[], sets?: Record<string,string> }} p
64
+ * dependsOn:tasks.md 行内注解反填(坑 taskcard-design-field-conflicts,2026-08-21 实证)——
65
+ * "(depends_on: task-01,02)" 注解由 CLI 解析进 frontmatter,省主代理逐卡裁决
66
+ * sets:--set key=value 覆盖(yamlScalar 转义;仅覆盖已存在的标量键,防注入任意结构)
67
+ * @returns {string}
68
+ */
69
+ export function buildTaskcardSkeleton({ taskId, title, titleZh, author, now, dependsOn = [], sets = {} }) {
70
+ const fm = {
71
+ id: taskId,
72
+ title: yamlScalar(title),
73
+ title_zh: yamlScalar(titleZh),
74
+ author: yamlScalar(author),
75
+ created_at: now,
76
+ priority: 'P0',
77
+ depends_on: (Array.isArray(dependsOn) && dependsOn.length > 0)
78
+ ? `[${dependsOn.map(d => `'${d}'`).join(', ')}]`
79
+ : '[]',
80
+ blocks: '[]',
81
+ requirement_ids: '[FR-XX]',
82
+ decision_ids: '[D-XXX@vN]',
83
+ }
84
+ // --set 覆盖(白名单键 + yamlScalar 转义值;depends_on 数组形态单独拼不进标量 map,跳过)
85
+ const SETTABLE_KEYS = new Set(['id', 'title', 'title_zh', 'author', 'priority', 'created_at', 'requirement_ids', 'decision_ids', 'blocks', 'goal'])
86
+ for (const [k, v] of Object.entries(sets || {})) {
87
+ if (SETTABLE_KEYS.has(k)) fm[k] = yamlScalar(v)
88
+ }
89
+ return `---
90
+ id: ${fm.id}
91
+ title: ${fm.title}
92
+ title_zh: ${fm.title_zh}
93
+ author: ${fm.author}
94
+ created_at: ${fm.created_at}
95
+ priority: ${fm.priority}
96
+ depends_on: ${fm.depends_on}
97
+ blocks: ${fm.blocks}
98
+ requirement_ids: ${fm.requirement_ids}
99
+ decision_ids: ${fm.decision_ids}
100
+ allowed_paths:
101
+ - src/example/file.ts
102
+ goal: >
103
+ 一句话说明这个 task 要做什么、为什么。
104
+ implementation:
105
+ - 具体步骤 1
106
+ - 具体步骤 2
107
+ acceptance:
108
+ - 可验证的验收条件 1
109
+ - 可验证的验收条件 2
110
+ verify:
111
+ - cd frontend && pnpm exec tsc --noEmit
112
+ constraints:
113
+ - 边界约束 1(如:不加测试)
114
+ - 边界约束 2(如:不修改传入参数)
115
+ ---
116
+
117
+ <!-- 骨架由 sillyspec taskcard 生成(LF 行尾 + frontmatter 已闭合 + 硬校验 9 字段齐全)。
118
+ 用 Edit tool 填充上方占位符(allowed_paths/goal/implementation/acceptance/verify/constraints 等),
119
+ 勿用 Write 整文件重写——会引入 CRLF 行尾/漏闭合 ---/漏字段回归。
120
+ ⚠️ plan --done 硬校验会拦截未替换的占位符(FR-XX / D-XXX / src/example/file.ts /
121
+ 一句话说明这个 task / 具体步骤 1 / 可验证的验收条件 1 / 边界约束 1)——占位符视同缺字段。
122
+ 可选字段按需插进上方 frontmatter(规则见 taskcard-rules):
123
+ provides: 仅当本 task 给其他 task 提供接口/DTO/响应时填
124
+ expects_from: 仅当本 task 消费其他 task 的契约时填
125
+ related_tests: 仅当本 task 改动导致既有测试断言失效时填(测试路径须同时进 allowed_paths) -->
126
+ `
127
+ }
128
+
129
+ /**
130
+ * taskcard 命令主逻辑:定位变更目录 → 解析任务清单(--task 显式列表 / 'all' 从 plan.md 取)→
131
+ * 逐个写骨架。
132
+ *
133
+ * @param {string} changeName 变更名(.sillyspec/changes/ 下目录名)
134
+ * @param {{ cwd: string, specDir?: string|null, taskIds: string[]|'all', title?: string|null,
135
+ * titleZh?: string|null, force?: boolean }} opts
136
+ * taskIds:normalizeTaskId 归一后的数组;或字面 'all' 表示从 plan.md 取全部 checkbox 任务
137
+ * @returns {{ created: string[], skipped: string[], tasksDir: string }}
138
+ */
139
+ export function cmdTaskcard(changeName, opts = {}) {
140
+ const { cwd, specDir = null, taskIds, title = null, titleZh = null, force = false, sets = {} } = opts
141
+ assertSafeChangeName(changeName, '变更名')
142
+
143
+ const changeDir = join(resolveSpecDir(cwd, { specDir }), 'changes', changeName)
144
+ if (!existsSync(changeDir)) {
145
+ throw new Error(`变更目录不存在: ${changeDir}(先完成 plan 前置步骤,或检查 --change 名)`)
146
+ }
147
+
148
+ // 任务注册表 tasks.md(2026-08-20-task-truth-unify 唯一真相;CRLF 归一后解析,
149
+ // --all 时强依赖)。tasks.md 缺失回退 plan.md(旧归档变更兼容读侧)。
150
+ // parseTaskRegistry(坑 taskcard-design-field-conflicts,2026-08-21):除编号/标题外还带出
151
+ // 行内注解([model:xxx] / (depends_on: task-01,02))——depends_on 直接反填进骨架 frontmatter。
152
+ let registry = []
153
+ const tasksMdPath = join(changeDir, 'tasks.md')
154
+ const planPath = join(changeDir, 'plan.md')
155
+ const registryPath = existsSync(tasksMdPath) ? tasksMdPath : planPath
156
+ if (existsSync(registryPath)) {
157
+ const registryContent = readFileSync(registryPath, 'utf8').replace(/\r\n/g, '\n')
158
+ registry = parseTaskRegistry(registryContent)
159
+ }
160
+
161
+ let ids
162
+ if (taskIds === 'all') {
163
+ if (registry.length === 0) {
164
+ throw new Error(
165
+ `--all 需要 tasks.md 中存在 checkbox 任务行(格式 "- [ ] task-XX: 任务名")` +
166
+ (existsSync(registryPath) ? ',当前任务清单未解析到任何任务行' : ',且变更目录下未找到 tasks.md/plan.md')
167
+ )
168
+ }
169
+ ids = registry.map(t => t.id)
170
+ } else if (Array.isArray(taskIds) && taskIds.length > 0) {
171
+ ids = taskIds
172
+ } else {
173
+ throw new Error('缺少任务清单:需 --task task-01[,task-02...] 或 --all')
174
+ }
175
+
176
+ // 标题优先级:--title/--title-zh 参数 > plan.md checkbox 行名称 > 占位符。
177
+ // 参数仅单任务时可用(多任务各卡标题不同,统一参数必错)。
178
+ if ((title || titleZh) && ids.length > 1) {
179
+ throw new Error('--title/--title-zh 仅在 --task 指定单个任务时可用(多任务标题自动取自 plan.md checkbox 行)')
180
+ }
181
+
182
+ const author = (() => {
183
+ try {
184
+ // QUAL-01 收口:原 execSync 字符串拼接(经 shell,违反自家注入规约)→ git-helper 数组形式
185
+ return git(cwd, ['config', 'user.name']) || 'unknown'
186
+ } catch {
187
+ return 'unknown'
188
+ }
189
+ })()
190
+ // 本地墙钟(坑 taskcard-created-at-utc:toISOString 落 UTC,本地 09:39 写 01:39,子代理手工改两次)
191
+ const now = nowWallClock()
192
+
193
+ const tasksDir = join(changeDir, 'tasks')
194
+ mkdirSync(tasksDir, { recursive: true })
195
+
196
+ const created = []
197
+ const skipped = []
198
+ for (const id of ids) {
199
+ const reg = registry.find(t => t.id === id)
200
+ const finalTitle = title || (reg ? reg.name : null) || `${id} fill-english-title`
201
+ const finalTitleZh = titleZh || (reg ? reg.name : null) || finalTitle
202
+ const filePath = join(tasksDir, `${id}.md`)
203
+ if (existsSync(filePath) && !force) {
204
+ skipped.push(filePath)
205
+ continue
206
+ }
207
+ writeFileSync(filePath, buildTaskcardSkeleton({
208
+ taskId: id, title: finalTitle, titleZh: finalTitleZh, author, now,
209
+ // depends_on 从 tasks.md 行内注解反填(坑 taskcard-design-field-conflicts):注册表
210
+ // 注解与 plan/design 的依赖声明同源,骨架直接带上省主代理逐卡转录
211
+ dependsOn: reg ? reg.dependsOn : [],
212
+ sets,
213
+ }), 'utf8')
214
+ created.push(filePath)
215
+ }
216
+ return { created, skipped, tasksDir }
217
+ }