@slycode/slycode 0.2.22 → 0.2.24

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 (176) hide show
  1. package/dist/bridge/api.d.ts +2 -1
  2. package/dist/bridge/api.js +114 -1
  3. package/dist/bridge/api.js.map +1 -1
  4. package/dist/bridge/git-utils.d.ts +9 -0
  5. package/dist/bridge/git-utils.js +49 -0
  6. package/dist/bridge/git-utils.js.map +1 -0
  7. package/dist/bridge/index.js +8 -2
  8. package/dist/bridge/index.js.map +1 -1
  9. package/dist/bridge/response-store.d.ts +46 -0
  10. package/dist/bridge/response-store.js +95 -0
  11. package/dist/bridge/response-store.js.map +1 -0
  12. package/dist/bridge/session-manager.d.ts +33 -1
  13. package/dist/bridge/session-manager.js +185 -2
  14. package/dist/bridge/session-manager.js.map +1 -1
  15. package/dist/bridge/types.d.ts +37 -0
  16. package/dist/data/scaffold-templates/tutorial-project/documentation/kanban.json +1 -1
  17. package/dist/messaging/bridge-client.d.ts +4 -0
  18. package/dist/messaging/bridge-client.js +20 -1
  19. package/dist/messaging/bridge-client.js.map +1 -1
  20. package/dist/messaging/index.js +38 -10
  21. package/dist/messaging/index.js.map +1 -1
  22. package/dist/scripts/kanban.js +448 -2
  23. package/dist/scripts/scaffold.js +40 -1
  24. package/dist/store/actions/approve.md +1 -1
  25. package/dist/store/actions/challenge-implementation.md +149 -0
  26. package/dist/store/actions/challenge.md +119 -0
  27. package/dist/store/actions/checkpoint.md +2 -2
  28. package/dist/store/actions/context.md +3 -3
  29. package/dist/store/actions/create-card.md +1 -1
  30. package/dist/store/actions/deep-design.md +13 -3
  31. package/dist/store/actions/design-requirements.md +11 -1
  32. package/dist/store/actions/explore.md +1 -1
  33. package/dist/store/actions/implement.md +1 -1
  34. package/dist/store/actions/onboard.md +2 -4
  35. package/dist/store/actions/show-card.md +2 -2
  36. package/dist/store/actions/summarize.md +3 -3
  37. package/dist/store/actions/test-review.md +1 -1
  38. package/dist/store/skills/kanban/SKILL.md +77 -3
  39. package/dist/web/.next/BUILD_ID +1 -1
  40. package/dist/web/.next/app-path-routes-manifest.json +1 -0
  41. package/dist/web/.next/build-manifest.json +2 -2
  42. package/dist/web/.next/prerender-manifest.json +3 -3
  43. package/dist/web/.next/routes-manifest.json +6 -0
  44. package/dist/web/.next/server/app/_global-error.html +2 -2
  45. package/dist/web/.next/server/app/_global-error.rsc +1 -1
  46. package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  48. package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  49. package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  50. package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  52. package/dist/web/.next/server/app/_not-found.html +1 -1
  53. package/dist/web/.next/server/app/_not-found.rsc +10 -10
  54. package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
  55. package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  56. package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
  57. package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  58. package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  59. package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  60. package/dist/web/.next/server/app/api/changelog/route/app-paths-manifest.json +3 -0
  61. package/dist/web/.next/server/app/api/changelog/route/build-manifest.json +11 -0
  62. package/dist/web/.next/server/app/api/changelog/route/server-reference-manifest.json +4 -0
  63. package/dist/web/.next/server/app/api/changelog/route.js +7 -0
  64. package/dist/web/.next/server/app/api/changelog/route.js.map +5 -0
  65. package/dist/web/.next/server/app/api/changelog/route.js.nft.json +1 -0
  66. package/dist/web/.next/server/app/api/changelog/route_client-reference-manifest.js +2 -0
  67. package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
  68. package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
  69. package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
  70. package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
  71. package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
  72. package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
  73. package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
  74. package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
  75. package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
  76. package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
  77. package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
  78. package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
  79. package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
  80. package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  81. package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
  82. package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
  83. package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
  84. package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
  85. package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
  86. package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
  87. package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
  88. package/dist/web/.next/server/app/page.js.nft.json +1 -1
  89. package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
  90. package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
  91. package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
  92. package/dist/web/.next/server/app-paths-manifest.json +1 -0
  93. package/dist/web/.next/server/chunks/[root-of-the-server]__92f81907._.js +1 -1
  94. package/dist/web/.next/server/chunks/[root-of-the-server]__a259539f._.js +3 -0
  95. package/dist/web/.next/server/chunks/_next-internal_server_app_api_changelog_route_actions_d6e239bf.js +3 -0
  96. package/dist/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_18324462.js +1 -1
  97. package/dist/web/.next/server/chunks/src_lib_scheduler_ts_03988e3e._.js +1 -1
  98. package/dist/web/.next/server/chunks/src_lib_scheduler_ts_7120457c._.js +1 -1
  99. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__1f5fc489._.js +4 -3
  100. package/dist/web/.next/server/chunks/ssr/{[root-of-the-server]__077f472c._.js → [root-of-the-server]__6183d28c._.js} +1 -1
  101. package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__bcbe4bf2._.js +4 -3
  102. package/dist/web/.next/server/chunks/ssr/src_components_Dashboard_tsx_efc4dc27._.js +1 -1
  103. package/dist/web/.next/server/chunks/ssr/src_components_c4135402._.js +1 -1
  104. package/dist/web/.next/server/chunks/ssr/src_contexts_VoiceContext_tsx_cfba7292._.js +1 -1
  105. package/dist/web/.next/server/pages/404.html +1 -1
  106. package/dist/web/.next/server/pages/500.html +2 -2
  107. package/dist/web/.next/server/server-reference-manifest.js +1 -1
  108. package/dist/web/.next/server/server-reference-manifest.json +1 -1
  109. package/dist/web/.next/static/chunks/293449b828207656.css +1 -0
  110. package/dist/web/.next/static/chunks/{8415039c5941cf5c.js → 3a5721af09d1c753.js} +4 -3
  111. package/dist/web/.next/static/chunks/{8fb2a99c64580de7.js → 98311243e9a5a0ec.js} +1 -1
  112. package/dist/web/.next/static/chunks/{b8e0c1aeea4a14bc.js → a47f36b030917d1f.js} +1 -1
  113. package/dist/web/.next/static/chunks/d60c422421920130.js +5 -0
  114. package/dist/web/.next/static/chunks/{f55f3c8c1a52f80c.js → f566a4b05a9cd6ba.js} +1 -1
  115. package/dist/web/.next/static/chunks/{4049cceee6a49323.js → fa78afe3ceed998b.js} +1 -1
  116. package/dist/web/src/app/api/changelog/route.ts +45 -0
  117. package/dist/web/src/app/api/kanban/route.ts +52 -12
  118. package/dist/web/src/app/api/terminal-classes/route.ts +43 -4
  119. package/dist/web/src/components/ActivityFeed.tsx +3 -0
  120. package/dist/web/src/components/BranchTab.tsx +115 -0
  121. package/dist/web/src/components/ChangelogModal.tsx +233 -0
  122. package/dist/web/src/components/Dashboard.tsx +11 -0
  123. package/dist/web/src/components/GlobalClaudePanel.tsx +6 -0
  124. package/dist/web/src/components/ProjectKanban.tsx +38 -8
  125. package/dist/web/src/components/VersionUpdateToast.tsx +6 -4
  126. package/dist/web/src/components/VoiceControlBar.tsx +1 -1
  127. package/dist/web/src/lib/scheduler.ts +2 -1
  128. package/dist/web/src/lib/types.ts +24 -0
  129. package/dist/web/tsconfig.tsbuildinfo +1 -1
  130. package/lib/cli/sync.d.ts +7 -0
  131. package/lib/cli/sync.d.ts.map +1 -1
  132. package/lib/cli/sync.js +25 -0
  133. package/lib/cli/sync.js.map +1 -1
  134. package/lib/cli/update.d.ts.map +1 -1
  135. package/lib/cli/update.js +9 -0
  136. package/lib/cli/update.js.map +1 -1
  137. package/package.json +1 -1
  138. package/templates/changelog.json +268 -0
  139. package/templates/kanban-seed.json +1 -1
  140. package/templates/store/actions/approve.md +1 -1
  141. package/templates/store/actions/challenge-implementation.md +149 -0
  142. package/templates/store/actions/challenge.md +119 -0
  143. package/templates/store/actions/checkpoint.md +2 -2
  144. package/templates/store/actions/context.md +3 -3
  145. package/templates/store/actions/create-card.md +1 -1
  146. package/templates/store/actions/deep-design.md +13 -3
  147. package/templates/store/actions/design-requirements.md +11 -1
  148. package/templates/store/actions/explore.md +1 -1
  149. package/templates/store/actions/implement.md +1 -1
  150. package/templates/store/actions/onboard.md +2 -4
  151. package/templates/store/actions/show-card.md +2 -2
  152. package/templates/store/actions/summarize.md +3 -3
  153. package/templates/store/actions/test-review.md +1 -1
  154. package/templates/store/skills/kanban/SKILL.md +77 -3
  155. package/templates/terminal-classes.json +51 -0
  156. package/templates/tutorial-project/documentation/kanban.json +1 -1
  157. package/templates/updates/actions/approve.md +1 -1
  158. package/templates/updates/actions/challenge-implementation.md +149 -0
  159. package/templates/updates/actions/challenge.md +119 -0
  160. package/templates/updates/actions/checkpoint.md +2 -2
  161. package/templates/updates/actions/context.md +3 -3
  162. package/templates/updates/actions/create-card.md +1 -1
  163. package/templates/updates/actions/deep-design.md +13 -3
  164. package/templates/updates/actions/design-requirements.md +11 -1
  165. package/templates/updates/actions/explore.md +1 -1
  166. package/templates/updates/actions/implement.md +1 -1
  167. package/templates/updates/actions/onboard.md +2 -4
  168. package/templates/updates/actions/show-card.md +2 -2
  169. package/templates/updates/actions/summarize.md +3 -3
  170. package/templates/updates/actions/test-review.md +1 -1
  171. package/templates/updates/skills/kanban/SKILL.md +77 -3
  172. package/dist/web/.next/static/chunks/18cfbdd7e977bb01.css +0 -1
  173. package/dist/web/.next/static/chunks/a0f5f9cdee8a22c1.js +0 -4
  174. /package/dist/web/.next/static/{b2V8jC3HBMi4vgm7Kie3H → O1Ine2WtyXv6EQJcwHyOV}/_buildManifest.js +0 -0
  175. /package/dist/web/.next/static/{b2V8jC3HBMi4vgm7Kie3H → O1Ine2WtyXv6EQJcwHyOV}/_clientMiddlewareManifest.json +0 -0
  176. /package/dist/web/.next/static/{b2V8jC3HBMi4vgm7Kie3H → O1Ine2WtyXv6EQJcwHyOV}/_ssgManifest.js +0 -0
