spec-wave 2.1.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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +327 -0
  3. package/README.zh-CN.md +327 -0
  4. package/RELEASING.md +52 -0
  5. package/assets/README.md +30 -0
  6. package/assets/ci/samples/README.md +76 -0
  7. package/assets/ci/samples/hgm-ingest.yml.example +42 -0
  8. package/assets/ci/samples/lint-wiki-delta.pin.yml.example +47 -0
  9. package/assets/ci/samples/lint-wiki-delta.yml.example +49 -0
  10. package/assets/ci/samples/pytest.yml.example +49 -0
  11. package/assets/ci/samples/quality.yml.example +56 -0
  12. package/assets/ci/samples/skills-validate.yml.example +38 -0
  13. package/assets/ci/samples/tech-graph.yml.example +55 -0
  14. package/assets/coding_wiki/templates/README.md +115 -0
  15. package/assets/coding_wiki/templates/_index.md +19 -0
  16. package/assets/coding_wiki/templates/context.md +18 -0
  17. package/assets/coding_wiki/templates/stable.md +26 -0
  18. package/assets/coding_wiki/templates/topics/wiki_layout.md +13 -0
  19. package/assets/coding_wiki/templates/topics/wikilinks_export.md +13 -0
  20. package/assets/coding_wiki/templates/volatile.md +25 -0
  21. package/assets/docs/POINTER_ONBOARDING.md +9 -0
  22. package/assets/docs/POINTER_RUNBOOK_wiki_delta.md +18 -0
  23. package/assets/docs/POINTER_SDD_HAT_FLOW.md +8 -0
  24. package/assets/docs/POINTER_USER_GUIDE.md +8 -0
  25. package/assets/graph/stubs/README.md +6 -0
  26. package/assets/graph/templates/00_main.graph.yaml +72 -0
  27. package/assets/graph/templates/00_main.md +85 -0
  28. package/assets/graph/templates/01_struct.md +38 -0
  29. package/assets/graph/templates/02_version.md +7 -0
  30. package/assets/graph/templates/10_flow_MAIN.graph.yaml +105 -0
  31. package/assets/graph/templates/10_flow_MAIN.md +103 -0
  32. package/assets/graph/templates/99_mermaid_protocol.md +156 -0
  33. package/assets/graph/templates/README.md +66 -0
  34. package/assets/harness/README.md +18 -0
  35. package/assets/harness/discipline-coverage.yaml +320 -0
  36. package/assets/harness/invokes/README.md +22 -0
  37. package/assets/harness/invokes/TEMPLATE_invoke.md +61 -0
  38. package/assets/harness/lifecycle.yaml +127 -0
  39. package/assets/harness/prompts/00-orchestrator.md +91 -0
  40. package/assets/harness/prompts/10-spec-requirements.md +63 -0
  41. package/assets/harness/prompts/10-task-requirements.md +70 -0
  42. package/assets/harness/prompts/20-spec-audit.md +50 -0
  43. package/assets/harness/prompts/20-task-audit.md +90 -0
  44. package/assets/harness/prompts/30-execute-code.md +64 -0
  45. package/assets/harness/prompts/40-self-check.md +65 -0
  46. package/assets/harness/prompts/FRAGMENT_00_delegate_only_v1_zh.md +19 -0
  47. package/assets/harness/prompts/FRAGMENT_30_gate_verify_v1_zh.md +34 -0
  48. package/assets/harness/prompts/FRAGMENT_30_invoke_block_v1_zh.md +28 -0
  49. package/assets/harness/prompts/FRAGMENT_hat_reanchor_v1_zh.md +19 -0
  50. package/assets/harness/prompts/README.md +67 -0
  51. package/assets/harness/prompts/TEMPLATE_30_gate_stop.md +36 -0
  52. package/assets/harness/templates/FRAGMENT_task_domain_infer_v1_zh.md +16 -0
  53. package/assets/harness/templates/ONTOLOGY_consumer_slice_v1.md +41 -0
  54. package/assets/harness/templates/QUICKREF_v1_zh.md +35 -0
  55. package/assets/harness/templates/README.md +50 -0
  56. package/assets/harness/templates/TASK_TEMPLATE.md +133 -0
  57. package/assets/harness/templates/TASK_done_README.md +76 -0
  58. package/assets/harness/templates/TASK_epic.md +103 -0
  59. package/assets/harness/templates/TASK_graph_bootstrap.md +115 -0
  60. package/assets/harness/templates/VIEW_done_by_domain.md +66 -0
  61. package/assets/harness/templates/VIEW_done_thin_pointer.md +16 -0
  62. package/assets/ide/adapters/AGENTS.md.fragment.example +33 -0
  63. package/assets/ide/adapters/CLAUDE.md.fragment.example +32 -0
  64. package/assets/ide/adapters/README.md +57 -0
  65. package/assets/ide/adapters/cursor-harness-starter.mdc.example +39 -0
  66. package/assets/ide/commands/claude/kit/apply-standards.md +10 -0
  67. package/assets/ide/commands/claude/kit/gate-status.md +10 -0
  68. package/assets/ide/commands/claude/kit/graph-check.md +8 -0
  69. package/assets/ide/commands/claude/kit/hat-00-delegate.md +9 -0
  70. package/assets/ide/commands/claude/kit/hat-10-spec.md +9 -0
  71. package/assets/ide/commands/claude/kit/hat-10-task.md +9 -0
  72. package/assets/ide/commands/claude/kit/hat-20-spec-audit.md +9 -0
  73. package/assets/ide/commands/claude/kit/hat-20-task-audit.md +9 -0
  74. package/assets/ide/commands/claude/kit/hat-reanchor.md +8 -0
  75. package/assets/ide/commands/claude/kit/init-guide.md +16 -0
  76. package/assets/ide/commands/claude/kit/sync-prompts-guide.md +9 -0
  77. package/assets/ide/commands/claude/kit/verify.md +16 -0
  78. package/assets/ide/commands/cursor/kit-apply-standards.md +12 -0
  79. package/assets/ide/commands/cursor/kit-gate-status.md +12 -0
  80. package/assets/ide/commands/cursor/kit-graph-check.md +10 -0
  81. package/assets/ide/commands/cursor/kit-hat-00-delegate.md +11 -0
  82. package/assets/ide/commands/cursor/kit-hat-10-spec.md +11 -0
  83. package/assets/ide/commands/cursor/kit-hat-10-task.md +11 -0
  84. package/assets/ide/commands/cursor/kit-hat-20-spec-audit.md +11 -0
  85. package/assets/ide/commands/cursor/kit-hat-20-task-audit.md +11 -0
  86. package/assets/ide/commands/cursor/kit-hat-reanchor.md +10 -0
  87. package/assets/ide/commands/cursor/kit-init-guide.md +18 -0
  88. package/assets/ide/commands/cursor/kit-sync-prompts-guide.md +11 -0
  89. package/assets/ide/commands/cursor/kit-verify.md +18 -0
  90. package/assets/ide/host-adapt/README.md +113 -0
  91. package/assets/ide/host-adapt/examples/mvp-hosts.yaml +78 -0
  92. package/assets/ide/host-adapt/host-adapt.schema.json +97 -0
  93. package/assets/ide/skills-orch/README.md +16 -0
  94. package/assets/ide/skills-orch/kit-apply-standards/SKILL.md +17 -0
  95. package/assets/ide/skills-orch/kit-gate-status/SKILL.md +17 -0
  96. package/assets/ide/skills-orch/kit-hat-reanchor/SKILL.md +15 -0
  97. package/assets/ide/skills-orch/kit-init-guide/SKILL.md +23 -0
  98. package/assets/ide/skills-orch/kit-verify/SKILL.md +23 -0
  99. package/assets/ontology.yaml +113 -0
  100. package/assets/skills/README.md +37 -0
  101. package/assets/skills/harness-00-delegate-only/SKILL.md +19 -0
  102. package/assets/skills/harness-10-spec/SKILL.md +63 -0
  103. package/assets/skills/harness-10-task/SKILL.md +70 -0
  104. package/assets/skills/harness-20-spec-audit/SKILL.md +50 -0
  105. package/assets/skills/harness-20-task-audit/SKILL.md +90 -0
  106. package/assets/skills/harness-20-task-audit/references/TEMPLATE_30_gate_stop.md +36 -0
  107. package/assets/skills/harness-hat-reanchor/SKILL.md +19 -0
  108. package/assets/standards/POINTER_workspace_truth_v1_zh.md +42 -0
  109. package/assets/standards/README.md +35 -0
  110. package/assets/standards/SOURCES_v1_zh.md +49 -0
  111. package/assets/standards/TEMPLATE_CODING_BASELINE_L1_v1_zh.md +88 -0
  112. package/assets/standards/TEMPLATE_CODING_BASELINE_L2_backend_v1_zh.md +75 -0
  113. package/assets/standards/TEMPLATE_CODING_BASELINE_L2_frontend_v1_zh.md +76 -0
  114. package/bin/dsh-coding-kit.js +7 -0
  115. package/bin/specgate.js +7 -0
  116. package/bin/validate_template_compliance.py +470 -0
  117. package/cordis.patch.yml +5 -0
  118. package/lib/cli-checks.d.ts +68 -0
  119. package/lib/cli-checks.js +812 -0
  120. package/lib/cli-checks.js.map +1 -0
  121. package/lib/cli-graph-hgm.d.ts +74 -0
  122. package/lib/cli-graph-hgm.js +416 -0
  123. package/lib/cli-graph-hgm.js.map +1 -0
  124. package/lib/cli-graph-yaml.d.ts +79 -0
  125. package/lib/cli-graph-yaml.js +602 -0
  126. package/lib/cli-graph-yaml.js.map +1 -0
  127. package/lib/cli-graph.d.ts +1 -0
  128. package/lib/cli-graph.js +199 -0
  129. package/lib/cli-graph.js.map +1 -0
  130. package/lib/cli-host.d.ts +24 -0
  131. package/lib/cli-host.js +1197 -0
  132. package/lib/cli-host.js.map +1 -0
  133. package/lib/cli-lifecycle.d.ts +2 -0
  134. package/lib/cli-lifecycle.js +401 -0
  135. package/lib/cli-lifecycle.js.map +1 -0
  136. package/lib/cli-refresh-ide-blocks.d.ts +66 -0
  137. package/lib/cli-refresh-ide-blocks.js +484 -0
  138. package/lib/cli-refresh-ide-blocks.js.map +1 -0
  139. package/lib/cli-shared.d.ts +74 -0
  140. package/lib/cli-shared.js +281 -0
  141. package/lib/cli-shared.js.map +1 -0
  142. package/lib/cli-skills.d.ts +52 -0
  143. package/lib/cli-skills.js +363 -0
  144. package/lib/cli-skills.js.map +1 -0
  145. package/lib/cli-status.d.ts +2 -0
  146. package/lib/cli-status.js +251 -0
  147. package/lib/cli-status.js.map +1 -0
  148. package/lib/cli-sync-prompts.d.ts +25 -0
  149. package/lib/cli-sync-prompts.js +158 -0
  150. package/lib/cli-sync-prompts.js.map +1 -0
  151. package/lib/cli-sync.d.ts +2 -0
  152. package/lib/cli-sync.js +124 -0
  153. package/lib/cli-sync.js.map +1 -0
  154. package/lib/cli-task-extra.d.ts +53 -0
  155. package/lib/cli-task-extra.js +408 -0
  156. package/lib/cli-task-extra.js.map +1 -0
  157. package/lib/cli-timeline.d.ts +28 -0
  158. package/lib/cli-timeline.js +68 -0
  159. package/lib/cli-timeline.js.map +1 -0
  160. package/lib/cli-wiki.d.ts +21 -0
  161. package/lib/cli-wiki.js +189 -0
  162. package/lib/cli-wiki.js.map +1 -0
  163. package/lib/cli.d.ts +22 -0
  164. package/lib/cli.js +1034 -0
  165. package/lib/cli.js.map +1 -0
  166. package/lib/host-contract.d.ts +23 -0
  167. package/lib/host-contract.js +143 -0
  168. package/lib/host-contract.js.map +1 -0
  169. package/lib/index.d.ts +18 -0
  170. package/lib/index.js +290 -0
  171. package/lib/index.js.map +1 -0
  172. package/lib/yaml.d.ts +4 -0
  173. package/lib/yaml.js +10 -0
  174. package/lib/yaml.js.map +1 -0
  175. package/package.json +87 -0
