@specship/specship-win32-arm64 0.12.0 → 0.13.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 (85) hide show
  1. package/lib/dist/bin/specship.js +48 -12
  2. package/lib/dist/bin/specship.js.map +1 -1
  3. package/lib/dist/db/migrations.d.ts +1 -1
  4. package/lib/dist/db/migrations.d.ts.map +1 -1
  5. package/lib/dist/db/migrations.js +16 -1
  6. package/lib/dist/db/migrations.js.map +1 -1
  7. package/lib/dist/db/schema.sql +10 -0
  8. package/lib/dist/db/spec-queries.d.ts +6 -0
  9. package/lib/dist/db/spec-queries.d.ts.map +1 -1
  10. package/lib/dist/db/spec-queries.js +8 -0
  11. package/lib/dist/db/spec-queries.js.map +1 -1
  12. package/lib/dist/index.d.ts +8 -0
  13. package/lib/dist/index.d.ts.map +1 -1
  14. package/lib/dist/index.js +9 -0
  15. package/lib/dist/index.js.map +1 -1
  16. package/lib/dist/server/cli.js +9 -7
  17. package/lib/dist/server/product-version.js +44 -0
  18. package/lib/dist/server/routes/claude.js +153 -42
  19. package/lib/dist/server/routes/config.js +35 -0
  20. package/lib/dist/server/routes/graph.js +22 -1
  21. package/lib/dist/server/routes/mcp.js +510 -0
  22. package/lib/dist/server/routes/projects.js +31 -3
  23. package/lib/dist/server/routes/status.js +3 -0
  24. package/lib/dist/server/routes/workflow.js +44 -3
  25. package/lib/dist/server/server-config.js +38 -0
  26. package/lib/dist/server/server.js +128 -58
  27. package/lib/dist/statusline/active-run.d.ts +2 -2
  28. package/lib/dist/statusline/active-run.d.ts.map +1 -1
  29. package/lib/dist/statusline/active-run.js +2 -2
  30. package/lib/dist/statusline/active-run.js.map +1 -1
  31. package/lib/dist/statusline/render.d.ts.map +1 -1
  32. package/lib/dist/statusline/render.js +41 -5
  33. package/lib/dist/statusline/render.js.map +1 -1
  34. package/lib/dist/statusline/types.d.ts +15 -0
  35. package/lib/dist/statusline/types.d.ts.map +1 -1
  36. package/lib/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  37. package/lib/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  38. package/lib/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  39. package/lib/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  40. package/lib/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  41. package/lib/dist/ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  42. package/lib/dist/ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  43. package/lib/dist/ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  44. package/lib/dist/ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  45. package/lib/dist/ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  46. package/lib/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  47. package/lib/dist/ui/assets/index-D8e4LiVb.js +51 -0
  48. package/lib/dist/ui/assets/index-ezwX2EfG.css +1 -0
  49. package/lib/dist/ui/favicon-16.png +0 -0
  50. package/lib/dist/ui/favicon-180.png +0 -0
  51. package/lib/dist/ui/favicon-32.png +0 -0
  52. package/lib/dist/ui/favicon.svg +20 -0
  53. package/lib/dist/ui/index.html +17 -0
  54. package/lib/dist/update/resolve-latest.d.ts +14 -0
  55. package/lib/dist/update/resolve-latest.d.ts.map +1 -0
  56. package/lib/dist/update/resolve-latest.js +29 -0
  57. package/lib/dist/update/resolve-latest.js.map +1 -0
  58. package/lib/dist/update/run-installer.d.ts +13 -0
  59. package/lib/dist/update/run-installer.d.ts.map +1 -0
  60. package/lib/dist/update/run-installer.js +43 -0
  61. package/lib/dist/update/run-installer.js.map +1 -0
  62. package/lib/dist/update/updater.d.ts +53 -0
  63. package/lib/dist/update/updater.d.ts.map +1 -0
  64. package/lib/dist/update/updater.js +164 -0
  65. package/lib/dist/update/updater.js.map +1 -0
  66. package/lib/dist/workflows/eta.d.ts +42 -0
  67. package/lib/dist/workflows/eta.d.ts.map +1 -0
  68. package/lib/dist/workflows/eta.js +146 -0
  69. package/lib/dist/workflows/eta.js.map +1 -0
  70. package/lib/dist/workflows/executor.d.ts.map +1 -1
  71. package/lib/dist/workflows/executor.js +25 -6
  72. package/lib/dist/workflows/executor.js.map +1 -1
  73. package/lib/dist/workflows/runners/prompt.d.ts +6 -0
  74. package/lib/dist/workflows/runners/prompt.d.ts.map +1 -1
  75. package/lib/dist/workflows/runners/prompt.js +10 -0
  76. package/lib/dist/workflows/runners/prompt.js.map +1 -1
  77. package/lib/dist/workflows/runners/types.d.ts +12 -0
  78. package/lib/dist/workflows/runners/types.d.ts.map +1 -1
  79. package/lib/node_modules/.package-lock.json +1 -1
  80. package/lib/package.json +3 -2
  81. package/package.json +1 -1
  82. package/lib/dist/server/ssr/md.mjs +0 -83
  83. package/lib/dist/server/ssr/public/app.css +0 -1
  84. package/lib/dist/server/ssr/render.mjs +0 -564
  85. package/lib/dist/server/ssr/routes.js +0 -73
