helloagents 3.0.32 → 3.0.35

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 (59) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +3 -4
  3. package/README.md +72 -73
  4. package/README_CN.md +72 -73
  5. package/bootstrap-lite.md +10 -12
  6. package/bootstrap.md +22 -24
  7. package/gemini-extension.json +1 -1
  8. package/install.ps1 +21 -3
  9. package/install.sh +19 -2
  10. package/package.json +2 -2
  11. package/scripts/capability-registry.mjs +5 -3
  12. package/scripts/cli-doctor-codex.mjs +150 -1
  13. package/scripts/cli-doctor-render.mjs +2 -1
  14. package/scripts/cli-lifecycle-hosts.mjs +76 -34
  15. package/scripts/cli-lifecycle.mjs +50 -15
  16. package/scripts/cli-messages.mjs +5 -5
  17. package/scripts/delivery-gate-messages.mjs +5 -4
  18. package/scripts/delivery-gate.mjs +11 -22
  19. package/scripts/guard.mjs +1 -1
  20. package/scripts/notify-closeout.mjs +61 -22
  21. package/scripts/notify-context.mjs +6 -6
  22. package/scripts/notify-payload.mjs +8 -0
  23. package/scripts/notify-route.mjs +1 -1
  24. package/scripts/notify-ui.mjs +14 -1
  25. package/scripts/notify.mjs +80 -4
  26. package/scripts/plan-contract.mjs +10 -14
  27. package/scripts/project-session-cleanup.mjs +45 -31
  28. package/scripts/qa-review-state.mjs +313 -0
  29. package/scripts/ralph-loop.mjs +86 -14
  30. package/scripts/runtime-scope.mjs +1 -3
  31. package/scripts/session-capsule.mjs +51 -13
  32. package/scripts/state-document.mjs +77 -0
  33. package/scripts/workflow-core.mjs +13 -19
  34. package/scripts/workflow-plan-files.mjs +1 -1
  35. package/scripts/workflow-recommendation.mjs +55 -67
  36. package/scripts/workflow-state.mjs +8 -8
  37. package/skills/commands/auto/SKILL.md +12 -12
  38. package/skills/commands/build/SKILL.md +9 -10
  39. package/skills/commands/commit/SKILL.md +1 -1
  40. package/skills/commands/help/SKILL.md +11 -13
  41. package/skills/commands/init/SKILL.md +18 -9
  42. package/skills/commands/loop/SKILL.md +70 -96
  43. package/skills/commands/plan/SKILL.md +7 -8
  44. package/skills/commands/prd/SKILL.md +3 -3
  45. package/skills/commands/qa/SKILL.md +49 -0
  46. package/skills/hello-ui/SKILL.md +3 -3
  47. package/skills/helloagents/SKILL.md +12 -15
  48. package/skills/qa-review/SKILL.md +92 -0
  49. package/templates/plans/contract.json +4 -7
  50. package/templates/plans/plan.md +1 -1
  51. package/templates/plans/tasks.md +1 -1
  52. package/templates/verify.yaml +1 -1
  53. package/scripts/review-state.mjs +0 -193
  54. package/scripts/verify-state.mjs +0 -175
  55. package/skills/commands/global/SKILL.md +0 -71
  56. package/skills/commands/verify/SKILL.md +0 -46
  57. package/skills/commands/wiki/SKILL.md +0 -57
  58. package/skills/hello-review/SKILL.md +0 -42
  59. package/skills/hello-verify/SKILL.md +0 -144
@@ -10,6 +10,7 @@ import {
10
10
  writeActiveProjectSession,
11
11
  writeJsonFileAtomic,
12
12
  } from './runtime-scope.mjs'
13
+ import { readStateDocument, writeStateDocument } from './state-document.mjs'
13
14
 
14
15
  export { getRuntimeScope }
15
16
 
@@ -68,7 +69,7 @@ function getScope(cwd, options = {}) {
68
69
  }
69
70
 
70
71
  export function getSessionCapsulePath(cwd = process.cwd(), options = {}) {
71
- return getScope(cwd, options).capsulePath
72
+ return getScope(cwd, options).statePath
72
73
  }
73
74
 