package/lib/cli.js ADDED
@@ -0,0 +1,1034 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync } from 'node:fs';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import readline from 'node:readline';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { cmdGraph } from "./cli-graph.js";
7
+ import { cmdHost, listKnownHostIds } from "./cli-host.js";
8
+ import { cmdRefreshIdeBlocks, countStaleIdeLiterals } from "./cli-refresh-ide-blocks.js";
9
+ import { cmdDiscipline, cmdLifecycle } from "./cli-lifecycle.js";
10
+ import { cmdSkills } from "./cli-skills.js";
11
+ import { buildDoneSnapshot, CliError, evaluateMayStart30, extractSection, extractTaskSlug, fail, findGate, kitLayoutJoin, KIT_LAYOUT_DIR, LEGACY_LAYOUT_DIR, legacyLayoutHint, normalizeSlug, packageRoot, parseHarnessMeta, parseHumanGates, resolveLayoutFile, resolveTarget, resolveTaskPath, STATUS_RE, takeOption } from "./cli-shared.js";
12
+ import { checkPre30InvokeHats, evalCloseGuard, findReview, findSpecReview, listBareSpecFiles, lintTaskFile, PLACEHOLDER_RE, runTestCheck, shouldSkipSpecAudit, } from "./cli-checks.js";
13
+ import { cmdStatus, cmdTimeline } from "./cli-status.js";
14
+ import { cmdSync } from "./cli-sync.js";
15
+ import { cmdTaskCheck, cmdTaskLintDone, cmdTaskLintWikiDelta, lintWikiDeltaMissing } from "./cli-task-extra.js";
16
+ import { cmdWiki } from "./cli-wiki.js";
17
+ // DEF-003 阶段二 T6 + PRD_DEF-003 后续棒 + doc-health:close 守卫求值顺序
18
+ const CLOSE_GUARD_ORDER = [
19
+ 'close_invoke',
20
+ 'close_self_check',
21
+ 'close_acceptance',
22
+ 'close_slug',
23
+ 'close_status',
24
+ 'close_review',
25
+ 'close_graph_delta',
26
+ 'close_kpi',
27
+ 'close_experience',
28
+ 'close_wiki_delta',
29
+ 'close_wiki_promotion',
30
+ 'close_pr_merged',
31
+ 'close_hub_index',
32
+ ];
33
+ // init --preset 合法词表(DEF-013 D1:当前唯一合法值;新增 preset 须先扩展此常量)
34
+ const VALID_PRESETS = ['harness-only'];
35
+ async function readPkgVersion() {
36
+ if (process.env.HARNESS_VERSION)
37
+ return process.env.HARNESS_VERSION;
38
+ const raw = await readFile(path.join(packageRoot(), 'package.json'), 'utf8');
39
+ const pkg = JSON.parse(raw);
40
+ return pkg.version ?? 'unknown';
41
+ }
42
+ function usage(version) {
43
+ console.log(`specgate CLI (v${version})
44
+ 过渡: npx dsh-coding-kit 仍可用(同入口)
45
+
46
+ 用法:
47
+ npx spec-wave --version | -V
48
+ npx spec-wave --help | -h
49
+ npx spec-wave init [--preset NAME] [--tools all|none|LIST] [--profile core|expanded] [--host-adapt|--no-host-adapt] [--target PATH] [--yes] (NAME 词表: harness-only)
50
+ npx spec-wave upgrade [--target PATH] [--yes]
51
+ npx spec-wave refresh-ide-blocks [--target PATH] [--dry-run] [--yes] [--json]
52
+ npx spec-wave check [--target PATH]
53
+ npx spec-wave verify [--target PATH] [--task FILE | --spec FILE] [--json] [--with-wiki-lint]
54
+ npx spec-wave gate-check [--target PATH] [--task FILE] [--json]
55
+ npx spec-wave audit [--target PATH] [--task FILE]
56
+ npx spec-wave task lint --file PATH
57
+ npx spec-wave task close --file PATH [--yes] [--json]
58
+ npx spec-wave status [--target PATH] [--task FILE] [--json] [--check]
59
+ npx spec-wave timeline --task FILE [--target PATH] [--json] [--limit N] [--ingest]
60
+ npx spec-wave lifecycle show [--json]
61
+ npx spec-wave lifecycle dry-run --transition ID --from STATE [--task PATH] [--target PATH]
62
+ npx spec-wave discipline show [--json]
63
+ npx spec-wave graph yaml compile|check|export …
64
+ npx spec-wave graph ingest|snapshot|axioms …
65
+ npx spec-wave sync index [--target PATH]
66
+ npx spec-wave sync prompts [--target PATH] [--yes] [--force] [--json]
67
+ npx spec-wave skills install [--target DIR] [--out DIR] [--global] [--force] [--with-execute-hats]
68
+ npx spec-wave skills build [--with-execute-hats]
69
+ npx spec-wave skills check
70
+ npx spec-wave host validate [--file PATH] [--json]
71
+ npx spec-wave host apply --tools LIST [--profile core] [--target PATH] [--file PATH] [--json] [--dry-run|--yes]
72
+ npx spec-wave host update [--tools LIST] [--profile core] [--target PATH] [--file PATH] [--json] [--dry-run|--yes] [--force]
73
+ npx spec-wave wiki export --json [--target PATH]
74
+ npx spec-wave task lint-done [--target PATH]
75
+ npx spec-wave task lint-wiki-delta [--target PATH] [--scope all|active|done] [--strict] [--json]
76
+ 诊断码: wiki_delta_missing(缺字段)· wiki_delta_wrong_section(字段写在 ## Harness 元信息 之外的节 · 替代 missing 不双报)
77
+ --strict 追加: wiki_delta_invalid / wiki_delta_path_missing;task lint --file E8 同口径查 wiki_delta 存在性
78
+ npx spec-wave task check --file PATH
79
+
80
+ Exit codes (P0 gates · failClosed):
81
+ 0 pass / informational (check always exits 0)
82
+ 1 usage error or non-blocking failure
83
+ 2 gate BLOCKED — do not proceed (verify / gate-check / audit / D5)
84
+ `);
85
+ }
86
+ /** 新写入路径(F4:一律 .coding-kit) */
87
+ function manifestWritePath(target) {
88
+ return kitLayoutJoin(target, 'manifest.json');
89
+ }
90
+ /** 解析路径(读:新优先,legacy 回退) */
91
+ function manifestPath(target) {
92
+ return resolveLayoutFile(target, 'manifest.json').abs;
93
+ }
94
+ async function readManifest(target) {
95
+ const hit = resolveLayoutFile(target, 'manifest.json');
96
+ if (hit.source === 'none')
97
+ return null;
98
+ return JSON.parse(await readFile(hit.abs, 'utf8'));
99
+ }
100
+ function nowUtc() {
101
+ return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
102
+ }
103
+ // 数值三元组比较(x.y.z):-1 a<b · 0 相等 · 1 a>b。
104
+ // 限制:不支持 pre-release 形态(如 1.2.2-beta.1);遇非纯数字段按「不等且方向未知」归 -1(维持旧版可升级提示),版本历史均为纯 x.y.z,未来引入 pre-release 再升级比较器。
105
+ // DEF-030:旧包 @cyning/harness 产品线版本形态词表(2.x 系列)。
106
+ // from_version 属 kit 线(1.x)时不算跨产品线迁移,回落原「降级安装」语义。
107
+ function isLegacyHarnessLineVersion(version) {
108
+ return /^2\./.test(version.trim());
109
+ }
110
+ function compareVersion(a, b) {
111
+ if (a === b)
112
+ return 0;
113
+ const pa = a.split('.').map((p) => Number.parseInt(p, 10));
114
+ const pb = b.split('.').map((p) => Number.parseInt(p, 10));
115
+ for (let i = 0; i < 3; i++) {
116
+ const x = pa[i];
117
+ const y = pb[i];
118
+ if (x === undefined || y === undefined || Number.isNaN(x) || Number.isNaN(y))
119
+ return -1;
120
+ if (x !== y)
121
+ return x < y ? -1 : 1;
122
+ }
123
+ return 0;
124
+ }
125
+ const INIT_USAGE = 'init [--preset NAME] [--tools all|none|LIST] [--profile core|expanded] [--host-adapt|--no-host-adapt] [--target PATH] [--yes] (NAME 词表: harness-only)';
126
+ /** 非 TTY / CI:须显式 `--tools`(对齐 OpenSpec) */
127
+ export function isInteractiveInit(stdin = process.stdin) {
128
+ return Boolean(stdin.isTTY);
129
+ }
130
+ /** 解析 init `--tools`;`none` 仅 init;`all`=适配表全量 */
131
+ export function parseInitToolsArg(toolsArg, knownIds) {
132
+ const tokens = Array.from(new Set(toolsArg
133
+ .split(',')
134
+ .map((s) => s.trim())
135
+ .filter((s) => s.length > 0)));
136
+ if (tokens.length < 1) {
137
+ fail(`init --tools 须为 all|none|LIST(逗号分隔 host_id)\n用法: ${INIT_USAGE}`);
138
+ }
139
+ if (tokens.includes('none')) {
140
+ if (tokens.length !== 1) {
141
+ fail(`init --tools none 不可与其它 host_id 混用\n用法: ${INIT_USAGE}`);
142
+ }
143
+ return { mode: 'none' };
144
+ }
145
+ if (tokens.includes('all')) {
146
+ if (tokens.length !== 1) {
147
+ fail(`init --tools all 不可与其它 host_id 混用\n用法: ${INIT_USAGE}`);
148
+ }
149
+ return { mode: 'hosts', ids: [...knownIds], toolsArg: 'all' };
150
+ }
151
+ const known = new Set(knownIds);
152
+ const unknown = tokens.filter((id) => !known.has(id));
153
+ if (unknown.length > 0) {
154
+ fail(`init --tools 未知 host_id: ${unknown.join(', ')}(合法: ${knownIds.join(', ')})\n用法: ${INIT_USAGE}`);
155
+ }
156
+ return { mode: 'hosts', ids: tokens, toolsArg: tokens.join(',') };
157
+ }
158
+ /**
159
+ * TTY 询问宿主选型(可注入 stdin/stdout;测用 mock Readable)。
160
+ * 接受:逗号多选 / all / none。
161
+ */
162
+ export async function promptInitTools(knownIds, io = {}) {
163
+ const input = io.input ?? process.stdin;
164
+ const output = io.output ?? process.stdout;
165
+ const rl = readline.createInterface({ input, output, terminal: false });
166
+ const ask = (q) => new Promise((resolve) => {
167
+ rl.question(q, (answer) => resolve(answer));
168
+ });
169
+ try {
170
+ output.write(`请选择要物化的 IDE/宿主(多选逗号分隔,或 all / none)\n可选: ${knownIds.join(', ')}\n`);
171
+ for (;;) {
172
+ const raw = (await ask('> ')).trim();
173
+ if (!raw) {
174
+ output.write('须输入 all、none 或 host_id 列表(不可空)\n');
175
+ continue;
176
+ }
177
+ try {
178
+ return parseInitToolsArg(raw, knownIds);
179
+ }
180
+ catch (err) {
181
+ if (err instanceof CliError) {
182
+ output.write(`${err.message}\n`);
183
+ continue;
184
+ }
185
+ throw err;
186
+ }
187
+ }
188
+ }
189
+ finally {
190
+ rl.close();
191
+ }
192
+ }
193
+ async function cmdInit(args, pkgVersion) {
194
+ if (args.includes('--help') || args.includes('-h')) {
195
+ console.log(`用法: npx spec-wave ${INIT_USAGE}`);
196
+ return;
197
+ }
198
+ const yes = args.includes('--yes');
199
+ const hostAdapt = args.includes('--host-adapt');
200
+ const noHostAdapt = args.includes('--no-host-adapt');
201
+ if (hostAdapt && noHostAdapt) {
202
+ fail(`init: --host-adapt 与 --no-host-adapt 不可同现\n用法: ${INIT_USAGE}`);
203
+ }
204
+ let rest = args.filter((a) => a !== '--yes' && a !== '--host-adapt' && a !== '--no-host-adapt');
205
+ const { value: preset, rest: r1 } = takeOption(rest, '--preset');
206
+ rest = r1;
207
+ const { value: targetArg, rest: r2 } = takeOption(rest, '--target');
208
+ rest = r2;
209
+ const hasToolsFlag = rest.includes('--tools');
210
+ const { value: toolsArg, rest: r3 } = takeOption(rest, '--tools');
211
+ rest = r3;
212
+ if (hasToolsFlag && toolsArg === undefined) {
213
+ fail(`init --tools 须跟 all|none|LIST\n用法: ${INIT_USAGE}`);
214
+ }
215
+ const { value: profileArg, rest: r4 } = takeOption(rest, '--profile');
216
+ rest = r4;
217
+ if (rest.length > 0)
218
+ fail(`init 未知参数: ${rest.join(' ')}\n用法: ${INIT_USAGE}`);
219
+ const profile = profileArg ?? 'core';
220
+ if (profile !== 'core' && profile !== 'expanded') {
221
+ fail(`init 仅支持 --profile core|expanded(收到: ${profile})\n用法: ${INIT_USAGE}`);
222
+ }
223
+ const target = resolveTarget(process.cwd(), targetArg);
224
+ const chosenPreset = preset || 'harness-only';
225
+ if (!VALID_PRESETS.includes(chosenPreset)) {
226
+ fail(`init --preset 取值非法: ${chosenPreset}(合法词表: ${VALID_PRESETS.join(' / ')})`);
227
+ }
228
+ const knownIds = listKnownHostIds();
229
+ let selection;
230
+ if (toolsArg === undefined) {
231
+ if (!isInteractiveInit()) {
232
+ fail(`init 非交互环境须显式 --tools all|none|LIST(对齐 OpenSpec;禁止假装已询问)\n用法: ${INIT_USAGE}`);
233
+ }
234
+ selection = await promptInitTools(knownIds);
235
+ }
236
+ else {
237
+ selection = parseInitToolsArg(toolsArg, knownIds);
238
+ }
239
+ const existing = await readManifest(target);
240
+ if (existing) {
241
+ console.log(`manifest 已存在,跳过写入: ${manifestPath(target)}`);
242
+ }
243
+ else {
244
+ const mf = {
245
+ version: pkgVersion,
246
+ preset: chosenPreset,
247
+ ide: [],
248
+ from_version: null,
249
+ upgraded_at: nowUtc(),
250
+ };
251
+ const dest = manifestWritePath(target);
252
+ mkdirSync(path.dirname(dest), { recursive: true });
253
+ await writeFile(dest, `${JSON.stringify(mf, null, 2)}\n`, 'utf8');
254
+ console.log(`已写入 manifest: ${dest}`);
255
+ console.log(`manifest: ${dest}`);
256
+ }
257
+ // freeze:`--no-host-adapt` → 只做过程根,不 apply、不写粘性(避免「记住了却未物化」)
258
+ const shouldApply = selection.mode === 'hosts' && !noHostAdapt;
259
+ if (selection.mode === 'none') {
260
+ console.log('已跳过 host 物化(--tools none)');
261
+ }
262
+ else if (!shouldApply) {
263
+ console.log('已跳过 host 物化(--no-host-adapt;未写粘性)');
264
+ }
265
+ else {
266
+ const applyArgs = [
267
+ 'apply',
268
+ '--tools',
269
+ selection.toolsArg,
270
+ '--profile',
271
+ profile,
272
+ '--target',
273
+ target,
274
+ ];
275
+ if (yes)
276
+ applyArgs.push('--yes');
277
+ console.log(`联动 host apply(同进程): --tools ${selection.toolsArg} --profile ${profile}${yes ? ' --yes' : '(dry-run;加 --yes 写盘)'}`);
278
+ await cmdHost(applyArgs);
279
+ }
280
+ if (!yes)
281
+ console.log('init 完成。');
282
+ }
283
+ async function cmdUpgrade(args, pkgVersion) {
284
+ if (args.includes('--help') || args.includes('-h')) {
285
+ console.log('用法: npx spec-wave upgrade [--target PATH] [--yes]');
286
+ return;
287
+ }
288
+ const yes = args.includes('--yes');
289
+ let rest = args.filter((a) => a !== '--yes');
290
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
291
+ rest = r1;
292
+ if (rest.length > 0)
293
+ fail(`upgrade 未知参数: ${rest.join(' ')}`);
294
+ const target = resolveTarget(process.cwd(), targetArg);
295
+ const current = await readManifest(target);
296
+ if (!current) {
297
+ fail('未接入(无 .coding-kit/manifest.json 或 legacy .cyning-harness/manifest.json)。建议: npx spec-wave init --preset harness-only --yes');
298
+ }
299
+ const next = {
300
+ version: pkgVersion,
301
+ preset: current.preset || 'harness-only',
302
+ ide: Array.isArray(current.ide) ? current.ide : [],
303
+ from_version: current.version === pkgVersion ? current.from_version : current.version,
304
+ upgraded_at: nowUtc(),
305
+ };
306
+ const dest = manifestWritePath(target);
307
+ mkdirSync(path.dirname(dest), { recursive: true });
308
+ await writeFile(dest, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
309
+ console.log(`upgrade: ${current.version} → ${pkgVersion}`);
310
+ console.log(`manifest: ${dest}`);
311
+ const legHint = legacyLayoutHint(target);
312
+ if (legHint)
313
+ console.log(legHint);
314
+ if (!yes)
315
+ console.log('upgrade 完成(S2 路径未写入)。');
316
+ // R-07 §5.2:upgrade 内嵌 dry-run 只读提示(不写 IDE 文件、不改 exit 码;扫描异常吞为提示级)
317
+ try {
318
+ const stale = countStaleIdeLiterals(target);
319
+ if (stale > 0) {
320
+ console.log('提示: 检测到 ' + stale + ' 处 IDE 块内旧命令字面;运行 `npx spec-wave refresh-ide-blocks --yes` 刷写(先 `refresh-ide-blocks --dry-run` 看详情)。');
321
+ }
322
+ }
323
+ catch {
324
+ // 提示级:扫描异常不影响 upgrade 语义
325
+ }
326
+ console.log('提示: manifest 已升级 · prompts 未自动同步(含 FRAGMENT_hat_reanchor / FRAGMENT_00_delegate_only) · 运行 `npx spec-wave sync prompts --yes`(先 `sync prompts` dry-run 看清单)。');
327
+ }
328
+ async function cmdCheck(args, pkgVersion) {
329
+ if (args.includes('--help') || args.includes('-h')) {
330
+ console.log('用法: npx spec-wave check [--target PATH]');
331
+ return;
332
+ }
333
+ let rest = args;
334
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
335
+ rest = r1;
336
+ if (rest.length > 0)
337
+ fail(`check 未知参数: ${rest.join(' ')}`);
338
+ const target = resolveTarget(process.cwd(), targetArg);
339
+ const manifest = await readManifest(target);
340
+ console.log(`目标: ${target}`);
341
+ console.log(`包版本: ${pkgVersion}`);
342
+ if (!manifest) {
343
+ console.log('状态: 未接入(无 .coding-kit/manifest.json 或 legacy .cyning-harness/manifest.json)');
344
+ console.log('建议: npx spec-wave init --preset harness-only --yes');
345
+ return;
346
+ }
347
+ console.log(`manifest.version: ${manifest.version}`);
348
+ console.log(`manifest.preset: ${manifest.preset}`);
349
+ const cmp = compareVersion(manifest.version, pkgVersion);
350
+ if (cmp === 0) {
351
+ console.log('状态: 已是最新');
352
+ }
353
+ else if (cmp < 0) {
354
+ console.log('状态: 可升级');
355
+ console.log('建议: npx spec-wave upgrade --yes');
356
+ }
357
+ else if (manifest.from_version != null && isLegacyHarnessLineVersion(manifest.from_version)) {
358
+ // DEF-028:from_version 属旧包产品线(2.x 系列)= 从旧产品线迁来,跨产品线版本号不可比,输出迁移语义而非降级警告
359
+ // DEF-030:判据收窄——kit 线(1.x)from_version 不走本分支,回落下方「降级安装」语义
360
+ console.log(`状态: 跨产品线迁移:@cyning/harness ${manifest.version} → dsh-coding-kit ${pkgVersion}(跨产品线版本号不可比)`);
361
+ console.log('建议: npx spec-wave upgrade --yes');
362
+ }
363
+ else {
364
+ console.log('状态: manifest 版本高于包版本(可能为降级安装)');
365
+ console.log('建议: 核对接入来源(manifest 由更高版本 CLI 写入)');
366
+ }
367
+ const bareSpecs = listBareSpecFiles(target);
368
+ if (bareSpecs.length > 0) {
369
+ console.log(`WARN: docs/spec 根级裸 SPEC-*.md(${bareSpecs.length})· 见 docs/spec/doc-health(新 SPEC 须专属夹)`);
370
+ for (const f of bareSpecs)
371
+ console.log(` - docs/spec/${f}`);
372
+ }
373
+ }
374
+ function formatGateCheck(taskFile, content) {
375
+ const gates = parseHumanGates(content);
376
+ const draft = findGate(gates, 'HG-TASK-DRAFT');
377
+ const audit = findGate(gates, 'HG-AUDIT-R1');
378
+ const graph = findGate(gates, 'HG-GRAPH-MODULES');
379
+ const lines = [];
380
+ lines.push(`task: ${path.basename(taskFile)}`);
381
+ lines.push('| gate | status | blocks_30 | 30 影响 |');
382
+ lines.push('|------|--------|-----------|--------|');
383
+ const draftStatus = draft?.status ?? '?';
384
+ const draftBlocks = draft?.blocksHats ?? '?';
385
+ const draftImpact = draftStatus === 'approved' ? '—' : draftBlocks.includes('30') ? '❌ 拒 30' : '—';
386
+ lines.push(`| HG-TASK-DRAFT | ${draftStatus} | ${draftBlocks} | ${draftImpact} |`);
387
+ const auditStatus = audit?.status ?? '?';
388
+ lines.push(`| HG-AUDIT-R1 | ${auditStatus} | 30 | ${auditStatus === 'approved' ? '✅ 可 30' : '❌ 拒 30'} |`);
389
+ if (graph && graph.status !== '—' && graph.status !== '?') {
390
+ lines.push(`| HG-GRAPH-MODULES | ${graph.status} | — | ${graph.status === 'approved' ? '✅' : '❌ 若 pending 拒 30'} |`);
391
+ }
392
+ lines.push('');
393
+ const may = evaluateMayStart30(gates);
394
+ let blocked = false;
395
+ if (auditStatus !== 'approved') {
396
+ blocked = true;
397
+ lines.push('→ 30 不可开工: HG-AUDIT-R1 非 approved(须维护者签 task 表)');
398
+ }
399
+ if (draft && draft.status !== 'approved' && draft.blocksHats.includes('30')) {
400
+ blocked = true;
401
+ lines.push('→ 30 不可开工: HG-TASK-DRAFT pending 且 blocks 30');
402
+ }
403
+ if (graph?.status === 'pending') {
404
+ blocked = true;
405
+ lines.push('→ 30 不可开工: HG-GRAPH-MODULES pending');
406
+ }
407
+ if (!blocked && !may.ok) {
408
+ blocked = true;
409
+ lines.push(`→ 30 不可开工: ${may.reason}`);
410
+ }
411
+ lines.push('');
412
+ return { text: lines.join('\n'), blocked };
413
+ }
414
+ async function cmdGateCheck(args) {
415
+ if (args.includes('--help') || args.includes('-h')) {
416
+ console.log('用法: npx spec-wave gate-check [--target PATH] [--task FILE] [--json]');
417
+ return;
418
+ }
419
+ const json = args.includes('--json');
420
+ let rest = args.filter((a) => a !== '--json');
421
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
422
+ rest = r1;
423
+ const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
424
+ rest = r2;
425
+ if (rest.length > 0)
426
+ fail(`gate-check 未知参数: ${rest.join(' ')}`);
427
+ const target = resolveTarget(process.cwd(), targetArg);
428
+ const mf = await readManifest(target);
429
+ if (!json) {
430
+ console.log('=== Harness gate-check ===');
431
+ console.log(`目标: ${target}`);
432
+ if (mf) {
433
+ console.log(`manifest.version: ${mf.version}`);
434
+ console.log(`manifest.preset: ${mf.preset}`);
435
+ }
436
+ else {
437
+ console.log(`manifest: (未接入 · 无 ${manifestPath(target)})`);
438
+ }
439
+ console.log('');
440
+ }
441
+ if (!taskFile)
442
+ fail('gate-check 须指定 --task FILE(1.1.0 P0 子集)');
443
+ const abs = resolveTaskPath(target, taskFile);
444
+ if (!existsSync(abs))
445
+ fail(`错误: 未找到 --task 文件 ${abs}`);
446
+ const formatted = formatGateCheck(abs, await readFile(abs, 'utf8'));
447
+ if (json) {
448
+ console.log(JSON.stringify({
449
+ command: 'gate-check',
450
+ target,
451
+ task: taskFile,
452
+ blocked: formatted.blocked,
453
+ verdict: formatted.blocked ? 'BLOCKED' : 'PASS',
454
+ }, null, 2));
455
+ }
456
+ else {
457
+ process.stdout.write(formatted.text);
458
+ }
459
+ if (formatted.blocked)
460
+ fail('', 2);
461
+ if (!json) {
462
+ console.log('闸检查: 未发现阻塞(仍须 Agent 首输出 GATE_VERIFY · 不得采信 invoke 字面 approved)');
463
+ }
464
+ }
465
+ async function cmdAudit(args) {
466
+ if (args.includes('--help') || args.includes('-h')) {
467
+ console.log('用法: npx spec-wave audit [--target PATH] [--task FILE]');
468
+ return;
469
+ }
470
+ let rest = args;
471
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
472
+ rest = r1;
473
+ const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
474
+ rest = r2;
475
+ if (rest.length > 0)
476
+ fail(`audit 未知参数: ${rest.join(' ')}`);
477
+ const target = resolveTarget(process.cwd(), targetArg);
478
+ console.log(`目标: ${target}`);
479
+ if (taskFile)
480
+ console.log(`task: ${taskFile}`);
481
+ let gateOk = true;
482
+ let gateText = '';
483
+ if (taskFile) {
484
+ const abs = resolveTaskPath(target, taskFile);
485
+ if (!existsSync(abs))
486
+ fail(`错误: 未找到 --task 文件 ${abs}`);
487
+ const formatted = formatGateCheck(abs, await readFile(abs, 'utf8'));
488
+ gateText = formatted.text;
489
+ gateOk = !formatted.blocked;
490
+ process.stdout.write(gateText);
491
+ }
492
+ const test = runTestCheck(target, taskFile);
493
+ console.log(`audit: ${gateOk && test.ok ? 'PASS' : 'FAIL'}`);
494
+ console.log(` gate-check: ${gateOk ? 'PASS' : 'FAIL'}`);
495
+ console.log(` test-check: ${test.ok ? 'PASS' : 'FAIL'}`);
496
+ if (test.reason)
497
+ console.log(` ${test.reason}`);
498
+ if (!gateOk || !test.ok)
499
+ fail('ICVO audit 未通过', 2);
500
+ }
501
+ function wikiLintJson(res) {
502
+ return { ok: res.ok, issues: res.issues, scanned: res.scanned };
503
+ }
504
+ function printWikiLintIssues(res) {
505
+ console.log('verify: 缺 wiki_delta 字段的 task(lint-wiki-delta · scope=all · 缺口可能来自兄弟 active/done task):');
506
+ for (const m of res.issues) {
507
+ console.log(` - ${m.path} · ${m.code} · ${m.detail}`);
508
+ }
509
+ console.log('复跑(与 PR CI 同命令): npx --yes spec-wave task lint-wiki-delta --target .');
510
+ }
511
+ // PRD_DEF-003 后续棒:verify --spec 真闸(SPEC→00 前查审查文存在性 · 消灭最后一个 notDelivered 命令面)。
512
+ // 语义映射旧包 @cyning/harness@2.24.0 lib/verify.js verifySpecTarget(差异见 cli-checks.ts findSpecReview 注释与 PR body):
513
+ // · 仅查审查文存在性,不跑 gate-check / audit D5 / task lint(与 --task 模式分离)
514
+ // · bugfix / skip_spec_audit 元信息豁免(shouldSkipSpecAudit)→ PASS
515
+ // · 缺失 → VERIFY: BLOCKED · missing spec R<n> review · exit 2
516
+ // · --allow-no-spec-review(canonical · lifecycle.yaml 登记)/ --allow-no-review(别名)真豁免 · 留痕(文本 + JSON waived[],与 T4 口径一致)
517
+ async function verifySpecMode(target, specFile, opts) {
518
+ const abs = resolveTaskPath(target, specFile);
519
+ const label = path.basename(abs);
520
+ const emitJson = (blocked, extra, wikiLint) => {
521
+ console.log(JSON.stringify({
522
+ command: 'verify',
523
+ target,
524
+ spec: specFile,
525
+ blocked,
526
+ verdict: blocked ? 'BLOCKED' : 'PASS',
527
+ ...(extra?.skipped ? { skipped: extra.skipped } : {}),
528
+ ...(extra?.waived && extra.waived.length > 0 ? { waived: extra.waived } : {}),
529
+ ...(wikiLint ? { wiki_lint: wikiLintJson(wikiLint) } : {}),
530
+ }, null, 2));
531
+ };
532
+ if (!existsSync(abs))
533
+ fail(`错误: 未找到 --spec 文件 ${abs}`);
534
+ const content = await readFile(abs, 'utf8');
535
+ // K3:--with-wiki-lint 在 --spec 模式同生效(20 审 R1 已定 · 复用 lintWikiDeltaMissing 导出 · 默认档 scope=all)
536
+ const wikiLint = opts.withWikiLint ? lintWikiDeltaMissing(target, { scope: 'all' }) : null;
537
+ if (shouldSkipSpecAudit(content)) {
538
+ if (wikiLint && !wikiLint.ok) {
539
+ if (opts.json)
540
+ emitJson(true, { skipped: 'bugfix / skip_spec_audit' }, wikiLint);
541
+ else {
542
+ console.log('verify: INFO · skip SPEC review gate(bugfix / skip_spec_audit)');
543
+ printWikiLintIssues(wikiLint);
544
+ console.log(`VERIFY: BLOCKED · wiki_delta 缺口(lint-wiki-delta · scope=all)· ${label}`);
545
+ }
546
+ fail('', 2);
547
+ }
548
+ if (opts.json)
549
+ emitJson(false, { skipped: 'bugfix / skip_spec_audit' }, wikiLint);
550
+ else {
551
+ console.log('verify: INFO · skip SPEC review gate(bugfix / skip_spec_audit)');
552
+ if (wikiLint)
553
+ console.log(`verify: wiki-lint PASS · scanned: ${wikiLint.scanned} · scope: all`);
554
+ console.log(`VERIFY: PASS · ${label}`);
555
+ }
556
+ return;
557
+ }
558
+ const allowFlag = opts.allowNoSpecReview ? '--allow-no-spec-review' : opts.allowNoReview ? '--allow-no-review' : null;
559
+ const reviewFound = findSpecReview(target, abs, content);
560
+ if (!reviewFound && !allowFlag) {
561
+ if (opts.json)
562
+ emitJson(true);
563
+ else
564
+ console.log(`VERIFY: BLOCKED · missing spec R<n> review · ${label}`);
565
+ fail('', 2);
566
+ }
567
+ const waived = [];
568
+ if (!reviewFound && allowFlag) {
569
+ waived.push(`missing spec R<n> review(${allowFlag} 豁免)`);
570
+ if (!opts.json) {
571
+ console.log(`verify: 留痕 · 缺 spec R<n> 审查文 · ${allowFlag} 豁免生效(仍须补审并由维护者签 HG-SPEC-SIGNOFF)`);
572
+ }
573
+ }
574
+ if (wikiLint && !wikiLint.ok) {
575
+ if (opts.json)
576
+ emitJson(true, { waived }, wikiLint);
577
+ else {
578
+ printWikiLintIssues(wikiLint);
579
+ console.log(`VERIFY: BLOCKED · wiki_delta 缺口(lint-wiki-delta · scope=all)· ${label}`);
580
+ }
581
+ fail('', 2);
582
+ }
583
+ if (opts.json)
584
+ emitJson(false, { waived }, wikiLint);
585
+ else {
586
+ if (wikiLint)
587
+ console.log(`verify: wiki-lint PASS · scanned: ${wikiLint.scanned} · scope: all`);
588
+ console.log(`VERIFY: PASS · ${label}`);
589
+ }
590
+ }
591
+ async function cmdVerify(args) {
592
+ if (args.includes('--help') || args.includes('-h')) {
593
+ console.log('用法: npx spec-wave verify [--target PATH] [--task FILE | --spec FILE] [--json] [--with-wiki-lint] [--allow-no-review] [--allow-invoke-gap] [--allow-no-spec-review]');
594
+ return;
595
+ }
596
+ const json = args.includes('--json');
597
+ let rest = args.filter((a) => a !== '--json');
598
+ const { value: targetArg, rest: r1 } = takeOption(rest, '--target');
599
+ rest = r1;
600
+ const { value: taskFile, rest: r2 } = takeOption(rest, '--task');
601
+ rest = r2;
602
+ const { value: specFile, rest: r3 } = takeOption(rest, '--spec');
603
+ rest = r3;
604
+ // DEF-003 阶段二 T4/T5:--allow-no-review / --allow-invoke-gap 真生效(硬闸豁免 · 留痕);
605
+ // PRD_DEF-003 后续棒:--allow-no-spec-review 真生效(verify --spec 豁免 · 留痕);
606
+ // 其余 --allow-* 仍走 DEF-011 fail-fast(--allow-lint-fail 等归 DEF-011 交接清单)
607
+ const allowNoReview = rest.includes('--allow-no-review');
608
+ rest = rest.filter((a) => a !== '--allow-no-review');
609
+ const allowInvokeGap = rest.includes('--allow-invoke-gap');
610
+ rest = rest.filter((a) => a !== '--allow-invoke-gap');
611
+ const allowNoSpecReview = rest.includes('--allow-no-spec-review');
612
+ rest = rest.filter((a) => a !== '--allow-no-spec-review');
613
+ // K3:--with-wiki-lint 追加闸(显式旗标 · 非破坏;preset 默认开启为独立决策点,本 task 不做)
614
+ const withWikiLint = rest.includes('--with-wiki-lint');
615
+ rest = rest.filter((a) => a !== '--with-wiki-lint');
616
+ if (rest.length > 0)
617
+ fail(`verify 未知参数: ${rest.join(' ')}`);
618
+ const target = resolveTarget(process.cwd(), targetArg);
619
+ // --task 与 --spec 互斥(旧包 lib/cli.js#487-491 语义 · exit 1 用法错误)
620
+ if (taskFile && specFile)
621
+ fail('verify:--task 与 --spec 互斥');
622
+ if (specFile) {
623
+ await verifySpecMode(target, specFile, { json, allowNoSpecReview, allowNoReview, withWikiLint });
624
+ return;
625
+ }
626
+ if (!taskFile)
627
+ fail('verify 须指定 --task FILE 或 --spec FILE');
628
+ const abs = resolveTaskPath(target, taskFile);
629
+ const label = path.basename(abs);
630
+ const emitJson = (blocked, waived, wikiLint) => {
631
+ console.log(JSON.stringify({
632
+ command: 'verify',
633
+ target,
634
+ task: taskFile,
635
+ blocked,
636
+ verdict: blocked ? 'BLOCKED' : 'PASS',
637
+ ...(waived && waived.length > 0 ? { waived } : {}),
638
+ ...(wikiLint ? { wiki_lint: wikiLintJson(wikiLint) } : {}),
639
+ }, null, 2));
640
+ };
641
+ if (!existsSync(abs)) {
642
+ if (json)
643
+ emitJson(true);
644
+ else
645
+ console.log(`VERIFY: BLOCKED · task 文件不存在 · ${label}`);
646
+ fail('', 2);
647
+ }
648
+ const content = await readFile(abs, 'utf8');
649
+ const formatted = formatGateCheck(abs, content);
650
+ if (!json)
651
+ process.stdout.write(formatted.text);
652
+ if (formatted.blocked) {
653
+ const gates = parseHumanGates(content);
654
+ const audit = findGate(gates, 'HG-AUDIT-R1');
655
+ const reason = audit?.status !== 'approved' ? 'HG-AUDIT-R1 pending' : formatted.text.includes('HG-TASK-DRAFT')
656
+ ? 'HG-TASK-DRAFT pending'
657
+ : 'gate-check blocked';
658
+ if (json)
659
+ emitJson(true);
660
+ else
661
+ console.log(`VERIFY: BLOCKED · ${reason} · ${label}`);
662
+ fail('', 2);
663
+ }
664
+ const test = runTestCheck(target, taskFile);
665
+ if (!test.ok) {
666
+ if (json)
667
+ emitJson(true);
668
+ else
669
+ console.log(`VERIFY: BLOCKED · ${test.reason} · ${label}`);
670
+ fail('', 2);
671
+ }
672
+ // DEF-003 阶段二 T4:R<n> 审查文存在性硬闸(findReview 与 status / dry-run 同口径 · cli-checks 单一实现源)
673
+ const reviewFound = findReview(target, abs);
674
+ if (!reviewFound && !allowNoReview) {
675
+ if (json)
676
+ emitJson(true);
677
+ else
678
+ console.log(`VERIFY: BLOCKED · missing R<n> review · ${label}`);
679
+ fail('', 2);
680
+ }
681
+ const waived = [];
682
+ if (!reviewFound && allowNoReview) {
683
+ waived.push('missing R<n> review(--allow-no-review 豁免)');
684
+ if (!json) {
685
+ console.log('verify: 留痕 · 缺 R<n> 审查文 · --allow-no-review 豁免生效(仍须补审并由维护者签 HG-AUDIT-R1)');
686
+ }
687
+ }
688
+ // DEF-003 阶段二 T5:pre-30 invoke hats 硬闸(required ∩ {10,20,00} · cli-checks 单一实现源,
689
+ // 与 task close 帽集合检查同口径;缺 40 不挡 30 · minimal 无 preRequired 不挡)
690
+ const invoke = checkPre30InvokeHats(target, abs);
691
+ if (!invoke.ok && !allowInvokeGap) {
692
+ if (json)
693
+ emitJson(true);
694
+ else
695
+ console.log(`VERIFY: BLOCKED · missing pre-30 invoke hats: ${invoke.missing.join(',')} · ${label}`);
696
+ fail('', 2);
697
+ }
698
+ if (!invoke.ok && allowInvokeGap) {
699
+ waived.push(`missing pre-30 invoke hats: ${invoke.missing.join(',')}(--allow-invoke-gap 豁免)`);
700
+ if (!json) {
701
+ console.log(`verify: 留痕 · 缺 pre-30 invoke hats: ${invoke.missing.join(',')}(${invoke.source})· --allow-invoke-gap 豁免生效(仍须补落 invoke 快照)`);
702
+ }
703
+ }
704
+ // K3:--with-wiki-lint 追加闸(既有全部硬闸之后 · 复用 lintWikiDeltaMissing 导出 · 默认档 scope=all);
705
+ // target 无 docs/tasks/ 候选目录时 scanned:0/ok:true,不得误 BLOCKED(失败路径表边缘行)
706
+ if (withWikiLint) {
707
+ const wikiLint = lintWikiDeltaMissing(target, { scope: 'all' });
708
+ if (!wikiLint.ok) {
709
+ if (json)
710
+ emitJson(true, waived, wikiLint);
711
+ else {
712
+ printWikiLintIssues(wikiLint);
713
+ console.log(`VERIFY: BLOCKED · wiki_delta 缺口(lint-wiki-delta · scope=all)· ${label}`);
714
+ }
715
+ fail('', 2);
716
+ }
717
+ if (json)
718
+ emitJson(false, waived, wikiLint);
719
+ else {
720
+ console.log(`verify: wiki-lint PASS · scanned: ${wikiLint.scanned} · scope: all`);
721
+ console.log(`VERIFY: PASS · ${label}`);
722
+ }
723
+ return;
724
+ }
725
+ if (json)
726
+ emitJson(false, waived);
727
+ else {
728
+ console.log(`VERIFY: PASS · ${label}`);
729
+ }
730
+ }
731
+ async function cmdTaskLint(args) {
732
+ const json = args.includes('--json');
733
+ let rest = args.filter((a) => a !== '--json');
734
+ const { value: fileArg, rest: r1 } = takeOption(rest, '--file');
735
+ rest = r1;
736
+ if (rest.length > 0)
737
+ fail(`task lint 未知参数: ${rest.join(' ')}`);
738
+ if (!fileArg)
739
+ fail('task lint 须指定 --file PATH');
740
+ const result = lintTaskFile(fileArg, process.cwd());
741
+ if (json) {
742
+ console.log(JSON.stringify(result, null, 2));
743
+ }
744
+ else {
745
+ for (const e of result.errors) {
746
+ console.log(` - [${e.rule}${e.line ? `:L${e.line}` : ''}] ${e.message}`);
747
+ }
748
+ for (const w of result.warnings) {
749
+ console.log(`warn: [${w.rule}${w.line ? `:L${w.line}` : ''}] ${w.message}`);
750
+ }
751
+ console.log(`LINT: ${result.ok ? 'PASS' : 'FAIL'} · ${path.basename(result.file)}`);
752
+ }
753
+ if (!result.ok)
754
+ fail('', 2);
755
+ }
756
+ async function cmdTaskClose(args) {
757
+ const yes = args.includes('--yes');
758
+ // K5(close-done-snapshot · 拟 1.8.0):task close 新增 --json 旗标
759
+ // (done_snapshot 唯绑归档事件 · 20 审 R2 口径裁决)
760
+ const json = args.includes('--json');
761
+ // DEF-003 阶段二 T6 + doc-health:close 守卫豁免旗标
762
+ const allowUnchecked = args.includes('--allow-unchecked');
763
+ const allowInvokeGap = args.includes('--allow-invoke-gap');
764
+ const allowNoReview = args.includes('--allow-no-review');
765
+ const allowKpiGap = args.includes('--allow-kpi-gap');
766
+ const allowExperienceGap = args.includes('--allow-experience-gap');
767
+ const allowWikiGap = args.includes('--allow-wiki-gap');
768
+ const allowNoPrMerge = args.includes('--allow-no-pr-merge');
769
+ const allowNoHub = args.includes('--allow-no-hub');
770
+ let rest = args.filter((a) => a !== '--yes' &&
771
+ a !== '--json' &&
772
+ a !== '--allow-unchecked' &&
773
+ a !== '--allow-invoke-gap' &&
774
+ a !== '--allow-no-review' &&
775
+ a !== '--allow-kpi-gap' &&
776
+ a !== '--allow-experience-gap' &&
777
+ a !== '--allow-wiki-gap' &&
778
+ a !== '--allow-no-pr-merge' &&
779
+ a !== '--allow-no-hub');
780
+ const { value: fileArg, rest: r1 } = takeOption(rest, '--file');
781
+ rest = r1;
782
+ const { value: targetArg, rest: r2 } = takeOption(rest, '--target');
783
+ rest = r2;
784
+ if (rest.length > 0)
785
+ fail(`task close 未知参数: ${rest.join(' ')}`);
786
+ if (!fileArg)
787
+ fail('task close 须指定 --file PATH');
788
+ const abs = path.resolve(process.cwd(), fileArg);
789
+ if (!existsSync(abs))
790
+ fail(`task 文件不存在: ${fileArg}`, 2);
791
+ const content = await readFile(abs, 'utf8');
792
+ const meta = parseHarnessMeta(content);
793
+ const fileSlug = extractTaskSlug(abs);
794
+ const slug = meta.task_slug ?? fileSlug;
795
+ const blockers = [];
796
+ const traces = [];
797
+ const closeAllowFlags = {
798
+ close_invoke: allowInvokeGap ? '--allow-invoke-gap' : undefined,
799
+ close_acceptance: allowUnchecked ? '--allow-unchecked' : undefined,
800
+ close_review: allowNoReview ? '--allow-no-review' : undefined,
801
+ close_kpi: allowKpiGap ? '--allow-kpi-gap' : undefined,
802
+ close_experience: allowExperienceGap ? '--allow-experience-gap' : undefined,
803
+ close_wiki_delta: allowWikiGap ? '--allow-wiki-gap' : undefined,
804
+ close_wiki_promotion: allowWikiGap ? '--allow-wiki-gap' : undefined,
805
+ close_pr_merged: allowNoPrMerge ? '--allow-no-pr-merge' : undefined,
806
+ close_hub_index: allowNoHub ? '--allow-no-hub' : undefined,
807
+ };
808
+ for (const guardId of CLOSE_GUARD_ORDER) {
809
+ const outcome = evalCloseGuard(guardId, abs, content);
810
+ if (!outcome)
811
+ continue;
812
+ if (outcome.status === 'warn') {
813
+ traces.push(`close: warn · ${guardId} · ${outcome.detail}`);
814
+ continue;
815
+ }
816
+ if (outcome.status === 'fail') {
817
+ const flag = closeAllowFlags[guardId];
818
+ if (flag)
819
+ traces.push(`close: 留痕 · ${guardId} · ${outcome.detail} → ${flag} 豁免生效`);
820
+ else
821
+ blockers.push(`${guardId}: ${outcome.detail}`);
822
+ }
823
+ }
824
+ const activeDir = path.dirname(abs);
825
+ const inActive = path.basename(activeDir) === 'active';
826
+ let dest = null;
827
+ if (targetArg && path.basename(targetArg).endsWith('.md')) {
828
+ dest = path.resolve(process.cwd(), targetArg);
829
+ }
830
+ else if (!inActive) {
831
+ blockers.push('源文件不在 */active/ 且未指定 --target(拒绝对 done 文件二次 close)');
832
+ }
833
+ else {
834
+ dest = path.join(path.dirname(activeDir), 'done', path.basename(abs));
835
+ }
836
+ if (dest && existsSync(dest))
837
+ blockers.push(`目标已存在(不覆盖): ${dest}`);
838
+ if (!json) {
839
+ for (const t of traces)
840
+ console.log(t);
841
+ }
842
+ if (blockers.length > 0) {
843
+ if (json) {
844
+ // K5:--json BLOCKED —— 非 0 退出 · JSON 仅错误面(无 done_snapshot 字段)
845
+ console.log(JSON.stringify({ ok: false, status: 'BLOCKED', slug, blockers, traces }, null, 2));
846
+ }
847
+ else {
848
+ for (const b of blockers)
849
+ console.log(` - ${b}`);
850
+ console.log(`CLOSE: BLOCKED · ${slug}`);
851
+ }
852
+ fail('', 2);
853
+ }
854
+ if (!yes) {
855
+ if (json) {
856
+ // K5:--json READY(dry-run · 含豁免 dry-run)—— 未归档 → done_snapshot 恒 null · exit 0
857
+ console.log(JSON.stringify({ ok: true, status: 'READY', slug, dest, traces, done_snapshot: null }, null, 2));
858
+ }
859
+ else {
860
+ console.log('mode: dry-run(未执行 mv · 加 --yes 执行)');
861
+ console.log(`dest: ${dest}`);
862
+ console.log(`CLOSE: READY · ${slug}`);
863
+ }
864
+ return;
865
+ }
866
+ if (!dest)
867
+ fail('无法解析归档目标', 2);
868
+ mkdirSync(path.dirname(dest), { recursive: true });
869
+ renameSync(abs, dest);
870
+ // K5:真归档(renameSync 执行)后构建 done 片段快照 —— 摘录归档文件内
871
+ // '## Harness 元信息' 节原文(extractSection · 归档真值防模板漂移);快照存在性
872
+ // 唯绑归档事件,与豁免旗标无关(20 审 R2 口径裁决:豁免 + --yes → 快照照打)
873
+ const snapshot = buildDoneSnapshot(dest);
874
+ if (json) {
875
+ console.log(JSON.stringify({
876
+ ok: true,
877
+ status: 'PASS',
878
+ slug,
879
+ dest,
880
+ traces,
881
+ done_snapshot: {
882
+ path: snapshot.path,
883
+ harness_meta_section: snapshot.harness_meta_section,
884
+ },
885
+ }, null, 2));
886
+ return;
887
+ }
888
+ console.log(`moved: ${abs} → ${dest}`);
889
+ console.log(`CLOSE: PASS · ${slug}`);
890
+ // K5(W1):PASS 分支 stdout 追加快照块 —— 归档后路径 + 元信息节摘录 + 禁手写提示;
891
+ // 取不到元信息节(异常态)打 canonical 模板占位 + WARN 行;不改 CLOSE: PASS 冻结文案,仅追加
892
+ console.log(`done_snapshot · path: ${snapshot.path}`);
893
+ if (snapshot.warn)
894
+ console.log(`WARN: ${snapshot.warn}`);
895
+ console.log('done_snapshot · harness_meta_section:');
896
+ console.log(snapshot.harness_meta_section);
897
+ console.log('done_snapshot · 禁止手写 done · 以此快照为格式真值');
898
+ }
899
+ const TASK_USAGE = 'task lint --file PATH · task close --file PATH [--yes] [--json] [--allow-unchecked] [--allow-invoke-gap] [--allow-no-review] [--allow-kpi-gap] [--allow-experience-gap] [--allow-wiki-gap] [--allow-no-pr-merge] [--allow-no-hub] · task lint-done · task lint-wiki-delta · task check --file PATH';
900
+ async function cmdTask(args) {
901
+ const [sub, ...rest] = args;
902
+ if (sub === '--help' || sub === '-h') {
903
+ console.log(`用法: ${TASK_USAGE}`);
904
+ return;
905
+ }
906
+ if (!sub)
907
+ fail(`task 子命令未知: (空)\n用法: ${TASK_USAGE}`);
908
+ if (sub === 'lint') {
909
+ await cmdTaskLint(rest);
910
+ return;
911
+ }
912
+ if (sub === 'close') {
913
+ await cmdTaskClose(rest);
914
+ return;
915
+ }
916
+ if (sub === 'lint-done') {
917
+ await cmdTaskLintDone(rest);
918
+ return;
919
+ }
920
+ if (sub === 'lint-wiki-delta') {
921
+ await cmdTaskLintWikiDelta(rest);
922
+ return;
923
+ }
924
+ if (sub === 'check') {
925
+ await cmdTaskCheck(rest);
926
+ return;
927
+ }
928
+ fail(`task 子命令未知: ${sub}\n用法: ${TASK_USAGE}`);
929
+ }
930
+ export async function runCli(argv) {
931
+ const pkgVersion = await readPkgVersion();
932
+ if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
933
+ usage(pkgVersion);
934
+ return;
935
+ }
936
+ if (argv.includes('--version') || argv.includes('-V')) {
937
+ console.log(pkgVersion);
938
+ return;
939
+ }
940
+ const [cmd, ...rest] = argv;
941
+ if (cmd === 'init') {
942
+ await cmdInit(rest, pkgVersion);
943
+ return;
944
+ }
945
+ if (cmd === 'upgrade') {
946
+ await cmdUpgrade(rest, pkgVersion);
947
+ return;
948
+ }
949
+ if (cmd === 'check') {
950
+ await cmdCheck(rest, pkgVersion);
951
+ return;
952
+ }
953
+ if (cmd === 'refresh-ide-blocks') {
954
+ await cmdRefreshIdeBlocks(rest);
955
+ return;
956
+ }
957
+ if (cmd === 'audit') {
958
+ await cmdAudit(rest);
959
+ return;
960
+ }
961
+ if (cmd === 'gate-check') {
962
+ await cmdGateCheck(rest);
963
+ return;
964
+ }
965
+ if (cmd === 'verify') {
966
+ await cmdVerify(rest);
967
+ return;
968
+ }
969
+ if (cmd === 'task') {
970
+ await cmdTask(rest);
971
+ return;
972
+ }
973
+ if (cmd === 'status') {
974
+ await cmdStatus(rest);
975
+ return;
976
+ }
977
+ if (cmd === 'timeline') {
978
+ await cmdTimeline(rest);
979
+ return;
980
+ }
981
+ if (cmd === 'lifecycle') {
982
+ await cmdLifecycle(rest);
983
+ return;
984
+ }
985
+ if (cmd === 'discipline') {
986
+ await cmdDiscipline(rest);
987
+ return;
988
+ }
989
+ if (cmd === 'skills') {
990
+ await cmdSkills(rest);
991
+ return;
992
+ }
993
+ if (cmd === 'host') {
994
+ await cmdHost(rest);
995
+ return;
996
+ }
997
+ if (cmd === 'sync') {
998
+ await cmdSync(rest);
999
+ return;
1000
+ }
1001
+ if (cmd === 'graph') {
1002
+ await cmdGraph(rest);
1003
+ return;
1004
+ }
1005
+ if (cmd === 'wiki') {
1006
+ await cmdWiki(rest);
1007
+ return;
1008
+ }
1009
+ fail(`未知命令: ${cmd}\n`);
1010
+ }
1011
+ function isMain() {
1012
+ const entry = process.argv[1];
1013
+ if (!entry)
1014
+ return false;
1015
+ try {
1016
+ const resolved = path.resolve(entry);
1017
+ const self = fileURLToPath(import.meta.url);
1018
+ if (resolved === self)
1019
+ return true;
1020
+ return path.basename(resolved) === path.basename(self);
1021
+ }
1022
+ catch {
1023
+ return false;
1024
+ }
1025
+ }
1026
+ if (isMain()) {
1027
+ runCli(process.argv.slice(2)).catch((err) => {
1028
+ const e = err;
1029
+ if (e.message)
1030
+ console.error(e.message);
1031
+ process.exit(typeof e.exitCode === 'number' ? e.exitCode : 1);
1032
+ });
1033
+ }
1034
+ //# sourceMappingURL=cli.js.map