aios-core 4.2.15 → 4.4.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 (329) hide show
  1. package/.aios-core/cli/commands/validate/index.js +1 -1
  2. package/.aios-core/core/code-intel/code-intel-client.js +19 -5
  3. package/.aios-core/core/code-intel/helpers/creation-helper.js +183 -0
  4. package/.aios-core/core/code-intel/helpers/devops-helper.js +166 -0
  5. package/.aios-core/core/code-intel/helpers/planning-helper.js +248 -0
  6. package/.aios-core/core/code-intel/helpers/qa-helper.js +187 -0
  7. package/.aios-core/core/code-intel/helpers/story-helper.js +146 -0
  8. package/.aios-core/core/code-intel/hook-runtime.js +186 -0
  9. package/.aios-core/core/code-intel/index.js +2 -0
  10. package/.aios-core/core/code-intel/providers/code-graph-provider.js +8 -0
  11. package/.aios-core/core/code-intel/providers/provider-interface.js +9 -0
  12. package/.aios-core/core/code-intel/providers/registry-provider.js +515 -0
  13. package/.aios-core/core/config/schemas/framework-config.schema.json +155 -7
  14. package/.aios-core/core/config/schemas/project-config.schema.json +329 -15
  15. package/.aios-core/core/config/template-overrides.js +84 -0
  16. package/.aios-core/core/docs/troubleshooting-guide.md +1 -1
  17. package/.aios-core/core/doctor/checks/agent-memory.js +63 -0
  18. package/.aios-core/core/doctor/checks/claude-md.js +56 -0
  19. package/.aios-core/core/doctor/checks/code-intel.js +131 -0
  20. package/.aios-core/core/doctor/checks/commands-count.js +81 -0
  21. package/.aios-core/core/doctor/checks/core-config.js +53 -0
  22. package/.aios-core/core/doctor/checks/entity-registry.js +53 -0
  23. package/.aios-core/core/doctor/checks/git-hooks.js +50 -0
  24. package/.aios-core/core/doctor/checks/graph-dashboard.js +48 -0
  25. package/.aios-core/core/doctor/checks/hooks-claude-count.js +118 -0
  26. package/.aios-core/core/doctor/checks/ide-sync.js +85 -0
  27. package/.aios-core/core/doctor/checks/index.js +46 -0
  28. package/.aios-core/core/doctor/checks/node-version.js +33 -0
  29. package/.aios-core/core/doctor/checks/npm-packages.js +35 -0
  30. package/.aios-core/core/doctor/checks/rules-files.js +61 -0
  31. package/.aios-core/core/doctor/checks/settings-json.js +121 -0
  32. package/.aios-core/core/doctor/checks/skills-count.js +72 -0
  33. package/.aios-core/core/doctor/fix-handler.js +165 -0
  34. package/.aios-core/core/doctor/formatters/json.js +14 -0
  35. package/.aios-core/core/doctor/formatters/text.js +59 -0
  36. package/.aios-core/core/doctor/index.js +94 -0
  37. package/.aios-core/core/graph-dashboard/cli.js +361 -0
  38. package/.aios-core/core/graph-dashboard/data-sources/code-intel-source.js +234 -0
  39. package/.aios-core/core/graph-dashboard/data-sources/metrics-source.js +95 -0
  40. package/.aios-core/core/graph-dashboard/data-sources/registry-source.js +106 -0
  41. package/.aios-core/core/graph-dashboard/formatters/dot-formatter.js +45 -0
  42. package/.aios-core/core/graph-dashboard/formatters/html-formatter.js +1437 -0
  43. package/.aios-core/core/graph-dashboard/formatters/json-formatter.js +13 -0
  44. package/.aios-core/core/graph-dashboard/formatters/mermaid-formatter.js +59 -0
  45. package/.aios-core/core/graph-dashboard/index.js +21 -0
  46. package/.aios-core/core/graph-dashboard/renderers/stats-renderer.js +217 -0
  47. package/.aios-core/core/graph-dashboard/renderers/status-renderer.js +125 -0
  48. package/.aios-core/core/graph-dashboard/renderers/tree-renderer.js +119 -0
  49. package/.aios-core/core/health-check/base-check.js +1 -1
  50. package/.aios-core/core/health-check/check-registry.js +1 -1
  51. package/.aios-core/core/health-check/checks/deployment/build-config.js +1 -1
  52. package/.aios-core/core/health-check/checks/deployment/ci-config.js +1 -1
  53. package/.aios-core/core/health-check/checks/deployment/deployment-readiness.js +1 -1
  54. package/.aios-core/core/health-check/checks/deployment/docker-config.js +1 -1
  55. package/.aios-core/core/health-check/checks/deployment/env-file.js +1 -1
  56. package/.aios-core/core/health-check/checks/deployment/index.js +1 -1
  57. package/.aios-core/core/health-check/checks/index.js +1 -1
  58. package/.aios-core/core/health-check/checks/local/disk-space.js +1 -1
  59. package/.aios-core/core/health-check/checks/local/environment-vars.js +1 -1
  60. package/.aios-core/core/health-check/checks/local/git-install.js +1 -1
  61. package/.aios-core/core/health-check/checks/local/ide-detection.js +1 -1
  62. package/.aios-core/core/health-check/checks/local/index.js +1 -1
  63. package/.aios-core/core/health-check/checks/local/memory.js +1 -1
  64. package/.aios-core/core/health-check/checks/local/network.js +1 -1
  65. package/.aios-core/core/health-check/checks/local/npm-install.js +1 -1
  66. package/.aios-core/core/health-check/checks/local/shell-environment.js +1 -1
  67. package/.aios-core/core/health-check/checks/project/agent-config.js +1 -1
  68. package/.aios-core/core/health-check/checks/project/aios-directory.js +1 -1
  69. package/.aios-core/core/health-check/checks/project/dependencies.js +1 -1
  70. package/.aios-core/core/health-check/checks/project/framework-config.js +1 -1
  71. package/.aios-core/core/health-check/checks/project/index.js +1 -1
  72. package/.aios-core/core/health-check/checks/project/node-version.js +1 -1
  73. package/.aios-core/core/health-check/checks/project/package-json.js +1 -1
  74. package/.aios-core/core/health-check/checks/project/task-definitions.js +1 -1
  75. package/.aios-core/core/health-check/checks/project/workflow-dependencies.js +1 -1
  76. package/.aios-core/core/health-check/checks/repository/branch-protection.js +1 -1
  77. package/.aios-core/core/health-check/checks/repository/commit-history.js +1 -1
  78. package/.aios-core/core/health-check/checks/repository/conflicts.js +1 -1
  79. package/.aios-core/core/health-check/checks/repository/git-repo.js +1 -1
  80. package/.aios-core/core/health-check/checks/repository/git-status.js +1 -1
  81. package/.aios-core/core/health-check/checks/repository/gitignore.js +1 -1
  82. package/.aios-core/core/health-check/checks/repository/index.js +1 -1
  83. package/.aios-core/core/health-check/checks/repository/large-files.js +1 -1
  84. package/.aios-core/core/health-check/checks/repository/lockfile-integrity.js +1 -1
  85. package/.aios-core/core/health-check/checks/services/api-endpoints.js +1 -1
  86. package/.aios-core/core/health-check/checks/services/claude-code.js +1 -1
  87. package/.aios-core/core/health-check/checks/services/gemini-cli.js +1 -1
  88. package/.aios-core/core/health-check/checks/services/github-cli.js +1 -1
  89. package/.aios-core/core/health-check/checks/services/index.js +1 -1
  90. package/.aios-core/core/health-check/checks/services/mcp-integration.js +1 -1
  91. package/.aios-core/core/health-check/engine.js +1 -1
  92. package/.aios-core/core/health-check/healers/backup-manager.js +1 -1
  93. package/.aios-core/core/health-check/healers/index.js +1 -1
  94. package/.aios-core/core/health-check/index.js +9 -2
  95. package/.aios-core/core/health-check/reporters/console.js +1 -1
  96. package/.aios-core/core/health-check/reporters/index.js +1 -1
  97. package/.aios-core/core/health-check/reporters/json.js +1 -1
  98. package/.aios-core/core/health-check/reporters/markdown.js +1 -1
  99. package/.aios-core/core/ids/layer-classifier.js +65 -0
  100. package/.aios-core/core/ids/registry-updater.js +49 -0
  101. package/.aios-core/core/index.esm.js +1 -1
  102. package/.aios-core/core/index.js +1 -1
  103. package/.aios-core/core/session/context-detector.js +2 -7
  104. package/.aios-core/core/synapse/context/context-tracker.js +9 -1
  105. package/.aios-core/core/synapse/engine.js +33 -13
  106. package/.aios-core/core/synapse/memory/memory-bridge.js +17 -43
  107. package/.aios-core/core/synapse/memory/synapse-memory-provider.js +201 -0
  108. package/.aios-core/core/synapse/runtime/hook-runtime.js +40 -2
  109. package/.aios-core/core/synapse/session/session-manager.js +3 -2
  110. package/.aios-core/core/synapse/utils/atomic-write.js +79 -0
  111. package/.aios-core/core-config.yaml +34 -1
  112. package/.aios-core/data/aios-kb.md +2 -2
  113. package/.aios-core/data/capability-detection.js +290 -0
  114. package/.aios-core/data/entity-registry.yaml +10450 -2129
  115. package/.aios-core/data/mcp-discipline.js +166 -0
  116. package/.aios-core/data/mcp-tool-examples.yaml +215 -0
  117. package/.aios-core/data/tok2-validation.js +168 -0
  118. package/.aios-core/data/tok3-token-comparison.js +123 -0
  119. package/.aios-core/data/tool-registry.yaml +648 -0
  120. package/.aios-core/data/tool-search-validation.js +174 -0
  121. package/.aios-core/data/workflow-chains.yaml +156 -0
  122. package/.aios-core/development/agents/aios-master.md +17 -10
  123. package/.aios-core/development/agents/analyst/MEMORY.md +33 -0
  124. package/.aios-core/development/agents/analyst.md +17 -10
  125. package/.aios-core/development/agents/architect/MEMORY.md +39 -0
  126. package/.aios-core/development/agents/architect.md +17 -10
  127. package/.aios-core/development/agents/data-engineer/MEMORY.md +32 -0
  128. package/.aios-core/development/agents/data-engineer.md +17 -10
  129. package/.aios-core/development/agents/dev/MEMORY.md +46 -0
  130. package/.aios-core/development/agents/dev.md +18 -11
  131. package/.aios-core/development/agents/devops/MEMORY.md +39 -0
  132. package/.aios-core/development/agents/devops.md +44 -10
  133. package/.aios-core/development/agents/pm/MEMORY.md +38 -0
  134. package/.aios-core/development/agents/pm.md +17 -10
  135. package/.aios-core/development/agents/po/MEMORY.md +45 -0
  136. package/.aios-core/development/agents/po.md +17 -10
  137. package/.aios-core/development/agents/qa/MEMORY.md +42 -0
  138. package/.aios-core/development/agents/qa.md +18 -11
  139. package/.aios-core/development/agents/sm/MEMORY.md +31 -0
  140. package/.aios-core/development/agents/sm.md +17 -10
  141. package/.aios-core/development/agents/squad-creator.md +18 -9
  142. package/.aios-core/development/agents/ux/MEMORY.md +31 -0
  143. package/.aios-core/development/agents/ux-design-expert.md +16 -9
  144. package/.aios-core/development/checklists/issue-triage-checklist.md +35 -0
  145. package/.aios-core/development/checklists/memory-audit-checklist.md +53 -0
  146. package/.aios-core/development/scripts/issue-triage.js +171 -0
  147. package/.aios-core/development/scripts/populate-entity-registry.js +412 -19
  148. package/.aios-core/development/scripts/unified-activation-pipeline.js +31 -10
  149. package/.aios-core/development/tasks/analyze-project-structure.md +48 -0
  150. package/.aios-core/development/tasks/apply-qa-fixes.md +7 -0
  151. package/.aios-core/development/tasks/architect-analyze-impact.md +8 -1
  152. package/.aios-core/development/tasks/brownfield-create-epic.md +41 -0
  153. package/.aios-core/development/tasks/brownfield-create-story.md +7 -0
  154. package/.aios-core/development/tasks/build-autonomous.md +7 -0
  155. package/.aios-core/development/tasks/create-deep-research-prompt.md +7 -0
  156. package/.aios-core/development/tasks/create-doc.md +44 -0
  157. package/.aios-core/development/tasks/create-next-story.md +17 -0
  158. package/.aios-core/development/tasks/create-suite.md +7 -0
  159. package/.aios-core/development/tasks/dev-develop-story.md +9 -1
  160. package/.aios-core/development/tasks/execute-checklist.md +7 -0
  161. package/.aios-core/development/tasks/github-devops-github-pr-automation.md +56 -0
  162. package/.aios-core/development/tasks/github-devops-pre-push-quality-gate.md +70 -0
  163. package/.aios-core/development/tasks/github-issue-triage.md +118 -0
  164. package/.aios-core/development/tasks/health-check.yaml +206 -171
  165. package/.aios-core/development/tasks/kb-mode-interaction.md +3 -3
  166. package/.aios-core/development/tasks/plan-create-context.md +47 -1
  167. package/.aios-core/development/tasks/plan-create-implementation.md +55 -0
  168. package/.aios-core/development/tasks/po-close-story.md +7 -0
  169. package/.aios-core/development/tasks/pr-automation.md +5 -5
  170. package/.aios-core/development/tasks/qa-create-fix-request.md +7 -0
  171. package/.aios-core/development/tasks/qa-fix-issues.md +7 -0
  172. package/.aios-core/development/tasks/qa-gate.md +56 -0
  173. package/.aios-core/development/tasks/qa-review-story.md +32 -1
  174. package/.aios-core/development/tasks/release-management.md +7 -0
  175. package/.aios-core/development/tasks/resolve-github-issue.md +608 -0
  176. package/.aios-core/development/tasks/review-contributor-pr.md +152 -0
  177. package/.aios-core/development/tasks/setup-llm-routing.md +1 -1
  178. package/.aios-core/development/tasks/spec-critique.md +8 -0
  179. package/.aios-core/development/tasks/spec-gather-requirements.md +7 -0
  180. package/.aios-core/development/tasks/spec-research-dependencies.md +4 -0
  181. package/.aios-core/development/tasks/spec-write-spec.md +5 -0
  182. package/.aios-core/development/tasks/triage-github-issues.md +356 -0
  183. package/.aios-core/development/tasks/validate-agents.md +4 -0
  184. package/.aios-core/development/tasks/validate-next-story.md +17 -0
  185. package/.aios-core/development/templates/agent-handoff-tmpl.yaml +48 -0
  186. package/.aios-core/development/templates/code-intel-integration-pattern.md +199 -0
  187. package/.aios-core/development/templates/ptc-entity-validation.md +113 -0
  188. package/.aios-core/development/templates/ptc-qa-gate.md +100 -0
  189. package/.aios-core/development/templates/ptc-research-aggregation.md +94 -0
  190. package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
  191. package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
  192. package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
  193. package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
  194. package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
  195. package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
  196. package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
  197. package/.aios-core/development/templates/squad/agent-template.md +11 -0
  198. package/.aios-core/development/templates/squad/task-template.md +21 -0
  199. package/.aios-core/development/templates/squad-template/LICENSE +21 -21
  200. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +1 -1
  201. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +1 -1
  202. package/.aios-core/framework-config.yaml +8 -0
  203. package/.aios-core/index.esm.js +1 -1
  204. package/.aios-core/index.js +1 -1
  205. package/.aios-core/infrastructure/integrations/ai-providers/index.js +1 -1
  206. package/.aios-core/infrastructure/schemas/task-v3-schema.json +6 -0
  207. package/.aios-core/infrastructure/scripts/collect-tool-usage.js +311 -0
  208. package/.aios-core/infrastructure/scripts/generate-optimization-report.js +497 -0
  209. package/.aios-core/infrastructure/scripts/generate-settings-json.js +300 -0
  210. package/.aios-core/infrastructure/scripts/git-config-detector.js +65 -9
  211. package/.aios-core/infrastructure/scripts/ide-sync/index.js +3 -1
  212. package/.aios-core/infrastructure/scripts/ide-sync/transformers/github-copilot.js +184 -0
  213. package/.aios-core/infrastructure/scripts/repository-detector.js +3 -3
  214. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +182 -182
  215. package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
  216. package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
  217. package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
  218. package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
  219. package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
  220. package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
  221. package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
  222. package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
  223. package/.aios-core/install-manifest.yaml +613 -305
  224. package/.aios-core/lib/build.json +1 -0
  225. package/.aios-core/local-config.yaml.template +71 -71
  226. package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
  227. package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
  228. package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
  229. package/.aios-core/monitor/hooks/notification.py +29 -29
  230. package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
  231. package/.aios-core/monitor/hooks/pre_compact.py +29 -29
  232. package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
  233. package/.aios-core/monitor/hooks/stop.py +29 -29
  234. package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
  235. package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
  236. package/.aios-core/product/templates/adr.hbs +125 -125
  237. package/.aios-core/product/templates/dbdr.hbs +241 -241
  238. package/.aios-core/product/templates/epic.hbs +212 -212
  239. package/.aios-core/product/templates/ide-rules/claude-rules.md +125 -0
  240. package/.aios-core/product/templates/pmdr.hbs +186 -186
  241. package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
  242. package/.aios-core/product/templates/prd.hbs +201 -201
  243. package/.aios-core/product/templates/story.hbs +263 -263
  244. package/.aios-core/product/templates/task.hbs +170 -170
  245. package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
  246. package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
  247. package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
  248. package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
  249. package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
  250. package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
  251. package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
  252. package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
  253. package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
  254. package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
  255. package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
  256. package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
  257. package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
  258. package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
  259. package/.aios-core/product/templates/tmpl-view.sql +177 -177
  260. package/.aios-core/scripts/pm.sh +0 -0
  261. package/.aios-core/user-guide.md +15 -15
  262. package/.aios-core/utils/filters/constants.js +10 -0
  263. package/.aios-core/utils/filters/content-filter.js +223 -0
  264. package/.aios-core/utils/filters/field-filter.js +126 -0
  265. package/.aios-core/utils/filters/index.js +180 -0
  266. package/.aios-core/utils/filters/schema-filter.js +157 -0
  267. package/.claude/CLAUDE.md +62 -0
  268. package/.claude/hooks/enforce-architecture-first.py +196 -196
  269. package/.claude/hooks/enforce-git-push-authority.sh +33 -0
  270. package/.claude/hooks/mind-clone-governance.py +192 -192
  271. package/.claude/hooks/read-protection.py +151 -151
  272. package/.claude/hooks/slug-validation.py +176 -176
  273. package/.claude/hooks/sql-governance.py +182 -182
  274. package/.claude/hooks/synapse-engine.cjs +28 -5
  275. package/.claude/hooks/write-path-validation.py +194 -194
  276. package/.claude/rules/agent-authority.md +105 -0
  277. package/.claude/rules/agent-handoff.md +97 -0
  278. package/.claude/rules/agent-memory-imports.md +15 -0
  279. package/.claude/rules/coderabbit-integration.md +101 -0
  280. package/.claude/rules/ids-principles.md +119 -0
  281. package/.claude/rules/story-lifecycle.md +145 -0
  282. package/.claude/rules/tool-examples.md +64 -0
  283. package/.claude/rules/tool-response-filtering.md +57 -0
  284. package/.claude/rules/workflow-execution.md +150 -0
  285. package/LICENSE +33 -33
  286. package/bin/aios-graph.js +9 -0
  287. package/bin/aios-init.js +2 -2
  288. package/bin/aios-minimal.js +0 -0
  289. package/bin/aios.js +17 -221
  290. package/bin/utils/detect-fsmonitor.js +70 -0
  291. package/bin/utils/framework-guard.js +238 -0
  292. package/bin/utils/validate-publish.js +108 -0
  293. package/package.json +6 -3
  294. package/packages/aios-install/bin/aios-install.js +0 -0
  295. package/packages/aios-install/bin/edmcp.js +0 -0
  296. package/packages/aios-pro-cli/bin/aios-pro.js +2 -0
  297. package/packages/installer/src/config/templates/core-config-template.js +25 -0
  298. package/packages/installer/src/installer/brownfield-upgrader.js +68 -5
  299. package/packages/installer/src/merger/index.js +3 -0
  300. package/packages/installer/src/merger/strategies/index.js +6 -0
  301. package/packages/installer/src/merger/strategies/yaml-merger.js +181 -0
  302. package/packages/installer/src/updater/index.js +4 -4
  303. package/packages/installer/src/wizard/i18n.js +321 -3
  304. package/packages/installer/src/wizard/ide-config-generator.js +173 -25
  305. package/packages/installer/src/wizard/index.js +119 -1
  306. package/packages/installer/src/wizard/pro-setup.js +137 -121
  307. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +271 -0
  308. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +192 -0
  309. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +610 -0
  310. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +134 -0
  311. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +186 -0
  312. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +309 -0
  313. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +230 -0
  314. package/packages/installer/tests/unit/merger/strategies.test.js +2 -2
  315. package/packages/installer/tests/unit/merger/yaml-merger.test.js +327 -0
  316. package/scripts/check-markdown-links.py +352 -352
  317. package/scripts/dashboard-parallel-dev.sh +0 -0
  318. package/scripts/dashboard-parallel-phase3.sh +0 -0
  319. package/scripts/dashboard-parallel-phase4.sh +0 -0
  320. package/scripts/install-monitor-hooks.sh +0 -0
  321. package/scripts/package-synapse.js +2 -1
  322. package/pro/README.md +0 -66
  323. package/pro/license/degradation.js +0 -220
  324. package/pro/license/errors.js +0 -450
  325. package/pro/license/feature-gate.js +0 -354
  326. package/pro/license/index.js +0 -181
  327. package/pro/license/license-api.js +0 -651
  328. package/pro/license/license-cache.js +0 -523
  329. package/pro/license/license-crypto.js +0 -303
