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.
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -4
- package/README.md +72 -73
- package/README_CN.md +72 -73
- package/bootstrap-lite.md +10 -12
- package/bootstrap.md +22 -24
- package/gemini-extension.json +1 -1
- package/install.ps1 +21 -3
- package/install.sh +19 -2
- package/package.json +2 -2
- package/scripts/capability-registry.mjs +5 -3
- package/scripts/cli-doctor-codex.mjs +150 -1
- package/scripts/cli-doctor-render.mjs +2 -1
- package/scripts/cli-lifecycle-hosts.mjs +76 -34
- package/scripts/cli-lifecycle.mjs +50 -15
- package/scripts/cli-messages.mjs +5 -5
- package/scripts/delivery-gate-messages.mjs +5 -4
- package/scripts/delivery-gate.mjs +11 -22
- package/scripts/guard.mjs +1 -1
- package/scripts/notify-closeout.mjs +61 -22
- package/scripts/notify-context.mjs +6 -6
- package/scripts/notify-payload.mjs +8 -0
- package/scripts/notify-route.mjs +1 -1
- package/scripts/notify-ui.mjs +14 -1
- package/scripts/notify.mjs +80 -4
- package/scripts/plan-contract.mjs +10 -14
- package/scripts/project-session-cleanup.mjs +45 -31
- package/scripts/qa-review-state.mjs +313 -0
- package/scripts/ralph-loop.mjs +86 -14
- package/scripts/runtime-scope.mjs +1 -3
- package/scripts/session-capsule.mjs +51 -13
- package/scripts/state-document.mjs +77 -0
- package/scripts/workflow-core.mjs +13 -19
- package/scripts/workflow-plan-files.mjs +1 -1
- package/scripts/workflow-recommendation.mjs +55 -67
- package/scripts/workflow-state.mjs +8 -8
- package/skills/commands/auto/SKILL.md +12 -12
- package/skills/commands/build/SKILL.md +9 -10
- package/skills/commands/commit/SKILL.md +1 -1
- package/skills/commands/help/SKILL.md +11 -13
- package/skills/commands/init/SKILL.md +18 -9
- package/skills/commands/loop/SKILL.md +70 -96
- package/skills/commands/plan/SKILL.md +7 -8
- package/skills/commands/prd/SKILL.md +3 -3
- package/skills/commands/qa/SKILL.md +49 -0
- package/skills/hello-ui/SKILL.md +3 -3
- package/skills/helloagents/SKILL.md +12 -15
- package/skills/qa-review/SKILL.md +92 -0
- package/templates/plans/contract.json +4 -7
- package/templates/plans/plan.md +1 -1
- package/templates/plans/tasks.md +1 -1
- package/templates/verify.yaml +1 -1
- package/scripts/review-state.mjs +0 -193
- package/scripts/verify-state.mjs +0 -175
- package/skills/commands/global/SKILL.md +0 -71
- package/skills/commands/verify/SKILL.md +0 -46
- package/skills/commands/wiki/SKILL.md +0 -57
- package/skills/hello-review/SKILL.md +0 -42
- 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).
|
|
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
|
|
97
|
-
|
|
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
|
|
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
|
-
|
|
144
|
+
writeStateDocument(scope.statePath, {
|
|
145
|
+
metadata: nextCapsule,
|
|
146
|
+
body: currentDocument.body,
|
|
147
|
+
})
|
|
126
148
|
writeActiveProjectSession(scope, {
|
|
127
|
-
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
|
|
155
|
-
if (!existsSync(
|
|
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
|
|
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: '
|
|
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: '
|
|
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:
|
|
98
|
-
reason: '
|
|
99
|
-
guidance:
|
|
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
|
|
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
|
|
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 ''
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
14
|
+
const qaMode = determineQaMode(plan)
|
|
16
15
|
const advisorRequirement = getAdvisorRequirement(plan.contract)
|
|
17
16
|
const visualRequirement = getVisualValidationRequirement(plan.contract)
|
|
18
|
-
const
|
|
19
|
-
|
|
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
|
|
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:
|
|
36
|
+
required: qaReady && advisorReady && visualReady,
|
|
40
37
|
...options,
|
|
41
38
|
})
|
|
42
39
|
|
|
43
40
|
return {
|
|
44
|
-
|
|
41
|
+
qaMode,
|
|
45
42
|
advisorRequirement,
|
|
46
43
|
visualRequirement,
|
|
47
|
-
|
|
48
|
-
reviewStatus,
|
|
44
|
+
qaStatus,
|
|
49
45
|
advisorStatus,
|
|
50
46
|
visualStatus,
|
|
51
47
|
closeoutStatus,
|
|
52
|
-
|
|
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: '
|
|
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
|
|
79
|
-
if (!
|
|
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
|
-
|
|
91
|
-
|
|
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: '
|
|
101
|
-
mode:
|
|
102
|
-
routeHint:
|
|
88
|
+
phase: 'plan',
|
|
89
|
+
mode: qaMode.mode,
|
|
90
|
+
routeHint: qaMode.guidance,
|
|
103
91
|
gateHint: plan.contractIssues.length > 0
|
|
104
|
-
? '交付把关:当前还不能进入 CONSOLIDATE;先补齐 `contract.json` 中的 `
|
|
105
|
-
: '交付把关:当前还不能进入 CONSOLIDATE;先补齐 tasks.md 中每个任务的“涉及文件”“完成标准”和“验证方式”,再进入
|
|
92
|
+
? '交付把关:当前还不能进入 CONSOLIDATE;先补齐 `contract.json` 中的 `qaMode` 与 `qaFocus`,再进入 ~qa。'
|
|
93
|
+
: '交付把关:当前还不能进入 CONSOLIDATE;先补齐 tasks.md 中每个任务的“涉及文件”“完成标准”和“验证方式”,再进入 ~qa。',
|
|
106
94
|
}
|
|
107
95
|
}
|
|
108
96
|
|
|
109
97
|
return {
|
|
110
|
-
phase: '
|
|
111
|
-
mode:
|
|
112
|
-
routeHint:
|
|
113
|
-
gateHint: `交付把关:进入 CONSOLIDATE
|
|
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 === '
|
|
126
|
-
return
|
|
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 完成剩余任务,再进入 ~
|
|
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 / ~
|
|
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 / ~
|
|
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 -> ~
|
|
160
|
+
nextPath: '~build -> ~qa',
|
|
173
161
|
summary: `${scopeLabel} "${plan.planName}" 仍有 ${classification.openCount} 个未完成任务。`,
|
|
174
|
-
guidance: '若用户是在继续当前功能、落实既有方案、或让你“继续做完”,优先复用现有 requirements.md / plan.md / tasks.md 进入 ~build;完成当前实现后再进入 ~
|
|
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.
|
|
168
|
+
if (closedPlanEvidence.qaMode?.mode === 'metadata-first') {
|
|
181
169
|
return {
|
|
182
170
|
scopeLabel,
|
|
183
171
|
plan,
|
|
184
172
|
status: 'closed',
|
|
185
|
-
nextCommand: '
|
|
186
|
-
nextPath: '~
|
|
187
|
-
summary: `${scopeLabel} "${plan.planName}"
|
|
188
|
-
guidance: closedPlanEvidence.
|
|
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: '
|
|
203
|
-
nextPath: '~
|
|
190
|
+
nextCommand: 'qa',
|
|
191
|
+
nextPath: '~qa -> CONSOLIDATE',
|
|
204
192
|
summary: `${scopeLabel} "${plan.planName}" 的任务已闭合,但当前 UI 契约仍要求独立 advisor 复查与视觉验收。`,
|
|
205
|
-
guidance: '先在 ~
|
|
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: '
|
|
215
|
-
nextPath: '~
|
|
202
|
+
nextCommand: 'qa',
|
|
203
|
+
nextPath: '~qa -> CONSOLIDATE',
|
|
216
204
|
summary: `${scopeLabel} "${plan.planName}" 的任务已闭合,但当前契约仍要求独立 advisor 复查。`,
|
|
217
|
-
guidance: '先在 ~
|
|
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: '
|
|
227
|
-
nextPath: '~
|
|
214
|
+
nextCommand: 'qa',
|
|
215
|
+
nextPath: '~qa -> CONSOLIDATE',
|
|
228
216
|
summary: `${scopeLabel} "${plan.planName}" 的任务已闭合,但当前 UI 契约仍要求视觉验收。`,
|
|
229
|
-
guidance: '先在 ~
|
|
217
|
+
guidance: '先在 ~qa 阶段完成视觉验收,并写入当前会话 `artifacts/visual.json` 记录 reason、tooling、screensChecked、statesChecked、status 与 summary;视觉验收通过后再进入 CONSOLIDATE。',
|
|
230
218
|
}
|
|
231
219
|
}
|
|
232
220
|
|
|
233
|
-
if (closedPlanEvidence.
|
|
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: '
|
|
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: '
|
|
256
|
-
nextPath: '~
|
|
243
|
+
nextCommand: 'qa',
|
|
244
|
+
nextPath: '~qa -> CONSOLIDATE',
|
|
257
245
|
summary: `${scopeLabel} "${plan.planName}" 的任务已全部闭合。`,
|
|
258
|
-
guidance: '若用户是在做收尾、验真、复查或准备交付,优先走 ~
|
|
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 === '
|
|
314
|
+
if (recommendation.nextCommand === 'qa' && plan.taskSummary.total >= 1) {
|
|
327
315
|
const action = buildDeliveryActionFromSnapshot(snapshot, cwd, recommendation)
|
|
328
|
-
if (action?.phase === '
|
|
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,
|
|
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
|
|
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 === '
|
|
82
|
+
if (skillName === 'qa') {
|
|
83
83
|
if (recommendation.stage === 'consolidate') {
|
|
84
84
|
return `当前工作流约束:${recommendation.summary} 当前应直接进入 CONSOLIDATE。${recommendation.guidance}`
|
|
85
85
|
}
|
|
86
|
-
return recommendation.nextCommand === '
|
|
87
|
-
? `当前工作流约束:${recommendation.summary} 当前应执行 ~
|
|
88
|
-
: `当前工作流约束:${recommendation.summary} 当前不该把 ~
|
|
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,
|
|
107
|
+
const message = buildCommandRouteMessage(skillName, recommendation, buildQaFocusHintFromSnapshot(snapshot))
|
|
108
108
|
return [message, ...contextHints].join(' ')
|
|
109
109
|
}
|
|
110
110
|
|