@dev-workflow/skill 0.1.0
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/LICENSE +21 -0
- package/README.md +80 -0
- package/bin/cli.mjs +9 -0
- package/package.json +39 -0
- package/skills/artifact/README.md +23 -0
- package/skills/execution/devFlow/SKILL.md +341 -0
- package/skills/execution/devFlow/agents/openai.yaml +7 -0
- package/skills/execution/devFlow/domains/backend/references/api-tech.md +173 -0
- package/skills/execution/devFlow/domains/backend/scripts/check_api_tech_doc.mjs +170 -0
- package/skills/execution/devFlow/domains/backend/scripts/check_api_tech_doc.test.mjs +89 -0
- package/skills/execution/devFlow/domains/backend/templates/api-tech.md +164 -0
- package/skills/execution/devFlow/domains/frontend/references/contract-check.md +270 -0
- package/skills/execution/devFlow/domains/frontend/references/foundation-freeze.md +92 -0
- package/skills/execution/devFlow/domains/frontend/references/lightweight-flow.md +55 -0
- package/skills/execution/devFlow/domains/frontend/references/page-build.md +268 -0
- package/skills/execution/devFlow/domains/frontend/references/page-tech.md +414 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/field-common.md +69 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-complex.md +105 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-id-code.md +44 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-number-date.md +106 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-relation.md +144 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-selectors.md +102 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/fields-text.md +130 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/index.md +42 -0
- package/skills/execution/devFlow/domains/frontend/references/product-design-specs/interaction.md +61 -0
- package/skills/execution/devFlow/domains/frontend/scripts/check_page_tech_doc.mjs +82 -0
- package/skills/execution/devFlow/domains/frontend/scripts/contract_check_static.mjs +145 -0
- package/skills/execution/devFlow/domains/frontend/scripts/generate_foundation_summary.mjs +224 -0
- package/skills/execution/devFlow/domains/frontend/templates/contract-report.md +66 -0
- package/skills/execution/devFlow/domains/frontend/templates/mini-plan-l0.md +7 -0
- package/skills/execution/devFlow/domains/frontend/templates/mini-plan-l1.md +9 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/component.tsx.tpl +8 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/constants.ts.tpl +5 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/index.ts.tpl +1 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/route.tsx.tpl +11 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/service.ts.tpl +17 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-build/types.ts.tpl +11 -0
- package/skills/execution/devFlow/domains/frontend/templates/page-tech.md +103 -0
- package/skills/execution/devFlow/domains/requirement/references/prd-review.md +129 -0
- package/skills/execution/devFlow/domains/requirement/templates/prd-review.md +57 -0
- package/skills/execution/figmaSync/SKILL.md +349 -0
- package/skills/execution/figmaSync/chapters/apply.md +177 -0
- package/skills/execution/figmaSync/chapters/plan.md +359 -0
- package/skills/execution/figmaSync/chapters/prepare.md +47 -0
- package/skills/execution/figmaSync/examples/native-css.md +129 -0
- package/skills/execution/figmaSync/examples/plan-doc-sample.md +39 -0
- package/skills/execution/figmaSync/scripts/_shared/session-log.mjs +82 -0
- package/skills/execution/figmaSync/scripts/_shared/token-resolver.mjs +132 -0
- package/skills/execution/figmaSync/scripts/_shared/token-source.mjs +145 -0
- package/skills/execution/figmaSync/scripts/figma-sync-report.mjs +448 -0
- package/skills/execution/figmaSync/scripts/icon-inventory.mjs +165 -0
- package/skills/execution/figmaSync/scripts/lookup-var.mjs +184 -0
- package/skills/execution/figmaSync/scripts/match-token.mjs +210 -0
- package/skills/execution/figmaSync/scripts/prepare-check.mjs +290 -0
- package/skills/execution/figmaSync/scripts/verify-plan.mjs +458 -0
- package/skills/execution/figmaSync/snapshots/README.md +13 -0
- package/skills/execution/figmaSync/snapshots/last-sync.md +1538 -0
- package/skills/execution/figmaSync/templates/PLAN.md.tpl +227 -0
- package/skills/review/consistency-checker/SKILL.md +112 -0
- package/skills/review/consistency-checker/rules/README.md +16 -0
- package/src/cli/agents.mjs +16 -0
- package/src/cli/copy.mjs +95 -0
- package/src/cli/install.mjs +131 -0
- package/src/cli/prompts.mjs +66 -0
- package/tools/lark/README.md +78 -0
- package/tools/lark/references/lark-doc.md +156 -0
- package/tools/lark/references/lark-read.md +235 -0
- package/tools/lark/references/prepare.md +147 -0
- package/tools/lark/scripts/lark_api.mjs +404 -0
- package/tools/lark/scripts/lark_api.test.mjs +63 -0
- package/tools/lark/scripts/lark_check_permissions.mjs +52 -0
- package/tools/lark/scripts/lark_publish_doc.mjs +299 -0
- package/tools/lark/scripts/lark_read_docx.mjs +328 -0
- package/tools/lark/scripts/markdown_to_lark_blocks.mjs +397 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const args = {
|
|
7
|
+
routeDir: null,
|
|
8
|
+
service: null,
|
|
9
|
+
types: null,
|
|
10
|
+
constants: null,
|
|
11
|
+
json: false
|
|
12
|
+
}
|
|
13
|
+
for (const arg of argv.slice(2)) {
|
|
14
|
+
if (arg.startsWith('--route-dir=')) args.routeDir = arg.slice('--route-dir='.length)
|
|
15
|
+
else if (arg.startsWith('--service=')) args.service = arg.slice('--service='.length)
|
|
16
|
+
else if (arg.startsWith('--types=')) args.types = arg.slice('--types='.length)
|
|
17
|
+
else if (arg.startsWith('--constants=')) args.constants = arg.slice('--constants='.length)
|
|
18
|
+
else if (arg === '--json') args.json = true
|
|
19
|
+
}
|
|
20
|
+
return args
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function exists(filePath) {
|
|
24
|
+
return Boolean(filePath && fs.existsSync(filePath))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isKebabCase(value) {
|
|
28
|
+
return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isPascalCase(value) {
|
|
32
|
+
return /^[A-Z][A-Za-z0-9]*\.tsx$/.test(value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function exportsPascalComponent(filePath) {
|
|
36
|
+
const source = fs.readFileSync(filePath, 'utf-8')
|
|
37
|
+
return (
|
|
38
|
+
/export\s+function\s+[A-Z][A-Za-z0-9]*\s*\(/.test(source) ||
|
|
39
|
+
/export\s+const\s+[A-Z][A-Za-z0-9]*\s*=/.test(source)
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectFiles(dir) {
|
|
44
|
+
if (!fs.existsSync(dir)) return []
|
|
45
|
+
const out = []
|
|
46
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
47
|
+
const full = path.join(dir, entry.name)
|
|
48
|
+
if (entry.isDirectory()) out.push(...collectFiles(full))
|
|
49
|
+
else out.push(full)
|
|
50
|
+
}
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function checkRouteTreeModified() {
|
|
55
|
+
const gitDir = '.git'
|
|
56
|
+
if (!fs.existsSync(gitDir)) return { status: 'unknown', modified: false }
|
|
57
|
+
try {
|
|
58
|
+
const { execSync } = await import('node:child_process')
|
|
59
|
+
const output = execSync('git status --porcelain -- src/routeTree.gen.ts', { encoding: 'utf-8' })
|
|
60
|
+
return { status: 'ok', modified: output.trim().length > 0 }
|
|
61
|
+
} catch {
|
|
62
|
+
return { status: 'unknown', modified: false }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const args = parseArgs(process.argv)
|
|
68
|
+
const checks = []
|
|
69
|
+
|
|
70
|
+
if (!args.routeDir) {
|
|
71
|
+
checks.push({ name: 'routeDir', ok: false, message: 'missing --route-dir' })
|
|
72
|
+
} else {
|
|
73
|
+
checks.push({
|
|
74
|
+
name: 'routeDirExists',
|
|
75
|
+
ok: fs.existsSync(args.routeDir),
|
|
76
|
+
message: args.routeDir
|
|
77
|
+
})
|
|
78
|
+
const rel = path.relative('src/routes', args.routeDir)
|
|
79
|
+
const segments = rel.split(path.sep).filter(Boolean)
|
|
80
|
+
checks.push({
|
|
81
|
+
name: 'routeDirNaming',
|
|
82
|
+
ok: !rel.startsWith('..') && segments.every(isKebabCase),
|
|
83
|
+
message: rel
|
|
84
|
+
})
|
|
85
|
+
checks.push({
|
|
86
|
+
name: 'routeFileExists',
|
|
87
|
+
ok: exists(path.join(args.routeDir, 'route.tsx')),
|
|
88
|
+
message: path.join(args.routeDir, 'route.tsx')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const componentFiles = collectFiles(args.routeDir).filter(
|
|
92
|
+
(file) =>
|
|
93
|
+
file.endsWith('.tsx') &&
|
|
94
|
+
path.basename(file) !== 'route.tsx' &&
|
|
95
|
+
path.basename(file) !== 'index.tsx'
|
|
96
|
+
)
|
|
97
|
+
const componentExports = componentFiles.filter(exportsPascalComponent)
|
|
98
|
+
const badComponents = componentExports.filter((file) => !isPascalCase(path.basename(file)))
|
|
99
|
+
checks.push({
|
|
100
|
+
name: 'componentFileNaming',
|
|
101
|
+
ok: badComponents.length === 0,
|
|
102
|
+
message:
|
|
103
|
+
badComponents.length === 0
|
|
104
|
+
? `${componentExports.length} component files checked`
|
|
105
|
+
: badComponents
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const typesPath = args.types ?? (args.routeDir ? path.join(args.routeDir, 'types.ts') : null)
|
|
110
|
+
const constantsPath =
|
|
111
|
+
args.constants ?? (args.routeDir ? path.join(args.routeDir, 'constants.ts') : null)
|
|
112
|
+
checks.push({ name: 'typesExists', ok: exists(typesPath), message: typesPath })
|
|
113
|
+
checks.push({ name: 'constantsExists', ok: exists(constantsPath), message: constantsPath })
|
|
114
|
+
if (args.service)
|
|
115
|
+
checks.push({ name: 'serviceExists', ok: exists(args.service), message: args.service })
|
|
116
|
+
|
|
117
|
+
const routeTree = await checkRouteTreeModified()
|
|
118
|
+
checks.push({
|
|
119
|
+
name: 'routeTreeNotModified',
|
|
120
|
+
ok: !routeTree.modified,
|
|
121
|
+
message: routeTree.status === 'unknown' ? 'git status unavailable' : 'src/routeTree.gen.ts'
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const result = {
|
|
125
|
+
ok: checks.every((check) => check.ok),
|
|
126
|
+
checks
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (args.json) {
|
|
130
|
+
console.log(JSON.stringify(result, null, 2))
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const check of checks) {
|
|
135
|
+
console.log(
|
|
136
|
+
`${check.ok ? 'PASS' : 'FAIL'} ${check.name}: ${Array.isArray(check.message) ? check.message.join(', ') : check.message}`
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
process.exit(result.ok ? 0 : 1)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main().catch((error) => {
|
|
143
|
+
console.error(error.message)
|
|
144
|
+
process.exit(1)
|
|
145
|
+
})
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const AUTO_HEADER = '<!-- AUTO-GENERATED. Do not edit. Regenerate from source files. -->'
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const args = { routeDir: null, service: null, out: null, check: false }
|
|
9
|
+
for (const arg of argv.slice(2)) {
|
|
10
|
+
if (arg.startsWith('--route-dir=')) args.routeDir = arg.slice('--route-dir='.length)
|
|
11
|
+
else if (arg.startsWith('--service=')) args.service = arg.slice('--service='.length)
|
|
12
|
+
else if (arg.startsWith('--out=')) args.out = arg.slice('--out='.length)
|
|
13
|
+
else if (arg === '--check') args.check = true
|
|
14
|
+
}
|
|
15
|
+
return args
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function collectFiles(dir) {
|
|
19
|
+
if (!fs.existsSync(dir)) return []
|
|
20
|
+
const out = []
|
|
21
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
22
|
+
const full = path.join(dir, entry.name)
|
|
23
|
+
if (entry.isDirectory()) out.push(...collectFiles(full))
|
|
24
|
+
else out.push(full)
|
|
25
|
+
}
|
|
26
|
+
return out.sort()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function read(file) {
|
|
30
|
+
return fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function rel(file) {
|
|
34
|
+
return file ? path.relative(process.cwd(), file) : ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function extractComponentProps(file) {
|
|
38
|
+
const source = read(file)
|
|
39
|
+
const out = []
|
|
40
|
+
const interfaceRe = /export\s+interface\s+([A-Z][A-Za-z0-9]*Props)\s*{([\s\S]*?)\n}/g
|
|
41
|
+
let m
|
|
42
|
+
while ((m = interfaceRe.exec(source)) !== null) {
|
|
43
|
+
out.push(`${m[1]} { ${m[2].replace(/\s+/g, ' ').trim()} }`)
|
|
44
|
+
}
|
|
45
|
+
const typeRe = /export\s+type\s+([A-Z][A-Za-z0-9]*Props)\s*=\s*([^;\n]+)/g
|
|
46
|
+
while ((m = typeRe.exec(source)) !== null) out.push(`${m[1]} = ${m[2].trim()}`)
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractExports(source, keyword) {
|
|
51
|
+
const re = new RegExp(`export\\s+${keyword}\\s+([A-Za-z0-9_]+)`, 'g')
|
|
52
|
+
const out = []
|
|
53
|
+
let m
|
|
54
|
+
while ((m = re.exec(source)) !== null) out.push(m[1])
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractServiceFunctions(file) {
|
|
59
|
+
const source = read(file)
|
|
60
|
+
const out = []
|
|
61
|
+
const re = /export\s+(?:async\s+)?function\s+([A-Za-z0-9_]+)\s*\(([^)]*)\)/g
|
|
62
|
+
let m
|
|
63
|
+
while ((m = re.exec(source)) !== null) out.push(`${m[1]}(${m[2].replace(/\s+/g, ' ').trim()})`)
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderList(items) {
|
|
68
|
+
return items.length > 0 ? items.map((item) => `- ${item}`).join('\n') : '- 未发现'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderTable(rows, empty) {
|
|
72
|
+
return rows.length > 0 ? rows.join('\n') : empty
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildSummary({ routeDir, service }) {
|
|
76
|
+
const files = collectFiles(routeDir)
|
|
77
|
+
const routeFile = path.join(routeDir, 'route.tsx')
|
|
78
|
+
const componentFiles = files.filter(
|
|
79
|
+
(file) =>
|
|
80
|
+
file.endsWith('.tsx') &&
|
|
81
|
+
path.basename(file) !== 'route.tsx' &&
|
|
82
|
+
path.basename(file) !== 'index.tsx'
|
|
83
|
+
)
|
|
84
|
+
const typesFile = path.join(routeDir, 'types.ts')
|
|
85
|
+
const constantsFile = path.join(routeDir, 'constants.ts')
|
|
86
|
+
const serviceFile = service && fs.existsSync(service) ? service : null
|
|
87
|
+
|
|
88
|
+
const componentRows = componentFiles.map((file) => {
|
|
89
|
+
const props = extractComponentProps(file)
|
|
90
|
+
return `| ${path.basename(file, '.tsx')} | ${rel(file)} | ${props.length > 0 ? props.join('<br/>') : '-'} | ${props.length > 0 ? 'OK' : 'NO_PROPS_EXPORT'} |`
|
|
91
|
+
})
|
|
92
|
+
const serviceRows = serviceFile
|
|
93
|
+
? extractServiceFunctions(serviceFile).map(
|
|
94
|
+
(signature) => `| ${signature.split('(')[0]} | ${rel(serviceFile)} | \`${signature}\` |`
|
|
95
|
+
)
|
|
96
|
+
: []
|
|
97
|
+
const typeRows = fs.existsSync(typesFile)
|
|
98
|
+
? [
|
|
99
|
+
...extractExports(read(typesFile), 'interface'),
|
|
100
|
+
...extractExports(read(typesFile), 'type')
|
|
101
|
+
].map((name) => `| ${name} | ${rel(typesFile)} |`)
|
|
102
|
+
: []
|
|
103
|
+
const constRows = fs.existsSync(constantsFile)
|
|
104
|
+
? extractExports(read(constantsFile), 'const').map(
|
|
105
|
+
(name) => `| ${name} | ${rel(constantsFile)} |`
|
|
106
|
+
)
|
|
107
|
+
: []
|
|
108
|
+
|
|
109
|
+
const pending = []
|
|
110
|
+
if (!fs.existsSync(routeFile)) pending.push('route.tsx 未发现')
|
|
111
|
+
if (!fs.existsSync(typesFile)) pending.push('types.ts 未发现')
|
|
112
|
+
if (!fs.existsSync(constantsFile)) pending.push('constants.ts 未发现')
|
|
113
|
+
if (!serviceFile) pending.push('service 文件未指定或未发现')
|
|
114
|
+
|
|
115
|
+
return `${AUTO_HEADER}
|
|
116
|
+
|
|
117
|
+
# foundation-summary:${path.basename(routeDir)}
|
|
118
|
+
|
|
119
|
+
## 1. 生成信息
|
|
120
|
+
|
|
121
|
+
- routeDir:${rel(routeDir)}
|
|
122
|
+
- generatedAt:${new Date().toISOString().replace(/\.\d{3}Z$/, 'Z')}
|
|
123
|
+
- generator:.agent/skills/devFlow/domains/frontend/scripts/generate_foundation_summary.mjs
|
|
124
|
+
|
|
125
|
+
## 2. 文件事实
|
|
126
|
+
|
|
127
|
+
### route
|
|
128
|
+
|
|
129
|
+
${fs.existsSync(routeFile) ? `- ${rel(routeFile)}` : '- 未发现'}
|
|
130
|
+
|
|
131
|
+
### components
|
|
132
|
+
|
|
133
|
+
${renderList(componentFiles.map(rel))}
|
|
134
|
+
|
|
135
|
+
### service
|
|
136
|
+
|
|
137
|
+
${serviceFile ? `- ${rel(serviceFile)}` : '- 未发现'}
|
|
138
|
+
|
|
139
|
+
### types
|
|
140
|
+
|
|
141
|
+
${fs.existsSync(typesFile) ? `- ${rel(typesFile)}` : '- 未发现'}
|
|
142
|
+
|
|
143
|
+
### constants
|
|
144
|
+
|
|
145
|
+
${fs.existsSync(constantsFile) ? `- ${rel(constantsFile)}` : '- 未发现'}
|
|
146
|
+
|
|
147
|
+
## 3. 组件 props
|
|
148
|
+
|
|
149
|
+
| 组件 | 文件 | Props | 解析状态 |
|
|
150
|
+
|---|---|---|---|
|
|
151
|
+
${renderTable(componentRows, '| - | - | - | 未发现组件 |')}
|
|
152
|
+
|
|
153
|
+
## 4. service 函数签名
|
|
154
|
+
|
|
155
|
+
| 函数 | 文件 | 签名 |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
${renderTable(serviceRows, '| - | - | 未发现 service 函数 |')}
|
|
158
|
+
|
|
159
|
+
## 5. types 导出
|
|
160
|
+
|
|
161
|
+
| 类型 | 文件 |
|
|
162
|
+
|---|---|
|
|
163
|
+
${renderTable(typeRows, '| - | 未发现类型导出 |')}
|
|
164
|
+
|
|
165
|
+
## 6. constants 导出
|
|
166
|
+
|
|
167
|
+
| 常量 | 文件 |
|
|
168
|
+
|---|---|
|
|
169
|
+
${renderTable(constRows, '| - | 未发现常量导出 |')}
|
|
170
|
+
|
|
171
|
+
## 7. figmaSync 允许修改范围
|
|
172
|
+
|
|
173
|
+
- 布局。
|
|
174
|
+
- CSS。
|
|
175
|
+
- Apex UI 组件选型和 props。
|
|
176
|
+
- 局部展示结构。
|
|
177
|
+
- PLAN.md 和 figma-plan.css。
|
|
178
|
+
|
|
179
|
+
## 8. figmaSync 禁止修改范围
|
|
180
|
+
|
|
181
|
+
- 路由路径。
|
|
182
|
+
- service 契约。
|
|
183
|
+
- API 请求基础封装。
|
|
184
|
+
- 类型语义。
|
|
185
|
+
- 页面核心状态模型。
|
|
186
|
+
- 业务组件边界。
|
|
187
|
+
- 权限逻辑。
|
|
188
|
+
- Wujie bridge。
|
|
189
|
+
- src/routeTree.gen.ts。
|
|
190
|
+
|
|
191
|
+
## 9. 待确认项
|
|
192
|
+
|
|
193
|
+
${renderList(pending)}
|
|
194
|
+
`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function main() {
|
|
198
|
+
const args = parseArgs(process.argv)
|
|
199
|
+
if (!args.routeDir || !fs.existsSync(args.routeDir)) {
|
|
200
|
+
console.error('[foundation-summary] missing or invalid --route-dir')
|
|
201
|
+
process.exit(1)
|
|
202
|
+
}
|
|
203
|
+
const out = args.out ?? path.join(args.routeDir, 'foundation-summary.md')
|
|
204
|
+
const next = buildSummary({ routeDir: args.routeDir, service: args.service })
|
|
205
|
+
const prev = fs.existsSync(out) ? fs.readFileSync(out, 'utf-8') : null
|
|
206
|
+
if (args.check) {
|
|
207
|
+
if (normalizeForCompare(prev) !== normalizeForCompare(next)) {
|
|
208
|
+
console.error(`[foundation-summary] out of date: ${out}`)
|
|
209
|
+
process.exit(1)
|
|
210
|
+
}
|
|
211
|
+
console.log(`[foundation-summary] up to date: ${out}`)
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
fs.writeFileSync(out, next)
|
|
215
|
+
console.log(`[foundation-summary] wrote ${out}`)
|
|
216
|
+
if (prev && prev !== next)
|
|
217
|
+
console.log('[foundation-summary] changed from previous snapshot; ask Human to confirm impact.')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
main()
|
|
221
|
+
|
|
222
|
+
function normalizeForCompare(text) {
|
|
223
|
+
return String(text ?? '').replace(/generatedAt:.+/g, 'generatedAt:<ignored>')
|
|
224
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# contract-report:{页面名称}
|
|
2
|
+
|
|
3
|
+
## 1. 总结论
|
|
4
|
+
|
|
5
|
+
- 结论:通过 / 不通过 / 阻塞
|
|
6
|
+
- 是否允许进入 page-build:是 / 否
|
|
7
|
+
- 结论说明:
|
|
8
|
+
|
|
9
|
+
## 2. 输入材料
|
|
10
|
+
|
|
11
|
+
| 材料 | 来源 | 是否已读取 | 关键证据 |
|
|
12
|
+
| --------------------- | ---- | ---------- | -------- |
|
|
13
|
+
| 页面方案 | | | |
|
|
14
|
+
| API 文档 / Apifox MCP | | | |
|
|
15
|
+
| 当前项目结构 | | | |
|
|
16
|
+
| 相似页面 | | | |
|
|
17
|
+
|
|
18
|
+
## 3. 检查范围
|
|
19
|
+
|
|
20
|
+
- 目标 route:
|
|
21
|
+
- 目标页面目录:
|
|
22
|
+
- 目标 service:
|
|
23
|
+
- 目标组件:
|
|
24
|
+
- 不检查范围:
|
|
25
|
+
|
|
26
|
+
## 4. Checklist
|
|
27
|
+
|
|
28
|
+
| 检查项 | 结论 | 证据 | 问题 | 是否阻塞 |
|
|
29
|
+
| -------------- | ---- | ---- | ---- | -------- |
|
|
30
|
+
| 路由路径 | | | | |
|
|
31
|
+
| 目录和文件命名 | | | | |
|
|
32
|
+
| API 契约 | | | | |
|
|
33
|
+
| 页面状态 | | | | |
|
|
34
|
+
| 组件映射 | | | | |
|
|
35
|
+
| 待确认项 | | | | |
|
|
36
|
+
|
|
37
|
+
## 5. API 契约明细
|
|
38
|
+
|
|
39
|
+
| 接口 | 请求路径 | Method | 入参 | 响应 | 调用组件 | 触发交互 | 刷新范围 | 异常反馈 | 证据 | 结论 |
|
|
40
|
+
| ---- | -------- | ------ | ---- | ---- | -------- | -------- | -------- | -------- | ---- | ---- |
|
|
41
|
+
|
|
42
|
+
## 6. 页面状态明细
|
|
43
|
+
|
|
44
|
+
| 状态 | 来源 | 变化条件 | 消费组件 | 证据 | 结论 |
|
|
45
|
+
| ---- | ---- | -------- | -------- | ---- | ---- |
|
|
46
|
+
|
|
47
|
+
## 7. 组件映射明细
|
|
48
|
+
|
|
49
|
+
| 组件 | 页面功能 | 输入 | 输出 / 事件 | 证据 | 结论 |
|
|
50
|
+
| ---- | -------- | ---- | ----------- | ---- | ---- |
|
|
51
|
+
|
|
52
|
+
## 8. 阻塞项
|
|
53
|
+
|
|
54
|
+
| 阻塞项 | 影响 | 需要补充的信息 | 建议回到章节 |
|
|
55
|
+
| ------ | ---- | -------------- | ------------ |
|
|
56
|
+
|
|
57
|
+
## 9. 非阻塞待确认项
|
|
58
|
+
|
|
59
|
+
| 待确认项 | 影响 | 后续处理 |
|
|
60
|
+
| -------- | ---- | -------- |
|
|
61
|
+
|
|
62
|
+
## 10. page-build 准入结论
|
|
63
|
+
|
|
64
|
+
- 准入结论:
|
|
65
|
+
- 可进入 page-build 的文件范围:
|
|
66
|
+
- 不允许 page-build 处理的内容:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Route } from './route'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { {{pageComponentName}}Shell } from './components/{{pageComponentName}}Shell'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('{{routePath}}')({
|
|
6
|
+
component: {{pageComponentName}}
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
function {{pageComponentName}}() {
|
|
10
|
+
return <{{pageComponentName}}Shell />
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import request, { type RequestOptions } from '@/services/request'
|
|
2
|
+
|
|
3
|
+
export interface {{listParamsName}} {
|
|
4
|
+
// TODO(api): confirm request params.
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface {{listResultName}} {
|
|
8
|
+
// TODO(api): confirm response shape.
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const {{apiPathsConstName}} = {
|
|
12
|
+
list: 'TODO(api): confirm endpoint path'
|
|
13
|
+
} as const
|
|
14
|
+
|
|
15
|
+
export function {{listFunctionName}}(params: {{listParamsName}}, options?: RequestOptions) {
|
|
16
|
+
return request.post<{{listResultName}}>({{apiPathsConstName}}.list, params, options)
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { {{listParamsName}}, {{listResultName}} } from '{{serviceModulePath}}'
|
|
2
|
+
|
|
3
|
+
export interface {{pageStateName}} {
|
|
4
|
+
params: {{listParamsName}}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type {{pageListResultName}} = {{listResultName}}
|
|
8
|
+
|
|
9
|
+
export interface {{pageViewModelName}} {
|
|
10
|
+
// TODO(api): map confirmed response fields to page view model.
|
|
11
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# 页面开发技术方案:{页面名称}
|
|
2
|
+
|
|
3
|
+
## 1. 背景与目标
|
|
4
|
+
|
|
5
|
+
## 2. 页面范围
|
|
6
|
+
|
|
7
|
+
### 2.1 页面入口
|
|
8
|
+
|
|
9
|
+
### 2.2 页面结构
|
|
10
|
+
|
|
11
|
+
### 2.3 页面状态
|
|
12
|
+
|
|
13
|
+
### 2.4 非目标范围
|
|
14
|
+
|
|
15
|
+
## 3. 需求拆解
|
|
16
|
+
|
|
17
|
+
### 3.1 功能与接口映射
|
|
18
|
+
|
|
19
|
+
| 功能 | PRD 交互项 | 接口 | 入参 | 响应数据 | 页面展示 / 行为 | 状态变化 | 成功后刷新 | 异常反馈 |
|
|
20
|
+
| ---- | ---------- | ---- | ---- | -------- | --------------- | -------- | ---------- | -------- |
|
|
21
|
+
|
|
22
|
+
### 3.2 用户流程
|
|
23
|
+
|
|
24
|
+
### 3.3 关键交互
|
|
25
|
+
|
|
26
|
+
| PRD 交互项 | 触发条件 | 前端行为 | 接口或状态影响 | 用户反馈 | 待确认 |
|
|
27
|
+
| ---------- | -------- | -------- | -------------- | -------- | ------ |
|
|
28
|
+
|
|
29
|
+
## 4. 数据与接口
|
|
30
|
+
|
|
31
|
+
### 4.1 依赖数据
|
|
32
|
+
|
|
33
|
+
### 4.2 接口补充说明
|
|
34
|
+
|
|
35
|
+
### 4.3 请求参数
|
|
36
|
+
|
|
37
|
+
### 4.4 响应数据
|
|
38
|
+
|
|
39
|
+
### 4.5 异常处理
|
|
40
|
+
|
|
41
|
+
## 5. 前端实现方案
|
|
42
|
+
|
|
43
|
+
### 5.1 目录结构与产物路径
|
|
44
|
+
|
|
45
|
+
### 5.2 可复用资源清单
|
|
46
|
+
|
|
47
|
+
### 5.3 UI 库组件清单
|
|
48
|
+
|
|
49
|
+
### 5.4 组件拆分
|
|
50
|
+
|
|
51
|
+
### 5.5 状态设计
|
|
52
|
+
|
|
53
|
+
### 5.6 数据流
|
|
54
|
+
|
|
55
|
+
```mermaid
|
|
56
|
+
flowchart LR
|
|
57
|
+
A["用户操作"] --> B["页面状态"]
|
|
58
|
+
B --> C["接口请求"]
|
|
59
|
+
C --> D["页面展示"]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 5.7 提交表单与校验(如涉及)
|
|
63
|
+
|
|
64
|
+
## 6. UI 与体验约束
|
|
65
|
+
|
|
66
|
+
### 6.1 设计稿信息
|
|
67
|
+
|
|
68
|
+
### 6.2 响应式要求
|
|
69
|
+
|
|
70
|
+
### 6.3 Loading 状态
|
|
71
|
+
|
|
72
|
+
### 6.4 空状态
|
|
73
|
+
|
|
74
|
+
### 6.5 错误反馈
|
|
75
|
+
|
|
76
|
+
## 7. 非功能性设计
|
|
77
|
+
|
|
78
|
+
### 7.1 性能设计
|
|
79
|
+
|
|
80
|
+
### 7.2 权限与安全
|
|
81
|
+
|
|
82
|
+
### 7.3 可访问性
|
|
83
|
+
|
|
84
|
+
### 7.4 埋点与监控
|
|
85
|
+
|
|
86
|
+
### 7.5 可维护性
|
|
87
|
+
|
|
88
|
+
### 7.6 可测试性
|
|
89
|
+
|
|
90
|
+
## 8. 边界场景
|
|
91
|
+
|
|
92
|
+
## 9. 完成标准
|
|
93
|
+
|
|
94
|
+
## 10. 风险与待确认项
|
|
95
|
+
|
|
96
|
+
### 10.1 风险项
|
|
97
|
+
|
|
98
|
+
#### PRD / 接口 / 仓库 / 规范冲突项
|
|
99
|
+
|
|
100
|
+
| 冲突项 | PRD 描述 | 接口或仓库现状 | 影响 | 建议确认对象 |
|
|
101
|
+
| ------ | -------- | -------------- | ---- | ------------ |
|
|
102
|
+
|
|
103
|
+
### 10.2 待确认问题
|