@brms/ai-skills 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/README.md +256 -0
- package/bin/brms-skills.mjs +411 -0
- package/package.json +30 -0
- package/skills/brms-prototype-generator/SKILL.md +129 -0
- package/skills/brms-prototype-generator/agents/openai.yaml +7 -0
- package/skills/brms-prototype-generator/examples/few-shot-examples.md +577 -0
- package/skills/brms-prototype-generator/references/01-list-query.md +444 -0
- package/skills/brms-prototype-generator/references/02-form-entry.md +129 -0
- package/skills/brms-prototype-generator/references/03-detail-display.md +125 -0
- package/skills/brms-prototype-generator/references/04-composite-page-package.md +339 -0
- package/skills/brms-prototype-generator/references/05-dialog-patterns.md +113 -0
- package/skills/brms-prototype-generator/references/06-backend-request-patterns.md +118 -0
- package/skills/brms-prototype-generator/references/resource-index.md +46 -0
- package/skills/brms-prototype-generator/references/system-prompt.md +242 -0
- package/skills/brms-prototype-generator/scripts/analyze-doc.mjs +554 -0
- package/skills/brms-prototype-generator/scripts/check-project.mjs +228 -0
- package/skills/brms-prototype-generator/scripts/discover-targets.mjs +158 -0
- package/skills/brms-prototype-generator/scripts/install-codex.mjs +74 -0
- package/skills/brms-prototype-generator/scripts/plan-pages.mjs +390 -0
- package/skills/brms-prototype-generator/scripts/validate-generated.mjs +838 -0
- package/skills/brms-prototype-generator/templates/user-input-template.md +182 -0
- package/skills/brms-vxe-plus-developer/SKILL.md +105 -0
- package/skills/brms-vxe-plus-developer/agents/openai.yaml +7 -0
- package/skills/brms-vxe-plus-developer/references/prototype-to-real.md +54 -0
- package/skills/brms-vxe-plus-developer/references/real-base-development.md +110 -0
- package/skills/brms-vxe-plus-developer/references/resource-index.md +43 -0
- package/skills/brms-vxe-plus-developer/references/review-checklist.md +49 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/01-mental-model.md +150 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/02-vxe-plus-form.md +302 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/03-vxe-plus-table.md +253 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/04-example-map.md +488 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/05-request-and-eiinfo.md +170 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/90-anti-patterns.md +137 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/README.md +43 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/README.md +21 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P0/A1P01601.vue +483 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/A1/P1/A1P11011.vue +444 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AB/BP/ABBP0201.vue +1648 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Bidding/formConfig.ts +228 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/AM/AF/component/AMAF0601/Record/columns.ts +110 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/BMBR01.vue +130 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/columns.ts +94 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/BM/BR/component/BMBR01/formConfig.ts +108 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/formConfig.ts +123 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Change/index.vue +103 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/columns.ts +48 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Clause/index.vue +202 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/formConfig.ts +117 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Correcte/index.vue +103 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/Payment/formConfig.ts +90 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/Payment/index.vue +42 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/columns.ts +376 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Pay/index.vue +619 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Domestic/formConfig.ts +73 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Domestic/index.vue +47 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Foreign/formConfig.ts +141 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/Foreign/index.vue +42 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/columns.ts +123 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/Settle/index.vue +593 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Explain/index.vue +68 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/columns.ts +150 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Fee/index.vue +235 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/columns.ts +63 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Files/index.vue +117 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/columns.ts +327 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Goods/index.vue +790 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve/formConfig.ts +341 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve/index.vue +63 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve2/formConfig.ts +232 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Approve2/index.vue +27 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Diff/columns.ts +46 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/Diff/index.vue +92 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/formConfig.ts +979 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Base/index.vue +62 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Other/formConfig.ts +179 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Other/index.vue +140 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Sign/formConfig.ts +118 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/Sign/index.vue +44 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Main/index.vue +168 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/Major/formConfig.ts +257 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/Major/index.vue +47 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/columns.ts +256 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Party/index.vue +738 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/formConfig.ts +174 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Price/index.vue +51 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/PM/PC/component/PMPC0101/Top/index.vue +924 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/SM/SW/SMSW0101.vue +567 -0
- package/skills/brms-vxe-plus-developer/references/vxe-plus-knowledge/sources/project/base/src/views/demo/index.vue +448 -0
- package/skills/brms-vxe-plus-developer/scripts/check-project.mjs +259 -0
- package/skills/brms-vxe-plus-developer/scripts/check-vxe-plus-page.mjs +137 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const PAGE_HEADING_RE = /^##\s+(?:(\d+)[.、]\s*)?(.+?)\s*$/
|
|
6
|
+
const SUB_HEADING_RE = /^###\s+(.+?)\s*$/
|
|
7
|
+
const LINK_RE = /\[[^\]]*]\(([^)]+\.md(?:#[^)]+)?)\)/gi
|
|
8
|
+
const BARE_MD_RE = /(?:^|[\s>::((])([A-Za-z0-9_.\-\/\\]+\.md)(?=$|[\s))。,,.;;])/gi
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = {
|
|
12
|
+
entry: '',
|
|
13
|
+
repoRoot: process.cwd(),
|
|
14
|
+
format: 'markdown',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
18
|
+
const arg = argv[i]
|
|
19
|
+
if (arg === '--repo-root') {
|
|
20
|
+
args.repoRoot = argv[i + 1] || ''
|
|
21
|
+
i += 1
|
|
22
|
+
}
|
|
23
|
+
else if (arg === '--format') {
|
|
24
|
+
args.format = argv[i + 1] || ''
|
|
25
|
+
i += 1
|
|
26
|
+
}
|
|
27
|
+
else if (arg === '--help' || arg === '-h') {
|
|
28
|
+
args.help = true
|
|
29
|
+
}
|
|
30
|
+
else if (!args.entry) {
|
|
31
|
+
args.entry = arg
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!['json', 'markdown', 'both'].includes(args.format))
|
|
39
|
+
throw new Error('--format must be one of: json, markdown, both')
|
|
40
|
+
|
|
41
|
+
return args
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizePath(filePath) {
|
|
45
|
+
return filePath.replaceAll('\\', '/')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toRepoRelative(filePath, repoRoot) {
|
|
49
|
+
return normalizePath(path.relative(repoRoot, filePath))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function stripAnchor(link) {
|
|
53
|
+
return link.replace(/#.*$/, '')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveMdReference(reference, fromFile, repoRoot) {
|
|
57
|
+
const cleanReference = decodeURI(stripAnchor(reference.trim())).replace(/^file:\/\//, '')
|
|
58
|
+
if (/^[a-z]+:/i.test(cleanReference))
|
|
59
|
+
return null
|
|
60
|
+
|
|
61
|
+
const candidate = path.isAbsolute(cleanReference)
|
|
62
|
+
? cleanReference
|
|
63
|
+
: path.resolve(path.dirname(fromFile), cleanReference)
|
|
64
|
+
|
|
65
|
+
if (!candidate.startsWith(repoRoot))
|
|
66
|
+
return null
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile())
|
|
69
|
+
return candidate
|
|
70
|
+
|
|
71
|
+
const repoCandidate = path.resolve(repoRoot, cleanReference)
|
|
72
|
+
if (repoCandidate.startsWith(repoRoot) && fs.existsSync(repoCandidate) && fs.statSync(repoCandidate).isFile())
|
|
73
|
+
return repoCandidate
|
|
74
|
+
|
|
75
|
+
return candidate
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function findMarkdownReferences(text) {
|
|
79
|
+
const references = new Set()
|
|
80
|
+
let match
|
|
81
|
+
|
|
82
|
+
while ((match = LINK_RE.exec(text)))
|
|
83
|
+
references.add(match[1])
|
|
84
|
+
|
|
85
|
+
while ((match = BARE_MD_RE.exec(text)))
|
|
86
|
+
references.add(match[1])
|
|
87
|
+
|
|
88
|
+
return [...references]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function collectDocuments(entryFile, repoRoot) {
|
|
92
|
+
const queue = [entryFile]
|
|
93
|
+
const seen = new Set()
|
|
94
|
+
const documents = []
|
|
95
|
+
const missingReferences = []
|
|
96
|
+
|
|
97
|
+
while (queue.length) {
|
|
98
|
+
const file = path.resolve(queue.shift())
|
|
99
|
+
if (seen.has(file))
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
seen.add(file)
|
|
103
|
+
if (!fs.existsSync(file)) {
|
|
104
|
+
missingReferences.push(toRepoRelative(file, repoRoot))
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const text = fs.readFileSync(file, 'utf8')
|
|
109
|
+
documents.push({ file, text })
|
|
110
|
+
|
|
111
|
+
for (const ref of findMarkdownReferences(text)) {
|
|
112
|
+
const resolved = resolveMdReference(ref, file, repoRoot)
|
|
113
|
+
if (!resolved)
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
if (!seen.has(resolved))
|
|
117
|
+
queue.push(resolved)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { documents, missingReferences: [...new Set(missingReferences)] }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseMarkdownTable(lines, startIndex) {
|
|
125
|
+
if (!lines[startIndex]?.trim().startsWith('|'))
|
|
126
|
+
return null
|
|
127
|
+
|
|
128
|
+
const headerLine = lines[startIndex]
|
|
129
|
+
const separatorLine = lines[startIndex + 1] || ''
|
|
130
|
+
if (!separatorLine.trim().startsWith('|') || !separatorLine.includes('-'))
|
|
131
|
+
return null
|
|
132
|
+
|
|
133
|
+
const headers = splitTableRow(headerLine)
|
|
134
|
+
const rows = []
|
|
135
|
+
let endIndex = startIndex + 2
|
|
136
|
+
|
|
137
|
+
while (endIndex < lines.length && lines[endIndex].trim().startsWith('|')) {
|
|
138
|
+
const values = splitTableRow(lines[endIndex])
|
|
139
|
+
const row = {}
|
|
140
|
+
headers.forEach((header, index) => {
|
|
141
|
+
row[header] = values[index] || ''
|
|
142
|
+
})
|
|
143
|
+
rows.push(row)
|
|
144
|
+
endIndex += 1
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
startLine: startIndex + 1,
|
|
149
|
+
endLine: endIndex,
|
|
150
|
+
headers,
|
|
151
|
+
rows,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function splitTableRow(line) {
|
|
156
|
+
return line
|
|
157
|
+
.trim()
|
|
158
|
+
.replace(/^\|/, '')
|
|
159
|
+
.replace(/\|$/, '')
|
|
160
|
+
.split('|')
|
|
161
|
+
.map(cell => cell.trim())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isFieldTable(table, activeSubHeading) {
|
|
165
|
+
const headers = table.headers.join('|').toLowerCase()
|
|
166
|
+
return /字段|field|类型|type/.test(headers) && (/字段|field/.test(headers) || /字段清单|表结构|table/i.test(activeSubHeading))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isFunctionTable(table, activeSubHeading) {
|
|
170
|
+
const headers = table.headers.join('|').toLowerCase()
|
|
171
|
+
return /功能|operation|button|action|处理逻辑/.test(headers) || /功能清单|按钮|操作/.test(activeSubHeading)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function hasFieldKey(headers) {
|
|
175
|
+
const normalized = headers.map(header => header.trim().toLowerCase())
|
|
176
|
+
return normalized.some(header =>
|
|
177
|
+
['fieldname', 'field_name', 'field', '字段名', '字段编码', '字段key', '字段 key', '字段代码'].includes(header),
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasDisplayName(headers) {
|
|
182
|
+
const normalized = headers.map(header => header.trim().toLowerCase())
|
|
183
|
+
return normalized.some(header =>
|
|
184
|
+
['displayname', 'display_name', 'label', 'title', '显示名', '展示名', '字段', '字段名称', '名称'].includes(header),
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function hasType(headers) {
|
|
189
|
+
const normalized = headers.map(header => header.trim().toLowerCase())
|
|
190
|
+
return normalized.some(header =>
|
|
191
|
+
['type', 'fieldtype', 'field_type', '类型', '字段类型'].includes(header),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function summarizeFieldTable(table, lineOffset = 0) {
|
|
196
|
+
return {
|
|
197
|
+
startLine: table.startLine + lineOffset,
|
|
198
|
+
rows: table.rows.length,
|
|
199
|
+
headers: table.headers,
|
|
200
|
+
hasFieldKey: hasFieldKey(table.headers),
|
|
201
|
+
hasDisplayName: hasDisplayName(table.headers),
|
|
202
|
+
hasType: hasType(table.headers),
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function extractPurpose(lines) {
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
const trimmed = line.trim()
|
|
209
|
+
if (!trimmed || trimmed.startsWith('|') || trimmed.startsWith('#') || trimmed === '---')
|
|
210
|
+
continue
|
|
211
|
+
return trimmed.replace(/^>\s*/, '')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return ''
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function analyzePage(section, repoRoot) {
|
|
218
|
+
const lines = section.content
|
|
219
|
+
const fieldTables = []
|
|
220
|
+
const functionTables = []
|
|
221
|
+
let activeSubHeading = ''
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
224
|
+
const subHeading = lines[i].match(SUB_HEADING_RE)
|
|
225
|
+
if (subHeading)
|
|
226
|
+
activeSubHeading = subHeading[1]
|
|
227
|
+
|
|
228
|
+
const table = parseMarkdownTable(lines, i)
|
|
229
|
+
if (!table)
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
if (isFieldTable(table, activeSubHeading))
|
|
233
|
+
fieldTables.push(summarizeFieldTable(table, section.line))
|
|
234
|
+
if (isFunctionTable(table, activeSubHeading))
|
|
235
|
+
functionTables.push({ startLine: table.startLine + section.line, rows: table.rows.length, headers: table.headers })
|
|
236
|
+
|
|
237
|
+
i = table.endLine - 1
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const purpose = extractPurpose(lines)
|
|
241
|
+
const hasRenderableFields = fieldTables.some(table => table.rows > 0 && table.hasDisplayName && table.hasType)
|
|
242
|
+
const hasCompleteFieldDesign = fieldTables.some(table => table.rows > 0 && table.hasFieldKey && table.hasDisplayName && table.hasType)
|
|
243
|
+
const hasFunctions = functionTables.some(table => table.rows > 0)
|
|
244
|
+
const complete = Boolean(section.title && (purpose || hasFunctions) && hasRenderableFields)
|
|
245
|
+
const issues = []
|
|
246
|
+
|
|
247
|
+
if (!purpose && !hasFunctions)
|
|
248
|
+
issues.push('missing purpose or function list')
|
|
249
|
+
if (!fieldTables.length)
|
|
250
|
+
issues.push('missing field table')
|
|
251
|
+
if (fieldTables.length && !hasCompleteFieldDesign)
|
|
252
|
+
issues.push('field design missing field keys')
|
|
253
|
+
if (!complete)
|
|
254
|
+
issues.push('page incomplete')
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
source: toRepoRelative(section.file, repoRoot),
|
|
258
|
+
line: section.line,
|
|
259
|
+
index: section.index,
|
|
260
|
+
title: section.title,
|
|
261
|
+
purpose,
|
|
262
|
+
fieldTables,
|
|
263
|
+
functionTables,
|
|
264
|
+
complete,
|
|
265
|
+
hasCompleteFieldDesign,
|
|
266
|
+
issues,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function extractSections(document) {
|
|
271
|
+
const lines = document.text.split(/\r?\n/)
|
|
272
|
+
const sections = []
|
|
273
|
+
|
|
274
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
275
|
+
const match = lines[i].match(PAGE_HEADING_RE)
|
|
276
|
+
if (!match)
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
const title = match[2].trim()
|
|
280
|
+
if (/附录|页面间导航关系|导航关系/.test(title))
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
let end = i + 1
|
|
284
|
+
while (end < lines.length && !PAGE_HEADING_RE.test(lines[end]))
|
|
285
|
+
end += 1
|
|
286
|
+
|
|
287
|
+
sections.push({
|
|
288
|
+
file: document.file,
|
|
289
|
+
line: i + 1,
|
|
290
|
+
index: match[1] ? Number(match[1]) : null,
|
|
291
|
+
title,
|
|
292
|
+
content: lines.slice(i + 1, end),
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return sections
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function extractNavigationBlocks(document, repoRoot) {
|
|
300
|
+
const lines = document.text.split(/\r?\n/)
|
|
301
|
+
const blocks = []
|
|
302
|
+
let inNavigation = false
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
305
|
+
if (/^##\s+.*(?:页面间导航关系|导航关系)/.test(lines[i])) {
|
|
306
|
+
inNavigation = true
|
|
307
|
+
continue
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (inNavigation && /^##\s+/.test(lines[i]))
|
|
311
|
+
inNavigation = false
|
|
312
|
+
|
|
313
|
+
if (!inNavigation)
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
if (lines[i].trim().startsWith('```')) {
|
|
317
|
+
const start = i + 1
|
|
318
|
+
let end = start
|
|
319
|
+
while (end < lines.length && !lines[end].trim().startsWith('```'))
|
|
320
|
+
end += 1
|
|
321
|
+
|
|
322
|
+
blocks.push({
|
|
323
|
+
source: toRepoRelative(document.file, repoRoot),
|
|
324
|
+
line: start + 1,
|
|
325
|
+
text: lines.slice(start, end).join('\n').trim(),
|
|
326
|
+
tree: parseNavigationTree(lines.slice(start, end)),
|
|
327
|
+
})
|
|
328
|
+
i = end
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return blocks
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parseNavigationTree(lines) {
|
|
336
|
+
const root = []
|
|
337
|
+
const stack = []
|
|
338
|
+
|
|
339
|
+
for (const rawLine of lines) {
|
|
340
|
+
const line = rawLine.replace(/\s+$/, '')
|
|
341
|
+
if (!line.trim())
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
const branchIndex = line.search(/[├└]/)
|
|
345
|
+
const pipeCount = branchIndex >= 0 ? (line.slice(0, branchIndex).match(/│/g) || []).length : 0
|
|
346
|
+
let level = 0
|
|
347
|
+
if (branchIndex >= 0)
|
|
348
|
+
level = Math.max(pipeCount + 1, Math.max(1, Math.floor(branchIndex / 6) + 1))
|
|
349
|
+
|
|
350
|
+
const label = line
|
|
351
|
+
.replace(/^[\s│├└─]+/, '')
|
|
352
|
+
.trim()
|
|
353
|
+
if (!label)
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
const noteMatch = label.match(/^(.*?)[((]([^()()]*)[))]$/)
|
|
357
|
+
const title = (noteMatch ? noteMatch[1] : label).trim()
|
|
358
|
+
const note = noteMatch ? noteMatch[2].trim() : ''
|
|
359
|
+
const node = {
|
|
360
|
+
title,
|
|
361
|
+
note,
|
|
362
|
+
level,
|
|
363
|
+
children: [],
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
while (stack.length && stack[stack.length - 1].level >= level)
|
|
367
|
+
stack.pop()
|
|
368
|
+
|
|
369
|
+
if (stack.length)
|
|
370
|
+
stack[stack.length - 1].node.children.push(node)
|
|
371
|
+
else
|
|
372
|
+
root.push(node)
|
|
373
|
+
|
|
374
|
+
stack.push({ level, node })
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return root
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function countNavigationNodes(nodes) {
|
|
381
|
+
return nodes.reduce((count, node) => count + 1 + countNavigationNodes(node.children || []), 0)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function formatNavigationTree(nodes, indent = 0) {
|
|
385
|
+
const lines = []
|
|
386
|
+
for (const node of nodes) {
|
|
387
|
+
const prefix = ' '.repeat(indent)
|
|
388
|
+
const note = node.note ? ` (${node.note})` : ''
|
|
389
|
+
lines.push(`${prefix}- ${node.title}${note}`)
|
|
390
|
+
lines.push(...formatNavigationTree(node.children || [], indent + 1))
|
|
391
|
+
}
|
|
392
|
+
return lines
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function analyze(entry, repoRoot) {
|
|
396
|
+
const entryFile = path.resolve(repoRoot, entry)
|
|
397
|
+
if (!fs.existsSync(entryFile))
|
|
398
|
+
throw new Error(`Entry Markdown not found: ${entry}`)
|
|
399
|
+
|
|
400
|
+
const { documents, missingReferences } = collectDocuments(entryFile, repoRoot)
|
|
401
|
+
const pages = documents.flatMap(document => extractSections(document).map(section => analyzePage(section, repoRoot)))
|
|
402
|
+
const navigationBlocks = documents.flatMap(document => extractNavigationBlocks(document, repoRoot))
|
|
403
|
+
const incompletePages = pages.filter(page => !page.complete)
|
|
404
|
+
const pagesMissingFieldKeys = pages.filter(page => page.fieldTables.length && !page.hasCompleteFieldDesign)
|
|
405
|
+
const completePages = pages.filter(page => page.complete).length
|
|
406
|
+
const generationModes = getGenerationModes(completePages)
|
|
407
|
+
const navigationTreeNodes = navigationBlocks.reduce((count, block) => count + countNavigationNodes(block.tree || []), 0)
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
entry: toRepoRelative(entryFile, repoRoot),
|
|
411
|
+
documents: documents.map(document => toRepoRelative(document.file, repoRoot)),
|
|
412
|
+
missingReferences,
|
|
413
|
+
summary: {
|
|
414
|
+
pageCount: pages.length,
|
|
415
|
+
completePages,
|
|
416
|
+
incompletePages: incompletePages.length,
|
|
417
|
+
pagesMissingFieldKeys: pagesMissingFieldKeys.length,
|
|
418
|
+
navigationBlocks: navigationBlocks.length,
|
|
419
|
+
navigationTreeNodes,
|
|
420
|
+
menuStructureDetected: navigationTreeNodes > 0,
|
|
421
|
+
shouldStop: completePages === 0,
|
|
422
|
+
shouldAskForTableDesign: pagesMissingFieldKeys.length > 0,
|
|
423
|
+
recommendedGenerationMode: generationModes.recommended,
|
|
424
|
+
},
|
|
425
|
+
generationModes: generationModes.modes,
|
|
426
|
+
pages,
|
|
427
|
+
navigationBlocks,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function getGenerationModes(completePages) {
|
|
432
|
+
const modes = [
|
|
433
|
+
{
|
|
434
|
+
id: 'single-agent-package',
|
|
435
|
+
name: '整包一次性生成',
|
|
436
|
+
recommendedWhen: '1-3 个完整页面,页面关系简单',
|
|
437
|
+
tradeoff: '最快,但页面多时更容易遗漏细节或规则漂移',
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
id: 'coordinator-sequential',
|
|
441
|
+
name: '总控逐页顺序生成',
|
|
442
|
+
recommendedWhen: '4 个以上页面,或产品希望每页更稳但不使用子智能体',
|
|
443
|
+
tradeoff: '最稳妥,速度较慢;总控每次只生成一个页面并做局部检查',
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: 'coordinator-subagents',
|
|
447
|
+
name: '总控 + 子智能体逐页生成',
|
|
448
|
+
recommendedWhen: '4 个以上页面,且当前环境支持子智能体/并行 agent',
|
|
449
|
+
tradeoff: '速度快、上下文更小;必须由总控统一共享数据、菜单和最终集成',
|
|
450
|
+
},
|
|
451
|
+
]
|
|
452
|
+
|
|
453
|
+
let recommended = 'single-agent-package'
|
|
454
|
+
if (completePages >= 4)
|
|
455
|
+
recommended = 'coordinator-subagents'
|
|
456
|
+
if (completePages === 0)
|
|
457
|
+
recommended = 'none'
|
|
458
|
+
|
|
459
|
+
return { recommended, modes }
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function toMarkdown(report) {
|
|
463
|
+
const lines = [
|
|
464
|
+
'# BRMS Prototype Document Analysis',
|
|
465
|
+
'',
|
|
466
|
+
`- Entry: \`${report.entry}\``,
|
|
467
|
+
`- Documents scanned: ${report.documents.length}`,
|
|
468
|
+
`- Pages detected: ${report.summary.pageCount}`,
|
|
469
|
+
`- Complete pages: ${report.summary.completePages}`,
|
|
470
|
+
`- Pages missing field keys: ${report.summary.pagesMissingFieldKeys}`,
|
|
471
|
+
`- Navigation blocks: ${report.summary.navigationBlocks}`,
|
|
472
|
+
`- Menu structure detected: ${report.summary.menuStructureDetected ? 'yes' : 'no'}`,
|
|
473
|
+
`- Navigation tree nodes: ${report.summary.navigationTreeNodes}`,
|
|
474
|
+
`- Recommended generation mode: ${report.summary.recommendedGenerationMode}`,
|
|
475
|
+
`- Stop required: ${report.summary.shouldStop ? 'yes' : 'no'}`,
|
|
476
|
+
'',
|
|
477
|
+
]
|
|
478
|
+
|
|
479
|
+
if (report.missingReferences.length) {
|
|
480
|
+
lines.push('## Missing References', '')
|
|
481
|
+
report.missingReferences.forEach(ref => lines.push(`- \`${ref}\``))
|
|
482
|
+
lines.push('')
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
lines.push('## Generation Modes', '')
|
|
486
|
+
report.generationModes.forEach((mode) => {
|
|
487
|
+
lines.push(`- **${mode.name}** (\`${mode.id}\`): ${mode.recommendedWhen};${mode.tradeoff}`)
|
|
488
|
+
})
|
|
489
|
+
lines.push('')
|
|
490
|
+
|
|
491
|
+
lines.push('## Pages', '')
|
|
492
|
+
report.pages.forEach((page) => {
|
|
493
|
+
lines.push(`### ${page.index ? `${page.index}. ` : ''}${page.title}`)
|
|
494
|
+
lines.push(`- Source: \`${page.source}:${page.line}\``)
|
|
495
|
+
lines.push(`- Complete: ${page.complete ? 'yes' : 'no'}`)
|
|
496
|
+
lines.push(`- Field tables: ${page.fieldTables.length}`)
|
|
497
|
+
lines.push(`- Function tables: ${page.functionTables.length}`)
|
|
498
|
+
lines.push(`- Field design complete: ${page.hasCompleteFieldDesign ? 'yes' : 'no'}`)
|
|
499
|
+
if (page.issues.length)
|
|
500
|
+
lines.push(`- Issues: ${page.issues.join('; ')}`)
|
|
501
|
+
lines.push('')
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
if (report.navigationBlocks.length) {
|
|
505
|
+
lines.push('## Navigation Blocks', '')
|
|
506
|
+
report.navigationBlocks.forEach((block, index) => {
|
|
507
|
+
lines.push(`### Block ${index + 1} (${block.source}:${block.line})`, '')
|
|
508
|
+
lines.push('```text')
|
|
509
|
+
lines.push(block.text)
|
|
510
|
+
lines.push('```', '')
|
|
511
|
+
if (block.tree?.length) {
|
|
512
|
+
lines.push('Parsed menu/navigation tree:', '')
|
|
513
|
+
lines.push(...formatNavigationTree(block.tree))
|
|
514
|
+
lines.push('')
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return lines.join('\n')
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function main() {
|
|
523
|
+
const args = parseArgs(process.argv.slice(2))
|
|
524
|
+
if (args.help || !args.entry) {
|
|
525
|
+
console.log('Usage: node scripts/analyze-doc.mjs <entry-md> --repo-root <repo> --format json|markdown|both')
|
|
526
|
+
process.exit(args.help ? 0 : 1)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const repoRoot = path.resolve(args.repoRoot || process.cwd())
|
|
530
|
+
const report = analyze(args.entry, repoRoot)
|
|
531
|
+
|
|
532
|
+
if (args.format === 'json') {
|
|
533
|
+
console.log(JSON.stringify(report, null, 2))
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (args.format === 'markdown') {
|
|
538
|
+
console.log(toMarkdown(report))
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
console.log('---MARKDOWN---')
|
|
543
|
+
console.log(toMarkdown(report))
|
|
544
|
+
console.log('---JSON---')
|
|
545
|
+
console.log(JSON.stringify(report, null, 2))
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
main()
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
553
|
+
process.exit(1)
|
|
554
|
+
}
|