sillyspec 3.20.2 → 3.20.3

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 (208) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
  2. package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
  4. package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
  5. package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
  7. package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
  8. package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
  9. package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
  10. package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
  11. package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
  12. package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
  13. package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
  14. package/.claude/skills/sillyspec-state/SKILL.md +8 -8
  15. package/.claude/skills/sillyspec-status/SKILL.md +1 -5
  16. package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
  17. package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
  18. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  19. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  24. package/.sillyspec/changes/auto-mode/design.md +50 -0
  25. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  26. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  27. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  28. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
  29. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  30. package/.sillyspec/changes/dashboard/design.md +219 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
  38. package/.sillyspec/projects/sillyspec.yaml +3 -0
  39. package/README.md +11 -13
  40. package/SKILL.md +40 -44
  41. package/dist/steps/brainstorm/01-load-context.md +30 -0
  42. package/dist/steps/brainstorm/02-reuse-check.md +6 -0
  43. package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
  44. package/dist/steps/brainstorm/04-module-split.md +23 -0
  45. package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
  46. package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
  47. package/dist/steps/brainstorm/07-present-design.md +3 -0
  48. package/dist/steps/brainstorm/08-write-design.md +21 -0
  49. package/dist/steps/brainstorm/09-self-review.md +15 -0
  50. package/dist/steps/brainstorm/10-user-confirm.md +3 -0
  51. package/dist/steps/brainstorm/11-output-spec.md +7 -0
  52. package/dist/steps/brainstorm/manifest.yaml +26 -0
  53. package/dist/steps/execute/01-load-context.md +41 -0
  54. package/dist/steps/execute/02-scan-conventions.md +47 -0
  55. package/dist/steps/execute/03-skill-mcp.md +19 -0
  56. package/dist/steps/execute/04-assign-task.md +22 -0
  57. package/dist/steps/execute/04b-prompt-template.md +54 -0
  58. package/dist/steps/execute/05-write-test.md +7 -0
  59. package/dist/steps/execute/06-write-code.md +8 -0
  60. package/dist/steps/execute/07-run-test.md +26 -0
  61. package/dist/steps/execute/08-fix-issues.md +28 -0
  62. package/dist/steps/execute/09-next-task.md +33 -0
  63. package/dist/steps/execute/manifest.yaml +28 -0
  64. package/dist/steps/plan/01-load-context.md +22 -0
  65. package/dist/steps/plan/02-anchor-confirm.md +1 -0
  66. package/dist/steps/plan/03-expand-tasks.md +33 -0
  67. package/dist/steps/plan/04-mark-order.md +15 -0
  68. package/dist/steps/plan/05-e2e-planning.md +17 -0
  69. package/dist/steps/plan/06-self-check.md +16 -0
  70. package/dist/steps/plan/07-save.md +1 -0
  71. package/dist/steps/plan/manifest.yaml +18 -0
  72. package/dist/steps/scan/01-env-detect.md +51 -0
  73. package/dist/steps/scan/02-tech-stack.md +16 -0
  74. package/dist/steps/scan/03-conventions.md +16 -0
  75. package/dist/steps/scan/04-structure.md +19 -0
  76. package/dist/steps/scan/05-quality.md +18 -0
  77. package/dist/steps/scan/06-complete.md +49 -0
  78. package/dist/steps/scan/manifest.yaml +16 -0
  79. package/dist/steps/verify/01-load-specs.md +28 -0
  80. package/dist/steps/verify/02-check-tasks.md +1 -0
  81. package/dist/steps/verify/03-check-design.md +6 -0
  82. package/dist/steps/verify/04-run-tests.md +7 -0
  83. package/dist/steps/verify/05-e2e-tests.md +27 -0
  84. package/dist/steps/verify/05b-e2e-fix.md +33 -0
  85. package/dist/steps/verify/06-code-quality.md +25 -0
  86. package/dist/steps/verify/07-lint-check.md +27 -0
  87. package/dist/steps/verify/08-output-report.md +14 -0
  88. package/dist/steps/verify/manifest.yaml +22 -0
  89. package/package.json +1 -7
  90. package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
  91. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
  92. package/packages/dashboard/dist/index.html +2 -2
  93. package/packages/dashboard/package-lock.json +6 -6
  94. package/packages/dashboard/server/executor.js +1 -1
  95. package/packages/dashboard/server/index.js +18 -98
  96. package/packages/dashboard/server/parser.js +71 -139
  97. package/packages/dashboard/server/watcher.js +6 -14
  98. package/packages/dashboard/src/App.vue +185 -418
  99. package/packages/dashboard/src/components/ActionBar.vue +1 -10
  100. package/packages/dashboard/src/components/CommandPalette.vue +1 -5
  101. package/packages/dashboard/src/components/DocPreview.vue +8 -105
  102. package/packages/dashboard/src/components/DocTree.vue +19 -75
  103. package/packages/dashboard/src/components/PipelineStage.vue +2 -22
  104. package/packages/dashboard/src/components/PipelineView.vue +5 -32
  105. package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
  106. package/packages/dashboard/src/components/StageBadge.vue +3 -17
  107. package/packages/dashboard/src/components/StepCard.vue +2 -7
  108. package/packages/dashboard/src/composables/useDashboard.js +0 -28
  109. package/src/derive.js +147 -0
  110. package/src/index.js +40 -688
  111. package/src/init.js +63 -119
  112. package/src/migrate.js +7 -7
  113. package/src/progress.js +248 -1474
  114. package/src/run.js +302 -3008
  115. package/src/setup.js +64 -2
  116. package/src/stages/archive.js +17 -123
  117. package/src/stages/brainstorm.js +48 -454
  118. package/src/stages/doctor.js +46 -99
  119. package/src/stages/execute.js +59 -420
  120. package/src/stages/index.js +18 -12
  121. package/src/stages/plan.js +189 -492
  122. package/src/stages/propose.js +11 -70
  123. package/src/stages/quick.js +13 -52
  124. package/src/stages/scan.js +68 -485
  125. package/src/stages/status.js +1 -1
  126. package/src/stages/verify.js +16 -203
  127. package/src/step.js +543 -0
  128. package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
  129. package/.husky/pre-push +0 -13
  130. package/CLAUDE.md +0 -18
  131. package/docs/brainstorm-plan-contract.md +0 -64
  132. package/docs/plan-execute-contract.md +0 -123
  133. package/docs/platform-scan-protocol.md +0 -298
  134. package/docs/revision-mode.md +0 -115
  135. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  136. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  137. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  138. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  139. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  140. package/docs/sillyspec/file-lifecycle.md +0 -125
  141. package/docs/workflow-contract-regression.md +0 -106
  142. package/docs/worktree-isolation.md +0 -252
  143. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  144. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  145. package/packages/dashboard/dist/prototype-overview.html +0 -256
  146. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  147. package/packages/dashboard/public/prototype-overview.html +0 -256
  148. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  149. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  150. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  151. package/packages/dashboard/src/composables/useLayout.js +0 -131
  152. package/src/brainstorm-postcheck.js +0 -158
  153. package/src/change-list.js +0 -52
  154. package/src/change-risk-profile.js +0 -352
  155. package/src/classify-change.js +0 -73
  156. package/src/constants.js +0 -70
  157. package/src/contract-matrix.js +0 -278
  158. package/src/db.js +0 -201
  159. package/src/endpoint-extractor.js +0 -315
  160. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  161. package/src/hooks/worktree-guard.js +0 -761
  162. package/src/knowledge-match.js +0 -130
  163. package/src/modules.js +0 -482
  164. package/src/scan-postcheck.js +0 -383
  165. package/src/stage-contract.js +0 -700
  166. package/src/stages/brainstorm-auto.js +0 -229
  167. package/src/stages/explore.js +0 -34
  168. package/src/stages/knowledge.js +0 -498
  169. package/src/stages/plan-postcheck.js +0 -513
  170. package/src/sync.js +0 -497
  171. package/src/task-review.js +0 -346
  172. package/src/workflow.js +0 -785
  173. package/src/worktree-apply.js +0 -549
  174. package/src/worktree.js +0 -932
  175. package/templates/workflows/archive-impact.yaml +0 -79
  176. package/templates/workflows/scan-docs.yaml +0 -132
  177. package/test/brainstorm-plan-contract.test.mjs +0 -273
  178. package/test/check-syntax.mjs +0 -26
  179. package/test/cli-top-level-aliases.test.mjs +0 -174
  180. package/test/contract-artifacts.test.mjs +0 -323
  181. package/test/decision-supersede.test.mjs +0 -277
  182. package/test/knowledge-match.test.mjs +0 -231
  183. package/test/plan-execute-contract.test.mjs +0 -330
  184. package/test/plan-optimization.test.mjs +0 -572
  185. package/test/platform-artifacts.test.mjs +0 -190
  186. package/test/platform-failure-samples.test.mjs +0 -199
  187. package/test/platform-recovery-chain.test.mjs +0 -179
  188. package/test/platform-recovery.test.mjs +0 -167
  189. package/test/platform-scan-p0.test.mjs +0 -175
  190. package/test/revision-v1.test.mjs +0 -1145
  191. package/test/run-sanitize-project-name.test.mjs +0 -51
  192. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  193. package/test/run-scan-project-parse.test.mjs +0 -200
  194. package/test/run-tests.mjs +0 -48
  195. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  196. package/test/scan-knowledge.test.mjs +0 -175
  197. package/test/scan-paths.test.mjs +0 -68
  198. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  199. package/test/scan-postcheck.test.mjs +0 -197
  200. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  201. package/test/spec-dir.test.mjs +0 -206
  202. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  203. package/test/stage-contract.test.mjs +0 -299
  204. package/test/stage-definitions.test.mjs +0 -39
  205. package/test/wait-gates.test.mjs +0 -501
  206. package/test/workflow-spec-base.test.mjs +0 -142
  207. package/test/worktree-guard.test.mjs +0 -136
  208. package/test/worktree-native-overlay.test.mjs +0 -188
