@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.
- package/dist/bridge/api.d.ts +2 -1
- package/dist/bridge/api.js +114 -1
- package/dist/bridge/api.js.map +1 -1
- package/dist/bridge/git-utils.d.ts +9 -0
- package/dist/bridge/git-utils.js +49 -0
- package/dist/bridge/git-utils.js.map +1 -0
- package/dist/bridge/index.js +8 -2
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/response-store.d.ts +46 -0
- package/dist/bridge/response-store.js +95 -0
- package/dist/bridge/response-store.js.map +1 -0
- package/dist/bridge/session-manager.d.ts +33 -1
- package/dist/bridge/session-manager.js +185 -2
- package/dist/bridge/session-manager.js.map +1 -1
- package/dist/bridge/types.d.ts +37 -0
- package/dist/data/scaffold-templates/tutorial-project/documentation/kanban.json +1 -1
- package/dist/messaging/bridge-client.d.ts +4 -0
- package/dist/messaging/bridge-client.js +20 -1
- package/dist/messaging/bridge-client.js.map +1 -1
- package/dist/messaging/index.js +38 -10
- package/dist/messaging/index.js.map +1 -1
- package/dist/scripts/kanban.js +448 -2
- package/dist/scripts/scaffold.js +40 -1
- package/dist/store/actions/approve.md +1 -1
- package/dist/store/actions/challenge-implementation.md +149 -0
- package/dist/store/actions/challenge.md +119 -0
- package/dist/store/actions/checkpoint.md +2 -2
- package/dist/store/actions/context.md +3 -3
- package/dist/store/actions/create-card.md +1 -1
- package/dist/store/actions/deep-design.md +13 -3
- package/dist/store/actions/design-requirements.md +11 -1
- package/dist/store/actions/explore.md +1 -1
- package/dist/store/actions/implement.md +1 -1
- package/dist/store/actions/onboard.md +2 -4
- package/dist/store/actions/show-card.md +2 -2
- package/dist/store/actions/summarize.md +3 -3
- package/dist/store/actions/test-review.md +1 -1
- package/dist/store/skills/kanban/SKILL.md +77 -3
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/app-path-routes-manifest.json +1 -0
- package/dist/web/.next/build-manifest.json +2 -2
- package/dist/web/.next/prerender-manifest.json +3 -3
- package/dist/web/.next/routes-manifest.json +6 -0
- package/dist/web/.next/server/app/_global-error.html +2 -2
- package/dist/web/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/_not-found.html +1 -1
- package/dist/web/.next/server/app/_not-found.rsc +10 -10
- package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
- package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/.next/server/app/api/changelog/route/app-paths-manifest.json +3 -0
- package/dist/web/.next/server/app/api/changelog/route/build-manifest.json +11 -0
- package/dist/web/.next/server/app/api/changelog/route/server-reference-manifest.json +4 -0
- package/dist/web/.next/server/app/api/changelog/route.js +7 -0
- package/dist/web/.next/server/app/api/changelog/route.js.map +5 -0
- package/dist/web/.next/server/app/api/changelog/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/changelog/route_client-reference-manifest.js +2 -0
- package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app-paths-manifest.json +1 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__92f81907._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__a259539f._.js +3 -0
- package/dist/web/.next/server/chunks/_next-internal_server_app_api_changelog_route_actions_d6e239bf.js +3 -0
- package/dist/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_18324462.js +1 -1
- package/dist/web/.next/server/chunks/src_lib_scheduler_ts_03988e3e._.js +1 -1
- package/dist/web/.next/server/chunks/src_lib_scheduler_ts_7120457c._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__1f5fc489._.js +4 -3
- package/dist/web/.next/server/chunks/ssr/{[root-of-the-server]__077f472c._.js → [root-of-the-server]__6183d28c._.js} +1 -1
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__bcbe4bf2._.js +4 -3
- package/dist/web/.next/server/chunks/ssr/src_components_Dashboard_tsx_efc4dc27._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_components_c4135402._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_contexts_VoiceContext_tsx_cfba7292._.js +1 -1
- package/dist/web/.next/server/pages/404.html +1 -1
- package/dist/web/.next/server/pages/500.html +2 -2
- package/dist/web/.next/server/server-reference-manifest.js +1 -1
- package/dist/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/.next/static/chunks/293449b828207656.css +1 -0
- package/dist/web/.next/static/chunks/{8415039c5941cf5c.js → 3a5721af09d1c753.js} +4 -3
- package/dist/web/.next/static/chunks/{8fb2a99c64580de7.js → 98311243e9a5a0ec.js} +1 -1
- package/dist/web/.next/static/chunks/{b8e0c1aeea4a14bc.js → a47f36b030917d1f.js} +1 -1
- package/dist/web/.next/static/chunks/d60c422421920130.js +5 -0
- package/dist/web/.next/static/chunks/{f55f3c8c1a52f80c.js → f566a4b05a9cd6ba.js} +1 -1
- package/dist/web/.next/static/chunks/{4049cceee6a49323.js → fa78afe3ceed998b.js} +1 -1
- package/dist/web/src/app/api/changelog/route.ts +45 -0
- package/dist/web/src/app/api/kanban/route.ts +52 -12
- package/dist/web/src/app/api/terminal-classes/route.ts +43 -4
- package/dist/web/src/components/ActivityFeed.tsx +3 -0
- package/dist/web/src/components/BranchTab.tsx +115 -0
- package/dist/web/src/components/ChangelogModal.tsx +233 -0
- package/dist/web/src/components/Dashboard.tsx +11 -0
- package/dist/web/src/components/GlobalClaudePanel.tsx +6 -0
- package/dist/web/src/components/ProjectKanban.tsx +38 -8
- package/dist/web/src/components/VersionUpdateToast.tsx +6 -4
- package/dist/web/src/components/VoiceControlBar.tsx +1 -1
- package/dist/web/src/lib/scheduler.ts +2 -1
- package/dist/web/src/lib/types.ts +24 -0
- package/dist/web/tsconfig.tsbuildinfo +1 -1
- package/lib/cli/sync.d.ts +7 -0
- package/lib/cli/sync.d.ts.map +1 -1
- package/lib/cli/sync.js +25 -0
- package/lib/cli/sync.js.map +1 -1
- package/lib/cli/update.d.ts.map +1 -1
- package/lib/cli/update.js +9 -0
- package/lib/cli/update.js.map +1 -1
- package/package.json +1 -1
- package/templates/changelog.json +268 -0
- package/templates/kanban-seed.json +1 -1
- package/templates/store/actions/approve.md +1 -1
- package/templates/store/actions/challenge-implementation.md +149 -0
- package/templates/store/actions/challenge.md +119 -0
- package/templates/store/actions/checkpoint.md +2 -2
- package/templates/store/actions/context.md +3 -3
- package/templates/store/actions/create-card.md +1 -1
- package/templates/store/actions/deep-design.md +13 -3
- package/templates/store/actions/design-requirements.md +11 -1
- package/templates/store/actions/explore.md +1 -1
- package/templates/store/actions/implement.md +1 -1
- package/templates/store/actions/onboard.md +2 -4
- package/templates/store/actions/show-card.md +2 -2
- package/templates/store/actions/summarize.md +3 -3
- package/templates/store/actions/test-review.md +1 -1
- package/templates/store/skills/kanban/SKILL.md +77 -3
- package/templates/terminal-classes.json +51 -0
- package/templates/tutorial-project/documentation/kanban.json +1 -1
- package/templates/updates/actions/approve.md +1 -1
- package/templates/updates/actions/challenge-implementation.md +149 -0
- package/templates/updates/actions/challenge.md +119 -0
- package/templates/updates/actions/checkpoint.md +2 -2
- package/templates/updates/actions/context.md +3 -3
- package/templates/updates/actions/create-card.md +1 -1
- package/templates/updates/actions/deep-design.md +13 -3
- package/templates/updates/actions/design-requirements.md +11 -1
- package/templates/updates/actions/explore.md +1 -1
- package/templates/updates/actions/implement.md +1 -1
- package/templates/updates/actions/onboard.md +2 -4
- package/templates/updates/actions/show-card.md +2 -2
- package/templates/updates/actions/summarize.md +3 -3
- package/templates/updates/actions/test-review.md +1 -1
- package/templates/updates/skills/kanban/SKILL.md +77 -3
- package/dist/web/.next/static/chunks/18cfbdd7e977bb01.css +0 -1
- package/dist/web/.next/static/chunks/a0f5f9cdee8a22c1.js +0 -4
- /package/dist/web/.next/static/{b2V8jC3HBMi4vgm7Kie3H → O1Ine2WtyXv6EQJcwHyOV}/_buildManifest.js +0 -0
- /package/dist/web/.next/static/{b2V8jC3HBMi4vgm7Kie3H → O1Ine2WtyXv6EQJcwHyOV}/_clientMiddlewareManifest.json +0 -0
- /package/dist/web/.next/static/{b2V8jC3HBMi4vgm7Kie3H → O1Ine2WtyXv6EQJcwHyOV}/_ssgManifest.js +0 -0
package/dist/scripts/kanban.js
CHANGED
|
@@ -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:${
|
|
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;
|
package/dist/scripts/scaffold.js
CHANGED
|
@@ -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 {
|