@lon-ask/dockit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +496 -0
  3. package/SKILL.md +154 -0
  4. package/apps/client/dist/assets/index-CqOXxsEZ.js +240 -0
  5. package/apps/client/dist/assets/index-DwvaANnI.css +1 -0
  6. package/apps/client/dist/index.html +13 -0
  7. package/apps/server/src/core/domain/entry.ts +22 -0
  8. package/apps/server/src/core/domain/errors.ts +27 -0
  9. package/apps/server/src/core/domain/knowledge-graph.ts +51 -0
  10. package/apps/server/src/core/domain/types.ts +168 -0
  11. package/apps/server/src/core/ports/IBuildRepository.ts +7 -0
  12. package/apps/server/src/core/ports/IDocumentNormalizer.ts +6 -0
  13. package/apps/server/src/core/ports/IDocumentStore.ts +4 -0
  14. package/apps/server/src/core/ports/IEntryReadModel.ts +9 -0
  15. package/apps/server/src/core/ports/IEntryRepository.ts +11 -0
  16. package/apps/server/src/core/ports/IKnowledgeGraph.ts +10 -0
  17. package/apps/server/src/core/ports/IPathResolver.ts +3 -0
  18. package/apps/server/src/core/ports/ISearchEngine.ts +9 -0
  19. package/apps/server/src/core/ports/ISourceProcessor.ts +7 -0
  20. package/apps/server/src/core/ports/ISourceRepository.ts +11 -0
  21. package/apps/server/src/core/usecases/BuildUseCase.ts +98 -0
  22. package/apps/server/src/core/usecases/ConfigUseCase.ts +64 -0
  23. package/apps/server/src/core/usecases/SearchUseCase.ts +16 -0
  24. package/apps/server/src/index.ts +98 -0
  25. package/apps/server/src/infrastructure/filesystem/FileSystemDocumentStore.ts +27 -0
  26. package/apps/server/src/infrastructure/graph/GraphSearchDecorator.ts +53 -0
  27. package/apps/server/src/infrastructure/graph/GraphifyKnowledgeGraph.ts +172 -0
  28. package/apps/server/src/infrastructure/graph/index.ts +2 -0
  29. package/apps/server/src/infrastructure/persistence/sqlite/SqliteBuildRepository.ts +34 -0
  30. package/apps/server/src/infrastructure/persistence/sqlite/SqliteEntryReadModel.ts +17 -0
  31. package/apps/server/src/infrastructure/persistence/sqlite/SqliteEntryRepository.ts +81 -0
  32. package/apps/server/src/infrastructure/persistence/sqlite/SqliteSourceRepository.ts +65 -0
  33. package/apps/server/src/infrastructure/persistence/sqlite/connection.ts +52 -0
  34. package/apps/server/src/infrastructure/search/SearchEngineFactory.ts +43 -0
  35. package/apps/server/src/infrastructure/search/json/JsonSearchEngine.ts +164 -0
  36. package/apps/server/src/infrastructure/search/vector/EmbeddingService.ts +23 -0
  37. package/apps/server/src/infrastructure/search/vector/VectorSearchEngine.ts +480 -0
  38. package/apps/server/src/infrastructure/source-processors/AntoraSourceProcessor.ts +14 -0
  39. package/apps/server/src/infrastructure/source-processors/AsciidocSourceProcessor.ts +12 -0
  40. package/apps/server/src/infrastructure/source-processors/DocumentNormalizer.ts +16 -0
  41. package/apps/server/src/infrastructure/source-processors/GithubMarkdownSourceProcessor.ts +12 -0
  42. package/apps/server/src/infrastructure/source-processors/MavenSourceProcessor.ts +12 -0
  43. package/apps/server/src/infrastructure/source-processors/PathResolver.ts +6 -0
  44. package/apps/server/src/infrastructure/source-processors/SourceCodeSourceProcessor.ts +260 -0
  45. package/apps/server/src/infrastructure/source-processors/ZipSourceProcessor.ts +12 -0
  46. package/apps/server/src/mcp-http.ts +102 -0
  47. package/apps/server/src/mcp.ts +432 -0
  48. package/apps/server/src/routes/build.ts +105 -0
  49. package/apps/server/src/routes/entries.ts +62 -0
  50. package/apps/server/src/routes/graph.ts +57 -0
  51. package/apps/server/src/routes/search.ts +28 -0
  52. package/apps/server/src/routes/sources.ts +105 -0
  53. package/apps/server/src/routes/viewer.ts +28 -0
  54. package/apps/server/src/services/antora.ts +238 -0
  55. package/apps/server/src/services/asciidoc.ts +221 -0
  56. package/apps/server/src/services/configLoader.ts +207 -0
  57. package/apps/server/src/services/githubMarkdown.ts +236 -0
  58. package/apps/server/src/services/maven.ts +178 -0
  59. package/apps/server/src/services/normalizer.ts +63 -0
  60. package/apps/server/src/services/paths.ts +5 -0
  61. package/apps/server/src/services/textExtractor.ts +49 -0
  62. package/apps/server/src/services/zip.ts +84 -0
  63. package/bin/commands/build.ts +85 -0
  64. package/bin/commands/dev.ts +36 -0
  65. package/bin/commands/get.ts +36 -0
  66. package/bin/commands/graph.ts +153 -0
  67. package/bin/commands/init.ts +170 -0
  68. package/bin/commands/list.ts +47 -0
  69. package/bin/commands/mcp.ts +32 -0
  70. package/bin/commands/search.ts +185 -0
  71. package/bin/commands/serve.ts +23 -0
  72. package/bin/commands/status.ts +46 -0
  73. package/bin/dockit-cli.ts +92 -0
  74. package/bin/dockit.js +17 -0
  75. package/bin/utils.ts +85 -0
  76. package/dockit.yaml +154 -0
  77. package/package.json +60 -0
  78. package/scripts/mcp-wrapper.sh +44 -0
