@specship/specship-win32-arm64 0.12.1 → 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 (64) 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/server/cli.js +9 -7
  9. package/lib/dist/server/product-version.js +44 -0
  10. package/lib/dist/server/routes/claude.js +153 -42
  11. package/lib/dist/server/routes/config.js +35 -0
  12. package/lib/dist/server/routes/graph.js +22 -1
  13. package/lib/dist/server/routes/mcp.js +510 -0
  14. package/lib/dist/server/routes/projects.js +31 -3
  15. package/lib/dist/server/routes/status.js +3 -0
  16. package/lib/dist/server/routes/workflow.js +29 -2
  17. package/lib/dist/server/server-config.js +38 -0
  18. package/lib/dist/server/server.js +128 -58
  19. package/lib/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  20. package/lib/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  21. package/lib/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  22. package/lib/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  23. package/lib/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  24. package/lib/dist/ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  25. package/lib/dist/ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  26. package/lib/dist/ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  27. package/lib/dist/ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  28. package/lib/dist/ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  29. package/lib/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  30. package/lib/dist/ui/assets/index-D8e4LiVb.js +51 -0
  31. package/lib/dist/ui/assets/index-ezwX2EfG.css +1 -0
  32. package/lib/dist/ui/favicon-16.png +0 -0
  33. package/lib/dist/ui/favicon-180.png +0 -0
  34. package/lib/dist/ui/favicon-32.png +0 -0
  35. package/lib/dist/ui/favicon.svg +20 -0
  36. package/lib/dist/ui/index.html +17 -0
  37. package/lib/dist/update/resolve-latest.d.ts +14 -0
  38. package/lib/dist/update/resolve-latest.d.ts.map +1 -0
  39. package/lib/dist/update/resolve-latest.js +29 -0
  40. package/lib/dist/update/resolve-latest.js.map +1 -0
  41. package/lib/dist/update/run-installer.d.ts +13 -0
  42. package/lib/dist/update/run-installer.d.ts.map +1 -0
  43. package/lib/dist/update/run-installer.js +43 -0
  44. package/lib/dist/update/run-installer.js.map +1 -0
  45. package/lib/dist/update/updater.d.ts +53 -0
  46. package/lib/dist/update/updater.d.ts.map +1 -0
  47. package/lib/dist/update/updater.js +164 -0
  48. package/lib/dist/update/updater.js.map +1 -0
  49. package/lib/dist/workflows/executor.d.ts.map +1 -1
  50. package/lib/dist/workflows/executor.js +4 -0
  51. package/lib/dist/workflows/executor.js.map +1 -1
  52. package/lib/dist/workflows/runners/prompt.d.ts +6 -0
  53. package/lib/dist/workflows/runners/prompt.d.ts.map +1 -1
  54. package/lib/dist/workflows/runners/prompt.js +10 -0
  55. package/lib/dist/workflows/runners/prompt.js.map +1 -1
  56. package/lib/dist/workflows/runners/types.d.ts +12 -0
  57. package/lib/dist/workflows/runners/types.d.ts.map +1 -1
  58. package/lib/node_modules/.package-lock.json +1 -1
  59. package/lib/package.json +3 -2
  60. package/package.json +1 -1
  61. package/lib/dist/server/ssr/md.mjs +0 -83
  62. package/lib/dist/server/ssr/public/app.css +0 -1
  63. package/lib/dist/server/ssr/render.mjs +0 -596
  64. package/lib/dist/server/ssr/routes.js +0 -73
@@ -33,6 +33,9 @@ import { registerReflectRoutes } from './routes/reflect.js';
33
33
  import { registerMaintainabilityRoutes } from './routes/maintainability.js';
34
34
  import { registerDomainRoutes } from './routes/domain.js';
35
35
  import { registerChatRoutes } from './routes/chat.js';
