@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
|
@@ -7,6 +7,7 @@ import { extractUpgradeScan, extractUpgradeProgress, extractUpgradeReport } from
|
|
|
7
7
|
import { extractCcaAssessment } from './extract-cca.js';
|
|
8
8
|
import { extractModernizeResult } from './extract-modernize.js';
|
|
9
9
|
import { extractTestCoverage } from './extract-test-coverage.js';
|
|
10
|
+
import { extractCleanCoreDecision } from './extract-clean-core.js';
|
|
10
11
|
import { extractPlan, laneConflictsWithPrior, LANE_NEW_FAMILY } from './extract-plan.js';
|
|
11
12
|
import { buildEnvelope } from './build.js';
|
|
12
13
|
import { buildPlanRevision } from './plan-run.js';
|
|
@@ -17,6 +18,7 @@ import { saveProject } from './save.js';
|
|
|
17
18
|
import { formatSavedLines } from './saved-lines.js';
|
|
18
19
|
import { writePlanHandover } from './handover-md.js';
|
|
19
20
|
import { ensureWorkspace, resolveWorkspacePath } from './workspace.js';
|
|
21
|
+
import { resolveProjectFolderSync, ensureProjectId } from './project-folder.js';
|
|
20
22
|
import { askYesNo, isYes } from './yes-no.js';
|
|
21
23
|
import { checkLaneConsistency, checkRequiredDeploy, LANE_CHANGED_AT_CREATE, readPlanLane } from './plan-lane.js';
|
|
22
24
|
import { specGapRefForSave } from './plan-sources.js';
|
|
@@ -103,6 +105,14 @@ const SKILL_REGISTRY = {
|
|
|
103
105
|
itemNoun: 'phases',
|
|
104
106
|
manifestMarker: 'plan-manifest',
|
|
105
107
|
},
|
|
108
|
+
'abap-clean-core': {
|
|
109
|
+
// 2026-10-06 (clean-core mission control): a triage table. The rows become
|
|
110
|
+
// team project decisions only after one Yes per row (clean-core-confirm.ts).
|
|
111
|
+
artefactType: 'clean-core-decision',
|
|
112
|
+
extract: (md) => extractCleanCoreDecision(md),
|
|
113
|
+
itemNoun: 'decisions',
|
|
114
|
+
manifestMarker: 'clean-core-manifest',
|
|
115
|
+
},
|
|
106
116
|
};
|
|
107
117
|
/**
|
|
108
118
|
* C1 (2026-06-11, defect D22) — map an inline artifact-manifest block in the
|
|
@@ -128,6 +138,8 @@ export function detectArtifactSkill(markdown) {
|
|
|
128
138
|
return 'abap-modernize';
|
|
129
139
|
if (/<!--\s*(?:csforge|cspeach):test-coverage-manifest\b/.test(markdown))
|
|
130
140
|
return 'abap-test';
|
|
141
|
+
if (/<!--\s*(?:csforge|cspeach):clean-core-manifest\b/.test(markdown))
|
|
142
|
+
return 'abap-clean-core';
|
|
131
143
|
const up = /<!--\s*(?:csforge|cspeach):upgrade-manifest\s*\n([\s\S]*?)\n\s*-->/.exec(markdown);
|
|
132
144
|
if (up) {
|
|
133
145
|
const art = /(?:^|\n)\s*artefact:\s*(\S+)/.exec(up[1] ?? '');
|
|
@@ -360,22 +372,54 @@ export async function runSaveCommand(args) {
|
|
|
360
372
|
}
|
|
361
373
|
// C1 review fix — name the revision target BEFORE the confirm so the user
|
|
362
374
|
// knows what a 'y' will do. Log line only, deliberately NOT a second
|
|
363
|
-
// question: the B5 headless contract (below) allows
|
|
375
|
+
// question: the B5 headless contract (below) allows one `prompt` question.
|
|
364
376
|
if (planExtract && prior) {
|
|
365
377
|
const priorFilename = prior.path.split(/[\\\/]/).pop();
|
|
366
378
|
args.log(`[save] this will save as v${prior.envelope.version + 1} of existing plan '${prior.envelope.title}' (${priorFilename})`);
|
|
367
379
|
}
|
|
368
|
-
// CONTRACT (B5): runSaveCommand
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
380
|
+
// CONTRACT (B5): runSaveCommand asks 1+N cards — this save confirm, then
|
|
381
|
+
// (clean-core decisions only) one card per row. Only the save confirm may
|
|
382
|
+
// fall back to `prompt`: the headless path in loop.ts (maybeOfferSave)
|
|
383
|
+
// installs a prompt that blanket-answers 'y' to WHATEVER it is asked, so
|
|
384
|
+
// the row cards go through `confirm` only (never `prompt`), and a headless
|
|
385
|
+
// run confirms no row. Any further question routed through `prompt` would
|
|
386
|
+
// silently receive 'y' too: give maybeOfferSave's headless prompt a
|
|
387
|
+
// question-aware answer first.
|
|
388
|
+
// Inside a journey a triage is saved without the question: the journey needs the rows.
|
|
389
|
+
const journey = spec.artefactType === 'clean-core-decision' ? args.journey : undefined;
|
|
390
|
+
const answer = journey ? 'y' : await askYesNo({ question: 'Save as project file?', confirm: args.confirm, prompt: args.prompt });
|
|
375
391
|
if (!isYes(answer)) {
|
|
376
392
|
args.onNotSaved?.('declined');
|
|
377
393
|
return null;
|
|
378
394
|
}
|
|
395
|
+
// 2026-10-06 — a clean-core triage becomes team project decisions only row by
|
|
396
|
+
// row, each on its own Yes. Without a joined team project nothing is asked.
|
|
397
|
+
let tally;
|
|
398
|
+
if (spec.artefactType === 'clean-core-decision') {
|
|
399
|
+
const { confirmCleanCoreRows, decideCleanCoreRows } = await import('./clean-core-confirm.js');
|
|
400
|
+
const { activeProject } = await import('../register/store.js');
|
|
401
|
+
const joined = activeProject() !== null;
|
|
402
|
+
if (journey && joined) {
|
|
403
|
+
// Guided journey: [Yes] [Keep as is] [Skip] per row.
|
|
404
|
+
const r = await decideCleanCoreRows(extract, {
|
|
405
|
+
card: journey.card, ask: journey.ask, skip: journey.skip, log: args.log,
|
|
406
|
+
});
|
|
407
|
+
extract = r.extract;
|
|
408
|
+
tally = r.tally;
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
if (journey) {
|
|
412
|
+
// Inside a journey: no team words, no command to type (Task 7).
|
|
413
|
+
const { JOURNEY_NOT_IN_PROJECT_LINE } = await import('./clean-core-confirm.js');
|
|
414
|
+
args.log(JOURNEY_NOT_IN_PROJECT_LINE);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
extract = await confirmCleanCoreRows(extract, {
|
|
418
|
+
...(args.confirm ? { confirm: args.confirm } : {}), log: args.log, joined,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
379
423
|
let env;
|
|
380
424
|
if (planExtract && prior) {
|
|
381
425
|
// C1 (D30) — the 49dd duplicate: the generic save used to mint a fresh
|
|
@@ -414,6 +458,17 @@ export async function runSaveCommand(args) {
|
|
|
414
458
|
let outDir;
|
|
415
459
|
try {
|
|
416
460
|
outDir = await ensureWorkspace();
|
|
461
|
+
// 2026-10-08 — the first save writes .cspeach/project.json (solo/home);
|
|
462
|
+
// a team folder uses its team id. Never fails the save.
|
|
463
|
+
try {
|
|
464
|
+
const pf = resolveProjectFolderSync(process.cwd());
|
|
465
|
+
const projectId = pf.kind === 'team' ? pf.projectId : ensureProjectId(pf.workDir);
|
|
466
|
+
try {
|
|
467
|
+
args.onProjectId?.(projectId);
|
|
468
|
+
}
|
|
469
|
+
catch { /* tagging is best-effort */ }
|
|
470
|
+
}
|
|
471
|
+
catch { /* id file is best-effort */ }
|
|
417
472
|
}
|
|
418
473
|
catch {
|
|
419
474
|
outDir = args.cwd;
|
|
@@ -428,7 +483,9 @@ export async function runSaveCommand(args) {
|
|
|
428
483
|
writePlanHandover(path, args.log);
|
|
429
484
|
// 1.1.28 — two plain lines; the old suggested-email block is gone (saved-lines.ts).
|
|
430
485
|
const [savedLine, reviewLine] = formatSavedLines(path, args.cwd);
|
|
431
|
-
|
|
486
|
+
// Inside a journey the file is plumbing: the closing lines say what was recorded.
|
|
487
|
+
if (!journey)
|
|
488
|
+
args.log(savedLine);
|
|
432
489
|
// Project Register (2026-09-18): when this laptop has joined a project, a
|
|
433
490
|
// decision / fix record is written to the LOCAL outbox as a by-product of the
|
|
434
491
|
// save. No project joined → `activeProject()` is one missing-file read and
|
|
@@ -438,20 +495,63 @@ export async function runSaveCommand(args) {
|
|
|
438
495
|
const joined = activeProject();
|
|
439
496
|
if (joined) {
|
|
440
497
|
const { recordFromEnvelope } = await import('../register/records.js');
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
498
|
+
// Task 15 — detail files are read from where the skill wrote them: the
|
|
499
|
+
// seat work root in a team folder, the start folder otherwise. The
|
|
500
|
+
// record itself still goes to the shared outbox.
|
|
501
|
+
const res = recordFromEnvelope(env, path, joined, { name: joined.name ?? args.author.name }, resolveProjectFolderSync(args.cwd).workRoot);
|
|
502
|
+
if (tally) {
|
|
503
|
+
const { journeyRowLines } = await import('./clean-core-confirm.js');
|
|
504
|
+
for (const line of journeyRowLines(tally, res?.refused ?? []))
|
|
505
|
+
args.log(line);
|
|
506
|
+
}
|
|
507
|
+
else if (res?.file)
|
|
508
|
+
args.log(`Recorded for team project ${joined.projectId}: ${res.written} objects → ${basename(res.file)}`);
|
|
509
|
+
if (res?.file) {
|
|
510
|
+
const { regenerateWorkingList } = await import('../register/workbooks.js');
|
|
511
|
+
// The name the team project knows this laptop by (init --lead), as the team verbs use.
|
|
512
|
+
const sheet = regenerateWorkingList(joined, joined.name ?? args.author.name);
|
|
513
|
+
if (journey) {
|
|
514
|
+
const { journeyListLine } = await import('./clean-core-confirm.js');
|
|
515
|
+
const line = journeyListLine(sheet.kind);
|
|
516
|
+
if (line)
|
|
517
|
+
args.log(line);
|
|
518
|
+
}
|
|
519
|
+
else if (sheet.line)
|
|
520
|
+
args.log(sheet.line.trimStart());
|
|
521
|
+
}
|
|
444
522
|
if (res?.refused.length) {
|
|
445
|
-
args.log(`Not recorded (${res.refused.length} not in the team project's object list): ${res.refused.slice(0, 5).join(', ')}${res.refused.length > 5 ? ', …' : ''}`);
|
|
523
|
+
args.log(`Not recorded (${res.refused.length} not in ${tally ? 'your project' : 'the team project'}'s object list): ${res.refused.slice(0, 5).join(', ')}${res.refused.length > 5 ? ', …' : ''}`);
|
|
446
524
|
}
|
|
447
525
|
}
|
|
448
526
|
}
|
|
449
527
|
catch (e) {
|
|
450
|
-
|
|
528
|
+
if (journey) {
|
|
529
|
+
const { notRecordedLine } = await import('../journey/run.js');
|
|
530
|
+
args.log(notRecordedLine(e));
|
|
531
|
+
}
|
|
532
|
+
else
|
|
533
|
+
args.log(`(team project record not written: ${e instanceof Error ? e.message : String(e)})`);
|
|
451
534
|
}
|
|
452
535
|
if (planExtract && prior) {
|
|
453
536
|
args.log(`(revision v${env.version} of the existing plan — version chain preserved, no new id)`);
|
|
454
537
|
}
|
|
455
|
-
|
|
538
|
+
if (!journey)
|
|
539
|
+
args.log(reviewLine);
|
|
540
|
+
if (journey && tally) {
|
|
541
|
+
if (tally.cancelled) {
|
|
542
|
+
journey.pause();
|
|
543
|
+
}
|
|
544
|
+
else if (journey.page?.length) {
|
|
545
|
+
// One triage per page: a row of the page the triage left out is put off
|
|
546
|
+
// until "carry on", so the journey moves on instead of asking it again — and says so.
|
|
547
|
+
const answered = new Set([...tally.yes, ...tally.kept.map((k) => k.key), ...tally.skipped]);
|
|
548
|
+
const left = journey.page.filter((k) => !answered.has(k.toUpperCase()));
|
|
549
|
+
if (left.length) {
|
|
550
|
+
journey.leaveForLater(left);
|
|
551
|
+
const { leftOutLine } = await import('./clean-core-confirm.js');
|
|
552
|
+
args.log(leftOutLine(left));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
456
556
|
return path;
|
|
457
557
|
}
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Never prints a user name.
|
|
12
12
|
*/
|
|
13
|
-
import { basename, dirname,
|
|
13
|
+
import { basename, dirname, resolve } from 'node:path';
|
|
14
|
+
import { resolveProjectFolderSync } from './project-folder.js';
|
|
14
15
|
function editorLabel() {
|
|
15
16
|
const url = process.env.CSPEACH_EDITOR_URL?.trim();
|
|
16
17
|
return url ? url : 'cspeach.dev/edit';
|
|
@@ -22,10 +23,23 @@ function sameDir(a, b) {
|
|
|
22
23
|
};
|
|
23
24
|
return norm(a) === norm(b);
|
|
24
25
|
}
|
|
25
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* `<file name> (in <where>)` — names the folder kind (2026-10-08): this
|
|
28
|
+
* project's .cspeach folder (solo), your own working folder (team), or the
|
|
29
|
+
* home fallback folder with a hint. Anywhere else: the folder itself.
|
|
30
|
+
*/
|
|
26
31
|
export function savedLocation(savedPath, cwd) {
|
|
27
32
|
const dir = dirname(savedPath);
|
|
28
|
-
const
|
|
33
|
+
const pf = resolveProjectFolderSync(cwd);
|
|
34
|
+
let where = dir;
|
|
35
|
+
if (sameDir(dir, pf.workDir)) {
|
|
36
|
+
if (pf.kind === 'solo')
|
|
37
|
+
where = "this project's .cspeach folder";
|
|
38
|
+
else if (pf.kind === 'team')
|
|
39
|
+
where = 'your own working folder for this team project, not shared';
|
|
40
|
+
else
|
|
41
|
+
where = `${dir} — start CSPeach in a project folder to keep projects apart`;
|
|
42
|
+
}
|
|
29
43
|
return `${basename(savedPath)} (in ${where})`;
|
|
30
44
|
}
|
|
31
45
|
export function formatSavedLines(savedPath, cwd) {
|