@@ -34,7 +34,7 @@ if (!PROJECT_ROOT) {
34
34
  console.error('Run this command from within a project that has a kanban board.');
35
35
  process.exit(1);
36
36
  }
37
- const PROJECT_NAME = path.basename(PROJECT_ROOT);
37
+ const PROJECT_NAME = path.basename(PROJECT_ROOT).replace(/_/g, '-');
38
38
  const KANBAN_PATH = path.join(PROJECT_ROOT, 'documentation', 'kanban.json');
39
39
  const EVENTS_PATH = path.join(PROJECT_ROOT, 'documentation', 'events.json');
40
40
  const AREA_INDEX_PATH = path.join(PROJECT_ROOT, '.claude', 'skills', 'context-priming', 'references', 'area-index.md');
@@ -65,6 +65,8 @@ Commands:
65
65
  problem Manage problems/issues
66
66
  notes Manage cross-agent notes
67
67
  automation Manage card automations (scheduled prompt execution)
68
+ prompt Send a prompt to another card's session (cross-card execution)
69
+ respond Reply to a cross-card prompt (callback for --wait mode)
68
70
  areas List available areas from context-priming
69
71
 
70
72
  Run 'kanban <command> --help' for command-specific options.
@@ -289,6 +291,38 @@ List available areas from context-priming area-index.md.
289
291
  These are valid values for the --areas option.