36
+ import { registerMcpRoutes } from './routes/mcp.js';
37
+ import { registerConfigRoutes } from './routes/config.js';
38
+ import { readServerConfig } from './server-config.js';
36
39
  /**
37
40
  * Lazy-import the specship core. Two delivery shapes are supported:
38
41
  *
@@ -43,11 +46,37 @@ import { registerChatRoutes } from './routes/chat.js';
43
46
  * resolution finding `@specship/specship` (it won't, the
44
47
  * bundle stages a single package by name `@specship/specship`).
45
48
  *
46
- * 2. **Workspace / dev** — `packages/server/dist/server.js` imports via
47
- * the workspace `file:../..` dep. Falls through to the named import.
49
+ * 2. **Workspace / dev** — `server/dist/server.js` imports via
50
+ * the workspace `file:..` dep. Falls through to the named import.
48
51
  *
49
52
  * The function caches the resolved module so the lookup only runs once.
50
53
  */
54
+ /**
55
+ * Locate the built desktop SPA (REQ-DESKTOP-017.A1) when the caller didn't
56
+ * pass an explicit `webDir`. Two delivery shapes, probed relative to this
57
+ * file so cwd never matters:
58
+ *
59
+ * 1. **Bundled** — `build-server-bundle.mjs` copies `ui/dist/**` to the
60
+ * root `dist/ui/`, which the platform tarball ships as
61
+ * `<bundle>/lib/dist/ui/` next to `dist/server/` (this file).
62
+ * 2. **Workspace / dev** — `server/{src,dist}/server.*` →
63
+ * repo root → `ui/dist/` (present only after `cd ui && npm run build`).
64
+ *
65
+ * Returns null when neither exists — the server then simply has no SPA
66
+ * mount, same as `webDir: null`.
67
+ */
68
+ export function resolveDefaultWebDir() {
69
+ const here = path.dirname(fileURLToPath(import.meta.url));
70
+ const candidates = [
71
+ path.resolve(here, '..', 'ui'), // bundled: dist/server → dist/ui
72
+ path.resolve(here, '..', '..', 'ui', 'dist'), // workspace: server/dist → <root>/ui/dist
73
+ ];
74
+ for (const dir of candidates) {
75
+ if (existsSync(path.join(dir, 'index.html')))
76
+ return dir;
77
+ }
78
+ return null;
79
+ }
51
80
  let cachedSpecShip = null;