@@ -0,0 +1,1437 @@
1
+ 'use strict';
2
+
3
+ // Design tokens aligned with dashboard globals.css (Design Tokens v2.0)
4
+ // Source of truth: pro-design-migration/apps/dashboard/src/app/globals.css
5
+ const THEME = {
6
+ bg: {
7
+ base: '#000000', // --bg-base
8
+ surface: '#0A0A0A', // --bg-surface
9
+ overlay: 'rgba(10,10,10,0.9)', // --bg-surface + opacity
10
+ },
11
+ text: {
12
+ primary: '#E8E8DF', // --text-primary
13
+ secondary: '#B8B8AC', // --text-secondary
14
+ tertiary: '#8A8A7F', // --text-tertiary
15
+ muted: '#6B6B63', // --text-muted
16
+ },
17
+ status: {
18
+ success: '#4ADE80', // --status-success
19
+ warning: '#FBBF24', // --status-warning
20
+ error: '#F87171', // --status-error
21
+ info: '#60A5FA', // --status-info
22
+ },
23
+ border: {
24
+ default: 'rgba(255,255,255,0.06)', // --border
25
+ subtle: 'rgba(255,255,255,0.04)', // --border-subtle (card-refined)
26
+ highlight: 'rgba(201,178,152,0.25)', // --border-gold
27
+ gold: 'rgba(201,178,152,0.25)', // --border-gold (alias for highlight)
28
+ goldStrong: 'rgba(201,178,152,0.5)', // --border-gold-strong (selection)
29
+ },
30
+ accent: {
31
+ gold: '#C9B298', // --accent-gold
32
+ },
33
+ agent: {
34
+ dev: '#22c55e', // --agent-dev
35
+ sm: '#f472b6', // --agent-sm
36
+ po: '#f97316', // --agent-po
37
+ qa: '#eab308', // --agent-qa
38
+ architect: '#8b5cf6', // --agent-architect
39
+ devops: '#ec4899', // --agent-devops
40
+ analyst: '#06b6d4', // --agent-analyst
41
+ },
42
+ tooltip: {
43
+ bg: '#0A0A0A', // = bg.surface (card-refined)
44
+ border: 'rgba(255,255,255,0.04)', // = border.subtle
45
+ shadow: '0 4px 12px rgba(0,0,0,0.5)', // --tooltip-shadow
46
+ },
47
+ radius: {
48
+ md: '4px', // --radius-md
49
+ },
50
+ controls: {
51
+ sliderThumb: '#C9B298', // = accent.gold
52
+ sliderTrack: 'rgba(255,255,255,0.1)', // slider track background
53
+ },
54
+ };
55
+
56
+ const CATEGORY_COLORS = {
57
+ agents: { color: THEME.agent.dev, shape: 'dot' },
58
+ tasks: { color: THEME.status.info, shape: 'box' },
59
+ templates: { color: THEME.status.warning, shape: 'diamond' },
60
+ checklists: { color: THEME.agent.po, shape: 'triangle' },
61
+ workflows: { color: THEME.agent.sm, shape: 'star' },
62
+ 'scripts/task': { color: THEME.status.success, shape: 'box' },
63
+ 'scripts/engine': { color: THEME.agent.devops, shape: 'box' },
64
+ 'scripts/infra': { color: THEME.agent.analyst, shape: 'box' },
65
+ utils: { color: THEME.agent.analyst, shape: 'ellipse' },
66
+ data: { color: THEME.agent.qa, shape: 'database' },
67
+ tools: { color: THEME.agent.architect, shape: 'hexagon' },
68
+ };
69
+
70
+ const DEFAULT_COLOR = { color: THEME.text.tertiary, shape: 'box' };
71
+
72
+ const LIFECYCLE_STYLES = {
73
+ production: { opacity: 1.0, borderDashes: false, colorOverride: null },
74
+ experimental: { opacity: 0.8, borderDashes: [5, 5], colorOverride: null },
75
+ deprecated: { opacity: 0.5, borderDashes: false, colorOverride: THEME.text.tertiary },
76
+ orphan: { opacity: 0.3, borderDashes: [2, 4], colorOverride: THEME.text.muted },
77
+ };
78
+
79
+ /**
80
+ * Sanitize a string for safe embedding inside HTML/JS.
81
+ * Prevents XSS by escaping HTML entities and JS-breaking chars.
82
+ * @param {string} str - Raw string
83
+ * @returns {string} Sanitized string
84
+ */
85
+ function _sanitize(str) {
86
+ return String(str)
87
+ .replace(/&/g, '&')
88
+ .replace(/</g, '&lt;')
89
+ .replace(/>/g, '&gt;')
90
+ .replace(/"/g, '&quot;')
91
+ .replace(/'/g, '&#x27;');
92
+ }
93
+
94
+ /**
95
+ * Build vis-network nodes array with category-based and lifecycle styling.
96
+ * @param {Array} nodes - Raw graph nodes
97
+ * @returns {Array} Styled nodes for vis-network
98
+ */
99
+ function _buildVisNodes(nodes) {
100
+ const seen = new Set();
101
+ return (nodes || []).reduce((acc, node) => {
102
+ if (seen.has(node.id)) return acc;
103
+ seen.add(node.id);
104
+
105
+ const category = (node.group || node.category || '').toLowerCase();
106
+ const style = CATEGORY_COLORS[category]
107
+ || (category === 'scripts' ? CATEGORY_COLORS['scripts/task'] : null)
108
+ || DEFAULT_COLOR;
109
+ const lifecycle = node.lifecycle || 'production';
110
+ const lcStyle = LIFECYCLE_STYLES[lifecycle] || LIFECYCLE_STYLES.production;
111
+ const nodeColor = lcStyle.colorOverride || style.color;
112
+
113
+ acc.push({
114
+ id: node.id,
115
+ label: _sanitize(node.label || node.id),
116
+ group: category,
117
+ lifecycle: lifecycle,
118
+ path: node.path || '',
119
+ color: {
120
+ background: nodeColor,
121
+ border: THEME.border.subtle,
122
+ highlight: { background: nodeColor, border: THEME.border.goldStrong },
123
+ hover: { background: nodeColor, border: THEME.border.gold },
124
+ },
125
+ opacity: lcStyle.opacity,
126
+ shapeProperties: { borderDashes: lcStyle.borderDashes },
127
+ shape: style.shape,
128
+ });
129
+ return acc;
130
+ }, []);
131
+ }
132
+
133
+ /**
134
+ * Build vis-network edges array.
135
+ * @param {Array} edges - Raw graph edges
136
+ * @returns {Array} Edges for vis-network
137
+ */
138
+ function _buildVisEdges(edges) {
139
+ return (edges || []).map((edge) => ({
140
+ from: edge.from,
141
+ to: edge.to,
142
+ arrows: 'to',
143
+ }));
144
+ }
145
+
146
+ /**
147
+ * Build the sidebar HTML for filters.
148
+ * @returns {string} Sidebar HTML
149
+ */
150
+ function _buildSidebar(nodes) {
151
+ // Compute node counts per category
152
+ const categoryCounts = {};
153
+ (nodes || []).forEach((n) => {
154
+ const cat = (n.group || n.category || '').toLowerCase();
155
+ categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
156
+ });
157
+
158
+ const categoryItems = Object.entries(CATEGORY_COLORS).map(([name, style]) => {
159
+ const count = categoryCounts[name] || 0;
160
+ return `<label class="filter-item">
161
+ <input type="checkbox" data-filter="category" value="${name}" checked>
162
+ <span class="status-dot" style="color:${style.color}"></span>
163
+ <span style="color:${THEME.text.secondary};font-size:11px">${name}</span>
164
+ <span style="color:${THEME.text.tertiary};font-size:11px;margin-left:auto">${count}</span>
165
+ </label>`;
166
+ }).join('\n');
167
+
168
+ const lifecycleItems = Object.entries(LIFECYCLE_STYLES).map(([name, style]) => {
169
+ const opacity = style.opacity;
170
+ return `<label class="filter-item">
171
+ <input type="checkbox" data-filter="lifecycle" value="${name}" checked>
172
+ <span style="opacity:${opacity}">&#9679;</span> ${name}
173
+ </label>`;
174
+ }).join('\n');
175
+
176
+ return `<div id="sidebar">
177
+ <div class="sidebar-header">
178
+ <span class="sidebar-title">Filters</span>
179
+ <button id="btn-toggle-sidebar" title="Toggle sidebar">&#9776;</button>
180
+ </div>
181
+ <div id="sidebar-content">
182
+ <div class="filter-section">
183
+ <input type="text" id="search-input" placeholder="Search entities..." autocomplete="off">
184
+ </div>
185
+ <div class="filter-section">
186
+ <div class="section-title">ENTITY TYPES</div>
187
+ <div class="gold-line"></div>
188
+ ${categoryItems}
189
+ </div>
190
+ <div class="filter-section">
191
+ <div class="section-title">Lifecycle</div>
192
+ ${lifecycleItems}
193
+ <label class="filter-item hide-orphans">
194
+ <input type="checkbox" id="hide-orphans"> Hide Orphans
195
+ </label>
196
+ </div>
197
+ <div class="filter-section physics-section">
198
+ <div class="section-title physics-toggle" style="cursor:pointer">PHYSICS</div>
199
+ <div class="gold-line"></div>
200
+ <div class="physics-content" style="display:none">
201
+ <div class="slider-row">
202
+ <label class="slider-label">Center Force <span id="val-center" style="color:${THEME.text.tertiary}">0.3</span></label>
203
+ <input type="range" id="slider-center" min="0" max="1" step="0.05" value="0.3" aria-label="Center Force">
204
+ </div>
205
+ <div class="slider-row">
206
+ <label class="slider-label">Repel Force <span id="val-repel" style="color:${THEME.text.tertiary}">-2000</span></label>
207
+ <input type="range" id="slider-repel" min="-30000" max="0" step="500" value="-2000" aria-label="Repel Force">
208
+ </div>
209
+ <div class="slider-row">
210
+ <label class="slider-label">Link Force <span id="val-link" style="color:${THEME.text.tertiary}">0.04</span></label>
211
+ <input type="range" id="slider-link" min="0" max="1" step="0.01" value="0.04" aria-label="Link Force">
212
+ </div>
213
+ <div class="slider-row">
214
+ <label class="slider-label">Link Distance <span id="val-distance" style="color:${THEME.text.tertiary}">95</span></label>
215
+ <input type="range" id="slider-distance" min="10" max="500" step="5" value="95" aria-label="Link Distance">
216
+ </div>
217
+ <div class="physics-buttons">
218
+ <button id="btn-physics-reset" class="action-btn">Reset</button>
219
+ <button id="btn-physics-pause" class="action-btn">Pause</button>
220
+ </div>
221
+ </div>
222
+ </div>
223
+ <div id="depth-selector" class="filter-section" style="display:none">
224
+ <div class="section-title">DEPTH</div>
225
+ <div class="gold-line"></div>
226
+ <div class="depth-buttons">
227
+ <button class="depth-btn active" data-depth="1">1</button>
228
+ <button class="depth-btn" data-depth="2">2</button>
229
+ <button class="depth-btn" data-depth="3">3</button>
230
+ <button class="depth-btn" data-depth="all">All</button>
231
+ </div>
232
+ <div id="depth-node-count" style="color:${THEME.text.tertiary};font-size:11px;margin-top:6px"></div>
233
+ </div>
234
+ <div class="filter-section">
235
+ <div class="section-title">NODE SIZE</div>
236
+ <div class="gold-line"></div>
237
+ <div class="size-buttons">
238
+ <button class="size-btn active" data-sizing="uniform">Uniform</button>
239
+ <button class="size-btn" data-sizing="degree">By Degree</button>
240
+ <button class="size-btn" data-sizing="in-degree">By In-Degree</button>
241
+ <button class="size-btn" data-sizing="out-degree">By Out-Degree</button>
242
+ </div>
243
+ </div>
244
+ <div class="filter-section">
245
+ <div class="section-title">LAYOUT</div>
246
+ <div class="gold-line"></div>
247
+ <div class="layout-buttons">
248
+ <button class="layout-btn active" data-layout="force">Force</button>
249
+ <button class="layout-btn" data-layout="hierarchical">Hierarchical</button>
250
+ <button class="layout-btn" data-layout="circular">Circular</button>
251
+ </div>
252
+ </div>
253
+ <div class="filter-section">
254
+ <div class="section-title">EXPORT</div>
255
+ <div class="gold-line"></div>
256
+ <div class="export-buttons">
257
+ <button class="export-btn" id="btn-export-png" aria-label="Export graph as PNG image">PNG</button>
258
+ <button class="export-btn" id="btn-export-json" aria-label="Export graph data as JSON file">JSON</button>
259
+ </div>
260
+ </div>
261
+ <div class="filter-section">
262
+ <div class="section-title">CLUSTERING</div>
263
+ <div class="gold-line"></div>
264
+ <button id="btn-cluster-category" class="action-btn">Cluster by Category</button>
265
+ </div>
266
+ <div class="filter-section">
267
+ <div class="section-title">STATISTICS</div>
268
+ <div class="gold-line"></div>
269
+ <div id="stats-panel">
270
+ <div class="stat-row"><span class="stat-label" style="color:${THEME.text.secondary}">Total Nodes</span> <span class="stat-value" id="stat-nodes" style="color:${THEME.text.primary}">0</span></div>
271
+ <div class="stat-row"><span class="stat-label" style="color:${THEME.text.secondary}">Total Edges</span> <span class="stat-value" id="stat-edges" style="color:${THEME.text.primary}">0</span></div>
272
+ <div class="stat-row"><span class="stat-label" style="color:${THEME.text.secondary}">Graph Density</span> <span class="stat-value" id="stat-density" style="color:${THEME.text.primary}">0</span></div>
273
+ <div class="stat-row"><span class="stat-label" style="color:${THEME.text.secondary}">Avg Degree</span> <span class="stat-value" id="stat-avg-degree" style="color:${THEME.text.primary}">0</span></div>
274
+ <div class="gold-line"></div>
275
+ <div class="stat-label" style="color:${THEME.text.secondary};margin-bottom:4px">Top 5 Connected</div>
276
+ <div id="stat-top5"></div>
277
+ </div>
278
+ </div>
279
+ <div class="filter-section actions">
280
+ <button id="btn-reset" class="action-btn">Reset / Show All</button>
281
+ <button id="btn-exit-focus" class="action-btn" style="display:none">Exit Focus Mode</button>
282
+ </div>
283
+ <div id="metrics" class="filter-section metrics"></div>
284
+ </div>
285
+ </div>`;
286
+ }
287
+
288
+ /**
289
+ * Build the legend HTML for the graph (kept for backward compat, now in sidebar).
290
+ * @returns {string} Legend HTML (empty — legend is now part of sidebar)
291
+ */
292
+ function _buildLegend() {
293
+ return '';
294
+ }
295
+
296
+ /**
297
+ * Format graph data as a self-contained HTML page with vis-network,
298
+ * DataView filtering, lifecycle styling, focus mode, search, and metrics.
299
+ * @param {Object} graphData - Normalized graph data { nodes, edges, source, isFallback }
300
+ * @param {Object} [options] - Formatting options
301
+ * @param {boolean} [options.autoRefresh] - Add meta-refresh for watch mode
302
+ * @param {number} [options.refreshInterval] - Refresh interval in seconds (default: 5)
303
+ * @returns {string} Complete HTML string
304
+ */
305
+ function formatAsHtml(graphData, options = {}) {
306
+ const visNodes = _buildVisNodes(graphData.nodes);
307
+ const visEdges = _buildVisEdges(graphData.edges);
308
+ const sidebar = _buildSidebar(graphData.nodes);
309
+
310
+ const nodesJson = JSON.stringify(visNodes);
311
+ const edgesJson = JSON.stringify(visEdges);
312
+
313
+ const metaRefresh = options.autoRefresh
314
+ ? `<meta http-equiv="refresh" content="${options.refreshInterval || 5}">`
315
+ : '';
316
+
317
+ const nodeCount = visNodes.length;
318
+ const isLargeGraph = nodeCount > 200;
319
+
320
+ return `<!DOCTYPE html>
321
+ <html>
322
+ <head>
323
+ <meta charset="utf-8">
324
+ <title>AIOS Graph Dashboard</title>
325
+ ${metaRefresh}
326
+ <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
327
+ <style>
328
+ * { box-sizing: border-box; }
329
+ body { margin: 0; background: ${THEME.bg.base}; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace; }
330
+ #graph { position: absolute; top: 0; left: 260px; right: 0; bottom: 0; }
331
+ #status { position: fixed; top: 10px; left: 270px; color: ${THEME.accent.gold}; font-family: monospace; font-size: 13px; z-index: 100; background: ${THEME.bg.overlay}; padding: 6px 12px; border-radius: ${THEME.radius.md}; }
332
+ #sidebar {
333
+ position: fixed; top: 0; left: 0; width: 260px; height: 100vh;
334
+ background: ${THEME.bg.surface}; border-right: 1px solid ${THEME.border.default};
335
+ overflow-y: auto; z-index: 200; color: ${THEME.text.secondary}; font-size: 12px;
336
+ }
337
+ #sidebar.collapsed { width: 40px; }
338
+ #sidebar.collapsed #sidebar-content { display: none; }
339
+ #sidebar.collapsed .sidebar-title { display: none; }
340
+ #sidebar.collapsed ~ #graph { left: 40px; }
341
+ #sidebar.collapsed ~ #status { left: 50px; }
342
+ .sidebar-header {
343
+ display: flex; justify-content: space-between; align-items: center;
344
+ padding: 10px 12px; border-bottom: 1px solid ${THEME.border.default};
345
+ }
346
+ .sidebar-title { font-size: 14px; font-weight: 600; color: ${THEME.text.primary}; }
347
+ #btn-toggle-sidebar {
348
+ background: none; border: none; color: ${THEME.text.secondary}; font-size: 16px;
349
+ cursor: pointer; padding: 2px 6px;
350
+ }
351
+ #btn-toggle-sidebar:hover { color: ${THEME.text.primary}; }
352
+ .filter-section { padding: 8px 12px; border-bottom: 1px solid ${THEME.border.default}; }
353
+ .section-title { font-size: 10px; text-transform: uppercase; color: ${THEME.accent.gold}; margin-bottom: 6px; letter-spacing: 0.2em; }
354
+ .filter-item { display: flex; align-items: center; gap: 6px; padding: 2px 0; cursor: pointer; }
355
+ .filter-item input[type="checkbox"] { margin: 0; cursor: pointer; }
356
+ .filter-item.hide-orphans { margin-top: 6px; padding-top: 6px; border-top: 1px solid ${THEME.border.default}; }
357
+ #search-input {
358
+ width: 100%; padding: 6px 8px; background: ${THEME.bg.base}; border: 1px solid ${THEME.border.default};
359
+ color: ${THEME.text.secondary}; border-radius: ${THEME.radius.md}; font-size: 12px; outline: none;
360
+ }
361
+ #search-input:focus { border-color: ${THEME.accent.gold}; }
362
+ .action-btn {
363
+ width: 100%; padding: 6px; margin-top: 4px; background: ${THEME.border.default}; border: none;
364
+ color: ${THEME.text.secondary}; border-radius: ${THEME.radius.md}; cursor: pointer; font-size: 12px;
365
+ }
366
+ .action-btn:hover { background: ${THEME.accent.gold}; color: ${THEME.bg.base}; }
367
+ .metrics { color: ${THEME.text.tertiary}; font-size: 11px; line-height: 1.6; }
368
+ .metrics b { color: ${THEME.text.secondary}; }
369
+ .status-dot {
370
+ display: inline-block; width: 6px; height: 6px; border-radius: 50%;
371
+ background: currentColor; box-shadow: 0 0 8px currentColor; flex-shrink: 0;
372
+ }
373
+ .gold-line {
374
+ height: 1px; margin: 6px 0;
375
+ background: linear-gradient(90deg, ${THEME.accent.gold}, transparent);
376
+ }
377
+ .slider-row { margin-bottom: 8px; }
378
+ .slider-label { display: flex; justify-content: space-between; font-size: 11px; color: ${THEME.text.secondary}; margin-bottom: 2px; }
379
+ .physics-buttons { display: flex; gap: 6px; margin-top: 6px; }
380
+ .physics-buttons .action-btn { flex: 1; }
381
+ input[type="range"] {
382
+ -webkit-appearance: none; width: 100%; height: 4px; border-radius: 2px;
383
+ background: ${THEME.controls.sliderTrack}; outline: none;
384
+ }
385
+ input[type="range"]::-webkit-slider-thumb {
386
+ -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%;
387
+ background: ${THEME.controls.sliderThumb}; cursor: pointer;
388
+ }
389
+ input[type="range"]::-moz-range-thumb {
390
+ width: 12px; height: 12px; border-radius: 50%; border: none;
391
+ background: ${THEME.controls.sliderThumb}; cursor: pointer;
392
+ }
393
+ input[type="range"]::-moz-range-track {
394
+ height: 4px; border-radius: 2px; background: ${THEME.controls.sliderTrack};
395
+ }
396
+ .depth-buttons { display: flex; gap: 6px; }
397
+ .depth-btn {
398
+ flex: 1; padding: 4px 0; background: ${THEME.border.default}; border: 1px solid ${THEME.border.subtle};
399
+ color: ${THEME.text.secondary}; border-radius: ${THEME.radius.md}; cursor: pointer;
400
+ font-size: 12px; font-family: inherit; text-align: center;
401
+ }
402
+ .depth-btn:hover { border-color: ${THEME.border.gold}; }
403
+ .depth-btn.active { background: ${THEME.accent.gold}; color: ${THEME.bg.base}; border-color: ${THEME.accent.gold}; }
404
+ .size-buttons, .layout-buttons { display: flex; flex-wrap: wrap; gap: 6px; }
405
+ .size-btn, .layout-btn {
406
+ flex: 1; min-width: 45%; padding: 4px 0; background: ${THEME.border.default}; border: 1px solid ${THEME.border.subtle};
407
+ color: ${THEME.text.secondary}; border-radius: ${THEME.radius.md}; cursor: pointer;
408
+ font-size: 11px; font-family: inherit; text-align: center;
409
+ }
410
+ .size-btn:hover, .layout-btn:hover { border-color: ${THEME.border.gold}; }
411
+ .size-btn.active, .layout-btn.active { background: ${THEME.accent.gold}; color: ${THEME.bg.base}; border-color: ${THEME.accent.gold}; }
412
+ .export-buttons { display: flex; gap: 6px; }
413
+ .export-btn {
414
+ flex: 1; padding: 6px 0; background: ${THEME.bg.surface}; border: 1px solid ${THEME.border.subtle};
415
+ color: ${THEME.text.secondary}; border-radius: ${THEME.radius.md}; cursor: pointer;
416
+ font-size: 11px; font-family: inherit; text-align: center;
417
+ }
418
+ .export-btn:hover { border-color: ${THEME.border.gold}; color: ${THEME.text.primary}; }
419
+ .stat-row { display: flex; justify-content: space-between; align-items: center; padding: 2px 0; font-size: 11px; }
420
+ .stat-label { font-size: 11px; }
421
+ .stat-value { font-size: 11px; font-weight: 600; }
422
+ .top5-item { display: flex; justify-content: space-between; font-size: 11px; padding: 1px 0; }
423
+ .top5-name { color: ${THEME.text.secondary}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; }
424
+ .top5-degree { color: ${THEME.text.primary}; font-weight: 600; margin-left: 8px; flex-shrink: 0; }
425
+ #minimap-container {
426
+ position: fixed; bottom: 16px; right: 16px; width: 200px; z-index: 400;
427
+ background: ${THEME.bg.surface}; border: 1px solid ${THEME.border.subtle};
428
+ border-radius: ${THEME.radius.md}; overflow: hidden;
429
+ }
430
+ #minimap-header {
431
+ display: flex; justify-content: space-between; align-items: center;
432
+ padding: 4px 8px; font-size: 10px; color: ${THEME.text.tertiary};
433
+ }
434
+ #minimap-toggle {
435
+ background: none; border: none; color: ${THEME.text.tertiary}; cursor: pointer;
436
+ font-size: 12px; padding: 0 2px;
437
+ }
438
+ #minimap-toggle:hover { color: ${THEME.text.primary}; }
439
+ #minimap-canvas { display: block; width: 200px; height: 150px; }
440
+ #minimap-container.collapsed #minimap-canvas { display: none; }
441
+ #minimap-container.collapsed { width: auto; }
442
+ #node-tooltip {
443
+ display: none; position: fixed; z-index: 500;
444
+ background: ${THEME.tooltip.bg}; border: 1px solid ${THEME.tooltip.border};
445
+ border-radius: ${THEME.radius.md}; padding: 12px;
446
+ box-shadow: ${THEME.tooltip.shadow}; max-width: 320px; pointer-events: auto;
447
+ }
448
+ #node-tooltip .tt-name { color: ${THEME.text.primary}; font-size: 13px; font-weight: 600; margin-bottom: 4px; }
449
+ #node-tooltip .tt-type { display: flex; align-items: center; gap: 6px; color: ${THEME.text.secondary}; font-size: 11px; margin-bottom: 4px; }
450
+ #node-tooltip .tt-path { color: ${THEME.text.tertiary}; font-size: 10px; font-family: monospace; margin-bottom: 4px; word-break: break-all; }
451
+ #node-tooltip .tt-deps { color: ${THEME.text.secondary}; font-size: 11px; }
452
+ </style>
453
+ </head>
454
+ <body>
455
+ <div id="status">Loading vis-network...</div>
456
+ ${sidebar}
457
+ <div id="node-tooltip" role="tooltip"></div>
458
+ <div id="graph"></div>
459
+ <div id="minimap-container">
460
+ <div id="minimap-header">
461
+ <span>Map</span>
462
+ <button id="minimap-toggle" aria-label="Toggle minimap">&#x2715;</button>
463
+ </div>
464
+ <canvas id="minimap-canvas" width="200" height="150"></canvas>
465
+ </div>
466
+ <script>
467
+ (function() {
468
+ var statusEl = document.getElementById('status');
469
+ if (typeof vis === 'undefined') {
470
+ statusEl.textContent = 'ERROR: vis-network failed to load from CDN';
471
+ statusEl.style.color = '${THEME.status.error}';
472
+ return;
473
+ }
474
+
475
+ statusEl.textContent = 'Creating graph (${nodeCount} nodes)...';
476
+
477
+ // --- Data Setup ---
478
+ var allNodesData = ${nodesJson};
479
+ var allEdgesData = ${edgesJson};
480
+ var nodesDataset = new vis.DataSet(allNodesData);
481
+ var edgesDataset = new vis.DataSet(allEdgesData);
482
+ var totalEntities = allNodesData.length;
483
+
484
+ // --- Filter State ---
485
+ var activeCategories = new Set(${JSON.stringify(Object.keys(CATEGORY_COLORS))});
486
+ var activeLifecycles = new Set(['production', 'experimental', 'deprecated', 'orphan']);
487
+ var hideOrphans = false;
488
+ var searchTerm = '';
489
+ var focusNodeId = null;
490
+ var focusNeighbors = null;
491
+
492
+ // --- DataView Filtering ---
493
+ var nodesView = new vis.DataView(nodesDataset, {
494
+ filter: function(node) {
495
+ if (focusNodeId) {
496
+ return focusNeighbors && focusNeighbors.has(node.id);
497
+ }
498
+ if (!activeCategories.has(node.group)) return false;
499
+ if (!activeLifecycles.has(node.lifecycle)) return false;
500
+ if (hideOrphans && node.lifecycle === 'orphan') return false;
501
+ if (searchTerm) {
502
+ var term = searchTerm.toLowerCase();
503
+ return node.label.toLowerCase().indexOf(term) !== -1 || node.id.toLowerCase().indexOf(term) !== -1;
504
+ }
505
+ return true;
506
+ }
507
+ });
508
+
509
+ var visibleNodeIds = new Set(nodesView.getIds());
510
+ var edgesView = new vis.DataView(edgesDataset, {
511
+ filter: function(edge) {
512
+ return visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to);
513
+ }
514
+ });
515
+
516
+ function refreshFilters() {
517
+ nodesView.refresh();
518
+ visibleNodeIds = new Set(nodesView.getIds());
519
+ edgesView.refresh();
520
+ updateMetrics();
521
+ if (typeof updateStatistics === 'function') updateStatistics();
522
+ if (typeof scheduleMinimapUpdate === 'function') scheduleMinimapUpdate();
523
+ }
524
+
525
+ // --- Network ---
526
+ var container = document.getElementById('graph');
527
+ var network = new vis.Network(container, { nodes: nodesView, edges: edgesView }, {
528
+ physics: {
529
+ stabilization: { iterations: ${isLargeGraph ? 200 : 100}, updateInterval: 25 },
530
+ barnesHut: {
531
+ gravitationalConstant: ${isLargeGraph ? -2000 : -3000},
532
+ springLength: ${isLargeGraph ? 200 : 150},
533
+ springConstant: 0.01,
534
+ damping: 0.3
535
+ }
536
+ },
537
+ nodes: {
538
+ font: { color: '${THEME.text.secondary}', size: ${isLargeGraph ? 10 : 12} },
539
+ borderWidth: 2,
540
+ scaling: { min: 5, max: 20 }
541
+ },
542
+ edges: {
543
+ color: { color: '${THEME.border.default}', highlight: '${THEME.border.highlight}' },
544
+ smooth: ${isLargeGraph ? 'false' : '{ type: "cubicBezier" }'}
545
+ },
546
+ interaction: {
547
+ hover: true,
548
+ tooltipDelay: 200,
549
+ hideEdgesOnDrag: true,
550
+ hideEdgesOnZoom: ${isLargeGraph ? 'true' : 'false'}
551
+ }
552
+ });
553
+
554
+ // --- Tooltip ---
555
+ var tooltipEl = document.getElementById('node-tooltip');
556
+
557
+ function showTooltip(nodeId, domPos) {
558
+ var nodeData = nodesDataset.get(nodeId);
559
+ if (!nodeData) return;
560
+
561
+ // Compute dependency count from edges
562
+ var depCount = 0;
563
+ allEdgesData.forEach(function(e) {
564
+ if (e.from === nodeId || e.to === nodeId) depCount++;
565
+ });
566
+
567
+ var catColor = nodeData.color && nodeData.color.background ? nodeData.color.background : '${THEME.text.tertiary}';
568
+
569
+ tooltipEl.innerHTML =
570
+ '<div class="tt-name">' + nodeData.label + '</div>' +
571
+ '<div class="tt-type"><span class="status-dot" style="color:' + catColor + '"></span> ' + (nodeData.group || 'unknown') + '</div>' +
572
+ (nodeData.path ? '<div class="tt-path">' + nodeData.path + '</div>' : '') +
573
+ '<div class="tt-deps">' + depCount + ' dependenc' + (depCount === 1 ? 'y' : 'ies') + '</div>';
574
+
575
+ // Position tooltip clamped to viewport
576
+ var x = domPos.x + 15;
577
+ var y = domPos.y + 15;
578
+ tooltipEl.style.display = 'block';
579
+ var rect = tooltipEl.getBoundingClientRect();
580
+ if (x + rect.width > window.innerWidth) x = domPos.x - rect.width - 15;
581
+ if (y + rect.height > window.innerHeight) y = domPos.y - rect.height - 15;
582
+ if (x < 0) x = 10;
583
+ if (y < 0) y = 10;
584
+ tooltipEl.style.left = x + 'px';
585
+ tooltipEl.style.top = y + 'px';
586
+
587
+ // Accessibility: link node to tooltip
588
+ container.setAttribute('aria-describedby', 'node-tooltip');
589
+ }
590
+
591
+ function hideTooltip() {
592
+ tooltipEl.style.display = 'none';
593
+ container.removeAttribute('aria-describedby');
594
+ }
595
+
596
+ document.addEventListener('keydown', function(e) {
597
+ if (e.key === 'Escape') hideTooltip();
598
+ });
599
+
600
+ function bindNetworkEvents() {
601
+ network.on('stabilizationProgress', function(params) {
602
+ var pct = Math.round(params.iterations / params.total * 100);
603
+ statusEl.textContent = 'Stabilizing... ' + pct + '%';
604
+ });
605
+
606
+ network.on('stabilizationIterationsDone', function() {
607
+ statusEl.textContent = 'Graph ready — ${nodeCount} nodes';
608
+ statusEl.style.color = '${THEME.status.success}';
609
+ network.fit({ animation: { duration: 500 } });
610
+ setTimeout(function() { statusEl.style.display = 'none'; }, 4000);
611
+ });
612
+
613
+ network.on('click', function(params) {
614
+ if (params.nodes.length === 1) {
615
+ var nodeId = params.nodes[0];
616
+ var canvasPos = network.getPosition(nodeId);
617
+ var domPos = network.canvasToDOM(canvasPos);
618
+ showTooltip(nodeId, domPos);
619
+ } else {
620
+ hideTooltip();
621
+ }
622
+ });
623
+
624
+ // --- Focus Mode ---
625
+ network.on('doubleClick', function(params) {
626
+ if (params.nodes.length === 1) {
627
+ enterFocusMode(params.nodes[0]);
628
+ }
629
+ });
630
+
631
+ // --- Minimap sync on network events ---
632
+ network.on('zoom', function() { if (typeof scheduleMinimapUpdate === 'function') scheduleMinimapUpdate(); });
633
+ network.on('dragEnd', function() { if (typeof scheduleMinimapUpdate === 'function') scheduleMinimapUpdate(); });
634
+ }
635
+ bindNetworkEvents();
636
+
637
+ // --- BFS Algorithm (GD-12) ---
638
+ function getNeighborsAtDepth(net, nodeId, maxDepth) {
639
+ var visited = new Set([nodeId]);
640
+ var levels = new Map();
641
+ levels.set(nodeId, 0);
642
+ var currentLevel = [nodeId];
643
+ for (var depth = 1; depth <= maxDepth; depth++) {
644
+ var nextLevel = [];
645
+ for (var i = 0; i < currentLevel.length; i++) {
646
+ var neighbors = net.getConnectedNodes(currentLevel[i]);
647
+ for (var j = 0; j < neighbors.length; j++) {
648
+ if (!visited.has(neighbors[j])) {
649
+ visited.add(neighbors[j]);
650
+ levels.set(neighbors[j], depth);
651
+ nextLevel.push(neighbors[j]);
652
+ }
653
+ }
654
+ }
655
+ currentLevel = nextLevel;
656
+ }
657
+ return { visited: visited, levels: levels };
658
+ }
659
+
660
+ var currentDepth = 1;
661
+ var depthLevels = null;
662
+
663
+ function applyDepthVisuals(levels) {
664
+ var updates = [];
665
+ var baseSizes = {};
666
+ allNodesData.forEach(function(n) {
667
+ baseSizes[n.id] = 25; // vis-network default node size
668
+ });
669
+
670
+ nodesDataset.forEach(function(node) {
671
+ var depth = levels ? levels.get(node.id) : undefined;
672
+ var update = { id: node.id };
673
+ if (depth === undefined) {
674
+ update.hidden = true;
675
+ } else if (depth === 0) {
676
+ update.hidden = false;
677
+ update.opacity = 1.0;
678
+ update.size = baseSizes[node.id] * 1.2;
679
+ update.color = {
680
+ background: node.color.background,
681
+ border: '${THEME.border.goldStrong}',
682
+ highlight: { background: node.color.background, border: '${THEME.border.goldStrong}' },
683
+ hover: { background: node.color.background, border: '${THEME.border.goldStrong}' }
684
+ };
685
+ } else if (depth === 1) {
686
+ update.hidden = false;
687
+ update.opacity = 1.0;
688
+ update.size = baseSizes[node.id];
689
+ update.color = {
690
+ background: node.color.background,
691
+ border: '${THEME.border.gold}',
692
+ highlight: { background: node.color.background, border: '${THEME.border.goldStrong}' },
693
+ hover: { background: node.color.background, border: '${THEME.border.gold}' }
694
+ };
695
+ } else if (depth === 2) {
696
+ update.hidden = false;
697
+ update.opacity = 0.7;
698
+ update.size = baseSizes[node.id] * 0.9;
699
+ update.color = {
700
+ background: node.color.background,
701
+ border: '${THEME.border.subtle}',
702
+ highlight: { background: node.color.background, border: '${THEME.border.goldStrong}' },
703
+ hover: { background: node.color.background, border: '${THEME.border.gold}' }
704
+ };
705
+ } else {
706
+ update.hidden = false;
707
+ update.opacity = 0.4;
708
+ update.size = baseSizes[node.id] * 0.8;
709
+ update.color = {
710
+ background: node.color.background,
711
+ border: '${THEME.border.subtle}',
712
+ highlight: { background: node.color.background, border: '${THEME.border.goldStrong}' },
713
+ hover: { background: node.color.background, border: '${THEME.border.gold}' }
714
+ };
715
+ }
716
+ updates.push(update);
717
+ });
718
+ nodesDataset.update(updates);
719
+ }
720
+
721
+ function updateDepthCount() {
722
+ var countEl = document.getElementById('depth-node-count');
723
+ if (!countEl) return;
724
+ var visible = 0;
725
+ nodesDataset.forEach(function(n) { if (!n.hidden) visible++; });
726
+ countEl.textContent = 'Nodes: ' + visible + ' / ' + totalEntities;
727
+ }
728
+
729
+ function setDepth(depth) {
730
+ currentDepth = depth;
731
+ var depthSelector = document.getElementById('depth-selector');
732
+ if (!depthSelector) return;
733
+ var btns = depthSelector.querySelectorAll('.depth-btn');
734
+ for (var i = 0; i < btns.length; i++) {
735
+ btns[i].classList.toggle('active', btns[i].getAttribute('data-depth') === String(depth));
736
+ }
737
+
738
+ if (depth === 'all') {
739
+ // Reset all nodes to visible, remove depth styling
740
+ var resetUpdates = [];
741
+ allNodesData.forEach(function(n) {
742
+ resetUpdates.push({ id: n.id, hidden: false, opacity: n.opacity || 1.0, size: undefined });
743
+ });
744
+ nodesDataset.update(resetUpdates);
745
+ focusNeighbors = null;
746
+ depthLevels = null;
747
+ } else {
748
+ var result = getNeighborsAtDepth(network, focusNodeId, depth);
749
+ depthLevels = result.levels;
750
+ focusNeighbors = result.visited;
751
+ applyDepthVisuals(result.levels);
752
+ }
753
+ refreshFilters();
754
+ updateDepthCount();
755
+ network.fit({ animation: { duration: 300 } });
756
+ }
757
+
758
+ function enterFocusMode(nodeId) {
759
+ focusNodeId = nodeId;
760
+ currentDepth = 1;
761
+ var result = getNeighborsAtDepth(network, nodeId, 1);
762
+ depthLevels = result.levels;
763
+ focusNeighbors = result.visited;
764
+ applyDepthVisuals(result.levels);
765
+ refreshFilters();
766
+ document.getElementById('btn-exit-focus').style.display = 'block';
767
+ // Show depth selector
768
+ var depthSelector = document.getElementById('depth-selector');
769
+ if (depthSelector) {
770
+ depthSelector.style.display = 'block';
771
+ var btns = depthSelector.querySelectorAll('.depth-btn');
772
+ for (var i = 0; i < btns.length; i++) {
773
+ btns[i].classList.toggle('active', btns[i].getAttribute('data-depth') === '1');
774
+ }
775
+ }
776
+ updateDepthCount();
777
+ network.fit({ animation: { duration: 300 } });
778
+ }
779
+
780
+ function exitFocusMode() {
781
+ focusNodeId = null;
782
+ focusNeighbors = null;
783
+ depthLevels = null;
784
+ // Restore original node properties
785
+ var resetUpdates = [];
786
+ allNodesData.forEach(function(n) {
787
+ resetUpdates.push({ id: n.id, hidden: false, opacity: n.opacity || 1.0, size: undefined });
788
+ });
789
+ nodesDataset.update(resetUpdates);
790
+ refreshFilters();
791
+ document.getElementById('btn-exit-focus').style.display = 'none';
792
+ // Hide depth selector
793
+ var depthSelector = document.getElementById('depth-selector');
794
+ if (depthSelector) depthSelector.style.display = 'none';
795
+ }
796
+
797
+ // Depth button click handlers
798
+ var depthBtns = document.querySelectorAll('.depth-btn');
799
+ for (var di = 0; di < depthBtns.length; di++) {
800
+ depthBtns[di].addEventListener('click', function() {
801
+ if (!focusNodeId) return;
802
+ var d = this.getAttribute('data-depth');
803
+ setDepth(d === 'all' ? 'all' : parseInt(d, 10));
804
+ });
805
+ }
806
+
807
+ // Keyboard shortcuts for depth (GD-12 AC 19)
808
+ document.addEventListener('keydown', function(e) {
809
+ if (!focusNodeId) return;
810
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
811
+ if (e.key === '1') setDepth(1);
812
+ else if (e.key === '2') setDepth(2);
813
+ else if (e.key === '3') setDepth(3);
814
+ else if (e.key === 'a' || e.key === 'A') setDepth('all');
815
+ });
816
+
817
+ // --- Sidebar Events ---
818
+ document.getElementById('btn-toggle-sidebar').addEventListener('click', function() {
819
+ var sb = document.getElementById('sidebar');
820
+ sb.classList.toggle('collapsed');
821
+ var graphEl = document.getElementById('graph');
822
+ graphEl.style.left = sb.classList.contains('collapsed') ? '40px' : '260px';
823
+ statusEl.style.left = sb.classList.contains('collapsed') ? '50px' : '270px';
824
+ });
825
+
826
+ // Category checkboxes
827
+ var catBoxes = document.querySelectorAll('input[data-filter="category"]');
828
+ for (var i = 0; i < catBoxes.length; i++) {
829
+ catBoxes[i].addEventListener('change', function() {
830
+ if (this.checked) {
831
+ activeCategories.add(this.value);
832
+ } else {
833
+ activeCategories.delete(this.value);
834
+ }
835
+ refreshFilters();
836
+ });
837
+ }
838
+
839
+ // Lifecycle checkboxes
840
+ var lcBoxes = document.querySelectorAll('input[data-filter="lifecycle"]');
841
+ for (var i = 0; i < lcBoxes.length; i++) {
842
+ lcBoxes[i].addEventListener('change', function() {
843
+ if (this.checked) {
844
+ activeLifecycles.add(this.value);
845
+ } else {
846
+ activeLifecycles.delete(this.value);
847
+ }
848
+ refreshFilters();
849
+ });
850
+ }
851
+
852
+ // Hide orphans toggle
853
+ document.getElementById('hide-orphans').addEventListener('change', function() {
854
+ hideOrphans = this.checked;
855
+ refreshFilters();
856
+ });
857
+
858
+ // Search input
859
+ var searchTimer = null;
860
+ document.getElementById('search-input').addEventListener('input', function() {
861
+ var val = this.value;
862
+ clearTimeout(searchTimer);
863
+ searchTimer = setTimeout(function() {
864
+ searchTerm = val;
865
+ refreshFilters();
866
+ }, 200);
867
+ });
868
+
869
+ // Reset button
870
+ document.getElementById('btn-reset').addEventListener('click', function() {
871
+ searchTerm = '';
872
+ focusNodeId = null;
873
+ focusNeighbors = null;
874
+ hideOrphans = false;
875
+ activeCategories = new Set(${JSON.stringify(Object.keys(CATEGORY_COLORS))});
876
+ activeLifecycles = new Set(['production', 'experimental', 'deprecated', 'orphan']);
877
+
878
+ document.getElementById('search-input').value = '';
879
+ document.getElementById('hide-orphans').checked = false;
880
+ document.getElementById('btn-exit-focus').style.display = 'none';
881
+
882
+ // Reset depth state (GD-12 O-3 fix)
883
+ depthLevels = null;
884
+ currentDepth = 1;
885
+ var depthSel = document.getElementById('depth-selector');
886
+ if (depthSel) depthSel.style.display = 'none';
887
+
888
+ // Restore original node properties (clear depth visuals)
889
+ var resetNodeUpdates = [];
890
+ allNodesData.forEach(function(n) {
891
+ resetNodeUpdates.push({ id: n.id, hidden: false, opacity: n.opacity || 1.0, size: undefined });
892
+ });
893
+ nodesDataset.update(resetNodeUpdates);
894
+
895
+ var allBoxes = document.querySelectorAll('#sidebar input[type="checkbox"][data-filter]');
896
+ for (var i = 0; i < allBoxes.length; i++) { allBoxes[i].checked = true; }
897
+
898
+ refreshFilters();
899
+ network.fit({ animation: { duration: 300 } });
900
+ });
901
+
902
+ // Exit focus button
903
+ document.getElementById('btn-exit-focus').addEventListener('click', exitFocusMode);
904
+
905
+ // --- Physics Controls ---
906
+ function _debounce(fn, ms) {
907
+ var timer;
908
+ return function() {
909
+ var args = arguments;
910
+ clearTimeout(timer);
911
+ timer = setTimeout(function() { fn.apply(null, args); }, ms);
912
+ };
913
+ }
914
+
915
+ var physicsDefaults = { center: 0.3, repel: -2000, link: 0.04, distance: 95 };
916
+ var physicsPaused = false;
917
+
918
+ var sliderCenter = document.getElementById('slider-center');
919
+ var sliderRepel = document.getElementById('slider-repel');
920
+ var sliderLink = document.getElementById('slider-link');
921
+ var sliderDistance = document.getElementById('slider-distance');
922
+ var valCenter = document.getElementById('val-center');
923
+ var valRepel = document.getElementById('val-repel');
924
+ var valLink = document.getElementById('val-link');
925
+ var valDistance = document.getElementById('val-distance');
926
+
927
+ function applyPhysics() {
928
+ network.setOptions({
929
+ physics: {
930
+ barnesHut: {
931
+ centralGravity: parseFloat(sliderCenter.value),
932
+ gravitationalConstant: parseFloat(sliderRepel.value),
933
+ springConstant: parseFloat(sliderLink.value),
934
+ springLength: parseFloat(sliderDistance.value)
935
+ }
936
+ }
937
+ });
938
+ }
939
+
940
+ var debouncedApply = _debounce(applyPhysics, 50);
941
+
942
+ function setupSlider(slider, valEl) {
943
+ slider.addEventListener('input', function() {
944
+ valEl.textContent = this.value;
945
+ debouncedApply();
946
+ });
947
+ }
948
+
949
+ setupSlider(sliderCenter, valCenter);
950
+ setupSlider(sliderRepel, valRepel);
951
+ setupSlider(sliderLink, valLink);
952
+ setupSlider(sliderDistance, valDistance);
953
+
954
+ // Physics toggle (collapse/expand)
955
+ var physicsToggle = document.querySelector('.physics-toggle');
956
+ var physicsContent = document.querySelector('.physics-content');
957
+ if (physicsToggle) {
958
+ physicsToggle.addEventListener('click', function() {
959
+ physicsContent.style.display = physicsContent.style.display === 'none' ? 'block' : 'none';
960
+ });
961
+ }
962
+
963
+ // Reset physics
964
+ document.getElementById('btn-physics-reset').addEventListener('click', function() {
965
+ sliderCenter.value = physicsDefaults.center;
966
+ sliderRepel.value = physicsDefaults.repel;
967
+ sliderLink.value = physicsDefaults.link;
968
+ sliderDistance.value = physicsDefaults.distance;
969
+ valCenter.textContent = physicsDefaults.center;
970
+ valRepel.textContent = physicsDefaults.repel;
971
+ valLink.textContent = physicsDefaults.link;
972
+ valDistance.textContent = physicsDefaults.distance;
973
+ applyPhysics();
974
+ });
975
+
976
+ // Pause/Resume physics
977
+ document.getElementById('btn-physics-pause').addEventListener('click', function() {
978
+ physicsPaused = !physicsPaused;
979
+ network.setOptions({ physics: { enabled: !physicsPaused } });
980
+ this.textContent = physicsPaused ? 'Resume' : 'Pause';
981
+ this.style.color = physicsPaused ? '${THEME.text.secondary}' : '';
982
+ });
983
+
984
+ // --- Degree Computation (GD-13) ---
985
+ function computeDegrees(edgesArr) {
986
+ var degrees = {};
987
+ for (var i = 0; i < edgesArr.length; i++) {
988
+ var e = edgesArr[i];
989
+ if (!degrees[e.from]) degrees[e.from] = { total: 0, in: 0, out: 0 };
990
+ if (!degrees[e.to]) degrees[e.to] = { total: 0, in: 0, out: 0 };
991
+ degrees[e.from].out++;
992
+ degrees[e.from].total++;
993
+ degrees[e.to].in++;
994
+ degrees[e.to].total++;
995
+ }
996
+ return degrees;
997
+ }
998
+
999
+ var nodeDegrees = computeDegrees(allEdgesData);
1000
+ var currentSizingMode = 'uniform';
1001
+
1002
+ function applySizing(mode) {
1003
+ currentSizingMode = mode;
1004
+ var sizeBtns = document.querySelectorAll('.size-btn');
1005
+ for (var i = 0; i < sizeBtns.length; i++) {
1006
+ sizeBtns[i].classList.toggle('active', sizeBtns[i].getAttribute('data-sizing') === mode);
1007
+ }
1008
+ if (mode === 'uniform') {
1009
+ var resetUpdates = [];
1010
+ allNodesData.forEach(function(n) { resetUpdates.push({ id: n.id, size: undefined }); });
1011
+ nodesDataset.update(resetUpdates);
1012
+ return;
1013
+ }
1014
+ var field = mode === 'degree' ? 'total' : (mode === 'in-degree' ? 'in' : 'out');
1015
+ var maxDeg = 0;
1016
+ allNodesData.forEach(function(n) {
1017
+ var d = nodeDegrees[n.id] ? nodeDegrees[n.id][field] : 0;
1018
+ if (d > maxDeg) maxDeg = d;
1019
+ });
1020
+ var minSize = 10, maxSize = 40;
1021
+ var updates = [];
1022
+ allNodesData.forEach(function(n) {
1023
+ var d = nodeDegrees[n.id] ? nodeDegrees[n.id][field] : 0;
1024
+ var size = maxDeg > 0 ? minSize + (d / maxDeg) * (maxSize - minSize) : minSize;
1025
+ updates.push({ id: n.id, size: size });
1026
+ });
1027
+ nodesDataset.update(updates);
1028
+ }
1029
+
1030
+ var sizeBtns = document.querySelectorAll('.size-btn');
1031
+ for (var si = 0; si < sizeBtns.length; si++) {
1032
+ sizeBtns[si].addEventListener('click', function() {
1033
+ applySizing(this.getAttribute('data-sizing'));
1034
+ });
1035
+ }
1036
+
1037
+ // --- Layout Switching (GD-13) ---
1038
+ var currentLayout = 'force';
1039
+
1040
+ var baseNetworkOptions = {
1041
+ physics: {
1042
+ stabilization: { iterations: ${isLargeGraph ? 200 : 100}, updateInterval: 25 },
1043
+ barnesHut: {
1044
+ gravitationalConstant: ${isLargeGraph ? -2000 : -3000},
1045
+ springLength: ${isLargeGraph ? 200 : 150},
1046
+ springConstant: 0.01,
1047
+ damping: 0.3
1048
+ }
1049
+ },
1050
+ nodes: {
1051
+ font: { color: '${THEME.text.secondary}', size: ${isLargeGraph ? 10 : 12} },
1052
+ borderWidth: 2,
1053
+ scaling: { min: 5, max: 20 }
1054
+ },
1055
+ edges: {
1056
+ color: { color: '${THEME.border.default}', highlight: '${THEME.border.highlight}' },
1057
+ smooth: ${isLargeGraph ? 'false' : '{ type: "cubicBezier" }'}
1058
+ },
1059
+ interaction: {
1060
+ hover: true,
1061
+ tooltipDelay: 200,
1062
+ hideEdgesOnDrag: true,
1063
+ hideEdgesOnZoom: ${isLargeGraph ? 'true' : 'false'}
1064
+ }
1065
+ };
1066
+
1067
+ function rebuildNetwork(opts) {
1068
+ network.destroy();
1069
+ network = new vis.Network(container, { nodes: nodesView, edges: edgesView }, opts);
1070
+ network.on('stabilizationIterationsDone', function() {
1071
+ network.fit({ animation: { duration: 500 } });
1072
+ });
1073
+ bindNetworkEvents();
1074
+ }
1075
+
1076
+ function switchLayout(layout) {
1077
+ currentLayout = layout;
1078
+ var layoutBtns = document.querySelectorAll('.layout-btn');
1079
+ for (var i = 0; i < layoutBtns.length; i++) {
1080
+ layoutBtns[i].classList.toggle('active', layoutBtns[i].getAttribute('data-layout') === layout);
1081
+ }
1082
+ // Dim/enable physics controls based on layout
1083
+ var physicsSection = document.querySelector('.physics-section');
1084
+ if (physicsSection) {
1085
+ physicsSection.style.opacity = layout === 'force' ? '1' : '0.4';
1086
+ physicsSection.style.pointerEvents = layout === 'force' ? 'auto' : 'none';
1087
+ }
1088
+ if (layout === 'force') {
1089
+ var forceOpts = JSON.parse(JSON.stringify(baseNetworkOptions));
1090
+ forceOpts.layout = { hierarchical: { enabled: false } };
1091
+ rebuildNetwork(forceOpts);
1092
+ } else if (layout === 'hierarchical') {
1093
+ var hierOpts = JSON.parse(JSON.stringify(baseNetworkOptions));
1094
+ hierOpts.physics = { enabled: false };
1095
+ hierOpts.layout = { hierarchical: { enabled: true, direction: 'UD', sortMethod: 'directed', levelSeparation: 150, nodeSpacing: 100, treeSpacing: 200 } };
1096
+ rebuildNetwork(hierOpts);
1097
+ } else if (layout === 'circular') {
1098
+ network.setOptions({
1099
+ physics: { enabled: false },
1100
+ layout: { hierarchical: { enabled: false } }
1101
+ });
1102
+ var ids = nodesView.getIds();
1103
+ var count = ids.length;
1104
+ var radius = Math.max(200, count * 3);
1105
+ var posUpdates = [];
1106
+ for (var ci = 0; ci < count; ci++) {
1107
+ var angle = (2 * Math.PI * ci) / count;
1108
+ posUpdates.push({ id: ids[ci], x: radius * Math.cos(angle), y: radius * Math.sin(angle) });
1109
+ }
1110
+ nodesDataset.update(posUpdates);
1111
+ network.fit({ animation: { duration: 500 } });
1112
+ }
1113
+ }
1114
+
1115
+ var layoutBtns = document.querySelectorAll('.layout-btn');
1116
+ for (var li = 0; li < layoutBtns.length; li++) {
1117
+ layoutBtns[li].addEventListener('click', function() {
1118
+ switchLayout(this.getAttribute('data-layout'));
1119
+ });
1120
+ }
1121
+
1122
+ // --- Metrics ---
1123
+ function updateMetrics() {
1124
+ var visible = nodesView.getIds().length;
1125
+ var visibleEdges = edgesView.getIds().length;
1126
+ var metricsEl = document.getElementById('metrics');
1127
+ metricsEl.innerHTML =
1128
+ '<b>Visible:</b> ' + visible + ' / ' + totalEntities + ' entities<br>' +
1129
+ '<b>Edges:</b> ' + visibleEdges + '<br>' +
1130
+ (focusNodeId ? '<b>Focus:</b> ' + focusNodeId + '<br>' : '');
1131
+ }
1132
+
1133
+ // --- Export (GD-14) ---
1134
+ function getTimestampFilename(ext) {
1135
+ var d = new Date();
1136
+ var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
1137
+ return 'aios-graph-' + d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + '-' + pad(d.getHours()) + pad(d.getMinutes()) + '.' + ext;
1138
+ }
1139
+
1140
+ document.getElementById('btn-export-png').addEventListener('click', function() {
1141
+ try {
1142
+ var dataURL = network.canvas.canvas.toDataURL('image/png');
1143
+ var a = document.createElement('a');
1144
+ a.href = dataURL;
1145
+ a.download = getTimestampFilename('png');
1146
+ document.body.appendChild(a);
1147
+ a.click();
1148
+ document.body.removeChild(a);
1149
+ } catch (e) {
1150
+ alert('PNG export failed: ' + e.message);
1151
+ }
1152
+ });
1153
+
1154
+ document.getElementById('btn-export-json').addEventListener('click', function() {
1155
+ var exportNodes = [];
1156
+ nodesDataset.forEach(function(n) {
1157
+ exportNodes.push({ id: n.id, label: n.label, category: n.group, lifecycle: n.lifecycle, path: n.path });
1158
+ });
1159
+ var exportEdges = [];
1160
+ edgesDataset.forEach(function(e) {
1161
+ exportEdges.push({ from: e.from, to: e.to });
1162
+ });
1163
+ var exportData = {
1164
+ nodes: exportNodes,
1165
+ edges: exportEdges,
1166
+ metadata: { total: exportNodes.length, timestamp: new Date().toISOString() }
1167
+ };
1168
+ var blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
1169
+ var a = document.createElement('a');
1170
+ a.href = URL.createObjectURL(blob);
1171
+ a.download = getTimestampFilename('json');
1172
+ document.body.appendChild(a);
1173
+ a.click();
1174
+ document.body.removeChild(a);
1175
+ URL.revokeObjectURL(a.href);
1176
+ });
1177
+
1178
+ // --- Minimap (GD-14) ---
1179
+ var minimapCanvas = document.getElementById('minimap-canvas');
1180
+ var minimapCtx = minimapCanvas.getContext('2d');
1181
+ var minimapContainer = document.getElementById('minimap-container');
1182
+ var minimapVisible = true;
1183
+
1184
+ document.getElementById('minimap-toggle').addEventListener('click', function() {
1185
+ minimapVisible = !minimapVisible;
1186
+ minimapContainer.classList.toggle('collapsed', !minimapVisible);
1187
+ this.innerHTML = minimapVisible ? '&#x2715;' : '&#x25A1;';
1188
+ if (minimapVisible) drawMinimap();
1189
+ });
1190
+
1191
+ function drawMinimap() {
1192
+ if (!minimapVisible) return;
1193
+ var cw = minimapCanvas.width;
1194
+ var ch = minimapCanvas.height;
1195
+ minimapCtx.clearRect(0, 0, cw, ch);
1196
+ minimapCtx.fillStyle = '${THEME.bg.surface}';
1197
+ minimapCtx.fillRect(0, 0, cw, ch);
1198
+
1199
+ var positions = network.getPositions();
1200
+ var ids = Object.keys(positions);
1201
+ if (ids.length === 0) return;
1202
+
1203
+ // Compute bounding box
1204
+ var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
1205
+ for (var i = 0; i < ids.length; i++) {
1206
+ var p = positions[ids[i]];
1207
+ if (p.x < minX) minX = p.x;
1208
+ if (p.x > maxX) maxX = p.x;
1209
+ if (p.y < minY) minY = p.y;
1210
+ if (p.y > maxY) maxY = p.y;
1211
+ }
1212
+ var pad = 50;
1213
+ minX -= pad; maxX += pad; minY -= pad; maxY += pad;
1214
+ var rangeX = maxX - minX || 1;
1215
+ var rangeY = maxY - minY || 1;
1216
+ var scaleX = cw / rangeX;
1217
+ var scaleY = ch / rangeY;
1218
+
1219
+ // Draw edges
1220
+ minimapCtx.strokeStyle = '${THEME.border.subtle}';
1221
+ minimapCtx.lineWidth = 0.5;
1222
+ var visibleIds = new Set(nodesView.getIds());
1223
+ allEdgesData.forEach(function(e) {
1224
+ if (!visibleIds.has(e.from) || !visibleIds.has(e.to)) return;
1225
+ var pf = positions[e.from];
1226
+ var pt = positions[e.to];
1227
+ if (!pf || !pt) return;
1228
+ minimapCtx.beginPath();
1229
+ minimapCtx.moveTo((pf.x - minX) * scaleX, (pf.y - minY) * scaleY);
1230
+ minimapCtx.lineTo((pt.x - minX) * scaleX, (pt.y - minY) * scaleY);
1231
+ minimapCtx.stroke();
1232
+ });
1233
+
1234
+ // Draw nodes as 2px dots
1235
+ minimapCtx.fillStyle = '${THEME.text.secondary}';
1236
+ for (var ni = 0; ni < ids.length; ni++) {
1237
+ if (!visibleIds.has(ids[ni])) continue;
1238
+ var np = positions[ids[ni]];
1239
+ minimapCtx.beginPath();
1240
+ minimapCtx.arc((np.x - minX) * scaleX, (np.y - minY) * scaleY, 2, 0, 2 * Math.PI);
1241
+ minimapCtx.fill();
1242
+ }
1243
+
1244
+ // Draw viewport rectangle
1245
+ var viewPos = network.getViewPosition();
1246
+ var scale = network.getScale();
1247
+ var graphEl = document.getElementById('graph');
1248
+ var vw = graphEl.clientWidth / scale;
1249
+ var vh = graphEl.clientHeight / scale;
1250
+ var vx = (viewPos.x - vw / 2 - minX) * scaleX;
1251
+ var vy = (viewPos.y - vh / 2 - minY) * scaleY;
1252
+ var vrw = vw * scaleX;
1253
+ var vrh = vh * scaleY;
1254
+ minimapCtx.strokeStyle = '${THEME.accent.gold}';
1255
+ minimapCtx.lineWidth = 1.5;
1256
+ minimapCtx.globalAlpha = 0.6;
1257
+ minimapCtx.strokeRect(vx, vy, vrw, vrh);
1258
+ minimapCtx.fillStyle = '${THEME.accent.gold}';
1259
+ minimapCtx.globalAlpha = 0.08;
1260
+ minimapCtx.fillRect(vx, vy, vrw, vrh);
1261
+ minimapCtx.globalAlpha = 1.0;
1262
+
1263
+ // Store transform for click handler
1264
+ minimapCanvas._transform = { minX: minX, minY: minY, scaleX: scaleX, scaleY: scaleY };
1265
+ }
1266
+
1267
+ minimapCanvas.addEventListener('click', function(evt) {
1268
+ var rect = minimapCanvas.getBoundingClientRect();
1269
+ var cx = evt.clientX - rect.left;
1270
+ var cy = evt.clientY - rect.top;
1271
+ var t = minimapCanvas._transform;
1272
+ if (!t) return;
1273
+ var gx = cx / t.scaleX + t.minX;
1274
+ var gy = cy / t.scaleY + t.minY;
1275
+ network.moveTo({ position: { x: gx, y: gy }, animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
1276
+ });
1277
+
1278
+ var minimapUpdatePending = false;
1279
+ function scheduleMinimapUpdate() {
1280
+ if (minimapUpdatePending) return;
1281
+ minimapUpdatePending = true;
1282
+ requestAnimationFrame(function() {
1283
+ minimapUpdatePending = false;
1284
+ drawMinimap();
1285
+ });
1286
+ }
1287
+
1288
+ network.on('zoom', scheduleMinimapUpdate);
1289
+ network.on('dragEnd', scheduleMinimapUpdate);
1290
+ network.on('stabilizationIterationsDone', function() { setTimeout(drawMinimap, 200); });
1291
+
1292
+ // --- Clustering (GD-15) ---
1293
+ var isClustered = false;
1294
+ var categoryColors = ${JSON.stringify(Object.fromEntries(Object.entries(CATEGORY_COLORS).map(([k, v]) => [k, v.color])))};
1295
+
1296
+ function clusterByCategory() {
1297
+ var categories = new Set();
1298
+ nodesView.forEach(function(node) { categories.add(node.group); });
1299
+ categories.forEach(function(cat) {
1300
+ var catNodes = [];
1301
+ nodesView.forEach(function(node) {
1302
+ if (node.group === cat) catNodes.push(node.id);
1303
+ });
1304
+ if (catNodes.length === 0) return;
1305
+ var clusterSize = Math.max(20, Math.min(50, 20 + (catNodes.length / allNodesData.length) * 30));
1306
+ network.cluster({
1307
+ joinCondition: function(nodeOptions) {
1308
+ return nodeOptions.group === cat && visibleNodeIds.has(nodeOptions.id);
1309
+ },
1310
+ clusterNodeProperties: {
1311
+ label: cat + ' (' + catNodes.length + ')',
1312
+ shape: 'dot',
1313
+ size: clusterSize,
1314
+ color: { background: categoryColors[cat] || '${THEME.text.tertiary}', border: '${THEME.border.gold}' },
1315
+ font: { color: '${THEME.text.primary}', size: 12 }
1316
+ }
1317
+ });
1318
+ });
1319
+ isClustered = true;
1320
+ network.setOptions({ physics: { stabilization: { iterations: 50 } } });
1321
+ }
1322
+
1323
+ function unclusterAll() {
1324
+ var clusterIds = [];
1325
+ nodesDataset.forEach(function(node) {
1326
+ if (network.isCluster(node.id)) clusterIds.push(node.id);
1327
+ });
1328
+ // Also check generated cluster node IDs
1329
+ network.body.data.nodes.forEach(function(node) {
1330
+ if (network.isCluster(node.id) && clusterIds.indexOf(node.id) === -1) clusterIds.push(node.id);
1331
+ });
1332
+ for (var i = 0; i < clusterIds.length; i++) {
1333
+ try { network.openCluster(clusterIds[i]); } catch(e) { /* already opened */ }
1334
+ }
1335
+ isClustered = false;
1336
+ }
1337
+
1338
+ document.getElementById('btn-cluster-category').addEventListener('click', function() {
1339
+ if (isClustered) {
1340
+ unclusterAll();
1341
+ this.textContent = 'Cluster by Category';
1342
+ } else {
1343
+ clusterByCategory();
1344
+ this.textContent = 'Uncluster All';
1345
+ }
1346
+ updateStatistics();
1347
+ });
1348
+
1349
+ // Double-click: cluster check before focus mode (Risk #3 mitigation)
1350
+ // Override the doubleClick handler from bindNetworkEvents
1351
+ network.off('doubleClick');
1352
+ network.on('doubleClick', function(params) {
1353
+ if (params.nodes.length === 1) {
1354
+ var nodeId = params.nodes[0];
1355
+ if (network.isCluster(nodeId)) {
1356
+ network.openCluster(nodeId);
1357
+ updateStatistics();
1358
+ } else {
1359
+ enterFocusMode(nodeId);
1360
+ }
1361
+ }
1362
+ });
1363
+
1364
+ // --- Statistics (GD-15) ---
1365
+ function computeGraphStats(nodeIds, edgesArr) {
1366
+ var V = nodeIds.length;
1367
+ var visibleSet = new Set(nodeIds);
1368
+ var visibleEdges = [];
1369
+ for (var i = 0; i < edgesArr.length; i++) {
1370
+ if (visibleSet.has(edgesArr[i].from) && visibleSet.has(edgesArr[i].to)) {
1371
+ visibleEdges.push(edgesArr[i]);
1372
+ }
1373
+ }
1374
+ var E = visibleEdges.length;
1375
+ var density = V > 1 ? (2 * E / (V * (V - 1))) : 0;
1376
+ var avgDegree = V > 0 ? (2 * E / V) : 0;
1377
+
1378
+ // Top 5 by degree
1379
+ var degMap = {};
1380
+ for (var j = 0; j < visibleEdges.length; j++) {
1381
+ degMap[visibleEdges[j].from] = (degMap[visibleEdges[j].from] || 0) + 1;
1382
+ degMap[visibleEdges[j].to] = (degMap[visibleEdges[j].to] || 0) + 1;
1383
+ }
1384
+ var sorted = Object.keys(degMap).map(function(id) { return { id: id, degree: degMap[id] }; });
1385
+ sorted.sort(function(a, b) { return b.degree - a.degree; });
1386
+ var top5 = sorted.slice(0, 5);
1387
+ // Resolve labels
1388
+ for (var k = 0; k < top5.length; k++) {
1389
+ var nd = nodesDataset.get(top5[k].id);
1390
+ top5[k].label = nd ? nd.label : top5[k].id;
1391
+ }
1392
+
1393
+ return { nodeCount: V, edgeCount: E, density: density, avgDegree: avgDegree, top5: top5 };
1394
+ }
1395
+
1396
+ function updateStatistics() {
1397
+ var visIds = nodesView.getIds();
1398
+ var stats = computeGraphStats(visIds, allEdgesData);
1399
+ var elNodes = document.getElementById('stat-nodes');
1400
+ var elEdges = document.getElementById('stat-edges');
1401
+ var elDensity = document.getElementById('stat-density');
1402
+ var elAvgDeg = document.getElementById('stat-avg-degree');
1403
+ var elTop5 = document.getElementById('stat-top5');
1404
+ if (elNodes) elNodes.textContent = stats.nodeCount;
1405
+ if (elEdges) elEdges.textContent = stats.edgeCount;
1406
+ if (elDensity) elDensity.textContent = stats.density.toFixed(2);
1407
+ if (elAvgDeg) elAvgDeg.textContent = stats.avgDegree.toFixed(1);
1408
+ if (elTop5) {
1409
+ var html = '';
1410
+ for (var i = 0; i < stats.top5.length; i++) {
1411
+ html += '<div class="top5-item"><span class="top5-name">' + stats.top5[i].label + '</span><span class="top5-degree">' + stats.top5[i].degree + '</span></div>';
1412
+ }
1413
+ elTop5.innerHTML = html;
1414
+ }
1415
+ }
1416
+
1417
+ updateMetrics();
1418
+ updateStatistics();
1419
+ setTimeout(drawMinimap, 1000);
1420
+ })();
1421
+ </script>
1422
+ </body>
1423
+ </html>`;
1424
+ }
1425
+
1426
+ module.exports = {
1427
+ formatAsHtml,
1428
+ _sanitize,
1429
+ _buildVisNodes,
1430
+ _buildVisEdges,
1431
+ _buildLegend,
1432
+ _buildSidebar,
1433
+ THEME,
1434
+ CATEGORY_COLORS,
1435
+ DEFAULT_COLOR,
1436
+ LIFECYCLE_STYLES,
1437
+ };