@shanyucoder/flowgrid 0.1.11 → 0.1.12
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/adapters/shared/resolve-hub-id.mjs +5 -1
- package/dist/docs/mcp/tools.js +59 -6
- package/dist/docs/mcp/tools.js.map +1 -1
- package/dist/docs/scan/screen-to-flows.d.ts +3 -0
- package/dist/docs/scan/screen-to-flows.js +18 -0
- package/dist/docs/scan/screen-to-flows.js.map +1 -0
- package/engines/docs/lib/render-bundle-markdown.mjs +19 -0
- package/engines/docs/lib/render-design-markdown.mjs +56 -0
- package/engines/docs/lib/screen-to-flows-index.mjs +121 -0
- package/engines/docs/vitepress/surfaces-nav.mjs +7 -0
- package/engines/spec/lib/audit-bundle-gaps.mjs +15 -3
- package/engines/spec/lib/audit-interaction-cases.mjs +185 -0
- package/engines/spec/lib/bundle-ir.mjs +7 -0
- package/engines/spec/lib/bundle-schema.mjs +1 -0
- package/engines/spec/lib/interaction-cases.mjs +46 -0
- package/engines/spec/split-bundle.mjs +1 -1
- package/harness/docs/extracts/agent-design-context.md +124 -0
- package/harness/docs/extracts/extract-registry.docs.json +6 -3
- package/harness/docs/skills/api-spec/SKILL.md +4 -0
- package/harness/docs/skills/grill-dev/SKILL.md +22 -9
- package/harness/docs/skills/spec/SKILL.md +8 -5
- package/harness/docs/skills/user-flow/SKILL.md +4 -2
- package/harness/fe/skills/prototype/SKILL.md +22 -10
- package/package.json +1 -1
- package/templates/shared/bundle-authoring.md +2 -1
- package/templates/shared/default-layout.ejs +47 -2
- package/templates/shared/feature.bundle.yaml +29 -1
- package/templates/shared/tpl-runtime-sequence.md +56 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-step audit for bundle interactionCases (see harness/docs/extracts/agent-design-context.md).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function countMutationActions(design) {
|
|
6
|
+
const actions = design?.actions
|
|
7
|
+
if (!Array.isArray(actions)) return 0
|
|
8
|
+
return actions.filter((a) => a && (a.executionContract || a.apiRef)).length
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function interactionCasesComplexityTriggers(bundle) {
|
|
12
|
+
const design = bundle?.design ?? {}
|
|
13
|
+
const statuses = design?.stateMatrix?.recordStatuses
|
|
14
|
+
const statusCount = Array.isArray(statuses) ? statuses.length : 0
|
|
15
|
+
const mutations = countMutationActions(design)
|
|
16
|
+
const scenarios = bundle?.userStories?.scenarios
|
|
17
|
+
const scenarioCount = Array.isArray(scenarios) ? scenarios.length : 0
|
|
18
|
+
const listOnly =
|
|
19
|
+
design?.codegen?.profile === 'list' ||
|
|
20
|
+
String(bundle?.gen?.codegen?.profile || '') === 'list'
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
complex:
|
|
24
|
+
statusCount >= 3 ||
|
|
25
|
+
mutations >= 2 ||
|
|
26
|
+
(!listOnly && scenarioCount >= 4) ||
|
|
27
|
+
Boolean(design?.stateMatrix?.behaviors?.length >= 3),
|
|
28
|
+
statusCount,
|
|
29
|
+
mutations,
|
|
30
|
+
scenarioCount,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {Record<string, unknown>} bundle parsed bundle YAML
|
|
36
|
+
* @param {{ addGap: Function, addConfirm: Function, addWarning: Function }} hooks
|
|
37
|
+
*/
|
|
38
|
+
export function auditInteractionCases(bundle, hooks) {
|
|
39
|
+
const { addGap, addConfirm, addWarning } = hooks
|
|
40
|
+
const ic = bundle?.interactionCases
|
|
41
|
+
const { complex, statusCount, mutations } = interactionCasesComplexityTriggers(bundle)
|
|
42
|
+
|
|
43
|
+
if (ic == null) {
|
|
44
|
+
if (!complex) return
|
|
45
|
+
addConfirm(
|
|
46
|
+
'CONFIRM_INTERACTION_CASES_POLICY',
|
|
47
|
+
'interactionCases',
|
|
48
|
+
'Screen has complex state/actions — declare interactionCases (IC-* + sequenceDiagram)?',
|
|
49
|
+
[
|
|
50
|
+
'(Recommended) Yes — set interactionCases.policy: required and items[]',
|
|
51
|
+
'No — simple screen (set interactionCases.policy: skip + skipReason)',
|
|
52
|
+
'Other — note in qa/',
|
|
53
|
+
],
|
|
54
|
+
0,
|
|
55
|
+
)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const policy = ic.policy
|
|
60
|
+
if (policy === 'skip') {
|
|
61
|
+
if (!String(ic.skipReason || '').trim()) {
|
|
62
|
+
addWarning(
|
|
63
|
+
'INTERACTION_CASES_SKIP_NO_REASON',
|
|
64
|
+
'interactionCases.skipReason',
|
|
65
|
+
'interactionCases.policy is skip but skipReason is empty.',
|
|
66
|
+
'Declare skipReason explaining why L2 sequences are not needed.',
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const items = Array.isArray(ic.items) ? ic.items : []
|
|
73
|
+
|
|
74
|
+
if (policy === 'required' && items.length === 0) {
|
|
75
|
+
addGap(
|
|
76
|
+
'INTERACTION_CASES_ITEMS_MISSING',
|
|
77
|
+
'critical',
|
|
78
|
+
'interactionCases.items',
|
|
79
|
+
'interactionCases.policy is required but items[] is empty.',
|
|
80
|
+
'Add at least one IC-* item with description and sequenceDiagram.',
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (items.length === 0) return
|
|
86
|
+
|
|
87
|
+
const actionIds = new Set(
|
|
88
|
+
(bundle?.design?.actions || [])
|
|
89
|
+
.map((a) => a?.id)
|
|
90
|
+
.filter(Boolean),
|
|
91
|
+
)
|
|
92
|
+
const statuses = new Set(bundle?.design?.stateMatrix?.recordStatuses || [])
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < items.length; i++) {
|
|
95
|
+
const item = items[i]
|
|
96
|
+
const base = `interactionCases.items[${i}]`
|
|
97
|
+
if (!item?.id) {
|
|
98
|
+
addGap(
|
|
99
|
+
'INTERACTION_CASES_MISSING_ID',
|
|
100
|
+
'critical',
|
|
101
|
+
`${base}.id`,
|
|
102
|
+
'Interaction case missing id (use IC-*).',
|
|
103
|
+
'Declare id: IC-SUBMIT-HAPPY',
|
|
104
|
+
)
|
|
105
|
+
} else if (!/^IC-[A-Z0-9][A-Z0-9-]*$/i.test(String(item.id))) {
|
|
106
|
+
addWarning(
|
|
107
|
+
'INTERACTION_CASES_ID_FORMAT',
|
|
108
|
+
`${base}.id`,
|
|
109
|
+
`Interaction case id "${item.id}" should match IC-* pattern.`,
|
|
110
|
+
'Rename to IC-<DOMAIN>-<CASE> (e.g. IC-SUBMIT-409).',
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
if (!String(item?.title || '').trim()) {
|
|
114
|
+
addGap(
|
|
115
|
+
'INTERACTION_CASES_MISSING_TITLE',
|
|
116
|
+
'critical',
|
|
117
|
+
`${base}.title`,
|
|
118
|
+
'Interaction case missing title.',
|
|
119
|
+
'Add short title for reviewers and agents.',
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
if (!String(item?.description || '').trim()) {
|
|
123
|
+
addGap(
|
|
124
|
+
'INTERACTION_CASES_MISSING_DESCRIPTION',
|
|
125
|
+
'critical',
|
|
126
|
+
`${base}.description`,
|
|
127
|
+
'Interaction case missing description (user case prose).',
|
|
128
|
+
'Describe user steps and expected UI/API feedback.',
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
const seq = String(item?.sequenceDiagram || '')
|
|
132
|
+
if (!seq.includes('sequenceDiagram')) {
|
|
133
|
+
addGap(
|
|
134
|
+
'INTERACTION_CASES_MISSING_SEQUENCE',
|
|
135
|
+
'critical',
|
|
136
|
+
`${base}.sequenceDiagram`,
|
|
137
|
+
'Interaction case missing Mermaid sequenceDiagram.',
|
|
138
|
+
'Add sequenceDiagram with alt/else for error paths when applicable.',
|
|
139
|
+
)
|
|
140
|
+
} else if (!/alt\s+/i.test(seq) && complex) {
|
|
141
|
+
addWarning(
|
|
142
|
+
'INTERACTION_CASES_NO_ALT',
|
|
143
|
+
`${base}.sequenceDiagram`,
|
|
144
|
+
'Sequence diagram has no alt/else block — recommend at least one error branch.',
|
|
145
|
+
'Model validation, 409, or timeout with alt/else.',
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
const actionId = item?.links?.actionId
|
|
149
|
+
if (actionId && actionIds.size && !actionIds.has(actionId)) {
|
|
150
|
+
addWarning(
|
|
151
|
+
'INTERACTION_CASES_UNKNOWN_ACTION',
|
|
152
|
+
`${base}.links.actionId`,
|
|
153
|
+
`links.actionId "${actionId}" not found in design.actions.`,
|
|
154
|
+
'Align with an existing action id or add the action.',
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
const st = item?.links?.recordStatus
|
|
158
|
+
if (st && statuses.size && !statuses.has(st)) {
|
|
159
|
+
addWarning(
|
|
160
|
+
'INTERACTION_CASES_UNKNOWN_STATUS',
|
|
161
|
+
`${base}.links.recordStatus`,
|
|
162
|
+
`links.recordStatus "${st}" not in stateMatrix.recordStatuses.`,
|
|
163
|
+
`Use one of: ${[...statuses].join(', ')}`,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (policy === 'required' && mutations >= 2 && items.length < mutations) {
|
|
169
|
+
addWarning(
|
|
170
|
+
'INTERACTION_CASES_FEW_ITEMS',
|
|
171
|
+
'interactionCases.items',
|
|
172
|
+
`Only ${items.length} interaction case(s) for ${mutations} mutation action(s).`,
|
|
173
|
+
'Consider one IC-* per critical mutation path (happy + conflict).',
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (complex && !policy) {
|
|
178
|
+
addWarning(
|
|
179
|
+
'INTERACTION_CASES_POLICY_UNSET',
|
|
180
|
+
'interactionCases.policy',
|
|
181
|
+
'interactionCases present but policy not set to required or skip.',
|
|
182
|
+
'Set policy: required or policy: skip with skipReason.',
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { applyOpenQaField } from './open-qa.mjs'
|
|
21
21
|
import { applyDesignApiFrom01 } from './hydrate-design-api.mjs'
|
|
22
22
|
import { isPlaceholderLegacy, projectBusinessPage } from './project-business-layout.mjs'
|
|
23
|
+
import { applyInteractionCasesToIr } from './interaction-cases.mjs'
|
|
23
24
|
|
|
24
25
|
export function bundlePageId(bundle) {
|
|
25
26
|
const v = bundle?.['page-id'] ?? bundle?.pageId ?? bundle?.id
|
|
@@ -39,6 +40,12 @@ export function buildIrFromBundle(bundle) {
|
|
|
39
40
|
const pageId = bundlePageId(bundle)
|
|
40
41
|
const legacy = { id: pageId, ...(bundle.legacy ?? {}) }
|
|
41
42
|
const design = buildDesignIr(bundle, designSpec, gen)
|
|
43
|
+
if (bundle.interactionCases != null) {
|
|
44
|
+
delete spec.interactionCases
|
|
45
|
+
applyInteractionCasesToIr(spec, design, bundle.interactionCases)
|
|
46
|
+
} else if (spec.interactionCases) {
|
|
47
|
+
delete spec.interactionCases
|
|
48
|
+
}
|
|
42
49
|
|
|
43
50
|
return { spec, legacy, design, designSpec, gen }
|
|
44
51
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split bundle interactionCases → ir/spec (prose) + ir/design (sequenceDiagram only).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {unknown} interactionCases
|
|
7
|
+
* @returns {{ specPart: object|null, designPart: object|null }}
|
|
8
|
+
*/
|
|
9
|
+
export function partitionInteractionCasesForIr(interactionCases) {
|
|
10
|
+
if (!interactionCases || typeof interactionCases !== 'object') {
|
|
11
|
+
return { specPart: null, designPart: null }
|
|
12
|
+
}
|
|
13
|
+
const { policy, skipReason, items } = interactionCases
|
|
14
|
+
const specPart = {
|
|
15
|
+
...(policy != null ? { policy } : {}),
|
|
16
|
+
...(skipReason != null && String(skipReason).trim() ? { skipReason } : {}),
|
|
17
|
+
items: Array.isArray(items)
|
|
18
|
+
? items.map((item) => {
|
|
19
|
+
if (!item || typeof item !== 'object') return item
|
|
20
|
+
const { id, title, description, links } = item
|
|
21
|
+
return { id, title, description, links }
|
|
22
|
+
})
|
|
23
|
+
: [],
|
|
24
|
+
}
|
|
25
|
+
const designItems = Array.isArray(items)
|
|
26
|
+
? items
|
|
27
|
+
.filter((item) => item && typeof item === 'object' && item.sequenceDiagram)
|
|
28
|
+
.map(({ id, sequenceDiagram }) => ({ id, sequenceDiagram }))
|
|
29
|
+
: []
|
|
30
|
+
const designPart = designItems.length ? { items: designItems } : null
|
|
31
|
+
return { specPart, designPart }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {Record<string, unknown>} spec
|
|
36
|
+
* @param {Record<string, unknown>} design
|
|
37
|
+
* @param {unknown} interactionCases from bundle
|
|
38
|
+
*/
|
|
39
|
+
export function applyInteractionCasesToIr(spec, design, interactionCases) {
|
|
40
|
+
const { specPart, designPart } = partitionInteractionCasesForIr(interactionCases)
|
|
41
|
+
if (!specPart && !designPart) return
|
|
42
|
+
if (specPart && (specPart.policy || specPart.skipReason || specPart.items?.length)) {
|
|
43
|
+
spec.interactionCases = specPart
|
|
44
|
+
}
|
|
45
|
+
if (designPart) design.interactionCases = designPart
|
|
46
|
+
}
|
|
@@ -34,7 +34,7 @@ async function main() {
|
|
|
34
34
|
const mdRel = mdOut ? path.relative(process.cwd(), mdOut) : null
|
|
35
35
|
const genDir = mdOut ? path.dirname(mdOut) : null
|
|
36
36
|
const extra = genDir
|
|
37
|
-
? ['data-model.md', 'api.md']
|
|
37
|
+
? ['data-model.md', 'api.md', 'design.md']
|
|
38
38
|
.filter((name) => fs.existsSync(path.join(genDir, name)))
|
|
39
39
|
.map((name) => path.relative(process.cwd(), path.join(genDir, name)))
|
|
40
40
|
: []
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Agent design context (L1 FLOW · L2 interactionCases · NFR · ACP)
|
|
2
|
+
|
|
3
|
+
**Status:** Implemented — `interactionCases`, split/render, audit, ACP skills, `screenToFlows`, MCP `linkedFlows` / `flowgrid_docs_user_flows?id=`.
|
|
4
|
+
|
|
5
|
+
**See also:** `product-id-convention.md`. BE runtime-sequence diagram layer is a future phase (not L1/L2).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem
|
|
10
|
+
|
|
11
|
+
Agents often read only `*.bundle.yaml` + `ir/design.yaml` and miss three independent axes:
|
|
12
|
+
|
|
13
|
+
| Axis | SSOT | Agent gap |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| **L1 Journey (cross-screen)** | `FLOW-*.md` (Markdown only) | No enforced read before codegen on `W-*` |
|
|
16
|
+
| **L2 Interaction (one screen)** | `interactionCases` on bundle + `stateMatrix` / `actions` | No timeline diagram per branch (409, double-submit, …) |
|
|
17
|
+
| **NFR overlay** | `bundle.nfr` + `architecture/08-*` links | NFR treated as appendix, not constraint |
|
|
18
|
+
|
|
19
|
+
**Sequence diagrams at the right layer materially improve codegen quality.**
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Layer model
|
|
24
|
+
|
|
25
|
+
| Layer | SSOT | Format | Question answered |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| **L1** | `FLOW-*` · `common/user-flows/` | MD + **§6 `sequenceDiagram`** | Where the user goes across screens |
|
|
28
|
+
| **L2** | `interactionCases` on bundle (optional) | YAML → split | One `W-*`: cases + per-case sequence |
|
|
29
|
+
| **L3** | `design.stateMatrix` | YAML | Machine state/button matrix |
|
|
30
|
+
| **L4** | bundle + `ir/design.yaml` + `01` | YAML | Fields, actions, apiRef, outcomes |
|
|
31
|
+
| **NFR** | `bundle.nfr` | MD bullets | Constraints on all cases |
|
|
32
|
+
|
|
33
|
+
**Do not** put L2 single-screen branches into FLOW MD. **Do not** require L2 on trivial list-only GET screens.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## L1 ↔ leaf link: `userFlows` on bundle
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
userFlows: |
|
|
41
|
+
- FLOW-checkout — role: step-3-ops — screens: W-ADM-ORD-01 (this leaf)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Token | Agent use |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `FLOW-*` | Open FLOW MD SSOT |
|
|
47
|
+
| `role` | Focus §4 stage |
|
|
48
|
+
| `screens` + `(this leaf)` | Filter journey to current screen |
|
|
49
|
+
|
|
50
|
+
Resolve FLOW paths: `architecture/03-user-flows/<FLOW>.md`, `surfaces/**/common/user-flows/<FLOW>.md`.
|
|
51
|
+
|
|
52
|
+
**Discovery:** `flowgrid_docs_route("W-…")` → `linkedFlows[]`; index `registries/docs-index.json` → `screenToFlows`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## L2: `interactionCases` on bundle
|
|
57
|
+
|
|
58
|
+
Authoring SSOT: `feature.bundle.yaml` (not separate FLOW files).
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
interactionCases:
|
|
62
|
+
policy: required | skip
|
|
63
|
+
skipReason: "" # required when policy: skip
|
|
64
|
+
items:
|
|
65
|
+
- id: IC-SUBMIT-409
|
|
66
|
+
title: ...
|
|
67
|
+
description: ...
|
|
68
|
+
sequenceDiagram: |
|
|
69
|
+
sequenceDiagram
|
|
70
|
+
...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Split:** prose → `ir/spec.yaml` → `ir/generated/spec.md` (§ Interaction cases); diagrams only → `ir/design.yaml` → `ir/generated/design.md`.
|
|
74
|
+
|
|
75
|
+
**Codegen read order for L2:** `spec.md` prose + `design.md` mermaid + `stateMatrix` / `actions` — not FLOW.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Agent context package (ACP) — read before codegen
|
|
80
|
+
|
|
81
|
+
For target leaf `W-*`:
|
|
82
|
+
|
|
83
|
+
1. **`userFlows`** → each **`FLOW-*.md`** (§3–§4 for this screen, **§6 sequence**).
|
|
84
|
+
2. **`bundle.nfr`** (+ linked `architecture/08-cross-cutting/*` when cited).
|
|
85
|
+
3. **`ir/generated/design.md`** when `interactionCases.policy: required` (after split + render).
|
|
86
|
+
4. **`ir/design.yaml`** + **`01-backend-spec.yaml`**.
|
|
87
|
+
5. Then patch `bundle.gen` / run gen.
|
|
88
|
+
|
|
89
|
+
**Chat checklist:** FLOW ids read · NFR read · L2 design.md yes/no.
|
|
90
|
+
|
|
91
|
+
**Skills:** `/spec` (declare `userFlows` when cross-screen); `/grill-dev`, `/prototype`, `/api-spec` (ACP); `/user-flow` (author FLOW + §6, trace `W-*` in §5).
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Audit (`flowgrid audit spec`)
|
|
96
|
+
|
|
97
|
+
**Step 1 — policy confirm** when screen is complex and `interactionCases` missing:
|
|
98
|
+
|
|
99
|
+
- Emit `CONFIRM_INTERACTION_CASES_POLICY` in `confirms[]`.
|
|
100
|
+
- Options: **(Recommended) required** — set `policy: required` + `items[]`; **skip** — `policy: skip` + `skipReason`; **Other** (free text).
|
|
101
|
+
|
|
102
|
+
**Step 2 — item validation** when `policy: required` or `items.length > 0`:
|
|
103
|
+
|
|
104
|
+
- Each item needs `id`, `title`, `description`, `sequenceDiagram`.
|
|
105
|
+
- Optional `links.actionId`, `links.recordStatus`, `links.scenario`.
|
|
106
|
+
|
|
107
|
+
Skip step 1 when `interactionCases.policy: skip` is already declared.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## VitePress (member review)
|
|
112
|
+
|
|
113
|
+
| Page | Source |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| Spec | `ir/generated/spec.md` |
|
|
116
|
+
| Linked flows | Rendered from `userFlows` |
|
|
117
|
+
| Design sequences | `ir/generated/design.md` |
|
|
118
|
+
| API | `ir/generated/api.md` |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## One-line summary
|
|
123
|
+
|
|
124
|
+
**L1:** `/user-flow` → `FLOW-*.md` + §6; leaf links via **`userFlows`**. **NFR:** mandatory read before design/API. **L2:** **`interactionCases`** on bundle → `spec.md` + `design.md`; never in FLOW.
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
".cursor/extracts/db-audit-wizard.md",
|
|
29
29
|
".cursor/extracts/agent-execution-protocol.md",
|
|
30
30
|
".cursor/extracts/common-scope.md",
|
|
31
|
-
".cursor/extracts/product-id-convention.md"
|
|
31
|
+
".cursor/extracts/product-id-convention.md",
|
|
32
|
+
".cursor/extracts/agent-design-context.md"
|
|
32
33
|
],
|
|
33
34
|
"spec-core": [
|
|
34
35
|
".cursor/extracts/spec-core.md",
|
|
35
36
|
".cursor/extracts/common-scope.md",
|
|
36
|
-
".cursor/extracts/product-id-convention.md"
|
|
37
|
+
".cursor/extracts/product-id-convention.md",
|
|
38
|
+
".cursor/extracts/agent-design-context.md"
|
|
37
39
|
],
|
|
38
40
|
"bqa-grill": [
|
|
39
41
|
".cursor/extracts/grill/validation.md",
|
|
@@ -43,7 +45,8 @@
|
|
|
43
45
|
"dev-grill": [
|
|
44
46
|
".cursor/extracts/codegen/readiness.md",
|
|
45
47
|
".cursor/extracts/docs-mark-detect.md",
|
|
46
|
-
".cursor/extracts/db-audit-wizard.md"
|
|
48
|
+
".cursor/extracts/db-audit-wizard.md",
|
|
49
|
+
".cursor/extracts/agent-design-context.md"
|
|
47
50
|
],
|
|
48
51
|
"grill-docs": [
|
|
49
52
|
".cursor/extracts/grill-docs-reconcile.md",
|
|
@@ -18,6 +18,10 @@ Shared extracts: `spec-evolution.md`, `api-spec-sync.md`, `entity-relationship.m
|
|
|
18
18
|
|
|
19
19
|
Hashtag extracts: `#call-external` → `call-external.md`; `#cross-entity-service` → `cross-entity-service.md`.
|
|
20
20
|
|
|
21
|
+
## Agent context (ACP) before `01`
|
|
22
|
+
|
|
23
|
+
Per **`agent-design-context.md`**: (1) `userFlows` → `FLOW-*.md` §6 (`flowgrid_docs_user_flows` / `flowgrid_docs_route`); (2) `bundle.nfr`; (3) `ir/generated/design.md` if `interactionCases.policy: required`; (4) `ir/design.yaml` + `01`. Print checklist: FLOW · NFR · L2.
|
|
24
|
+
|
|
21
25
|
---
|
|
22
26
|
|
|
23
27
|
## Rule: Audit Interlock
|
|
@@ -28,25 +28,38 @@ disable-model-invocation: true
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
+
## Rule: Agent context (ACP) before codegen
|
|
32
|
+
|
|
33
|
+
Per **`agent-design-context.md`** — read **before** patching `bundle.gen` / codegen:
|
|
34
|
+
|
|
35
|
+
1. **`userFlows`** → each linked **`FLOW-*.md`** (§3–§4 for this `W-*`, **§6 sequence**).
|
|
36
|
+
2. **`bundle.nfr`** (+ linked `architecture/08` if cited).
|
|
37
|
+
3. **`ir/generated/design.md`** when `interactionCases.policy: required` (after `split` + `render`).
|
|
38
|
+
4. Then **`ir/design.yaml`** + **`01`**.
|
|
39
|
+
|
|
40
|
+
Print checklist in chat: FLOW ids read · NFR read · L2 design.md yes/no.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
31
44
|
## Rule: Audit Interlock with Page Type
|
|
32
45
|
|
|
33
46
|
- **[MANDATORY]** Before grilling, run: `flowgrid audit spec <bundle> --type <profile>`. Treat `warnings[]` as non-blocking; nudge BQA via handoff if summary still has `[placeholder]` brackets.
|
|
34
47
|
- **[MANDATORY]** After `bundle.gen` patch: `flowgrid split` + `flowgrid render` (FE uses `ir/design.yaml`; BA PDF path uses `ir/generated/spec.md`).
|
|
35
|
-
- `<profile>`
|
|
36
|
-
-
|
|
48
|
+
- `<profile>` comes from confirmed `gen.codegen.profile` (list | create | detail | admin-crud | auth | ...).
|
|
49
|
+
- If profile is unset → ask the member to confirm profile first; do **not** run audit with `--type unknown`.
|
|
37
50
|
- Script output `gaps[]` + `confirms[]` (`CONFIRM_UX_*`, **`CONFIRM_DB_*`** — `db-audit-wizard.md`).
|
|
38
51
|
- Agent patches structural `gaps[]`; UX/DB confirms → wizard; BE drift → `/api-update` then re-audit.
|
|
39
52
|
|
|
40
53
|
---
|
|
41
54
|
|
|
42
|
-
## Rule: Zone-Based Grill (
|
|
55
|
+
## Rule: Zone-Based Grill (avoid lost-in-the-middle)
|
|
43
56
|
|
|
44
|
-
- **[MANDATORY]** Grill
|
|
45
|
-
- **[MANDATORY]**
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
- **[MANDATORY]** Script
|
|
49
|
-
- **[STRICTLY FORBIDDEN]**
|
|
57
|
+
- **[MANDATORY]** Grill by zone; do **not** grill the entire bundle in one pass.
|
|
58
|
+
- **[MANDATORY]** Split the bundle into zones based on actual content:
|
|
59
|
+
- One zone per turn: read zone data → analyze quality (logic, consistency, cross-field gaps) → patch.
|
|
60
|
+
- If a zone is too large → subdivide further.
|
|
61
|
+
- **[MANDATORY]** Script checks **quantity** (field present or not). Agent checks **quality** (correct content, logic, cross-field gaps).
|
|
62
|
+
- **[STRICTLY FORBIDDEN]** Single all-in-one pass that drops items in the middle.
|
|
50
63
|
|
|
51
64
|
## Rule: Missing Information / Hard Gate & Workload Threshold (Law 2)
|
|
52
65
|
|
|
@@ -9,7 +9,7 @@ disable-model-invocation: true
|
|
|
9
9
|
> **[MANDATORY]** Re-read this entire `SKILL.md` via file-read tool. **STRICTLY FORBIDDEN** to rely on memory.
|
|
10
10
|
> **[MANDATORY]** Read `.flowgrid/templates/feature.bundle.yaml` + `.flowgrid/templates/bundle-authoring.md` BEFORE generating any YAML. **Do NOT** use `design-spec.yaml` (deprecated).
|
|
11
11
|
> If templates are missing → STOP: *"Template missing. Run `flowgrid init` to generate templates."*
|
|
12
|
-
> **Extract:** `spec-ssot-prep.md`, `spec-prd-lite.md`, `db-audit-wizard.md`, `design-leaf-signoff.md`.
|
|
12
|
+
> **Extract:** `spec-ssot-prep.md`, `spec-prd-lite.md`, `db-audit-wizard.md`, `design-leaf-signoff.md`, `agent-design-context.md`.
|
|
13
13
|
> Physical interlocks: `AGENTS.md` + `SSOT_AGENT_PROTOCOL.md` (Laws 1–7). Chat-only done = **FAILED**.
|
|
14
14
|
|
|
15
15
|
# /spec — Function detail (design)
|
|
@@ -62,7 +62,7 @@ Hub: [spec-ssot-prep.md](../../../docs/workflows/spec-ssot-prep.md) · extract `
|
|
|
62
62
|
- `<pageType>` = page type đã xác định ở bước trên (list | create | detail | admin-crud | auth | ...).
|
|
63
63
|
- Script output:
|
|
64
64
|
- `gaps[]` → required fields missing → Agent patches bundle directly.
|
|
65
|
-
- `warnings[]` → **quality hints** (`scopeIn`, `nonGoals`, `nfr`, `userFlows`, placeholders) — fix when info exists; **does not** block split.
|
|
65
|
+
- `warnings[]` → **quality hints** (`scopeIn`, `nonGoals`, `nfr`, `userFlows`, placeholders) — fix when info exists; **does not** block split. Risks → `/risk-register` only (`WARN_BUNDLE_RISKS_FORBIDDEN` if `risks:` on bundle).
|
|
66
66
|
- `confirms[]` → AskQuestion wizard (one question at a time, ≥3 options):
|
|
67
67
|
- UX: `category: ux`, `UX_*`, `CONFIRM_UX_*` — `flowgrid-ux-common.mdc`
|
|
68
68
|
- **DB:** `category: db`, `CONFIRM_DB_*` — `.cursor/extracts/db-audit-wizard.md` (entities, multi-table, `db` vs `01`, `#derived-data`)
|
|
@@ -149,11 +149,14 @@ Each zone turn — **in order**:
|
|
|
149
149
|
- **[MANDATORY]** List columns: `key` consistent with `bind.field`; computed-only columns → document `#derived-data` (see `common/data-model/derived-data.md`), no fake `db`.
|
|
150
150
|
- **[STRICTLY FORBIDDEN]** Empty `entities: []` while form/list has multiple persisted `db.field` without QA defer.
|
|
151
151
|
- **Authoring detail:** [bundle-authoring.md § Data model](../../../templates/shared/bundle-authoring.md#data-model--phase-0-erd-vs-screen-detail) · [tpl-screen-data-model.md](../../../templates/shared/tpl-screen-data-model.md) (multi-table).
|
|
152
|
-
- **
|
|
152
|
+
- **After split:** `ir/generated/data-model.md` for review. **Audit:** `CONFIRM_DB_*` → member wizard; re-audit after confirm. BE SSOT: `01` must match `db` (drift → `/api-update`).
|
|
153
153
|
|
|
154
154
|
### Rule: Summary extensions (PRD lite)
|
|
155
155
|
- **[MANDATORY]** `summary` bullets: business_goals, stakeholders, user_journey, context (input/output), optional solution.
|
|
156
|
-
- **[RECOMMENDED]** Fill `scopeIn`, `nonGoals`, `userFlows`, `nfr` when PO/BA
|
|
156
|
+
- **[RECOMMENDED]** Fill `scopeIn`, `nonGoals`, `userFlows`, `nfr` when PO/BA provide facts. Complex single-screen flows → `interactionCases` (L2) or resolve audit `CONFIRM_INTERACTION_CASES_POLICY`. Project risks → **`/risk-register`**, never `risks:` on bundle.
|
|
157
|
+
- **[MANDATORY]** `userFlows:` bullets link **`FLOW-*`** (`FLOW-id — role: … — screens: W-* (this leaf)`). Journey sequence lives in **`FLOW-*.md` §6** — not in bundle.
|
|
158
|
+
- **[RECOMMENDED]** Multi-state / multi-case on **one screen:** optional top-level **`interactionCases`** (`policy`, `items[]` with `IC-*`, `description`, `sequenceDiagram`) — see `agent-design-context.md`. After split: prose → `spec.md`, diagrams → `design.md`.
|
|
159
|
+
- **[MANDATORY]** Consume `confirms[]` **`CONFIRM_INTERACTION_CASES_POLICY`** from audit — set `policy: required` or `policy: skip` + `skipReason`.
|
|
157
160
|
- **[MANDATORY]** Replace template `[placeholder]` brackets in `summary` / metrics / non-goals before handoff grill.
|
|
158
161
|
|
|
159
162
|
### Rule: User Stories (`userStories`)
|
|
@@ -269,6 +272,6 @@ Each zone turn — **in order**:
|
|
|
269
272
|
- [ ] UX gap questions used checklist-backed `(Recommended)` options (`flowgrid-ux-common.mdc`), not open brainstorming.
|
|
270
273
|
- [ ] `userStories` scenarios/AC reflect UX affordances patched in `design` (incl. audit `suggestedStoryPatch`).
|
|
271
274
|
- [ ] YAML strings with `:` or `[]` are double-quoted. No `.md` written by hand.
|
|
272
|
-
- [ ] PRD fields (`scopeIn`, `nonGoals`, `userFlows`, `nfr`) filled or deferred via `qa/` (no template brackets).
|
|
275
|
+
- [ ] PRD fields (`scopeIn`, `nonGoals`, `userFlows`, `nfr`) filled or deferred via `qa/` (no template brackets). No project risks on bundle (`/risk-register` only).
|
|
273
276
|
- [ ] `pnpm docs:split` + `pnpm docs:render` run with zero errors; `ir/generated/spec.md` has TOC + overview sections.
|
|
274
277
|
- [ ] Handoff → `/testcase` created.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: user-flow
|
|
3
|
-
description: /user-flow —
|
|
3
|
+
description: /user-flow — Author cross-surface user journeys (FLOW-*.md) on the docs hub.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
extractBundle: architecture-core
|
|
6
6
|
---
|
|
@@ -12,7 +12,9 @@ extractBundle: architecture-core
|
|
|
12
12
|
|
|
13
13
|
**Mindset:** Model the process by **business actions on surfaces**, not by repository or service topology.
|
|
14
14
|
|
|
15
|
-
**Template:** `architecture/03-user-flows/FLOW-template.md` (or `**/common/user-flows/FLOW-*.md`) — **Non-goals** in §1 when information exists (
|
|
15
|
+
**Template:** `architecture/03-user-flows/FLOW-template.md` (or `**/common/user-flows/FLOW-*.md`) — **Non-goals** in §1 when information exists (out-of-hub KPIs).
|
|
16
|
+
|
|
17
|
+
**Leaf link:** Bundles reference this file via `userFlows:` (`FLOW-* — role — screens`). Agents read FLOW **before** codegen (`agent-design-context.md`). **Do not** put single-screen multi-state cases here — use bundle `interactionCases` (L2).
|
|
16
18
|
|
|
17
19
|
---
|
|
18
20
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prototype
|
|
3
|
-
description: /prototype — UI from FLOWGRID_DOCS_ROOT ir/design.yaml with mock API via
|
|
3
|
+
description: /prototype — UI from FLOWGRID_DOCS_ROOT ir/design.yaml with mock API via the FE code repo.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /prototype — UI Prototype (Mock API Boundary)
|
|
8
8
|
|
|
9
|
-
**Owner:**
|
|
9
|
+
**Owner:** FE code repo (`--type=fe`) · Adapters: `nuxt4` | `nextjs` | `dotnet-line`
|
|
10
10
|
|
|
11
11
|
## Artifact & Target ID Resolution Rule
|
|
12
12
|
|
|
@@ -23,6 +23,17 @@ surfaces/<surface>/CMP-*/<NN…>/ir/design.yaml
|
|
|
23
23
|
|
|
24
24
|
Read the **entire** `ir/design.yaml` (script + agent inspection). Do not filter keys from `*.bundle.yaml`. **`ir/spec.yaml`** is business prose only.
|
|
25
25
|
|
|
26
|
+
## Agent context (ACP) before gen
|
|
27
|
+
|
|
28
|
+
Per **`agent-design-context.md`** (resolve `W-*` via `flowgrid_docs_route` → `linkedFlows[]`):
|
|
29
|
+
|
|
30
|
+
1. **`userFlows` / linked `FLOW-*.md`** — §6 sequence for journey context.
|
|
31
|
+
2. **`bundle.nfr`** (from bundle or `ir/spec.yaml` mirror).
|
|
32
|
+
3. **`ir/generated/design.md`** when `interactionCases.policy: required`.
|
|
33
|
+
4. Then **`ir/design.yaml`**.
|
|
34
|
+
|
|
35
|
+
Print checklist in chat: FLOW ids read · NFR · L2 design.md yes/no.
|
|
36
|
+
|
|
26
37
|
**Docs hub is read-only for this skill.** Never Write/patch `*.bundle.yaml`, `ir/*`, or any file under `FLOWGRID_DOCS_ROOT`. Missing `ir/design.yaml`, empty `codegen.profile`, or `flowgrid gen` failure → **STOP**, quote the CLI error, hand off to docs-hub `/grill-dev` (or `/spec`). Do not invent SSOT to make gen pass. Login/forgot/reset must be `codegen.profile: auth` (not `create`) or gen writes `(dashboard)`.
|
|
27
38
|
|
|
28
39
|
Do not invent sibling docs-hub paths. Pass `FLOWGRID_DOCS_ROOT` or `--docs-root`.
|
|
@@ -38,7 +49,7 @@ Do not invent sibling docs-hub paths. Pass `FLOWGRID_DOCS_ROOT` or `--docs-root`
|
|
|
38
49
|
|
|
39
50
|
## Route
|
|
40
51
|
|
|
41
|
-
Architecture/C4 →
|
|
52
|
+
Architecture/C4 → docs hub (`FLOWGRID_DOCS_ROOT`); IR/registry/gen →
|
|
42
53
|
`FLOWGRID_DOCS_ROOT`; symbols/call-graph for repo X → Platform DNA-wired
|
|
43
54
|
`codegraph-<repo-key>`. Never workspace-parent graphs, sibling-path inference,
|
|
44
55
|
or member-edited MCP. Local ArtifactGraph is allowlist/tag hints for this repo
|
|
@@ -75,7 +86,7 @@ For each HANDOFF / `#needs-component: Mo…` after gen:
|
|
|
75
86
|
4. Re-run `flowgrid gen` so slots bind to the new file.
|
|
76
87
|
5. Dotnet-line / no `components.json`: skip this section; do not invent React/shadcn APIs.
|
|
77
88
|
|
|
78
|
-
Do not copy the shadcn skill into
|
|
89
|
+
Do not copy the shadcn skill into the docs hub. Do not author `#ui:` tags from memory if `shadcn search` can name the registry item.
|
|
79
90
|
|
|
80
91
|
`dotnet-line` requires the .NET 8 SDK (`FLOWGRID_DOTNET`, then `dotnet`) and
|
|
81
92
|
is limited to the pilot-specific `kiosk-check-in` profile. Its main pass also
|
|
@@ -88,14 +99,15 @@ if local ArtifactGraph available: recommend/check the FE repo's allowlisted gen
|
|
|
88
99
|
else: run flowgrid gen:dry / gen directly
|
|
89
100
|
|
|
90
101
|
Missing ArtifactGraph never blocks prototype generation. Complete the direct,
|
|
91
|
-
deterministic
|
|
102
|
+
deterministic FE-repo fallback first, then follow
|
|
92
103
|
`.cursor/rules/flowgrid-code-optional-integrations.mdc` for once-per-run telemetry.
|
|
93
104
|
```
|
|
94
105
|
|
|
95
|
-
Docs render / `spec:split` remain flowgrid /
|
|
106
|
+
Docs render / `spec:split` remain flowgrid / docs-hub handoffs.
|
|
96
107
|
|
|
97
108
|
## Translation Rule
|
|
98
|
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
109
|
+
|
|
110
|
+
- Wrap all static UI copy with the framework’s native i18n helper.
|
|
111
|
+
- Read the `i18n` block from `ir/design.yaml`.
|
|
112
|
+
- Generate or update locale files (`.json` for FE/Node, `.resx` for dotnet-line).
|
|
113
|
+
- Do **not** hard-code mixed languages in UI (e.g. avoid `login / đăng nhập` in one string).
|
package/package.json
CHANGED
|
@@ -10,7 +10,8 @@ Hub: `docs/templates/feature.bundle.yaml` · split: `pnpm spec:split`
|
|
|
10
10
|
| `summary` | Phải trình bày dạng bullet. Bắt buộc có các tiêu đề (chuẩn Arc42 business): **mục tiêu nghiệp vụ** (business_goals), **các bên liên quan** (stakeholders), **kịch bản người dùng** (user_journey), **bối cảnh** (description, input liên kết cross-page/module, output) và **cách giải quyết** (tùy chọn). Mục đích để 100% Non-tech Stakeholder hiểu và duyệt. |
|
|
11
11
|
| `scopeIn` | **Khuyến nghị** — trong phạm vi màn/phase (PRD). Audit `WARN_NO_SCOPE_IN`. |
|
|
12
12
|
| `nonGoals` | **Khuyến nghị** — ngoài phạm vi màn/phase (PRD). Audit `WARN_NO_NON_GOALS`. |
|
|
13
|
-
| `userFlows` | **
|
|
13
|
+
| `userFlows` | **Recommended** — `FLOW-* — role: … — screens: W-* (this leaf)`; agents read linked `FLOW-*.md` §6 before codegen (`agent-design-context.md`). |
|
|
14
|
+
| `interactionCases` | **Optional L2** — `policy` + `items[]` (`IC-*`, `description`, `sequenceDiagram`); split → `spec.md` + `ir/generated/design.md`. Audit `CONFIRM_INTERACTION_CASES_POLICY`. |
|
|
14
15
|
| `nfr` | **Khuyến nghị** — hiệu năng, bảo mật. Audit `WARN_NO_NFR`. |
|
|
15
16
|
| _(rủi ro)_ | **Không** khai báo trên bundle. SSOT: `architecture/11-risks/risk-register.md` — `/risk-register`. Key `risks:` → audit `WARN_BUNDLE_RISKS_FORBIDDEN`. |
|
|
16
17
|
| `userStories` | **Khối User Stories chuyên sâu cho màn hình:** `primary`, `contextAndHandoff` (+ `screenAccess`), `scenarios` (5 kịch bản chuẩn + **scenario thứ 6 “Affordances UX”** khi màn có delete/filter/breadcrumb/disabled/import — xem `feature.bundle.yaml`), `acceptanceCriteria` (kèm checkbox UX khi áp dụng). **Split:** `pnpm spec:split` copy nguyên khối sang `ir/spec.yaml` (business prose); **không** tự sinh từ `design` — Agent phải cập nhật `userStories` khi bổ sung DSL/`#needs-component`/audit `UX_*`. Render → `## User Stories & Screen Journey` trong Markdown. |
|