@cspeach/cli 1.1.29 → 1.1.31
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/.cspeach/project.json +4 -0
- package/README.md +11 -1
- package/dist/agent/loop.js +127 -11
- package/dist/auth/auth-file.js +2 -2
- package/dist/cli.js +39 -13
- package/dist/commands/config-set.js +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/plan-continue.js +1 -1
- package/dist/commands/plan-gate.js +8 -1
- package/dist/commands/plan-resume.js +24 -18
- package/dist/commands/plan-startup.js +49 -6
- package/dist/commands/team-discover.js +195 -0
- package/dist/commands/team-plan.js +585 -0
- package/dist/commands/team.js +652 -46
- package/dist/commands/whoami.js +1 -1
- package/dist/config/paths.js +15 -1
- package/dist/journey/chain.js +728 -0
- package/dist/journey/clean-core.js +213 -0
- package/dist/journey/flow.js +166 -0
- package/dist/journey/run.js +435 -0
- package/dist/journey/session-deps.js +77 -0
- package/dist/journey/trigger.js +153 -0
- package/dist/lane/app-chain.js +9 -7
- package/dist/lane/chain-token.js +9 -5
- package/dist/lane/roots.js +97 -41
- package/dist/one-shot.js +21 -1
- package/dist/projects/clean-core-confirm.js +192 -0
- package/dist/projects/detail-path.js +9 -4
- package/dist/projects/extract-clean-core.js +82 -0
- package/dist/projects/migration.js +4 -105
- package/dist/projects/plan-lane.js +67 -17
- package/dist/projects/plan-sources.js +13 -7
- package/dist/projects/project-folder.js +131 -0
- package/dist/projects/promote-command.js +39 -0
- package/dist/projects/save-command.js +115 -15
- package/dist/projects/saved-lines.js +17 -3
- package/dist/projects/sort-command.js +499 -0
- package/dist/projects/sort-core.js +248 -0
- package/dist/projects/validate.js +2 -1
- package/dist/projects/workspace-retire.js +48 -0
- package/dist/projects/workspace.js +182 -120
- package/dist/register/atc-file.js +9 -1
- package/dist/register/discover.js +901 -0
- package/dist/register/findings.js +82 -0
- package/dist/register/measure.js +60 -1
- package/dist/register/offer.js +125 -0
- package/dist/register/picture.js +25 -0
- package/dist/register/plan-import.js +325 -0
- package/dist/register/records.js +57 -0
- package/dist/register/status-file/estate-parts.js +168 -0
- package/dist/register/status-file/plan-parts.js +68 -0
- package/dist/register/status-file/progress-chart.js +110 -0
- package/dist/register/status-file/register-map.js +221 -0
- package/dist/register/status-file/register-model.js +341 -0
- package/dist/register/status-file/register-parts.js +555 -0
- package/dist/register/status-file/register-status-file.js +189 -0
- package/dist/register/status-file/team-css.js +5 -0
- package/dist/register/status-file-sync.js +22 -0
- package/dist/register/status-html.js +19 -0
- package/dist/register/store.js +40 -2
- package/dist/register/workbooks.js +124 -0
- package/dist/register/write-whole.js +22 -0
- package/dist/register/xlsx.js +127 -0
- package/dist/register/xref.js +117 -0
- package/dist/renderer/startup-lines.js +11 -0
- package/dist/renderer/tool-labels.js +3 -1
- package/dist/renderer/tool-widget.js +15 -1
- package/dist/repl/at-completer.js +6 -2
- package/dist/repl/at-picker.js +1 -1
- package/dist/repl/journey-entry.js +25 -0
- package/dist/repl.js +158 -39
- package/dist/session/backfill.js +342 -0
- package/dist/session/project-match.js +63 -0
- package/dist/session/resume.js +72 -24
- package/dist/session/schema.js +3 -1
- package/dist/session/store.js +96 -51
- package/dist/skill-catalog.js +2 -2
- package/dist/skills/bundled-skills.js +45 -45
- package/dist/tools/_filesystem-shared.js +190 -21
- package/dist/tools/_team-work-path.js +133 -0
- package/dist/tools/approval.js +7 -1
- package/dist/tools/filesystem/extract-xlsx.js +27 -3
- package/dist/tools/filesystem/file-edit.js +12 -4
- package/dist/tools/filesystem/file-read.js +8 -4
- package/dist/tools/filesystem/file-write.js +12 -4
- package/dist/tools/filesystem/glob.js +33 -8
- package/dist/tools/filesystem/grep.js +9 -4
- package/dist/tools/filesystem/read-document.js +6 -3
- package/dist/tools/project/cca_merge.js +8 -2
- package/dist/tools/project/team_estate.js +91 -0
- package/dist/tools/project/team_findings.js +145 -0
- package/dist/tools/project/upgrade_merge_progress.js +8 -2
- package/dist/tools/sap-read.js +21 -4
- package/dist/tools/sap-write.js +203 -5
- package/dist/tools/subagent/read_agent.js +3 -0
- package/dist/tools/tool-loading.js +2 -2
- package/dist/tools/transport-fit-check.js +117 -0
- package/dist/tools/transport-fit.js +15 -7
- package/dist/tools/transport.js +57 -2
- package/dist/ui/app.js +2 -2
- package/dist/ui/approval-modal.js +74 -6
- package/dist/ui/widgets/confirm-card.js +68 -5
- package/dist/util/open-in-browser.js +143 -0
- package/package.json +120 -120
package/dist/repl.js
CHANGED
|
@@ -10,7 +10,7 @@ import { select, password as promptPassword } from './repl/themed-prompts.js';
|
|
|
10
10
|
import { loadConfig, resolveLocalBuild, resolveStandalone, DEFAULT_CONFIG } from './config/loader.js';
|
|
11
11
|
import { getLook, setLook, setBackground } from './renderer/look.js';
|
|
12
12
|
import { applyColorMode } from './renderer/color-mode.js';
|
|
13
|
-
import { startEarlyBrand, aliasSourceNote, panelsHintLine, panelsResumeLine, panelsStartTail, panelsInterruptedNote, formatPromptEcho } from './renderer/startup-lines.js';
|
|
13
|
+
import { startEarlyBrand, aliasSourceNote, panelsHintLine, panelsResumeLine, panelsStartTail, panelsInterruptedNote, lastSessionHereLine, formatPromptEcho } from './renderer/startup-lines.js';
|
|
14
14
|
import { formatAttention, formatFailure, formatInfo } from './renderer/notice-shape.js';
|
|
15
15
|
import { formatPanel } from './renderer/panel-rows.js';
|
|
16
16
|
import { sanitizeControlChars } from './renderer/sanitize.js';
|
|
@@ -21,6 +21,7 @@ import { createOfflineAdtClient } from './sap/offline-adt-client.js';
|
|
|
21
21
|
import { STANDALONE_ALIAS } from './tools/index.js';
|
|
22
22
|
import { resumeStandaloneDecision, applySystemSwitch } from './repl/resume-standalone.js';
|
|
23
23
|
import { clearAppFlow } from './lane/app-flow.js';
|
|
24
|
+
import { clearJourneyFlow, isJourneyHop, journeyHopLine } from './journey/flow.js';
|
|
24
25
|
import { applyResumedSkill, isShortFollowUp } from './router/resume-routing.js';
|
|
25
26
|
import { readableInterruptReason, isUserStopReason } from './session/interrupt-reason.js';
|
|
26
27
|
import { getStore } from './auth/api-key.js';
|
|
@@ -79,8 +80,8 @@ import { registerReadline, withInquirer } from './repl/inquirer-guard.js';
|
|
|
79
80
|
import { pipeRevivedStdin } from './repl/reset-tty-stdin.js';
|
|
80
81
|
import { logsDir } from './config/paths.js';
|
|
81
82
|
import { noticeLogPathFor, pruneNoticeLogs, setNoticeLogPath } from './renderer/notice-log.js';
|
|
82
|
-
import {
|
|
83
|
-
import {
|
|
83
|
+
import { maybeOfferAbapforgeMigration } from './projects/migration.js';
|
|
84
|
+
import { shouldOfferAbapforgeMigration } from './projects/workspace-retire.js';
|
|
84
85
|
import { slashCompleter } from './repl/slash-completer.js';
|
|
85
86
|
import { openSlashPicker, isExactSlashMatch } from './repl/slash-picker.js';
|
|
86
87
|
import { openAtPicker, pendingAtTokenPrefix } from './repl/at-picker.js';
|
|
@@ -169,12 +170,15 @@ import './tools/project/playbook_get.js';
|
|
|
169
170
|
import './tools/project/convention_get.js';
|
|
170
171
|
import './tools/project/cca_merge.js';
|
|
171
172
|
import './tools/project/upgrade_merge_progress.js';
|
|
173
|
+
import './tools/project/team_findings.js';
|
|
174
|
+
import './tools/project/team_estate.js';
|
|
172
175
|
import './tools/subagent/_background-shared.js';
|
|
173
176
|
import './tools/subagent/background_run.js';
|
|
174
177
|
import './tools/subagent/monitor_emit.js';
|
|
175
178
|
import './tools/subagent/schedule_draft_create.js';
|
|
176
179
|
import './tools/subagent/agent_run.js';
|
|
177
180
|
import './tools/subagent/read_agent.js';
|
|
181
|
+
import { folderTag, currentFolderKey } from './session/project-match.js';
|
|
178
182
|
const HISTORY_SIZE = 500;
|
|
179
183
|
// Persistence (read + dedup-guarded append) lives in ./repl/history.ts —
|
|
180
184
|
// the single writer shared with the Ink Footer (Task 8, ux-wave1).
|
|
@@ -389,22 +393,52 @@ export async function runRepl(opts) {
|
|
|
389
393
|
const earlyBrand = getLook() === 'panels'
|
|
390
394
|
? startEarlyBrand({ version: CLI_VERSION, tty: process.stdout.isTTY === true, write: (s) => { process.stdout.write(s); } })
|
|
391
395
|
: undefined;
|
|
396
|
+
// 2026-10-08 (Task 12) — once, tag sessions saved before folders existed so
|
|
397
|
+
// the lines below and the pickers see them. Both looks; capped, never throws.
|
|
398
|
+
try {
|
|
399
|
+
const { backfillSessionFolders } = await import('./session/backfill.js');
|
|
400
|
+
await backfillSessionFolders().catch(() => { });
|
|
401
|
+
}
|
|
402
|
+
catch { /* best effort */ }
|
|
392
403
|
// v0.6 — surface interrupted sessions at startup so the developer doesn't
|
|
393
404
|
// have to remember the session id from the crash UX. Like `git stash list`
|
|
394
405
|
// for resilience: if you have any unfinished work, you see it immediately.
|
|
395
406
|
// Skip when --resume is already in flight (the user is resuming right now).
|
|
407
|
+
// 2026-10-08 (sessions/folders) — both lines are scoped to THIS folder; the
|
|
408
|
+
// dim "Last session here" line points at `cspeach --continue`.
|
|
396
409
|
if (!opts?.resumedSession) {
|
|
397
410
|
try {
|
|
398
|
-
const { countInterruptedSessions } = await import('./session/store.js');
|
|
399
|
-
const
|
|
411
|
+
const { countInterruptedSessions, latestSessionHere } = await import('./session/store.js');
|
|
412
|
+
const here = currentFolderKey(process.cwd());
|
|
413
|
+
const n = await countInterruptedSessions(here);
|
|
400
414
|
if (n > 0) {
|
|
401
415
|
if (getLook() === 'panels')
|
|
402
416
|
startupNotices.push(panelsInterruptedNote(n) + '\n');
|
|
403
417
|
else
|
|
404
|
-
console.log(formatAttention({ text: `You have ${n} interrupted session${n === 1 ? '' : 's'}`, next: 'cspeach --resume (
|
|
418
|
+
console.log(formatAttention({ text: `You have ${n} interrupted session${n === 1 ? '' : 's'}`, next: 'cspeach --resume (this folder\'s sessions)' }));
|
|
419
|
+
}
|
|
420
|
+
const last = await latestSessionHere(here);
|
|
421
|
+
if (last) {
|
|
422
|
+
if (getLook() === 'panels')
|
|
423
|
+
startupNotices.push(lastSessionHereLine(last) + '\n');
|
|
424
|
+
else
|
|
425
|
+
console.log(lastSessionHereLine(last));
|
|
405
426
|
}
|
|
406
427
|
}
|
|
407
428
|
catch { /* startup banner is best-effort */ }
|
|
429
|
+
// Task 11 — files left in old save locations: point to `cspeach files
|
|
430
|
+
// sort`, at most once per folder per day.
|
|
431
|
+
try {
|
|
432
|
+
const { legacyHintLine, claimLegacyHintToday } = await import('./projects/workspace.js');
|
|
433
|
+
const hint = legacyHintLine(process.cwd());
|
|
434
|
+
if (hint && claimLegacyHintToday(process.cwd())) {
|
|
435
|
+
if (getLook() === 'panels')
|
|
436
|
+
startupNotices.push(formatAttention({ text: hint }));
|
|
437
|
+
else
|
|
438
|
+
console.log(chalk.dim(hint));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
catch { /* the hint is best-effort */ }
|
|
408
442
|
}
|
|
409
443
|
// Phase 2.1 of Track B — when CSPEACH_PROJECT_CONTEXT=on the dogfooder
|
|
410
444
|
// is on the experimental enrichment path. Surface that visibly so they
|
|
@@ -725,7 +759,7 @@ export async function runRepl(opts) {
|
|
|
725
759
|
// through the same role resolution new sessions use (after the awaited
|
|
726
760
|
// served config), marks it 'predicted', saves, then stays pinned. Every
|
|
727
761
|
// resume entry (picker, --resume <id>) lands here via session/resume.ts.
|
|
728
|
-
const session = opts?.resumedSession ?? newSession(makeSessionId(), selectedAlias, null, resolveModelRole('session_default', cfg));
|
|
762
|
+
const session = opts?.resumedSession ?? newSession(makeSessionId(), selectedAlias, null, resolveModelRole('session_default', cfg), folderTag(process.cwd()));
|
|
729
763
|
// Round 6 item 4c — a resumed session keeps its skill for the first plain prompt.
|
|
730
764
|
let resumeKeepsSkill = Boolean(opts?.resumedSession?.skill);
|
|
731
765
|
// Round 7 item 8c — the footer shows a resumed session's spend at once.
|
|
@@ -973,7 +1007,8 @@ export async function runRepl(opts) {
|
|
|
973
1007
|
return;
|
|
974
1008
|
}
|
|
975
1009
|
// Task 13 — no echo for a plan hop: the chain already said "Next: <title>".
|
|
976
|
-
|
|
1010
|
+
// Journey hops (Task 3) — the journey already said its step line.
|
|
1011
|
+
const notice = isJourneyHop(session, cmd) ? null : autoRoutedNotice(cmd);
|
|
977
1012
|
if (notice)
|
|
978
1013
|
chunkEmitter.emit('chunk', formatInfo(notice));
|
|
979
1014
|
setImmediate(() => { void handleSubmit(cmd, { planHop }); });
|
|
@@ -1002,7 +1037,10 @@ export async function runRepl(opts) {
|
|
|
1002
1037
|
// new turn's content touched with no separator and looked like
|
|
1003
1038
|
// garbled overlap).
|
|
1004
1039
|
if (trimmed !== '/exit' && trimmed !== '/quit') {
|
|
1005
|
-
|
|
1040
|
+
// Rehearsal fix 1 — a journey hop was queued by CSPeach, not typed: one
|
|
1041
|
+
// plain journey line, never "You › <the command>".
|
|
1042
|
+
const hopLine = journeyHopLine(session, trimmed);
|
|
1043
|
+
chunkEmitter.emit('chunk', hopLine ? `\n${hopLine}\n` : formatPromptEcho(trimmed, process.stdout.columns || 80));
|
|
1006
1044
|
}
|
|
1007
1045
|
if (trimmed === '/exit' || trimmed === '/quit') {
|
|
1008
1046
|
done = true;
|
|
@@ -1212,6 +1250,7 @@ export async function runRepl(opts) {
|
|
|
1212
1250
|
session.todos = [];
|
|
1213
1251
|
todoEmitter.emit('update', []);
|
|
1214
1252
|
clearAppFlow(session); // Task 7 — /new ends an /abap-app flow
|
|
1253
|
+
clearJourneyFlow(session); // Task 3 — and a clean-core journey
|
|
1215
1254
|
chunkEmitter.emit('chunk', '\n' + formatInfo('Fresh start · the next prompt picks a skill again'));
|
|
1216
1255
|
return;
|
|
1217
1256
|
}
|
|
@@ -1270,6 +1309,41 @@ export async function runRepl(opts) {
|
|
|
1270
1309
|
if (rejectUnknownSkill(parsed, (m) => chunkEmitter.emit('chunk', chalk.yellow(`\n${m}\n`)))) {
|
|
1271
1310
|
return;
|
|
1272
1311
|
}
|
|
1312
|
+
// Guided clean-core journey (Task 3) — "start clean core on ZFI_AR",
|
|
1313
|
+
// "/abap-clean-core start ZFI_AR", "carry on" in a project folder.
|
|
1314
|
+
// Detected here, before the classifier; any other line is untouched.
|
|
1315
|
+
{
|
|
1316
|
+
const { detectJourney } = await import('./journey/trigger.js');
|
|
1317
|
+
const journey = detectJourney(effectiveInput, { cwd: process.cwd(), session });
|
|
1318
|
+
if (journey) {
|
|
1319
|
+
const { runJourneyEntry } = await import('./repl/journey-entry.js');
|
|
1320
|
+
const journeyAbort = new AbortController();
|
|
1321
|
+
currentTurnAbort = journeyAbort;
|
|
1322
|
+
interruptCount = 0;
|
|
1323
|
+
if (pendingCancel.applyTo(journeyAbort))
|
|
1324
|
+
interruptCount = 1;
|
|
1325
|
+
turnStatusEmitter.start('clean core');
|
|
1326
|
+
try {
|
|
1327
|
+
await runJourneyEntry(journey, {
|
|
1328
|
+
session, cwd: process.cwd(), adt, sapAlias: selectedAlias, standalone, headless: isHeadless(),
|
|
1329
|
+
emit: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
1330
|
+
queueNext: (cmd) => pendingDispatchAccessor.set(cmd),
|
|
1331
|
+
signal: journeyAbort.signal,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
finally {
|
|
1335
|
+
turnStatusEmitter.stop();
|
|
1336
|
+
currentTurnAbort = null;
|
|
1337
|
+
postTurn = true;
|
|
1338
|
+
}
|
|
1339
|
+
const queued = pendingDispatchAccessor.get();
|
|
1340
|
+
if (queued) {
|
|
1341
|
+
pendingDispatchAccessor.set(null);
|
|
1342
|
+
autoDispatch(queued);
|
|
1343
|
+
}
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1273
1347
|
// Record the prose body (post-shortcut stripping) for /reroute. Using
|
|
1274
1348
|
// `parsed.prompt` rather than `trimmed` means:
|
|
1275
1349
|
// - plain prose: `parsed.prompt === trimmed`, stored as-is
|
|
@@ -2354,6 +2428,17 @@ export async function runRepl(opts) {
|
|
|
2354
2428
|
// sits in this folder. Best-effort: never blocks the prompt.
|
|
2355
2429
|
if (!isHeadless()) {
|
|
2356
2430
|
void (async () => {
|
|
2431
|
+
// 2026-10-08 — one-time offer to retire the old `workspace_folder`
|
|
2432
|
+
// setting, before the plan offer so the two never overlap.
|
|
2433
|
+
try {
|
|
2434
|
+
const { maybeOfferWorkspaceRetire } = await import('./projects/workspace-retire.js');
|
|
2435
|
+
const { inkAwareConfirm } = await import('./agent/loop.js');
|
|
2436
|
+
await maybeOfferWorkspaceRetire({
|
|
2437
|
+
confirm: async (q) => (await inkAwareConfirm(q, { default: 'y' })) === 'y',
|
|
2438
|
+
log: (line) => chunkEmitter.emit('chunk', line + '\n'),
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
catch { /* the offer is best-effort */ }
|
|
2357
2442
|
try {
|
|
2358
2443
|
const { offerContinueAtStartup, inkStartupChooser } = await import('./commands/plan-startup.js');
|
|
2359
2444
|
const { resolveWorkspacePath } = await import('./projects/workspace.js');
|
|
@@ -2362,6 +2447,7 @@ export async function runRepl(opts) {
|
|
|
2362
2447
|
return;
|
|
2363
2448
|
await offerContinueAtStartup({
|
|
2364
2449
|
workspaceDir: ws,
|
|
2450
|
+
cwd: process.cwd(),
|
|
2365
2451
|
sessionId: session.id,
|
|
2366
2452
|
sapAlias: session.sap_system ?? undefined,
|
|
2367
2453
|
choose: inkStartupChooser(),
|
|
@@ -2662,40 +2748,40 @@ export async function runRepl(opts) {
|
|
|
2662
2748
|
registerReadline(rl, (v) => {
|
|
2663
2749
|
suppressRlClose = v;
|
|
2664
2750
|
}, { input: pasteInput });
|
|
2665
|
-
// 2026-
|
|
2666
|
-
//
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
],
|
|
2679
|
-
})),
|
|
2680
|
-
log: (line) => console.log(chalk.dim(line)),
|
|
2681
|
-
});
|
|
2751
|
+
// 2026-10-08: one-time offer to retire the old `workspace_folder` setting
|
|
2752
|
+
// (files now save in the folder you start in). Best-effort, never blocks.
|
|
2753
|
+
if (!isHeadless()) {
|
|
2754
|
+
try {
|
|
2755
|
+
const { maybeOfferWorkspaceRetire } = await import('./projects/workspace-retire.js');
|
|
2756
|
+
const { inkAwareConfirm } = await import('./agent/loop.js');
|
|
2757
|
+
await maybeOfferWorkspaceRetire({
|
|
2758
|
+
confirm: async (q) => (await inkAwareConfirm(q, { default: 'y' })) === 'y',
|
|
2759
|
+
log: (line) => console.log(chalk.dim(line)),
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
catch { /* best-effort */ }
|
|
2763
|
+
}
|
|
2682
2764
|
// 2026-06-15: one-time output-namespace migration. The customer-output
|
|
2683
2765
|
// root was renamed from the dead-brand `.abapforge/` to `.cspeach/`.
|
|
2684
2766
|
// Workspaces that still have a top-level `.abapforge/` get offered a
|
|
2685
2767
|
// one-time merge-move into `.cspeach/`. The dual-read resolver keeps old
|
|
2686
2768
|
// projects working if the user declines; "never" suppresses future offers.
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2769
|
+
// 2026-10-08: solo folders only — a team folder must not merge into the
|
|
2770
|
+
// shared team .cspeach, and a home start must not target the CLI home.
|
|
2771
|
+
if (shouldOfferAbapforgeMigration(process.cwd())) {
|
|
2772
|
+
await maybeOfferAbapforgeMigration(process.cwd(), {
|
|
2773
|
+
prompt: async ({ legacyDir, newDir }) => withInquirer(() => select({
|
|
2774
|
+
message: `Found legacy CSPeach outputs in ${legacyDir}. The output folder is now '.cspeach/'. Move them?`,
|
|
2775
|
+
default: 'move',
|
|
2776
|
+
choices: [
|
|
2777
|
+
{ value: 'move', name: `Move everything into ${newDir} (recommended)` },
|
|
2778
|
+
{ value: 'skip', name: `Not now — ask me again next time` },
|
|
2779
|
+
{ value: 'never', name: `Leave it and never ask again` },
|
|
2780
|
+
],
|
|
2781
|
+
})),
|
|
2782
|
+
log: (line) => console.log(chalk.dim(line)),
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2699
2785
|
// 2026-10-04 (spec §3.5) — "Continue '<plan>'?" (classic: numbered
|
|
2700
2786
|
// choices, Enter = the first). Continue queues the resume for the loop.
|
|
2701
2787
|
if (!isHeadless()) {
|
|
@@ -2707,6 +2793,7 @@ export async function runRepl(opts) {
|
|
|
2707
2793
|
if (ws) {
|
|
2708
2794
|
await offerContinueAtStartup({
|
|
2709
2795
|
workspaceDir: ws,
|
|
2796
|
+
cwd: process.cwd(),
|
|
2710
2797
|
sessionId: session.id,
|
|
2711
2798
|
sapAlias: session.sap_system ?? undefined,
|
|
2712
2799
|
choose: classicStartupChooser(inkAwarePrompt, (...l) => l.forEach((x) => console.log(x))),
|
|
@@ -2742,9 +2829,13 @@ export async function runRepl(opts) {
|
|
|
2742
2829
|
// destroys the session context.
|
|
2743
2830
|
// Task 13 — no echo for a plan hop: the chain already said "Next: <title>".
|
|
2744
2831
|
{
|
|
2745
|
-
const n = autoRoutedNotice(dispatchedCmd);
|
|
2832
|
+
const n = isJourneyHop(session, dispatchedCmd) ? null : autoRoutedNotice(dispatchedCmd);
|
|
2746
2833
|
if (n)
|
|
2747
2834
|
console.log(formatInfo(n).trimEnd());
|
|
2835
|
+
// Rehearsal fix 1 — the journey hop says what CSPeach is doing (the Ink mirror).
|
|
2836
|
+
const hop = journeyHopLine(session, dispatchedCmd);
|
|
2837
|
+
if (hop)
|
|
2838
|
+
console.log(hop);
|
|
2748
2839
|
classicPlanHop = planHops.take(dispatchedCmd);
|
|
2749
2840
|
}
|
|
2750
2841
|
line = dispatchedCmd;
|
|
@@ -3040,6 +3131,7 @@ export async function runRepl(opts) {
|
|
|
3040
3131
|
session.awaitingSkillAnswer = false;
|
|
3041
3132
|
session.todos = []; // P2 — /new clears the plan (spec §4)
|
|
3042
3133
|
clearAppFlow(session); // Task 7 — /new ends an /abap-app flow
|
|
3134
|
+
clearJourneyFlow(session); // Task 3 — and a clean-core journey
|
|
3043
3135
|
console.log(formatInfo('Fresh start · the next prompt picks a skill again').trimEnd());
|
|
3044
3136
|
continue;
|
|
3045
3137
|
}
|
|
@@ -3222,6 +3314,33 @@ export async function runRepl(opts) {
|
|
|
3222
3314
|
if (rejectUnknownSkill(parsed, (m) => console.log(chalk.yellow(`\n${m}\n`)))) {
|
|
3223
3315
|
continue;
|
|
3224
3316
|
}
|
|
3317
|
+
// Guided clean-core journey (Task 3) — classic mirror of the Ink entry.
|
|
3318
|
+
// Cards become one typed line here (no card listener); a queued step is
|
|
3319
|
+
// picked up by the loop-top dispatch consumer.
|
|
3320
|
+
{
|
|
3321
|
+
const { detectJourney } = await import('./journey/trigger.js');
|
|
3322
|
+
const journey = detectJourney(resolvedTrimmed, { cwd: process.cwd(), session });
|
|
3323
|
+
if (journey) {
|
|
3324
|
+
const { runJourneyEntry } = await import('./repl/journey-entry.js');
|
|
3325
|
+
const journeyAbort = new AbortController();
|
|
3326
|
+
const onJourneySigint = () => { journeyAbort.abort(); };
|
|
3327
|
+
turnInProgress = true;
|
|
3328
|
+
rl.on('SIGINT', onJourneySigint);
|
|
3329
|
+
try {
|
|
3330
|
+
await runJourneyEntry(journey, {
|
|
3331
|
+
session, cwd: process.cwd(), adt, sapAlias: selectedAlias, standalone, headless: isHeadless(),
|
|
3332
|
+
emit: (line) => console.log(line),
|
|
3333
|
+
queueNext: (cmd) => pendingDispatchAccessor.set(cmd),
|
|
3334
|
+
signal: journeyAbort.signal,
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
finally {
|
|
3338
|
+
rl.removeListener('SIGINT', onJourneySigint);
|
|
3339
|
+
turnInProgress = false;
|
|
3340
|
+
}
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3225
3344
|
// Record the prose body (post-shortcut stripping) for /reroute. Using
|
|
3226
3345
|
// `parsed.prompt` rather than `trimmed` means:
|
|
3227
3346
|
// - plain prose: `parsed.prompt === trimmed`, stored as-is
|