Skip to content

Commit 811ebde

Browse files
authored
Merge pull request #16 from stack-wuh/fix/20260917-fix-missing-arg-hints
20260917-fix-missing-arg-hints
2 parents ffff1a4 + e4854b1 commit 811ebde

9 files changed

Lines changed: 102 additions & 38 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Shadow dev workflow 的确定性脚手架 CLI。所有命令走 plan → execute
99
| 命令 | 说明 |
1010
|------|------|
1111
| `repo inspect` | 查看仓库状态(分支、HEAD、脏文件) |
12-
| `change create\|approve` | 创建/批准变更 brief |
12+
| `change create\|approve\|list` | 创建/批准变更 brief;列出活动变更 |
1313
| `issue plan\|execute` | 创建 GitHub issue |
1414
| `branch plan\|execute` | 建功能分支 |
1515
| `sync plan\|execute` | fast-forward 同步上游 |
@@ -33,6 +33,7 @@ CLI 在 stderr 渲染一层人类提示:进场横幅(命令+参数)、收
3333
- 语言解析:`--lang zh|en` > `SHADOW_DEV_LANG` > 系统 locale 自动探测 > 默认 `zh`。非法取值报 `INVALID_LANG`(退出码 2)。
3434
- 关闭提示:`SHADOW_DEV_QUIET=1`(或 `true`)时 stderr 零输出,适合日志管道。
3535
- 语言只影响 stderr 文案;错误 code、JSON 结构、`nextStep` 模板均不本地化。
36+
- 缺必填参数报错时,stderr 逐行列出该命令在命令目录中的完整参数描述(`flag * 说明`,含示例值与来源位置),示例行的占位符与目录一致(如 `--name <change-name>`)——提示与人用 help 共享同一事实源 `lib/commands.mjs`。
3637
- `shadow-dev help` 概览默认只回最小面:`data.help`(命令一览字符串,约 350 字节);agent 需要结构化明细(usage/参数/必填/示例/nextStep)时用 `shadow-dev help --full`。`shadow-dev help <命令>` 查看单组详情,恒定结构化(组面小)。stderr 中文命令表不受 `--full` 影响。
3738

3839
## 平台兼容

