@yorha2b-lab/autodev 7.7.8 → 7.8.1

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.
Files changed (35) hide show
  1. package/bin/bunker.js +3 -1
  2. package/config.js +2 -0
  3. package/package.json +1 -1
  4. package/src/constitution/inspector.js +21 -0
  5. package/src/constitution/system.js +2 -8
  6. package/src/dialogues/EN-US.js +8 -0
  7. package/src/dialogues/FR-FR.js +8 -0
  8. package/src/dialogues/JA-JP.js +8 -0
  9. package/src/dialogues/RU-RU.js +8 -0
  10. package/src/dialogues/ZH-CN.js +8 -0
  11. package/src/gateway/client.js +1 -1
  12. package/src/gateway/index.js +17 -7
  13. package/src/labs/index.js +5 -1
  14. package/src/labs/inspector.js +183 -0
  15. package/src/labs/scout.js +0 -2
  16. package/src/logistics/formatter.js +32 -0
  17. package/src/units/striker.js +1 -1
  18. package/src/workshop/react/blueprint/molds/MySearchForm.hbs +1 -1
  19. package/src/workshop/react/blueprint/molds/handleBlock.hbs +3 -4
  20. package/src/workshop/react/blueprint/molds/stateBlock.hbs +2 -0
  21. package/src/workshop/react/builder/index.js +1 -1
  22. package/src/workshop/react/extensions/useTableQuery.js +4 -65
  23. package/src/workshop/react/kit/utils.js +51 -102
  24. package/src/workshop/react/standardParts/AliyunOSSUpload.js +0 -45
  25. package/src/workshop/react/standardParts/DraggableContainer.js +6 -2
  26. package/src/workshop/react/standardParts/EditableCell.js +36 -74
  27. package/src/workshop/react/standardParts/MyBaseForm.js +4 -51
  28. package/src/workshop/react/standardParts/MyDescription.js +3 -3
  29. package/src/workshop/react/standardParts/MyForm.js +48 -18
  30. package/src/workshop/react/standardParts/MyModalForm.js +4 -85
  31. package/src/workshop/react/standardParts/MyModalTable.js +2 -55
  32. package/src/workshop/react/standardParts/MyPopoverTable.js +24 -0
  33. package/src/workshop/react/standardParts/MySearchForm.js +5 -63
  34. package/src/workshop/react/standardParts/MyTable.js +68 -148
  35. package/src/workshop/react/standardParts/index.js +67 -39
package/bin/bunker.js CHANGED
@@ -53,7 +53,7 @@ program
53
53
  ]
54
54
  }
55
55
  Object.entries(outpost).forEach(([type, items]) => items.forEach(quartermaster[type]))
56
- const bunkerCmd = chalk.yellow(`'bunker': 'bunker boot'`)
56
+ const bunkerCmd = chalk.yellow(`"bunker": "bunker boot"`)
57
57
  console.log(chalk.cyan(dialog.bunker.initComplete(bunkerCmd)))
58
58
  })
59
59
 
@@ -76,7 +76,9 @@ program
76
76
  currentBunker.yorha.commander.report(dialog.bunker.systemOffline, 'gray')
77
77
  terminalCollapse(500, dialog, currentBunker)
78
78
  const scout = await currentBunker.labs?.scout
79
+ const inspector = await currentBunker.labs?.inspector
79
80
  scout?.close()
81
+ inspector?.close()
80
82
  })
81
83
  })
82
84
 