52
81
  async function loadSpecShip() {
53
82
  if (cachedSpecShip)
@@ -110,59 +139,102 @@ export async function createServer(options) {
110
139
  // provided one or explicitly opted out. Hooks into the primary project's
111
140
  // SQLite handle — when there's no primary, analytics endpoints will return
112
141
  // 409 until one is set (typically the next server restart with -p).
142
+ //
143
+ // The persisted runtime config (~/.specship/server-config.json) gates the
144
+ // start too: the Settings ingest toggle must survive a restart
145
+ // (REQ-DESKTOP-028.A2). CLI `--no-ingest` still wins over everything.
113
146
  let watcher = options.watcher ?? null;
114
147
  let ownedWatcher = false;
115
- if (!watcher && options.ingest !== false && primaryCg) {
148
+ let ingestEnabled = watcher != null
149
+ || (options.ingest !== false && readServerConfig().ingestEnabled !== false);
150
+ const startIngestWatcher = () => {
151
+ if (watcher)
152
+ return true;
153
+ if (!primaryCg)
154
+ return false;
116
155
  const cgAny = primaryCg;
117
156
  const dbHandle = cgAny.db?.getDb ? cgAny.db.getDb() : cgAny.queries?.db;
118
- if (dbHandle) {
119
- // Build a sync resolveGraph: for the primary project path return the
120
- // already-open SpecShip instance (which satisfies GraphLike).
121
- // Sessions from other project paths resolve to null — they'll be left
122
- // as 'unresolved' and retried on the next boot when that project is primary.
123
- // The stored project_path is the lossy-decoded slug (every '-' → '/'), so
124
- // an exact compare against the real primaryPath never matched and savings
125
- // stayed 0. primaryProjectMatcher accepts both the real path and its
126
- // mangled stored form. Sessions from OTHER projects still resolve to null
127
- // (left 'unresolved', retried when that project is primary).
128
- const primaryPath = options.projectRoot ?? null;
129
- const isPrimary = primaryPath ? primaryProjectMatcher(primaryPath) : () => false;
130
- const resolveGraph = (projectPath) => primaryPath && isPrimary(projectPath) ? primaryCg : null;
131
- watcher = startWatcher(dbHandle, { verbose, resolveGraph });
132
- ownedWatcher = true;
157
+ if (!dbHandle)
158
+ return false;
159
+ // Build a sync resolveGraph: for the primary project path return the
160
+ // already-open SpecShip instance (which satisfies GraphLike).
161
+ // Sessions from other project paths resolve to null — they'll be left
162
+ // as 'unresolved' and retried on the next boot when that project is primary.
163
+ // The stored project_path is the lossy-decoded slug (every '-' → '/'), so
164
+ // an exact compare against the real primaryPath never matched and savings
165
+ // stayed 0. primaryProjectMatcher accepts both the real path and its
166
+ // mangled stored form. Sessions from OTHER projects still resolve to null
167
+ // (left 'unresolved', retried when that project is primary).
168
+ const primaryPath = options.projectRoot ?? null;
169
+ const isPrimary = primaryPath ? primaryProjectMatcher(primaryPath) : () => false;
170
+ const resolveGraph = (projectPath) => primaryPath && isPrimary(projectPath) ? primaryCg : null;
171
+ watcher = startWatcher(dbHandle, { verbose, resolveGraph });
172
+ ownedWatcher = true;
173
+ if (verbose)
174
+ console.error('[specship-server] JSONL ingest watcher started');
175
+ // Backfill displaced_files / resolution for pre-upgrade rows (is_specship=1,
176
+ // resolution IS NULL). Idempotent — safe to run on every boot. Non-fatal:
177
+ // a failure here must never abort server startup.
178
+ try {
179
+ backfillDisplaced(dbHandle, resolveGraph);
133
180
  if (verbose)
134
- console.error('[specship-server] JSONL ingest watcher started');
135
- // Backfill displaced_files / resolution for pre-upgrade rows (is_specship=1,
136
- // resolution IS NULL). Idempotent — safe to run on every boot. Non-fatal:
137
- // a failure here must never abort server startup.
138
- try {
139
- backfillDisplaced(dbHandle, resolveGraph);
140
- if (verbose)
141
- console.error('[specship-server] specship-impact backfill complete');
142
- }
143
- catch (err) {
144
- console.error('[specship-server] specship-impact backfill failed (non-fatal):', err instanceof Error ? err.message : String(err));
145
- }
146
- // Re-cost prompts ingested while their model family was unpriced,
147
- // e.g. fable sessions stuck at $0 (REQ-DASHINT-001.A2). Idempotent.
148
- try {
149
- const recosted = recostUnpricedPrompts(dbHandle);
150
- if (verbose && recosted > 0)
151
- console.error(`[specship-server] re-costed ${recosted} unpriced prompts`);
152
- }
153
- catch (err) {
154
- console.error('[specship-server] pricing backfill failed (non-fatal):', err instanceof Error ? err.message : String(err));
155
- }
181
+ console.error('[specship-server] specship-impact backfill complete');
182
+ }
183
+ catch (err) {
184
+ console.error('[specship-server] specship-impact backfill failed (non-fatal):', err instanceof Error ? err.message : String(err));
185
+ }
186
+ // Re-cost prompts ingested while their model family was unpriced,
187
+ // e.g. fable sessions stuck at $0 (REQ-DASHINT-001.A2). Idempotent.
188
+ try {
189
+ const recosted = recostUnpricedPrompts(dbHandle);
190
+ if (verbose && recosted > 0)
191
+ console.error(`[specship-server] re-costed ${recosted} unpriced prompts`);
192
+ }
193
+ catch (err) {
194
+ console.error('[specship-server] pricing backfill failed (non-fatal):', err instanceof Error ? err.message : String(err));
195
+ }
196
+ return true;
197
+ };
198
+ if (!watcher && ingestEnabled) {
199
+ if (primaryCg) {
200
+ startIngestWatcher();
201
+ }
202
+ else if (verbose) {
203
+ console.error('[specship-server] no primary project — analytics will be empty until one is set');
156
204
  }
157
- }
158
- else if (!primaryCg && options.ingest !== false && verbose) {
159
- console.error('[specship-server] no primary project — analytics will be empty until one is set');
160
205
  }
161
206
  // Decorate the Fastify instance so route handlers can access shared state
162
207
  // without globals or DI containers.
163
208
  app.decorate('projects', registry);
164
209
  app.decorate('primaryCg', primaryCg);
165
210
  app.decorate('watcher', watcher);
211
+ // Runtime ingest control for GET/PUT /api/config (REQ-DESKTOP-028.A2).
212
+ // `enabled` is the configured intent (round-trips even when no primary
213
+ // project can host a watcher yet); `active` is whether one is running.
214
+ // start/stop keep `app.watcher` in sync so /api/refresh always sees the
215
+ // live handle.
216
+ app.decorate('ingestControl', {
217
+ enabled: () => ingestEnabled,
218
+ active: () => watcher != null,
219
+ start: () => {
220
+ ingestEnabled = true;
221
+ const started = startIngestWatcher();
222
+ app.watcher = watcher;
223
+ return started;
224
+ },
225
+ stop: () => {
226
+ ingestEnabled = false;
227
+ if (watcher) {
228
+ try {
229
+ watcher.stop();
230
+ }
231
+ catch { /* ignore */ }
232
+ }
233
+ watcher = null;
234
+ ownedWatcher = false;
235
+ app.watcher = null;
236
+ },
237
+ });
166
238
  // activeCg(req): resolve `?project=<slug>` → SpecShip instance, with the
167
239
  // primary as fallback. Returns null when nothing is selectable; route
168
240
  // handlers respond 409 in that case.
@@ -193,19 +265,17 @@ export async function createServer(options) {
193
265
  await registerMaintainabilityRoutes(app);
194
266
  await registerDomainRoutes(app);
195
267
  await registerChatRoutes(app);
196
- // SSR dashboard (DASH-LEAN-DOC / REQ-DASHLEAN-004): explicit page routes
197
- // rendered in-process from the /api handlers above (via app.inject), plus the
198
- // dashboard's static assets. Registered AFTER /api and BEFORE the SPA
199
- // fallback so the explicit page routes win.
200
- if (options.ssr) {
201
- const { registerSsrRoutes } = await import('./ssr/routes.js');
202
- await registerSsrRoutes(app);
203
- }
204
- // Optional: serve the built Angular UI from `webDir` and fall back to
205
- // index.html for client-side routes. Must register AFTER the /api/*
206
- // routes so they take precedence over the SPA wildcard.
207
- if (options.webDir) {
208
- const indexPath = path.join(options.webDir, 'index.html');
268
+ await registerMcpRoutes(app);
269
+ await registerConfigRoutes(app);
270
+ // Serve the built desktop SPA (the `ui/` module) from `webDir` and fall back
271
+ // to index.html for client-side routes. This is the dashboard's only surface
272
+ // (REQ-DESKTOP-033 — the server-rendered dashboard retired). Must register
273
+ // AFTER the /api/* routes so they take precedence over the SPA wildcard. When
274
+ // the caller passed no webDir at all, auto-detect the SPA shipped next to the
275
+ // server build; `null` explicitly opts out (headless / API only).
276
+ const webDir = options.webDir === undefined ? resolveDefaultWebDir() : options.webDir;
277
+ if (webDir) {
278
+ const indexPath = path.join(webDir, 'index.html');
209
279
  let indexBuffer = null;
210
280
  try {
211
281
  indexBuffer = await fs.readFile(indexPath);
@@ -217,7 +287,7 @@ export async function createServer(options) {
217
287
  }
218
288
  if (indexBuffer) {
219
289
  const cachedIndex = indexBuffer;
220
- const serveStatic = makeStaticHandler(options.webDir);
290
+ const serveStatic = makeStaticHandler(webDir);
221
291
  // A single 404 handler does both jobs:
222
292
  // 1. Try the requested URL as a real file under webDir → serve it
223
293
  // with the right content-type. Covers the SPA's chunk-*.js,
@@ -250,7 +320,7 @@ export async function createServer(options) {
250
320
  reply.code(200).type('text/html').send(cachedIndex);
251
321
  });
252
322
  if (verbose)
253
- console.error(`[specship-server] serving SPA from ${options.webDir}`);
323
+ console.error(`[specship-server] serving SPA from ${webDir}`);
254
324
  }
255
325
  }
256
326
  await app.listen({ host, port });