@@ -0,0 +1 @@
1
+ .slide-enter-active[data-v-b5f771a9],.slide-leave-active[data-v-b5f771a9]{transition:all .2s ease}.slide-enter-from[data-v-b5f771a9],.slide-leave-to[data-v-b5f771a9]{opacity:0;max-height:0;overflow:hidden}.slide-enter-to[data-v-b5f771a9],.slide-leave-from[data-v-b5f771a9]{max-height:300px}.line-clamp-2[data-v-fac2f74c]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.doc-preview[data-v-fc57afbe]{font-size:13px;line-height:1.7;color:#374151}.doc-preview[data-v-fc57afbe] h1{color:#d97706;font-size:18px;font-weight:700;margin:0 0 16px;border-bottom:1px solid #E5E5EA;padding-bottom:8px}.doc-preview[data-v-fc57afbe] h2{color:#1c1c1e;font-size:15px;font-weight:600;margin:20px 0 10px}.doc-preview[data-v-fc57afbe] h3{color:#d97706;font-size:14px;font-weight:600;margin:16px 0 8px}.doc-preview[data-v-fc57afbe] p{margin:8px 0}.doc-preview[data-v-fc57afbe] strong{color:#1c1c1e;font-weight:600}.doc-preview[data-v-fc57afbe] a{color:#d97706;text-decoration:none}.doc-preview[data-v-fc57afbe] a:hover{text-decoration:underline}.doc-preview[data-v-fc57afbe] code{background:#e5e5ea;color:#1c1c1e;padding:1px 5px;border-radius:3px;font-size:12px;font-family:JetBrains Mono,monospace}.doc-preview[data-v-fc57afbe] pre{background:#1c1c1e;color:#e5e5e7;border:1px solid #E5E5EA;border-radius:6px;padding:12px 16px;overflow-x:auto;font-size:12px;margin:8px 0;line-height:1.5}.doc-preview[data-v-fc57afbe] pre code{background:none;color:inherit;padding:0;border-radius:0;font-size:inherit}.doc-preview[data-v-fc57afbe] ul,.doc-preview[data-v-fc57afbe] ol{padding-left:20px;margin:8px 0;color:#374151}.doc-preview[data-v-fc57afbe] li{margin:4px 0}.doc-preview[data-v-fc57afbe] blockquote{border-left:3px solid #D97706;padding-left:12px;margin:12px 0;color:#636366;font-style:italic}.doc-preview[data-v-fc57afbe] table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px}.doc-preview[data-v-fc57afbe] thead th{background:#f0f0f3;color:#1c1c1e;font-weight:600;text-align:left;padding:8px 12px;border:1px solid #E5E5EA}.doc-preview[data-v-fc57afbe] tbody td{padding:6px 12px;border:1px solid #E5E5EA;color:#374151}.doc-preview[data-v-fc57afbe] tbody tr:hover{background:#f9fafb}.doc-preview[data-v-fc57afbe] hr{border:none;border-top:1px solid #E5E5EA;margin:16px 0}.project-overview[data-v-543019d4]{display:flex;align-items:center;background:#fff;border-bottom:1px solid #E5E5EA;padding:8px 16px;min-height:48px;gap:8px;flex-shrink:0;overflow-x:auto}.ov-section[data-v-543019d4]{display:flex;align-items:center;gap:6px;white-space:nowrap;flex-shrink:0}.ov-clickable[data-v-543019d4]{cursor:pointer;border-radius:4px;padding:2px 6px;margin:-2px -6px;transition:background .15s}.ov-clickable[data-v-543019d4]:hover{background:#d977060f}.ov-name[data-v-543019d4]{font-weight:600;font-size:13px;color:#1c1c1e}.ov-path[data-v-543019d4],.ov-label[data-v-543019d4]{font-size:11px;color:#636366}.ov-value[data-v-543019d4]{font-size:12px;color:#1c1c1e;font-weight:500}.ov-tag[data-v-543019d4]{font-size:10px;padding:1px 8px;border-radius:10px;background:#d9770614;color:#d97706;font-weight:500}.ov-divider[data-v-543019d4]{width:1px;height:20px;background:#e5e5ea;flex-shrink:0}.ov-git[data-v-543019d4]{margin-left:auto;gap:10px}.ov-branch[data-v-543019d4]{font-size:11px;color:#636366;background:#f5f5f7;padding:1px 8px;border-radius:4px}.ov-commit[data-v-543019d4]{font-size:11px;color:#636366;max-width:200px;overflow:hidden;text-overflow:ellipsis}.ov-dirty[data-v-543019d4]{font-size:11px;color:#d97706;font-weight:500}*{margin:0;padding:0;box-sizing:border-box}body{font-family:DM Sans,-apple-system,BlinkMacSystemFont,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#app{width:100vw;height:100vh;overflow:hidden}/*! tailwindcss v4.2.2 | 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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction: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-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--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-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-black:#000;--spacing:.25rem;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--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);--animate-pulse-dot:pulse-dot 1.5s ease-in-out infinite}}@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}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.inset-x-0{inset-inline:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.bottom-0{bottom:calc(var(--spacing) * 0)}.left-0{left:calc(var(--spacing) * 0)}.z-10{z-index:10}.z-20{z-index:20}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-4{margin-inline:calc(var(--spacing) * 4)}.-mt-0\.5{margin-top:calc(var(--spacing) * -.5)}.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)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.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}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.list-item{display:list-item}.table{display:table}.h-1{height:calc(var(--spacing) * 1)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-12{height:calc(var(--spacing) * 12)}.h-\[2px\]{height:2px}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-72{max-height:calc(var(--spacing) * 72)}.w-0{width:calc(var(--spacing) * 0)}.w-1{width:calc(var(--spacing) * 1)}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-\[2px\]{width:2px}.w-\[200px\]{width:200px}.w-\[340px\]{width:340px}.w-px{width:1px}.w-screen{width:100vw}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse-dot{animation:var(--animate-pulse-dot)}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.resize{resize:both}.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}.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-6{gap:calc(var(--spacing) * 6)}:where(.space-y-0\.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)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * 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-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)))}:where(.space-y-px>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(1px * var(--tw-space-y-reverse));margin-block-end:calc(1px * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.p-3{padding:calc(var(--spacing) * 3)}.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-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.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-4{padding-block:calc(var(--spacing) * 4)}.py-px{padding-block:1px}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-0{padding-bottom:calc(var(--spacing) * 0)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-3\.5{padding-left:calc(var(--spacing) * 3.5)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-\[3px\]{padding-left:3px}.text-center{text-align:center}.font-\[DM_Sans\,sans-serif\]{font-family:DM Sans,sans-serif}.font-\[JetBrains_Mono\,monospace\]{font-family:JetBrains Mono,monospace}.font-mono{font-family:var(--font-mono)}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[18px\]{font-size:18px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.2em\]{--tw-tracking:.2em;letter-spacing:.2em}.tracking-\[0\.15em\]{--tw-tracking:.15em;letter-spacing:.15em}.tracking-\[0\.25em\]{--tw-tracking:.25em;letter-spacing:.25em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-black{color:var(--color-black)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.ordinal{--tw-ordinal:ordinal;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px 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)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-filter{-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{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.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))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hover\:bg-\[\#D97706\]:hover{background-color:#d97706}.hover\:bg-\[\#FEF3C7\]:hover{background-color:#fef3c7}}.active\:bg-\[\#D97706\]:active{background-color:#d97706}}@keyframes pulse-glow{0%,to{box-shadow:0 0 #fbbf2466}50%{box-shadow:0 0 12px 2px #fbbf2426}}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(1.5)}}@keyframes breathe{0%,to{opacity:.5}50%{opacity:1}}@keyframes shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}@keyframes slide-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes glow-border{0%,to{border-color:#fbbf2426}50%{border-color:#fbbf2480}}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-3px)}}.font-mono-log{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.noise-bg{position:relative}.noise-bg:before{content:"";pointer-events:none;z-index:0;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");position:absolute;top:0;right:0;bottom:0;left:0}.accent-stripe{background:repeating-linear-gradient(-45deg,#0000,#0000 8px,#fbbf2408 8px,#fbbf2408 9px)}.progress-gradient{background:linear-gradient(90deg,#fbbf24,#f59e0b,#fb923c)}.skeleton-shimmer{background:linear-gradient(90deg,#1a1e28 25%,#2a3040,#1a1e28 75%) 0 0/200% 100%;animation:1.5s ease-in-out infinite shimmer}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:#2a3040;border-radius:10px}::-webkit-scrollbar-thumb:hover{background:#3a4555}*{scrollbar-width:thin;scrollbar-color:#2a2a2d transparent}::selection{color:#fbbf24;background:#fbbf2433}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@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-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{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-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@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-duration{syntax:"*";inherits:false}
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="/assets/index-Bq_Z2hne.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-O2W5RV4z.css">
10
+ <script type="module" crossorigin src="/assets/index-D1EVTLmc.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-DGe8CqeP.css">
12
12
  </head>
13
13
  <body style="background-color: #0A0A0B; margin: 0;">
14
14
  <div id="app"></div>
@@ -2050,9 +2050,9 @@
2050
2050
  }