290
292
  `;
291
293
 
294
+ const PROMPT_HELP = `
295
+ Usage: kanban prompt <card-id> "prompt text" [options]
296
+
297
+ Send a prompt to another card's session (cross-card execution).
298
+ If no running session exists, one is created automatically.
299
+
300
+ Options:
301
+ --provider <id> Target provider (claude|codex|gemini). Default: stage default
302
+ --model <id> Model for new sessions (e.g. opus, o3)
303
+ --wait Wait for a response (sync mode with callback)
304
+ --timeout <secs> Timeout for --wait mode (default: 120 seconds)
305
+ --force Bypass session lock and busy checks
306
+ --fresh Stop any existing session and start a clean one
307
+
308
+ Modes:
309
+ Fire-and-forget: kanban prompt <card-id> "do something"
310
+ Wait for response: kanban prompt <card-id> "analyze this" --wait --timeout 60
311
+ Fresh session: kanban prompt <card-id> "review" --fresh --provider codex --wait
312
+
313
+ When using --wait, the called card must run 'kanban respond <id> "response"' to return data.
314
+ `;
315
+
316
+ const RESPOND_HELP = `
317
+ Usage: kanban respond <response-id> "response data"
318
+
319
+ Reply to a cross-card prompt that used --wait mode.
320
+ The response-id is provided in the callback instruction appended to the prompt.
321
+
322
+ Example:
323
+ kanban respond abc-123-def "Analysis complete. Found 3 issues..."
324
+ `;
325
+
292
326
  const BOARD_HELP = `
293
327
  Usage: kanban board [options]
294
328
 
@@ -364,6 +398,7 @@ function writeKanban(data) {
364
398
  }
365
399
 
