@hongmaple0820/scale-engine 0.48.0 → 0.50.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 (242) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/agents/evidenceDiscipline.d.ts +7 -0
  4. package/dist/agents/evidenceDiscipline.js +21 -0
  5. package/dist/agents/evidenceDiscipline.js.map +1 -0
  6. package/dist/agents/profiles.js +8 -1
  7. package/dist/agents/profiles.js.map +1 -1
  8. package/dist/agents/types.d.ts +1 -0
  9. package/dist/api/DashboardHttpConfig.d.ts +28 -0
  10. package/dist/api/DashboardHttpConfig.js +110 -0
  11. package/dist/api/DashboardHttpConfig.js.map +1 -0
  12. package/dist/api/cli.js +102 -11
  13. package/dist/api/cli.js.map +1 -1
  14. package/dist/api/http.d.ts +1 -0
  15. package/dist/api/http.js +50 -0
  16. package/dist/api/http.js.map +1 -0
  17. package/dist/artifact/types.d.ts +64 -0
  18. package/dist/artifact/types.js.map +1 -1
  19. package/dist/bootstrap/DependencyBootstrap.d.ts +1 -0
  20. package/dist/bootstrap/DependencyBootstrap.js +14 -3
  21. package/dist/bootstrap/DependencyBootstrap.js.map +1 -1
  22. package/dist/cli/cortexApplyCommand.d.ts +26 -0
  23. package/dist/cli/cortexApplyCommand.js +74 -0
  24. package/dist/cli/cortexApplyCommand.js.map +1 -0
  25. package/dist/cli/cortexCandidateCommands.d.ts +42 -0
  26. package/dist/cli/cortexCandidateCommands.js +119 -0
  27. package/dist/cli/cortexCandidateCommands.js.map +1 -0
  28. package/dist/cli/cortexCommands.d.ts +51 -0
  29. package/dist/cli/cortexCommands.js +127 -13
  30. package/dist/cli/cortexCommands.js.map +1 -1
  31. package/dist/cli/engineBootstrap.d.ts +1 -1
  32. package/dist/cli/engineBootstrap.js +2 -0
  33. package/dist/cli/engineBootstrap.js.map +1 -1
  34. package/dist/cli/evalCommands.js +13 -1
  35. package/dist/cli/evalCommands.js.map +1 -1
  36. package/dist/cli/phaseCommands.d.ts +81 -1
  37. package/dist/cli/phaseCommands.js +465 -31
  38. package/dist/cli/phaseCommands.js.map +1 -1
  39. package/dist/cli/runtimeSkillCommands.js +12 -2
  40. package/dist/cli/runtimeSkillCommands.js.map +1 -1
  41. package/dist/cli/shieldCommands.d.ts +1 -0
  42. package/dist/cli/shieldCommands.js +20 -7
  43. package/dist/cli/shieldCommands.js.map +1 -1
  44. package/dist/cli/workflowEvidenceCommands.d.ts +120 -0
  45. package/dist/cli/workflowEvidenceCommands.js +228 -2
  46. package/dist/cli/workflowEvidenceCommands.js.map +1 -1
  47. package/dist/cortex/AutoFixEventObservations.d.ts +11 -0
  48. package/dist/cortex/AutoFixEventObservations.js +72 -0
  49. package/dist/cortex/AutoFixEventObservations.js.map +1 -0
  50. package/dist/cortex/GateEvidenceObservations.d.ts +22 -0
  51. package/dist/cortex/GateEvidenceObservations.js +179 -0
  52. package/dist/cortex/GateEvidenceObservations.js.map +1 -0
  53. package/dist/cortex/GovernanceMetrics.d.ts +2 -0
  54. package/dist/cortex/GovernanceMetrics.js +112 -22
  55. package/dist/cortex/GovernanceMetrics.js.map +1 -1
  56. package/dist/cortex/InstinctApplicationRecorder.d.ts +28 -0
  57. package/dist/cortex/InstinctApplicationRecorder.js +145 -0
  58. package/dist/cortex/InstinctApplicationRecorder.js.map +1 -0
  59. package/dist/cortex/InstinctCandidateAudit.d.ts +3 -0
  60. package/dist/cortex/InstinctCandidateAudit.js +39 -0
  61. package/dist/cortex/InstinctCandidateAudit.js.map +1 -0
  62. package/dist/cortex/InstinctCandidateReview.d.ts +32 -0
  63. package/dist/cortex/InstinctCandidateReview.js +125 -0
  64. package/dist/cortex/InstinctCandidateReview.js.map +1 -0
  65. package/dist/cortex/InstinctExtractor.d.ts +1 -0
  66. package/dist/cortex/InstinctExtractor.js +24 -17
  67. package/dist/cortex/InstinctExtractor.js.map +1 -1
  68. package/dist/cortex/InstinctRuntimeEvidence.d.ts +14 -0
  69. package/dist/cortex/InstinctRuntimeEvidence.js +120 -0
  70. package/dist/cortex/InstinctRuntimeEvidence.js.map +1 -0
  71. package/dist/cortex/InstinctStore.d.ts +50 -4
  72. package/dist/cortex/InstinctStore.js +262 -48
  73. package/dist/cortex/InstinctStore.js.map +1 -1
  74. package/dist/cortex/InstinctValidation.d.ts +9 -0
  75. package/dist/cortex/InstinctValidation.js +55 -0
  76. package/dist/cortex/InstinctValidation.js.map +1 -0
  77. package/dist/cortex/SessionInjector.d.ts +1 -0
  78. package/dist/cortex/SessionInjector.js +28 -8
  79. package/dist/cortex/SessionInjector.js.map +1 -1
  80. package/dist/dashboard/DashboardServer.d.ts +79 -0
  81. package/dist/dashboard/DashboardServer.js +330 -6
  82. package/dist/dashboard/DashboardServer.js.map +1 -1
  83. package/dist/dashboard/spa/app.js +515 -0
  84. package/dist/dashboard/spa/components/DataTable.js +53 -0
  85. package/dist/dashboard/spa/components/EventStream.js +66 -0
  86. package/dist/dashboard/spa/components/LoadingState.js +39 -0
  87. package/dist/dashboard/spa/components/MetricCard.js +30 -0
  88. package/dist/dashboard/spa/components/Panel.js +27 -0
  89. package/dist/dashboard/spa/components/StatusBadge.js +51 -0
  90. package/dist/dashboard/spa/i18n.js +767 -0
  91. package/dist/dashboard/spa/index.html +463 -0
  92. package/dist/dashboard/spa/pages/costs.js +522 -0
  93. package/dist/dashboard/spa/pages/documents.js +540 -0
  94. package/dist/dashboard/spa/pages/knowledge.js +457 -0
  95. package/dist/dashboard/spa/pages/monitoring.js +361 -0
  96. package/dist/dashboard/spa/pages/overview.js +301 -0
  97. package/dist/dashboard/spa/pages/topology-renderers.js +251 -0
  98. package/dist/dashboard/spa/pages/topology.js +370 -0
  99. package/dist/dashboard/spa/pages/workflow-renderers.js +239 -0
  100. package/dist/dashboard/spa/pages/workflow.js +217 -0
  101. package/dist/env/EnvironmentDoctor.js +12 -7
  102. package/dist/env/EnvironmentDoctor.js.map +1 -1
  103. package/dist/eval/BenchmarkPublisher.d.ts +2 -0
  104. package/dist/eval/BenchmarkPublisher.js +43 -0
  105. package/dist/eval/BenchmarkPublisher.js.map +1 -1
  106. package/dist/eval/WorkflowEval.d.ts +9 -0
  107. package/dist/eval/WorkflowEval.js +348 -2
  108. package/dist/eval/WorkflowEval.js.map +1 -1
  109. package/dist/guardrails/ast/confirmers.d.ts +18 -0
  110. package/dist/guardrails/ast/confirmers.js +69 -0
  111. package/dist/guardrails/ast/confirmers.js.map +1 -0
  112. package/dist/guardrails/ast/parse.d.ts +20 -0
  113. package/dist/guardrails/ast/parse.js +51 -0
  114. package/dist/guardrails/ast/parse.js.map +1 -0
  115. package/dist/memory/MemoryBrain.d.ts +13 -0
  116. package/dist/memory/MemoryBrain.js +47 -0
  117. package/dist/memory/MemoryBrain.js.map +1 -1
  118. package/dist/memory/MemoryFabric.d.ts +1 -0
  119. package/dist/memory/MemoryFabric.js +12 -8
  120. package/dist/memory/MemoryFabric.js.map +1 -1
  121. package/dist/memory/MemoryLearning.d.ts +1 -0
  122. package/dist/memory/MemoryLearning.js +6 -3
  123. package/dist/memory/MemoryLearning.js.map +1 -1
  124. package/dist/memory/MemoryProviders.d.ts +8 -1
  125. package/dist/memory/MemoryProviders.js +143 -29
  126. package/dist/memory/MemoryProviders.js.map +1 -1
  127. package/dist/output/HTMLDocumentRenderer.d.ts +9 -0
  128. package/dist/output/HTMLDocumentRenderer.js +19 -0
  129. package/dist/output/HTMLDocumentRenderer.js.map +1 -1
  130. package/dist/review/FreshContextVerifier.d.ts +35 -0
  131. package/dist/review/FreshContextVerifier.js +120 -0
  132. package/dist/review/FreshContextVerifier.js.map +1 -0
  133. package/dist/review/JsonLlmClient.d.ts +37 -0
  134. package/dist/review/JsonLlmClient.js +94 -0
  135. package/dist/review/JsonLlmClient.js.map +1 -0
  136. package/dist/review/LlmJudge.d.ts +61 -0
  137. package/dist/review/LlmJudge.js +167 -0
  138. package/dist/review/LlmJudge.js.map +1 -0
  139. package/dist/runtime/AiOsRuntime.d.ts +14 -1
  140. package/dist/runtime/AiOsRuntime.js +59 -3
  141. package/dist/runtime/AiOsRuntime.js.map +1 -1
  142. package/dist/runtime/RuntimeDoctor.js +3 -1
  143. package/dist/runtime/RuntimeDoctor.js.map +1 -1
  144. package/dist/runtime/RuntimeEvidenceLedger.d.ts +6 -0
  145. package/dist/runtime/RuntimeEvidenceLedger.js +52 -1
  146. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -1
  147. package/dist/runtime/SessionLedger.d.ts +2 -0
  148. package/dist/runtime/SessionLedger.js +4 -0
  149. package/dist/runtime/SessionLedger.js.map +1 -1
  150. package/dist/setup/SetupVerification.js +53 -5
  151. package/dist/setup/SetupVerification.js.map +1 -1
  152. package/dist/shield/PolicyCompiler.js +73 -12
  153. package/dist/shield/PolicyCompiler.js.map +1 -1
  154. package/dist/shield/ProtectedPaths.js +4 -2
  155. package/dist/shield/ProtectedPaths.js.map +1 -1
  156. package/dist/skills/SkillCatalog.d.ts +2 -0
  157. package/dist/skills/SkillCatalog.js +8 -0
  158. package/dist/skills/SkillCatalog.js.map +1 -1
  159. package/dist/skills/SkillDoctor.d.ts +19 -2
  160. package/dist/skills/SkillDoctor.js +163 -13
  161. package/dist/skills/SkillDoctor.js.map +1 -1
  162. package/dist/tools/SafeCommandRunner.d.ts +1 -0
  163. package/dist/tools/SafeCommandRunner.js +1 -0
  164. package/dist/tools/SafeCommandRunner.js.map +1 -1
  165. package/dist/tools/ToolCapabilityRegistry.js +25 -3
  166. package/dist/tools/ToolCapabilityRegistry.js.map +1 -1
  167. package/dist/tools/ToolOrchestrator.js +21 -0
  168. package/dist/tools/ToolOrchestrator.js.map +1 -1
  169. package/dist/version.d.ts +1 -1
  170. package/dist/version.js +1 -1
  171. package/dist/workflow/AgentLoopReadiness.d.ts +103 -0
  172. package/dist/workflow/AgentLoopReadiness.js +371 -0
  173. package/dist/workflow/AgentLoopReadiness.js.map +1 -0
  174. package/dist/workflow/BoundaryEnforcement.d.ts +60 -0
  175. package/dist/workflow/BoundaryEnforcement.js +182 -0
  176. package/dist/workflow/BoundaryEnforcement.js.map +1 -0
  177. package/dist/workflow/EcosystemReadinessGate.d.ts +46 -0
  178. package/dist/workflow/EcosystemReadinessGate.js +126 -0
  179. package/dist/workflow/EcosystemReadinessGate.js.map +1 -0
  180. package/dist/workflow/EngineeringStandards.js +67 -12
  181. package/dist/workflow/EngineeringStandards.js.map +1 -1
  182. package/dist/workflow/GateCatalog.js +21 -2
  183. package/dist/workflow/GateCatalog.js.map +1 -1
  184. package/dist/workflow/GovernanceTemplatePacks.js +2 -26
  185. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -1
  186. package/dist/workflow/GovernanceTemplates.js +8 -1
  187. package/dist/workflow/GovernanceTemplates.js.map +1 -1
  188. package/dist/workflow/ProfileEnforcement.d.ts +7 -0
  189. package/dist/workflow/ProfileEnforcement.js +12 -0
  190. package/dist/workflow/ProfileEnforcement.js.map +1 -0
  191. package/dist/workflow/ReleaseDeploymentLedger.d.ts +63 -0
  192. package/dist/workflow/ReleaseDeploymentLedger.js +154 -0
  193. package/dist/workflow/ReleaseDeploymentLedger.js.map +1 -0
  194. package/dist/workflow/ReviewAnalyzer.js +50 -3
  195. package/dist/workflow/ReviewAnalyzer.js.map +1 -1
  196. package/dist/workflow/ReviewStore.d.ts +10 -0
  197. package/dist/workflow/ReviewStore.js.map +1 -1
  198. package/dist/workflow/SessionPreamble.d.ts +7 -0
  199. package/dist/workflow/SessionPreamble.js +48 -9
  200. package/dist/workflow/SessionPreamble.js.map +1 -1
  201. package/dist/workflow/SurfaceCoverage.d.ts +19 -0
  202. package/dist/workflow/SurfaceCoverage.js +57 -0
  203. package/dist/workflow/SurfaceCoverage.js.map +1 -0
  204. package/dist/workflow/VerificationCommands.d.ts +1 -0
  205. package/dist/workflow/VerificationCommands.js.map +1 -1
  206. package/dist/workflow/VerificationProfile.d.ts +5 -0
  207. package/dist/workflow/VerificationProfile.js +26 -0
  208. package/dist/workflow/VerificationProfile.js.map +1 -1
  209. package/dist/workflow/VerificationSchema.d.ts +3 -0
  210. package/dist/workflow/VerificationSchema.js +6 -0
  211. package/dist/workflow/VerificationSchema.js.map +1 -1
  212. package/dist/workflow/WorkflowEffectiveness.d.ts +97 -0
  213. package/dist/workflow/WorkflowEffectiveness.js +302 -0
  214. package/dist/workflow/WorkflowEffectiveness.js.map +1 -0
  215. package/dist/workflow/WorkflowEffectivenessRenderer.d.ts +2 -0
  216. package/dist/workflow/WorkflowEffectivenessRenderer.js +67 -0
  217. package/dist/workflow/WorkflowEffectivenessRenderer.js.map +1 -0
  218. package/dist/workflow/WorkflowEffectivenessScoring.d.ts +6 -0
  219. package/dist/workflow/WorkflowEffectivenessScoring.js +243 -0
  220. package/dist/workflow/WorkflowEffectivenessScoring.js.map +1 -0
  221. package/dist/workflow/gates/EnhancedGates.js +2 -0
  222. package/dist/workflow/gates/EnhancedGates.js.map +1 -1
  223. package/dist/workflow/gates/GateSystem.d.ts +16 -0
  224. package/dist/workflow/gates/GateSystem.js +208 -41
  225. package/dist/workflow/gates/GateSystem.js.map +1 -1
  226. package/dist/workflow/gates/MetaGovernanceGates.js +269 -8
  227. package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -1
  228. package/dist/workflow/gates/TestIntegrityGate.d.ts +51 -0
  229. package/dist/workflow/gates/TestIntegrityGate.js +175 -0
  230. package/dist/workflow/gates/TestIntegrityGate.js.map +1 -0
  231. package/dist/workflow/types.d.ts +1 -1
  232. package/docs/guides/DEVELOPMENT_WORKFLOW.md +28 -0
  233. package/docs/reference/cli.md +2 -1
  234. package/docs/start/agent-governance-demo.md +1 -1
  235. package/docs/workflow/E2E_EXAMPLE.md +133 -0
  236. package/docs/workflow/README.md +7 -1
  237. package/docs/workflow/TEMPLATE_GUIDE.md +162 -0
  238. package/docs/workflow/templates/github-actions-scale-preflight.yml +4 -1
  239. package/docs/workflow/templates/plan.md +26 -0
  240. package/docs/workflow/templates/spec.md +28 -0
  241. package/package.json +7 -3
  242. package/scripts/workflow/run-vitest.mjs +123 -0