@@ -0,0 +1 @@
1
+ /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";--font-mono:"SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:#fafaf9;--color-bg-alt:#f5f5f4;--color-surface:#fff;--color-border:#e7e5e4;--color-text:#1c1917;--color-text-dim:#78716c;--color-text-muted:#a8a29e;--color-primary:#b45309;--color-primary-hover:#92400e;--color-accent:#0f766e;--color-danger:#dc2626;--color-success:#16a34a;--color-warning:#d97706;--color-terminal-bg:#171717;--color-terminal-fg:#a3e635}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.top-0{top:calc(var(--spacing) * 0)}.top-1\/2{top:50%}.top-full{top:100%}.right-0{right:calc(var(--spacing) * 0)}.right-3{right:calc(var(--spacing) * 3)}.right-12{right:calc(var(--spacing) * 12)}.bottom-0{bottom:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.left-3{left:calc(var(--spacing) * 3)}.left-3\.5{left:calc(var(--spacing) * 3.5)}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.hidden{display:none}.inline-flex{display:inline-flex}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-full{height:100%}.h-screen{height:100vh}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-0\.5{width:calc(var(--spacing) * .5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-\[420px\]{width:420px}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-border{border-color:var(--color-border)}.border-t-primary{border-top-color:var(--color-primary)}.bg-accent\/5{background-color:#0f766e0d}@supports (color:color-mix(in lab,red,red)){.bg-accent\/5{background-color:color-mix(in oklab,var(--color-accent) 5%,transparent)}}.bg-accent\/10{background-color:#0f766e1a}@supports (color:color-mix(in lab,red,red)){.bg-accent\/10{background-color:color-mix(in oklab,var(--color-accent) 10%,transparent)}}.bg-bg{background-color:var(--color-bg)}.bg-bg-alt{background-color:var(--color-bg-alt)}.bg-bg-alt\/50{background-color:#f5f5f480}@supports (color:color-mix(in lab,red,red)){.bg-bg-alt\/50{background-color:color-mix(in oklab,var(--color-bg-alt) 50%,transparent)}}.bg-black\/40{background-color:#0006}@supports (color:color-mix(in lab,red,red)){.bg-black\/40{background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.bg-danger{background-color:var(--color-danger)}.bg-danger\/5{background-color:#dc26260d}@supports (color:color-mix(in lab,red,red)){.bg-danger\/5{background-color:color-mix(in oklab,var(--color-danger) 5%,transparent)}}.bg-primary{background-color:var(--color-primary)}.bg-primary\/10{background-color:#b453091a}@supports (color:color-mix(in lab,red,red)){.bg-primary\/10{background-color:color-mix(in oklab,var(--color-primary) 10%,transparent)}}.bg-success{background-color:var(--color-success)}.bg-success\/5{background-color:#16a34a0d}@supports (color:color-mix(in lab,red,red)){.bg-success\/5{background-color:color-mix(in oklab,var(--color-success) 5%,transparent)}}.bg-surface{background-color:var(--color-surface)}.bg-terminal-bg{background-color:var(--color-terminal-bg)}.bg-warning{background-color:var(--color-warning)}.bg-warning\/5{background-color:#d977060d}@supports (color:color-mix(in lab,red,red)){.bg-warning\/5{background-color:color-mix(in oklab,var(--color-warning) 5%,transparent)}}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-20{padding-block:calc(var(--spacing) * 20)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-16{padding-right:calc(var(--spacing) * 16)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-9{padding-left:calc(var(--spacing) * 9)}.pl-10{padding-left:calc(var(--spacing) * 10)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--color-accent)}.text-danger{color:var(--color-danger)}.text-primary{color:var(--color-primary)}.text-success{color:var(--color-success)}.text-terminal-fg{color:var(--color-terminal-fg)}.text-text{color:var(--color-text)}.text-text-dim{color:var(--color-text-dim)}.text-text-muted{color:var(--color-text-muted)}.text-warning{color:var(--color-warning)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-accent\/20{--tw-ring-color:#0f766e33}@supports (color:color-mix(in lab,red,red)){.ring-accent\/20{--tw-ring-color:color-mix(in oklab, var(--color-accent) 20%, transparent)}}.ring-border{--tw-ring-color:var(--color-border)}.ring-danger\/20{--tw-ring-color:#dc262633}@supports (color:color-mix(in lab,red,red)){.ring-danger\/20{--tw-ring-color:color-mix(in oklab, var(--color-danger) 20%, transparent)}}.ring-primary{--tw-ring-color:var(--color-primary)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.group-hover\:text-primary:is(:where(.group):hover *){color:var(--color-primary)}.group-hover\:opacity-100:is(:where(.group):hover *),.group-hover\/src\:opacity-100:is(:where(.group\/src):hover *){opacity:1}}.peer-checked\:bg-primary:is(:where(.peer):checked~*){background-color:var(--color-primary)}.peer-checked\:ring-primary\/30:is(:where(.peer):checked~*){--tw-ring-color:#b453094d}@supports (color:color-mix(in lab,red,red)){.peer-checked\:ring-primary\/30:is(:where(.peer):checked~*){--tw-ring-color:color-mix(in oklab, var(--color-primary) 30%, transparent)}}.placeholder\:text-text-muted::placeholder{color:var(--color-text-muted)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:start-\[2px\]:after{content:var(--tw-content);inset-inline-start:2px}.after\:top-\[2px\]:after{content:var(--tw-content);top:2px}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing) * 4)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing) * 4)}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:bg-white:after{content:var(--tw-content);background-color:var(--color-white)}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.peer-checked\:after\:translate-x-full:is(:where(.peer):checked~*):after{content:var(--tw-content);--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media(hover:hover){.hover\:bg-bg-alt:hover{background-color:var(--color-bg-alt)}.hover\:bg-bg-alt\/50:hover{background-color:#f5f5f480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-bg-alt\/50:hover{background-color:color-mix(in oklab,var(--color-bg-alt) 50%,transparent)}}.hover\:bg-danger\/5:hover{background-color:#dc26260d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-danger\/5:hover{background-color:color-mix(in oklab,var(--color-danger) 5%,transparent)}}.hover\:bg-primary-hover:hover{background-color:var(--color-primary-hover)}.hover\:bg-primary\/10:hover{background-color:#b453091a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/10:hover{background-color:color-mix(in oklab,var(--color-primary) 10%,transparent)}}.hover\:bg-surface:hover{background-color:var(--color-surface)}.hover\:text-danger:hover{color:var(--color-danger)}.hover\:text-text:hover{color:var(--color-text)}.hover\:underline:hover{text-decoration-line:underline}.hover\:ring-text-muted:hover{--tw-ring-color:var(--color-text-muted)}}.focus\:border-primary:focus{border-color:var(--color-primary)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-primary\/30:focus{--tw-ring-color:#b453094d}@supports (color:color-mix(in lab,red,red)){.focus\:ring-primary\/30:focus{--tw-ring-color:color-mix(in oklab, var(--color-primary) 30%, transparent)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:40rem){.sm\:inline-flex{display:inline-flex}}@media(min-width:48rem){.md\:table-cell{display:table-cell}.md\:p-8{padding:calc(var(--spacing) * 8)}}}:root.dark{color-scheme:dark;--color-bg:#0c0a09;--color-bg-alt:#171412;--color-surface:#1c1917;--color-border:#292524;--color-text:#fafaf9;--color-text-dim:#a8a29e;--color-text-muted:#78716c;--color-primary:#f59e0b;--color-primary-hover:#d97706;--color-primary-light:#451a03;--color-accent:#14b8a6;--color-danger:#ef4444;--color-success:#22c55e;--color-warning:#f59e0b;--color-terminal-bg:#0a0a0a;--color-terminal-fg:#a3e635}*{transition-property:color,background-color,border-color;transition-duration:.15s}body{background-color:var(--color-bg);min-height:100vh;color:var(--color-text);margin:0}#root{min-height:100vh}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--color-border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--color-text-muted)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Dockit — Documentation Hub</title>
7
+ <script type="module" crossorigin src="/assets/index-CqOXxsEZ.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DwvaANnI.css">
9
+ </head>
10
+ <body class="antialiased">
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,22 @@
1
+ import type { EntryStatus } from '../domain/types.js';
2
+
3
+ export function canTransitionTo(current: EntryStatus, next: EntryStatus): boolean {
4
+ const transitions: Record<EntryStatus, EntryStatus[]> = {
5
+ pending: ['building'],
6
+ building: ['ready', 'error'],
7
+ ready: ['building'],
8
+ error: ['building'],
9
+ };
10
+ return transitions[current]?.includes(next) ?? false;
11
+ }
12
+
13
+ export function generateEntryId(name: string, version: string): string {
14
+ const namePart = name
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9]+/g, '-')
17
+ .replace(/^-+|-+$/g, '');
18
+ const versionPart = version
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9x]+/g, '');
21
+ return `${namePart}-${versionPart}`;
22
+ }
@@ -0,0 +1,27 @@
1
+ export class DomainError extends Error {
2
+ constructor(message: string, public readonly code: string) {
3
+ super(message);
4
+ this.name = 'DomainError';
5
+ }
6
+ }
7
+
8
+ export class NotFoundError extends DomainError {
9
+ constructor(resource: string, id: string) {
10
+ super(`${resource} not found: ${id}`, 'NOT_FOUND');
11
+ this.name = 'NotFoundError';
12
+ }
13
+ }
14
+
15
+ export class ValidationError extends DomainError {
16
+ constructor(message: string, public readonly field?: string, public readonly value?: unknown) {
17
+ super(message, 'VALIDATION_ERROR');
18
+ this.name = 'ValidationError';
19
+ }
20
+ }
21
+
22
+ export class BuildError extends DomainError {
23
+ constructor(message: string, public readonly entryId: string) {
24
+ super(`Build failed for entry ${entryId}: ${message}`, 'BUILD_ERROR');
25
+ this.name = 'BuildError';
26
+ }
27
+ }
@@ -0,0 +1,51 @@
1
+ export interface GraphNode {
2
+ id: string;
3
+ name: string;
4
+ type: string;
5
+ file: string;
6
+ line: number;
7
+ community?: number;
8
+ degree?: number;
9
+ metadata?: Record<string, unknown>;
10
+ }
11
+
12
+ export interface GraphEdge {
13
+ source: string;
14
+ target: string;
15
+ type: string;
16
+ weight?: number;
17
+ }
18
+
19
+ export interface GraphMetadata {
20
+ nodeCount: number;
21
+ edgeCount: number;
22
+ communityCount: number;
23
+ godNodes: number;
24
+ languages: string[];
25
+ }
26
+
27
+ export interface GraphPathResult {
28
+ found: boolean;
29
+ nodes: GraphNode[];
30
+ edges: GraphEdge[];
31
+ length: number;
32
+ }
33
+
34
+ export interface GraphQueryResult {
35
+ nodes: GraphNode[];
36
+ edges: GraphEdge[];
37
+ totalNodes: number;
38
+ totalEdges: number;
39
+ }
40
+
41
+ export interface KnowledgeGraphData {
42
+ nodes: GraphNode[];
43
+ edges: GraphEdge[];
44
+ metadata: {
45
+ nodeCount: number;
46
+ edgeCount: number;
47
+ communityCount: number;
48
+ godNodes: number;
49
+ languages: string[];
50
+ };
51
+ }
@@ -0,0 +1,168 @@
1
+ export type EntryStatus = 'pending' | 'building' | 'ready' | 'error';
2
+ export type SourceType = 'zip' | 'antora' | 'maven' | 'asciidoc' | 'github-markdown' | 'source-code';
3
+ export type SourceStatus = 'pending' | 'building' | 'ready' | 'error';
4
+ export type BuildStatus = 'pending' | 'building' | 'ready' | 'error';
5
+ export type SearchEngineType = 'json' | 'vector';
6
+
7
+ export interface Entry {
8
+ id: string;
9
+ name: string;
10
+ version: string;
11
+ description: string;
12
+ status: EntryStatus;
13
+ created_at: string;
14
+ updated_at: string;
15
+ }
16
+
17
+ export interface ZipSourceConfig {
18
+ url?: string;
19
+ localPath?: string;
20
+ }
21
+
22
+ export interface AntoraSourceConfig {
23
+ repoUrl?: string;
24
+ zipPath?: string;
25
+ localPath?: string;
26
+ playbookOverrides?: Record<string, unknown>;
27
+ graphifyEnabled?: boolean;
28
+ graphifySourcePath?: string;
29
+ }
30
+
31
+ export interface MavenSourceConfig {
32
+ groupId: string;
33
+ artifactId: string;
34
+ version: string;
35
+ classifier?: string;
36
+ useMavenCommand?: boolean;
37
+ localJar?: string;
38
+ }
39
+
40
+ export interface AsciidocSourceConfig {
41
+ repoUrl?: string;
42
+ zipPath?: string;
43
+ localPath?: string;
44
+ sourcePath?: string;
45
+ graphifyEnabled?: boolean;
46
+ graphifySourcePath?: string;
47
+ }
48
+
49
+ export interface GithubMarkdownSourceConfig {
50
+ repoUrl?: string;
51
+ localPath?: string;
52
+ sourcePath?: string;
53
+ branch?: string;
54
+ graphifyEnabled?: boolean;
55
+ graphifySourcePath?: string;
56
+ }
57
+
58
+ export interface SourceCodeSourceConfig {
59
+ repoUrl?: string;
60
+ localPath?: string;
61
+ zipPath?: string;
62
+ sourcePath?: string;
63
+ branch?: string;
64
+ graphifySourcePath?: string;
65
+ }
66
+
67
+ export type SourceConfig = ZipSourceConfig | AntoraSourceConfig | MavenSourceConfig | AsciidocSourceConfig | GithubMarkdownSourceConfig | SourceCodeSourceConfig;
68
+
69
+ export interface Source {
70
+ id: string;
71
+ entry_id: string;
72
+ type: SourceType;
73
+ label: string;
74
+ config: SourceConfig;
75
+ status: SourceStatus;
76
+ created_at: string;
77
+ }
78
+
79
+ export interface Build {
80
+ id: string;
81
+ entry_id: string;
82
+ status: BuildStatus;
83
+ log: string;
84
+ started_at: string;
85
+ finished_at: string;
86
+ }
87
+
88
+ export interface CreateEntryInput {
89
+ id?: string;
90
+ name: string;
91
+ version: string;
92
+ description?: string;
93
+ }
94
+
95
+ export interface UpdateEntryInput {
96
+ name?: string;
97
+ version?: string;
98
+ description?: string;
99
+ }
100
+
101
+ export interface CreateSourceInput {
102
+ type: SourceType;
103
+ label: string;
104
+ config: SourceConfig;
105
+ }
106
+
107
+ export interface UpdateSourceInput {
108
+ label?: string;
109
+ config?: SourceConfig;
110
+ }
111
+
112
+ export interface SearchResult {
113
+ path: string;
114
+ title: string;
115
+ headings: string[];
116
+ snippet: string;
117
+ }
118
+
119
+ export interface GlobalSearchResult extends SearchResult {
120
+ entryId: string;
121
+ entryName: string;
122
+ entryVersion: string;
123
+ }
124
+
125
+ export interface HtmlFile {
126
+ relativePath: string;
127
+ fullPath: string;
128
+ }
129
+
130
+ export interface SearchConfig {
131
+ engine: SearchEngineType;
132
+ }
133
+
134
+ export interface DockitConfig {
135
+ entries: DockitEntryConfig[];
136
+ search?: SearchConfig;
137
+ mcp?: {
138
+ toolPrefix?: string;
139
+ maxSearchResults?: number;
140
+ autoBuild?: boolean;
141
+ };
142
+ }
143
+
144
+ export interface DockitEntryConfig {
145
+ id: string;
146
+ name: string;
147
+ version: string;
148
+ description?: string;
149
+ sources: DockitSourceConfig[];
150
+ }
151
+
152
+ export interface DockitSourceConfig {
153
+ type: SourceType;
154
+ label: string;
155
+ repoUrl?: string;
156
+ zipPath?: string;
157
+ url?: string;
158
+ groupId?: string;
159
+ artifactId?: string;
160
+ version?: string;
161
+ classifier?: string;
162
+ sourcePath?: string;
163
+ playbookOverrides?: Record<string, unknown>;
164
+ branch?: string;
165
+ localPath?: string;
166
+ localJar?: string;
167
+ useMavenCommand?: boolean;
168
+ }
@@ -0,0 +1,7 @@
1
+ import type { Build } from '../domain/types.js';
2
+
3
+ export interface IBuildRepository {
4
+ create(entryId: string): Promise<Build>;
5
+ update(buildId: string, status: Build['status'], log: string): Promise<void>;
6
+ findLatest(entryId: string): Promise<Build | undefined>;
7
+ }
@@ -0,0 +1,6 @@
1
+ import type { Source } from '../domain/types.js';
2
+
3
+ export interface IDocumentNormalizer {
4
+ normalize(sources: Array<{ label: string; dir: string }>, outputDir: string, log: (msg: string) => void): string[];
5
+ filterSources(sources: Array<{ label: string; dir: string }>, allSourceRecords: Source[]): Array<{ label: string; dir: string }>;
6
+ }
@@ -0,0 +1,4 @@
1
+ export interface IDocumentStore {
2
+ getDocument(entryId: string, path: string): Promise<string>;
3
+ documentExists(entryId: string, path: string): Promise<boolean>;
4
+ }
@@ -0,0 +1,9 @@
1
+ export interface EntryReadModelItem {
2
+ id: string;
3
+ name: string;
4
+ version: string;
5
+ }
6
+
7
+ export interface IEntryReadModel {
8
+ listReadyEntries(): Promise<EntryReadModelItem[]>;
9
+ }
@@ -0,0 +1,11 @@
1
+ import type { Entry, CreateEntryInput, UpdateEntryInput } from '../domain/types.js';
2
+
3
+ export interface IEntryRepository {
4
+ findAll(): Promise<(Entry & { source_count: number })[]>;
5
+ findById(id: string): Promise<Entry | undefined>;
6
+ save(entry: Entry): Promise<void>;
7
+ update(id: string, input: UpdateEntryInput): Promise<void>;
8
+ delete(id: string): Promise<void>;
9
+ updateStatus(id: string, status: Entry['status']): Promise<void>;
10
+ create(input: CreateEntryInput): Promise<Entry>;
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { GraphQueryResult, GraphPathResult, GraphNode, GraphMetadata } from '../domain/knowledge-graph.js';
2
+
3
+ export interface IKnowledgeGraph {
4
+ exists(): boolean;
5
+ query(query: string, limit?: number): GraphQueryResult;
6
+ findPath(from: string, to: string): GraphPathResult;
7
+ findGodNodes(limit?: number): GraphNode[];
8
+ getMetadata(): GraphMetadata;
9
+ getNode(id: string): GraphNode | undefined;
10
+ }
@@ -0,0 +1,3 @@
1
+ export interface IPathResolver {
2
+ readonly dataRoot: string;
3
+ }
@@ -0,0 +1,9 @@
1
+ import type { SearchResult, GlobalSearchResult, HtmlFile, SearchEngineType } from '../domain/types.js';
2
+
3
+ export interface ISearchEngine {
4
+ readonly capability: SearchEngineType;
5
+
6
+ buildIndex(entryId: string, htmlFiles: HtmlFile[], log: (msg: string) => void): Promise<void>;
7
+ search(entryId: string, query: string, limit?: number): Promise<SearchResult[]>;
8
+ globalSearch(query: string, limit?: number): Promise<GlobalSearchResult[]>;
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { SourceType, Source } from '../domain/types.js';
2
+
3
+ export interface ISourceProcessor {
4
+ readonly sourceType: SourceType;
5
+ process(source: Source, sourceDir: string, entryDir: string, entryId: string, log: (msg: string) => void): Promise<string>;
6
+ runGraphify?(config: Record<string, unknown>, entryDir: string, log: (msg: string) => void): Promise<void>;
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { Source, CreateSourceInput, UpdateSourceInput } from '../domain/types.js';
2
+
3
+ export interface ISourceRepository {
4
+ findByEntryId(entryId: string): Promise<Source[]>;
5
+ findById(id: string): Promise<Source | undefined>;
6
+ save(source: Source): Promise<void>;
7
+ create(entryId: string, input: CreateSourceInput): Promise<Source>;
8
+ update(id: string, input: UpdateSourceInput): Promise<void>;
9
+ delete(id: string): Promise<void>;
10
+ updateStatus(id: string, status: Source['status']): Promise<void>;
11
+ }
@@ -0,0 +1,98 @@
1
+ import path from 'node:path';
2
+ import type { IBuildRepository } from '../../core/ports/IBuildRepository.js';
3
+ import type { ISourceRepository } from '../../core/ports/ISourceRepository.js';
4
+ import type { IEntryRepository } from '../../core/ports/IEntryRepository.js';
5
+ import type { ISearchEngine } from '../../core/ports/ISearchEngine.js';
6
+ import type { IPathResolver } from '../../core/ports/IPathResolver.js';
7
+ import type { ISourceProcessor } from '../../core/ports/ISourceProcessor.js';
8
+ import type { IDocumentNormalizer } from '../../core/ports/IDocumentNormalizer.js';
9
+ import type { Source, HtmlFile } from '../../core/domain/types.js';
10
+ import { BuildError } from '../../core/domain/errors.js';
11
+
12
+ export interface BuildResult {
13
+ buildId: string;
14
+ entryId: string;
15
+ status: 'ready' | 'error';
16
+ log: string;
17
+ }
18
+
19
+ export class BuildUseCase {
20
+ constructor(
21
+ private readonly buildRepo: IBuildRepository,
22
+ private readonly sourceRepo: ISourceRepository,
23
+ private readonly entryRepo: IEntryRepository,
24
+ private readonly searchEngine: ISearchEngine,
25
+ private readonly processors: ISourceProcessor[],
26
+ private readonly documentNormalizer: IDocumentNormalizer,
27
+ private readonly pathResolver: IPathResolver,
28
+ ) {}
29
+
30
+ async build(entryId: string): Promise<BuildResult> {
31
+ const sources = await this.sourceRepo.findByEntryId(entryId);
32
+ if (sources.length === 0) throw new BuildError('Entry has no sources', entryId);
33
+
34
+ await this.entryRepo.updateStatus(entryId, 'building');
35
+ const build = await this.buildRepo.create(entryId);
36
+ const logLines: string[] = [];
37
+ const log = (msg: string) => logLines.push(`[${new Date().toISOString()}] ${msg}`);
38
+ log('Build started');
39
+
40
+ const entryDir = path.join(this.pathResolver.dataRoot, entryId);
41
+ const bundleDir = path.join(entryDir, 'bundle');
42
+
43
+ try {
44
+ const normalizedSources: Array<{ label: string; dir: string }> = [];
45
+
46
+ const scProcessor = this.processors.find((p) => p.sourceType === 'source-code');
47
+
48
+ for (const source of sources) {
49
+ const sourceDir = path.join(entryDir, 'sources', source.id);
50
+ log(`Processing source [${source.type}]: ${source.label}`);
51
+
52
+ await this.sourceRepo.updateStatus(source.id, 'building');
53
+
54
+ try {
55
+ const processor = this.processors.find((p) => p.sourceType === source.type);
56
+ if (!processor) throw new BuildError(`No processor for source type: ${source.type}`, entryId);
57
+ const outputDir = await processor.process(source, sourceDir, entryDir, entryId, log);
58
+ normalizedSources.push({ label: source.label, dir: outputDir });
59
+
60
+ const sourceConfig = source.config as Record<string, unknown>;
61
+ if (sourceConfig.graphifyEnabled && scProcessor?.runGraphify) {
62
+ log(` Graphify enabled — generating knowledge graph...`);
63
+ await scProcessor.runGraphify(sourceConfig, entryDir, log);
64
+ }
65
+
66
+ await this.sourceRepo.updateStatus(source.id, 'ready');
67
+ } catch (err) {
68
+ await this.sourceRepo.updateStatus(source.id, 'error');
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ log(` ERROR processing source ${source.label}: ${message}`);
71
+ throw err;
72
+ }
73
+ }
74
+
75
+ log('Normalizing documentation bundle');
76
+ const htmlSources = this.documentNormalizer.filterSources(normalizedSources, sources);
77
+ const htmlFiles = this.documentNormalizer.normalize(htmlSources, bundleDir, log);
78
+
79
+ log('Building search index');
80
+ await this.searchEngine.buildIndex(
81
+ entryId,
82
+ htmlFiles.map((f): HtmlFile => ({ relativePath: f, fullPath: path.join(bundleDir, f) })),
83
+ log,
84
+ );
85
+
86
+ const fullLog = logLines.join('\n');
87
+ await this.buildRepo.update(build.id, 'ready', fullLog);
88
+ await this.entryRepo.updateStatus(entryId, 'ready');
89
+
90
+ return { buildId: build.id, entryId, status: 'ready', log: fullLog };
91
+ } catch (err) {
92
+ const fullLog = logLines.join('\n');
93
+ await this.buildRepo.update(build.id, 'error', fullLog);
94
+ await this.entryRepo.updateStatus(entryId, 'error');
95
+ return { buildId: build.id, entryId, status: 'error', log: fullLog };
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,64 @@
1
+ import type { IEntryRepository } from '../ports/IEntryRepository.js';
2
+ import type { ISourceRepository } from '../ports/ISourceRepository.js';
3
+ import type { Entry, Source, CreateEntryInput, UpdateEntryInput, CreateSourceInput, UpdateSourceInput } from '../domain/types.js';
4
+ import { NotFoundError, ValidationError } from '../domain/errors.js';
5
+ import { generateEntryId } from '../domain/entry.js';
6
+
7
+ export class ConfigUseCase {
8
+ constructor(
9
+ private readonly entryRepo: IEntryRepository,
10
+ private readonly sourceRepo: ISourceRepository,
11
+ ) {}
12
+
13
+ async listEntries(): Promise<(Entry & { source_count: number })[]> {
14
+ return this.entryRepo.findAll();
15
+ }
16
+
17
+ async getEntry(id: string): Promise<Entry | undefined> {
18
+ return this.entryRepo.findById(id);
19
+ }
20
+
21
+ async getEntryWithSources(id: string): Promise<(Entry & { sources: Source[] }) | undefined> {
22
+ const entry = await this.entryRepo.findById(id);
23
+ if (!entry) return undefined;
24
+ const sources = await this.sourceRepo.findByEntryId(id);
25
+ return { ...entry, sources };
26
+ }
27
+
28
+ async createEntry(input: CreateEntryInput): Promise<Entry> {
29
+ if (!input.name?.trim()) throw new ValidationError('Entry name is required', 'name', input.name);
30
+ if (!input.version?.trim()) throw new ValidationError('Entry version is required', 'version', input.version);
31
+ const id = input.id ?? generateEntryId(input.name, input.version);
32
+ return this.entryRepo.create({ ...input, id });
33
+ }
34
+
35
+ async updateEntry(id: string, input: UpdateEntryInput): Promise<void> {
36
+ const existing = await this.entryRepo.findById(id);
37
+ if (!existing) throw new NotFoundError('Entry', id);
38
+ await this.entryRepo.update(id, input);
39
+ }
40
+
41
+ async deleteEntry(id: string): Promise<void> {
42
+ const existing = await this.entryRepo.findById(id);
43
+ if (!existing) throw new NotFoundError('Entry', id);
44
+ await this.entryRepo.delete(id);
45
+ }
46
+
47
+ async createSource(entryId: string, input: CreateSourceInput): Promise<Source> {
48
+ const entry = await this.entryRepo.findById(entryId);
49
+ if (!entry) throw new NotFoundError('Entry', entryId);
50
+ return this.sourceRepo.create(entryId, input);
51
+ }
52
+
53
+ async updateSource(id: string, input: UpdateSourceInput): Promise<void> {
54
+ const existing = await this.sourceRepo.findById(id);
55
+ if (!existing) throw new NotFoundError('Source', id);
56
+ await this.sourceRepo.update(id, input);
57
+ }
58
+
59
+ async deleteSource(id: string): Promise<void> {
60
+ const existing = await this.sourceRepo.findById(id);
61
+ if (!existing) throw new NotFoundError('Source', id);
62
+ await this.sourceRepo.delete(id);
63
+ }
64
+ }
@@ -0,0 +1,16 @@
1
+ import type { ISearchEngine } from '../ports/ISearchEngine.js';
2
+ import type { SearchResult, GlobalSearchResult } from '../domain/types.js';
3
+
4
+ export class SearchUseCase {
5
+ constructor(private readonly searchEngine: ISearchEngine) {}
6
+
7
+ async searchEntry(entryId: string, query: string, limit = 20): Promise<SearchResult[]> {
8
+ if (!query.trim()) return [];
9
+ return this.searchEngine.search(entryId, query, limit);
10
+ }
11
+
12
+ async globalSearch(query: string, limit = 30): Promise<GlobalSearchResult[]> {
13
+ if (!query.trim()) return [];
14
+ return this.searchEngine.globalSearch(query, limit);
15
+ }
16
+ }