366
400
  function findCard(kanban, cardId, includeArchived = false) {
401
+ // First try exact ID match
367
402
  for (const stage of VALID_STAGES) {
368
403
  const cards = kanban.stages[stage] || [];
369
404
  const card = cards.find(c => c.id === cardId);
@@ -374,6 +409,17 @@ function findCard(kanban, cardId, includeArchived = false) {
374
409
  return { card, stage };
375
410
  }
376
411
  }
412
+
413
+ // Fall back to exact title match (case-insensitive, non-archived only)
414
+ const titleLower = cardId.toLowerCase();
415
+ for (const stage of VALID_STAGES) {
416
+ const cards = kanban.stages[stage] || [];
417
+ const card = cards.find(c => !c.archived && c.title && c.title.toLowerCase() === titleLower);
418
+ if (card) {
419
+ return { card, stage };
420
+ }
421
+ }
422
+
377
423
  return null;
378
424
  }
379
425
 
@@ -1817,7 +1863,7 @@ function cmdAutomation(args) {
1817
1863
  const bridgePort = process.env.BRIDGE_PORT || process.env.PORT || '3004';
1818
1864
  const bridgeUrl = process.env.BRIDGE_URL || `http://localhost:${bridgePort}`;
1819
1865
  const provider = auto.provider || 'claude';
1820
- const sessionName = `${PROJECT_NAME}:${provider}:card:${cardId}`;
1866
+ const sessionName = `${PROJECT_NAME}:${provider}:card:${card.id}`;
1821
1867
 
1822
1868
  console.log(`Triggering automation: ${cardId}`);
1823
1869
  console.log(` Session: ${sessionName}`);
@@ -2157,6 +2203,400 @@ function cmdReorder(args) {
2157
2203
  applyAndConfirm([...listedCards, ...unlistedCards]);
2158
2204
  }
2159
2205
 
2206
+ // ============================================================================
2207
+ // Prompt Command (cross-card execution)
2208
+ // ============================================================================
2209
+
2210
+ function cmdPrompt(args) {
2211
+ const opts = parseArgs(args);
2212
+
2213
+ if (opts.help || args.length === 0) {
2214
+ console.log(PROMPT_HELP);
2215
+ return;
2216
+ }
2217
+
2218
+ const cardId = opts._[0];
2219
+ const promptText = opts._[1];
2220
+
2221
+ if (!cardId) {
2222
+ console.error('Error: Card ID required');
2223
+ process.exit(1);
2224
+ }
2225
+
2226
+ if (!promptText) {
2227
+ console.error('Error: Prompt text required');
2228
+ console.error('Usage: kanban prompt <card-id> "your prompt text"');
2229
+ process.exit(1);
2230
+ }
2231
+
2232
+ const kanban = readKanban();
2233
+ const result = findCard(kanban, cardId);
2234
+ if (!result) {
2235
+ console.error(`Error: Card '${cardId}' not found`);
2236
+ process.exit(1);
2237
+ }
2238
+
2239
+ const { card, stage } = result;
2240
+ const providerFlag = opts.provider;
2241
+ const modelFlag = opts.model;
2242
+ const create = opts.create || false;
2243
+ const wait = opts.wait || false;
2244
+ const timeout = parseInt(opts.timeout || '120', 10);
2245
+ const force = opts.force || false;
2246
+ const fresh = opts.fresh || false;
2247
+
2248
+ // Determine bridge URL
2249
+ const bridgePort = process.env.BRIDGE_PORT || process.env.PORT || '3004';
2250
+ const bridgeUrl = process.env.BRIDGE_URL || `http://localhost:${bridgePort}`;
2251
+
2252
+ // Load providers.json for stage defaults
2253
+ let providers = null;
2254
+ try {
2255
+ const providersPath = path.join(PROJECT_ROOT, 'data', 'providers.json');
2256
+ providers = JSON.parse(fs.readFileSync(providersPath, 'utf-8'));
2257
+ } catch { /* providers.json optional */ }
2258
+
2259
+ const doPrompt = async () => {
2260
+ try {
2261
+ // Resolve provider: flag > stage default > 'claude'
2262
+ let provider = providerFlag;
2263
+ if (!provider && providers?.defaults?.stages?.[stage]) {
2264
+ provider = providers.defaults.stages[stage].provider;
2265
+ }
2266
+ if (!provider) provider = 'claude';
2267
+
2268
+ const sessionName = `${PROJECT_NAME}:${provider}:card:${card.id}`;
2269
+
2270
+ // Check if session exists on bridge
2271
+ let sessionExists = false;
2272
+ let sessionRunning = false;
2273
+ let sessionStatus = 'not_found';
2274
+ try {
2275
+ const infoRes = await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}`);
2276
+ if (infoRes.ok) {
2277
+ const info = await infoRes.json();
2278
+ if (info && info.status) {
2279
+ sessionExists = true;
2280
+ sessionStatus = info.status;
2281
+ sessionRunning = info.status === 'running';
2282
+ }
2283
+ }
2284
+ } catch (err) {
2285
+ if (err.cause?.code === 'ECONNREFUSED' || err.message?.includes('fetch failed')) {
2286
+ console.error('Error: Bridge is not running. Start it with: cd bridge && npm run dev');
2287
+ process.exit(1);
2288
+ }
2289
+ throw err;
2290
+ }
2291
+
2292
+ // --fresh: stop existing session so we get a clean one with CLI-arg delivery
2293
+ if (fresh && sessionRunning) {
2294
+ console.log(` Stopping existing session (--fresh)...`);
2295
+ try {
2296
+ await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}?action=stop`, { method: 'DELETE' });
2297
+ // Brief wait for cleanup
2298
+ await new Promise(r => setTimeout(r, 1000));
2299
+ } catch { /* best effort */ }
2300
+ sessionRunning = false;
2301
+ sessionStatus = 'stopped';
2302
+ }
2303
+
2304
+ // Build the full prompt upfront (with callback instruction for --wait)
2305
+ // This must happen BEFORE session creation so it can be passed as a CLI arg
2306
+ // Auto-prepend card context so the called AI always knows its own card
2307
+ const cardContext = `[Card: ${card.title} (${card.id})]
2308
+ Stage: ${stage} | Type: ${card.type || 'unknown'} | Priority: ${card.priority || 'unknown'}
2309
+ Description: ${card.description || '(no description)'}
2310
+ ---
2311
+
2312
+ `;
2313
+ let fullPrompt = cardContext + promptText;
2314
+ let responseId = null;
2315
+
2316
+ if (wait) {
2317
+ responseId = `resp-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
2318
+ fullPrompt += `\n\nIMPORTANT: When you have completed this task, you MUST run the following command to return your response:\nsly-kanban respond ${responseId} "<your response here>"`;
2319
+
2320
+ // Register response on bridge
2321
+ const currentSessionName = process.env.SLYCODE_SESSION || `${PROJECT_NAME}:unknown:caller`;
2322
+ const regRes = await fetch(`${bridgeUrl}/responses`, {
2323
+ method: 'POST',
2324
+ headers: { 'Content-Type': 'application/json' },
2325
+ body: JSON.stringify({
2326
+ responseId,
2327
+ callingSession: currentSessionName,
2328
+ targetSession: sessionName,
2329
+ }),
2330
+ });
2331
+ if (!regRes.ok) {
2332
+ console.error('Error: Failed to register response on bridge.');
2333
+ process.exit(1);
2334
+ }
2335
+ }
2336
+
2337
+ // Handle no running session — resume if stopped, create fresh if none exists
2338
+ if (!sessionRunning) {
2339
+ const isStopped = sessionExists && (sessionStatus === 'stopped' || sessionStatus === 'detached');
2340
+ const isFresh = fresh || !isStopped; // --fresh forces fresh; no prior session = fresh
2341
+
2342
+ if (isStopped && !fresh) {
2343
+ console.log(`Resuming stopped session for card ${cardId}...`);
2344
+ } else {
2345
+ console.log(`No running session for card ${cardId}. Starting one...`);
2346
+ }
2347
+ console.log(`${isFresh ? 'Creating' : 'Resuming'} session: ${sessionName} (provider: ${provider}${modelFlag ? ', model: ' + modelFlag : ''})`);
2348
+
2349
+ // Record prompt chain depth BEFORE creating session (prevents race condition)
2350
+ const callingSessionForCreate = process.env.SLYCODE_SESSION || undefined;
2351
+ if (callingSessionForCreate) {
2352
+ try {
2353
+ const chainRes = await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}/chain`, {
2354
+ method: 'POST',
2355
+ headers: { 'Content-Type': 'application/json' },
2356
+ body: JSON.stringify({ callingSession: callingSessionForCreate }),
2357
+ });
2358
+ const chainResult = await chainRes.json();
2359
+ if (!chainResult.success) {
2360
+ console.error(`Error: ${chainResult.error || 'Depth limit reached'}`);
2361
+ process.exit(1);
2362
+ }
2363
+ } catch { /* best effort */ }
2364
+ }
2365
+ const createBody = {
2366
+ name: sessionName,
2367
+ provider,
2368
+ skipPermissions: true,
2369
+ cwd: PROJECT_ROOT,
2370
+ fresh: isFresh,
2371
+ prompt: fullPrompt, // Always pass prompt as CLI arg — OS-level delivery, no timing issues
2372
+ };
2373
+ if (isFresh && modelFlag) createBody.model = modelFlag; // Model only on fresh (resume reconnects to existing)
2374
+
2375
+ const createRes = await fetch(`${bridgeUrl}/sessions`, {
2376
+ method: 'POST',
2377
+ headers: { 'Content-Type': 'application/json' },
2378
+ body: JSON.stringify(createBody),
2379
+ });
2380
+
2381
+ if (!createRes.ok) {
2382
+ const errBody = await createRes.text();
2383
+ console.error(`Error creating session: ${errBody}`);
2384
+ process.exit(1);
2385
+ }
2386
+
2387
+ // Wait for session to be alive (liveness check)
2388
+ console.log(' Waiting for session to start...');
2389
+ const livenessStart = Date.now();
2390
+ const livenessTimeout = 20000; // 20s
2391
+ let alive = false;
2392
+ while (Date.now() - livenessStart < livenessTimeout) {
2393
+ await new Promise(r => setTimeout(r, 2000));
2394
+ try {
2395
+ const checkRes = await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}`);
2396
+ if (checkRes.ok) {
2397
+ const checkInfo = await checkRes.json();
2398
+ if (checkInfo && checkInfo.status === 'running') {
2399
+ alive = true;
2400
+ break;
2401
+ }
2402
+ if (checkInfo && checkInfo.status === 'stopped') {
2403
+ console.error('Error: Session stopped during startup.');
2404
+ process.exit(1);
2405
+ }
2406
+ }
2407
+ } catch { /* retry */ }
2408
+ }
2409
+
2410
+ if (!alive) {
2411
+ console.error('Error: Session did not start within 20s.');
2412
+ process.exit(1);
2413
+ }
2414
+ console.log(' Session started. Prompt delivered via CLI arg.');
2415
+
2416
+ // For fire-and-forget, prompt was passed as CLI arg — we're done
2417
+ if (!wait) {
2418
+ console.log(`Prompt delivered to ${cardId} via session creation.`);
2419
+ emitEvent(kanban, 'card.prompt', { cardId, provider, mode: 'fire-and-forget', created: true });
2420
+ return;
2421
+ }
2422
+
2423
+ // For --wait, prompt was already delivered via CLI arg — skip to polling
2424
+ } else {
2425
+ // Session was already running — submit prompt via bridge submit endpoint
2426
+ // Pass callingSession for depth tracking (derive from SLYCODE_SESSION env or best guess)
2427
+ const callingSession = process.env.SLYCODE_SESSION || undefined;
2428
+ const submitRes = await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}/submit`, {
2429
+ method: 'POST',
2430
+ headers: { 'Content-Type': 'application/json' },
2431
+ body: JSON.stringify({ prompt: fullPrompt, force, callingSession }),
2432
+ });
2433
+
2434
+ const submitResult = await submitRes.json();
2435
+
2436
+ if (!submitRes.ok) {
2437
+ if (submitResult.locked) {
2438
+ console.error(`Error: ${submitResult.error}`);
2439
+ } else if (submitResult.busy) {
2440
+ console.error(`Warning: ${submitResult.error}`);
2441
+ } else {
2442
+ console.error(`Error: ${submitResult.error || 'Failed to submit prompt'}`);
2443
+ }
2444
+ process.exit(1);
2445
+ }
2446
+
2447
+ // Fire-and-forget mode
2448
+ if (!wait) {
2449
+ console.log(`Prompt delivered to ${cardId} (session: ${sessionName}).`);
2450
+ emitEvent(kanban, 'card.prompt', { cardId, provider, mode: 'fire-and-forget' });
2451
+ return;
2452
+ }
2453
+ }
2454
+
2455
+ // --wait mode: poll for response
2456
+ console.log(`Prompt sent to ${cardId}. Waiting for response (timeout: ${timeout}s)...`);
2457
+ const pollStart = Date.now();
2458
+ const pollInterval = 2000; // 2 seconds
2459
+ const timeoutMs = timeout * 1000;
2460
+
2461
+ while (Date.now() - pollStart < timeoutMs) {
2462
+ await new Promise(r => setTimeout(r, pollInterval));
2463
+
2464
+ try {
2465
+ const pollRes = await fetch(`${bridgeUrl}/responses/${responseId}`);
2466
+ if (pollRes.ok) {
2467
+ const pollData = await pollRes.json();
2468
+ if (pollData.status === 'received' && pollData.data) {
2469
+ // Happy path: response received
2470
+ console.log(pollData.data);
2471
+ emitEvent(kanban, 'card.prompt', { cardId, provider, mode: 'wait', outcome: 'received' });
2472
+ return;
2473
+ }
2474
+ }
2475
+ } catch { /* retry */ }
2476
+ }
2477
+
2478
+ // Timeout — determine which outcome
2479
+ // Mark caller as timed out so bridge can inject late responses
2480
+ try {
2481
+ await fetch(`${bridgeUrl}/responses/${responseId}/timeout`, { method: 'POST' });
2482
+ } catch { /* best effort */ }
2483
+
2484
+ // Check if target session is still active
2485
+ let isActive = false;
2486
+ try {
2487
+ const statsRes = await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}`);
2488
+ if (statsRes.ok) {
2489
+ const statsData = await statsRes.json();
2490
+ if (statsData && statsData.lastOutputAt) {
2491
+ const outputAge = Date.now() - new Date(statsData.lastOutputAt).getTime();
2492
+ isActive = outputAge < 3000; // active if output within 3s
2493
+ }
2494
+ }
2495
+ } catch { /* ignore */ }
2496
+
2497
+ if (isActive) {
2498
+ // Outcome 2: timeout + activity ongoing
2499
+ console.error(`[TIMEOUT] Target session is still actively processing (active for ${Math.round((Date.now() - pollStart) / 1000)}s).`);
2500
+ console.error('A response may still arrive — the session appears to be working.');
2501
+ console.error('If you need the response, you can wait longer.');
2502
+ console.error(`Session: ${sessionName}`);
2503
+ } else {
2504
+ // Outcome 3: timeout + activity stopped — fetch snapshot
2505
+ let snapshot = '';
2506
+ try {
2507
+ const snapRes = await fetch(`${bridgeUrl}/sessions/${encodeURIComponent(sessionName)}/snapshot?lines=20`);
2508
+ if (snapRes.ok) {
2509
+ const snapData = await snapRes.json();
2510
+ snapshot = snapData.content || '';
2511
+ }
2512
+ } catch { /* ignore */ }
2513
+
2514
+ console.error(`[TIMEOUT] Target session stopped processing without responding (timeout: ${timeout}s).`);
2515
+ if (snapshot) {
2516
+ console.error('\nTerminal snapshot (last 20 lines):');
2517
+ console.error('---');
2518
+ console.error(snapshot);
2519
+ console.error('---');
2520
+ }
2521
+ console.error('\nPossible cause: Permission prompt, user-facing question, or agent did not call respond.');
2522
+ console.error(`Session: ${sessionName}`);
2523
+ }
2524
+
2525
+ emitEvent(kanban, 'card.prompt', { cardId, provider, mode: 'wait', outcome: 'timeout', active: isActive });
2526
+ process.exit(1);
2527
+
2528
+ } catch (err) {
2529
+ if (err.cause?.code === 'ECONNREFUSED' || err.message?.includes('fetch failed')) {
2530
+ console.error('Error: Bridge is not running. Start it with: cd bridge && npm run dev');
2531
+ } else {
2532
+ console.error('Error:', err.message);
2533
+ }
2534
+ process.exit(1);
2535
+ }
2536
+ };
2537
+
2538
+ doPrompt();
2539
+ }
2540
+
2541
+ // ============================================================================
2542
+ // Respond Command (cross-card callback)
2543
+ // ============================================================================
2544
+
2545
+ function cmdRespond(args) {
2546
+ const opts = parseArgs(args);
2547
+
2548
+ if (opts.help || args.length === 0) {
2549
+ console.log(RESPOND_HELP);
2550
+ return;
2551
+ }
2552
+
2553
+ const responseId = opts._[0];
2554
+ const responseData = opts._[1];
2555
+
2556
+ if (!responseId) {
2557
+ console.error('Error: Response ID required');
2558
+ process.exit(1);
2559
+ }
2560
+
2561
+ if (!responseData) {
2562
+ console.error('Error: Response data required');
2563
+ console.error('Usage: kanban respond <response-id> "your response data"');
2564
+ process.exit(1);
2565
+ }
2566
+
2567
+ // Determine bridge URL
2568
+ const bridgePort = process.env.BRIDGE_PORT || process.env.PORT || '3004';
2569
+ const bridgeUrl = process.env.BRIDGE_URL || `http://localhost:${bridgePort}`;
2570
+
2571
+ const doRespond = async () => {
2572
+ try {
2573
+ const res = await fetch(`${bridgeUrl}/responses/${encodeURIComponent(responseId)}`, {
2574
+ method: 'POST',
2575
+ headers: { 'Content-Type': 'application/json' },
2576
+ body: JSON.stringify({ data: responseData }),
2577
+ });
2578
+
2579
+ if (!res.ok) {
2580
+ const errBody = await res.json().catch(() => ({ error: 'Unknown error' }));
2581
+ console.error(`Error: ${errBody.error || 'Failed to deliver response'}`);
2582
+ process.exit(1);
2583
+ }
2584
+
2585
+ const result = await res.json();
2586
+ console.log(`Response delivered.${result.lateInjection ? ' (late injection — caller had already timed out)' : ''}`);
2587
+ } catch (err) {
2588
+ if (err.cause?.code === 'ECONNREFUSED' || err.message?.includes('fetch failed')) {
2589
+ console.error('Error: Bridge is not running.');
2590
+ } else {
2591
+ console.error('Error:', err.message);
2592
+ }
2593
+ process.exit(1);
2594
+ }
2595
+ };
2596
+
2597
+ doRespond();
2598
+ }
2599
+
2160
2600
  // ============================================================================
