@yorha2b-lab/autodev 7.7.7 → 7.8.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.
Files changed (31) hide show
  1. package/bin/bunker.js +2 -0
  2. package/config.js +2 -0
  3. package/package.json +1 -1
  4. package/src/constitution/inspector.js +20 -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/workshop/react/builder/index.js +1 -1
  18. package/src/workshop/react/extensions/useTableQuery.js +0 -51
  19. package/src/workshop/react/kit/utils.js +51 -102
  20. package/src/workshop/react/standardParts/AliyunOSSUpload.js +0 -45
  21. package/src/workshop/react/standardParts/DraggableContainer.js +1 -1
  22. package/src/workshop/react/standardParts/EditableCell.js +32 -74
  23. package/src/workshop/react/standardParts/MyBaseForm.js +1 -48
  24. package/src/workshop/react/standardParts/MyDescription.js +2 -2
  25. package/src/workshop/react/standardParts/MyForm.js +41 -15
  26. package/src/workshop/react/standardParts/MyModalForm.js +3 -84
  27. package/src/workshop/react/standardParts/MyModalTable.js +0 -53
  28. package/src/workshop/react/standardParts/MyPopoverTable.js +23 -0
  29. package/src/workshop/react/standardParts/MySearchForm.js +2 -60
  30. package/src/workshop/react/standardParts/MyTable.js +38 -122
  31. package/src/workshop/react/standardParts/index.js +5 -18
package/bin/bunker.js CHANGED
@@ -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.7",
3
+ "version": "7.8.0",
4
4
  "description": "基于 AI 视觉与语义协议的前端 (React+Antd) 代码构筑与旧系统重构引擎",