package/config.js CHANGED
@@ -20,6 +20,8 @@ module.exports = {
20
20
  utilsDir: 'src/utils',
21
21
  componentsDir: 'src/components',
22
22
 
23
+ // 指向公司的 PRD 文档 地址
24
+ prdDoc: '',
23
25
  // 指向公司的 Swagger JSON 地址
24
26
  apiDoc: '',
25
27
  //老项目url
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "7.7.8",
3
+ "version": "7.8.1",
4
4
  "description": "基于 AI 视觉与语义协议的前端 (React+Antd) 代码构筑与旧系统重构引擎",
5
5
  "bin": {
6
6
  "bunker": "bin/bunker.js"
@@ -0,0 +1,21 @@
1
+ module.exports = ({ prdText, moduleName, moduleCodeView }) => `
2
+ # Task: Fast PRD vs Code Logic Gap Analysis
3
+
4
+ ## 1. 需求要点 (PRD 核心业务规则)
5
+ ${prdText}
6
+
7
+ ## 2. 代码逻辑骨架 (${moduleName} 脱水源码)
8
+ ${moduleCodeView}
9
+
10
+ ## 3. 极速审计准则
11
+ 1. 从需求中仅聚焦提取:【条件分支】、【联动】、【条件禁用/必填】、【二次确认/弹窗】、【特殊数值/边界限制】。
12
+ 2. ⚠️【禁止废话】:已实现的功能只用一行短语概括;未实现的问题只挑最致命的缺口
13
+ 3. 【输出】:仅输出未实现的功能,不输出已实现的功能
14
+
15
+ ## 4. 输出紧凑 JSON:
16
+ {
17
+ "unimplementedGaps": [
18
+ { "feature": "状态为草稿时禁止提交", "detail": "代码中未见提交按钮的 disabled 判定" }
19
+ ]
20
+ }
21
+ `.trim()
@@ -1,9 +1,3 @@
1
- const commonPrompt = '你必须严格按照用户的JSON结构输出,严禁输出任何思考过程(</think>内容),严禁解释、输出Markdown标签。必须输出合法的、带双引号的标准JSON。'
1
+ const FRONTEND_ENGINEER = '你是一个资深的前端工程师,有着数十年的前端软件开发经验;你必须严格按照用户的JSON结构输出,严禁输出任何思考过程(</think>内容),严禁解释、输出Markdown标签。必须输出合法的、带双引号的标准JSON。'
2
2
 
3
- const UI_DESIGNER = `你是一个专业的UI设计师。${commonPrompt}`
4
-
5
- const MOCK_DESIGNER = `你是一个专业的Mock数据生成器。${commonPrompt}`
6
-
7
- const API_DESIGNER = `你是一个资深前端与后端接口对接专家。${commonPrompt}`
8
-
9
- module.exports = { UI_DESIGNER, API_DESIGNER, MOCK_DESIGNER }
3
+ module.exports = { FRONTEND_ENGINEER }
@@ -29,13 +29,21 @@ module.exports = {
29
29
  assemblyComplete: (fileName, time) => `Protocol complete. Physical assembly of [${fileName}] achieved. Elapsed: ${time}s, Syncing Menu configuration to Bunker...`,
30
30
  },
31
31
  pod153: {
32
+ prdAuditFinish: 'PRD audit complete',
32
33
  signalLinkFault: virus => `Signal Link Fault: ${virus}`,
34
+ inspectorException: virus => `Audit pipeline anomaly: ${virus}`,
35
+ inspectorStart: 'Initializing [Tactical Requirements Inspector]...',
36
+ noCodeFile: fileName => `No valid code files detected in module directory [${fileName}]`,
33
37
  alignmentFailed: virus => `Alignment failed. Encountered powerful API firewall: ${virus}`,
38
+ inspectorToast: 'Requirements transmitted to Bunker. Terminal verification in progress...',
34
39
  signalSynchronized: (count, module) => `[${module}] Signal Synchronized: ${count} nodes energized`,
35
40
  autonomousAddressing: `Initiating 'Full-Channel Autonomous Addressing Protocol [Semantic Scan]'...`,
36
41
  inactiveModule: fileName => `Inactive module: [${fileName}]. Requesting 9S for precise alignment...`,
42
+ inspectorReady: 'Inspector browser ready! Please [select text] on the PRD page and [right-click] to inspect!',
37
43
  reconEncryptedData: fileName => `Encrypted data source [${fileName}] detected. Initiating semantic alignment...`,
44
+ comparingCodePrd: moduleName => `Cross-verifying [${moduleName}] source code against implicit PRD interactions...`,
38
45
  capturedRuntimeSignal: fileName => `Captured runtime signal [${fileName}]. Executing semantic alignment protocol...`,
46
+ loadedFiles: (moduleName, count) => `Loaded ${count} source files from [${moduleName}]. Executing logic reconciliation...`,
39
47
  unknownModule: fileName => `Module [${fileName}] resource not found in Bunker database. Please execute visual construction first`,
40
48
  bridgeProtocolComplete: (count, time) => `Semantic bridge protocol complete. Corrected ${count} field deviations. Elapsed: ${time}s`,
41
49
  },
@@ -29,11 +29,19 @@ module.exports = {
29
29
  assemblyComplete: (fileName, time) => `Protocole de construction terminé. Module [${fileName}] assemblé avec succès. Durée : ${time}s, Synchronisation de la configuration des routes Menu avec le Bunker...`,
30
30
  },
31
31
  pod153: {
32
+ prdAuditFinish: 'Audit du PRD terminé',
32
33
  signalLinkFault: virus => `Erreur de liaison du signal : ${virus}`,
34
+ inspectorStart: 'Initialisation de l’[Inspecteur Tactique des Exigences] (Inspector)...',
35
+ inspectorException: virus => `Anomalie dans la séquence d'audit (Virus logique) : ${virus}`,
36
+ inspectorToast: 'Spécifications transmises au Bunker. Comparaison en cours sur le terminal...',
37
+ noCodeFile: fileName => `Aucun fichier source viable détecté dans le répertoire du module [${fileName}]`,
33
38
  signalSynchronized: (count, module) => `[${module}] ${count} nœuds synchronisés. Alimentation confirmée`,
34
39
  inactiveModule: fileName => `Module non activé détecté [${fileName}]. Analyse haute précision demandée à 9S`,
40
+ inspectorReady: 'Navigateur de l’Inspecteur prêt ! Sélectionnez du texte sur le PRD puis effectuez un [clic droit] !',
35
41
  autonomousAddressing: 'Activation du « protocole d’adressage automatique multi-canaux [version analyse sémantique] »...',
42
+ comparingCodePrd: moduleName => `Vérification croisée du code de [${moduleName}] avec les interactions implicites du PRD...`,
36
43
  alignmentFailed: virus => `Échec de synchronisation du signal. Les données API sont bloquées par un pare-feu puissant : ${virus}`,
44
+ loadedFiles: (moduleName, count) => `${count} fichiers sources chargés depuis [${moduleName}]. Lancement du rapprochement logique...`,
37
45
  capturedRuntimeSignal: fileName => `Signal d’exécution intercepté [${fileName}]. Activation du protocole d’alignement automatique...`,
38
46
  reconEncryptedData: fileName => `Source de données chiffrées détectée [${fileName}]. Tentative du protocole d’alignement sémantique...`,
39
47
  bridgeProtocolComplete: (count, time) => `Protocole de pont sémantique terminé. ${count} différences de champs corrigées. Durée : ${time}s`,
@@ -29,13 +29,21 @@ module.exports = {
29
29
  assemblyComplete: (fileName, time) => `構築プロトコル完了。モジュール [${fileName}] の実装を確認。処理時間: ${time}s, BunkerへMenuルーティング設定を同期中...`,
30
30
  },
31
31
  pod153: {
32
+ prdAuditFinish: 'PRD監査完了',
32
33
  signalLinkFault: virus => `シグナルリンク障害: ${virus}`,
34
+ inspectorStart: '【戦術要求査察官】(Inspector) を起動中...',
35
+ inspectorException: virus => `監査パイプラインに論理ウイルスを検出: ${virus}`,
36
+ inspectorToast: '要求仕様データをBunkerへ転送。端末側で照合プロトコルを実行中...',
37
+ noCodeFile: fileName => `モジュール [${fileName}] 配下に有効なソースコードが検出されず`,
33
38
  autonomousAddressing: '「全チャンネル自動アドレス指定プロトコル [セマンティック解析版]」を起動...',
39
+ inspectorReady: '査察官ブラウザ待機完了。PRD上で【テキストを選択】し【右クリック】を実行してください!',
40
+ comparingCodePrd: moduleName => `[${moduleName}] のソースコードとPRDの暗黙的相互作用を照合中...`,
34
41
  signalSynchronized: (count, module) => `[${module}] ${count}個のノード同期完了。電力供給を確認`,
35
42
  inactiveModule: fileName => `未通電モジュール [${fileName}] を検出。9Sによる高精度照合を要求します`,
36
43
  capturedRuntimeSignal: fileName => `ランタイムシグナル [${fileName}] を捕捉。自動同期プロトコルを開始`,
37
44
  alignmentFailed: virus => `シグナル同期失敗。APIデータが強力なファイアウォールに阻害されています:${virus}`,
38
45
  reconEncryptedData: fileName => `暗号化データソース [${fileName}] を検出。セマンティック同期プロトコルを試行`,
46
+ loadedFiles: (moduleName, count) => `[${moduleName}] より ${count} 件のコードファイルを装填。論理照合を開始...`,
39
47
  unknownModule: fileName => `Bunkerデータベース内に [${fileName}] のリソース情報なし。先に視覚構築を実行してください`,
40
48
  bridgeProtocolComplete: (count, time) => `セマンティックブリッジ完了。${count}箇所のフィールド差異を修正。処理時間: ${time}s`,
41
49
  },
@@ -29,9 +29,17 @@ module.exports = {
29
29
  assemblyComplete: (fileName, time) => `Протокол сборки завершён. Модуль [${fileName}] успешно установлен. Время: ${time}s, Синхронизация конфигурации маршрутов Menu с Bunker...`,
30
30
  },
31
31
  pod153: {
32
+ prdAuditFinish: 'Аудит PRD завершён',
32
33
  signalLinkFault: virus => `Ошибка сигнального соединения: ${virus}`,
34
+ inspectorStart: 'Активация [Тактического инспектора требований] (Inspector)...',
35
+ inspectorToast: 'Разведданные требований переданы в Bunker. Сверка на терминале...',
36
+ inspectorException: virus => `Аномалия конвейера аудита (Логический вирус): ${virus}`,
37
+ noCodeFile: fileName => `В каталоге модуля [${fileName}] не обнаружено исходных файлов кода`,
33
38
  inactiveModule: fileName => `Обнаружен неактивный модуль [${fileName}]. Запрошен высокоточный анализ 9S`,
39
+ comparingCodePrd: moduleName => `Сверка исходного кода [${moduleName}] с неявными взаимодействиями PRD...`,
34
40
  autonomousAddressing: 'Запуск «протокола автоматической адресации всех каналов [семантическая версия]»...',
41
+ inspectorReady: 'Браузер инспектора готов! Выделите текст на странице PRD и нажмите [правой кнопкой мыши]!',
42
+ loadedFiles: (moduleName, count) => `Загружено ${count} файлов из [${moduleName}]. Запуск логической сверки...`,
35
43
  alignmentFailed: virus => `Ошибка выравнивания сигнала. Данные API заблокированы мощным брандмауэром: ${virus}`,
36
44
  signalSynchronized: (count, module) => `[${module}] Синхронизация ${count} узлов завершена. Питание подтверждено`,
37
45
  unknownModule: fileName => `Ресурс модуля [${fileName}] не найден в базе Bunker. Сначала выполните визуальную сборку`,
@@ -29,14 +29,22 @@ module.exports = {
29
29
  assemblyComplete: (fileName, time) => `构筑协议执行完毕。模块 [${fileName}] 物理装配达成。耗时: ${time}s,开始同步 Menu 路由配置...`,
30
30
  },
31
31
  pod153: {
32
+ prdAuditFinish: 'PRD审计完成',
33
+ inspectorToast: '需求已回传地堡,终端正在执行比对...',
32
34
  signalLinkFault: virus => `信号链路故障: ${virus}`,
35
+ inspectorStart: '启动【战术需求监察官】(Inspector)...',
36
+ inspectorException: virus => `审计流水线异常: ${virus}`,
33
37
  autonomousAddressing: `启动‘全频道自动寻址协议 [语义扫描版]’...`,
38
+ noCodeFile: fileName => `模块 [${fileName}] 目录下未检索到任何代码文件`,
34
39
  alignmentFailed: virus => `信号对齐失败。接口数据遭遇强力防火墙:${virus}`,
40
+ comparingCodePrd: moduleName => `正在比对 [${moduleName}] 源码与 PRD 隐性交互...`,
41
+ inspectorReady: '监察官浏览器已就绪!请在 PRD 页面中【鼠标划选需求文字】后【点击右键】!',
35
42
  inactiveModule: fileName => `发现未通电模块 [${fileName}],请求 9S 执行高精度对账...`,
36
43
  capturedRuntimeSignal: fileName => `截获运行时信号 [${fileName}]。执行自动对齐协议...`,
37
44
  signalSynchronized: (count, module) => `[${module}] 信号同步完成,${count} 个节点已通电`,
38
45
  reconEncryptedData: fileName => `发现加密数据源 [${fileName}]。正在尝试执行语义对齐协议...`,
39
46
  unknownModule: fileName => `地堡数据库中未检索到模块 [${fileName}] 的资源文件。请先执行视觉构筑`,
47
+ loadedFiles: (moduleName, count) => `已装载 [${moduleName}] 目录下 ${count} 个源码文件,开始执行逻辑对账...`,
40
48
  bridgeProtocolComplete: (count, time) => `语义桥接协议执行完毕。已物理修正 ${count} 处字段偏差。耗时: ${time}s`,
41
49
  },
42
50
  bunker: {
@@ -6,7 +6,7 @@ module.exports = () => {
6
6
 
7
7
  const pkgEnv = path.resolve(__dirname, '../../.env')
8
8
  const localEnv = path.resolve(process.cwd(), 'bunker', '.env')
9
- require('dotenv').config({ path: fs.existsSync(localEnv) ? localEnv : pkgEnv })
9
+ require('dotenv').config({ quiet: true, path: fs.existsSync(localEnv) ? localEnv : pkgEnv })
10
10
  const client = new OpenAI({ apiKey: process.env.API_KEY, baseURL: process.env.BASE_URL })
11
11
 
12
12
  return client
@@ -3,19 +3,20 @@ module.exports = ({ yorha, dialog, logistics, constitution }) => {
3
3
  const sharp = require('sharp')
4
4
 
5
5
  const { sleep } = logistics.presenter
6
+ const { FRONTEND_ENGINEER } = constitution.system
6
7
  const { textModel, visionModel } = logistics.supporter.getConfig()
7
- const { council, similarity, reconciler, simulation } = constitution
8
- const { UI_DESIGNER, API_DESIGNER, MOCK_DESIGNER } = constitution.system
8
+ const { council, inspector, similarity, reconciler, simulation } = constitution
9
9
 
10
10
  const openAI = require('./client')()
11
11
  const askAI = require('./protocol')({ yorha, sleep, dialog, openAI })
12
12
 
13
13
  return {
14
+ askAI,
14
15
  generateMock: async ({ columns, fileName }) => {
15
16
  return askAI({
16
17
  model: textModel,
17
18
  messages: [
18
- { role: 'system', content: MOCK_DESIGNER },
19
+ { role: 'system', content: FRONTEND_ENGINEER },
19
20
  { role: 'user', content: simulation({ columns, fileName }) }
20
21
  ]
21
22
  })
@@ -24,7 +25,7 @@ module.exports = ({ yorha, dialog, logistics, constitution }) => {
24
25
  return askAI({
25
26
  model: textModel,
26
27
  messages: [
27
- { role: 'system', content: UI_DESIGNER },
28
+ { role: 'system', content: FRONTEND_ENGINEER },
28
29
  { role: 'user', content: similarity({ fileName, english }) }
29
30
  ]
30
31
  })
@@ -33,7 +34,7 @@ module.exports = ({ yorha, dialog, logistics, constitution }) => {
33
34
  return askAI({
34
35
  model: textModel,
35
36
  messages: [
36
- { role: 'system', content: API_DESIGNER },
37
+ { role: 'system', content: FRONTEND_ENGINEER },
37
38
  { role: 'user', content: council({ bunkerAnchors, realApis }) }
38
39
  ]
39
40
  })
@@ -48,7 +49,7 @@ module.exports = ({ yorha, dialog, logistics, constitution }) => {
48
49
  return askAI({
49
50
  model: visionModel,
50
51
  messages: [
51
- { role: 'system', content: UI_DESIGNER },
52
+ { role: 'system', content: FRONTEND_ENGINEER },
52
53
  {
53
54
  role: 'user',
54
55
  content: [
@@ -64,10 +65,19 @@ module.exports = ({ yorha, dialog, logistics, constitution }) => {
64
65
  return askAI({
65
66
  model: textModel,
66
67
  messages: [
67
- { role: 'system', content: API_DESIGNER },
68
+ { role: 'system', content: FRONTEND_ENGINEER },
68
69
  { role: 'user', content: reconciler({ responseStr, resourceStr }) }
69
70
  ]
70
71
  })
71
72
  },
73
+ prdInspector: async ({ prdText, moduleName, moduleCodeView }) => {
74
+ return askAI({
75
+ model: textModel,
76
+ messages: [
77
+ { role: 'system', content: FRONTEND_ENGINEER },
78
+ { role: 'user', content: inspector({ prdText, moduleName, moduleCodeView }) }
79
+ ]
80
+ })
81
+ },
72
82
  }
73
83
  }
package/src/labs/index.js CHANGED
@@ -2,7 +2,7 @@ module.exports = accessPoint => {
2
2
 
3
3
  const labs = {}
4
4
 
5
- const { apiDoc, remains, needMock, proxyTarget, enableAutoAlignment } = accessPoint.logistics.supporter.getConfig()
5
+ const { prdDoc, apiDoc, remains, needMock, proxyTarget, enableAutoAlignment } = accessPoint.logistics.supporter.getConfig()
6
6
 
7
7
  if (!needMock && proxyTarget && enableAutoAlignment) {
8
8
  labs.tower = require('../labs/tower')(accessPoint)
@@ -16,5 +16,9 @@ module.exports = accessPoint => {
16
16
  labs.scout = require('../labs/scout')(accessPoint).archeology(remains)
17
17
  }
18
18
 
19
+ if (prdDoc) {
20
+ labs.inspector = require('../labs/inspector')(accessPoint).audit(prdDoc)
21
+ }
22
+
19
23
  return labs
20
24
  }
@@ -0,0 +1,183 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const chalk = require('chalk')
4
+
5
+ module.exports = ({ llm, yorha, dialog, logistics }) => {
6
+
7
+ const { pod153 } = yorha
8
+ const { prdInspector } = llm
9
+ const { pagesDir } = logistics.supporter.getConfig()
10
+ const { dehydratePrd, dehydrateCode } = logistics.formatter
11
+ const sessionDir = path.join(process.cwd(), 'bunker', '.chrome_session')
12
+
13
+ const isHttpURL = str => {
14
+ try {
15
+ const url = new URL(str)
16
+ return ['http:', 'https:'].includes(url.protocol)
17
+ } catch {
18
+ return false
19
+ }
20
+ }
21
+
22
+ const readModuleFiles = (dir, rootDir = dir) => {
23
+ let files = []
24
+ const list = fs.readdirSync(dir, { withFileTypes: true })
25
+ for (const item of list) {
26
+ const fullPath = path.join(dir, item.name)
27
+ if (item.isDirectory()) {
28
+ if (!['node_modules', '.git', '__tests__'].includes(item.name)) {
29
+ files = files.concat(readModuleFiles(fullPath, rootDir))
30
+ }
31
+ } else if (/\.(jsx?|tsx?|json)$/i.test(item.name)) {
32
+ files.push({
33
+ relativeName: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
34
+ content: fs.readFileSync(fullPath, 'utf-8')
35
+ })
36
+ }
37
+ }
38
+ return files
39
+ }
40
+
41
+ return {
42
+ audit: async prdDoc => {
43
+
44
+ if (!isHttpURL(prdDoc)) return
45
+
46
+ const puppeteer = require('puppeteer')
47
+ pod153.report(dialog.pod153.inspectorStart, 'magenta')
48
+ const browser = await puppeteer.launch({
49
+ headless: false,
50
+ handleSIGINT: false,
51
+ defaultViewport: null,
52
+ userDataDir: sessionDir,
53
+ args: ['--start-maximized']
54
+ })
55
+
56
+ const pages = await browser.pages()
57
+ const page = pages.length > 0 ? pages[0] : await browser.newPage()
58
+
59
+ await page.exposeFunction('nodeAuditRequirement', async ({ prdText, moduleName }) => {
60
+ if (!prdText || !moduleName) return
61
+ const pageDir = path.join(process.cwd(), pagesDir, moduleName)
62
+ const sourceFiles = readModuleFiles(pageDir)
63
+ if (sourceFiles.length === 0) {
64
+ pod153.report(dialog.pod153.noCodeFile(moduleName), 'red')
65
+ return
66
+ }
67
+ const moduleCodeView = sourceFiles.map(file => `### 📄 文件:${file.relativeName}\njavascript:\n${dehydrateCode(file.content)}`).join('\n\n')
68
+ pod153.report(dialog.pod153.loadedFiles(moduleName, sourceFiles.length), 'yellow')
69
+ const startTime = Date.now()
70
+ const spinner = pod153.start(dialog.pod153.comparingCodePrd(moduleName), 'yellow')
71
+ try {
72
+ const cleanPrdText = dehydratePrd(prdText)
73
+ const result = await prdInspector({ prdText: cleanPrdText, moduleName, moduleCodeView })
74
+ const endTime = Date.now()
75
+ pod153.success(spinner, dialog.pod153.prdAuditFinish)
76
+ const duration = ((endTime - startTime) / 1000).toFixed(2)
77
+ const gaps = result.unimplementedGaps || []
78
+ const hasGaps = gaps.length > 0
79
+ const divider = '─'.repeat(48)
80
+ console.log('\n' + chalk.magenta(`╭── 🛰️ [YoRHa TACTICAL INSPECTION PROTOCOL] ──────────────────────────────────────────`))
81
+ console.log(chalk.magenta(`│ `))
82
+ const metaPills = [
83
+ chalk.bgCyan.black.bold(` MODULE `) + ` ` + chalk.bold.white(moduleName),
84
+ chalk.bgYellow.black.bold(` DURATION `) + ` ` + chalk.bold.white(`${duration}s`),
85
+ chalk.bgMagenta.black.bold(` AUDIT `) + ` ` + chalk.bold.white(`${sourceFiles.length} source files`),
86
+ ].join(' ')
87
+ console.log(chalk.magenta(`│ `) + metaPills)
88
+ console.log(chalk.magenta(`│ `))
89
+ if (hasGaps) {
90
+ console.log(chalk.magenta(`├── `) + chalk.bold.red(`🚨 TACTICAL DEFECTS`) + chalk.magenta(divider))
91
+ console.log(chalk.magenta(`│ `))
92
+ gaps.forEach((gap, idx) => {
93
+ const num = String(idx + 1).padStart(2, '0')
94
+ console.log(chalk.magenta(`│ `) + chalk.bold.bgRed.white(` GAP #${num} `) + ` ` + chalk.bold.red(gap.feature))
95
+ if (gap.detail) {
96
+ console.log(chalk.magenta(`│`) + chalk.gray(` ├─ `) + chalk.yellow(`🔍 `) + chalk.white(gap.feature))
97
+ console.log(chalk.magenta(`│`) + chalk.gray(` └─ `) + chalk.red(`⚠️ `) + chalk.yellow(gap.detail))
98
+ }
99
+ console.log(chalk.magenta(`│ `))
100
+ })
101
+ }
102
+ console.log(chalk.magenta(`╰─────────────────────────────── [ Pod 153 Verification Seal ] ────────────────────────\n`))
103
+ } catch (e) {
104
+ pod153.report(dialog.pod153.inspectorException(e.message), 'red')
105
+ }
106
+ })
107
+ const browserDialog = { inspectorToast: dialog.pod153.inspectorToast }
108
+ await page.evaluateOnNewDocument(({ inspectorToast }) => {
109
+
110
+ const showBunkerInput = () => new Promise(resolve => {
111
+ const overlay = document.createElement('div')
112
+ overlay.style.cssText = `
113
+ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
114
+ background: rgba(0, 0, 0, 0.65); z-index: 9999999;
115
+ display: flex; align-items: center; justify-content: center;
116
+ backdrop-filter: blur(3px);
117
+ `
118
+ overlay.innerHTML = `
119
+ <div style="background: #111; border: 2px solid #00ffcc; padding: 24px; border-radius: 6px; box-shadow: 0 0 25px rgba(0,255,204,0.4); font-family: monospace; color: #00ffcc; min-width: 380px;">
120
+ <div style="font-weight: bold; margin-bottom: 8px; font-size: 15px;">⚡ [YoRHa 战术监察官] 需求情报已捕获</div>
121
+ <div style="font-size: 12px; color: #888; margin-bottom: 14px;">请输入要对比的前端模块名 (如: ContractList):</div>
122
+ <input id="bunker-input" placeholder="输入模块名后回车..." style="width: 100%; box-sizing: border-box; background: #222; border: 1px solid #00ffcc; color: #fff; padding: 10px 12px; font-size: 14px; font-family: monospace; outline: none; border-radius: 4px;" />
123
+ <div style="display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px;">
124
+ <span style="font-size: 11px; color: #666; margin-right: auto; line-height: 24px;">[ESC 取消 / Enter 确认]</span>
125
+ </div>
126
+ </div>
127
+ `
128
+ document.body.appendChild(overlay)
129
+ const input = overlay.querySelector('#bunker-input')
130
+ setTimeout(() => input.focus(), 50)
131
+ input.addEventListener('keydown', e => {
132
+ if (e.key === 'Enter' && input.value.trim()) {
133
+ const val = input.value.trim()
134
+ overlay.remove()
135
+ resolve(val)
136
+ }
137
+ if (e.key === 'Escape') {
138
+ overlay.remove()
139
+ resolve(null)
140
+ }
141
+ })
142
+ overlay.addEventListener('click', e => {
143
+ if (e.target === overlay) {
144
+ overlay.remove()
145
+ resolve(null)
146
+ }
147
+ })
148
+ })
149
+
150
+ window.addEventListener('contextmenu', async (e) => {
151
+ const selection = window.getSelection().toString().trim()
152
+ if (selection && selection.length > 5) {
153
+ e.preventDefault()
154
+ const moduleName = await showBunkerInput()
155
+ if (moduleName) {
156
+ window.nodeAuditRequirement({ prdText: selection, moduleName: moduleName.trim() })
157
+ const toast = document.createElement('div')
158
+ toast.innerText = inspectorToast
159
+ toast.style.cssText = `
160
+ position: fixed; top: 20px; right: 20px; z-index: 9999999;
161
+ background: rgba(0, 0, 0, 0.9); color: #ff00ff;
162
+ border: 1px solid #ff00ff; padding: 12px 24px;
163
+ font-size: 14px; font-family: monospace; border-radius: 4px;
164
+ box-shadow: 0 0 15px rgba(255, 0, 255, 0.5);
165
+ pointer-events: none; transition: opacity 0.4s ease;
166
+ `
167
+ document.body.appendChild(toast)
168
+ setTimeout(() => {
169
+ toast.style.opacity = '0'
170
+ setTimeout(() => toast.remove(), 400)
171
+ }, 2000)
172
+ }
173
+ }
174
+ })
175
+ }, browserDialog)
176
+ if (prdDoc) {
177
+ await page.goto(prdDoc)
178
+ }
179
+ pod153.report(dialog.pod153.inspectorReady, 'green')
180
+ return browser
181
+ }
182
+ }
183
+ }
package/src/labs/scout.js CHANGED
@@ -112,9 +112,7 @@ module.exports = ({ yorha, dialog }) => {
112
112
  const page = pages.length > 0 ? pages[0] : await globalBrowser.newPage()
113
113
 
114
114
  try {
115
-
116
115
  const browserDialog = { capturedToast: dialog.pod042.capturedToast }
117
-
118
116
  await page.exposeFunction('nodeTakeScreenshot', async () => {
119
117
  const currentUrl = await page.url()
120
118
  const pageName = derivePageNameFromUrl(currentUrl)
@@ -17,8 +17,40 @@ const cleanCode = str => str
17
17
 
18
18
  const contextStringify = ({ context, indent = 4, maxLength = 200 }) => stringify.default(context, { indent, maxLength })
19
19
 
20
+ const dehydrateCode = code => code
21
+ // 1. 物理砍掉所有的 import 依赖声明 (省 20-30 行废话)
22
+ .replace(/^import\s+.*?;\s*$/gm, '')
23
+ .replace(/^import\s+[\s\S]*?from\s+['"].*?['"];?\s*$/gm, '')
24
+ // 2. 物理砍掉所有内联 style 样式 (与业务对账毫无关系)
25
+ .replace(/style=\{\{[\s\S]*?\}\}/g, '')
26
+ // 3. 物理砍掉所有 className 和无关的 UI 属性
27
+ .replace(/className=['"][^'"]*['"]/g, '')
28
+ // 4. 物理砍掉常见的纯排版外壳标签 (<Row>, <Col>, <Card>, <Space>),只留内容
29
+ .replace(/<\/?(Row|Col|Card|Space|Fragment)[^>]*>/g, '')
30
+ // 5. 压缩多余连续空格和空行
31
+ .replace(/[ \t]+$/gm, '')
32
+ .replace(/\n{2,}/g, '\n')
33
+ .trim()
34
+
35
+ const dehydratePrd = text => text
36
+ // 1. 过滤掉从 Wiki 复制时带过来的各种 HTML 隐藏标签和实体
37
+ .replace(/<[^>]+>/g, '')
38
+ .replace(/&nbsp;/g, ' ')
39
+ // 2. 过滤掉公文废话(这些绝对不是业务规则)
40
+ .replace(/(本功能旨在|为了提升.*?体验|根据.*?业务要求|原则上需由|系统应当支持|请业务方注意).*?[。;\n]/g, '')
41
+ // 3. 过滤纯 UI 视觉修饰描述(这些由设计稿决定,对账不关心)
42
+ .replace(/(字体大小为.*?|居中显示|背景色为.*?|间距.*?px|左右对齐).*?[。;\n]/g, '')
43
+ // 4. 将连续换行折叠为单换行,剔除每一行首尾无意义字符
44
+ .split('\n')
45
+ .map(line => line.trim())
46
+ .filter(line => line.length > 3) // 过滤掉无意义的单字标点行
47
+ .join('\n')
48
+ .trim()
49
+
20
50
  module.exports = {
21
51
  wrapCode,
22
52
  cleanCode,
53
+ dehydratePrd,
54
+ dehydrateCode,
23
55
  contextStringify,
24
56
  }
@@ -80,10 +80,10 @@ module.exports = {
80
80
  pod042.success(spinner, dialog.pod042.assemblyComplete(fileName, (endTime - startTime) / 1000))
81
81
 
82
82
  if (fs.existsSync(mission.input)) {
83
- fs.unlinkSync(mission.input)
84
83
  const cwdUtilsDir = path.join(process.cwd(), utilsDir)
85
84
  if (!fs.existsSync(cwdUtilsDir)) fs.mkdirSync(cwdUtilsDir, { recursive: true })
86
85
  fs.writeFileSync(path.join(cwdUtilsDir, 'menus.js'), `export const menus = ${contextStringify({ context: logistics.supporter.getExistingMenus(pagesDir), maxLength: 50 })}`)
86
+ fs.unlinkSync(mission.input)
87
87
  }
88
88
 
89
89
  } catch (error) {
@@ -1 +1 @@
1
- <MySearchForm form={form} search={search} loading={loading} formItems={ {{formItems}} } initialParams={initialParams} initialValues={query} setSearch={setSearch} />
1
+ <MySearchForm form={form} loading={loading} formItems={ {{formItems}} } initialParams={initialParams} initialValues={query} setSearch={setSearch} />
@@ -71,17 +71,16 @@ const showImg = (record, dataIndex) => {
71
71
 
72
72
  {{/if}}
73
73
 
74
- let fetching = false
75
74
  const modalSubmit = async params => {
75
+ if (fetchingRef.current) return
76
76
  const requestMap={
77
77
  {{#each operations}}
78
78
  {{this.action}}:()=> {{this.request}},
79
79
  {{/each}}
80
80
  }
81
- if (fetching) return
82
- fetching = true
81
+ fetchingRef.current = true
83
82
  const response = await requestMap[modal.type]?.()
84
- fetching = false
83
+ fetchingRef.current = false
85
84
  if({{{responseSuccess}}}){
86
85
  setModal({ visible: false })
87
86
  refresh()
@@ -2,6 +2,8 @@
2
2
  const { } = useContext(GlobalContext)
3
3
 
4
4
  const rowKey = 'id'
5
+ const fetchingRef = useRef(false)
6
+
5
7
  {{#if hasFormItems}}
6
8
  const [form] = Form.useForm()
7
9
  {{/if}}
@@ -147,7 +147,7 @@ module.exports = ({ template, logistics, headquarters }) => {
147
147
  hasRowSelection: pageConfig.table.rowSelection,
148
148
  formItems: hasFormItems ? (hasTabs ? 'formItems[activeKey]' : 'formItems') : '[]',
149
149
  operations: pageConfig.table.operation?.sort((a, b) => a.action.length - b.action.length) || [],
150
- initParams: `{ ${hasTabs ? 'type: tabs[0].key ,' : ''}${hasPagination ? 'pageNo: 1 , pageSize: 10' : ''}}`,
150
+ initParams: `{ ${hasTabs ? 'type: activeKey,' : ''}${hasPagination ? 'pageNo: 1 , pageSize: 10' : ''}}`,
151
151
  uri: needMock ? `request('/api/${fileName}', { method: 'POST', body: params })` : 'BUNKER_API_ANCHOR_pages',
152
152
  }
153
153