@cspeach/cli 1.1.47 → 1.1.49
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/agent/level.js +1 -1
- package/dist/agent/loop.js +7 -6
- package/dist/agent/sap-connection-adapter.js +2 -0
- package/dist/agent/system-prompt.js +2 -2
- package/dist/agent/tool-dispatch.js +13 -1
- package/dist/approvals/op-labels.js +49 -10
- package/dist/approvals/render.js +14 -23
- package/dist/approvals/risk-floor.js +196 -19
- package/dist/approvals/visible-changes.js +35 -0
- package/dist/commands/config-set.js +5 -5
- package/dist/commands/cost.js +2 -1
- package/dist/commands/help.js +1 -1
- package/dist/commands/project.js +301 -0
- package/dist/commands/team-plan.js +16 -6
- package/dist/commands/team.js +295 -68
- package/dist/config/loader.js +13 -0
- package/dist/cost/cost-log.js +7 -4
- package/dist/cost/credits-wire.js +11 -6
- package/dist/cost/credits.js +15 -0
- package/dist/cost/display-mode.js +16 -4
- package/dist/doctor/checks/zcspeach.js +96 -0
- package/dist/journey/chain.js +489 -61
- package/dist/journey/clean-core.js +61 -36
- package/dist/journey/flow.js +75 -2
- package/dist/journey/people.js +37 -0
- package/dist/journey/rebuild.js +7 -6
- package/dist/journey/run.js +129 -42
- package/dist/journey/trigger.js +16 -10
- package/dist/one-shot.js +1 -1
- package/dist/project-context/render.js +7 -0
- package/dist/projects/clean-core-confirm.js +166 -44
- package/dist/projects/email-template.js +56 -0
- package/dist/projects/extract-cca.js +8 -7
- package/dist/projects/index.js +0 -1
- package/dist/projects/merge-cca.js +0 -19
- package/dist/projects/merge-upgrade.js +2 -2
- package/dist/projects/project-folder.js +2 -1
- package/dist/projects/save-command.js +51 -14
- package/dist/register/decision-rules.js +2 -1
- package/dist/register/export-files.js +18 -0
- package/dist/register/picture.js +3 -3
- package/dist/register/project-transport.js +125 -0
- package/dist/register/records.js +25 -13
- package/dist/register/rules-edit.js +5 -0
- package/dist/register/status-file/progress-chart.js +17 -6
- package/dist/register/status-file/register-map.js +8 -5
- package/dist/register/status-file/register-model.js +55 -36
- package/dist/register/status-file/register-parts.js +97 -82
- package/dist/register/status-file/register-status-file.js +16 -7
- package/dist/register/status-file/report-model.js +293 -205
- package/dist/register/status-file/team-css.js +1 -1
- package/dist/register/status-html.js +11 -4
- package/dist/register/store.js +2 -2
- package/dist/register/workbooks.js +24 -13
- package/dist/renderer/footer-line.js +10 -2
- package/dist/renderer/status-footer.js +2 -1
- package/dist/renderer/tool-labels.js +2 -2
- package/dist/repl/builtin-commands.js +1 -1
- package/dist/repl/continue-skill.js +5 -0
- package/dist/repl/level-command.js +6 -15
- package/dist/repl.js +21 -8
- package/dist/router/router-core.js +5 -3
- package/dist/session/audit-export.js +4 -1
- package/dist/skill-catalog.js +4 -7
- package/dist/tools/_team-work-path.js +53 -0
- package/dist/tools/approval.js +97 -19
- package/dist/tools/filesystem/file-edit.js +8 -1
- package/dist/tools/filesystem/file-write.js +8 -1
- package/dist/tools/local-files.js +31 -0
- package/dist/tools/project/_merge-shared.js +2 -2
- package/dist/tools/project/_team-tool-shared.js +5 -0
- package/dist/tools/project/cca_merge.js +1 -11
- package/dist/tools/project/rules_propose.js +2 -1
- package/dist/tools/project/team_findings.js +81 -28
- package/dist/tools/project/team_lane_move.js +127 -0
- package/dist/tools/project/upgrade_merge_progress.js +8 -4
- package/dist/tools/subagent/schedule_create.js +131 -0
- package/dist/tools/text-elements/tools.js +7 -1
- package/dist/tools/tool-loading.js +1 -1
- package/dist/tools/update-method-intercept.js +18 -1
- package/dist/tools/write-pipeline.js +19 -5
- package/dist/ui/approval-modal.js +6 -3
- package/dist/ui/error-emitter.js +21 -0
- package/dist/ui/footer-line.js +19 -1
- package/dist/ui/header.js +41 -0
- package/dist/ui/ink-classifier-route.js +19 -0
- package/dist/ui/rich-error-box.js +9 -0
- package/dist/ui/sidebar.js +12 -0
- package/dist/ui/status-emitter.js +19 -0
- package/dist/ui/status-line.js +62 -0
- package/dist/ui/status-row.js +38 -0
- package/dist/ui/todo-panel.js +85 -0
- package/dist/ui/widgets/ask-form.js +250 -0
- package/dist/ui/widgets/ask-question-modal.js +178 -0
- package/fiori/zlas-inspection-lr/package.json +26 -0
- package/fiori/zlas-inspection-lr/ui5-local.yaml +45 -0
- package/fiori/zlas-inspection-lr/ui5.yaml +41 -0
- package/fiori/zlas-inspection-lr/webapp/Component.js +12 -0
- package/fiori/zlas-inspection-lr/webapp/i18n/i18n.properties +9 -0
- package/fiori/zlas-inspection-lr/webapp/index.html +35 -0
- package/fiori/zlas-inspection-lr/webapp/manifest.json +145 -0
- package/live-check-2026-10-17-1343.txt +41 -0
- package/package.json +121 -121
- package/test-harness/fe-draft/app.ts +0 -188
- package/test-harness/fe-draft/fe-draft.e2e.test.ts +0 -461
- package/test-harness/fe-draft/manifest-enhancer.unit.test.ts +0 -51
- package/test-harness/fe-draft/routes.ts +0 -224
- package/test-harness/fe-draft/routes.unit.test.ts +0 -45
- package/test-harness/fe-draft/server.ts +0 -225
- package/test-harness/fe-draft/variants.ts +0 -196
package/dist/journey/chain.js
CHANGED
|
@@ -13,27 +13,41 @@
|
|
|
13
13
|
* Every queued command is built by code (run.ts triageCommand / refactorCommand).
|
|
14
14
|
* Headless runs nothing and queues nothing: runJourneyStage says the next step.
|
|
15
15
|
*/
|
|
16
|
-
import { estateSummaryOf } from '@cspeach/register-core';
|
|
17
|
-
import { DEFAULT_ATC_VARIANT, FIRST_CHECK_PER_PACKAGE_LINE, FIRST_CHECK_WHOLE_MAX, runTeamCommand, whoAmI } from '../commands/team.js';
|
|
18
|
-
import { activeProject, readEstate, readProjectFile, VARIANT_NAME } from '../register/store.js';
|
|
16
|
+
import { estateSummaryOf, viewWords } from '@cspeach/register-core';
|
|
17
|
+
import { DEFAULT_ATC_VARIANT, FIRST_CHECK_PER_PACKAGE_LINE, FIRST_CHECK_WHOLE_MAX, isLead, ruleObject, runTeamCommand, whoAmI } from '../commands/team.js';
|
|
18
|
+
import { activeProject, readBaseline, readEstate, readProjectFile, VARIANT_NAME } from '../register/store.js';
|
|
19
19
|
import { findingsByObject } from '../register/findings.js';
|
|
20
20
|
import { shown, shownUpTo } from '../register/atc-file.js';
|
|
21
21
|
import { cspeachWritesThisSession } from '../register/offer.js';
|
|
22
22
|
import { cardSlotEmitter } from '../ui/card-slot.js';
|
|
23
|
-
import {
|
|
23
|
+
import { decidePage, folderKind, journeyView, readJourneyFacts, stageOf, stepOf, STEPS, toDecide, toFix, toRedesign } from './clean-core.js';
|
|
24
24
|
import { handOffRebuild, rebuildCard, rebuildGroups } from './rebuild.js';
|
|
25
|
-
import { addJourneyLeftOut, addJourneySkips, clearJourneyFlow, forgetNotNow, getJourneyFlow, journeyHopLine, journeySkips, journeyStepKey, noteJourneyStop, pauseJourneyFlow, setJourneyAt, setJourneyPage, setJourneyQueued, startJourneyFlow, } from './flow.js';
|
|
25
|
+
import { addJourneyLeftOut, addJourneySkips, clearJourneyFlow, forgetDoneFingerprint, forgetNotNow, forgetSkippedRows, getJourneyFlow, journeyHopLine, journeySkips, journeyStepKey, folderReadThisSession, markFolderRead, markJourneyEnded, markSkippedAsked, noteJourneyStop, pauseJourneyFlow, setJourneyAt, setJourneyDevelopers, setJourneyPage, setJourneyQueued, startJourneyFlow, } from './flow.js';
|
|
26
|
+
import { parsePeople } from './people.js';
|
|
26
27
|
import { recordFullNotice } from '../renderer/notice-log.js';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
28
|
+
import { RISK_NOTE } from '../approvals/risk-floor.js';
|
|
29
|
+
import { firstCheckPerPackage, needsFirstCheck, notInViewLine, notRecordedLine, passedOverLine, refactorCommand, runJourneyStage, shownReason } from './run.js';
|
|
30
|
+
import { cleanKeepReason, FACT_ROWS, KEEP_QUESTION, RETIRE_QUESTION, retireNeedsLine } from '../projects/clean-core-confirm.js';
|
|
29
31
|
import { sessionTeamDeps } from './session-deps.js';
|
|
30
32
|
import { lookupVariant, variantLookupLine, variantWhat, VARIANT_TITLE } from '../register/variant.js';
|
|
33
|
+
import { isModifiable } from '@cspeach/sap-client';
|
|
34
|
+
import { notARequestLine, TRANSPORT_REQUEST } from '../register/project-transport.js';
|
|
31
35
|
/* ── words ──────────────────────────────────────────────────────────────── */
|
|
32
36
|
export const PAUSED_LINE = (step) => step ? `Paused at step ${step}. Say carry on to continue.` : 'Paused. Say carry on to continue.';
|
|
33
37
|
export const LOOK_TITLE = 'Look at the system?';
|
|
34
38
|
export const START_TITLE = 'Start the project here?';
|
|
35
39
|
export const NOTHING_READ_LINE = 'Nothing was read from the system.';
|
|
36
40
|
export const NOT_STARTED_LINE = 'No project was started.';
|
|
41
|
+
/** B1: asked at every project start, before "Start the project here?". */
|
|
42
|
+
export const PEOPLE_TITLE = 'Anyone else working on this?';
|
|
43
|
+
export const PEOPLE_QUESTION = 'Who else? Full names, comma separated — for example: James Miller, Emily Parker';
|
|
44
|
+
/** R8: said before a read that can take a while (a project folder read cold, or a big one). */
|
|
45
|
+
export const READING_LINE = 'Reading the folder…';
|
|
46
|
+
/** R8: a baseline above this many objects is read slowly enough to say so on every run. */
|
|
47
|
+
const READING_BIG = 200;
|
|
48
|
+
export const JUST_YOU_LINE = 'Carrying on with just you.';
|
|
49
|
+
/** One person's line on the people cards and the start card. */
|
|
50
|
+
const shareFact = (name) => `${shown(name)}: a share of the work, set at the start.`;
|
|
37
51
|
/** I15: the start above 500 objects measured nothing; this card offers the first check per package. */
|
|
38
52
|
export const FIRST_CHECK_TITLE = 'Run the first check per package now?';
|
|
39
53
|
/**
|
|
@@ -158,9 +172,14 @@ function queueStep(h, command) {
|
|
|
158
172
|
const line = journeyHopLine(h.session, command);
|
|
159
173
|
h.queueNext(command, { by: 'journey', ...(line ? { line } : {}) });
|
|
160
174
|
}
|
|
161
|
-
/**
|
|
162
|
-
|
|
175
|
+
/**
|
|
176
|
+
* End the chain: optional last line, the guarded run off. `done`: it ended at
|
|
177
|
+
* DONE, whose line says "say carry on" — so bare "carry on" picks it up (B8).
|
|
178
|
+
*/
|
|
179
|
+
function end(h, line, done = false) {
|
|
163
180
|
clearJourneyFlow(h.session);
|
|
181
|
+
if (done)
|
|
182
|
+
markJourneyEnded(h.session);
|
|
164
183
|
if (line)
|
|
165
184
|
h.emit(line);
|
|
166
185
|
}
|
|
@@ -227,7 +246,13 @@ export async function enterJourney(trigger, h) {
|
|
|
227
246
|
return;
|
|
228
247
|
}
|
|
229
248
|
startJourneyFlow(h.session, trigger.kind === 'start' ? trigger.packages : []);
|
|
249
|
+
// B8: a start sentence is a new run: its DONE never reads as "nothing left" against an earlier one.
|
|
250
|
+
if (trigger.kind === 'start')
|
|
251
|
+
forgetDoneFingerprint(h.session);
|
|
230
252
|
forgetNotNow(h.session);
|
|
253
|
+
// G3: "carry on" asks the rows skipped again (a start sentence keeps them put off).
|
|
254
|
+
if (trigger.kind === 'resume')
|
|
255
|
+
forgetSkippedRows(h.session);
|
|
231
256
|
try {
|
|
232
257
|
await drive(h);
|
|
233
258
|
}
|
|
@@ -269,36 +294,38 @@ function startScope(cwd, packages) {
|
|
|
269
294
|
/** The card shows this much of a recorded reason, wrapped over up to FACT_ROWS rows. */
|
|
270
295
|
export const CARD_REASON = 300;
|
|
271
296
|
const LEVEL = /^[A-D]$/;
|
|
297
|
+
/** The view's words with a capital first letter, for a fact line. */
|
|
298
|
+
const sentence = (words) => words.charAt(0).toUpperCase() + words.slice(1);
|
|
272
299
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
*
|
|
281
|
-
* findings, a failed check, why (the decision's reason as written, cleaned
|
|
282
|
-
* for the screen, at most 300 characters, wrapped on the card), the target level when one was set
|
|
283
|
-
* on purpose, the approval promise, and where this object stands in the list.
|
|
300
|
+
* The facts of the fix card, from the project view only (no SAP call): the
|
|
301
|
+
* open findings, a failed check, the decision's reason in the view's card
|
|
302
|
+
* words (`viewWords.cardReason`: "Proposed reason:", "Why:" or "Reason
|
|
303
|
+
* recorded:"; cleaned for the screen, at most 300 characters, wrapped on the
|
|
304
|
+
* card), the target only when a person or the project set one
|
|
305
|
+
* (`viewWords.target`, never a level the model wrote), the approval promise,
|
|
306
|
+
* and where this object stands in the list ("12 objects marked fix; this is
|
|
307
|
+
* the first.", or "1 object marked fix.").
|
|
284
308
|
*/
|
|
285
309
|
export function fixFacts(o, at, of, open = []) {
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
310
|
+
const r = o.decision.reason;
|
|
311
|
+
const reason = viewWords.cardReason(r ? { ...r, text: shownReason(r.text, CARD_REASON) } : null);
|
|
312
|
+
const t = o.target;
|
|
313
|
+
const now = o.level.now ?? o.level.decided;
|
|
314
|
+
const target = t.from !== 'none' && t.level
|
|
315
|
+
? [`${sentence(viewWords.target({ ...t, by: t.by ? { ...t.by, name: shown(t.by.name) } : null }))}${now && now !== t.level ? `, now ${now}` : ''}.`]
|
|
316
|
+
: [];
|
|
293
317
|
return [
|
|
294
|
-
o.
|
|
295
|
-
...(
|
|
296
|
-
...(reason ? [
|
|
297
|
-
...
|
|
318
|
+
o.proof.open === null ? 'Not checked yet.' : `${plural(o.proof.open, 'open finding', 'open findings')}.`,
|
|
319
|
+
...(o.part === 'failed-check' ? ['Its last fix failed the check.', ...open] : []),
|
|
320
|
+
...(reason ? [reason] : []),
|
|
321
|
+
...target,
|
|
298
322
|
'CSPeach asks before it writes to SAP.',
|
|
299
|
-
|
|
323
|
+
of === 1 ? '1 object marked fix.' : `${plural(of, 'object marked fix', 'objects marked fix')}; this is ${whichOne(at)}.`,
|
|
300
324
|
];
|
|
301
325
|
}
|
|
326
|
+
const ORDINALS = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth'];
|
|
327
|
+
/** Live re-run L4: "the first" … "the tenth", then "number 11" ("1 of 12" read as "one of twelve"). */
|
|
328
|
+
const whichOne = (at) => (ORDINALS[at - 1] ? `the ${ORDINALS[at - 1]}` : `number ${at.toLocaleString('en-US')}`);
|
|
302
329
|
/** "Still open:" shows at most this many lines on a card. */
|
|
303
330
|
const STILL_OPEN_ROWS = 3;
|
|
304
331
|
/** A fact wraps onto at most this many rows on a card that also lists what is still open. */
|
|
@@ -340,20 +367,28 @@ export function stillOpenLines(key) {
|
|
|
340
367
|
* only the chosen object first. Objects whose names are not ABAP names never
|
|
341
368
|
* reach this card: the chain passes them over first.
|
|
342
369
|
*/
|
|
343
|
-
export async function fixCard(confirm, objects, skips, openOf = () => []) {
|
|
344
|
-
|
|
370
|
+
export async function fixCard(confirm, objects, skips, view, passOver, openOf = () => []) {
|
|
371
|
+
// Fix round 1: an object the card cannot show is passed over with its line, never dropped in silence.
|
|
372
|
+
const list = objects.filter((o) => {
|
|
373
|
+
const line = fixPassOverLine(o, view);
|
|
374
|
+
if (line)
|
|
375
|
+
passOver(o, line);
|
|
376
|
+
return !line;
|
|
377
|
+
});
|
|
345
378
|
if (!list.length)
|
|
346
379
|
return { kind: 'none' };
|
|
347
380
|
const many = list.length > 1;
|
|
348
381
|
for (let i = 0; i < list.length * 20; i++) {
|
|
349
382
|
const o = list[i % list.length];
|
|
350
383
|
const name = o.name.toUpperCase();
|
|
351
|
-
const
|
|
384
|
+
const v = view.object(o.key);
|
|
385
|
+
const failed = v.part === 'failed-check';
|
|
386
|
+
const open = failed ? openOf(o) : [];
|
|
352
387
|
const res = await confirm({
|
|
353
388
|
id: 'journey-fix',
|
|
354
389
|
tag: 'QUESTION',
|
|
355
|
-
title:
|
|
356
|
-
facts: fixFacts(
|
|
390
|
+
title: failed ? `${name}'s fix failed the check — try again?` : `Fix ${name} now?`,
|
|
391
|
+
facts: fixFacts(v, (i % list.length) + 1, list.length, open),
|
|
357
392
|
choices: [{ value: 'yes', label: 'Yes' }, ...(many ? [{ value: 'another', label: 'Pick another' }] : []), { value: 'later', label: 'Later' }],
|
|
358
393
|
initial: 0,
|
|
359
394
|
aliases: { y: 'yes', ...(many ? { p: 'another' } : {}), l: 'later' },
|
|
@@ -372,9 +407,15 @@ export async function fixCard(confirm, objects, skips, openOf = () => []) {
|
|
|
372
407
|
}
|
|
373
408
|
return { kind: 'cancel' };
|
|
374
409
|
}
|
|
375
|
-
/**
|
|
410
|
+
/** Why the fix card cannot show an object: a name that is not an ABAP name, or an object the project view does not hold. Null: it can. */
|
|
411
|
+
export function fixPassOverLine(o, view) {
|
|
412
|
+
if (refactorCommand({ type: o.type, name: o.name }) === null)
|
|
413
|
+
return passedOverLine(o.name);
|
|
414
|
+
return view.object(o.key) ? null : notInViewLine(o.name);
|
|
415
|
+
}
|
|
416
|
+
/** The object's target for the fix turn's prompt, from the project view: only when a person or the project set one. */
|
|
376
417
|
function targetFor(o) {
|
|
377
|
-
const level = o
|
|
418
|
+
const level = o && o.target.from !== 'none' ? o.target.level : null;
|
|
378
419
|
return level && LEVEL.test(level) ? { target: level } : {};
|
|
379
420
|
}
|
|
380
421
|
export const VARIANT_QUESTION = 'Which check variant? Type its name.';
|
|
@@ -442,6 +483,166 @@ async function variantCard(h, team, confirm) {
|
|
|
442
483
|
h.emit(VARIANT_TRIES_LINE);
|
|
443
484
|
return null;
|
|
444
485
|
}
|
|
486
|
+
/** Task 8 (B11): asked at the start, after the check variant. */
|
|
487
|
+
export const TRANSPORT_TITLE = 'Which transport request will hold the fixes?';
|
|
488
|
+
export const TRANSPORT_QUESTION = 'Which transport request? Type its number.';
|
|
489
|
+
/** Said when five answers gave no request that can take the fixes; the project starts without one. */
|
|
490
|
+
export const TRANSPORT_TRIES_LINE = 'Too many tries to choose a transport request. The project starts without one.';
|
|
491
|
+
/** The longest request description the card shows. */
|
|
492
|
+
const TRANSPORT_DESCRIPTION_MAX = 60;
|
|
493
|
+
/** The session can read a transport request from SAP (the card needs it). Standalone and no-SAP sessions cannot. */
|
|
494
|
+
function readsTransports(h, team) {
|
|
495
|
+
return !h.noSap && !team.deps.noSap && typeof h.adt?.transportRead === 'function';
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Task 8 (B11), step 2, after the check variant: "Which transport request
|
|
499
|
+
* will hold the fixes?" [Type it] [None yet]. A typed number is checked for
|
|
500
|
+
* shape, read from SAP (as the release card reads it: request_approval
|
|
501
|
+
* attachReleaseFacts) and shown back — "Record fixes in <REQ> "<text>"?"
|
|
502
|
+
* [Yes] [Use another] [None yet] with its owner and status. A task, a
|
|
503
|
+
* request SAP cannot read, or one that can no longer take changes is said in
|
|
504
|
+
* one line and the question comes again; five answers, then none yet.
|
|
505
|
+
* Returns the request, '' for none yet, or null when a card or the question
|
|
506
|
+
* was put away (no project, as on every start card).
|
|
507
|
+
*/
|
|
508
|
+
async function transportCard(h, confirm) {
|
|
509
|
+
const first = await confirm({
|
|
510
|
+
id: 'journey-transport',
|
|
511
|
+
tag: 'QUESTION',
|
|
512
|
+
title: TRANSPORT_TITLE,
|
|
513
|
+
choices: [{ value: 'type', label: 'Type it' }, { value: 'none', label: 'None yet' }],
|
|
514
|
+
initial: 0,
|
|
515
|
+
aliases: { t: 'type', n: 'none' },
|
|
516
|
+
footnote: footnote('START'),
|
|
517
|
+
});
|
|
518
|
+
if (first.cancelled || first.value === null)
|
|
519
|
+
return null;
|
|
520
|
+
if (first.value !== 'type')
|
|
521
|
+
return '';
|
|
522
|
+
const ask = h.ask ?? journeyAsk();
|
|
523
|
+
for (let tries = 0; tries < 5; tries++) {
|
|
524
|
+
let said;
|
|
525
|
+
try {
|
|
526
|
+
said = await ask(TRANSPORT_QUESTION);
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const asked = said.trim().toUpperCase();
|
|
532
|
+
if (!TRANSPORT_REQUEST.test(asked)) {
|
|
533
|
+
h.emit(notARequestLine(shownUpTo(said.trim(), 60)));
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
let doc;
|
|
537
|
+
try {
|
|
538
|
+
doc = await h.adt.transportRead(asked);
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
h.emit(`CSPeach could not read ${asked}.`);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
const number = shown(String(doc.number ?? '')).trim().toUpperCase();
|
|
545
|
+
if (doc.kind === 'task') {
|
|
546
|
+
// A task document without its request's number says so without one.
|
|
547
|
+
h.emit(TRANSPORT_REQUEST.test(number) && number !== asked
|
|
548
|
+
? `${asked} is a task of ${number}. Type the request number.`
|
|
549
|
+
: `${asked} is a task, not a request. Type the request number.`);
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const status = shown(String(doc.statusText || doc.status || '')).trim() || 'of unknown status';
|
|
553
|
+
if (!isModifiable(doc.status)) {
|
|
554
|
+
h.emit(`${asked} is ${status}; it cannot take changes.`);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const request = TRANSPORT_REQUEST.test(number) ? number : asked;
|
|
558
|
+
const description = shownUpTo(String(doc.description ?? '').trim(), TRANSPORT_DESCRIPTION_MAX);
|
|
559
|
+
const owner = shown(String(doc.owner ?? '')).trim();
|
|
560
|
+
const res = await confirm({
|
|
561
|
+
id: 'journey-transport-confirm',
|
|
562
|
+
tag: 'QUESTION',
|
|
563
|
+
title: `Record fixes in ${request}${description ? ` "${description}"` : ''}?`,
|
|
564
|
+
facts: [`${owner ? `Owner: ${owner}. ` : ''}Status: ${status}.`],
|
|
565
|
+
choices: [{ value: 'yes', label: 'Yes' }, { value: 'another', label: 'Use another' }, { value: 'none', label: 'None yet' }],
|
|
566
|
+
initial: 0,
|
|
567
|
+
aliases: { y: 'yes', u: 'another', n: 'none' },
|
|
568
|
+
footnote: footnote('START'),
|
|
569
|
+
});
|
|
570
|
+
if (res.cancelled || res.value === null)
|
|
571
|
+
return null;
|
|
572
|
+
if (res.value === 'yes')
|
|
573
|
+
return request;
|
|
574
|
+
if (res.value === 'none')
|
|
575
|
+
return '';
|
|
576
|
+
}
|
|
577
|
+
h.emit(TRANSPORT_TRIES_LINE);
|
|
578
|
+
return '';
|
|
579
|
+
}
|
|
580
|
+
/** "James Miller", "James Miller and Emily Parker", "A, B and C". */
|
|
581
|
+
const andList = (names) => names.length < 2 ? names.join('') : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
582
|
+
/**
|
|
583
|
+
* B1, step 2 — "Anyone else working on this?" [Just me] [Add people], before
|
|
584
|
+
* "Start the project here?". Add people asks for full names (free text,
|
|
585
|
+
* people.ts parsePeople: three tries, then just you) and shows them back —
|
|
586
|
+
* "Add James Miller and Emily Parker?" [Yes] [Retype] [Just me]. Returns the
|
|
587
|
+
* names (none: Just me), or null when a card or the question was put away.
|
|
588
|
+
*/
|
|
589
|
+
async function peopleCard(h, confirm, me, packages) {
|
|
590
|
+
const res = await confirm({
|
|
591
|
+
id: 'journey-people',
|
|
592
|
+
tag: 'QUESTION',
|
|
593
|
+
title: PEOPLE_TITLE,
|
|
594
|
+
facts: ['Each person gets a share of the packages, balanced by the work.'],
|
|
595
|
+
choices: [{ value: 'me', label: 'Just me' }, { value: 'add', label: 'Add people' }],
|
|
596
|
+
initial: 0,
|
|
597
|
+
aliases: { j: 'me', a: 'add' },
|
|
598
|
+
footnote: footnote('START'),
|
|
599
|
+
});
|
|
600
|
+
if (res.cancelled || res.value === null)
|
|
601
|
+
return null;
|
|
602
|
+
if (res.value !== 'add')
|
|
603
|
+
return [];
|
|
604
|
+
const ask = h.ask ?? journeyAsk();
|
|
605
|
+
for (let round = 0; round < 5; round++) {
|
|
606
|
+
let names = null;
|
|
607
|
+
for (let tries = 0; tries < 3 && !names; tries++) {
|
|
608
|
+
let said;
|
|
609
|
+
try {
|
|
610
|
+
said = await ask(PEOPLE_QUESTION);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
const parsed = parsePeople(said, packages);
|
|
616
|
+
// The lead is "me" already: typed again under their own name, it is not someone else.
|
|
617
|
+
if (parsed.ok)
|
|
618
|
+
names = parsed.names.filter((x) => x.toLowerCase() !== me.trim().toLowerCase());
|
|
619
|
+
else
|
|
620
|
+
h.emit(parsed.line);
|
|
621
|
+
}
|
|
622
|
+
if (!names?.length) {
|
|
623
|
+
h.emit(JUST_YOU_LINE);
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
const back = await confirm({
|
|
627
|
+
id: 'journey-people-names',
|
|
628
|
+
tag: 'QUESTION',
|
|
629
|
+
title: `Add ${andList(names.map((x) => shown(x)))}?`,
|
|
630
|
+
facts: [`You: ${shown(me)} (lead).`, ...names.map(shareFact)],
|
|
631
|
+
choices: [{ value: 'yes', label: 'Yes' }, { value: 'retype', label: 'Retype' }, { value: 'me', label: 'Just me' }],
|
|
632
|
+
initial: 0,
|
|
633
|
+
aliases: { y: 'yes', r: 'retype', j: 'me' },
|
|
634
|
+
footnote: footnote('START'),
|
|
635
|
+
});
|
|
636
|
+
if (back.cancelled || back.value === null)
|
|
637
|
+
return null;
|
|
638
|
+
if (back.value === 'yes')
|
|
639
|
+
return names;
|
|
640
|
+
if (back.value === 'me')
|
|
641
|
+
return [];
|
|
642
|
+
}
|
|
643
|
+
h.emit(JUST_YOU_LINE);
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
445
646
|
/**
|
|
446
647
|
* I15: "Run the first check per package now? It resumes if stopped." Yes runs
|
|
447
648
|
* `team refresh --all --per-package`; Later (or the card put away) ends the
|
|
@@ -472,8 +673,14 @@ async function firstCheckCard(h, team, skips, packages) {
|
|
|
472
673
|
return true;
|
|
473
674
|
}
|
|
474
675
|
/* ── the chain ──────────────────────────────────────────────────────────── */
|
|
676
|
+
/** G3: how many of my objects the session skipped and are still undecided (the DONE count's "skipped this time": the view's part). */
|
|
677
|
+
function skippedRows(view, me, skips) {
|
|
678
|
+
if (!skips.decide?.size)
|
|
679
|
+
return 0;
|
|
680
|
+
return view.me(me).parts.skipped;
|
|
681
|
+
}
|
|
475
682
|
/** Where the chain is: the stage, and a print of what the session put off (no progress → stop). */
|
|
476
|
-
const fingerprint = (stage, s) => `${stage}|${s.decide?.size ?? 0}|${s.fix?.size ?? 0}|${s.prove ? 1 : 0}|${s.report ? 1 : 0}|${s.rebuild?.size ?? 0}`;
|
|
683
|
+
const fingerprint = (stage, s) => `${stage}|${s.decide?.size ?? 0}|${s.fix?.size ?? 0}|${s.prove ? 1 : 0}|${s.report ? 1 : 0}|${s.rebuild?.size ?? 0}|${s.retire?.size ?? 0}`;
|
|
477
684
|
/** The newest check's rule ids per object (the kind proposal's ALV fact, F6); none when they cannot be read. */
|
|
478
685
|
function rulesReader() {
|
|
479
686
|
let byKey = null;
|
|
@@ -487,6 +694,135 @@ function rulesReader() {
|
|
|
487
694
|
}
|
|
488
695
|
return (key) => byKey?.get(key)?.latest.map((r) => r.rule) ?? [];
|
|
489
696
|
}
|
|
697
|
+
/** This seat is the project's lead (the one test, commands/team.ts). Unreadable → not the lead. */
|
|
698
|
+
function leadHere(team) {
|
|
699
|
+
try {
|
|
700
|
+
const joined = activeProject();
|
|
701
|
+
return !!joined && isLead(readProjectFile(joined.projectDir), team.deps);
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/** Where-used, in the words CSPeach prints for it (the words a retire reason quotes). */
|
|
708
|
+
function callersLine(c) {
|
|
709
|
+
if (!c)
|
|
710
|
+
return 'Where-used could not be read: no inventory for this object.';
|
|
711
|
+
if (!c.read)
|
|
712
|
+
return `Where-used could not be read: ${shownUpTo(c.why, 120).replace(/[\s.]+$/, '')}.`;
|
|
713
|
+
return c.objects === 0 ? 'No callers found (where-used read OK).' : `Callers found: ${plural(c.objects, 'object', 'objects')} (where-used read OK).`;
|
|
714
|
+
}
|
|
715
|
+
/** Usage with its source and period, as team_findings gives it; "unknown" says why. */
|
|
716
|
+
function usageLine(u) {
|
|
717
|
+
if (!u)
|
|
718
|
+
return 'Usage: unknown.';
|
|
719
|
+
const source = u.source ? `, from ${shownUpTo(u.source, 60)}` : '';
|
|
720
|
+
const period = u.period ? ` (${shownUpTo(u.period.from, 30)} to ${shownUpTo(u.period.to, 30)})` : '';
|
|
721
|
+
const last = u.lastUsed ? `, last used ${shownUpTo(u.lastUsed, 30)}` : '';
|
|
722
|
+
const why = !u.source && u.why ? ` — ${shownUpTo(u.why, 160).replace(/[\s.]+$/, '')}` : '';
|
|
723
|
+
return `Usage: ${u.state}${source}${period}${last}${why}.`;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* The facts of the lead's retire card: who proposed it and why (the project
|
|
727
|
+
* view's decision: a person's retire candidate), the evidence, where it stands.
|
|
728
|
+
* When the decision that stands is another one (a conflict), the person whose
|
|
729
|
+
* retire still stands is named, with no words that are not theirs.
|
|
730
|
+
*/
|
|
731
|
+
export function retireFacts(o, v, at, of) {
|
|
732
|
+
const d = v?.decision;
|
|
733
|
+
const reason = shownReason(d?.reason?.text, CARD_REASON).replace(/[\s.]+$/, '');
|
|
734
|
+
const proposer = d && d.from === 'person' && d.what === 'retire-candidate' && d.by ? shown(d.by.name).trim() : '';
|
|
735
|
+
const standing = proposer ? '' : shown(d?.standing?.find((x) => x.what === 'retire-candidate')?.name ?? '').trim();
|
|
736
|
+
return [
|
|
737
|
+
...(proposer ? [`Proposed by ${proposer}${reason ? `: ${reason}` : ''}.`] : standing ? [`Proposed by ${standing}.`] : []),
|
|
738
|
+
callersLine(o.evidence?.callers),
|
|
739
|
+
usageLine(o.evidence?.usage),
|
|
740
|
+
`Row ${at} of ${of}.`,
|
|
741
|
+
];
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Task 6 (B4): "Retire <NAME>?" [Yes] [Not yet], one card per candidate. Yes
|
|
745
|
+
* asks "What did you check?" and writes the lead's ruling with those words
|
|
746
|
+
* (`team rule <T> <N> retire --reason "…"`, run in process); nothing typed
|
|
747
|
+
* says so and shows the card again. Not yet puts it off for the session.
|
|
748
|
+
*/
|
|
749
|
+
async function retireCards(h, team, list, at, view) {
|
|
750
|
+
const ask = h.ask ?? journeyAsk();
|
|
751
|
+
// Round 1: the step it runs in (the report is step 6); DONE has no step, so no footnote.
|
|
752
|
+
const foot = stepOf(at) ? { footnote: footnote(at) } : {};
|
|
753
|
+
for (let i = 0; i < list.length; i++) {
|
|
754
|
+
const o = list[i];
|
|
755
|
+
const type = shown(o.type.toUpperCase());
|
|
756
|
+
const name = shown(o.name.toUpperCase());
|
|
757
|
+
const later = () => addJourneySkips(h.session, { retire: new Set([o.key]) });
|
|
758
|
+
let settled = false;
|
|
759
|
+
for (let tries = 0; tries < 5 && !settled; tries++) {
|
|
760
|
+
const res = await team.confirm({
|
|
761
|
+
id: 'journey-retire',
|
|
762
|
+
tag: 'QUESTION',
|
|
763
|
+
title: `Retire ${name}?`,
|
|
764
|
+
facts: retireFacts(o, view?.object(o.key), i + 1, list.length),
|
|
765
|
+
choices: [{ value: 'yes', label: 'Yes' }, { value: 'later', label: 'Not yet' }],
|
|
766
|
+
initial: 0,
|
|
767
|
+
aliases: { y: 'yes', n: 'later' },
|
|
768
|
+
...foot,
|
|
769
|
+
wrapFacts: FACT_ROWS,
|
|
770
|
+
});
|
|
771
|
+
if (res.cancelled || res.value === null)
|
|
772
|
+
return 'cancel';
|
|
773
|
+
if (res.value === 'later') {
|
|
774
|
+
later();
|
|
775
|
+
settled = true;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
let said;
|
|
779
|
+
try {
|
|
780
|
+
said = await ask(RETIRE_QUESTION);
|
|
781
|
+
}
|
|
782
|
+
catch {
|
|
783
|
+
return 'cancel';
|
|
784
|
+
}
|
|
785
|
+
const reason = cleanKeepReason(said);
|
|
786
|
+
if (!reason) {
|
|
787
|
+
h.emit(retireNeedsLine(type, name));
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
team.said.length = 0;
|
|
791
|
+
try {
|
|
792
|
+
ruleObject({ type: o.type, name: o.name, classification: 'retire', reason }, team.deps);
|
|
793
|
+
h.emit(`✓ Recorded: ${type} ${name} — retired, reason recorded.`);
|
|
794
|
+
}
|
|
795
|
+
catch (e) {
|
|
796
|
+
h.emit(notRecordedLine(e));
|
|
797
|
+
later();
|
|
798
|
+
}
|
|
799
|
+
settled = true;
|
|
800
|
+
}
|
|
801
|
+
if (!settled) {
|
|
802
|
+
later();
|
|
803
|
+
h.emit(`${name}: left for later.`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return 'done';
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* R8: a project folder this laptop records into, read for the first time in
|
|
810
|
+
* the session or holding a baseline of more than 200 objects. Any other folder
|
|
811
|
+
* (no project, or someone else's) reads no register, so says nothing. Headless
|
|
812
|
+
* prints results only, as before.
|
|
813
|
+
*/
|
|
814
|
+
function slowRead(h, me) {
|
|
815
|
+
if (h.headless || folderKind(h.cwd, me).kind !== 'joined')
|
|
816
|
+
return false;
|
|
817
|
+
if (!folderReadThisSession(h.session, h.cwd))
|
|
818
|
+
return true;
|
|
819
|
+
try {
|
|
820
|
+
return readBaseline(h.cwd).length > READING_BIG;
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
490
826
|
async function drive(h) {
|
|
491
827
|
const flow = getJourneyFlow(h.session);
|
|
492
828
|
if (!flow)
|
|
@@ -497,10 +833,19 @@ async function drive(h) {
|
|
|
497
833
|
let ranInventory = false;
|
|
498
834
|
let saidInventory = false;
|
|
499
835
|
let previous = '';
|
|
836
|
+
// G3: Yes on the skipped rows goes straight to the triage, without "Next 40?" first.
|
|
837
|
+
let reask = false;
|
|
838
|
+
let saidReading = false;
|
|
500
839
|
for (let round = 0; round < 24; round++) {
|
|
501
840
|
const me = whoAmI(team.deps);
|
|
502
841
|
const skips = journeySkips(h.session);
|
|
842
|
+
// R8: the read below is where a cold or big project folder goes quiet. Said once a run.
|
|
843
|
+
if (!saidReading && slowRead(h, me)) {
|
|
844
|
+
h.emit(READING_LINE);
|
|
845
|
+
saidReading = true;
|
|
846
|
+
}
|
|
503
847
|
const facts = readJourneyFacts(h.cwd, me, skips);
|
|
848
|
+
markFolderRead(h.session, h.cwd);
|
|
504
849
|
const stage = stageOf(facts);
|
|
505
850
|
setJourneyAt(h.session, stage);
|
|
506
851
|
// Ctrl-C: what was measured is kept; "carry on" picks up from the folder.
|
|
@@ -518,6 +863,9 @@ async function drive(h) {
|
|
|
518
863
|
let runAs = stage;
|
|
519
864
|
let runSkips = skips;
|
|
520
865
|
let startVariant;
|
|
866
|
+
let startTransport;
|
|
867
|
+
// Task 8 (B11): the start asked for its transport (the card, or headless: the line).
|
|
868
|
+
let transportAsked = false;
|
|
521
869
|
let bigStart = false;
|
|
522
870
|
if (!h.headless) {
|
|
523
871
|
// Step 1 — "Look at the system?": no inventory yet, or one older than a week.
|
|
@@ -555,6 +903,13 @@ async function drive(h) {
|
|
|
555
903
|
if (runAs === 'START') {
|
|
556
904
|
const scope = startScope(h.cwd, flow.packages);
|
|
557
905
|
if (scope) {
|
|
906
|
+
// B1: who else works on it, asked at every start; Just me leaves the start card as it was.
|
|
907
|
+
const people = await peopleCard(h, confirm, me, scope.names);
|
|
908
|
+
if (!people) {
|
|
909
|
+
end(h, NOT_STARTED_LINE);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
setJourneyDevelopers(h.session, people.length ? [...people, 'me'] : undefined);
|
|
558
913
|
const n = scope.objects;
|
|
559
914
|
bigStart = n > FIRST_CHECK_WHOLE_MAX;
|
|
560
915
|
const yes = await yesNo(confirm, {
|
|
@@ -567,7 +922,7 @@ async function drive(h) {
|
|
|
567
922
|
'Saved in this folder.',
|
|
568
923
|
// Recording fix 5: the name every record is written under, seen before anything is written.
|
|
569
924
|
`You: ${shown(me)}.`,
|
|
570
|
-
`You own all ${n.toLocaleString('en-US')}
|
|
925
|
+
...(people.length ? people.map(shareFact) : [`You own all ${n.toLocaleString('en-US')}.`]),
|
|
571
926
|
'Writes nothing to SAP.',
|
|
572
927
|
],
|
|
573
928
|
footnote: footnote('START'),
|
|
@@ -584,6 +939,17 @@ async function drive(h) {
|
|
|
584
939
|
return;
|
|
585
940
|
}
|
|
586
941
|
startVariant = chosen;
|
|
942
|
+
// B11: which request holds the fixes — only where the session can read one from SAP.
|
|
943
|
+
if (readsTransports(h, team)) {
|
|
944
|
+
const request = await transportCard(h, confirm);
|
|
945
|
+
if (request === null) {
|
|
946
|
+
end(h, NOT_STARTED_LINE);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (request)
|
|
950
|
+
startTransport = request;
|
|
951
|
+
transportAsked = true;
|
|
952
|
+
}
|
|
587
953
|
}
|
|
588
954
|
}
|
|
589
955
|
// T7 review 1: nothing measured yet, and the scope is too big for one check — never
|
|
@@ -598,14 +964,19 @@ async function drive(h) {
|
|
|
598
964
|
// Step 4 — "Fix <NAME> now?".
|
|
599
965
|
if (runAs === 'FIX' && facts.register) {
|
|
600
966
|
// A name that is not an ABAP name never reaches a card or a command: one line, passed over.
|
|
601
|
-
const
|
|
967
|
+
const view = facts.view ?? journeyView(facts.register);
|
|
968
|
+
// A name that is not an ABAP name, or an object the view does not hold, never reaches a card: one line, passed over.
|
|
969
|
+
const bad = toFix(facts.register, me, skips, view).map((o) => ({ o, line: fixPassOverLine(o, view) })).filter((x) => x.line !== null);
|
|
602
970
|
if (bad.length) {
|
|
603
|
-
for (const
|
|
604
|
-
h.emit(
|
|
605
|
-
addJourneySkips(h.session, { fix: new Set(bad.map((
|
|
971
|
+
for (const x of bad)
|
|
972
|
+
h.emit(x.line);
|
|
973
|
+
addJourneySkips(h.session, { fix: new Set(bad.map((x) => x.o.key)) });
|
|
606
974
|
continue;
|
|
607
975
|
}
|
|
608
|
-
const pick = await fixCard(confirm, toFix(facts.register, me, skips), skips, (o) =>
|
|
976
|
+
const pick = await fixCard(confirm, toFix(facts.register, me, skips, view), skips, view, (o, line) => {
|
|
977
|
+
h.emit(line);
|
|
978
|
+
addJourneySkips(h.session, { fix: new Set([o.key]) });
|
|
979
|
+
}, (o) => stillOpenLines(o.key));
|
|
609
980
|
if (pick.kind === 'cancel') {
|
|
610
981
|
pause(h, stepOf('FIX'));
|
|
611
982
|
return;
|
|
@@ -618,10 +989,53 @@ async function drive(h) {
|
|
|
618
989
|
if (pick.kind === 'yes')
|
|
619
990
|
runSkips = pick.skips;
|
|
620
991
|
}
|
|
992
|
+
// Task 6 (B4) — at the report and at the end, the lead rules on each
|
|
993
|
+
// retire candidate: one card each. Then the stage derives again, so the
|
|
994
|
+
// report that follows shows the ruling. A developer never sees it.
|
|
995
|
+
if ((runAs === 'REPORT' || runAs === 'DONE') && facts.register && leadHere(team)) {
|
|
996
|
+
// Task 9 fix round 1: the view's candidates (active only, as every count says), never the register's flag.
|
|
997
|
+
const byKey = new Map(facts.register.objects.map((o) => [o.key, o]));
|
|
998
|
+
const view = facts.view ?? journeyView(facts.register);
|
|
999
|
+
const candidates = view.all.candidates.filter((v) => !skips.retire?.has(v.key)).map((v) => byKey.get(v.key)).filter((o) => !!o);
|
|
1000
|
+
if (candidates.length) {
|
|
1001
|
+
if ((await retireCards(h, team, candidates, runAs, view)) === 'cancel') {
|
|
1002
|
+
pause(h, stepOf(runAs));
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
previous = '';
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
// G3 — before DONE: the rows skipped this session, still undecided, asked about once.
|
|
1010
|
+
// Once a run (the flag lives on the flow): "Later" on "Next 40?" counts as asked.
|
|
1011
|
+
if (runAs === 'DONE' && facts.view && !getJourneyFlow(h.session)?.skippedAsked) {
|
|
1012
|
+
const skipped = skippedRows(facts.view, me, skips);
|
|
1013
|
+
if (skipped) {
|
|
1014
|
+
markSkippedAsked(h.session);
|
|
1015
|
+
const res = await confirm({
|
|
1016
|
+
id: 'journey-skipped',
|
|
1017
|
+
tag: 'QUESTION',
|
|
1018
|
+
title: `Look at the ${plural(skipped, 'row', 'rows')} you skipped?`,
|
|
1019
|
+
choices: [{ value: 'yes', label: 'Yes' }, { value: 'later', label: 'Later' }],
|
|
1020
|
+
initial: 0,
|
|
1021
|
+
aliases: { y: 'yes', l: 'later' },
|
|
1022
|
+
footnote: footnote('DECIDE'),
|
|
1023
|
+
});
|
|
1024
|
+
if (res.cancelled || res.value === null) {
|
|
1025
|
+
pause(h, stepOf('DECIDE'));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (res.value === 'yes') {
|
|
1029
|
+
forgetSkippedRows(h.session);
|
|
1030
|
+
reask = true;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
621
1035
|
// Step 2, Task 5 (D3, F1) — at the end, the rebuild hand-off: one card
|
|
622
1036
|
// per object or app label. Yes ends the journey, then queues the rebuild.
|
|
623
1037
|
if (runAs === 'DONE' && facts.register) {
|
|
624
|
-
const groups = rebuildGroups(toRedesign(facts.register, me), skips.rebuild, rulesReader());
|
|
1038
|
+
const groups = rebuildGroups(toRedesign(facts.register, me, facts.view), skips.rebuild, rulesReader(), (key) => facts.view?.object(key)?.target);
|
|
625
1039
|
if (groups.passed.length) {
|
|
626
1040
|
// A rebuild CSPeach cannot hand off is said once, with why, and not asked again.
|
|
627
1041
|
for (const p of groups.passed)
|
|
@@ -649,9 +1063,11 @@ async function drive(h) {
|
|
|
649
1063
|
}
|
|
650
1064
|
}
|
|
651
1065
|
// Step 3, from the second page on — "Next 40? [Yes] [Later]".
|
|
652
|
-
if (runAs === 'DECIDE' &&
|
|
653
|
-
|
|
654
|
-
|
|
1066
|
+
if (runAs === 'DECIDE' && reask)
|
|
1067
|
+
reask = false;
|
|
1068
|
+
else if (runAs === 'DECIDE' && facts.register && (getJourneyFlow(h.session)?.pages ?? 0) > 0) {
|
|
1069
|
+
const left = toDecide(facts.register, me, skips, facts.view);
|
|
1070
|
+
const next = decidePage(facts.register, me, skips, facts.view).length;
|
|
655
1071
|
const res = await confirm({
|
|
656
1072
|
id: 'journey-next-page',
|
|
657
1073
|
tag: 'QUESTION',
|
|
@@ -669,15 +1085,23 @@ async function drive(h) {
|
|
|
669
1085
|
if (res.value === 'later') {
|
|
670
1086
|
// Left out, not skipped: "carry on" brings them back to deciding.
|
|
671
1087
|
addJourneyLeftOut(h.session, left.map((o) => o.key));
|
|
1088
|
+
markSkippedAsked(h.session);
|
|
672
1089
|
h.emit('Left for later.');
|
|
673
1090
|
continue;
|
|
674
1091
|
}
|
|
675
1092
|
}
|
|
676
1093
|
}
|
|
1094
|
+
const developers = runAs === 'START' ? getJourneyFlow(h.session)?.developers : undefined;
|
|
677
1095
|
const r = await runJourneyStage(runAs, {
|
|
678
1096
|
cwd: h.cwd, team, headless: h.headless, skips: runSkips, packages: flow.packages,
|
|
679
1097
|
...(h.openFile ? { openFile: h.openFile } : {}),
|
|
680
1098
|
...(startVariant ? { variant: startVariant } : {}),
|
|
1099
|
+
...(developers ? { developers: [...developers] } : {}),
|
|
1100
|
+
...(startTransport ? { transport: startTransport } : {}),
|
|
1101
|
+
// Headless shows no card: where the card would be asked, the start says the transport as a line.
|
|
1102
|
+
...(transportAsked || (h.headless && runAs === 'START' && readsTransports(h, team)) ? { transportAsked: true } : {}),
|
|
1103
|
+
// B8: the DONE summary knows what the last DONE of this session summed up (headless: as before).
|
|
1104
|
+
...(h.headless ? {} : { session: h.session }),
|
|
681
1105
|
});
|
|
682
1106
|
if (runAs === 'INVENTORY')
|
|
683
1107
|
ranInventory = true;
|
|
@@ -705,18 +1129,18 @@ async function drive(h) {
|
|
|
705
1129
|
}
|
|
706
1130
|
const skill = /^\/(\S+)/.exec(command)[1];
|
|
707
1131
|
if (runAs === 'DECIDE') {
|
|
708
|
-
setJourneyQueued(h.session, { command, skill, stage: 'DECIDE', left: facts.register ? toDecide(facts.register, me, skips).length : 0 });
|
|
1132
|
+
setJourneyQueued(h.session, { command, skill, stage: 'DECIDE', left: facts.register ? toDecide(facts.register, me, skips, facts.view).length : 0 });
|
|
709
1133
|
// The page the triage is shown (team_findings reads it): at most 40, in the queued area.
|
|
710
|
-
setJourneyPage(h.session, facts.register ? decidePage(facts.register, me, skips).map((o) => o.key) : []);
|
|
1134
|
+
setJourneyPage(h.session, facts.register ? decidePage(facts.register, me, skips, facts.view).map((o) => o.key) : []);
|
|
711
1135
|
}
|
|
712
1136
|
else {
|
|
713
1137
|
const key = r.next.key;
|
|
714
|
-
setJourneyQueued(h.session, { command, skill, stage: 'FIX', ...(key ? { key } : {}), callsAt: toolCallsOf(h.session).length, variant: projectVariant(h.cwd), ...targetFor(facts.
|
|
1138
|
+
setJourneyQueued(h.session, { command, skill, stage: 'FIX', ...(key ? { key } : {}), callsAt: toolCallsOf(h.session).length, variant: projectVariant(h.cwd), ...targetFor(key ? facts.view?.object(key) : undefined) });
|
|
715
1139
|
}
|
|
716
1140
|
queueStep(h, command);
|
|
717
1141
|
return;
|
|
718
1142
|
}
|
|
719
|
-
end(h); // stop or done: the runner said its last words
|
|
1143
|
+
end(h, undefined, r.next.kind === 'done'); // stop or done: the runner said its last words
|
|
720
1144
|
return;
|
|
721
1145
|
}
|
|
722
1146
|
pause(h);
|
|
@@ -741,9 +1165,9 @@ export async function afterJourneyTurn(h, t) {
|
|
|
741
1165
|
const stage = stageOf(facts);
|
|
742
1166
|
let moved = stage !== q.stage;
|
|
743
1167
|
if (!moved && facts.register && q.stage === 'DECIDE')
|
|
744
|
-
moved = toDecide(facts.register, me, skips).length < (q.left ?? Number.MAX_SAFE_INTEGER);
|
|
1168
|
+
moved = toDecide(facts.register, me, skips, facts.view).length < (q.left ?? Number.MAX_SAFE_INTEGER);
|
|
745
1169
|
if (!moved && facts.register && q.stage === 'FIX')
|
|
746
|
-
moved = !toFix(facts.register, me).some((o) => o.key === q.key);
|
|
1170
|
+
moved = !toFix(facts.register, me, {}, facts.view).some((o) => o.key === q.key);
|
|
747
1171
|
if (!moved) {
|
|
748
1172
|
if (/\?\s*$/.test(t.assistantText.trim()))
|
|
749
1173
|
return;
|
|
@@ -755,10 +1179,10 @@ export async function afterJourneyTurn(h, t) {
|
|
|
755
1179
|
// nothing recorded. "Left for later" alone hid what was still open: one
|
|
756
1180
|
// card says it and asks what to do.
|
|
757
1181
|
const o = facts.register?.objects.find((x) => x.key === q.key);
|
|
758
|
-
// Proof first: a fix
|
|
759
|
-
if (o &&
|
|
1182
|
+
// Proof first: the view's part — a fix proven for its own target, or claimed again since, is not offered again.
|
|
1183
|
+
if (o && facts.view?.object(o.key)?.part === 'failed-check') {
|
|
760
1184
|
setJourneyQueued(h.session, undefined);
|
|
761
|
-
const goOn = await retryCard(h, team, o);
|
|
1185
|
+
const goOn = await retryCard(h, team, o, facts.view?.object(o.key));
|
|
762
1186
|
if (!goOn)
|
|
763
1187
|
return;
|
|
764
1188
|
try {
|
|
@@ -796,7 +1220,7 @@ export async function afterJourneyTurn(h, t) {
|
|
|
796
1220
|
* Returns true when the chain goes on from the folder (drive), false when it
|
|
797
1221
|
* stopped or a command was queued.
|
|
798
1222
|
*/
|
|
799
|
-
async function retryCard(h, team, o) {
|
|
1223
|
+
async function retryCard(h, team, o, v) {
|
|
800
1224
|
const name = o.name.toUpperCase();
|
|
801
1225
|
const type = o.type.toUpperCase();
|
|
802
1226
|
const level = o.decision?.level && LEVEL.test(o.decision.level) ? o.decision.level : undefined;
|
|
@@ -839,7 +1263,7 @@ async function retryCard(h, team, o) {
|
|
|
839
1263
|
return false;
|
|
840
1264
|
}
|
|
841
1265
|
h.emit(`Step ${stepOf('FIX')} of ${STEPS} — fix ${name} again.`);
|
|
842
|
-
setJourneyQueued(h.session, { command, skill: 'abap-refactor', stage: 'FIX', key: o.key, callsAt: toolCallsOf(h.session).length, variant: projectVariant(h.cwd), ...targetFor(
|
|
1266
|
+
setJourneyQueued(h.session, { command, skill: 'abap-refactor', stage: 'FIX', key: o.key, callsAt: toolCallsOf(h.session).length, variant: projectVariant(h.cwd), ...targetFor(v) });
|
|
843
1267
|
queueStep(h, command);
|
|
844
1268
|
return false;
|
|
845
1269
|
}
|
|
@@ -929,6 +1353,10 @@ export function journeyFixBlock(variant, target) {
|
|
|
929
1353
|
"This fix is one step of CSPeach's guided clean-core run.",
|
|
930
1354
|
`The project's check variant is ${v}.`,
|
|
931
1355
|
...(t ? [`This object's target is clean-core level ${t}. CSPeach proves the fix against that target with this check.`] : []),
|
|
1356
|
+
// Task 7 — the approval card shows what callers or users will see differently.
|
|
1357
|
+
"On the approval request, fill changes[].behaviour for every change: what callers or users will see differently, or 'none'.",
|
|
1358
|
+
// Task 7 (R2) — the card's risk line is CSPeach's reading; the model relays it.
|
|
1359
|
+
RISK_NOTE,
|
|
932
1360
|
`After you activate the object, run ATC on it with the variant ${v}, not the default one.`,
|
|
933
1361
|
// Final review M4: with a target other than A, findings that only matter for A may stay.
|
|
934
1362
|
t && t !== 'A'
|