5
5
  "bin": {
6
6
  "bunker": "bin/bunker.js"
@@ -0,0 +1,20 @@
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
+
14
+ ## 4. 输出紧凑 JSON:
15
+ {
16
+ "unimplementedGaps": [
17
+ { "feature": "状态为草稿时禁止提交", "detail": "代码中未见提交按钮的 disabled 判定" }
18
+ ]
19
+ }
20
+ `.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
  }
@@ -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
 
@@ -1,34 +1,5 @@
1
1
  import { useRef, useState, useEffect, useCallback } from 'react'
2
2
 
3
- /**
4
- * @hook useTableQuery
5
- * @description [地堡核心逻辑引擎] 高级异步数据驱动钩子。
6
- * 负责全自动管理表格的生命周期,包括数据抓取、状态同步、动态列指纹识别及“竞态病毒”物理免疫。
7
- *
8
- * @param {Object} params - 引擎配置包
9
- * @param {Array} [params.cols] - 初始战备物资:前端预设的静态列配置
10
- * @param {Function} params.api - 任务指令:必须返回 Promise 的数据抓取函数
11
- * @param {Object} [params.initialParams={}] - 初始导航坐标:包含 pageNo, pageSize 及默认过滤参数
12
- * @param {Function} params.formatResponse - 数据解码协议:将 API 原始信号转化为 { data, total, columns } 结构
13
- *
14
- * @returns {Object} 物理操作句柄包
15
- * @returns {number} .total - 仓库物资总量
16
- * @returns {boolean} .loading - 引擎负载状态
17
- * @returns {Function} .setSearch - 坐标修改器
18
- * @returns {Array} .dataSource - 已解压的物理物资列表
19
- * @returns {Function} .setLoading - 负载状态手动干预句柄
20
- * @returns {Object} .search - 当前导航参数(pageNo, pageSize 等)
21
- * @returns {Function} .refresh - 强制重启协议:立即执行一次数据抓取
22
- * @returns {Array} .columns - 当前生效的动态列配置(已通过指纹协议校验)
23
- * @returns {Function} .setDataSource - 物资流手动修改句柄(用于行内编辑等乐观更新)
24
- *
25
- * @example
26
- * const { dataSource, loading, refresh } = useTableQuery({
27
- * api:async params=>await fetchUsers(params),
28
- * initialParams: { pageNo: 1, pageSize: 10 },
29
- * formatResponse: (res) => ({ data: res.list, total: res.total })
30
- * });
31
- */
32
3
  export const useTableQuery = ({ api, cols = [], initialParams = {}, formatResponse, isLocalPaging = false }) => {
33
4
 
34
5
  const [total, setTotal] = useState(0)
@@ -37,13 +8,9 @@ export const useTableQuery = ({ api, cols = [], initialParams = {}, formatRespon
37
8
  const [dataSource, setDataSource] = useState([])
38
9
  const [search, setSearch] = useState(initialParams)
39
10
 
40
- // 💡 引用锁定协议:确保异步回调中永远能指向最新的指令地址
41
11
  const apiRef = useRef(api)
42
- // 💡 [核心防御] 信号 ID 计数器:用于物理拦截由于网络时延导致的“竞态病毒(Race Condition)”
43
12
  const fetchIdRef = useRef(0)
44
- // 💡 物理记录仪:记录上一次“真正”的过滤参数(不含分页)
45
13
  const lastFilterRef = useRef('')
46
- // 💡 [核心防御] 数据解码协议引用:确保异步回调中永远能指向最新的指令地址
47
14
  const formatResponseRef = useRef(formatResponse)
48
15
 
49
16
  useEffect(() => {
@@ -54,21 +21,8 @@ export const useTableQuery = ({ api, cols = [], initialParams = {}, formatRespon
54
21
  formatResponseRef.current = formatResponse
55
22
  }, [formatResponse])
56
23
 
57
- /**
58
- * @function getColumnSchema
59
- * @description [指纹识别协议] 对列配置执行“结构化脱水”。
60
- * 通过提取标题、字段和排序状态生成物理指纹,用于判定列结构是否发生“基因变异”。
61
- * @param {Array} cols - 待扫描的列配置
62
- * @returns {string} 物理指纹字符串
63
- */
64
24
  const getColumnSchema = cols => cols.map(col => `${col.title}-${col.dataIndex}-${col.sortOrder}`).join('|')
65
25
 
66
- /**
67
- * @async
68
- * @function fetchData
69
- * @description [数据装配任务] 执行核心的数据抓取与物理封存逻辑。
70
- * 具备信号溯源能力,确保只有最后一次发出的指令能修改地堡状态。
71
- */
72
26
  const fetchData = useCallback(async () => {
73
27
  if (!apiRef.current) return
74
28
 
@@ -81,16 +35,12 @@ export const useTableQuery = ({ api, cols = [], initialParams = {}, formatRespon
81
35
  }
82
36
 
83
37
  setLoading(true)
84
- // 💡 信号加标:为本次请求分配唯一的物理 ID
85
38
  const currentFetchId = ++fetchIdRef.current
86
39
  try {
87
40
  const response = await apiRef.current(search)
88
- // 💡 [物理拦截] 信号校准:若当前 ID 不是最新 ID,说明该信号已过期,执行丢弃协议
89
41
  if (currentFetchId !== fetchIdRef.current) return
90
42
  const { data, total, columns: newCols } = formatResponseRef.current?.(response ?? {}) || {}
91
- // 💡 [性能装甲] 只有当列指纹发生变化时,才触发 React 的重绘逻辑
92
43
  if (newCols) {
93
- // 使用函数式状态更新,既能拿到最新的 prevCols,又无需将 columns 作为 useCallback 的依赖!
94
44
  setColumns(prevCols => {
95
45
  if (getColumnSchema(newCols) !== getColumnSchema(prevCols)) {
96
46
  return newCols
@@ -103,7 +53,6 @@ export const useTableQuery = ({ api, cols = [], initialParams = {}, formatRespon
103
53
  } catch (error) {
104
54
  console.error('查询失败', error)
105
55
  } finally {
106
- // 💡 状态闭环:确保只有最新的信号能解除 Loading 锁定
107
56
  if (currentFetchId === fetchIdRef.current) {
108
57
  setLoading(false)
109
58
  }