@shanyucoder/flowgrid 0.1.8 → 0.1.9
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/engines/docs/lib/qa-item.mjs +91 -0
- package/engines/docs/lib/render-qa-list.mjs +123 -25
- package/engines/spec/lib/open-qa.mjs +71 -21
- package/harness/docs/extracts/agent-execution-protocol.md +2 -2
- package/harness/docs/extracts/api-codegen-readiness.md +1 -1
- package/harness/docs/extracts/api-spec-sync.md +1 -1
- package/harness/docs/extracts/call-external.md +1 -1
- package/harness/docs/extracts/design-leaf-signoff.md +2 -2
- package/harness/docs/extracts/extract-registry.docs.json +2 -1
- package/harness/docs/extracts/qa-inbox.md +19 -10
- package/harness/docs/extracts/qa-team.md +32 -0
- package/harness/docs/extracts/spec-evolution.md +1 -1
- package/harness/docs/extracts/spec-prd-lite.md +1 -1
- package/harness/docs/schemas/flowgrid-docs/qa-item.schema.json +65 -0
- package/harness/docs/skills/api-spec/SKILL.md +1 -1
- package/harness/docs/skills/api-update/SKILL.md +1 -1
- package/harness/docs/skills/grill-bqa/SKILL.md +2 -2
- package/harness/docs/skills/grill-dev/SKILL.md +1 -1
- package/harness/docs/skills/grill-docs/SKILL.md +2 -2
- package/harness/docs/skills/qa-resolve/SKILL.md +13 -12
- package/harness/docs/skills/qa-review/SKILL.md +45 -0
- package/harness/docs/skills/spec/SKILL.md +1 -1
- package/harness/docs/skills/update-spec/SKILL.md +2 -2
- package/harness/fe/extracts/wire-audit-loop.md +1 -1
- package/harness/fe/skills/grill-wire/SKILL.md +1 -1
- package/harness/fe/skills/wire/SKILL.md +1 -1
- package/package.json +1 -1
- package/templates/project-skeleton/qa/README.md +4 -8
- package/templates/schemas/qa-item.schema.json +98 -0
- package/templates/shared/bundle-authoring.md +5 -4
- package/templates/shared/ir-spec.yaml +1 -1
- package/templates/shared/qa-authoring.md +78 -0
- package/templates/shared/qa-item.yaml +35 -14
- package/templates/shared/tpl-api-contract.md +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import Ajv2020 from 'ajv/dist/2020.js'
|
|
5
|
+
import { parse } from 'yaml'
|
|
6
|
+
|
|
7
|
+
export const QA_ITEM_SCHEMA_ID = 'flowgrid-qa-item/v1'
|
|
8
|
+
|
|
9
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..')
|
|
10
|
+
|
|
11
|
+
let compiledValidator = null
|
|
12
|
+
|
|
13
|
+
function loadSchema() {
|
|
14
|
+
const schemaPath = path.join(packageRoot, 'templates/schemas/qa-item.schema.json')
|
|
15
|
+
return JSON.parse(readFileSync(schemaPath, 'utf8'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {Date} [date]
|
|
20
|
+
* @returns {string} YYYYMMDD HH:mm
|
|
21
|
+
*/
|
|
22
|
+
export function formatQaAt(date = new Date()) {
|
|
23
|
+
const d = date
|
|
24
|
+
const y = d.getFullYear()
|
|
25
|
+
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
26
|
+
const day = String(d.getDate()).padStart(2, '0')
|
|
27
|
+
const h = String(d.getHours()).padStart(2, '0')
|
|
28
|
+
const min = String(d.getMinutes()).padStart(2, '0')
|
|
29
|
+
return `${y}${m}${day} ${h}:${min}`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} screen e.g. W-HOTEL-LIST
|
|
34
|
+
*/
|
|
35
|
+
export function suggestQaShort(screen) {
|
|
36
|
+
const s = String(screen ?? '').trim()
|
|
37
|
+
if (!s) return 'QA'
|
|
38
|
+
if (/^W-/i.test(s)) return s.slice(2).toUpperCase()
|
|
39
|
+
return s.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, '').toUpperCase()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} hubRoot
|
|
44
|
+
* @param {string} shortSlug e.g. HOTEL-LIST
|
|
45
|
+
* @returns {string} four-digit sequence
|
|
46
|
+
*/
|
|
47
|
+
export function nextQaSequence(hubRoot, shortSlug) {
|
|
48
|
+
const qaDir = path.join(path.resolve(hubRoot), 'qa')
|
|
49
|
+
const prefix = `${shortSlug}_`
|
|
50
|
+
let max = 0
|
|
51
|
+
const scan = (dir) => {
|
|
52
|
+
if (!existsSync(dir)) return
|
|
53
|
+
for (const name of readdirSync(dir)) {
|
|
54
|
+
if (!name.toLowerCase().startsWith(prefix.toLowerCase())) continue
|
|
55
|
+
const m = name.match(/_(\d{4})\.ya?ml$/i)
|
|
56
|
+
if (m) max = Math.max(max, Number.parseInt(m[1], 10))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
scan(qaDir)
|
|
60
|
+
scan(path.join(qaDir, 'open'))
|
|
61
|
+
return String(max + 1).padStart(4, '0')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} hubRoot
|
|
66
|
+
* @param {string} shortSlug
|
|
67
|
+
*/
|
|
68
|
+
export function nextQaId(hubRoot, shortSlug) {
|
|
69
|
+
return `${shortSlug}_${nextQaSequence(hubRoot, shortSlug)}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {unknown} doc parsed YAML object
|
|
74
|
+
* @returns {{ ok: true } | { ok: false, errors: import('ajv').ErrorObject[] }}
|
|
75
|
+
*/
|
|
76
|
+
export function validateQaItem(doc) {
|
|
77
|
+
if (!compiledValidator) {
|
|
78
|
+
compiledValidator = new Ajv2020({ allErrors: true }).compile(loadSchema())
|
|
79
|
+
}
|
|
80
|
+
const ok = compiledValidator(doc)
|
|
81
|
+
if (ok) return { ok: true }
|
|
82
|
+
return { ok: false, errors: compiledValidator.errors ?? [] }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} filePath
|
|
87
|
+
*/
|
|
88
|
+
export function validateQaItemFile(filePath) {
|
|
89
|
+
const doc = parse(readFileSync(filePath, 'utf8'))
|
|
90
|
+
return validateQaItem(doc)
|
|
91
|
+
}
|
|
@@ -4,10 +4,23 @@ import { parse } from 'yaml'
|
|
|
4
4
|
|
|
5
5
|
export const QA_LIST_FILE = 'qa/index.md'
|
|
6
6
|
|
|
7
|
-
function
|
|
7
|
+
function listQaYamlFiles(qaDir) {
|
|
8
|
+
if (!existsSync(qaDir)) return []
|
|
9
|
+
const out = []
|
|
10
|
+
for (const name of readdirSync(qaDir)) {
|
|
11
|
+
if (!/\.ya?ml$/i.test(name)) continue
|
|
12
|
+
if (name === 'index.yaml') continue
|
|
13
|
+
out.push(path.join(qaDir, name))
|
|
14
|
+
}
|
|
15
|
+
return out.sort()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function listLegacyOpenYaml(qaDir) {
|
|
19
|
+
const openDir = path.join(qaDir, 'open')
|
|
8
20
|
if (!existsSync(openDir)) return []
|
|
9
21
|
return readdirSync(openDir)
|
|
10
|
-
.filter((n) =>
|
|
22
|
+
.filter((n) => /\.ya?ml$/i.test(n))
|
|
23
|
+
.map((n) => path.join(openDir, n))
|
|
11
24
|
.sort()
|
|
12
25
|
}
|
|
13
26
|
|
|
@@ -16,41 +29,126 @@ function oneLine(text) {
|
|
|
16
29
|
return String(text).trim().split(/\n/)[0].trim()
|
|
17
30
|
}
|
|
18
31
|
|
|
32
|
+
function escapeCell(s) {
|
|
33
|
+
return String(s ?? '')
|
|
34
|
+
.replace(/\|/g, '\\|')
|
|
35
|
+
.replace(/\n/g, ' ')
|
|
36
|
+
.trim()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function lastUpdate(doc) {
|
|
40
|
+
const updates = Array.isArray(doc?.updates) ? doc.updates : []
|
|
41
|
+
if (updates.length) return updates[updates.length - 1]
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function summaryFromDoc(doc) {
|
|
46
|
+
const last = lastUpdate(doc)
|
|
47
|
+
if (last?.text) return oneLine(last.text)
|
|
48
|
+
if (doc?.question) return oneLine(doc.question)
|
|
49
|
+
if (doc?.resolution?.summary) return oneLine(doc.resolution.summary)
|
|
50
|
+
return oneLine(doc?.kind) || '—'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function lastAt(doc) {
|
|
54
|
+
const last = lastUpdate(doc)
|
|
55
|
+
if (last?.at) return String(last.at)
|
|
56
|
+
if (doc?.resolution?.decidedAt) return String(doc.resolution.decidedAt)
|
|
57
|
+
return '—'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isOpen(doc) {
|
|
61
|
+
if (doc?.status === 'closed') return false
|
|
62
|
+
if (doc?.status === 'open') return true
|
|
63
|
+
if (doc?.resolution) return false
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
19
67
|
/**
|
|
20
|
-
*
|
|
68
|
+
* @param {string} full — absolute path to yaml
|
|
69
|
+
* @param {string} linkName — basename for markdown link
|
|
70
|
+
*/
|
|
71
|
+
function catalogRow(full, linkName) {
|
|
72
|
+
let id = linkName.replace(/\.ya?ml$/i, '')
|
|
73
|
+
let screen = ''
|
|
74
|
+
let kind = ''
|
|
75
|
+
let summary = ''
|
|
76
|
+
let lastAtStr = '—'
|
|
77
|
+
let state = 'open'
|
|
78
|
+
try {
|
|
79
|
+
const doc = parse(readFileSync(full, 'utf8')) ?? {}
|
|
80
|
+
if (doc.id) id = String(doc.id)
|
|
81
|
+
if (doc.screen) screen = String(doc.screen)
|
|
82
|
+
if (doc.kind) kind = String(doc.kind)
|
|
83
|
+
summary = summaryFromDoc(doc)
|
|
84
|
+
lastAtStr = lastAt(doc)
|
|
85
|
+
state = isOpen(doc) ? 'open' : 'closed'
|
|
86
|
+
} catch {
|
|
87
|
+
/* still list */
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const relLink = full.includes(`${path.sep}open${path.sep}`)
|
|
91
|
+
? `open/${linkName}`
|
|
92
|
+
: linkName
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
link: `[${id}](${relLink})`,
|
|
97
|
+
state,
|
|
98
|
+
screen: screen || '—',
|
|
99
|
+
lastAt: lastAtStr,
|
|
100
|
+
summary: summary || kind || '—',
|
|
101
|
+
sortKey: `${lastAtStr}\t${id}`,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Always write qa/index.md — catalog table only (SSOT = per-id YAML files).
|
|
21
107
|
*/
|
|
22
108
|
export function writeQaList(root = process.cwd()) {
|
|
23
109
|
const hub = path.resolve(root)
|
|
24
110
|
const qaDir = path.join(hub, 'qa')
|
|
25
|
-
const openDir = path.join(qaDir, 'open')
|
|
26
111
|
mkdirSync(qaDir, { recursive: true })
|
|
27
112
|
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
113
|
+
const seen = new Set()
|
|
114
|
+
const rows = []
|
|
115
|
+
for (const full of [...listQaYamlFiles(qaDir), ...listLegacyOpenYaml(qaDir)]) {
|
|
116
|
+
const name = path.basename(full)
|
|
117
|
+
if (seen.has(name)) continue
|
|
118
|
+
seen.add(name)
|
|
119
|
+
rows.push(catalogRow(full, name))
|
|
120
|
+
}
|
|
121
|
+
rows.sort((a, b) => a.sortKey.localeCompare(b.sortKey))
|
|
122
|
+
|
|
123
|
+
const lines = [
|
|
124
|
+
'# QA — catalog',
|
|
125
|
+
'',
|
|
126
|
+
'_Sinh bởi `flowgrid render` — **chỉ trạng thái + link**. Nội dung SSOT: một file YAML / mã QA._',
|
|
127
|
+
'',
|
|
128
|
+
'Workflow: [qa-team.md](../workflows/qa-team.md) · `/qa-resolve` · `/qa-review`',
|
|
129
|
+
'',
|
|
130
|
+
'| Mã | Status | Screen | Cập nhật cuối | Tóm tắt |',
|
|
131
|
+
'| --- | --- | --- | --- | --- |',
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if (!rows.length) {
|
|
135
|
+
lines.push('| _—_ | _empty_ | — | — | Không có QA file |', '')
|
|
32
136
|
} else {
|
|
33
|
-
for (const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let question = ''
|
|
38
|
-
try {
|
|
39
|
-
const doc = parse(readFileSync(full, 'utf8')) ?? {}
|
|
40
|
-
if (doc.id) id = String(doc.id)
|
|
41
|
-
if (doc.kind) kind = String(doc.kind)
|
|
42
|
-
question = oneLine(doc.question)
|
|
43
|
-
} catch {
|
|
44
|
-
/* list the file anyway */
|
|
45
|
-
}
|
|
46
|
-
const bits = [`[${id}](open/${name})`]
|
|
47
|
-
if (kind) bits.push(kind)
|
|
48
|
-
if (question) bits.push(question)
|
|
49
|
-
lines.push(`- ${bits.join(' — ')}`)
|
|
137
|
+
for (const r of rows) {
|
|
138
|
+
lines.push(
|
|
139
|
+
`| ${r.link} | ${escapeCell(r.state)} | ${escapeCell(r.screen)} | ${escapeCell(r.lastAt)} | ${escapeCell(r.summary)} |`,
|
|
140
|
+
)
|
|
50
141
|
}
|
|
51
142
|
lines.push('')
|
|
52
143
|
}
|
|
53
144
|
|
|
145
|
+
lines.push(
|
|
146
|
+
'## Đọc timeline',
|
|
147
|
+
'',
|
|
148
|
+
'Mở file YAML — đọc `updates[]` từ trên xuống (append-only). Không copy full log vào index.',
|
|
149
|
+
'',
|
|
150
|
+
)
|
|
151
|
+
|
|
54
152
|
const out = path.join(hub, ...QA_LIST_FILE.split('/'))
|
|
55
153
|
writeFileSync(out, lines.join('\n'))
|
|
56
154
|
return out
|
|
@@ -2,11 +2,20 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { parse } from 'yaml'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
function qaDirHasFiles(qaDir) {
|
|
6
|
+
if (!existsSync(qaDir)) return false
|
|
7
|
+
for (const name of readdirSync(qaDir)) {
|
|
8
|
+
if (/\.ya?ml$/i.test(name)) return true
|
|
9
|
+
if (name === 'open' && existsSync(path.join(qaDir, 'open'))) return true
|
|
10
|
+
}
|
|
11
|
+
return existsSync(path.join(qaDir, 'index.md'))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Walk up from a bundle dir to the docs hub (`qa/` or `architecture/`). */
|
|
6
15
|
export function findDocsHubRoot(fromDir) {
|
|
7
16
|
let dir = path.resolve(fromDir)
|
|
8
17
|
for (let i = 0; i < 40; i++) {
|
|
9
|
-
if (existsSync(path.join(dir, '
|
|
18
|
+
if (existsSync(path.join(dir, 'architecture')) || qaDirHasFiles(path.join(dir, 'qa'))) {
|
|
10
19
|
return dir
|
|
11
20
|
}
|
|
12
21
|
const parent = path.dirname(dir)
|
|
@@ -16,32 +25,73 @@ export function findDocsHubRoot(fromDir) {
|
|
|
16
25
|
return null
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
function listQaYamlPaths(hubRoot) {
|
|
29
|
+
const qaDir = path.join(hubRoot, 'qa')
|
|
30
|
+
const paths = []
|
|
31
|
+
if (!existsSync(qaDir)) return paths
|
|
32
|
+
for (const name of readdirSync(qaDir)) {
|
|
33
|
+
if (/\.ya?ml$/i.test(name)) paths.push(path.join(qaDir, name))
|
|
34
|
+
}
|
|
35
|
+
const openDir = path.join(qaDir, 'open')
|
|
36
|
+
if (existsSync(openDir)) {
|
|
37
|
+
for (const name of readdirSync(openDir)) {
|
|
38
|
+
if (/\.ya?ml$/i.test(name)) paths.push(path.join(openDir, name))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return paths
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function qaAppliesToBundle(doc, stem, bundleId, bundlePath) {
|
|
45
|
+
if (stem.startsWith(`QA-${bundleId}-`)) return true
|
|
46
|
+
if (doc?.screen && String(doc.screen) === bundleId) return true
|
|
47
|
+
const tp = doc?.target?.path
|
|
48
|
+
if (tp && bundlePath) {
|
|
49
|
+
const base = path.basename(bundlePath)
|
|
50
|
+
if (String(tp).includes(base)) return true
|
|
51
|
+
}
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isOpenQa(doc) {
|
|
56
|
+
if (doc?.status === 'closed') return false
|
|
57
|
+
if (doc?.status === 'open') return true
|
|
58
|
+
if (doc?.resolution) return false
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function questionLine(doc) {
|
|
63
|
+
if (typeof doc?.question === 'string' && doc.question.trim()) {
|
|
64
|
+
return doc.question.trim().split('\n')[0]
|
|
65
|
+
}
|
|
66
|
+
const updates = Array.isArray(doc?.updates) ? doc.updates : []
|
|
67
|
+
const q = updates.find((u) => u?.kind === 'question')
|
|
68
|
+
if (q?.text) return String(q.text).trim().split('\n')[0]
|
|
69
|
+
const last = updates[updates.length - 1]
|
|
70
|
+
if (last?.text) return String(last.text).trim().split('\n')[0]
|
|
71
|
+
return ''
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function openQaCsv(hubRoot, bundleId, bundlePath = '') {
|
|
75
|
+
return getOpenQaDetails(hubRoot, bundleId, bundlePath).csv
|
|
21
76
|
}
|
|
22
77
|
|
|
23
|
-
export function getOpenQaDetails(hubRoot, bundleId) {
|
|
78
|
+
export function getOpenQaDetails(hubRoot, bundleId, bundlePath = '') {
|
|
24
79
|
if (!hubRoot || !bundleId) return { csv: '', techDebt: [] }
|
|
25
|
-
const openDir = path.join(hubRoot, 'qa', 'open')
|
|
26
|
-
if (!existsSync(openDir)) return { csv: '', techDebt: [] }
|
|
27
|
-
const prefix = `QA-${bundleId}-`
|
|
28
80
|
const ids = []
|
|
29
81
|
const techDebt = []
|
|
30
|
-
for (const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!stem.startsWith(prefix)) continue
|
|
34
|
-
let id = stem
|
|
35
|
-
let kind = ''
|
|
36
|
-
let question = ''
|
|
82
|
+
for (const full of listQaYamlPaths(hubRoot)) {
|
|
83
|
+
const stem = path.basename(full).replace(/\.ya?ml$/i, '')
|
|
84
|
+
let doc = {}
|
|
37
85
|
try {
|
|
38
|
-
|
|
39
|
-
if (parsed && typeof parsed.id === 'string' && parsed.id.trim()) id = parsed.id.trim()
|
|
40
|
-
if (parsed && typeof parsed.kind === 'string') kind = parsed.kind
|
|
41
|
-
if (parsed && typeof parsed.question === 'string') question = parsed.question.trim().split('\n')[0]
|
|
86
|
+
doc = parse(readFileSync(full, 'utf8')) ?? {}
|
|
42
87
|
} catch {
|
|
43
|
-
|
|
88
|
+
continue
|
|
44
89
|
}
|
|
90
|
+
if (!isOpenQa(doc)) continue
|
|
91
|
+
if (!qaAppliesToBundle(doc, stem, bundleId, bundlePath)) continue
|
|
92
|
+
const id = typeof doc.id === 'string' && doc.id.trim() ? doc.id.trim() : stem
|
|
93
|
+
const kind = typeof doc.kind === 'string' ? doc.kind : ''
|
|
94
|
+
const question = questionLine(doc)
|
|
45
95
|
ids.push(id)
|
|
46
96
|
if (kind === 'tech-debt') {
|
|
47
97
|
techDebt.push({ id, status: 'open', summary: question })
|
|
@@ -57,7 +107,7 @@ const QA_FIELD = 'Q&A'
|
|
|
57
107
|
/** Set or remove `Q&A` on a business spec object (comma-separated ids). */
|
|
58
108
|
export function applyOpenQaField(spec, bundlePath, bundleId) {
|
|
59
109
|
const hub = findDocsHubRoot(path.dirname(path.resolve(bundlePath)))
|
|
60
|
-
const qa = getOpenQaDetails(hub, bundleId)
|
|
110
|
+
const qa = getOpenQaDetails(hub, bundleId, bundlePath)
|
|
61
111
|
if (qa.csv) spec[QA_FIELD] = qa.csv
|
|
62
112
|
else delete spec[QA_FIELD]
|
|
63
113
|
if (qa.techDebt && qa.techDebt.length > 0) spec.pendingTechDebt = qa.techDebt
|
|
@@ -17,7 +17,7 @@ surfaces/<surface>/CMP-*/<slug>/ # no modules/ segment
|
|
|
17
17
|
|
|
18
18
|
1. **PRE-FLIGHT:** First action = `{{FLOWGRID_READ_TOOL}}` / read target `SKILL.md`. Never memory.
|
|
19
19
|
4. **NO RAM CACHING:** Durable results → disk immediately. Prior file = next input.
|
|
20
|
-
5. **ZERO BUSINESS HALLUCINATION:** Data only from User prompt or ArtifactGraph. Gaps → AskQuestion (MUST include "Log as Tech Debt" option); if member selects "Log as Tech Debt" → `qa
|
|
20
|
+
5. **ZERO BUSINESS HALLUCINATION:** Data only from User prompt or ArtifactGraph. Gaps → AskQuestion (MUST include "Log as Tech Debt" option); if member selects "Log as Tech Debt" → `qa/<SHORT>_NNNN.yaml` + `#missing_info QA-…`. No invented business fields.
|
|
21
21
|
6. **GRILL HARD GATE:** AG re-check → micro-scope → propose → **STOP for Confirm** before product SSOT write.
|
|
22
22
|
7. **HUMAN DSL:** Common/DSL only via `/common`, `/docs-mark`, custom-base, or grill Confirm. `/spec` consumes only (no `common/yaml`).
|
|
23
23
|
|
|
@@ -26,6 +26,6 @@ surfaces/<surface>/CMP-*/<slug>/ # no modules/ segment
|
|
|
26
26
|
```text
|
|
27
27
|
{{FLOWGRID_READ_TOOL}} SKILL.md
|
|
28
28
|
→ durable writes immediately (No RAM)
|
|
29
|
-
→ AskQuestion or qa
|
|
29
|
+
→ AskQuestion or qa + grill Confirm before SSOT fill
|
|
30
30
|
→ common/DSL only when human-gated
|
|
31
31
|
```
|
|
@@ -29,6 +29,6 @@ flowgrid render
|
|
|
29
29
|
|
|
30
30
|
## Still missing after grill?
|
|
31
31
|
|
|
32
|
-
`#tech-debt:QA-*` + `qa
|
|
32
|
+
`#tech-debt:QA-*` + `qa/` — or `/qa-resolve`. Do not ship silent gaps.
|
|
33
33
|
|
|
34
34
|
Registry: BE checkout `registries/codegen.registry.json` (after `registry:sync`).
|
|
@@ -28,7 +28,7 @@ Path: `…/common/yaml/<slug>/01-backend-spec.yaml` — same trio rules, scan be
|
|
|
28
28
|
|
|
29
29
|
## Missing facts
|
|
30
30
|
|
|
31
|
-
AskQuestion (Recommended / Other / Tech Debt) → `qa
|
|
31
|
+
AskQuestion (Recommended / Other / Tech Debt) → `qa/*` — **no** `openQuestions` in YAML.
|
|
32
32
|
|
|
33
33
|
## Field rename guard
|
|
34
34
|
|
|
@@ -10,7 +10,7 @@ Hashtag only (via `/api-spec`, `/grill-api-spec`, `/api-update`) — **not** a s
|
|
|
10
10
|
## Rules
|
|
11
11
|
|
|
12
12
|
- Never edit `ir/design.yaml` for external contract detail.
|
|
13
|
-
- No invented secrets — AskQuestion or `qa
|
|
13
|
+
- No invented secrets — AskQuestion or `qa/`.
|
|
14
14
|
- Grill: timeout/retry/idempotency must be explicit before `api-gen`.
|
|
15
15
|
|
|
16
16
|
Hub: `docs/references/skills/call-external.md`.
|
|
@@ -4,12 +4,12 @@ Hub SSOT: `docs/workflows/design-leaf-signoff.md` — **không** engine gate.
|
|
|
4
4
|
|
|
5
5
|
## Rubric 6 mục (lead/BA)
|
|
6
6
|
|
|
7
|
-
1. `flowgrid audit spec <bundle> --type <profile>` — `gaps[]` xử lý; `confirms[]` chốt hoặc `qa
|
|
7
|
+
1. `flowgrid audit spec <bundle> --type <profile>` — `gaps[]` xử lý; `confirms[]` chốt hoặc `qa`.
|
|
8
8
|
2. VitePress `ir/generated/spec.md` (+ `data-model.md`) — stories, list columns, AC không placeholder.
|
|
9
9
|
3. Spot 2–3 scenario ↔ `design.actions` / validation messages VI.
|
|
10
10
|
4. `api/.../01-backend-spec.yaml` ↔ `ir/design.yaml` apiRef; `audit fe-be` nếu có BE.
|
|
11
11
|
5. `grillStatus.dev: done` (theo flow); `gen:dry` pass; `bundle.gen` đầy đủ.
|
|
12
|
-
6. `qa
|
|
12
|
+
6. `qa` rỗng hoặc debt đã accept.
|
|
13
13
|
|
|
14
14
|
Optional: **Data model** — `db-erd` LCA + `design.sections[].db` / `spec.entities` (xem hub `architecture-data.md`). `/grill-prototype` SSOT issues; `registry:sync` trên FE standard base.
|
|
15
15
|
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"docs-hub": [
|
|
16
16
|
".cursor/extracts/docs-phase-hooks.md",
|
|
17
17
|
".cursor/extracts/agent-execution-protocol.md",
|
|
18
|
-
".cursor/extracts/qa-inbox.md"
|
|
18
|
+
".cursor/extracts/qa-inbox.md",
|
|
19
|
+
".cursor/extracts/qa-team.md"
|
|
19
20
|
],
|
|
20
21
|
"spec-requirement": [
|
|
21
22
|
".cursor/extracts/spec-requirement.md",
|
|
@@ -7,28 +7,37 @@ There is **no** `openQuestions` on YAML. Gaps use **AskQuestion** in the skill s
|
|
|
7
7
|
AskQuestion: MUST include your Recommended options AND an explicit "Log as Tech Debt (Pending)" option. (The UI automatically provides the 3rd "Other" option). **STOP**.
|
|
8
8
|
|
|
9
9
|
- Member picks A/B/C (Recommended) → write that fact into the real spec field (`design`, `01`, …).
|
|
10
|
-
- Member picks **Log as Tech Debt (Pending)** → create
|
|
10
|
+
- Member picks **Log as Tech Debt (Pending)** → create **one** `qa/<SHORT>_NNNN.yaml` with first `updates[]` line `kind: question`. Do not invent the answer.
|
|
11
11
|
- Member picks **Other** and types a decision → write that text into the spec field.
|
|
12
12
|
|
|
13
13
|
## When to create a file
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
**[MANDATORY]** Read whole `.flowgrid/templates/qa-item.yaml` + `.flowgrid/templates/qa-authoring.md` before writing.
|
|
16
|
+
|
|
17
|
+
1. Pick **SHORT** slug (stable) — see `suggestQaShort(W-*)` in authoring doc.
|
|
18
|
+
2. Next id: glob `qa/<SHORT>_*.yaml` **or** `nextQaId(hub, SHORT)` (`engines/docs/lib/qa-item.mjs`).
|
|
19
|
+
3. Copy golden template → `qa/<SHORT>_NNNN.yaml`:
|
|
20
|
+
- `schema: flowgrid-qa-item/v1`
|
|
21
|
+
- `id` = basename; `status: open`; `target.path` + `target.at`; `screen` / `bundleId` / `skill` / `kind`
|
|
22
|
+
4. First `updates[]`: `at` = `YYYYMMDD HH:mm` (`formatQaAt`), `kind: question`, `text` = open question.
|
|
23
|
+
5. Point spec: `#missing_info <id>` / `#tech-debt:<id>` on `target.at`.
|
|
24
|
+
6. `flowgrid split` — `ir/spec.yaml` Q&A lists open ids.
|
|
25
|
+
|
|
26
|
+
Optional: `validateQaItem` / schema `templates/schemas/qa-item.schema.json`.
|
|
20
27
|
|
|
21
28
|
## Close (`/qa-resolve`)
|
|
22
29
|
|
|
23
|
-
Prompt: `/qa-resolve
|
|
30
|
+
Prompt: `/qa-resolve <id>` plus **solution** on following lines.
|
|
31
|
+
|
|
32
|
+
Append `kind: answer` to **same file**; patch bundle/01; `status: closed`; gỡ tags; `flowgrid split`.
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
## Review (team)
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
`/qa-review <id>` → append `kind: review` to **same file** — see `qa-team.md`.
|
|
28
37
|
|
|
29
38
|
## Do not block grill
|
|
30
39
|
|
|
31
|
-
`grillStatus` may be `done` while `
|
|
40
|
+
`grillStatus` may be `done` while `status: open` QA files exist.
|
|
32
41
|
|
|
33
42
|
## Not this folder
|
|
34
43
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# QA — one file, append timeline (docs hub)
|
|
2
|
+
|
|
3
|
+
Hub: `docs/workflows/qa-team.md`.
|
|
4
|
+
|
|
5
|
+
## File naming
|
|
6
|
+
|
|
7
|
+
`qa/<SHORT>_<NNNN>.yaml` — e.g. `HOTEL-LIST_0001.yaml`. Next index: glob same prefix, max NNNN + 1.
|
|
8
|
+
|
|
9
|
+
`id` inside file = basename without `.yaml`.
|
|
10
|
+
|
|
11
|
+
## Rules
|
|
12
|
+
|
|
13
|
+
1. **One case = one file** for life of that question thread.
|
|
14
|
+
2. **`updates[]` append-only** — `at` as `YYYYMMDD HH:mm`, `kind`: question | answer | review | note.
|
|
15
|
+
3. **`qa/index.md`** is render-only catalog — do not put business log in index.
|
|
16
|
+
4. **needs-change:** append `kind: review` with text; set `status: open`; do **not** create a second file.
|
|
17
|
+
|
|
18
|
+
## Open (Tech Debt)
|
|
19
|
+
|
|
20
|
+
Template: `.flowgrid/templates/qa-item.yaml` · rules: `qa-authoring.md` · `schema: flowgrid-qa-item/v1`.
|
|
21
|
+
|
|
22
|
+
Create file + first update `kind: question`. Tag field: `#missing_info <id>` / `#tech-debt:<id>`.
|
|
23
|
+
|
|
24
|
+
## /qa-resolve
|
|
25
|
+
|
|
26
|
+
Append `kind: answer` with user solution text; patch `target.path` / `target.at`; remove QA tags from spec; `status: closed`; `flowgrid split`.
|
|
27
|
+
|
|
28
|
+
## /qa-review
|
|
29
|
+
|
|
30
|
+
Append `kind: review` only. If needs-change, `status: open`.
|
|
31
|
+
|
|
32
|
+
Legacy `qa/` paths: migrate to flat `qa/<id>.yaml` when touching old hubs.
|
|
@@ -14,7 +14,7 @@ Set `breaking: true`; require re-grill (`/grill-api-spec` or `/grill-dev`) befor
|
|
|
14
14
|
|
|
15
15
|
## Deferred work
|
|
16
16
|
|
|
17
|
-
Use `pendingTechDebt[]` with `id: QA-<feature.id>-NNNN` + matching `qa
|
|
17
|
+
Use `pendingTechDebt[]` with `id: QA-<feature.id>-NNNN` + matching `qa/` file — not prose in `requirements`.
|
|
18
18
|
|
|
19
19
|
## OpenAPI
|
|
20
20
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| Functional scope | `spec.requirements`, `design.sections`, `design.actions` |
|
|
11
11
|
| API | `api/<seq>/01-backend-spec.yaml` — **not** `spec.api` |
|
|
12
12
|
| Tech / codegen | `gen`, `grill-dev` — not in first `/spec` pass |
|
|
13
|
-
| Open questions | `qa
|
|
13
|
+
| Open questions | `qa/*.yaml` |
|
|
14
14
|
|
|
15
15
|
**Author path:** `.flowgrid/templates/feature.bundle.yaml` + `bundle-authoring.md` only. **`design-spec.yaml` deprecated.**
|
|
16
16
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://platform.local/schemas/flowgrid-docs/qa-item.schema.json",
|
|
4
|
+
"title": "FlowGrid QA inbox item",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema", "id", "status", "target", "updates"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "flowgrid-qa-item/v1"
|
|
12
|
+
},
|
|
13
|
+
"id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^[A-Z0-9][A-Z0-9._-]*_\\d{4}$"
|
|
16
|
+
},
|
|
17
|
+
"screen": { "type": "string" },
|
|
18
|
+
"bundleId": { "type": "string" },
|
|
19
|
+
"status": { "type": "string", "enum": ["open", "closed"] },
|
|
20
|
+
"kind": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": ["customer", "choice", "tech-debt", "integration", "coverage"]
|
|
23
|
+
},
|
|
24
|
+
"skill": { "type": "string" },
|
|
25
|
+
"tags": { "type": "array", "items": { "type": "string" } },
|
|
26
|
+
"target": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"required": ["path", "at"],
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"properties": {
|
|
31
|
+
"path": { "type": "string" },
|
|
32
|
+
"at": { "type": "string" }
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"options": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["label"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"label": { "type": "string" },
|
|
42
|
+
"recommended": { "type": "boolean" }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"updates": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"minItems": 1,
|
|
49
|
+
"items": { "$ref": "#/$defs/update" }
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"$defs": {
|
|
53
|
+
"update": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": ["at", "kind", "text"],
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"properties": {
|
|
58
|
+
"at": { "type": "string", "pattern": "^\\d{8} \\d{2}:\\d{2}$" },
|
|
59
|
+
"by": { "type": "string" },
|
|
60
|
+
"kind": { "type": "string", "enum": ["question", "answer", "review", "note"] },
|
|
61
|
+
"text": { "type": "string", "minLength": 1 }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -110,7 +110,7 @@ Use the **same** skill (`/api-spec`) and same trio layout when there is no porta
|
|
|
110
110
|
- **[MANDATORY]** Set `feature.source.kind`, `base: none`, `integrationRefs[]` (or equivalent refs), empty `portalRefs`, `contexts.portalLayout: none`, and `contexts.auth` (API key / HMAC / OAuth).
|
|
111
111
|
- **[MANDATORY]** Domain tags: `#webhook-inbound`, `#webhook-outbound`, `#partner-api`, `#public-api`, `#call-external`, `#err:*` (include `#err:signature-invalid`, `#err:rate-limit`, `#err:unauthorized` when relevant).
|
|
112
112
|
- **[MANDATORY]** Scan sibling `…/api/<seq>/` and surface `common/yaml/` before creating new endpoints; reuse via `#reuse-api` when applicable.
|
|
113
|
-
- **[STRICTLY FORBIDDEN]** Do not invent HMAC secrets or partner validation logic; use AskQuestion or `qa
|
|
113
|
+
- **[STRICTLY FORBIDDEN]** Do not invent HMAC secrets or partner validation logic; use AskQuestion or `qa/` tech debt.
|
|
114
114
|
|
|
115
115
|
---
|
|
116
116
|
|
|
@@ -66,7 +66,7 @@ Shared extracts: `api-spec-sync.md`, `spec-evolution.md`, `entity-relationship.m
|
|
|
66
66
|
|
|
67
67
|
## Done Criteria
|
|
68
68
|
|
|
69
|
-
- Portal delta in `01`; or if deferred: `pendingTechDebt` with `id: QA-<feature.id>-NNNN` + `qa
|
|
69
|
+
- Portal delta in `01`; or if deferred: `pendingTechDebt` with `id: QA-<feature.id>-NNNN` + `qa/` file.
|
|
70
70
|
- `source.portalRefs` current.
|
|
71
71
|
- `changeLog` + version bumped.
|
|
72
72
|
- Handoff: `/grill-api-spec {slug}` (re-run gates + codegen tags).
|
|
@@ -32,7 +32,7 @@ disable-model-invocation: true
|
|
|
32
32
|
- `<pageType>` from `gen.codegen.profile` when set; else infer from prompt (list | create | detail | auth | admin-crud | …) — same table as `docs/workflows/grill-and-human-review.md`.
|
|
33
33
|
- If profile unknown → AskQuestion to lock profile **before** audit (do not use `--type unknown`).
|
|
34
34
|
- **[MANDATORY]** Consume `gaps[]` (patch bundle) and `confirms[]` (AskQuestion with `(Recommended)` from audit): `CONFIRM_UX_*` + **`CONFIRM_DB_*`** (`db-audit-wizard.md`).
|
|
35
|
-
- **[MANDATORY]** After any bundle patch in Step A or B: **re-run** `flowgrid audit spec` until structural `gaps[]` empty or deferred via `qa
|
|
35
|
+
- **[MANDATORY]** After any bundle patch in Step A or B: **re-run** `flowgrid audit spec` until structural `gaps[]` empty or deferred via `qa/<SHORT>_NNNN.yaml`.
|
|
36
36
|
- **[RECOMMENDED]** Resolve `warnings[]` (placeholders, missing metrics/non-goals) when BQA has answers; sync `userStories` if UX copy changed.
|
|
37
37
|
- **[MANDATORY]** After reconcile: `flowgrid split` + `flowgrid render` — stakeholder review uses `ir/generated/spec.md`.
|
|
38
38
|
- **[STRICTLY FORBIDDEN]** Skip audit and rely on manual zone review only.
|
|
@@ -44,7 +44,7 @@ disable-model-invocation: true
|
|
|
44
44
|
- **[MANDATORY]** For `#missing_info` / open gaps: re-check ArtifactGraph → micro-scope → evaluate total gap volume:
|
|
45
45
|
- **Small Scope (≤5 questions):** `AskQuestion` wizard in chat thread — **one question at a time**, **≥3 options**: (1) `(Recommended)`, (2) `Other` (free text), (3) `Log as Tech Debt (Pending)`.
|
|
46
46
|
- **Large Scope (≥10 gaps):** **[MANDATORY HARD STOP IN CHAT]**. Do not spam single questions in chat. Generate an implementation plan / Plan Mode document partitioned into sequential Phases (3–5 gaps per phase) with disk offloading at boundaries.
|
|
47
|
-
- **[MANDATORY]** If member selects "Log as Tech Debt" → create `qa
|
|
47
|
+
- **[MANDATORY]** If member selects "Log as Tech Debt" → copy from `.flowgrid/templates/qa-item.yaml` per `qa-authoring.md`; create `qa/<SHORT>_NNNN.yaml`. Close later with `/qa-resolve`.
|
|
48
48
|
- **[STRICTLY FORBIDDEN]** Never write `openQuestions` in YAML. Never silently overwrite settled SSOT without explicit confirmation.
|
|
49
49
|
|
|
50
50
|
---
|
|
@@ -54,7 +54,7 @@ disable-model-invocation: true
|
|
|
54
54
|
→ **Proactively brainstorm** logical suggestions from business context in Vietnamese (e.g. login page → suggest `module: auth, entity: user`).
|
|
55
55
|
- **Small Scope (≤5 questions):** Trigger `AskQuestion` wizard — **one question at a time**, ≥3 options: (1) `(Recommended)`, (2) `Other` (free text), (3) `Log as Tech Debt (Pending)`. Wait for member answer before showing next question.
|
|
56
56
|
- **Large Scope (≥10 gaps/endpoints):** **[MANDATORY HARD STOP IN CHAT]**. Do not spam single questions in chat. Generate an implementation plan / Plan Mode document partitioned into sequential Phases (3–5 endpoints/gaps per phase) with disk offloading at boundaries.
|
|
57
|
-
- ✅ If member selects "Log as Tech Debt" → create `qa
|
|
57
|
+
- ✅ If member selects "Log as Tech Debt" → create `qa/<SHORT>_NNNN.yaml`; maintain `grillStatus.dev: pending`.
|
|
58
58
|
- ❌ Do NOT set `grillStatus.dev: done` until profile + entity/module + endpoint actions are all verified and confirmed.
|
|
59
59
|
|
|
60
60
|
---
|
|
@@ -42,7 +42,7 @@ disable-model-invocation: true
|
|
|
42
42
|
## Rule: Audit interlock (sau reconcile)
|
|
43
43
|
|
|
44
44
|
- **[MANDATORY]** After any bundle patch that touches `design.*`, `userStories`, `bundle.gen`, or `api` refs: run `flowgrid audit spec <bundle> --type <pageType>` (same profile rules as `/grill-dev`). Resolve `CONFIRM_DB_*` via `db-audit-wizard.md` before handoff.
|
|
45
|
-
- Patch structural `gaps[]`; defer remainder via `qa
|
|
45
|
+
- Patch structural `gaps[]`; defer remainder via `qa/` per Law 2.
|
|
46
46
|
- Address `warnings[]` when reconcile changes business prose (`summary`, `successMetrics`, `nonGoals`, `userStories`).
|
|
47
47
|
- ❌ Do not hand off FE until audit has been re-run post-reconcile.
|
|
48
48
|
|
|
@@ -81,6 +81,6 @@ disable-model-invocation: true
|
|
|
81
81
|
|
|
82
82
|
## Verification Checklist
|
|
83
83
|
|
|
84
|
-
- [ ] Conflicts reconciled in `*.bundle.yaml`, or deferred with `qa
|
|
84
|
+
- [ ] Conflicts reconciled in `*.bundle.yaml`, or deferred with `qa/<SHORT>_…`. No `openQuestions`.
|
|
85
85
|
- [ ] `bundle.gen.codegen.profile` present and correct.
|
|
86
86
|
- [ ] `flowgrid audit spec` re-run after reconcile; `flowgrid split` succeeded with zero errors.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qa-resolve
|
|
3
3
|
extractBundle: docs-hub
|
|
4
|
-
description: EXCLUSIVE /qa-resolve — close one qa
|
|
4
|
+
description: EXCLUSIVE /qa-resolve — append answer + close one qa/<id>.yaml. Prompt is QA id + solution. Do not use for full-screen grill or first-time /spec.
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -10,14 +10,14 @@ disable-model-invocation: true
|
|
|
10
10
|
|
|
11
11
|
# /qa-resolve — Close One Open QA
|
|
12
12
|
|
|
13
|
-
**When:** Member provides `
|
|
13
|
+
**When:** Member provides `HOTEL-LIST_0001` (or legacy `QA-<page-id>-NNNN`) **along with** an explicit decision/solution.
|
|
14
14
|
|
|
15
15
|
**Not this skill:**
|
|
16
16
|
- Unknown answers needing brainstorming → `/grill-bqa` / `/grill-dev` / `/grill-docs` / `/api-spec`
|
|
17
17
|
- FE delta without an existing QA file → `/update-spec`
|
|
18
18
|
- Portal/BE sync without closing QA files → `/api-update`
|
|
19
19
|
|
|
20
|
-
**Extract:** `.cursor/extracts/qa-inbox.md`
|
|
20
|
+
**Extract:** `.cursor/extracts/qa-inbox.md` · `qa-team.md`
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -25,8 +25,8 @@ disable-model-invocation: true
|
|
|
25
25
|
|
|
26
26
|
| Read (whole file) | Write | NEVER do |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| `qa/open/<id>.yaml` | Patch `target.path` only | Read generated `*.md` as SSOT |
|
|
29
|
-
| Target bundle **or** `01-backend-spec.yaml` (entire file) |
|
|
28
|
+
| `qa/<id>.yaml` (legacy: `qa/open/<id>.yaml`) | Patch `target.path` only | Read generated `*.md` as SSOT |
|
|
29
|
+
| Target bundle **or** `01-backend-spec.yaml` (entire file) | **Append** `updates[]` — never delete prior lines | Author `openQuestions` |
|
|
30
30
|
| `ir/design.yaml` — ONLY to locate field if `at` is a design pointer | `flowgrid split` after patch | Full-screen rewrite (use `/spec`) |
|
|
31
31
|
|
|
32
32
|
---
|
|
@@ -41,10 +41,10 @@ disable-model-invocation: true
|
|
|
41
41
|
|
|
42
42
|
## Rule: Resolving the QA File
|
|
43
43
|
|
|
44
|
-
- **[MANDATORY]** Step 1: Locate `qa
|
|
45
|
-
- Zero matches → **STOP**, list available
|
|
44
|
+
- **[MANDATORY]** Step 1: Locate `qa/<id>.yaml`. If missing, glob `qa/*` and legacy `qa/*` matching `id:`.
|
|
45
|
+
- Zero matches → **STOP**, list available QA ids to user.
|
|
46
46
|
- Multiple matches → **STOP**, prompt user to clarify which file to close.
|
|
47
|
-
- **[MANDATORY]** Step 2: Read `target.path`, `target.at`, `kind`, `skill`, and `question` from
|
|
47
|
+
- **[MANDATORY]** Step 2: Read `target.path`, `target.at`, `kind`, `skill`, and latest `question` from `updates[]` (or legacy `question` field).
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
@@ -60,9 +60,10 @@ disable-model-invocation: true
|
|
|
60
60
|
|
|
61
61
|
- **[MANDATORY]** Post-patch execution:
|
|
62
62
|
1. Write solution into field at `target.at` (replacing `#missing_info` / empty / placeholder).
|
|
63
|
-
2. Remove this ID from `#missing_info QA-…`, `#tech-debt:QA-…`, and all tag lists.
|
|
64
|
-
3. **
|
|
63
|
+
2. Remove this ID from `#missing_info QA-…`, `#tech-debt:QA-…`, `#missing_info <id>`, and all tag lists.
|
|
64
|
+
3. **Same file:** Append `updates[]` entry `kind: answer`, `at` now (`YYYYMMDD HH:mm`), `text` = solution; set `status: closed`.
|
|
65
65
|
4. Run `flowgrid split` / `pnpm docs:split` so `ir/spec.yaml` Q&A removes this ID.
|
|
66
|
+
5. Remind `flowgrid render` to refresh `qa/index.md`.
|
|
66
67
|
- **[MANDATORY]** Preserve existing error matrices (`onSuccess` / `onCommonError` / `onSpecificError`, `#err:*`) unless solution specifically alters those fields.
|
|
67
68
|
|
|
68
69
|
---
|
|
@@ -84,8 +85,8 @@ disable-model-invocation: true
|
|
|
84
85
|
|
|
85
86
|
## Verification Checklist
|
|
86
87
|
|
|
87
|
-
- [ ] Read `qa
|
|
88
|
+
- [ ] Read `qa/<id>.yaml`; patched only `target.path` field.
|
|
88
89
|
- [ ] Solution sourced strictly from user prompt (or single AskQuestion turn).
|
|
89
|
-
- [ ]
|
|
90
|
+
- [ ] Appended `kind: answer`; `status: closed`; tags removed for this ID.
|
|
90
91
|
- [ ] `flowgrid split` executed; `ir/spec.yaml` Q&A reflects closed status.
|
|
91
92
|
- [ ] Did not author `openQuestions` or `bundle.spec.api`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qa-review
|
|
3
|
+
description: /qa-review — append review line on qa/<id>.yaml (team).
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> [!CRITICAL] MANDATORY PRE-FLIGHT
|
|
8
|
+
> **[MANDATORY]** Read `.cursor/extracts/qa-team.md` and hub `docs/workflows/qa-team.md`.
|
|
9
|
+
|
|
10
|
+
# /qa-review — Review QA timeline (team)
|
|
11
|
+
|
|
12
|
+
**Owner:** docs hub (`--type=Document` or consumer `FLOWGRID_DOCS_ROOT`)
|
|
13
|
+
|
|
14
|
+
**When:** After `/qa-resolve` appended `kind: answer`; senior checks decision quality and spec patch.
|
|
15
|
+
|
|
16
|
+
**Not this skill:** First answer + patch (`/qa-resolve`); silent spec edits (`/update-spec` without review note).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Input
|
|
21
|
+
|
|
22
|
+
- `HOTEL-LIST_0001` (or legacy `QA-<page-id>-NNNN`)
|
|
23
|
+
- Optional: reviewer id + verdict in user prompt
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
|
|
29
|
+
1. Read **whole** `qa/<id>.yaml` (legacy: `qa/` or same basename under `qa/`).
|
|
30
|
+
2. Read **whole** target `*.bundle.yaml` or `01-backend-spec.yaml` at `target.path`; verify field at `target.at` matches latest `kind: answer` text.
|
|
31
|
+
3. **Append only** to `updates[]` (never edit or delete prior lines):
|
|
32
|
+
- `at`: now (`YYYYMMDD HH:mm`)
|
|
33
|
+
- `by`: reviewer
|
|
34
|
+
- `kind`: `review`
|
|
35
|
+
- `text`: `approved: …` or `needs-change: …` (substantive notes)
|
|
36
|
+
4. If `needs-change` in text → set `status: open` on **same file** (middle fixes spec + `/qa-resolve` or append another `answer` later).
|
|
37
|
+
5. Remind: `flowgrid render` to refresh `qa/index.md`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Verification
|
|
42
|
+
|
|
43
|
+
- [ ] File exists; timeline append-only.
|
|
44
|
+
- [ ] Review notes substantive for `needs-change`.
|
|
45
|
+
- [ ] No invented business beyond comparing answer vs spec.
|
|
@@ -26,7 +26,7 @@ disable-model-invocation: true
|
|
|
26
26
|
- **Small Scope (≤5 gaps):** Run `AskQuestion` wizard — **one question at a time**, **≥3 options**: (1) `(Recommended)`, (2) Alternative, (3) `Log as Tech Debt (Pending)`. Show next question only after member answers current one.
|
|
27
27
|
- **Large Scope (≥10 gaps OR multi-screen scope):** **[MANDATORY HARD STOP IN CHAT]**. Do not spam single questions in chat. Generate an implementation plan / Plan Mode document partitioned into Phases (3–5 questions/fields per phase) to prevent session token overflow.
|
|
28
28
|
- ❌ Never invent, assume, or silently skip missing fields.
|
|
29
|
-
- **[MANDATORY]** If member chooses "Log as Tech Debt" →
|
|
29
|
+
- **[MANDATORY]** If member chooses "Log as Tech Debt" → read `.flowgrid/templates/qa-item.yaml` + `qa-authoring.md`; create `qa/<SHORT>_NNNN.yaml` (`schema: flowgrid-qa-item/v1`) + tag `#missing_info <id>`. Do not block on it.
|
|
30
30
|
- **[RECOMMENDED]** Brainstorm business text (context, input, output, screen descriptions) proactively in Vietnamese for Non-tech audience — do not wait to be told.
|
|
31
31
|
|
|
32
32
|
---
|
|
@@ -27,7 +27,7 @@ Doc hub: `platform/toolchain/UPDATE-SPEC-FLOW.md` · `platform/toolchain/FEATURE
|
|
|
27
27
|
## Rule: Scope Boundaries
|
|
28
28
|
|
|
29
29
|
- **[MANDATORY]** Scope: patch bundle (delta only); emit `#update:*` tags; bump `specRevision`; run `flowgrid split/check`.
|
|
30
|
-
- **[STRICTLY FORBIDDEN]** Full rewrite → `/spec`. Close `qa
|
|
30
|
+
- **[STRICTLY FORBIDDEN]** Full rewrite → `/spec`. Close `qa` item → `/qa-resolve`. Production code → NOT this skill.
|
|
31
31
|
- **[MANDATORY]** Legacy re-mine / trace lại từ code cũ: dùng **`/legacy /spec`** (adopt lại) hoặc **`/update-spec`** với delta `legacy` / `legacyEvidence` + `#update:*` — **không** skill riêng.
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -37,7 +37,7 @@ Doc hub: `platform/toolchain/UPDATE-SPEC-FLOW.md` · `platform/toolchain/FEATURE
|
|
|
37
37
|
- **[MANDATORY]** Gaps or ambiguity regarding delta scope, evaluate total gap volume:
|
|
38
38
|
- **Small Scope (≤5 questions):** Trigger `AskQuestion` wizard — one question at a time, **≥3 options**: (1) `(Recommended)`, (2) `Other`, (3) `Log as Tech Debt (Pending)`.
|
|
39
39
|
- **Large Scope (≥10 gaps):** **[MANDATORY HARD STOP IN CHAT]**. Do not spam single questions in chat. Generate an implementation plan / Plan Mode document partitioned into sequential Phases (3–5 gaps per phase) with disk offloading at boundaries.
|
|
40
|
-
- ✅ If "Log as Tech Debt" is selected → create `qa
|
|
40
|
+
- ✅ If "Log as Tech Debt" is selected → create `qa/` entry; do not invent business data.
|
|
41
41
|
- ❌ Never invent delta scope or novel business fields without explicit user confirmation.
|
|
42
42
|
- Path SSOT: `surfaces/<surface>/CMP-*/<slug>/` — NO `modules/` segment.
|
|
43
43
|
|
|
@@ -47,7 +47,7 @@ Re-run **entire chain** after fixes in any lane (code, plan, or spec).
|
|
|
47
47
|
| Business/spec/UX wrong vs what API actually returns | docs hub | `/update-spec` (paste-ready `/docs-hub` prompt) |
|
|
48
48
|
| `01` / OpenAPI wrong; BE field/status codes | docs hub | `/api-update` → BE `/audit-api` → re-wire |
|
|
49
49
|
| SC screen uncovered | tests-docs | `/testcase` or `/scenario` + `audit scenario` |
|
|
50
|
-
| Intentional defer | docs | `qa
|
|
50
|
+
| Intentional defer | docs | `qa` + `QA-*` / `coverage_deferred` on SC |
|
|
51
51
|
|
|
52
52
|
**Forbidden:** patch `ir/*` or `01` from FE repo; invent AC on tests hub; skip `audit e2e` because manual QA passed.
|
|
53
53
|
|
|
@@ -54,7 +54,7 @@ Re-run full chain after downstream lanes fix gaps.
|
|
|
54
54
|
| UX/spec/AC wrong vs observed behaviour | docs-hub `/update-spec` (paste-ready prompt per `grill-testcase` rule) |
|
|
55
55
|
| `FEBE_*`, wrong endpoint/field on `01` | docs-hub `/api-update` → BE `/audit-api` → member re-runs `/wire` |
|
|
56
56
|
| `SC_SCREEN_NO_TC` | tests-hub `/testcase` or `/scenario` |
|
|
57
|
-
| Defer only | `qa
|
|
57
|
+
| Defer only | `qa` + team policy |
|
|
58
58
|
|
|
59
59
|
- **[STRICTLY FORBIDDEN]** Close `#update:*` / `#wire-only` here — only `/wire` after spec merge confirms.
|
|
60
60
|
- **[STRICTLY FORBIDDEN]** Run `/api` codegen or full product regression from this skill.
|
|
@@ -56,7 +56,7 @@ flowgrid audit e2e --e2e-root <playwright-dir> --tests-docs "$FLOWGRID_TESTS_DOC
|
|
|
56
56
|
Optional: pass explicit `cases/**/TC-*.yaml` paths instead of `--tests-docs`.
|
|
57
57
|
|
|
58
58
|
- Parse JSON: `missingInPlaywright`, `matrixRowsUncovered`, `orphanTestCases`, `orphanSpecs`, `gaps[]`.
|
|
59
|
-
- **[MANDATORY]** Fix or defer every `critical` / `warning` gap before marking wire done; log deferrals in `qa
|
|
59
|
+
- **[MANDATORY]** Fix or defer every `critical` / `warning` gap before marking wire done; log deferrals in `qa` if team policy allows.
|
|
60
60
|
- **[MANDATORY]** Portal leaf with `apiRef`: `flowgrid audit fe-be <bundle.yaml>`.
|
|
61
61
|
- **[MANDATORY]** When `SC-*` covers screen: `flowgrid audit scenario <SC.yaml> --tests-docs "$FLOWGRID_TESTS_DOC"`.
|
|
62
62
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
# QA inbox
|
|
1
|
+
# QA inbox
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One file per case: `qa/<SHORT>_0001.yaml`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- `bundle.id` is the screen leaf id from `/spec` (e.g. `cmp-adm-002-02-01-02`)
|
|
7
|
-
- Sequence is **per screen** (`0001`, `0002`, …). Different screens never share a counter.
|
|
8
|
-
- Close with **`/qa-resolve QA-<bundle.id>-NNNN`** plus the decision (skill deletes the file).
|
|
5
|
+
After `flowgrid init`, copy from `.flowgrid/templates/qa-item.yaml` and follow `.flowgrid/templates/qa-authoring.md`.
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
Rules: `.cursor/extracts/qa-inbox.md`
|
|
7
|
+
Append-only `updates[]` · `flowgrid render` refreshes `index.md`.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://platform.local/schemas/flowgrid-docs/qa-item.schema.json",
|
|
4
|
+
"title": "FlowGrid QA inbox item",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema", "id", "status", "target", "updates"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"const": "flowgrid-qa-item/v1"
|
|
12
|
+
},
|
|
13
|
+
"id": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^[A-Z0-9][A-Z0-9._-]*_\\d{4}$",
|
|
16
|
+
"description": "Same as filename without .yaml — SHORT_NNNN"
|
|
17
|
+
},
|
|
18
|
+
"screen": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "W-* or bundle page-id for humans and split matching"
|
|
21
|
+
},
|
|
22
|
+
"bundleId": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Legacy QA-<bundleId>-NNNN prefix; optional when screen/target.path enough"
|
|
25
|
+
},
|
|
26
|
+
"status": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["open", "closed"]
|
|
29
|
+
},
|
|
30
|
+
"kind": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": ["customer", "choice", "tech-debt", "integration", "coverage"]
|
|
33
|
+
},
|
|
34
|
+
"skill": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Skill that opened the gap — spec, grill-dev, api-spec, …"
|
|
37
|
+
},
|
|
38
|
+
"tags": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": { "type": "string" }
|
|
41
|
+
},
|
|
42
|
+
"target": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["path", "at"],
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"properties": {
|
|
47
|
+
"path": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Repo-relative path to bundle or 01-backend-spec.yaml"
|
|
50
|
+
},
|
|
51
|
+
"at": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "YAML pointer — field gets #missing_info <id> until resolve"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"options": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"description": "Optional AskQuestion options captured when opening (audit trail)",
|
|
60
|
+
"items": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": ["label"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"label": { "type": "string" },
|
|
65
|
+
"recommended": { "type": "boolean" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"updates": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"minItems": 1,
|
|
72
|
+
"items": { "$ref": "#/$defs/update" }
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"$defs": {
|
|
76
|
+
"update": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["at", "kind", "text"],
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"properties": {
|
|
81
|
+
"at": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"pattern": "^\\d{8} \\d{2}:\\d{2}$",
|
|
84
|
+
"description": "YYYYMMDD HH:mm (team TZ)"
|
|
85
|
+
},
|
|
86
|
+
"by": { "type": "string" },
|
|
87
|
+
"kind": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"enum": ["question", "answer", "review", "note"]
|
|
90
|
+
},
|
|
91
|
+
"text": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"minLength": 1
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -303,13 +303,14 @@ actions:
|
|
|
303
303
|
|
|
304
304
|
YAML only per schema. No explanation. No markdown.
|
|
305
305
|
|
|
306
|
-
## Questions: AskQuestion wizard or hub `qa
|
|
306
|
+
## Questions: AskQuestion wizard or hub `qa/`
|
|
307
307
|
|
|
308
308
|
Do **not** write `openQuestions` anywhere. Schema/render không còn field này.
|
|
309
309
|
|
|
310
310
|
- Member trả lời **ngay:** AskQuestion (options + Recommended + **Other**) → **STOP** → ghi field thật.
|
|
311
|
-
- Member chọn **
|
|
312
|
-
-
|
|
311
|
+
- Member chọn **Log as Tech Debt:** copy `.flowgrid/templates/qa-item.yaml` — quy tắc field/timeline: **`qa-authoring.md`** · extract `qa-inbox.md`.
|
|
312
|
+
- Đóng sau bằng **`/qa-resolve <id>` + giải pháp** (append `kind: answer`, `status: closed`).
|
|
313
|
+
- `grillStatus` có thể `done` khi vẫn còn file QA `status: open`.
|
|
313
314
|
|
|
314
315
|
## YAML Syntax & Escaping Rules
|
|
315
316
|
|
|
@@ -318,7 +319,7 @@ Do **not** write `openQuestions` anywhere. Schema/render không còn field này.
|
|
|
318
319
|
|
|
319
320
|
## ir/spec.yaml vs ir/design.yaml
|
|
320
321
|
|
|
321
|
-
`pnpm spec:split` ghi hai file. FlowGrid bộ docs grill **không** đọc `ir/*` khi author — chỉ bundle. Split ghi `"Q&A":` trên **`ir/spec.yaml`** (id `QA-<bundle.id>-NNNN` cách nhau bởi `, `) từ `qa
|
|
322
|
+
`pnpm spec:split` ghi hai file. FlowGrid bộ docs grill **không** đọc `ir/*` khi author — chỉ bundle. Split ghi `"Q&A":` trên **`ir/spec.yaml`** (id `QA-<bundle.id>-NNNN` cách nhau bởi `, `) từ `qa/` — không nhét list vào bundle.
|
|
322
323
|
|
|
323
324
|
Downstream (UI gen, API gen, testcase) đọc **chỉ `ir/design.yaml`**. Thiếu file = split/spec chưa xong. Story/copy chưa đủ thì **bổ sung design** (bundle.gen + split), không đọc `ir/spec.yaml`. `ir/spec.yaml` chỉ văn mô tả + `legacy` + `qa` (id treo).
|
|
324
325
|
|
|
@@ -14,5 +14,5 @@ actors: []
|
|
|
14
14
|
requirements: []
|
|
15
15
|
acceptance: []
|
|
16
16
|
|
|
17
|
-
# Filled by spec:split from qa
|
|
17
|
+
# Filled by spec:split from qa/<SHORT>_NNNN.yaml (comma-separated). Omit when none.
|
|
18
18
|
# "Q&A": QA-role-domain-function-0001, QA-role-domain-function-0002
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# flowgrid-qa-item/v1 — authoring rules (QA inbox)
|
|
2
|
+
|
|
3
|
+
Hub template: `.flowgrid/templates/qa-item.yaml` (sau `flowgrid init`) · Workflow: `docs/workflows/qa-team.md`
|
|
4
|
+
|
|
5
|
+
## File naming
|
|
6
|
+
|
|
7
|
+
| Part | Rule | Example |
|
|
8
|
+
|------|------|---------|
|
|
9
|
+
| **SHORT** | Slug màn/module, ổn định, `A-Z0-9` + `-` | `HOTEL-LIST`, `ADM-AUTH` |
|
|
10
|
+
| **NNNN** | Tăng dần theo SHORT (0001…) | `0001`, `0002` |
|
|
11
|
+
| **Path** | `qa/<SHORT>_<NNNN>.yaml` | `qa/HOTEL-LIST_0001.yaml` |
|
|
12
|
+
| **id** | **Bắt buộc** = basename không `.yaml` | `HOTEL-LIST_0001` |
|
|
13
|
+
|
|
14
|
+
Legacy id `QA-<page-id>-NNNN` vẫn đọc được — file mới dùng `<SHORT>_NNNN`.
|
|
15
|
+
|
|
16
|
+
Gợi ý SHORT từ `W-HOTEL-LIST` → `HOTEL-LIST` (bỏ prefix `W-`). Team có thể chọn slug module cố định (vd. `ADM-AUTH` cho nhiều màn auth).
|
|
17
|
+
|
|
18
|
+
## Top-level fields
|
|
19
|
+
|
|
20
|
+
| Key | Required | Purpose |
|
|
21
|
+
|-----|----------|---------|
|
|
22
|
+
| `schema` | yes | Luôn `flowgrid-qa-item/v1` |
|
|
23
|
+
| `id` | yes | Khớp tên file |
|
|
24
|
+
| `status` | yes | `open` \| `closed` |
|
|
25
|
+
| `target.path` | yes | Bundle hoặc `01-backend-spec.yaml` (repo-relative) |
|
|
26
|
+
| `target.at` | yes | Pointer field — gắn `#missing_info <id>` / `#tech-debt:<id>` |
|
|
27
|
+
| `updates` | yes | Timeline append-only (≥1 dòng) |
|
|
28
|
+
| `screen` | khuyến nghị | `W-*` hoặc `page-id` — catalog + split |
|
|
29
|
+
| `bundleId` | khi legacy | `page-id` bundle để `Q&A` trên `ir/spec.yaml` |
|
|
30
|
+
| `kind` | khuyến nghị | `customer` \| `choice` \| `tech-debt` \| `integration` \| `coverage` |
|
|
31
|
+
| `skill` | khuyến nghị | Skill mở gap: `spec`, `grill-dev`, `api-spec`, … |
|
|
32
|
+
| `options` | optional | Copy options AskQuestion khi mở (audit) |
|
|
33
|
+
| `tags` | optional | Mirror tag đã gắn trên spec |
|
|
34
|
+
|
|
35
|
+
## `updates[]` (append-only)
|
|
36
|
+
|
|
37
|
+
**Không** sửa/xóa dòng cũ. Chỉ **thêm** cuối mảng.
|
|
38
|
+
|
|
39
|
+
| `kind` | Ai / khi |
|
|
40
|
+
|--------|----------|
|
|
41
|
+
| `question` | Mở QA (Log Tech Debt) — **dòng đầu** |
|
|
42
|
+
| `answer` | `/qa-resolve` — sau khi patch `target.at` |
|
|
43
|
+
| `review` | `/qa-review` — `approved:` hoặc `needs-change:` |
|
|
44
|
+
| `note` | Ghi chú, không đổi spec |
|
|
45
|
+
|
|
46
|
+
| `at` | Format **bắt buộc** | `20260930 08:00` (YYYYMMDD HH:mm) |
|
|
47
|
+
| `by` | Member / role | `ba-lead`, `middle-dev` |
|
|
48
|
+
| `text` | Nội dung | Block `\|` multiline |
|
|
49
|
+
|
|
50
|
+
### Vòng đời status
|
|
51
|
+
|
|
52
|
+
- Mở file: `status: open`, `updates[0].kind: question`
|
|
53
|
+
- `/qa-resolve`: append `answer`, `status: closed`, gỡ tag trên spec
|
|
54
|
+
- `/qa-review` + `needs-change`: append `review`, **`status: open`** lại (cùng file)
|
|
55
|
+
|
|
56
|
+
## Agent workflow (mở QA)
|
|
57
|
+
|
|
58
|
+
1. Đọc **whole** `.flowgrid/templates/qa-item.yaml` + file này.
|
|
59
|
+
2. Chọn SHORT (team convention hoặc từ `screen`).
|
|
60
|
+
3. `nextQaId(hub, SHORT)` hoặc glob `qa/<SHORT>_*.yaml` → max NNNN + 1.
|
|
61
|
+
4. Copy template → `qa/<SHORT>_<NNNN>.yaml`; set `id`, `target`, `at` now, `updates[0]`.
|
|
62
|
+
5. Patch spec: `#missing_info <id>` trên field `target.at`.
|
|
63
|
+
6. `flowgrid split` + `flowgrid render`.
|
|
64
|
+
|
|
65
|
+
## Agent output
|
|
66
|
+
|
|
67
|
+
- **YAML only** khi tạo/sửa QA file — không markdown giải thích trong repo.
|
|
68
|
+
- Quote string có `:`; multiline dùng `|`.
|
|
69
|
+
|
|
70
|
+
## Không dùng QA file cho
|
|
71
|
+
|
|
72
|
+
- ADR → `architecture/09-decisions`
|
|
73
|
+
- Risk dài hạn → `architecture/11-risks`
|
|
74
|
+
- `openQuestions` trên bundle — **cấm**
|
|
75
|
+
|
|
76
|
+
## Schema
|
|
77
|
+
|
|
78
|
+
Validate (optional CI): `templates/schemas/qa-item.schema.json` · `flowgrid` test `validateQaItem`.
|
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
#
|
|
2
|
-
id
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# flowgrid-qa-item/v1 — Authoring: qa-authoring.md · Hub: docs/workflows/qa-team.md
|
|
2
|
+
# Copy to qa/<SHORT>_NNNN.yaml (id = basename without .yaml). Append-only updates[].
|
|
3
|
+
|
|
4
|
+
schema: flowgrid-qa-item/v1
|
|
5
|
+
id: HOTEL-LIST_0001
|
|
6
|
+
screen: W-HOTEL-LIST
|
|
7
|
+
bundleId: cmp-adm-000-01-01
|
|
8
|
+
status: open
|
|
9
|
+
kind: tech-debt
|
|
10
|
+
skill: spec
|
|
11
|
+
tags:
|
|
12
|
+
- "#missing_info HOTEL-LIST_0001"
|
|
5
13
|
target:
|
|
6
|
-
path: surfaces/<surface>/CMP-*/<
|
|
7
|
-
at: design.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
path: surfaces/<surface>/CMP-*/<slug>.bundle.yaml
|
|
15
|
+
at: design.sections.main.items.filter_timezone.purpose
|
|
16
|
+
options:
|
|
17
|
+
- label: "(Recommended) Theo timezone property"
|
|
18
|
+
recommended: true
|
|
19
|
+
- label: Theo locale user
|
|
20
|
+
updates:
|
|
21
|
+
- at: "20260930 08:00"
|
|
22
|
+
by: <member-id>
|
|
23
|
+
kind: question
|
|
24
|
+
text: |
|
|
25
|
+
Filter timezone lấy theo property hay user locale?
|
|
26
|
+
# Append only — never delete or rewrite prior lines:
|
|
27
|
+
# - at: "20260930 10:00"
|
|
28
|
+
# by: ba-lead
|
|
29
|
+
# kind: answer
|
|
30
|
+
# text: |
|
|
31
|
+
# Theo property TZ; label UTC+7 trên UI.
|
|
32
|
+
# - at: "20261001 08:00"
|
|
33
|
+
# by: senior
|
|
34
|
+
# kind: review
|
|
35
|
+
# text: |
|
|
36
|
+
# needs-change: thêm AC khi property chưa set TZ.
|
|
@@ -112,7 +112,7 @@ Skill: `/openapi` · Redoc/Swagger UI: `openapi_build_ui` (tùy hub).
|
|
|
112
112
|
|
|
113
113
|
- Chuỗi có `:` → bọc `"..."`.
|
|
114
114
|
- Chạy `flowgrid api:check` trước handoff.
|
|
115
|
-
- Thiếu fact → AskQuestion hoặc `qa
|
|
115
|
+
- Thiếu fact → AskQuestion hoặc `qa/` — **không** `openQuestions` trong YAML.
|
|
116
116
|
|
|
117
117
|
---
|
|
118
118
|
|