@@ -0,0 +1,540 @@
1
+ /**
2
+ * Documents Page v2 - Markdown rendering, full-text search, doc metadata, favorites.
3
+ */
4
+ ;(() => {
5
+ 'use strict'
6
+
7
+ const { copyText, downloadText, fetchJSON, formatTime, t, $, $$, dom } = window.Dashboard
8
+ const { autoRefreshControl, dataNote, el, emptyState, panel, renderText } = dom
9
+
10
+ let currentDoc = null
11
+ let allDocs = []
12
+ let favorites = new Set(JSON.parse(localStorage.getItem('scale-doc-favorites') || '[]'))
13
+
14
+ async function renderDocuments() {
15
+ const app = $('#app')
16
+ const search = el('input', {
17
+ id: 'doc-search',
18
+ type: 'text',
19
+ className: 'search-box',
20
+ placeholder: t('documents.searchPlaceholder'),
21
+ style: { flex: '1', maxWidth: '400px' },
22
+ })
23
+ const refresh = el('button', { id: 'doc-refresh', className: 'topo-btn', text: t('common.refresh') })
24
+ const copyIndex = el('button', { id: 'doc-copy-index', className: 'topo-btn', text: t('documents.copyIndex') })
25
+ const downloadIndex = el('button', { id: 'doc-download-index', className: 'topo-btn', text: t('documents.downloadIndex') })
26
+ async function loadDocs() {
27
+ allDocs = await fetchJSON('/api/documents') ?? []
28
+ if (!$('#doc-tree')) return
29
+ renderDocTree(allDocs)
30
+ renderDocDataNote(allDocs)
31
+ renderPrototypeGallery(allDocs)
32
+ }
33
+
34
+ app.replaceChildren(
35
+ el('div', { className: 'page-toolbar' }, [
36
+ search,
37
+ refresh,
38
+ copyIndex,
39
+ downloadIndex,
40
+ autoRefreshControl(loadDocs),
41
+ el('div', { className: 'toolbar-spacer' }),
42
+ el('span', { className: 'text-muted text-sm', id: 'doc-count' }),
43
+ ]),
44
+ el('div', { id: 'doc-data-note' }),
45
+ panel(t('documents.prototypeGallery'), 'doc-prototypes'),
46
+ el('div', { className: 'doc-layout' }, [
47
+ el('div', { className: 'doc-tree', id: 'doc-tree' }, [
48
+ el('div', { className: 'loading-placeholder', text: t('common.loading'), style: { height: '200px' } }),
49
+ ]),
50
+ el('div', { className: 'doc-renderer', id: 'doc-renderer' }, [
51
+ emptyStateWithHint(t('documents.selectHint'), `${t('documents.supportedTypes')}. ${t('documents.prototypeHint')}`, '\uD83D\uDCC4'),
52
+ ]),
53
+ ])
54
+ )
55
+
56
+ await loadDocs()
57
+
58
+ search.addEventListener('input', (event) => {
59
+ const query = event.target.value.toLowerCase()
60
+ const docs = query
61
+ ? allDocs.filter(doc => doc.name.toLowerCase().includes(query) || doc.path.toLowerCase().includes(query))
62
+ : allDocs
63
+ renderDocTree(docs)
64
+ })
65
+
66
+ refresh.addEventListener('click', loadDocs)
67
+ copyIndex.addEventListener('click', () => copyText(documentIndexText(allDocs), copyIndex))
68
+ downloadIndex.addEventListener('click', () => {
69
+ downloadText(`scale-documents-${Date.now()}.json`, JSON.stringify(documentIndexPayload(allDocs), null, 2), 'application/json;charset=utf-8')
70
+ })
71
+ }
72
+
73
+ function renderDocDataNote(docs) {
74
+ const node = $('#doc-data-note')
75
+ if (!node) return
76
+ const htmlCount = docs.filter(doc => doc.type === 'html').length
77
+ node.replaceChildren(dataNote([
78
+ { strong: true, text: t('common.snapshot') },
79
+ `${t('common.lastLoaded')}: ${formatTime(Date.now())}`,
80
+ t('documents.dataHint'),
81
+ t('documents.previewable', { count: htmlCount }),
82
+ ]))
83
+ }
84
+
85
+ function renderPrototypeGallery(docs) {
86
+ const container = $('#doc-prototypes')
87
+ if (!container) return
88
+ const prototypes = docs.filter(doc => doc.type === 'html')
89
+ if (prototypes.length === 0) {
90
+ container.replaceChildren(emptyStateWithHint(t('documents.noPrototypes'), t('documents.noPrototypesHint'), '\u25cc'))
91
+ return
92
+ }
93
+ container.replaceChildren(el('div', { className: 'prototype-grid' }, prototypes.map(doc => {
94
+ const preview = el('button', { className: 'topo-btn', text: t('documents.preview') })
95
+ preview.addEventListener('click', () => selectDocument(doc.path, doc.type))
96
+ const open = el('button', { className: 'topo-btn', text: t('common.newTab') })
97
+ open.addEventListener('click', () => window.open(documentUrl(doc.path), '_blank'))
98
+ const copy = el('button', { className: 'topo-btn', text: t('documents.copyLink') })
99
+ copy.addEventListener('click', () => copyText(new URL(documentUrl(doc.path), window.location.origin).href, copy))
100
+ return el('div', { className: 'prototype-card' }, [
101
+ el('div', { className: 'prototype-preview' }, [
102
+ el('iframe', { attrs: { src: documentUrl(doc.path), title: doc.name } }),
103
+ ]),
104
+ el('div', { className: 'prototype-body' }, [
105
+ el('div', { className: 'prototype-title', text: doc.name, title: doc.path }),
106
+ el('div', { className: 'prototype-meta', text: `${doc.path} · ${formatSize(doc.size ?? 0)}` }),
107
+ el('div', { className: 'action-row' }, [preview, open, copy]),
108
+ ]),
109
+ ])
110
+ })))
111
+ }
112
+
113
+ function renderDocTree(docs) {
114
+ const tree = $('#doc-tree')
115
+ if (!tree) return
116
+ const count = $('#doc-count')
117
+ if (count) count.textContent = t('documents.docCount', { count: docs.length })
118
+
119
+ if (docs.length === 0) {
120
+ tree.replaceChildren(el('div', {
121
+ className: 'text-muted text-sm',
122
+ text: t('documents.noDocuments'),
123
+ style: { padding: '12px' },
124
+ }))
125
+ return
126
+ }
127
+
128
+ const children = []
129
+ const favDocs = docs.filter(doc => favorites.has(doc.path))
130
+ if (favDocs.length > 0) {
131
+ children.push(el('div', { className: 'doc-tree-folder', text: `\u2605 ${t('documents.favorites')}` }))
132
+ children.push(...favDocs.map(file => renderDocItem(file)))
133
+ }
134
+
135
+ for (const [folder, files] of sortedFolders(docs)) {
136
+ children.push(el('div', { className: 'doc-tree-folder', text: folder === 'root' ? '/' : folder }))
137
+ children.push(...sortDocs(files).map(file => renderDocItem(file)))
138
+ }
139
+
140
+ tree.replaceChildren(...children)
141
+ wireTree(tree, docs)
142
+ }
143
+
144
+ function sortedFolders(docs) {
145
+ const folders = {}
146
+ for (const doc of docs) {
147
+ const parts = doc.path.split('/')
148
+ const folder = parts.length > 1 ? parts.slice(0, -1).join('/') : 'root'
149
+ if (!folders[folder]) folders[folder] = []
150
+ folders[folder].push(doc)
151
+ }
152
+ return Object.entries(folders).sort(([left], [right]) => {
153
+ const leftHasFav = folders[left].some(doc => favorites.has(doc.path))
154
+ const rightHasFav = folders[right].some(doc => favorites.has(doc.path))
155
+ if (leftHasFav && !rightHasFav) return -1
156
+ if (!leftHasFav && rightHasFav) return 1
157
+ return left.localeCompare(right)
158
+ })
159
+ }
160
+
161
+ function sortDocs(files) {
162
+ return [...files].sort((left, right) => {
163
+ const leftFav = favorites.has(left.path) ? 0 : 1
164
+ const rightFav = favorites.has(right.path) ? 0 : 1
165
+ return leftFav - rightFav || left.name.localeCompare(right.name)
166
+ })
167
+ }
168
+
169
+ function renderDocItem(file) {
170
+ const isFav = favorites.has(file.path)
171
+ return el('div', {
172
+ className: ['doc-tree-item', currentDoc === file.path ? 'active' : ''].filter(Boolean).join(' '),
173
+ dataset: { path: file.path, type: file.type },
174
+ style: { display: 'flex', alignItems: 'center', gap: '6px' },
175
+ }, [
176
+ el('span', { text: getDocIcon(file.type) }),
177
+ el('span', {
178
+ text: file.name,
179
+ style: { flex: '1', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
180
+ }),
181
+ el('span', { className: 'text-muted text-sm', text: formatSize(file.size), style: { flexShrink: '0' } }),
182
+ el('span', {
183
+ className: 'doc-fav-btn',
184
+ text: isFav ? '\u2605' : '\u2606',
185
+ title: isFav ? t('documents.removeFromFavorites') : t('documents.addToFavorites'),
186
+ dataset: { path: file.path },
187
+ style: { cursor: 'pointer', color: isFav ? '#ffaa00' : 'var(--text-2)', fontSize: '12px' },
188
+ }),
189
+ ])
190
+ }
191
+
192
+ function wireTree(tree, docs) {
193
+ $$('.doc-tree-item', tree).forEach((item) => {
194
+ item.addEventListener('click', (event) => {
195
+ if (event.target.classList.contains('doc-fav-btn')) return
196
+ selectDocument(item.dataset.path, item.dataset.type)
197
+ })
198
+ })
199
+ $$('.doc-fav-btn', tree).forEach((button) => {
200
+ button.addEventListener('click', (event) => {
201
+ event.stopPropagation()
202
+ const path = button.dataset.path
203
+ if (favorites.has(path)) favorites.delete(path)
204
+ else favorites.add(path)
205
+ localStorage.setItem('scale-doc-favorites', JSON.stringify([...favorites]))
206
+ renderDocTree(docs)
207
+ })
208
+ })
209
+ }
210
+
211
+ function selectDocument(path, type) {
212
+ const tree = $('#doc-tree')
213
+ if (tree) {
214
+ $$('.doc-tree-item', tree).forEach(node => node.classList.toggle('active', node.dataset.path === path))
215
+ }
216
+ loadDocument(path, type)
217
+ }
218
+
219
+ function getDocIcon(type) {
220
+ switch (type) {
221
+ case 'html': return '\uD83C\uDF10'
222
+ case 'json': return '\uD83D\uDCC4'
223
+ case 'md': return '\uD83D\uDCDD'
224
+ default: return '\uD83D\uDCC4'
225
+ }
226
+ }
227
+
228
+ async function loadDocument(path, type) {
229
+ const renderer = $('#doc-renderer')
230
+ if (!renderer) return
231
+ renderer.replaceChildren(el('div', { className: 'loading-placeholder', text: t('common.loading') }))
232
+
233
+ try {
234
+ const res = await fetch(documentUrl(path))
235
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
236
+ const text = await res.text()
237
+ if (!renderer.isConnected || $('#doc-renderer') !== renderer) return
238
+
239
+ if (type === 'html') renderHtmlDocument(renderer, path, text)
240
+ else if (type === 'md') renderMarkdownDocument(renderer, path, text)
241
+ else if (type === 'json') renderJsonDocument(renderer, path, text)
242
+ else renderPlainDocument(renderer, path, text)
243
+
244
+ currentDoc = path
245
+ } catch (error) {
246
+ renderer.replaceChildren(emptyState(`${t('documents.failedToLoad')}: ${errorMessage(error)}`, '\u26A0'))
247
+ }
248
+ }
249
+
250
+ function renderHtmlDocument(renderer, path, text) {
251
+ const openButton = el('button', {
252
+ className: 'topo-btn doc-open-ext',
253
+ text: `\u2197 ${t('common.newTab')}`,
254
+ title: t('common.newTab'),
255
+ })
256
+ openButton.addEventListener('click', () => window.open(documentUrl(path), '_blank'))
257
+ renderer.replaceChildren(
258
+ docHeader(path, formatSize(text.length), documentActions(path, text, [openButton])),
259
+ el('iframe', {
260
+ attrs: { src: documentUrl(path) },
261
+ style: {
262
+ width: '100%',
263
+ height: 'calc(100% - 50px)',
264
+ minHeight: '550px',
265
+ border: '1px solid var(--border)',
266
+ borderRadius: 'var(--radius)',
267
+ },
268
+ })
269
+ )
270
+ }
271
+
272
+ function renderMarkdownDocument(renderer, path, text) {
273
+ renderer.replaceChildren(
274
+ docHeader(path, formatSize(text.length), documentActions(path, text)),
275
+ renderMarkdown(text)
276
+ )
277
+ }
278
+
279
+ function renderJsonDocument(renderer, path, text) {
280
+ try {
281
+ const json = JSON.parse(text)
282
+ const formatted = JSON.stringify(json, null, 2)
283
+ renderer.replaceChildren(
284
+ docHeader(path, `${Object.keys(json).length} ${t('documents.keys')} \u00b7 ${formatSize(text.length)}`, documentActions(path, formatted)),
285
+ renderJsonPre(formatted)
286
+ )
287
+ } catch (error) {
288
+ observeRecoverableError(error)
289
+ renderPlainDocument(renderer, path, text)
290
+ }
291
+ }
292
+
293
+ function renderPlainDocument(renderer, path, text) {
294
+ renderer.replaceChildren(
295
+ docHeader(path, formatSize(text.length), documentActions(path, text)),
296
+ el('pre', {
297
+ text,
298
+ style: { fontSize: '13px', whiteSpace: 'pre-wrap', color: 'var(--text-1)', lineHeight: '1.5' },
299
+ })
300
+ )
301
+ }
302
+
303
+ function docHeader(path, meta, actions = []) {
304
+ return el('div', {
305
+ style: { marginBottom: '12px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
306
+ }, [
307
+ el('div', {}, [
308
+ el('span', { text: fileName(path), style: { fontWeight: '600' } }),
309
+ el('span', { className: 'text-muted text-sm', text: meta, style: { marginLeft: '8px' } }),
310
+ ]),
311
+ actions.length ? el('div', { style: { display: 'flex', gap: '8px' } }, actions) : null,
312
+ ])
313
+ }
314
+
315
+ function documentActions(path, text, extraActions = []) {
316
+ const copyButton = el('button', { className: 'topo-btn doc-copy', text: t('common.copy'), title: t('common.copy') })
317
+ copyButton.addEventListener('click', async () => {
318
+ try {
319
+ await copyText(text, copyButton)
320
+ } catch (error) {
321
+ observeRecoverableError(error)
322
+ }
323
+ })
324
+ const downloadButton = el('button', { className: 'topo-btn doc-download', text: t('common.download'), title: t('common.download') })
325
+ downloadButton.addEventListener('click', () => downloadText(fileName(path), text, contentTypeForPath(path)))
326
+ return [copyButton, downloadButton, ...extraActions]
327
+ }
328
+
329
+ function documentIndexPayload(docs) {
330
+ return {
331
+ exportedAt: new Date().toISOString(),
332
+ count: docs.length,
333
+ previewableHtml: docs.filter(doc => doc.type === 'html').length,
334
+ documents: docs.map(doc => ({
335
+ name: doc.name,
336
+ path: doc.path,
337
+ type: doc.type,
338
+ size: doc.size,
339
+ })),
340
+ }
341
+ }
342
+
343
+ function documentIndexText(docs) {
344
+ return documentIndexPayload(docs).documents
345
+ .map(doc => `${doc.type}\t${formatSize(doc.size ?? 0)}\t${doc.path}`)
346
+ .join('\n')
347
+ }
348
+
349
+ function contentTypeForPath(path) {
350
+ const lower = String(path).toLowerCase()
351
+ if (lower.endsWith('.json')) return 'application/json;charset=utf-8'
352
+ if (lower.endsWith('.html')) return 'text/html;charset=utf-8'
353
+ if (lower.endsWith('.md')) return 'text/markdown;charset=utf-8'
354
+ return 'text/plain;charset=utf-8'
355
+ }
356
+
357
+ function renderMarkdown(markdown) {
358
+ const root = el('div', { className: 'markdown-body', style: { fontSize: '14px', lineHeight: '1.7', color: 'var(--text-0)' } })
359
+ const lines = markdown.replace(/\r\n/g, '\n').split('\n')
360
+ let codeLines = []
361
+ let codeLang = ''
362
+ let paragraph = []
363
+ let list = null
364
+
365
+ const flushParagraph = () => {
366
+ if (paragraph.length === 0) return
367
+ const p = el('p', { style: { margin: '8px 0' } })
368
+ appendInline(p, paragraph.join(' '))
369
+ root.appendChild(p)
370
+ paragraph = []
371
+ }
372
+ const flushList = () => {
373
+ if (!list) return
374
+ root.appendChild(list)
375
+ list = null
376
+ }
377
+ const flushCode = () => {
378
+ const code = el('code', { text: codeLines.join('\n') })
379
+ const pre = el('pre', {
380
+ attrs: codeLang ? { 'data-lang': codeLang } : {},
381
+ style: { background: 'var(--bg-2)', padding: '12px', borderRadius: 'var(--radius)', fontSize: '13px', lineHeight: '1.5', overflow: 'auto', margin: '12px 0' },
382
+ }, [code])
383
+ root.appendChild(pre)
384
+ codeLines = []
385
+ codeLang = ''
386
+ }
387
+
388
+ for (const line of lines) {
389
+ const fence = line.match(/^```(\w*)\s*$/)
390
+ if (fence && codeLang === '' && codeLines.length === 0) {
391
+ flushParagraph()
392
+ flushList()
393
+ codeLang = fence[1] || 'plain'
394
+ codeLines = ['__OPEN__']
395
+ continue
396
+ }
397
+ if (fence && codeLines.length > 0) {
398
+ codeLines.shift()
399
+ flushCode()
400
+ continue
401
+ }
402
+ if (codeLines.length > 0) {
403
+ codeLines.push(line)
404
+ continue
405
+ }
406
+
407
+ if (!line.trim()) {
408
+ flushParagraph()
409
+ flushList()
410
+ continue
411
+ }
412
+ const heading = line.match(/^(#{1,3})\s+(.+)$/)
413
+ if (heading) {
414
+ flushParagraph()
415
+ flushList()
416
+ root.appendChild(headingNode(heading[1].length, heading[2]))
417
+ continue
418
+ }
419
+ if (/^---+$/.test(line.trim())) {
420
+ flushParagraph()
421
+ flushList()
422
+ root.appendChild(el('hr', { style: { border: 'none', borderTop: '1px solid var(--border)', margin: '20px 0' } }))
423
+ continue
424
+ }
425
+ const item = line.match(/^[-*]\s+(.+)$/)
426
+ if (item) {
427
+ flushParagraph()
428
+ if (!list) list = el('ul', { style: { margin: '8px 0 8px 20px' } })
429
+ const li = el('li', { style: { padding: '2px 0' } })
430
+ appendInline(li, item[1])
431
+ list.appendChild(li)
432
+ continue
433
+ }
434
+ paragraph.push(line.trim())
435
+ }
436
+
437
+ if (codeLines.length > 0) {
438
+ codeLines.shift()
439
+ flushCode()
440
+ }
441
+ flushParagraph()
442
+ flushList()
443
+ return root
444
+ }
445
+
446
+ function headingNode(level, text) {
447
+ const sizes = { 1: '22px', 2: '18px', 3: '16px' }
448
+ const margins = { 1: '28px 0 12px', 2: '24px 0 10px', 3: '20px 0 8px' }
449
+ const node = el(`h${level}`, { style: { margin: margins[level], fontSize: sizes[level], color: 'var(--text-0)' } })
450
+ appendInline(node, text)
451
+ return node
452
+ }
453
+
454
+ function appendInline(parent, text) {
455
+ const pattern = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\)|\*[^*]+\*)/g
456
+ let lastIndex = 0
457
+ for (const match of text.matchAll(pattern)) {
458
+ if (match.index > lastIndex) parent.appendChild(document.createTextNode(text.slice(lastIndex, match.index)))
459
+ parent.appendChild(inlineNode(match[0]))
460
+ lastIndex = match.index + match[0].length
461
+ }
462
+ if (lastIndex < text.length) parent.appendChild(document.createTextNode(text.slice(lastIndex)))
463
+ }
464
+
465
+ function inlineNode(token) {
466
+ if (token.startsWith('**')) return el('strong', { text: token.slice(2, -2) })
467
+ if (token.startsWith('`')) return el('code', { text: token.slice(1, -1), style: { background: 'var(--bg-2)', padding: '2px 6px', borderRadius: '3px', fontSize: '12px' } })
468
+ if (token.startsWith('*')) return el('em', { text: token.slice(1, -1) })
469
+ const link = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/)
470
+ if (!link) return document.createTextNode(token)
471
+ return el('a', { text: link[1], attrs: { href: safeHref(link[2]), target: '_blank', rel: 'noopener noreferrer' } })
472
+ }
473
+
474
+ function renderJsonPre(formatted) {
475
+ const pre = el('pre', {
476
+ style: { fontSize: '13px', lineHeight: '1.5', whiteSpace: 'pre-wrap', color: 'var(--text-1)', background: 'var(--bg-2)', padding: '16px', borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 'calc(100% - 50px)' },
477
+ })
478
+ const tokenPattern = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b|\btrue\b|\bfalse\b|\bnull\b/gi
479
+ let lastIndex = 0
480
+ for (const match of formatted.matchAll(tokenPattern)) {
481
+ if (match.index > lastIndex) pre.appendChild(document.createTextNode(formatted.slice(lastIndex, match.index)))
482
+ pre.appendChild(jsonToken(match[0], formatted.slice(match.index + match[0].length)))
483
+ lastIndex = match.index + match[0].length
484
+ }
485
+ if (lastIndex < formatted.length) pre.appendChild(document.createTextNode(formatted.slice(lastIndex)))
486
+ return pre
487
+ }
488
+
489
+ function jsonToken(token, tail) {
490
+ const color = token.startsWith('"')
491
+ ? (tail.trimStart().startsWith(':') ? '#5588ff' : '#00dc82')
492
+ : (/^(true|false|null)$/i.test(token) ? '#aa88ff' : '#ffaa00')
493
+ return el('span', { text: token, style: { color } })
494
+ }
495
+
496
+ function emptyStateWithHint(message, hint, icon) {
497
+ const state = emptyState(message, icon)
498
+ state.appendChild(el('p', { className: 'text-muted text-sm', text: hint, style: { marginTop: '8px' } }))
499
+ return state
500
+ }
501
+
502
+ function documentUrl(path) {
503
+ return `/api/documents/${String(path).split('/').map(segment => encodeURIComponent(segment)).join('/')}`
504
+ }
505
+
506
+ function safeHref(value) {
507
+ const href = String(value || '')
508
+ if (/^(https?:|mailto:|#|\/)/i.test(href)) return href
509
+ return '#'
510
+ }
511
+
512
+ function fileName(path) {
513
+ return String(path).split('/').pop() || path
514
+ }
515
+
516
+ function formatSize(bytes) {
517
+ if (bytes < 1024) return bytes + 'B'
518
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB'
519
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB'
520
+ }
521
+
522
+ function errorMessage(error) {
523
+ return error instanceof Error ? error.message : String(error || 'Unknown error')
524
+ }
525
+
526
+ function observeRecoverableError(error) {
527
+ void error
528
+ }
529
+
530
+ window.addEventListener('search', (event) => {
531
+ const query = event.detail.toLowerCase()
532
+ $$('.doc-tree-item').forEach((item) => {
533
+ const name = item.textContent.toLowerCase()
534
+ item.style.display = name.includes(query) ? '' : 'none'
535
+ })
536
+ })
537
+
538
+ window.DashboardPages = window.DashboardPages || {}
539
+ window.DashboardPages.documents = renderDocuments
540
+ })()