@@ -0,0 +1 @@
1
+ @font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2) format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-latin-wght-normal-BgDaEnEv.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2) format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Mono Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2) format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Geist Mono Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2) format("woff2-variations");unicode-range:U+2000-2001,U+2004-2008,U+200A,U+23B8-23BD,U+2500-259F}@font-face{font-family:Geist Mono Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2) format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Mono Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2) format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2) format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root,[data-theme=dark]{color-scheme:dark;--bg-canvas: #0E1014;--bg-canvas-2: #0B0D11;--bg-panel: #161A21;--bg-panel-2: #1A1F28;--bg-elevated: #20262F;--bg-hover: rgba(255,255,255,.04);--bg-active: rgba(255,255,255,.07);--border-subtle: rgba(255,255,255,.07);--border-strong: rgba(255,255,255,.12);--border-focus: #3B82F6;--text-primary: #E8EBF0;--text-secondary: #9AA3B2;--text-muted: #5C6573;--text-faint: #424B59;--accent: #3B82F6;--accent-strong: #2563EB;--accent-soft: rgba(59,130,246,.14);--accent-line: rgba(59,130,246,.35);--node-code: #A586F5;--node-spec: #5B93F2;--node-test: #46C26B;--node-route: #29D2BE;--node-code-soft: rgba(165,134,245,.14);--node-spec-soft: rgba(91,147,242,.14);--node-test-soft: rgba(70,194,107,.14);--node-route-soft: rgba(41,210,190,.14);--node-text: #ffffff;--dot-grid: rgba(255,255,255,.045);--success: #46C26B;--success-soft: rgba(70,194,107,.13);--warn: #E5A50A;--warn-soft: rgba(229,165,10,.14);--error: #F2555A;--error-soft: rgba(242,85,90,.14);--info: #58A6FF;--info-soft: rgba(88,166,255,.13);--font-ui: "Geist", system-ui, -apple-system, sans-serif;--font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;--fs-title: 22px;--fs-base: 13px;--fs-sm: 12px;--fs-xs: 11px;--fs-big: 28px;--r-sm: 5px;--r-md: 8px;--r-lg: 12px;--r-pill: 999px;--motion-fast: .1s ease;--shadow-pop: 0 12px 32px -8px rgba(0,0,0,.6), 0 2px 8px rgba(0,0,0,.4);--shadow-panel: 0 1px 0 rgba(255,255,255,.02) inset;--sidebar-w: 232px;--sidebar-w-collapsed: 56px;--status-h: 30px}[data-theme=light]{color-scheme:light;--bg-canvas: #F4F6F8;--bg-canvas-2: #E9ECF1;--bg-panel: #FFFFFF;--bg-panel-2: #F8FAFC;--bg-elevated: #FFFFFF;--bg-hover: rgba(15,23,42,.045);--bg-active: rgba(15,23,42,.07);--border-subtle: rgba(15,23,42,.1);--border-strong: rgba(15,23,42,.18);--border-focus: #2563EB;--text-primary: #161B22;--text-secondary: #4A5566;--text-muted: #7B8696;--text-faint: #A9B2C0;--accent: #2563EB;--accent-strong: #2563EB;--accent-soft: rgba(37,99,235,.1);--accent-line: rgba(37,99,235,.3);--node-code: #7C56E0;--node-spec: #2F6FE0;--node-test: #1E9E4C;--node-route: #0E9C8E;--node-code-soft: rgba(124,86,224,.12);--node-spec-soft: rgba(47,111,224,.12);--node-test-soft: rgba(30,158,76,.12);--node-route-soft: rgba(14,156,142,.12);--node-text: #1A2230;--dot-grid: rgba(15,23,42,.07);--success: #1E9E4C;--success-soft: rgba(30,158,76,.12);--warn: #B27500;--warn-soft: rgba(178,117,0,.13);--error: #DC4A4F;--error-soft: rgba(220,74,79,.12);--info: #2563EB;--info-soft: rgba(37,99,235,.11);--shadow-pop: 0 12px 32px -10px rgba(15,23,42,.22), 0 2px 8px rgba(15,23,42,.1);--shadow-panel: 0 1px 0 rgba(15,23,42,.02) inset}[data-density=compact]{--fs-title: 19px;--status-h: 26px;--sidebar-w: 204px}[data-density=compact] .card-pad{padding:10px}*{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;background:var(--bg-canvas);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--fs-base);line-height:1.45;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-feature-settings:"cv01","cv03","ss03";overflow:hidden}#root{height:100%}::selection{background:var(--accent-soft)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#ffffff14;border-radius:999px;border:2px solid transparent;background-clip:content-box}::-webkit-scrollbar-thumb:hover{background:#ffffff29;background-clip:content-box}.mono{font-family:var(--font-mono);font-feature-settings:"zero"}.tnum{font-variant-numeric:tabular-nums}.btn{font-family:var(--font-ui);font-size:var(--fs-base);font-weight:500;border:1px solid transparent;border-radius:var(--r-sm);padding:6px 12px;display:inline-flex;align-items:center;gap:7px;cursor:pointer;white-space:nowrap;transition:background var(--motion-fast),border-color var(--motion-fast),color var(--motion-fast);user-select:none}.btn svg{width:14px;height:14px;flex-shrink:0}.btn-primary{background:var(--accent-strong);color:#fff}.btn-primary.is-hover,.btn-primary:hover:not(:disabled){background:var(--accent)}.btn-secondary{background:var(--bg-elevated);color:var(--text-primary);border-color:var(--border-subtle)}.btn-secondary.is-hover,.btn-secondary:hover:not(:disabled){background:var(--bg-elevated);border-color:var(--border-strong)}.btn-secondary.is-active,.btn-secondary:active:not(:disabled){background:var(--bg-active)}.btn-ghost{background:transparent;color:var(--text-secondary)}.btn-ghost.is-hover,.btn-ghost:hover:not(:disabled){background:var(--bg-hover);color:var(--text-primary)}.btn-ghost.is-active,.btn-ghost:active:not(:disabled){background:var(--bg-active)}.btn-destructive{background:var(--error-soft);color:var(--error);border-color:#f2555a4d}.btn-destructive.is-hover,.btn-destructive:hover:not(:disabled){background:#f2555a38}.btn-sm{padding:4px 9px;font-size:var(--fs-sm)}.btn-xs{padding:2px 7px;font-size:var(--fs-xs);border-radius:4px}.btn:disabled{opacity:.4;cursor:not-allowed}.pill{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-xs);font-weight:600;padding:2px 8px;border-radius:var(--r-pill);letter-spacing:.01em;white-space:nowrap;line-height:1.5}.pill svg{width:11px;height:11px}.pill-dot{width:6px;height:6px;border-radius:50%}.card{background:var(--bg-panel);border:1px solid var(--border-subtle);border-radius:var(--r-lg)}.card-pad{padding:14px}.card-btn{cursor:pointer;transition:background var(--motion-fast),border-color var(--motion-fast)}.card-btn:hover:not(:disabled){background:var(--bg-panel-2);border-color:var(--border-strong)}.card-btn:active:not(:disabled){background:var(--bg-elevated)}.list-row{transition:background var(--motion-fast)}.list-row:hover{background:var(--bg-hover)}.list-row:active{background:var(--bg-active)}.list-row.selected,.list-row[aria-selected=true]{background:var(--accent-soft)}.seg{display:inline-flex;background:var(--bg-canvas);border:1px solid var(--border-subtle);border-radius:7px;padding:2px;gap:2px}.seg-btn{border:none;cursor:pointer;border-radius:5px;padding:5px 12px;font-size:var(--fs-sm);font-weight:500;font-family:var(--font-ui);background:transparent;color:var(--text-muted);transition:background var(--motion-fast),color var(--motion-fast),box-shadow var(--motion-fast)}.seg-btn.sm{padding:3px 9px}.seg-btn:hover:not(:disabled):not(.active){background:var(--bg-hover);color:var(--text-secondary)}.seg-btn:active{background:var(--bg-active)}.seg-btn:disabled{opacity:.4;cursor:not-allowed}.seg-btn.active{background:var(--bg-elevated);color:var(--text-primary);box-shadow:0 1px 2px #0000004d}.input{background:var(--bg-canvas);border:1px solid var(--border-subtle);border-radius:var(--r-sm);color:var(--text-primary);font-family:var(--font-ui);font-size:var(--fs-base);padding:6px 10px;outline:none;transition:border-color var(--motion-fast),box-shadow var(--motion-fast)}.input:focus{border-color:var(--border-focus);box-shadow:0 0 0 3px var(--accent-soft)}.input::placeholder{color:var(--text-muted)}.eyebrow{font-size:var(--fs-xs);font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted)}:focus-visible{outline:2px solid var(--accent);outline-offset:1px}.sp-prose{font-size:15px;line-height:1.62;color:var(--text-secondary);text-wrap:pretty}.sp-prose strong,.sp-strong{color:var(--text-primary);font-weight:600}.sp-code{font-family:var(--font-mono);font-size:.86em;font-feature-settings:"zero";background:var(--bg-canvas);color:var(--text-primary);padding:1px 6px;border-radius:5px;border:1px solid var(--border-subtle);white-space:nowrap}.sp-kw{font-family:var(--font-mono);font-size:.7em;font-weight:600;letter-spacing:.05em;padding:1px 6px;border-radius:4px;white-space:nowrap;position:relative;top:-1px}.sp-kw-must{color:var(--node-spec);background:var(--node-spec-soft)}.sp-kw-should{color:var(--warn);background:var(--warn-soft)}.sp-kw-may{color:var(--text-muted);background:#ffffff0d}[data-theme=light] .sp-kw-may{background:#0f172a0d}.sp-statement{position:relative;padding:16px 18px 16px 20px;border-radius:var(--r-md);background:var(--bg-panel);border:1px solid var(--border-subtle);overflow:hidden}.sp-statement:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--sp-accent, var(--node-spec))}.sp-crit{display:flex;gap:12px;align-items:flex-start;padding:12px 14px;border-top:1px solid var(--border-subtle);transition:background var(--motion-fast)}.sp-crit:first-child{border-top:none}.sp-crit:hover{background:var(--bg-hover)}.sp-crit-mark{width:20px;height:20px;border-radius:6px;flex-shrink:0;margin-top:1px;display:grid;place-items:center}.sp-crit-id{font-family:var(--font-mono);font-size:11px;font-weight:600;flex-shrink:0;color:var(--text-muted);margin-top:2px;width:22px}.sp-crit-text{font-size:13.5px;line-height:1.55;color:var(--text-secondary);flex:1;text-wrap:pretty}.sp-meta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:2px;background:var(--border-subtle);border:1px solid var(--border-subtle);border-radius:var(--r-md);overflow:hidden}.sp-meta-cell{background:var(--bg-panel);padding:9px 12px}.sp-meta-k{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.sp-meta-v{font-size:13px;color:var(--text-primary);margin-top:3px}.sp-doc{max-width:700px}.sp-breadcrumb{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--text-muted);margin-bottom:20px}.sp-breadcrumb .sp-path{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sp-title{font-size:27px;font-weight:650;letter-spacing:-.025em;line-height:1.16;margin:0 0 16px;text-wrap:pretty}.sp-metaline{display:flex;align-items:center;gap:11px;flex-wrap:wrap;font-size:12.5px;color:var(--text-muted)}.sp-metaline .sep{width:3px;height:3px;border-radius:50%;background:var(--text-faint);flex-shrink:0}.sp-metaline .fact b{color:var(--text-secondary);font-weight:550}.sp-label{display:flex;align-items:center;gap:8px;margin-bottom:12px;font-size:11px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--text-muted)}.sp-label .ct{margin-left:auto;font-family:var(--font-mono);font-size:11px;letter-spacing:0;text-transform:none}.sp-sec{margin-bottom:30px}.sp-statement.lead{padding:20px 22px 20px 24px;background:var(--bg-canvas)}.sp-statement.lead .sp-prose{font-size:16.5px;line-height:1.7;color:var(--text-primary)}.sp-rationale{font-size:14px;line-height:1.72;color:var(--text-secondary);padding-left:16px;border-left:2px solid var(--border-strong);text-wrap:pretty}.sp-rationale .sp-code{font-size:.84em}.sp-crit-text{font-size:14px}.sp-edit-bar{position:sticky;top:0;z-index:6;display:flex;align-items:center;gap:12px;padding:11px 28px;background:color-mix(in srgb,var(--bg-panel) 88%,transparent);border-bottom:1px solid var(--border-subtle);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.sp-edit-bar:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent)}.sp-fl{font-size:10px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted);font-weight:600;display:block;margin-bottom:7px}.sp-input-lg{width:100%;font-size:19px;font-weight:600;letter-spacing:-.01em;padding:10px 14px;line-height:1.3}.sp-textarea{width:100%;resize:vertical;line-height:1.65;display:block;font-family:var(--font-mono);font-size:13px;min-height:110px;padding:13px 15px;tab-size:2}.sp-textarea::placeholder{font-family:var(--font-mono)}.sp-edit-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 22px;margin-bottom:4px}@media (max-width: 1100px){.sp-edit-grid{grid-template-columns:1fr}}.sp-select{appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:28px;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%238792a3" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>');background-repeat:no-repeat;background-position:right 9px center;background-size:13px}.sp-write-area{position:relative}.sp-kw-legend{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:9px}.sp-kw-legend .label{font-size:11px;color:var(--text-muted)}.sp-crit-edit{display:flex;gap:10px;align-items:center;padding:9px 10px 9px 8px;border-top:1px solid var(--border-subtle)}.sp-crit-edit:first-child{border-top:none}.sp-crit-grip{color:var(--text-muted);cursor:grab;flex-shrink:0;display:grid;place-items:center;opacity:.5}.sp-crit-grip:hover{opacity:1}.sp-crit-num{font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--text-muted);width:22px;flex-shrink:0;text-align:center}.sp-icon-btn{width:28px;height:28px;border-radius:var(--r-sm);flex-shrink:0;display:grid;place-items:center;cursor:pointer;background:transparent;border:1px solid transparent;color:var(--text-muted);transition:background var(--motion-fast),color var(--motion-fast)}.sp-icon-btn:hover:not(:disabled){background:var(--error-soft);color:var(--error)}.sp-dirty-dot{width:7px;height:7px;border-radius:50%;background:var(--warn);box-shadow:0 0 0 3px var(--warn-soft)}.sp-status-auto{display:flex;align-items:center;gap:10px;padding:8px 12px;min-height:38px;box-sizing:border-box;background:var(--bg-canvas);border:1px solid var(--border-subtle);border-radius:var(--r-md)}.row{display:flex;align-items:center}.col{display:flex;flex-direction:column}.gap-2{gap:2px}.gap-4{gap:4px}.gap-6{gap:6px}.gap-8{gap:8px}.gap-10{gap:10px}.gap-12{gap:12px}.gap-16{gap:16px}.grow{flex:1}.muted{color:var(--text-muted)}.secondary{color:var(--text-secondary)}.tabular{font-variant-numeric:tabular-nums}.scroll-y{overflow-y:auto;overflow-x:hidden}@keyframes pulsePill{0%{box-shadow:0 0 0 0 currentColor}to{box-shadow:0 0 0 8px transparent}}@keyframes slideInRight{0%{opacity:0;transform:translate(14px)}to{opacity:1;transform:translate(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes runningDash{to{stroke-dashoffset:-16}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes skeleton{0%{opacity:.5}50%{opacity:.9}to{opacity:.5}}.skel{background:var(--bg-elevated);border-radius:5px;animation:skeleton 1.4s ease-in-out infinite}@media (prefers-reduced-motion: reduce){*{animation-duration:.001ms!important;transition-duration:.001ms!important}}:root{--font-ui: "Geist Variable", "Geist", system-ui, -apple-system, sans-serif;--font-mono: "Geist Mono Variable", "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace}
Binary file
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="tile" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#12161E"></stop><stop offset="1" stop-color="#0B0D11"></stop>
5
+ </linearGradient>
6
+ <linearGradient id="lead" x1="18" y1="11" x2="26" y2="21" gradientUnits="userSpaceOnUse">
7
+ <stop stop-color="#6FA0F6"></stop><stop offset="1" stop-color="#5B93F2"></stop>
8
+ </linearGradient>
9
+ </defs>
10
+ <rect width="32" height="32" rx="7.2" fill="url(#tile)"></rect>
11
+ <rect x="0.5" y="0.5" width="31" height="31" rx="6.7" fill="none" stroke="rgba(255,255,255,0.07)"></rect>
12
+ <g transform="translate(4.6 4.6) scale(0.715)">
13
+ <path d="M5 16 H11 M11 9 L23 16 L11 23" stroke="rgba(150,165,200,0.5)" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" fill="none"></path>
14
+ <line x1="11" y1="9" x2="11" y2="23" stroke="rgba(150,165,200,0.4)" stroke-width="1.9" stroke-linecap="round"></line>
15
+ <circle cx="23" cy="16" r="3.6" fill="url(#lead)"></circle>
16
+ <circle cx="11" cy="9" r="2.9" fill="#A586F5"></circle>
17
+ <circle cx="11" cy="23" r="2.9" fill="#46C26B"></circle>
18
+ <circle cx="5" cy="16" r="2.1" fill="#29D2BE"></circle>
19
+ </g>
20
+ </svg>
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>SpecShip</title>
7
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
9
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
10
+ <link rel="apple-touch-icon" sizes="180x180" href="/favicon-180.png" />
11
+ <script type="module" crossorigin src="/assets/index-D8e4LiVb.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-ezwX2EfG.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolve the latest published version for an install method
3
+ * (REQ-CLI-UPDATE-002 / 004). `fetch` is injected so the parsing is testable.
4
+ *
5
+ * - bundle → the GitHub `releases/latest` redirect lands on `.../tag/vX.Y.Z`;
6
+ * we read the version out of the final URL (same source `install.sh` uses).
7
+ * - npm → the registry packument's `dist-tags.latest`.
8
+ *
9
+ * Both throw on an unreachable / non-OK source so the caller (`runUpdate`) fails
10
+ * cleanly without touching the existing install.
11
+ */
12
+ import type { InstallMethod } from './updater';
13
+ export declare function resolveLatestVersion(method: InstallMethod, fetchFn?: typeof fetch): Promise<string>;
14
+ //# sourceMappingURL=resolve-latest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-latest.d.ts","sourceRoot":"","sources":["../../src/update/resolve-latest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAK/C,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,MAAM,CAAC,CAgBjB"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveLatestVersion = resolveLatestVersion;
4
+ const GITHUB_LATEST = 'https://github.com/selvakumarEsra/specship/releases/latest';
5
+ const NPM_PACKUMENT = 'https://registry.npmjs.org/@specship/specship';
6
+ async function resolveLatestVersion(method, fetchFn = fetch) {
7
+ if (method === 'npm') {
8
+ const res = await fetchFn(NPM_PACKUMENT);
9
+ if (!res.ok)
10
+ throw new Error(`npm registry responded ${res.status}`);
11
+ const body = (await res.json());
12
+ const latest = body['dist-tags']?.latest;
13
+ if (!latest)
14
+ throw new Error('npm registry returned no dist-tags.latest');
15
+ return stripV(latest);
16
+ }
17
+ // bundle
18
+ const res = await fetchFn(GITHUB_LATEST);
19
+ if (!res.ok)
20
+ throw new Error(`GitHub responded ${res.status}`);
21
+ const tag = (res.url || '').match(/\/releases\/tag\/([^/?#]+)/)?.[1];
22
+ if (!tag)
23
+ throw new Error(`could not read a release tag from ${res.url || '(no url)'}`);
24
+ return stripV(tag);
25
+ }
26
+ function stripV(v) {
27
+ return v.replace(/^v/, '');
28
+ }
29
+ //# sourceMappingURL=resolve-latest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-latest.js","sourceRoot":"","sources":["../../src/update/resolve-latest.ts"],"names":[],"mappings":";;AAgBA,oDAmBC;AAtBD,MAAM,aAAa,GAAG,4DAA4D,CAAC;AACnF,MAAM,aAAa,GAAG,+CAA+C,CAAC;AAE/D,KAAK,UAAU,oBAAoB,CACxC,MAAqB,EACrB,UAAwB,KAAK;IAE7B,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA0C,CAAC;QACzE,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QACzC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC1E,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAED,SAAS;IACT,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,4BAA4B,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,GAAG,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC,CAAC;IACxF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { InstallMethod } from './updater';
2
+ export interface InstallerEnv {
3
+ installDir: string;
4
+ binDir: string;
5
+ }
6
+ export interface InstallerCommand {
7
+ command: string;
8
+ args: string[];
9
+ extraEnv: Record<string, string>;
10
+ }
11
+ export declare function installerCommand(method: InstallMethod, env: InstallerEnv): InstallerCommand;
12
+ export declare function runInstaller(method: InstallMethod, env: InstallerEnv): Promise<void>;
13
+ //# sourceMappingURL=run-installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-installer.d.ts","sourceRoot":"","sources":["../../src/update/run-installer.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG/C,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAW3F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAapF"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installerCommand = installerCommand;
4
+ exports.runInstaller = runInstaller;
5
+ /**
6
+ * The real installer adapter for `specship update` (REQ-CLI-UPDATE-002).
7
+ *
8
+ * `installerCommand` is pure (built + tested here); `runInstaller` is the thin
9
+ * spawn wrapper the CLI drives. A non-zero exit rejects, which `runUpdate`
10
+ * surfaces as a clean failure that leaves the existing install intact
11
+ * (REQ-CLI-UPDATE-004.A2).
12
+ */
13
+ const node_child_process_1 = require("node:child_process");
14
+ const updater_1 = require("./updater");
15
+ function installerCommand(method, env) {
16
+ if (method === 'npm') {
17
+ return { command: 'npm', args: ['i', '-g', '@specship/specship@latest'], extraEnv: {} };
18
+ }
19
+ // bundle: re-run install.sh, threading the existing install/bin dirs through
20
+ // so the update lands exactly where the original install did.
21
+ return {
22
+ command: 'sh',
23
+ args: ['-c', updater_1.BUNDLE_INSTALL_CMD],
24
+ extraEnv: { SPECSHIP_INSTALL_DIR: env.installDir, SPECSHIP_BIN_DIR: env.binDir },
25
+ };
26
+ }
27
+ function runInstaller(method, env) {
28
+ const { command, args, extraEnv } = installerCommand(method, env);
29
+ return new Promise((resolve, reject) => {
30
+ const child = (0, node_child_process_1.spawn)(command, args, {
31
+ stdio: 'inherit',
32
+ env: { ...process.env, ...extraEnv },
33
+ });
34
+ child.on('error', reject);
35
+ child.on('exit', (code) => {
36
+ if (code === 0)
37
+ resolve();
38
+ else
39
+ reject(new Error(`${method} updater exited with code ${code ?? 'null'}`));
40
+ });
41
+ });
42
+ }
43
+ //# sourceMappingURL=run-installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-installer.js","sourceRoot":"","sources":["../../src/update/run-installer.ts"],"names":[],"mappings":";;AAuBA,4CAWC;AAED,oCAaC;AAjDD;;;;;;;GAOG;AACH,2DAA2C;AAE3C,uCAA+C;AAa/C,SAAgB,gBAAgB,CAAC,MAAqB,EAAE,GAAiB;IACvE,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,2BAA2B,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1F,CAAC;IACD,6EAA6E;IAC7E,8DAA8D;IAC9D,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,CAAC,IAAI,EAAE,4BAAkB,CAAC;QAChC,QAAQ,EAAE,EAAE,oBAAoB,EAAE,GAAG,CAAC,UAAU,EAAE,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE;KACjF,CAAC;AACJ,CAAC;AAED,SAAgB,YAAY,CAAC,MAAqB,EAAE,GAAiB;IACnE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,IAAA,0BAAK,EAAC,OAAO,EAAE,IAAI,EAAE;YACjC,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE;SACrC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;;gBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,MAAM,6BAA6B,IAAI,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC;QACjF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,53 @@
1
+ export type InstallMethod = 'bundle' | 'npm' | 'unknown';
2
+ /** Exit code from `--check` when a newer version is available (distinct from
3
+ * the `1` used for real errors, so scripts/hooks can gate on it). */
4
+ export declare const CHECK_UPDATE_AVAILABLE = 10;
5
+ /** The manual fallbacks we point users at when we can't self-update. */
6
+ export declare const BUNDLE_INSTALL_CMD = "curl -fsSL https://raw.githubusercontent.com/selvakumarEsra/specship/main/install.sh | sh";
7
+ export declare const NPM_INSTALL_CMD = "npm i -g @specship/specship@latest";
8
+ export interface UpdateDeps {
9
+ /** The version currently running (package.json version). */
10
+ currentVersion: string;
11
+ /** Determine how this binary was installed. */
12
+ detect: () => InstallMethod;
13
+ /** Resolve the latest published version for a method. Throws when the
14
+ * release source is unreachable. */
15
+ resolveLatest: (method: InstallMethod) => Promise<string>;
16
+ /** Perform the real update for a method. Throws when the underlying updater
17
+ * (bundle installer / npm) exits non-zero. */
18
+ runInstaller: (method: InstallMethod) => Promise<void>;
19
+ /** Bundle install dir (for messages). */
20
+ installDir: string;
21
+ /** Bundle symlink dir (for messages). */
22
+ binDir: string;
23
+ }
24
+ export interface UpdateOptions {
25
+ /** Read-only: report status, install nothing. */
26
+ check?: boolean;
27
+ }
28
+ export interface UpdateResult {
29
+ exitCode: number;
30
+ method: InstallMethod;
31
+ currentVersion: string;
32
+ latestVersion?: string;
33
+ updateAvailable?: boolean;
34
+ /** True only when the installer actually ran and succeeded. */
35
+ changed: boolean;
36
+ /** Human-facing output lines; the CLI prints these. */
37
+ lines: string[];
38
+ }
39
+ /**
40
+ * Which install channel produced the running binary, decided from the
41
+ * directory the running `specship.js` lives in.
42
+ *
43
+ * - under the bundle install dir (default `~/.specship`) → 'bundle'
44
+ * - under a `node_modules` tree → 'npm'
45
+ * - neither → 'unknown' (we refuse to guess)
46
+ */
47
+ export declare function detectInstallMethod(binDirname: string, installDir: string): InstallMethod;
48
+ /** Numeric, per-segment version compare (not lexical). Tolerates a leading `v`. */
49
+ export declare function compareVersions(a: string, b: string): -1 | 0 | 1;
50
+ /** True when `latest` is strictly newer than `current`. */
51
+ export declare function isUpdateAvailable(current: string, latest: string): boolean;
52
+ export declare function runUpdate(deps: UpdateDeps, opts?: UpdateOptions): Promise<UpdateResult>;
53
+ //# sourceMappingURL=updater.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updater.d.ts","sourceRoot":"","sources":["../../src/update/updater.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;AAEzD;sEACsE;AACtE,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,8FAC8D,CAAC;AAC9F,eAAO,MAAM,eAAe,uCAAuC,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,aAAa,CAAC;IAC5B;yCACqC;IACrC,aAAa,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D;mDAC+C;IAC/C,YAAY,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;IACjB,uDAAuD;IACvD,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CAMzF;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAahE;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAmGjG"}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NPM_INSTALL_CMD = exports.BUNDLE_INSTALL_CMD = exports.CHECK_UPDATE_AVAILABLE = void 0;
7
+ exports.detectInstallMethod = detectInstallMethod;
8
+ exports.compareVersions = compareVersions;
9
+ exports.isUpdateAvailable = isUpdateAvailable;
10
+ exports.runUpdate = runUpdate;
11
+ /**
12
+ * `specship update` core — self-update to the latest release (CLI-UPDATE-DOC).
13
+ *
14
+ * The orchestrator (`runUpdate`) is pure and dependency-injected: it takes the
15
+ * current version, an install-method detector, a latest-version resolver, and
16
+ * an installer runner, and returns a structured result. It never calls
17
+ * process.exit, never touches the network, and never spawns — the CLI command
18
+ * (`src/bin/specship.ts`) wires the real deps, prints `result.lines`, and exits
19
+ * with `result.exitCode`. This keeps every acceptance criterion unit-testable
20
+ * without mutating the machine's real install.
21
+ */
22
+ const node_path_1 = __importDefault(require("node:path"));
23
+ /** Exit code from `--check` when a newer version is available (distinct from
24
+ * the `1` used for real errors, so scripts/hooks can gate on it). */
25
+ exports.CHECK_UPDATE_AVAILABLE = 10;
26
+ /** The manual fallbacks we point users at when we can't self-update. */
27
+ exports.BUNDLE_INSTALL_CMD = 'curl -fsSL https://raw.githubusercontent.com/selvakumarEsra/specship/main/install.sh | sh';
28
+ exports.NPM_INSTALL_CMD = 'npm i -g @specship/specship@latest';
29
+ /**
30
+ * Which install channel produced the running binary, decided from the
31
+ * directory the running `specship.js` lives in.
32
+ *
33
+ * - under the bundle install dir (default `~/.specship`) → 'bundle'
34
+ * - under a `node_modules` tree → 'npm'
35
+ * - neither → 'unknown' (we refuse to guess)
36
+ */
37
+ function detectInstallMethod(binDirname, installDir) {
38
+ const dir = node_path_1.default.resolve(binDirname);
39
+ const root = node_path_1.default.resolve(installDir);
40
+ if (dir === root || dir.startsWith(root + node_path_1.default.sep))
41
+ return 'bundle';
42
+ if (dir.split(node_path_1.default.sep).includes('node_modules'))
43
+ return 'npm';
44
+ return 'unknown';
45
+ }
46
+ /** Numeric, per-segment version compare (not lexical). Tolerates a leading `v`. */
47
+ function compareVersions(a, b) {
48
+ const parse = (v) => v.replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
49
+ const pa = parse(a);
50
+ const pb = parse(b);
51
+ const len = Math.max(pa.length, pb.length);
52
+ for (let i = 0; i < len; i++) {
53
+ const da = pa[i] ?? 0;
54
+ const db = pb[i] ?? 0;
55
+ if (da > db)
56
+ return 1;
57
+ if (da < db)
58
+ return -1;
59
+ }
60
+ return 0;
61
+ }
62
+ /** True when `latest` is strictly newer than `current`. */
63
+ function isUpdateAvailable(current, latest) {
64
+ return compareVersions(latest, current) > 0;
65
+ }
66
+ async function runUpdate(deps, opts = {}) {
67
+ const { currentVersion } = deps;
68
+ const method = deps.detect();
69
+ // Refuse to guess when we can't tell how it was installed (REQ-CLI-UPDATE-002.A3).
70
+ if (method === 'unknown') {
71
+ return {
72
+ exitCode: 1,
73
+ method,
74
+ currentVersion,
75
+ changed: false,
76
+ lines: [
77
+ "Couldn't determine how SpecShip was installed, so it can't self-update.",
78
+ 'Update manually with whichever you used:',
79
+ ` bundle (install.sh): ${exports.BUNDLE_INSTALL_CMD}`,
80
+ ` npm: ${exports.NPM_INSTALL_CMD}`,
81
+ ],
82
+ };
83
+ }
84
+ // Resolve the latest version; an unreachable source is a clean failure
85
+ // that must not touch the existing install (REQ-CLI-UPDATE-004.A1).
86
+ let latestVersion;
87
+ try {
88
+ latestVersion = await deps.resolveLatest(method);
89
+ }
90
+ catch (err) {
91
+ return {
92
+ exitCode: 1,
93
+ method,
94
+ currentVersion,
95
+ changed: false,
96
+ lines: [`Could not reach the release source to check for updates: ${errMsg(err)}`],
97
+ };
98
+ }
99
+ const updateAvailable = isUpdateAvailable(currentVersion, latestVersion);
100
+ // --check: read-only report, never installs (REQ-CLI-UPDATE-003).
101
+ if (opts.check) {
102
+ const lines = [
103
+ `Current: ${currentVersion}`,
104
+ `Latest: ${latestVersion}`,
105
+ updateAvailable
106
+ ? `Update available → run \`specship update\` to install ${latestVersion}.`
107
+ : 'You are on the latest version.',
108
+ ];
109
+ return {
110
+ exitCode: updateAvailable ? exports.CHECK_UPDATE_AVAILABLE : 0,
111
+ method,
112
+ currentVersion,
113
+ latestVersion,
114
+ updateAvailable,
115
+ changed: false,
116
+ lines,
117
+ };
118
+ }
119
+ // Already current → no-op (REQ-CLI-UPDATE-001.A2).
120
+ if (!updateAvailable) {
121
+ return {
122
+ exitCode: 0,
123
+ method,
124
+ currentVersion,
125
+ latestVersion,
126
+ updateAvailable: false,
127
+ changed: false,
128
+ lines: [`Already up to date (${currentVersion}).`],
129
+ };
130
+ }
131
+ // Perform the update; a failing installer surfaces + leaves the install
132
+ // intact (REQ-CLI-UPDATE-004.A2).
133
+ try {
134
+ await deps.runInstaller(method);
135
+ }
136
+ catch (err) {
137
+ return {
138
+ exitCode: 1,
139
+ method,
140
+ currentVersion,
141
+ latestVersion,
142
+ updateAvailable: true,
143
+ changed: false,
144
+ lines: [`Update failed: ${errMsg(err)}`, 'Your existing install is unchanged.'],
145
+ };
146
+ }
147
+ // Success → report old → new + remind to restart (REQ-CLI-UPDATE-001.A1, 005.A1).
148
+ return {
149
+ exitCode: 0,
150
+ method,
151
+ currentVersion,
152
+ latestVersion,
153
+ updateAvailable: true,
154
+ changed: true,
155
+ lines: [
156
+ `Updated ${currentVersion} → ${latestVersion}.`,
157
+ 'Restart any running `specship serve` / Claude Code MCP session to pick up the new version.',
158
+ ],
159
+ };
160
+ }
161
+ function errMsg(err) {
162
+ return err instanceof Error ? err.message : String(err);
163
+ }
164
+ //# sourceMappingURL=updater.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/update/updater.ts"],"names":[],"mappings":";;;;;;AAkEA,kDAMC;AAGD,0CAaC;AAGD,8CAEC;AAED,8BAmGC;AAlMD;;;;;;;;;;GAUG;AACH,0DAA6B;AAI7B;sEACsE;AACzD,QAAA,sBAAsB,GAAG,EAAE,CAAC;AAEzC,wEAAwE;AAC3D,QAAA,kBAAkB,GAC7B,2FAA2F,CAAC;AACjF,QAAA,eAAe,GAAG,oCAAoC,CAAC;AAoCpE;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CAAC,UAAkB,EAAE,UAAkB;IACxE,MAAM,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,mBAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,mBAAI,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrE,IAAI,GAAG,CAAC,KAAK,CAAC,mBAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,mFAAmF;AACnF,SAAgB,eAAe,CAAC,CAAS,EAAE,CAAS;IAClD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAC1B,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,2DAA2D;AAC3D,SAAgB,iBAAiB,CAAC,OAAe,EAAE,MAAc;IAC/D,OAAO,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;AAC9C,CAAC;AAEM,KAAK,UAAU,SAAS,CAAC,IAAgB,EAAE,OAAsB,EAAE;IACxE,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAE7B,mFAAmF;IACnF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO;YACL,QAAQ,EAAE,CAAC;YACX,MAAM;YACN,cAAc;YACd,OAAO,EAAE,KAAK;YACd,KAAK,EAAE;gBACL,yEAAyE;gBACzE,0CAA0C;gBAC1C,0BAA0B,0BAAkB,EAAE;gBAC9C,0BAA0B,uBAAe,EAAE;aAC5C;SACF,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,oEAAoE;IACpE,IAAI,aAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,aAAa,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,CAAC;YACX,MAAM;YACN,cAAc;YACd,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,CAAC,4DAA4D,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;SACnF,CAAC;IACJ,CAAC;IAED,MAAM,eAAe,GAAG,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;IAEzE,kEAAkE;IAClE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,GAAG;YACZ,YAAY,cAAc,EAAE;YAC5B,YAAY,aAAa,EAAE;YAC3B,eAAe;gBACb,CAAC,CAAC,yDAAyD,aAAa,GAAG;gBAC3E,CAAC,CAAC,gCAAgC;SACrC,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,8BAAsB,CAAC,CAAC,CAAC,CAAC;YACtD,MAAM;YACN,cAAc;YACd,aAAa;YACb,eAAe;YACf,OAAO,EAAE,KAAK;YACd,KAAK;SACN,CAAC;IACJ,CAAC;IAED,mDAAmD;IACnD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO;YACL,QAAQ,EAAE,CAAC;YACX,MAAM;YACN,cAAc;YACd,aAAa;YACb,eAAe,EAAE,KAAK;YACtB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,CAAC,uBAAuB,cAAc,IAAI,CAAC;SACnD,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,kCAAkC;IAClC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,CAAC;YACX,MAAM;YACN,cAAc;YACd,aAAa;YACb,eAAe,EAAE,IAAI;YACrB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,CAAC,kBAAkB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,qCAAqC,CAAC;SAChF,CAAC;IACJ,CAAC;IAED,kFAAkF;IAClF,OAAO;QACL,QAAQ,EAAE,CAAC;QACX,MAAM;QACN,cAAc;QACd,aAAa;QACb,eAAe,EAAE,IAAI;QACrB,OAAO,EAAE,IAAI;QACb,KAAK,EAAE;YACL,WAAW,cAAc,MAAM,aAAa,GAAG;YAC/C,4FAA4F;SAC7F;KACF,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Workflow run time-to-completion estimation (WORKFLOW-ETA-DOC).
3
+ *
4
+ * Estimates the remaining machine time of a `running` run as a range — the
5
+ * sum over its not-yet-completed steps of that step's historical duration
6
+ * quantiles (p50 → low, p90 → high), computed from prior completed runs of
7
+ * the SAME workflow (REQ-ETA-001/002). Recomputing after each step completes
8
+ * is what makes the estimate tighten as the run progresses — this function is
9
+ * pure read, cheap, and safe to call on every poll.
10
+ *
11
+ * Honesty rules:
12
+ * - Human gate-wait is unbounded latency: approval request→grant spans are
13
+ * subtracted from any step duration they overlap, and a paused run gets
14
+ * a "waiting since" timestamp instead of a number (REQ-ETA-003).
15
+ * - Fewer than MIN_HISTORY_RUNS completed prior runs → no estimate at all,
16
+ * never a fabricated default (REQ-ETA-004).
17
+ *
18
+ * Step identity: the executor logs step_started/step_completed with the step
19
+ * id inside the event's data JSON (the step_id column predates that and is
20
+ * often null), hence the COALESCE.
21
+ */
22
+ import { SqliteDatabase } from '../db/sqlite-adapter';
23
+ export interface RunEtaAvailable {
24
+ available: true;
25
+ /** Optimistic remaining time — sum of remaining steps' historical medians. */
26
+ lowMs: number;
27
+ /** Pessimistic remaining time — sum of remaining steps' historical p90s. */
28
+ highMs: number;
29
+ /** How many completed prior runs the estimate is based on. */
30
+ basisRuns: number;
31
+ }
32
+ export interface RunEtaUnavailable {
33
+ available: false;
34
+ reason: 'not_found' | 'not_running' | 'paused' | 'insufficient_history';
35
+ /** For 'paused': when the run began waiting on the human (approval request). */
36
+ waitingSinceMs?: number;
37
+ }
38
+ export type RunEta = RunEtaAvailable | RunEtaUnavailable;
39
+ /** Below this many completed prior runs, report insufficient_history. */
40
+ export declare const MIN_HISTORY_RUNS = 3;
41
+ export declare function estimateRunEta(db: SqliteDatabase, runId: string): RunEta;
42
+ //# sourceMappingURL=eta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eta.d.ts","sourceRoot":"","sources":["../../src/workflows/eta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,IAAI,CAAC;IAChB,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,KAAK,CAAC;IACjB,MAAM,EAAE,WAAW,GAAG,aAAa,GAAG,QAAQ,GAAG,sBAAsB,CAAC;IACxE,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,MAAM,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAEzD,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAoBlC,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA8GxE"}
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /**
3
+ * Workflow run time-to-completion estimation (WORKFLOW-ETA-DOC).
4
+ *
5
+ * Estimates the remaining machine time of a `running` run as a range — the
6
+ * sum over its not-yet-completed steps of that step's historical duration
7
+ * quantiles (p50 → low, p90 → high), computed from prior completed runs of
8
+ * the SAME workflow (REQ-ETA-001/002). Recomputing after each step completes
9
+ * is what makes the estimate tighten as the run progresses — this function is
10
+ * pure read, cheap, and safe to call on every poll.
11
+ *
12
+ * Honesty rules:
13
+ * - Human gate-wait is unbounded latency: approval request→grant spans are
14
+ * subtracted from any step duration they overlap, and a paused run gets
15
+ * a "waiting since" timestamp instead of a number (REQ-ETA-003).
16
+ * - Fewer than MIN_HISTORY_RUNS completed prior runs → no estimate at all,
17
+ * never a fabricated default (REQ-ETA-004).
18
+ *
19
+ * Step identity: the executor logs step_started/step_completed with the step
20
+ * id inside the event's data JSON (the step_id column predates that and is
21
+ * often null), hence the COALESCE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.MIN_HISTORY_RUNS = void 0;
25
+ exports.estimateRunEta = estimateRunEta;
26
+ /** Below this many completed prior runs, report insufficient_history. */
27
+ exports.MIN_HISTORY_RUNS = 3;
28
+ /** Quantile of a non-empty sorted array (nearest-rank on small samples). */
29
+ function quantile(sortedAsc, q) {
30
+ const idx = Math.min(sortedAsc.length - 1, Math.max(0, Math.ceil(q * sortedAsc.length) - 1));
31
+ return sortedAsc[idx] ?? 0;
32
+ }
33
+ /** Overlap of [aStart, aEnd] with [bStart, bEnd], ≥ 0. */
34
+ function overlap(aStart, aEnd, bStart, bEnd) {
35
+ return Math.max(0, Math.min(aEnd, bEnd) - Math.max(aStart, bStart));
36
+ }
37
+ function estimateRunEta(db, runId) {
38
+ const run = db
39
+ .prepare(`SELECT id, workflow_name AS name, status, metadata FROM workflow_runs WHERE id = ?`)
40
+ .get(runId);
41
+ if (!run)
42
+ return { available: false, reason: 'not_found' };
43
+ if (run.status === 'paused') {
44
+ const w = db
45
+ .prepare(`SELECT MAX(created_at) AS t FROM workflow_events
46
+ WHERE workflow_run_id = ? AND event_type = 'approval_requested'`)
47
+ .get(runId);
48
+ return { available: false, reason: 'paused', waitingSinceMs: w?.t ?? undefined };
49
+ }
50
+ if (run.status !== 'running')
51
+ return { available: false, reason: 'not_running' };
52
+ const prior = db
53
+ .prepare(`SELECT id FROM workflow_runs
54
+ WHERE workflow_name = ? AND status = 'completed' AND id != ?
55
+ ORDER BY completed_at DESC LIMIT 50`)
56
+ .all(run.name, runId);
57
+ if (prior.length < exports.MIN_HISTORY_RUNS)
58
+ return { available: false, reason: 'insufficient_history' };
59
+ const placeholders = prior.map(() => '?').join(',');
60
+ const events = db
61
+ .prepare(`SELECT workflow_run_id AS rid, event_type AS et,
62
+ COALESCE(step_id, json_extract(data, '$.stepId')) AS sid,
63
+ created_at AS t
64
+ FROM workflow_events
65
+ WHERE workflow_run_id IN (${placeholders})
66
+ AND event_type IN ('step_started', 'step_completed', 'approval_requested', 'approval_granted')
67
+ ORDER BY created_at ASC`)
68
+ .all(...prior.map((p) => p.id));
69
+ // Human-wait spans per prior run: sequential request→grant pairs.
70
+ const waitSpans = new Map();
71
+ const pendingReq = new Map();
72
+ for (const e of events) {
73
+ if (e.et === 'approval_requested')
74
+ pendingReq.set(e.rid, e.t);
75
+ else if (e.et === 'approval_granted') {
76
+ const req = pendingReq.get(e.rid);
77
+ if (req !== undefined) {
78
+ const spans = waitSpans.get(e.rid) ?? [];
79
+ spans.push([req, e.t]);
80
+ waitSpans.set(e.rid, spans);
81
+ pendingReq.delete(e.rid);
82
+ }
83
+ }
84
+ }
85
+ // Per-step ACTIVE durations across prior runs (gate-wait subtracted), and
86
+ // the step universe in first-seen order.
87
+ const durations = new Map();
88
+ const firstSeen = new Map();
89
+ const openStep = new Map(); // rid → in-flight step
90
+ for (const e of events) {
91
+ if (!e.sid)
92
+ continue;
93
+ if (e.et === 'step_started') {
94
+ openStep.set(e.rid, { sid: e.sid, t: e.t });
95
+ if (!firstSeen.has(e.sid))
96
+ firstSeen.set(e.sid, e.t);
97
+ }
98
+ else if (e.et === 'step_completed') {
99
+ const open = openStep.get(e.rid);
100
+ if (!open || open.sid !== e.sid)
101
+ continue;
102
+ openStep.delete(e.rid);
103
+ let dur = e.t - open.t;
104
+ for (const [ws, we] of waitSpans.get(e.rid) ?? [])
105
+ dur -= overlap(open.t, e.t, ws, we);
106
+ if (dur >= 0) {
107
+ const arr = durations.get(e.sid) ?? [];
108
+ arr.push(dur);
109
+ durations.set(e.sid, arr);
110
+ }
111
+ }
112
+ }
113
+ if (durations.size === 0)
114
+ return { available: false, reason: 'insufficient_history' };
115
+ // Steps this run has already completed: run metadata + its own event log.
116
+ const done = new Set();
117
+ try {
118
+ const meta = run.metadata ? JSON.parse(run.metadata) : {};
119
+ if (Array.isArray(meta.completedNodes))
120
+ for (const n of meta.completedNodes)
121
+ done.add(String(n));
122
+ }
123
+ catch { /* unreadable metadata — fall back to the event log alone */ }
124
+ const ownCompleted = db
125
+ .prepare(`SELECT DISTINCT COALESCE(step_id, json_extract(data, '$.stepId')) AS sid
126
+ FROM workflow_events
127
+ WHERE workflow_run_id = ? AND event_type IN ('step_completed', 'step_skipped')`)
128
+ .all(runId);
129
+ for (const r of ownCompleted)
130
+ if (r.sid)
131
+ done.add(r.sid);
132
+ const universe = [...durations.keys()].sort((a, b) => (firstSeen.get(a) ?? 0) - (firstSeen.get(b) ?? 0));
133
+ const remaining = universe.filter((sid) => !done.has(sid));
134
+ // Fallback distribution for a remaining step with no history of its own:
135
+ // this workflow's overall per-step durations (never another workflow's).
136
+ const allDurs = [...durations.values()].flat().sort((a, b) => a - b);
137
+ let lowMs = 0;
138
+ let highMs = 0;
139
+ for (const sid of remaining) {
140
+ const durs = (durations.get(sid) ?? allDurs).slice().sort((a, b) => a - b);
141
+ lowMs += quantile(durs, 0.5);
142
+ highMs += quantile(durs, 0.9);
143
+ }
144
+ return { available: true, lowMs: Math.round(lowMs), highMs: Math.round(highMs), basisRuns: prior.length };
145
+ }
146
+ //# sourceMappingURL=eta.js.map