‎cli.mjs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ async function handle(r, p, o) {
7171
if (d === 'task' && a === 'set') return { ok: true, command: 'task.set', data: task.set(r, o) }
7272
if (d === 'change' && a === 'create') return { ok: true, command: 'change.create', data: change.create(r, o) }
7373
if (d === 'change' && a === 'approve') return { ok: true, command: 'change.approve', data: change.approve(r, o) }
74+
if (d === 'change' && a === 'list') return { ok: true, command: 'change.list', data: change.list(r) }
7475
if (Object.hasOwn(DOMAINS, d)) {
7576
const verb = a === 'rebuild' ? s : a, c = a === 'rebuild' ? `${d}.rebuild` : d
7677
if (verb === 'plan') return await planDomain(c, DOMAINS[d], r, o)
@@ -98,7 +99,7 @@ try {
9899
}
99100
if (jsonEnabled(o)) out(v)
100101
} catch (e) {
101-
human.error(L, e, p)
102+
human.error(L, e, p, o)
102103
if (jsonEnabled(o)) fail(e.code || e.message, e.message, e.status || 1)
103104
else process.exitCode = e.status || 1
104105
}

‎lib/commands.mjs‎

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// summary/args.desc 提供 zh/en 双版(机器流不本地化,人用层按语言取用);next 为稳定英文命令模板,{x} 由执行结果填充。
33
const f = (flag, required, zh, en) => ({ flag, required, desc: { zh, en } })
44
const c = (usage, zh, en, args, example, next = null) => ({ usage, summary: { zh, en }, args, example, next })
5-
const N = f('--name', true, '变更名,如 20260917-feature-x', 'change name, e.g. 20260917-feature-x')
5+
const N = f('--name', true, '变更名(shadow-docs/changes/ 下的子目录,如 20260917-feature-x)', 'change name (subdirectory under shadow-docs/changes/, e.g. 20260917-feature-x)')
66
const CF = f('--confirm', true, '写操作显式确认', 'explicit confirmation for mutating commands')
77
const PH = f('--plan-hash', false, '缺省时读取 brief 中持久化的 planHash', 'defaults to the planHash persisted in the brief')
88
const T = f('--title', false, '标题,缺省为变更名', 'title, defaults to change name')
@@ -12,30 +12,31 @@ const M = f('--message', false, '提交信息', 'commit message')
1212

1313
export const COMMANDS = {
1414
'repo.inspect': c('repo inspect', '查看仓库状态(分支、HEAD、脏文件)', 'show repository state (branch, HEAD, dirty files)', [], 'shadow-dev repo inspect'),
15-
'change.create': c('change create', '创建变更 brief', 'create a change brief', [N, f('--type', false, 'feature|fix|build|chore|docs|refactor|style|test,默认 feat', 'one of feature|fix|build|chore|docs|refactor|style|test, default feat'), f('--scope', false, '影响范围', 'scope'), f('--base-branch', false, '基线分支,默认 main', 'base branch, default main'), FI, f('--body-file', false, 'brief 正文来源文件', 'file supplying the brief body'), f('--repository', false, 'GitHub owner/repo', 'GitHub owner/repo'), CF], 'shadow-dev change create --name <n> --type feature --confirm', 'change approve --name {name} --confirm'),
16-
'change.approve': c('change approve', '批准 brief(draft → proposed)', 'approve the brief (draft → proposed)', [N, CF], 'shadow-dev change approve --name <n> --confirm', 'branch plan --name {name}'),
17-
'issue.plan': c('issue plan', '预览创建 GitHub issue', 'plan a GitHub issue', [N, T, B, f('--labels', false, '逗号分隔标签', 'comma-separated labels')], 'shadow-dev issue plan --name <n> --title "标题" --labels feature', 'issue execute --name {name} --title "{title}" --body "{body}" --labels {labels} --confirm'),
18-
'issue.execute': c('issue execute', '创建 GitHub issue', 'create the GitHub issue', [N, T, B, f('--labels', false, '逗号分隔标签', 'comma-separated labels'), PH, CF], 'shadow-dev issue execute --name <n> --confirm', null),
19-
'branch.plan': c('branch plan', '预览建功能分支', 'plan creating the feature branch', [N], 'shadow-dev branch plan --name <n>', 'branch execute --name {name} --confirm'),
20-
'branch.execute': c('branch execute', '从基线分支创建并切换', 'create and switch to the feature branch', [N, PH, CF], 'shadow-dev branch execute --name <n> --confirm', null),
21-
'sync.plan': c('sync plan', '预览 fast-forward 同步上游', 'plan a fast-forward sync with upstream', [N], 'shadow-dev sync plan --name <n>', 'sync execute --name {name} --confirm'),
22-
'sync.execute': c('sync execute', 'fetch 后仅 fast-forward 合并上游', 'fetch then ff-only merge upstream', [N, PH, CF], 'shadow-dev sync execute --name <n> --confirm', null),
23-
'conflict.inspect': c('conflict inspect', '检查与其他 active brief 的文件重叠', 'check file overlaps with other active briefs', [N], 'shadow-dev conflict inspect --name <n>', null),
24-
'task.list': c('task list', '列出 brief 任务清单', 'list the brief task checklist', [N], 'shadow-dev task list --name <n>', null),
25-
'task.set': c('task set', '勾选/取消任务', 'tick or untick a task', [N, f('--task', true, '任务 id,如 task-3', 'task id, e.g. task-3'), f('--state', true, 'todo|done', 'todo|done'), CF], 'shadow-dev task set --name <n> --task task-1 --state done --confirm', null),
26-
'review.plan': c('review plan', '预览审查记录', 'plan the review record', [N], 'shadow-dev review plan --name <n>', 'review execute --name {name} --conclusion passed --confirm'),
27-
'review.execute': c('review execute', '写入审查结论与知识评估(任务未全部勾选会被拒绝)', 'persist review conclusion and knowledge action (blocked until all tasks are done)', [N, f('--conclusion', false, 'passed|blocked,默认 passed', 'passed|blocked, default passed'), f('--knowledge', false, '新增|更新|废弃|无需变更', 'knowledge action: 新增|更新|废弃|无需变更'), f('--target', false, '知识卡片路径', 'knowledge card path'), f('--reason', false, '知识动作理由', 'knowledge action reason'), PH, CF], 'shadow-dev review execute --name <n> --conclusion passed --confirm', null),
28-
'commit.plan': c('commit plan', '预览按显式文件列表提交', 'plan an explicit-file-list commit', [N, f('--files', true, '逗号分隔文件列表', 'comma-separated file list'), f('--message', true, '提交信息', 'commit message')], 'shadow-dev commit plan --name <n> --files a.mjs,b.mjs --message "fix: x"', 'commit execute --name {name} --files {files} --message "{message}" --confirm'),
29-
'commit.execute': c('commit execute', '提交并写 checkpoint(参数须与 plan 完全一致)', 'commit and write checkpoint (args must match the plan exactly)', [N, FI, M, PH, CF], 'shadow-dev commit execute --name <n> --files a.mjs --message "fix: x" --confirm', null),
30-
'publish.plan': c('publish plan', '预览推送分支并创建/复用 PR', 'plan pushing the branch and creating/reusing the PR', [N, T, B], 'shadow-dev publish plan --name <n> --title "标题"', 'publish execute --name {name} --title "{title}" --body "{body}" --confirm'),
31-
'publish.execute': c('publish execute', '推送分支并创建/复用 PR(参数须与 plan 完全一致)', 'push and create/reuse the PR (args must match the plan exactly)', [N, T, B, PH, CF], 'shadow-dev publish execute --name <n> --confirm', 'archive plan --name {name}'),
32-
'release.plan': c('release plan', '预览提交+推送+PR 复合发布', 'plan the commit+push+PR composite release', [N, FI, M, T, B], 'shadow-dev release plan --name <n> --files a.mjs --message "feat: x" --title "标题"', 'release execute --name {name} --confirm'),
33-
'release.execute': c('release execute', '执行复合发布(缺省参数回退 brief workflow.release)', 'run the composite release (params default to the stored workflow.release plan)', [N, FI, M, T, B, PH, CF], 'shadow-dev release execute --name <n> --confirm', 'archive plan --name {name}'),
34-
'pr.inspect': c('pr inspect', '查看 brief 关联 PR', 'inspect the PR linked to the brief', [N], 'shadow-dev pr inspect --name <n>', null),
35-
'reconcile.plan': c('reconcile plan', '预览 brief 状态与实际进度对齐', 'plan reconciling brief state with actual progress', [N], 'shadow-dev reconcile plan --name <n>', 'reconcile execute --name {name} --confirm'),
36-
'reconcile.execute': c('reconcile execute', '回写对齐后的状态', 'persist the reconciled state', [N, PH, CF], 'shadow-dev reconcile execute --name <n> --confirm', null),
37-
'archive.plan': c('archive plan', '预览归档(要求 review passed 且 PR merged)', 'plan archiving (requires review passed and PR merged)', [N], 'shadow-dev archive plan --name <n>', 'archive execute --name {name} --confirm'),
38-
'archive.execute': c('archive execute', '移入 archive 并重建 INDEX', 'move into archive and rebuild INDEX', [N, PH, CF], 'shadow-dev archive execute --name <n> --confirm', null),
15+
'change.create': c('change create', '创建变更 brief', 'create a change brief', [N, f('--type', false, 'feature|fix|build|chore|docs|refactor|style|test,默认 feat', 'one of feature|fix|build|chore|docs|refactor|style|test, default feat'), f('--scope', false, '影响范围', 'scope'), f('--base-branch', false, '基线分支,默认 main', 'base branch, default main'), FI, f('--body-file', false, 'brief 正文来源文件', 'file supplying the brief body'), f('--repository', false, 'GitHub owner/repo', 'GitHub owner/repo'), CF], 'shadow-dev change create --name <change-name> --type feature --confirm', 'change approve --name {name} --confirm'),
16+
'change.approve': c('change approve', '批准 brief(draft → proposed)', 'approve the brief (draft → proposed)', [N, CF], 'shadow-dev change approve --name <change-name> --confirm', 'branch plan --name {name}'),
17+
'change.list': c('change list', '列出活动变更的名称、类型、状态与分支', 'list active changes with name, type, status and branch', [], 'shadow-dev change list'),
18+
'issue.plan': c('issue plan', '预览创建 GitHub issue', 'plan a GitHub issue', [N, T, B, f('--labels', false, '逗号分隔标签', 'comma-separated labels')], 'shadow-dev issue plan --name <change-name> --title "标题" --labels feature', 'issue execute --name {name} --title "{title}" --body "{body}" --labels {labels} --confirm'),
19+
'issue.execute': c('issue execute', '创建 GitHub issue', 'create the GitHub issue', [N, T, B, f('--labels', false, '逗号分隔标签', 'comma-separated labels'), PH, CF], 'shadow-dev issue execute --name <change-name> --confirm', null),
20+
'branch.plan': c('branch plan', '预览建功能分支', 'plan creating the feature branch', [N], 'shadow-dev branch plan --name <change-name>', 'branch execute --name {name} --confirm'),
21+
'branch.execute': c('branch execute', '从基线分支创建并切换', 'create and switch to the feature branch', [N, PH, CF], 'shadow-dev branch execute --name <change-name> --confirm', null),
22+
'sync.plan': c('sync plan', '预览 fast-forward 同步上游', 'plan a fast-forward sync with upstream', [N], 'shadow-dev sync plan --name <change-name>', 'sync execute --name {name} --confirm'),
23+
'sync.execute': c('sync execute', 'fetch 后仅 fast-forward 合并上游', 'fetch then ff-only merge upstream', [N, PH, CF], 'shadow-dev sync execute --name <change-name> --confirm', null),
24+
'conflict.inspect': c('conflict inspect', '检查与其他 active brief 的文件重叠', 'check file overlaps with other active briefs', [N], 'shadow-dev conflict inspect --name <change-name>', null),
25+
'task.list': c('task list', '列出 brief 任务清单', 'list the brief task checklist', [N], 'shadow-dev task list --name <change-name>', null),
26+
'task.set': c('task set', '勾选/取消任务', 'tick or untick a task', [N, f('--task', true, '任务 id,如 task-3', 'task id, e.g. task-3'), f('--state', true, 'todo|done', 'todo|done'), CF], 'shadow-dev task set --name <change-name> --task task-1 --state done --confirm', null),
27+
'review.plan': c('review plan', '预览审查记录', 'plan the review record', [N], 'shadow-dev review plan --name <change-name>', 'review execute --name {name} --conclusion passed --confirm'),
28+
'review.execute': c('review execute', '写入审查结论与知识评估(任务未全部勾选会被拒绝)', 'persist review conclusion and knowledge action (blocked until all tasks are done)', [N, f('--conclusion', false, 'passed|blocked,默认 passed', 'passed|blocked, default passed'), f('--knowledge', false, '新增|更新|废弃|无需变更', 'knowledge action: 新增|更新|废弃|无需变更'), f('--target', false, '知识卡片路径', 'knowledge card path'), f('--reason', false, '知识动作理由', 'knowledge action reason'), PH, CF], 'shadow-dev review execute --name <change-name> --conclusion passed --confirm', null),
29+
'commit.plan': c('commit plan', '预览按显式文件列表提交', 'plan an explicit-file-list commit', [N, f('--files', true, '逗号分隔文件列表', 'comma-separated file list'), f('--message', true, '提交信息', 'commit message')], 'shadow-dev commit plan --name <change-name> --files a.mjs,b.mjs --message "fix: x"', 'commit execute --name {name} --files {files} --message "{message}" --confirm'),
30+
'commit.execute': c('commit execute', '提交并写 checkpoint(参数须与 plan 完全一致)', 'commit and write checkpoint (args must match the plan exactly)', [N, FI, M, PH, CF], 'shadow-dev commit execute --name <change-name> --files a.mjs --message "fix: x" --confirm', null),
31+
'publish.plan': c('publish plan', '预览推送分支并创建/复用 PR', 'plan pushing the branch and creating/reusing the PR', [N, T, B], 'shadow-dev publish plan --name <change-name> --title "标题"', 'publish execute --name {name} --title "{title}" --body "{body}" --confirm'),
32+
'publish.execute': c('publish execute', '推送分支并创建/复用 PR(参数须与 plan 完全一致)', 'push and create/reuse the PR (args must match the plan exactly)', [N, T, B, PH, CF], 'shadow-dev publish execute --name <change-name> --confirm', 'archive plan --name {name}'),
33+
'release.plan': c('release plan', '预览提交+推送+PR 复合发布', 'plan the commit+push+PR composite release', [N, FI, M, T, B], 'shadow-dev release plan --name <change-name> --files a.mjs --message "feat: x" --title "标题"', 'release execute --name {name} --confirm'),
34+
'release.execute': c('release execute', '执行复合发布(缺省参数回退 brief workflow.release)', 'run the composite release (params default to the stored workflow.release plan)', [N, FI, M, T, B, PH, CF], 'shadow-dev release execute --name <change-name> --confirm', 'archive plan --name {name}'),
35+
'pr.inspect': c('pr inspect', '查看 brief 关联 PR', 'inspect the PR linked to the brief', [N], 'shadow-dev pr inspect --name <change-name>', null),
36+
'reconcile.plan': c('reconcile plan', '预览 brief 状态与实际进度对齐', 'plan reconciling brief state with actual progress', [N], 'shadow-dev reconcile plan --name <change-name>', 'reconcile execute --name {name} --confirm'),
37+
'reconcile.execute': c('reconcile execute', '回写对齐后的状态', 'persist the reconciled state', [N, PH, CF], 'shadow-dev reconcile execute --name <change-name> --confirm', null),
38+
'archive.plan': c('archive plan', '预览归档(要求 review passed 且 PR merged)', 'plan archiving (requires review passed and PR merged)', [N], 'shadow-dev archive plan --name <change-name>', 'archive execute --name {name} --confirm'),
39+
'archive.execute': c('archive execute', '移入 archive 并重建 INDEX', 'move into archive and rebuild INDEX', [N, PH, CF], 'shadow-dev archive execute --name <change-name> --confirm', null),
3940
'index.rebuild.plan': c('index rebuild plan', '预览变更索引重建', 'plan rebuilding the change index', [], 'shadow-dev index rebuild plan', 'index rebuild execute --plan-hash {planHash} --confirm'),
4041
'index.rebuild.execute': c('index rebuild execute', '重建 INDEX.md(无 brief 域,--plan-hash 为唯一凭证)', 'rebuild INDEX.md (briefless command: --plan-hash is the only credential)', [f('--plan-hash', true, 'index rebuild plan 的输出', 'hash from index rebuild plan'), CF], 'shadow-dev index rebuild execute --plan-hash <hash> --confirm', null),
4142
}

‎lib/domains/change.mjs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { existsSync } from 'node:fs'
1+
import { existsSync, readdirSync } from 'node:fs'
2+
import { join } from 'node:path'
23
import { brief, write, ap } from '../brief.mjs'
34
import { name, confirm, readBody, fileList } from '../input.mjs'
45
import { err } from '../errors.mjs'
@@ -28,6 +29,20 @@ export function create(r, o) {
2829
return { name: n, path: b.path }
2930
}
3031

32+
// 活动变更发现入口:变更名即 shadow-docs/changes/ 下子目录名;archive 与解析失败的目录跳过(与 indexer 同规则)
33+
export function list(r) {
34+
const base = join(r, 'shadow-docs', 'changes'), changes = []
35+
if (existsSync(base)) for (const e of readdirSync(base, { withFileTypes: true })) {
36+
if (!e.isDirectory() || e.name === 'archive') continue
37+
try {
38+
const d = brief(r, e.name).data
39+
changes.push({ name: d.name ?? e.name, type: d.type ?? null, status: d.status ?? null, branch: d.branch ?? null })
40+
} catch {}
41+
}
42+
changes.sort((a, b) => a.name.localeCompare(b.name))
43+
return { changes }
44+
}
45+
3146
export function approve(r, o) {
3247
confirm(o)
3348
const b = brief(r, name(o))

‎lib/human.mjs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,17 @@ export function done(L, v, ms) {
1414
if (v.data?.nextStep) w(ui(L, 'next', { step: v.data.nextStep }))
1515
}
1616

17-
export function error(L, e, p) {
17+
// 参数行单一格式源:help 详情与缺参错误共用,内容恒从命令目录派生
18+
const argLine = (L, a) => ` ${a.flag}${a.required ? ' *' : ' '} ${a.desc[L] ?? a.desc.zh}`
19+
20+
export function error(L, e, p, o = {}) {
1821
const code = e.code || e.message
1922
w(ui(L, 'error', { code, hint: hint(L, code) }))
2023
const spec = lookup(p)
21-
if (spec) w(ui(L, 'example', { example: spec.example }))
24+
if (!spec) return
25+
// 必填参数缺失时逐行列出目录中该参数的完整描述(flag → option key 即去掉 -- 前缀)
26+
for (const a of spec.args) if (a.required && o[a.flag.slice(2)] === undefined) w(argLine(L, a))
27+
w(ui(L, 'example', { example: spec.example }))
2228
}
2329

2430
export function printHelp(L, v) {
@@ -34,7 +40,7 @@ export function printHelp(L, v) {
3440
}
3541
for (const e of Object.values(v.data.commands)) {
3642
w(` ${e.usage} — ${e.summary[L] ?? e.summary.zh}`)
37-
for (const a of e.args) w(` ${a.flag}${a.required ? ' *' : ' '} ${a.desc[L] ?? a.desc.zh}`)
43+
for (const a of e.args) w(argLine(L, a))
3844
w(ui(L, 'example', { example: e.example }))
3945
}
4046
}

0 commit comments

Comments
 (0)