74
75
  export function getSessionEventsPath(cwd = process.cwd(), options = {}) {
@@ -93,8 +94,9 @@ export function getSessionArtifactRelativePath(cwd, fileName, options = {}) {
93
94
 
94
95
  export function readSessionCapsule(cwd = process.cwd(), options = {}) {
95
96
  const scope = getScope(cwd, options)
96
- const capsule = readJsonFile(scope.capsulePath, null)
97
- if (!capsule || typeof capsule !== 'object') return buildEmptyCapsule(scope)
97
+ const { metadata } = readStateDocument(scope.statePath)
98
+ const capsule = metadata && typeof metadata === 'object' ? metadata : null
99
+ if (!capsule || Array.isArray(capsule)) return buildEmptyCapsule(scope)
98
100
  return {
99
101
  ...buildEmptyCapsule(scope),
100
102
  ...capsule,
@@ -109,7 +111,24 @@ export function readSessionCapsule(cwd = process.cwd(), options = {}) {
109
111
  }
110
112
 
111
113
  export function writeSessionCapsule(cwd, capsule, options = {}) {
112
- const scope = getScope(cwd, options)
114
+ const normalizedOptions = normalizeOptions(options)
115
+ const scope = getScope(cwd, normalizedOptions)
116
+ const currentDocument = readStateDocument(scope.statePath)
117
+ const hasBody = Boolean(currentDocument.body && currentDocument.body.trim())
118
+ if (!hasBody && normalizedOptions.ensureProjectLocal !== true && !existsSync(scope.statePath)) {
119
+ return {
120
+ ...buildEmptyCapsule(scope),
121
+ ...capsule,
122
+ scope: scope.scope,
123
+ key: scope.key,
124
+ cwd: scope.cwd,
125
+ branch: scope.branch,
126
+ workspace: scope.workspace || scope.branch,
127
+ session: scope.session,
128
+ sessionMode: scope.sessionMode,
129
+ updatedAt: new Date().toISOString(),
130
+ }
131
+ }
113
132
  const nextCapsule = {
114
133
  ...buildEmptyCapsule(scope),
115
134
  ...capsule,
@@ -122,9 +141,12 @@ export function writeSessionCapsule(cwd, capsule, options = {}) {
122
141
  sessionMode: scope.sessionMode,
123
142
  updatedAt: new Date().toISOString(),
124
143
  }
125
- writeJsonFileAtomic(scope.capsulePath, nextCapsule)
144
+ writeStateDocument(scope.statePath, {
145
+ metadata: nextCapsule,
146
+ body: currentDocument.body,
147
+ })
126
148
  writeActiveProjectSession(scope, {
127
- env: normalizeOptions(options).env,
149
+ env: normalizedOptions.env,
128
150
  })
129
151
  return nextCapsule
130
152
  }
@@ -151,8 +173,8 @@ export function writeCapsuleSection(cwd, section, value, options = {}) {
151
173
  }
152
174
 
153
175
  export function clearCapsuleSection(cwd, section, options = {}) {
154
- const capsulePath = getSessionCapsulePath(cwd, options)
155
- if (!existsSync(capsulePath)) return false
176
+ const statePath = getSessionCapsulePath(cwd, options)
177
+ if (!existsSync(statePath)) return false
156
178
 
157
179
  const capsule = readSessionCapsule(cwd, options)
158
180
  if (!Object.prototype.hasOwnProperty.call(capsule, section)) return false
@@ -180,6 +202,13 @@ export function appendSessionEvent(cwd, eventPayload, options = {}) {
180
202
  const eventName = eventPayload?.event || ''
181
203
  if (!eventName) return ''
182
204
 
205
+ writeActiveProjectSession(scope, {
206
+ host: eventPayload.host || '',
207
+ source: eventPayload.source || eventName,
208
+ env: scopedOptions.env,
209
+ })
210
+ if (!shouldRecordSessionEvents(scopedOptions)) return ''
211
+
183
212
  mkdirSync(dirname(scope.eventsPath), { recursive: true })
184
213
  const payload = {
185
214
  ts: new Date().toISOString(),
@@ -192,17 +221,13 @@ export function appendSessionEvent(cwd, eventPayload, options = {}) {
192
221
  encoding: 'utf-8',
193
222
  flag: 'a',
194
223
  })
195
- writeActiveProjectSession(scope, {
196
- host: eventPayload.host || '',
197
- source: eventPayload.source || eventName,
198
- env: scopedOptions.env,
199
- })
200
224
  return scope.eventsPath
201
225
  }
202
226
 
203
227
  export function resetSessionEvents(cwd, options = {}) {
204
228
  const scope = getScope(cwd, options)
205
229
  if (scope.scope === 'project-session' && !scope.active) return ''
230
+ if (!shouldRecordSessionEvents(options)) return ''
206
231
  mkdirSync(dirname(scope.eventsPath), { recursive: true })
207
232
  writeFileSync(scope.eventsPath, '', 'utf-8')
208
233
  return scope.eventsPath
@@ -242,3 +267,16 @@ export function clearSessionArtifact(cwd, fileName, options = {}) {
242
267
  export function removeSessionCapsule(cwd, options = {}) {
243
268
  removeRuntimeFile(getSessionCapsulePath(cwd, options))
244
269
  }
270
+
271
+ function shouldRecordSessionEvents(options = {}) {
272
+ const normalizedOptions = normalizeOptions(options)
273
+ const payload = normalizedOptions.payload || {}
274
+ if (normalizedOptions.traceEvents === true || payload.traceEvents === true || payload._helloagentsTraceEvents === true) {
275
+ return true
276
+ }
277
+
278
+ const raw = String(normalizedOptions.env?.HELLOAGENTS_TRACE_EVENTS || process.env.HELLOAGENTS_TRACE_EVENTS || '')
279
+ .trim()
280
+ .toLowerCase()
281
+ return raw === '1' || raw === 'true' || raw === 'yes'
282
+ }
@@ -0,0 +1,77 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { dirname } from 'node:path'
3
+
4
+ const STATE_META_BEGIN = '<!-- HELLOAGENTS_STATE_META'
5
+ const STATE_META_END = 'HELLOAGENTS_STATE_META -->'
6
+
7
+ function normalizeText(content = '') {
8
+ return String(content || '').replace(/^\uFEFF/, '')
9
+ }
10
+
11
+ function splitLines(content = '') {
12
+ return normalizeText(content).replace(/\r\n/g, '\n').split('\n')
13
+ }
14
+
15
+ export function parseStateDocument(content = '') {
16
+ const lines = splitLines(content)
17
+ if (lines[0]?.trim() !== STATE_META_BEGIN) {
18
+ return {
19
+ hasMetadata: false,
20
+ metadata: null,
21
+ body: normalizeText(content),
22
+ }
23
+ }
24
+
25
+ const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === STATE_META_END)
26
+ if (endIndex < 0) {
27
+ return {
28
+ hasMetadata: false,
29
+ metadata: null,
30
+ body: normalizeText(content),
31
+ }
32
+ }
33
+
34
+ const metadataText = lines.slice(1, endIndex).join('\n').trim()
35
+ const body = lines.slice(endIndex + 1).join('\n').replace(/^\n+/, '')
36
+ try {
37
+ return {
38
+ hasMetadata: true,
39
+ metadata: JSON.parse(metadataText),
40
+ body,
41
+ }
42
+ } catch {
43
+ return {
44
+ hasMetadata: false,
45
+ metadata: null,
46
+ body,
47
+ }
48
+ }
49
+ }
50
+
51
+ export function readStateDocument(filePath) {
52
+ if (!filePath || !existsSync(filePath)) {
53
+ return {
54
+ hasMetadata: false,
55
+ metadata: null,
56
+ body: '',
57
+ }
58
+ }
59
+
60
+ return parseStateDocument(readFileSync(filePath, 'utf-8'))
61
+ }
62
+
63
+ export function composeStateDocument({ metadata = {}, body = '' } = {}) {
64
+ const normalizedBody = normalizeText(body).replace(/^\n+/, '')
65
+ return [
66
+ STATE_META_BEGIN,
67
+ JSON.stringify(metadata, null, 2),
68
+ STATE_META_END,
69
+ '',
70
+ normalizedBody,
71
+ ].join('\n').replace(/\n+$/, '\n')
72
+ }
73
+
74
+ export function writeStateDocument(filePath, { metadata = {}, body = '' } = {}) {
75
+ mkdirSync(dirname(filePath), { recursive: true })
76
+ writeFileSync(filePath, composeStateDocument({ metadata, body }), 'utf-8')
77
+ }
@@ -20,6 +20,7 @@ function describeStateLabel(state) {
20
20
  }
21
21
  return '当前会话的状态文件'
22
22
  }
23
+
23
24
  export function classifyPlan(plan) {
24
25
  if (!plan) {
25
26
  return {
@@ -64,24 +65,14 @@ export function classifyPlan(plan) {
64
65
  }
65
66
  }
66
67
 
67
- export function determineVerifyMode(plan) {
68
+ export function determineQaMode(plan) {
68
69
  if (!plan) return null
69
70
 
70
71
  if (plan.contractIssues.length > 0) {
71
72
  return {
72
73
  mode: 'metadata-first',
73
- reason: '方案包缺少可信的结构化契约',
74
- guidance: '验证分流:当前还不适合直接进入 reviewer / tester;先回到 ~plan / ~prd 补齐 `contract.json`,明确 `verifyMode`、`reviewerFocus` `testerFocus`。',
75
- }
76
- }
77
-
78
- if (plan.contract?.verifyMode === 'review-first') {
79
- const reviewerFocus = plan.contract.reviewerFocus.join(';')
80
- const testerFocus = plan.contract.testerFocus.join(';')
81
- return {
82
- mode: 'review-first',
83
- reason: '方案契约已明确要求审查优先',
84
- guidance: `验证分流:当前更适合审查优先;先执行 reviewer / hello-review 范围审查,再交给 tester / hello-verify 跑完整验证。${reviewerFocus ? ` reviewer 重点:${reviewerFocus}。` : ''}${testerFocus ? ` tester 重点:${testerFocus}。` : ''}`.trim(),
74
+ reason: '方案包缺少可信的结构化 QA 契约',
75
+ guidance: '质量闭环:当前还不适合直接进入 ~qa;先回到 ~plan / ~prd 补齐 `contract.json`,明确 `qaMode` `qaFocus`。',
85
76
  }
86
77
  }
87
78
 
@@ -89,14 +80,16 @@ export function determineVerifyMode(plan) {
89
80
  return {
90
81
  mode: 'metadata-first',
91
82
  reason: 'tasks.md 仍缺少可信的任务元数据',
92
- guidance: '验证分流:当前还不适合直接进入 reviewer / tester;先补齐 tasks.md 中每个任务的“涉及文件”“完成标准”和“验证方式”。',
83
+ guidance: '质量闭环:当前还不适合直接进入 ~qa;先补齐 tasks.md 中每个任务的“涉及文件”“完成标准”和“验证方式”。',
93
84
  }
94
85
  }
95
86
 
87
+ const qaMode = plan.contract?.qaMode === 'deep' ? 'deep' : 'standard'
88
+ const qaFocus = (plan.contract?.qaFocus || []).join(';')
96
89
  return {
97
- mode: 'test-first',
98
- reason: '方案契约已明确验证主路径,且任务契约已完整',
99
- guidance: `验证分流:当前更适合测试优先;先执行 tester / hello-verify 跑完整验证,再针对失败点或关键边界补充 hello-review。${plan.contract?.testerFocus?.length ? ` tester 重点:${plan.contract.testerFocus.join(';')}。` : ''}`.trim(),
90
+ mode: qaMode,
91
+ reason: qaMode === 'deep' ? '方案契约已明确要求深度 qa-review' : '方案契约已明确要求统一 qa-review',
92
+ guidance: `质量闭环:当前统一使用 qa-review;先完成阻断性质量审查,再运行验证命令、修复失败项并留下最新 qa-review 证据。${qaFocus ? ` QA 重点:${qaFocus}。` : ''}`.trim(),
100
93
  }
101
94
  }
102
95
 
@@ -131,12 +124,13 @@ function collectStateSyncIssues(snapshot) {
131
124
  return issues
132
125
  }
133
126
 
134
- export function buildVerifyModeHintFromSnapshot(snapshot) {
127
+ export function buildQaFocusHintFromSnapshot(snapshot) {
135
128
  const plan = getTargetPlans(snapshot)[0]
136
129
  if (!plan) return ''
137
130
  if (!plan.planSections['风险与验证'] && plan.taskSummary.total === 0) return ''
138
- return determineVerifyMode(plan)?.guidance || ''
131
+ return determineQaMode(plan)?.guidance || ''
139
132
  }
133
+
140
134
  export function buildStateSyncHintFromSnapshot(snapshot) {
141
135
  const issues = collectStateSyncIssues(snapshot)
142
136
  if (issues.length === 0) return ''
@@ -18,7 +18,7 @@ const PLAN_TEMPLATE_MARKERS = {
18
18
  /# \{项目\/功能名称\} — 实施规划/,
19
19
  /\[本次要解决的问题、范围边界、验收目标\]/,
20
20
  /\[关键决策及理由\]/,
21
- /\[功能完成时必须为真的条件、关键验收点、reviewer \/ tester 关注边界\]/,
21
+ /\[功能完成时必须为真的条件、关键验收点、qaMode qaFocus\]/,
22
22
  ],
23
23
  'tasks.md': [
24
24
  /# \{项目\/功能名称\} — 任务分解/,
@@ -1,23 +1,21 @@
1
1
  import { getCloseoutEvidenceStatus } from './closeout-state.mjs'
2
2
  import { getAdvisorEvidenceStatus } from './advisor-state.mjs'
3
3
  import { getAdvisorRequirement, getVisualValidationRequirement } from './plan-contract.mjs'
4
- import { getReviewEvidenceStatus } from './review-state.mjs'
4
+ import { getQaReviewEvidenceStatus } from './qa-review-state.mjs'
5
5
  import { getVisualEvidenceStatus } from './visual-state.mjs'
6
- import { getVerifyEvidenceStatus } from './verify-state.mjs'
7
6
  import {
8
7
  classifyPlan,
9
- determineVerifyMode,
8
+ determineQaMode,
10
9
  getTargetPlans,
11
10
  normalizeTaskFile,
12
11
  } from './workflow-core.mjs'
13
12
 
14
13
  function getClosedPlanEvidenceStatus(cwd, plan, options = {}) {
15
- const verifyMode = determineVerifyMode(plan)
14
+ const qaMode = determineQaMode(plan)
16
15
  const advisorRequirement = getAdvisorRequirement(plan.contract)
17
16
  const visualRequirement = getVisualValidationRequirement(plan.contract)
18
- const verificationStatus = getVerifyEvidenceStatus(cwd, options)
19
- const reviewStatus = getReviewEvidenceStatus(cwd, {
20
- required: verifyMode?.mode === 'review-first',
17
+ const qaStatus = getQaReviewEvidenceStatus(cwd, {
18
+ required: qaMode?.mode !== 'metadata-first',
21
19
  ...options,
22
20
  })
23
21
  const advisorStatus = getAdvisorEvidenceStatus(cwd, {
@@ -31,26 +29,23 @@ function getClosedPlanEvidenceStatus(cwd, plan, options = {}) {
31
29
  states: visualRequirement.states,
32
30
  ...options,
33
31
  })
34
- const verifyReady = !verificationStatus.required || verificationStatus.status === 'valid'
35
- const reviewReady = !reviewStatus.required || reviewStatus.status === 'valid'
32
+ const qaReady = !qaStatus.required || qaStatus.status === 'valid'
36
33
  const advisorReady = !advisorStatus.required || advisorStatus.status === 'valid'
37
34
  const visualReady = !visualStatus.required || visualStatus.status === 'valid'
38
35
  const closeoutStatus = getCloseoutEvidenceStatus(cwd, {
39
- required: verifyReady && reviewReady && advisorReady && visualReady,
36
+ required: qaReady && advisorReady && visualReady,
40
37
  ...options,
41
38
  })
42
39
 
43
40
  return {
44
- verifyMode,
41
+ qaMode,
45
42
  advisorRequirement,
46
43
  visualRequirement,
47
- verificationStatus,
48
- reviewStatus,
44
+ qaStatus,
49
45
  advisorStatus,
50
46
  visualStatus,
51
47
  closeoutStatus,
52
- verifyReady,
53
- reviewReady,
48
+ qaReady,
54
49
  advisorReady,
55
50
  visualReady,
56
51
  closeoutReady: !closeoutStatus.required || closeoutStatus.status === 'valid',
@@ -63,7 +58,7 @@ function buildConsolidateAction(recommendation) {
63
58
  phase: 'consolidate',
64
59
  mode: recommendation.mode,
65
60
  routeHint: recommendation.guidance,
66
- gateHint: '交付把关:审查与验证证据已满足;先写当前会话 `artifacts/closeout.json` 记录需求覆盖与交付清单,再更新 `state_path` 并归档后才可交付。',
61
+ gateHint: '交付把关:qa-review 与附加证据已满足;先写当前会话 `artifacts/closeout.json` 记录需求覆盖与交付清单,再更新 `state_path` 并归档后才可交付。',
67
62
  }
68
63
  }
69
64
 
@@ -75,8 +70,8 @@ function buildConsolidateAction(recommendation) {
75
70
  }
76
71
  }
77
72
 
78
- function buildVerifyAction(plan, verifyMode) {
79
- if (!verifyMode) return null
73
+ function buildQaAction(plan, qaMode) {
74
+ if (!qaMode) return null
80
75
  const advisorRequirement = getAdvisorRequirement(plan.contract)
81
76
  const visualRequirement = getVisualValidationRequirement(plan.contract)
82
77
  const extraChecks = []
@@ -87,30 +82,23 @@ function buildVerifyAction(plan, verifyMode) {
87
82
  extraChecks.push('完成视觉验收并写入当前会话 `artifacts/visual.json`')
88
83
  }
89
84
  const gateSuffix = extraChecks.length > 0 ? ` ${extraChecks.join(',')},再进入 CONSOLIDATE。` : ''
90
- if (verifyMode.mode === 'review-first') {
91
- return {
92
- phase: 'verify',
93
- mode: verifyMode.mode,
94
- routeHint: verifyMode.guidance,
95
- gateHint: `交付把关:进入 CONSOLIDATE 前,必须先完成 reviewer / hello-review 范围审查,再完成 tester / hello-verify 全量验证,并留下最新验证证据;两步都通过后才可交付。${gateSuffix}`.trim(),
96
- }
97
- }
98
- if (verifyMode.mode === 'metadata-first') {
85
+
86
+ if (qaMode.mode === 'metadata-first') {
99
87
  return {
100
- phase: 'verify',
101
- mode: verifyMode.mode,
102
- routeHint: verifyMode.guidance,
88
+ phase: 'plan',
89
+ mode: qaMode.mode,
90
+ routeHint: qaMode.guidance,
103
91
  gateHint: plan.contractIssues.length > 0
104
- ? '交付把关:当前还不能进入 CONSOLIDATE;先补齐 `contract.json` 中的 `verifyMode`、`reviewerFocus`、`testerFocus`,再进入 reviewer / tester。'
105
- : '交付把关:当前还不能进入 CONSOLIDATE;先补齐 tasks.md 中每个任务的“涉及文件”“完成标准”和“验证方式”,再进入 reviewer / tester。',
92
+ ? '交付把关:当前还不能进入 CONSOLIDATE;先补齐 `contract.json` 中的 `qaMode` `qaFocus`,再进入 ~qa。'
93
+ : '交付把关:当前还不能进入 CONSOLIDATE;先补齐 tasks.md 中每个任务的“涉及文件”“完成标准”和“验证方式”,再进入 ~qa。',
106
94
  }
107
95
  }
108
96
 
109
97
  return {
110
- phase: 'verify',
111
- mode: verifyMode.mode,
112
- routeHint: verifyMode.guidance,
113
- gateHint: `交付把关:进入 CONSOLIDATE 前,先完成 tester / hello-verify 全量验证并留下最新验证证据,再针对失败点或关键边界补充 hello-review;确认通过后才可交付。${gateSuffix}`.trim(),
98
+ phase: 'qa',
99
+ mode: qaMode.mode,
100
+ routeHint: qaMode.guidance,
101
+ gateHint: `交付把关:进入 CONSOLIDATE 前,必须完成 qa-review 全量质量闭环,并留下最新 qa-review 证据。${gateSuffix}`.trim(),
114
102
  }
115
103
  }
116
104
 
@@ -122,19 +110,19 @@ export function buildDeliveryActionFromSnapshot(snapshot, cwd, recommendation =
122
110
  }
123
111
 
124
112
  const plan = getTargetPlans(snapshot)[0]
125
- if (recommendation.nextCommand === 'verify' && plan) {
126
- return buildVerifyAction(plan, determineVerifyMode(plan))
113
+ if (recommendation.nextCommand === 'qa' && plan) {
114
+ return buildQaAction(plan, determineQaMode(plan))
127
115
  }
128
116
  if (recommendation.nextCommand === 'build') {
129
117
  return {
130
118
  phase: 'build',
131
- gateHint: '交付把关:当前还不能报告完成;先回到 ~build 完成剩余任务,再进入 ~verify。',
119
+ gateHint: '交付把关:当前还不能报告完成;先回到 ~build 完成剩余任务,再进入 ~qa。',
132
120
  }
133
121
  }
134
122
  if (recommendation.nextCommand === 'plan') {
135
123
  return {
136
124
  phase: 'plan',
137
- gateHint: '交付把关:当前还不能报告完成;先回到 ~plan 修复或补齐当前方案包,再进入 ~build / ~verify。',
125
+ gateHint: '交付把关:当前还不能报告完成;先回到 ~plan 修复或补齐当前方案包,再进入 ~build / ~qa。',
138
126
  }
139
127
  }
140
128
 
@@ -152,13 +140,13 @@ function buildPlanRecommendation(scopeLabel, plan, classification) {
152
140
  status: classification.status,
153
141
  details: classification.details,
154
142
  nextCommand: 'plan',
155
- nextPath: '~plan -> ~build / ~verify',
143
+ nextPath: '~plan -> ~build / ~qa',
156
144
  summary: classification.status === 'incomplete'
157
145
  ? `${scopeLabel} "${plan.planName}" 仍不完整(${classification.details.join(';')})。`
158
146
  : `${scopeLabel} "${plan.planName}" 尚未形成可执行任务清单。`,
159
147
  guidance: classification.status === 'incomplete'
160
- ? '优先先走 ~plan 修复或补全当前方案包,再进入实现或验证;不要把不完整的结构化产物直接当成可交付依据。'
161
- : '先回到 ~plan 补齐 tasks.md 的原子任务,再进入实现、验证或收尾。',
148
+ ? '优先先走 ~plan 修复或补全当前方案包,再进入实现或 qa-review;不要把不完整的结构化产物直接当成可交付依据。'
149
+ : '先回到 ~plan 补齐 tasks.md 的原子任务,再进入实现、qa-review 或收尾。',
162
150
  }
163
151
  }
164
152
 
@@ -169,23 +157,23 @@ function buildInProgressRecommendation(scopeLabel, plan, classification) {
169
157
  status: classification.status,
170
158
  details: classification.details,
171
159
  nextCommand: 'build',
172
- nextPath: '~build -> ~verify',
160
+ nextPath: '~build -> ~qa',
173
161
  summary: `${scopeLabel} "${plan.planName}" 仍有 ${classification.openCount} 个未完成任务。`,
174
- guidance: '若用户是在继续当前功能、落实既有方案、或让你“继续做完”,优先复用现有 requirements.md / plan.md / tasks.md 进入 ~build;完成当前实现后再进入 ~verify。除非用户明确要求重规划或现有方案已失效,不要重新回到 ~idea。',
162
+ guidance: '若用户是在继续当前功能、落实既有方案、或让你“继续做完”,优先复用现有 requirements.md / plan.md / tasks.md 进入 ~build;完成当前实现后再进入 ~qa。除非用户明确要求重规划或现有方案已失效,不要重新回到 ~idea。',
175
163
  }
176
164
  }
177
165
 
178
166
  function buildClosedRecommendation(scopeLabel, plan, cwd, options = {}) {
179
167
  const closedPlanEvidence = getClosedPlanEvidenceStatus(cwd, plan, options)
180
- if (closedPlanEvidence.verifyMode?.mode === 'metadata-first') {
168
+ if (closedPlanEvidence.qaMode?.mode === 'metadata-first') {
181
169
  return {
182
170
  scopeLabel,
183
171
  plan,
184
172
  status: 'closed',
185
- nextCommand: 'verify',
186
- nextPath: '~verify -> CONSOLIDATE',
187
- summary: `${scopeLabel} "${plan.planName}" 的任务已全部闭合,但验证契约仍未结构化。`,
188
- guidance: closedPlanEvidence.verifyMode.guidance,
173
+ nextCommand: 'plan',
174
+ nextPath: '~plan -> ~qa',
175
+ summary: `${scopeLabel} "${plan.planName}" 的任务已全部闭合,但 QA 契约仍未结构化。`,
176
+ guidance: closedPlanEvidence.qaMode.guidance,
189
177
  }
190
178
  }
191
179
 
@@ -199,10 +187,10 @@ function buildClosedRecommendation(scopeLabel, plan, cwd, options = {}) {
199
187
  scopeLabel,
200
188
  plan,
201
189
  status: 'closed',
202
- nextCommand: 'verify',
203
- nextPath: '~verify -> CONSOLIDATE',
190
+ nextCommand: 'qa',
191
+ nextPath: '~qa -> CONSOLIDATE',
204
192
  summary: `${scopeLabel} "${plan.planName}" 的任务已闭合,但当前 UI 契约仍要求独立 advisor 复查与视觉验收。`,
205
- guidance: '先在 ~verify 阶段完成独立 advisor / style advisor 复查,并写入当前会话 `artifacts/advisor.json`;再完成视觉验收并写入当前会话 `artifacts/visual.json`,记录 reason、tooling、screensChecked、statesChecked、status 与 summary;两项都通过后再进入 CONSOLIDATE。',
193
+ guidance: '先在 ~qa 阶段完成独立 advisor / style advisor 复查,并写入当前会话 `artifacts/advisor.json`;再完成视觉验收并写入当前会话 `artifacts/visual.json`,记录 reason、tooling、screensChecked、statesChecked、status 与 summary;两项都通过后再进入 CONSOLIDATE。',
206
194
  }
207
195
  }
208
196
 
@@ -211,10 +199,10 @@ function buildClosedRecommendation(scopeLabel, plan, cwd, options = {}) {
211
199
  scopeLabel,
212
200
  plan,
213
201
  status: 'closed',
214
- nextCommand: 'verify',
215
- nextPath: '~verify -> CONSOLIDATE',
202
+ nextCommand: 'qa',
203
+ nextPath: '~qa -> CONSOLIDATE',
216
204
  summary: `${scopeLabel} "${plan.planName}" 的任务已闭合,但当前契约仍要求独立 advisor 复查。`,
217
- guidance: '先在 ~verify 阶段完成独立 advisor / style advisor 复查,并写入当前会话 `artifacts/advisor.json` 记录复查原因、focus、来源与结论;advisor 通过后再进入 CONSOLIDATE。',
205
+ guidance: '先在 ~qa 阶段完成独立 advisor / style advisor 复查,并写入当前会话 `artifacts/advisor.json` 记录复查原因、focus、来源与结论;advisor 通过后再进入 CONSOLIDATE。',
218
206
  }
219
207
  }
220
208
 
@@ -223,25 +211,25 @@ function buildClosedRecommendation(scopeLabel, plan, cwd, options = {}) {
223
211
  scopeLabel,
224
212
  plan,
225
213
  status: 'closed',
226
- nextCommand: 'verify',
227
- nextPath: '~verify -> CONSOLIDATE',
214
+ nextCommand: 'qa',
215
+ nextPath: '~qa -> CONSOLIDATE',
228
216
  summary: `${scopeLabel} "${plan.planName}" 的任务已闭合,但当前 UI 契约仍要求视觉验收。`,
229
- guidance: '先在 ~verify 阶段完成视觉验收,并写入当前会话 `artifacts/visual.json` 记录 reason、tooling、screensChecked、statesChecked、status 与 summary;视觉验收通过后再进入 CONSOLIDATE。',
217
+ guidance: '先在 ~qa 阶段完成视觉验收,并写入当前会话 `artifacts/visual.json` 记录 reason、tooling、screensChecked、statesChecked、status 与 summary;视觉验收通过后再进入 CONSOLIDATE。',
230
218
  }
231
219
  }
232
220
 
233
- if (closedPlanEvidence.verifyReady && closedPlanEvidence.reviewReady && closedPlanEvidence.advisorReady && closedPlanEvidence.visualReady) {
221
+ if (closedPlanEvidence.qaReady && closedPlanEvidence.advisorReady && closedPlanEvidence.visualReady) {
234
222
  return {
235
223
  scopeLabel,
236
224
  plan,
237
225
  status: 'closed',
238
226
  stage: 'consolidate',
239
227
  mode: closedPlanEvidence.closeoutReady ? 'ready' : 'closeout-pending',
240
- nextCommand: 'verify',
228
+ nextCommand: 'qa',
241
229
  nextPath: 'CONSOLIDATE',
242
230
  summary: closedPlanEvidence.closeoutReady
243
231
  ? `${scopeLabel} "${plan.planName}" 的任务与交付证据已闭合。`
244
- : `${scopeLabel} "${plan.planName}" 的任务、审查与验证已闭合。`,
232
+ : `${scopeLabel} "${plan.planName}" 的任务与 qa-review 已闭合。`,
245
233
  guidance: closedPlanEvidence.closeoutReady
246
234
  ? '当前进入 CONSOLIDATE:更新 `state_path`、知识文件并归档方案后即可交付;不要无故重开新的方案包或重新跑一遍无关验证。'
247
235
  : '当前进入 CONSOLIDATE:先写当前会话 `artifacts/closeout.json` 记录需求覆盖与交付清单,再更新 `state_path` 并归档后交付。',
@@ -252,10 +240,10 @@ function buildClosedRecommendation(scopeLabel, plan, cwd, options = {}) {
252
240
  scopeLabel,
253
241
  plan,
254
242
  status: 'closed',
255
- nextCommand: 'verify',
256
- nextPath: '~verify -> CONSOLIDATE',
243
+ nextCommand: 'qa',
244
+ nextPath: '~qa -> CONSOLIDATE',
257
245
  summary: `${scopeLabel} "${plan.planName}" 的任务已全部闭合。`,
258
- guidance: '若用户是在做收尾、验真、复查或准备交付,优先走 ~verify 或 CONSOLIDATE;不要无故重开新的方案包。',
246
+ guidance: '若用户是在做收尾、验真、复查或准备交付,优先走 ~qa 或 CONSOLIDATE;不要无故重开新的方案包。',
259
247
  }
260
248
  }
261
249
 
@@ -323,9 +311,9 @@ export function buildOrchestrationHintFromSnapshot(snapshot, cwd, recommendation
323
311
  if (recommendation.nextCommand === 'build') {
324
312
  return buildBuildOrchestrationHint(plan)
325
313
  }
326
- if (recommendation.nextCommand === 'verify' && plan.taskSummary.total >= 1) {
314
+ if (recommendation.nextCommand === 'qa' && plan.taskSummary.total >= 1) {
327
315
  const action = buildDeliveryActionFromSnapshot(snapshot, cwd, recommendation)
328
- if (action?.phase === 'verify') {
316
+ if (action?.phase === 'qa') {
329
317
  return `编排提示:当前已进入收尾;${[action.routeHint, action.gateHint].filter(Boolean).join(' ')}`
330
318
  }
331
319
  }
@@ -1,10 +1,10 @@
1
1
  import { basename } from 'node:path'
2
2
 
3
3
  import {
4
+ buildQaFocusHintFromSnapshot,
4
5
  buildStateRoleHintFromSnapshot,
5
6
  buildStateSyncHintFromSnapshot,
6
7
  buildUiContractHint,
7
- buildVerifyModeHintFromSnapshot,
8
8
  getWorkflowSnapshot,
9
9
  readStateSnapshot,
10
10
  listPlanPackages,
@@ -54,14 +54,14 @@ export function buildWorkflowRouteHint(cwd, options = {}) {
54
54
  return `${recommendation.summary} 当前应执行 ~${recommendation.nextCommand}。执行路径:${recommendation.nextPath}。${recommendation.guidance}${suffix ? ` ${suffix}` : ''}`
55
55
  }
56
56
 
57
- function buildCommandRouteMessage(skillName, recommendation, verifyModeHint) {
57
+ function buildCommandRouteMessage(skillName, recommendation, qaFocusHint) {
58
58
  if (skillName === 'auto') {
59
59
  return recommendation.stage === 'consolidate'
60
60
  ? `当前工作流约束:${recommendation.summary} 本次 ~auto 应直接完成当前收尾。${recommendation.guidance} 未命中阻塞判定前不要停下,也不要把收尾动作写成“下一步建议”。`
61
61
  : `当前工作流约束:${recommendation.summary} 本次 ~auto 的执行主路径:${recommendation.nextPath}。${recommendation.guidance} 命中主路径后继续执行后续阶段;未触发阻塞判定前不要停下,也不要把阶段结果写成“下一步建议”。`
62
62
  }
63
63
  if (skillName === 'loop') {
64
- return `当前工作流约束:用户已显式使用 ~loop,应按 ~loop 的循环规则直接执行。现有工作流只作上下文参考:${recommendation.summary} ${recommendation.guidance} 除非达到迭代上限、达成目标或命中阻塞判定,否则不要停下,也不要把单轮结果写成“下一步建议”。`
64
+ return `当前工作流约束:用户已显式使用 ~loop,应把它视为长任务入口,默认按“/goal -> ~auto -> ~qa”直接推进。现有工作流只作上下文参考:${recommendation.summary} ${recommendation.guidance} 若当前宿主不支持 /goal,则按 ~auto 持续推进,并在交付前强制进入 ~qa。未命中阻塞判定前不要停下,也不要把阶段结果写成“下一步建议”。`
65
65
  }
66
66
  if (skillName === 'plan') {
67
67
  if (recommendation.stage === 'consolidate') {
@@ -79,13 +79,13 @@ function buildCommandRouteMessage(skillName, recommendation, verifyModeHint) {
79
79
  ? `当前工作流约束:${recommendation.summary} 当前应执行 ~build。${recommendation.guidance}`
80
80
  : `当前工作流约束:${recommendation.summary} 当前不该继续 ~build;先按 ~${recommendation.nextCommand} 处理。只有在用户明确提出新增实现范围时,才继续 ~build。`
81
81
  }
82
- if (skillName === 'verify') {
82
+ if (skillName === 'qa') {
83
83
  if (recommendation.stage === 'consolidate') {
84
84
  return `当前工作流约束:${recommendation.summary} 当前应直接进入 CONSOLIDATE。${recommendation.guidance}`
85
85
  }
86
- return recommendation.nextCommand === 'verify'
87
- ? `当前工作流约束:${recommendation.summary} 当前应执行 ~verify。${recommendation.guidance}`
88
- : `当前工作流约束:${recommendation.summary} 当前不该把 ~verify 当成越级入口;先按 ~${recommendation.nextCommand} 处理。即使执行 ~verify,也不能越过当前工作流边界。${verifyModeHint ? ` 若本次仅做阶段内审查或验真,${verifyModeHint}` : ''}`
86
+ return recommendation.nextCommand === 'qa'
87
+ ? `当前工作流约束:${recommendation.summary} 当前应执行 ~qa。${recommendation.guidance}`
88
+ : `当前工作流约束:${recommendation.summary} 当前不该把 ~qa 当成越级入口;先按 ~${recommendation.nextCommand} 处理。即使执行 ~qa,也不能越过当前工作流边界。${qaFocusHint ? ` 若本次只是阶段内收尾,${qaFocusHint}` : ''}`
89
89
  }
90
90
  return `当前工作流约束:${recommendation.summary} 当前应执行 ~${recommendation.nextCommand}。${recommendation.guidance}`
91
91
  }
@@ -104,7 +104,7 @@ export function buildCommandRouteHint(skillName, cwd, options = {}) {
104
104
  return contextHints.join(' ')
105
105
  }
106
106
 
107
- const message = buildCommandRouteMessage(skillName, recommendation, buildVerifyModeHintFromSnapshot(snapshot))
107
+ const message = buildCommandRouteMessage(skillName, recommendation, buildQaFocusHintFromSnapshot(snapshot))
108
108
  return [message, ...contextHints].join(' ')
109
109
  }
110
110