2161
2601
  // Main
2162
2602
  // ============================================================================
@@ -2208,6 +2648,12 @@ function main() {
2208
2648
  case 'automation':
2209
2649
  cmdAutomation(commandArgs);
2210
2650
  break;
2651
+ case 'prompt':
2652
+ cmdPrompt(commandArgs);
2653
+ break;
2654
+ case 'respond':
2655
+ cmdRespond(commandArgs);
2656
+ break;
2211
2657
  case 'areas':
2212
2658
  cmdAreas(commandArgs);
2213
2659
  break;
@@ -49,7 +49,7 @@ const SCAFFOLD_GROUPS = [
49
49
  id: 'project-mgmt',
50
50
  name: 'Project Management',
51
51
  description: 'Task tracking, event history, and archiving',
52
- staticItems: ['documentation/kanban.json', 'documentation/events.json', 'documentation/archive'],
52
+ staticItems: ['documentation/kanban.json', 'documentation/events.json', 'documentation/terminal-classes.json', 'documentation/archive'],
53
53
  },
54
54
  {
55
55
  id: 'documentation',
@@ -415,6 +415,11 @@ function analyzeDirectory(targetPath, providers) {
415
415
  status: fileExists(path.join(targetPath, 'documentation', 'events.json')) ? 'present' : 'missing',
416
416
  group: 'project-mgmt',
417
417
  });
418
+ items.push({
419
+ path: 'documentation/terminal-classes.json',
420
+ status: fileExists(path.join(targetPath, 'documentation', 'terminal-classes.json')) ? 'present' : 'missing',
421
+ group: 'project-mgmt',
422
+ });
418
423
 
419
424
  // Check config files
420
425
  items.push({
@@ -745,6 +750,40 @@ function runCreate(args) {
745
750
  }
746
751
  }
747
752
 
753
+ // Step 5c: Create terminal-classes.json (skip if exists)
754
+ if (shouldProcess('documentation/terminal-classes.json')) {
755
+ const tcPath = path.join(targetPath, 'documentation', 'terminal-classes.json');
756
+ if (fs.existsSync(tcPath)) {
757
+ results.push({ action: 'skipped', path: 'documentation/terminal-classes.json (already exists)', group: 'project-mgmt' });
758
+ } else {
759
+ ensureDir(path.join(targetPath, 'documentation'));
760
+ // Try to copy from package templates, fall back to a minimal default
761
+ const templatePath = path.join(TEMPLATES_DIR, 'terminal-classes.json');
762
+ if (fs.existsSync(templatePath)) {
763
+ fs.copyFileSync(templatePath, tcPath);
764
+ } else {
765
+ // Minimal default with standard terminal classes
766
+ const defaultClasses = {
767
+ $schema: './terminal-classes.schema.json',
768
+ version: '1.0',
769
+ classes: [
770
+ { id: 'global-terminal', name: 'Global Terminal', description: 'Terminal on the dashboard screen' },
771
+ { id: 'project-terminal', name: 'Project Terminal', description: 'Terminal panel at the bottom of a project view' },
772
+ { id: 'backlog', name: 'Backlog', description: 'Card terminals in the Backlog stage' },
773
+ { id: 'design', name: 'Design', description: 'Card terminals in the Design stage' },
774
+ { id: 'implementation', name: 'Implementation', description: 'Card terminals in the Implementation stage' },
775
+ { id: 'testing', name: 'Testing', description: 'Card terminals in the Testing stage' },
776
+ { id: 'done', name: 'Done', description: 'Card terminals in the Done stage' },
777
+ { id: 'automation', name: 'Automation', description: 'Card terminals for automation cards' },
778
+ { id: 'action-assistant', name: 'Action Assistant', description: 'AI assistant in the Sly Action Configuration modal' },
779
+ ],
780
+ };
781
+ fs.writeFileSync(tcPath, JSON.stringify(defaultClasses, null, 2) + '\n', 'utf-8');
782
+ }
783
+ results.push({ action: 'created', path: 'documentation/terminal-classes.json', group: 'project-mgmt' });
784
+ }
785
+ }
786
+
748
787
  // Step 6: git init
749
788
  if (shouldProcess('.git') && !dirExists(path.join(targetPath, '.git'))) {
750
789
  try {
@@ -7,7 +7,7 @@ group: "Card Actions"
7
7
  placement: both
8
8
  scope: global
9
9
  classes:
10
- testing: 30
10
+ testing: 40
11
11
  ---
12
12
 
13
13
  {{cardContext}}