2051
2051
  },
2052
2052
  "node_modules/postcss": {
2053
- "version": "8.5.14",
2054
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
2055
- "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
2053
+ "version": "8.5.8",
2054
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz",
2055
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
2056
2056
  "funding": [
2057
2057
  {
2058
2058
  "type": "opencollective",
@@ -2219,9 +2219,9 @@
2219
2219
  }
2220
2220
  },
2221
2221
  "node_modules/vite": {
2222
- "version": "6.4.2",
2223
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
2224
- "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
2222
+ "version": "6.4.1",
2223
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.1.tgz",
2224
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
2225
2225
  "dev": true,
2226
2226
  "license": "MIT",
2227
2227
  "dependencies": {
@@ -9,7 +9,7 @@ import { spawn } from 'child_process'
9
9
  * @returns {function} Kill function to terminate the process
10
10
  */
11
11
  export function executeCommand(projectPath, command, onOutput, onComplete) {
12
- const args = String(command || '').trim().split(/\s+/).filter(Boolean)
12
+ const args = command.split(' ')
13
13
  const proc = spawn('npx', ['sillyspec', ...args], {
14
14
  cwd: projectPath,
15
15
  env: { ...process.env }
@@ -1,11 +1,11 @@
1
1
  import { createServer } from 'http'
2
2
  import { WebSocketServer } from 'ws'
3
3
  import { existsSync, readFileSync, readdirSync, realpathSync, watch } from 'fs'
4
- import { join, dirname, basename, sep, resolve, relative } from 'path'
4
+ import { join, dirname, basename, sep, resolve } from 'path'
5
5
  import { fileURLToPath } from 'url'
6
6
  import { homedir } from 'os'
7
7
  import open from 'open'
8
- import { parseProjectState, parseSillyspecDocsTree, parseProjectOverview, parseGitDetail, parseTechStackDetail } from './parser.js'
8
+ import { parseProjectState, parseDocsTree, parseProjectOverview, parseGitDetail, parseTechStackDetail, parseDocsList } from './parser.js'
9
9
  import { startWatcher, stopWatcher, addCustomScanPath, removeCustomScanPath, getCustomScanPaths, customScanPaths } from './watcher.js'
10
10
  import { executeCommand } from './executor.js'
11
11
 
@@ -14,31 +14,6 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
14
14
  // WebSocket clients and active processes
15
15
  let wss = null
16
16
  const activeProcesses = new Map()
17
- const allowedStages = new Set(['brainstorm', 'plan', 'execute', 'verify', 'scan', 'quick', 'explore', 'archive', 'status', 'doctor', 'auto'])
18
-
19
- function isAllowedCliCommand(command) {
20
- const args = String(command || '').trim().split(/\s+/).filter(Boolean)
21
- if (args.length === 0) return false
22
- if (args[0] === 'run') {
23
- if (!allowedStages.has(args[1])) return false
24
- const allowedFlags = new Set(['--status', '--reset', '--done', '--skip', '--output', '--input', '--change'])
25
- for (let i = 2; i < args.length; i++) {
26
- if (args[i].startsWith('-') && !allowedFlags.has(args[i])) return false
27
- if (['--output', '--input', '--change'].includes(args[i])) i++
28
- }
29
- return true
30
- }
31
- if (args[0] === 'progress') {
32
- const sub = args[1]
33
- if (!['show', 'status', 'validate', 'reset'].includes(sub)) return false
34
- for (let i = 2; i < args.length; i++) {
35
- if (!['--stage', '--deep'].includes(args[i])) return false
36
- if (args[i] === '--stage') i++
37
- }
38
- return true
39
- }
40
- return false
41
- }
42
17
 
43
18
  // Progress file watchers: projectPath -> { watcher, timer, refCount }
44
19
  const progressWatchers = new Map()
@@ -48,13 +23,12 @@ function startProgressWatch(projectPath) {
48
23
  progressWatchers.get(projectPath).refCount++
49
24
  return
50
25
  }
51
- // Watch the SQLite database file for changes (replaces old progress.json watch)
52
- const dbFile = join(projectPath, '.sillyspec', '.runtime', 'sillyspec.db')
53
- if (!existsSync(dbFile)) return
26
+ const progressFile = join(projectPath, '.sillyspec', '.runtime', 'progress.json')
27
+ if (!existsSync(progressFile)) return
54
28
 
55
29
  let timer = null
56
30
  try {
57
- const watcher = watch(dbFile, (eventType) => {
31
+ const watcher = watch(progressFile, (eventType) => {
58
32
  if (timer) clearTimeout(timer)
59
33
  timer = setTimeout(() => {
60
34
  timer = null
@@ -93,30 +67,6 @@ function broadcast(data) {
93
67
  })
94
68
  }
95
69
 
96
- function isInside(parent, child) {
97
- const rel = relative(resolve(parent), resolve(child))
98
- return rel === '' || (!!rel && !rel.startsWith('..') && !rel.includes(`..${sep}`))
99
- }
100
-
101
- function isSillyspecPath(filePath) {
102
- const parts = resolve(filePath).split(/[\\/]+/)
103
- return parts.includes('.sillyspec')
104
- }
105
-
106
- function isViewableDocPath(filePath) {
107
- return /\.(md|markdown|mdx|html?|txt|log|json|ya?ml|toml|xml|csv)$/i.test(filePath)
108
- }
109
-
110
- function isLocalOrigin(origin) {
111
- if (!origin) return true
112
- try {
113
- const { hostname } = new URL(origin)
114
- return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'
115
- } catch {
116
- return false
117
- }
118
- }
119
-
120
70
  // --- Shared scan logic (aligned with watcher.js) ---
121
71
 
122
72
  const excludeDirs = new Set([
@@ -228,14 +178,6 @@ function handleCliExecute(ws, data) {
228
178
  return
229
179
  }
230
180
 
231
- if (!isAllowedCliCommand(command)) {
232
- ws.send(JSON.stringify({
233
- type: 'cli:error',
234
- data: { message: `Command not allowed: ${command}` }
235
- }))
236
- return
237
- }
238
-
239
181
  discoverProjects().then(projects => {
240
182
  const project = projects.find(p => p.name === projectName)
241
183
  if (!project) {
@@ -279,14 +221,7 @@ function handleCliExecute(ws, data) {
279
221
  */
280
222
  function startServer({ port = 3456, open: openBrowser = true } = {}) {
281
223
  const server = createServer((req, res) => {
282
- const origin = req.headers.origin
283
- if (!isLocalOrigin(origin)) {
284
- res.writeHead(403)
285
- res.end('Forbidden')
286
- return
287
- }
288
- res.setHeader('Access-Control-Allow-Origin', origin || `http://127.0.0.1:${port}`)
289
- res.setHeader('Vary', 'Origin')
224
+ res.setHeader('Access-Control-Allow-Origin', '*')
290
225
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
291
226
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
292
227
 
@@ -318,7 +253,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
318
253
  try {
319
254
  if (type === 'git') data = parseGitDetail(projectPath)
320
255
  else if (type === 'tech') data = parseTechStackDetail(projectPath)
321
- else if (type === 'docs') data = parseSillyspecDocsTree(projectPath).groups
256
+ else if (type === 'docs') data = parseDocsList(projectPath)
322
257
  else { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid type' })); return }
323
258
  res.setHeader('Content-Type', 'application/json')
324
259
  res.writeHead(200)
@@ -375,9 +310,9 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
375
310
  res.end(JSON.stringify({ error: 'Missing path parameter' }))
376
311
  return
377
312
  }
378
- // Security: only allow reading viewable text documents under a .sillyspec tree.
313
+ // Security: only allow reading files under .sillyspec/docs/
379
314
  const normalizedPath = resolve(filePath)
380
- if (!isSillyspecPath(normalizedPath) || !isViewableDocPath(normalizedPath)) {
315
+ if (!normalizedPath.includes('.sillyspec' + sep + 'docs')) {
381
316
  res.writeHead(403)
382
317
  res.end(JSON.stringify({ error: 'Access denied' }))
383
318
  return
@@ -407,7 +342,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
407
342
  res.end(JSON.stringify({ error: 'Missing project parameter' }))
408
343
  return
409
344
  }
410
- const docs = parseSillyspecDocsTree(projectPath)
345
+ const docs = parseDocsTree(projectPath)
411
346
  res.setHeader('Content-Type', 'application/json')
412
347
  res.writeHead(200)
413
348
  res.end(JSON.stringify(docs))
@@ -418,15 +353,8 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
418
353
  const distDir = join(__dirname, '../dist')
419
354
  const indexPath = join(distDir, 'index.html')
420
355
  if (existsSync(distDir)) {
421
- const url = new URL(req.url || '/', `http://${req.headers.host}`)
422
- const requestPath = decodeURIComponent(url.pathname)
423
- const filePath = resolve(distDir, requestPath === '/' ? 'index.html' : `.${requestPath}`)
424
- if (!isInside(distDir, filePath)) {
425
- res.writeHead(403)
426
- res.end('Access denied')
427
- return
428
- }
429
- if (existsSync(filePath)) {
356
+ const filePath = join(distDir, req.url === '/' ? 'index.html' : req.url.replace(/^\//, ''))
357
+ if (existsSync(filePath) && !filePath.includes('..')) {
430
358
  const ext = filePath.split('.').pop()
431
359
  const mimeTypes = { html: 'text/html', js: 'application/javascript', css: 'text/css', svg: 'image/svg+xml', png: 'image/png', jpg: 'image/jpeg' }
432
360
  res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream')
@@ -452,11 +380,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
452
380
  console.error('WebSocket server error:', err)
453
381
  })
454
382
 
455
- wss.on('connection', (ws, req) => {
456
- if (!isLocalOrigin(req.headers.origin)) {
457
- ws.close(1008, 'Forbidden origin')
458
- return
459
- }
383
+ wss.on('connection', (ws) => {
460
384
  console.log('WebSocket client connected')
461
385
 
462
386
  // Send initial projects list
@@ -510,11 +434,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
510
434
  discoverProjects().then(projects => {
511
435
  broadcast({
512
436
  type: 'projects:updated',
513
- data: projects.map(p => ({
514
- ...p,
515
- state: parseProjectState(p.path),
516
- overview: parseProjectOverview(p.path)
517
- }))
437
+ data: projects.map(p => ({ ...p, state: parseProjectState(p.path) }))
518
438
  })
519
439
  })
520
440
  }
@@ -530,7 +450,7 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
530
450
  break
531
451
  case 'docs:get':
532
452
  if (data.data?.projectPath) {
533
- const docs = parseSillyspecDocsTree(data.data.projectPath)
453
+ const docs = parseDocsTree(data.data.projectPath)
534
454
  ws.send(JSON.stringify({ type: 'docs:tree', data: docs }))
535
455
  }
536
456
  break
@@ -565,10 +485,10 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
565
485
  console.error('Failed to start file watcher:', err)
566
486
  }
567
487
 
568
- server.listen(port, '127.0.0.1', () => {
569
- console.log(`Dashboard server running on http://127.0.0.1:${port}`)
488
+ server.listen(port, () => {
489
+ console.log(`Dashboard server running on http://localhost:${port}`)
570
490
  if (openBrowser) {
571
- open(`http://127.0.0.1:${port}`)
491
+ open(`http://localhost:${port}`)
572
492
  }
573
493
  })
574
494
 
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, existsSync, readdirSync, statSync } from 'fs'
2
2
  import { execSync } from 'child_process'
3
- import { join, relative, sep } from 'path'
3
+ import { join } from 'path'
4
4
  import { fileURLToPath } from 'url'
5
5
  import { dirname } from 'path'
6
6
 
@@ -77,13 +77,18 @@ export function parseProjectOverview(projectPath) {
77
77
 
78
78
  // --- Last active ---
79
79
  const sillyspecDir = join(projectPath, '.sillyspec')
80
- // Progress is stored in SQLite (.sillyspec/.runtime/sillyspec.db), not progress.json
81
- // Use mtime of the DB file as a fallback for lastActive
82
- const dbPath = join(sillyspecDir, '.runtime', 'sillyspec.db')
83
- if (existsSync(dbPath)) {
80
+ const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
81
+ if (existsSync(progressPath)) {
84
82
  try {
85
- const s = statSync(dbPath)
86
- result.lastActive = s.mtime.toISOString()
83
+ const progress = JSON.parse(readFileSync(progressPath, 'utf-8'))
84
+ if (progress.stages) {
85
+ for (const stageData of Object.values(progress.stages)) {
86
+ if (stageData.lastActive && (!result.lastActive || new Date(stageData.lastActive) > new Date(result.lastActive))) {
87
+ result.lastActive = stageData.lastActive
88
+ }
89
+ }
90
+ }
91
+ if (progress.lastActive) result.lastActive = progress.lastActive
87
92
  } catch {}
88
93
  }
89
94
  if (!result.lastActive) {
@@ -318,155 +323,82 @@ export function parseDocsTree(projectPath) {
318
323
  return { groups }
319
324
  }
320
325
 
321
- const VIEWABLE_SILLYSPEC_DOC_EXTENSIONS = new Set([
322
- '.md', '.markdown', '.mdx',
323
- '.html', '.htm',
324
- '.txt', '.log',
325
- '.json', '.yaml', '.yml', '.toml',
326
- '.xml', '.csv'
327
- ])
328
-
329
- const SILLYSPEC_DOC_GROUPS = [
330
- { key: 'docs', label: '📚 docs', dir: 'docs' },
331
- { key: 'changes', label: '⚙️ changes', dir: 'changes' },
332
- { key: 'plans', label: '🧾 plans', dir: 'plans' },
333
- { key: 'quicklog', label: '⚡ quicklog', dir: 'quicklog' },
334
- { key: 'knowledge', label: '🧠 knowledge', dir: 'knowledge' },
335
- { key: 'projects', label: '📁 projects', dir: 'projects' },
336
- { key: 'workspace', label: '🗂️ workspace', dir: 'workspace' },
337
- { key: 'shared', label: '🔗 shared', dir: 'shared' },
338
- { key: 'runtime', label: '🧰 .runtime', dir: '.runtime' }
339
- ]
340
-
341
- function sillyspecDocExt(fileName) {
342
- const index = fileName.lastIndexOf('.')
343
- return index === -1 ? '' : fileName.slice(index).toLowerCase()
344
- }
345
-
346
- function isViewableSillyspecDoc(fileName) {
347
- return VIEWABLE_SILLYSPEC_DOC_EXTENSIONS.has(sillyspecDocExt(fileName))
348
- }
349
-
350
- function titleFromSillyspecDoc(filePath, fileName) {
351
- const ext = sillyspecDocExt(fileName)
352
- if (ext === '.md' || ext === '.markdown' || ext === '.mdx') {
353
- try {
354
- const content = readFileSync(filePath, 'utf-8')
355
- const titleMatch = content.match(/^#\s+(.+)$/m)
356
- if (titleMatch) return titleMatch[1]
357
- } catch {}
358
- }
359
- return fileName.replace(/\.(md|markdown|mdx|html?|txt|log|json|ya?ml|toml|xml|csv)$/i, '')
360
- }
361
-
362
- function listSillyspecDocsRecursive(dir, rootDir) {
363
- const files = []
364
- try {
365
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
366
- const filePath = join(dir, entry.name)
367
- if (entry.isDirectory()) {
368
- files.push(...listSillyspecDocsRecursive(filePath, rootDir))
369
- } else if (entry.isFile() && isViewableSillyspecDoc(entry.name)) {
370
- const s = statSync(filePath)
371
- files.push({
372
- name: relative(rootDir, filePath).split(sep).join('/'),
373
- path: filePath,
374
- title: titleFromSillyspecDoc(filePath, entry.name),
375
- extension: sillyspecDocExt(entry.name).slice(1),
376
- size: s.size,
377
- mtime: s.mtime.toISOString()
378
- })
379
- }
380
- }
381
- } catch {}
382
- return files
383
- }
384
-
385
- export function parseSillyspecDocsTree(projectPath) {
386
- const sillyspecDir = join(projectPath, '.sillyspec')
387
- if (!existsSync(sillyspecDir)) return { groups: [] }
388
-
389
- const groups = []
390
- const seen = new Set()
391
-
392
- for (const group of SILLYSPEC_DOC_GROUPS) {
393
- const groupDir = join(sillyspecDir, group.dir)
394
- if (!existsSync(groupDir)) continue
395
-
396
- const files = listSillyspecDocsRecursive(groupDir, groupDir)
397
- .sort((a, b) => a.name.localeCompare(b.name))
398
-
399
- if (files.length === 0) continue
400
- for (const file of files) seen.add(file.path)
401
- groups.push({ key: group.key, label: group.label, project: '.sillyspec', files })
402
- }
403
-
404
- const rootFiles = []
405
- try {
406
- for (const entry of readdirSync(sillyspecDir, { withFileTypes: true })) {
407
- if (!entry.isFile() || !isViewableSillyspecDoc(entry.name)) continue
408
- const filePath = join(sillyspecDir, entry.name)
409
- if (seen.has(filePath)) continue
410
- const s = statSync(filePath)
411
- rootFiles.push({
412
- name: entry.name,
413
- path: filePath,
414
- title: titleFromSillyspecDoc(filePath, entry.name),
415
- extension: sillyspecDocExt(entry.name).slice(1),
416
- size: s.size,
417
- mtime: s.mtime.toISOString()
418
- })
419
- }
420
- } catch {}
421
-
422
- if (rootFiles.length > 0) {
423
- groups.unshift({ key: 'root', label: '📄 .sillyspec', project: '.sillyspec', files: rootFiles })
424
- }
425
-
426
- return { groups }
427
- }
428
-
429
326
  /**
430
- * Parse project state from .sillyspec SQLite database via CLI.
431
- * Progress data is stored in SQLite (.sillyspec/.runtime/sillyspec.db),
432
- * accessed through `sillyspec progress show`.
327
+ * Parse project state from .sillyspec directory
433
328
  * @param {string} projectPath - Path to the project directory
434
- * @returns {object|null} Project state with currentStage, stages, lastActive
329
+ * @returns {object} Project state with currentStage, nextStep, progress, stages, specs, lastActive
435
330
  */
436
331
  export function parseProjectState(projectPath) {
437
332
  const sillyspecDir = join(projectPath, '.sillyspec')
438
- const dbPath = join(sillyspecDir, '.runtime', 'sillyspec.db')
439
333
 
440
- if (!existsSync(sillyspecDir) || !existsSync(dbPath)) {
334
+ if (!existsSync(sillyspecDir)) {
441
335
  return null
442
336
  }
443
337
 
444
338
  let currentStage = ''
339
+ let nextStep = null
340
+ let progress = { stages: {} }
341
+ let stages = []
342
+ let specs = []
445
343
  let lastActive = null
446
344
 
447
- // Use DB file mtime as lastActive indicator
448
- try {
449
- const s = statSync(dbPath)
450
- lastActive = s.mtime.toISOString()
451
- } catch {}
345
+ // Read progress.json for current stage
346
+ const progressPath = join(sillyspecDir, '.runtime', 'progress.json')
347
+ if (existsSync(progressPath)) {
348
+ try {
349
+ const progressData = JSON.parse(readFileSync(progressPath, 'utf-8'))
350
+ progress = progressData
351
+ currentStage = progressData.currentStage || ''
352
+ stages = Object.keys(progressData.stages || {})
353
+
354
+ // Find last active
355
+ if (progressData.lastActive) lastActive = progressData.lastActive
356
+ if (progressData.stages) {
357
+ for (const [stageName, stageData] of Object.entries(progressData.stages)) {
358
+ if (stageData.lastActive || stageData.startedAt) {
359
+ const t = stageData.lastActive || stageData.startedAt
360
+ if (!lastActive || new Date(t) > new Date(lastActive)) lastActive = t
361
+ }
362
+ }
363
+ }
364
+ } catch (err) {
365
+ // Progress file exists but couldn't be parsed
366
+ }
367
+ }
452
368
 
453
- // Use CLI to read current stage from SQLite
454
- try {
455
- const output = execSync('sillyspec progress show 2>/dev/null', {
456
- cwd: projectPath, encoding: 'utf-8', timeout: 5000
457
- })
458
- const stageMatch = output.match(/当前阶段:\s*(\S+)/)
459
- if (stageMatch) currentStage = stageMatch[1]
460
- } catch {
461
- // CLI unavailable or no active change
369
+ // List all spec files
370
+ const specsDir = join(sillyspecDir, 'specs')
371
+ if (existsSync(specsDir)) {
372
+ try {
373
+ const specFiles = readdirSync(specsDir)
374
+ .filter(f => f.endsWith('.md'))
375
+ .sort()
376
+
377
+ specs = specFiles.map(f => {
378
+ const specPath = join(specsDir, f)
379
+ try {
380
+ const content = readFileSync(specPath, 'utf-8')
381
+ const titleMatch = content.match(/^#\s+(.+)$/m)
382
+ return {
383
+ name: f,
384
+ title: titleMatch ? titleMatch[1] : f,
385
+ path: specPath
386
+ }
387
+ } catch {
388
+ return { name: f, title: f, path: specPath }
389
+ }
390
+ })
391
+ } catch (err) {
392
+ // Specs directory couldn't be read
393
+ }
462
394
  }
463
395
 
464
396
  return {
465
397
  currentStage,
466
- nextStep: null,
467
- progress: { stages: {} },
468
- stages: [],
469
- specs: [],
398
+ nextStep,
399
+ progress,
400
+ stages,
401
+ specs,
470
402
  lastActive
471
403
  }
472
404
  }
@@ -2,7 +2,7 @@ import chokidar from 'chokidar'
2
2
  import { join, basename, dirname, sep } from 'path'
3
3
  import { homedir } from 'os'
4
4
  import { existsSync, readdirSync, realpathSync } from 'fs'
5
- import { parseProjectState, parseProjectOverview } from './parser.js'
5
+ import { parseProjectState } from './parser.js'
6
6
 
7
7
  let watcher = null
8
8
  let updateCallback = null
@@ -146,12 +146,8 @@ function scanSelf(seen) {
146
146
  const cwd = process.cwd()
147
147
  const projects = []
148
148
 
149
- let realPath
150
- try { realPath = realpathSync(cwd) } catch { realPath = cwd }
151
- const normalizedPath = realPath.toLowerCase()
152
-
153
- if (!seen.has(normalizedPath)) {
154
- seen.add(normalizedPath)
149
+ if (!seen.has(cwd)) {
150
+ seen.add(cwd)
155
151
  const sillyspecPath = join(cwd, '.sillyspec')
156
152
  if (existsSync(sillyspecPath)) {
157
153
  projects.push({
@@ -204,9 +200,8 @@ export function startWatcher(callback) {
204
200
  // Parse initial states
205
201
  for (const project of projects) {
206
202
  const state = parseProjectState(project.path)
207
- const overview = parseProjectOverview(project.path)
208
203
  if (state) {
209
- projectStates.set(project.name, { ...project, state, overview })
204
+ projectStates.set(project.name, { ...project, state })
210
205
  }
211
206
  }
212
207
 
@@ -266,9 +261,8 @@ async function handleFileChange(filePath) {
266
261
  const project = projectStates.get(projectName)
267
262
  if (project) {
268
263
  const newState = parseProjectState(project.path)
269
- const overview = parseProjectOverview(project.path)
270
264
  if (newState) {
271
- projectStates.set(projectName, { ...project, state: newState, overview })
265
+ projectStates.set(projectName, { ...project, state: newState })
272
266
  }
273
267
  }
274
268
 
@@ -286,13 +280,11 @@ async function rescanProjects() {
286
280
  for (const project of projects) {
287
281
  if (!projectStates.has(project.name)) {
288
282
  const state = parseProjectState(project.path)
289
- const overview = parseProjectOverview(project.path)
290
283
  if (state) {
291
284
  projectStates.set(project.name, {
292
285
  name: project.name,
293
286
  path: project.path,
294
- state,
295
- overview
287
+ state
296
288
  })
297
289
  }
298
290
  }