@cspeach/cli 1.1.30 → 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 +121 -8
- package/dist/cli.js +36 -14
- package/dist/commands/config-set.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 +375 -41
- 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 +20 -1
- package/dist/projects/clean-core-confirm.js +156 -0
- package/dist/projects/detail-path.js +9 -4
- package/dist/projects/extract-clean-core.js +3 -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 +83 -12
- 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/workspace-retire.js +48 -0
- package/dist/projects/workspace.js +182 -121
- package/dist/register/discover.js +901 -0
- package/dist/register/findings.js +5 -2
- package/dist/register/offer.js +100 -8
- package/dist/register/picture.js +6 -2
- package/dist/register/plan-import.js +325 -0
- package/dist/register/records.js +25 -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 +11 -3
- package/dist/register/status-file/register-model.js +40 -6
- package/dist/register/status-file/register-parts.js +57 -11
- package/dist/register/status-file/register-status-file.js +24 -6
- package/dist/register/status-file/team-css.js +1 -1
- package/dist/register/status-file-sync.js +3 -0
- package/dist/register/status-html.js +3 -1
- package/dist/register/store.js +40 -2
- package/dist/register/workbooks.js +27 -7
- package/dist/register/xlsx.js +13 -3
- package/dist/register/xref.js +117 -0
- package/dist/renderer/startup-lines.js +11 -0
- package/dist/renderer/tool-labels.js +2 -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 +157 -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 +1 -1
- package/dist/tools/_filesystem-shared.js +174 -12
- 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 +77 -4
- package/dist/tools/project/upgrade_merge_progress.js +8 -2
- package/dist/tools/sap-read.js +21 -4
- package/dist/tools/sap-write.js +200 -10
- package/dist/tools/subagent/read_agent.js +3 -0
- package/dist/tools/tool-loading.js +1 -1
- 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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guided clean-core journey, slice 1 (design .superpowers/sdd/guided-journey-design.md §2).
|
|
3
|
+
*
|
|
4
|
+
* The stage is derived from the folder every time it is needed — never stored.
|
|
5
|
+
* `stageOf` is pure: it reads only the facts handed to it. `readJourneyFacts`
|
|
6
|
+
* gathers those facts from the folder and the register, read only, and never
|
|
7
|
+
* throws: a folder halfway through a sync reads as UNREADABLE.
|
|
8
|
+
*
|
|
9
|
+
* UNREADABLE the folder's project or its object list cannot be read now
|
|
10
|
+
* INVENTORY no discovery in the folder
|
|
11
|
+
* START no project in the folder
|
|
12
|
+
* JOIN a project in the folder this laptop does not record into
|
|
13
|
+
* DECIDE my objects with findings (or not measured) and no decision
|
|
14
|
+
* FIX my objects decided "fix" with no claim yet, then fixes that failed the check
|
|
15
|
+
* PROVE my objects whose fix is claimed and not measured since
|
|
16
|
+
* REPORT a record newer than the status page in reports/
|
|
17
|
+
* DONE
|
|
18
|
+
*
|
|
19
|
+
* "Rebuild beside the core" is not an /abap-refactor job: those objects never
|
|
20
|
+
* hold the journey, and `toRedesign` lists them so a stage can say so.
|
|
21
|
+
*
|
|
22
|
+
* `skip` is the session's own memory (rows skipped, fixes put off): it never
|
|
23
|
+
* reaches the folder, so a new session asks again.
|
|
24
|
+
*/
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import { DISCOVERY_DIR, TEAM_FILE, activeProject, readBaseline, readEstate, readProjectFile } from '../register/store.js';
|
|
28
|
+
import { registerFor } from '../register/picture.js';
|
|
29
|
+
import { reportFileNames } from '../register/workbooks.js';
|
|
30
|
+
import { samePath } from '../commands/team-discover.js';
|
|
31
|
+
export const STEPS = 6;
|
|
32
|
+
/** "Step n of 6". Joining a project is part of step 2; UNREADABLE and DONE have no step. */
|
|
33
|
+
const STAGE_STEP = {
|
|
34
|
+
INVENTORY: 1, START: 2, JOIN: 2, DECIDE: 3, FIX: 4, PROVE: 5, REPORT: 6,
|
|
35
|
+
};
|
|
36
|
+
export const stepOf = (stage) => STAGE_STEP[stage];
|
|
37
|
+
/** States that need nothing more from the person. */
|
|
38
|
+
const SETTLED = new Set(['clean', 'verified', 'retired']);
|
|
39
|
+
/** A fix was claimed and a later check still found it (or found more). */
|
|
40
|
+
const FAILED_CHECK = new Set(['did-not-hold', 'regressed']);
|
|
41
|
+
const same = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
42
|
+
const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
43
|
+
const size = (o) => (o.openFindings === null ? Number.MAX_SAFE_INTEGER : o.openFindings);
|
|
44
|
+
const smallestFirst = (a, b) => size(a) - size(b) || byName(a, b);
|
|
45
|
+
/** My objects: in a lane I own, in scope, not retired. */
|
|
46
|
+
export function myObjects(register, me) {
|
|
47
|
+
const lanes = new Set(register.lanes.filter((l) => same(l.owner, me)).map((l) => l.id));
|
|
48
|
+
return register.objects.filter((o) => o.lane !== null && lanes.has(o.lane) && o.inScope && o.state !== 'retired');
|
|
49
|
+
}
|
|
50
|
+
/** My objects that still need a decision, in register order. */
|
|
51
|
+
export function toDecide(register, me, skip = {}) {
|
|
52
|
+
return myObjects(register, me).filter((o) => !o.decision && !SETTLED.has(o.state) && !skip.decide?.has(o.key));
|
|
53
|
+
}
|
|
54
|
+
/** A triage is shown at most this many objects at a time (design §2: pages of 40). */
|
|
55
|
+
export const PAGE_SIZE = 40;
|
|
56
|
+
/**
|
|
57
|
+
* The next page to decide: the first objects left to decide in the area of
|
|
58
|
+
* the first one (the triage is queued for that one area), in register order,
|
|
59
|
+
* at most PAGE_SIZE.
|
|
60
|
+
*/
|
|
61
|
+
export function decidePage(register, me, skip = {}) {
|
|
62
|
+
const left = toDecide(register, me, skip);
|
|
63
|
+
const lane = left[0]?.lane;
|
|
64
|
+
return left.filter((o) => o.lane === lane).slice(0, PAGE_SIZE);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* My objects decided "fix" that need a fix: first the ones nobody has claimed
|
|
68
|
+
* yet, then the ones whose claimed fix failed the check (did not hold, or
|
|
69
|
+
* regressed). Each group smallest first (fewest open findings, never measured last).
|
|
70
|
+
*/
|
|
71
|
+
export function toFix(register, me, skip = {}) {
|
|
72
|
+
const mine = myObjects(register, me).filter((o) => o.decision?.classification === 'fix' && !SETTLED.has(o.state) && !skip.fix?.has(o.key));
|
|
73
|
+
// A failed fix already claimed again waits for its check (toProve), not for another fix.
|
|
74
|
+
const retry = mine.filter((o) => FAILED_CHECK.has(o.state) && !claimedSinceCheck(o));
|
|
75
|
+
const fresh = mine.filter((o) => !FAILED_CHECK.has(o.state) && !o.fixes.some((f) => f.status === 'fixed'));
|
|
76
|
+
return [...fresh.sort(smallestFirst), ...retry.sort(smallestFirst)];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A `fixed` claim newer than the object's own last measurement. The register
|
|
80
|
+
* keeps a failed fix at did-not-hold / regressed until the NEXT check, even
|
|
81
|
+
* after a new claim; the journey reads the new claim as waiting for that check.
|
|
82
|
+
*/
|
|
83
|
+
export function claimedSinceCheck(o) {
|
|
84
|
+
const checked = o.lastMeasuredAt ? Date.parse(o.lastMeasuredAt) : NaN;
|
|
85
|
+
return o.fixes.some((f) => f.status === 'fixed' && (!Number.isFinite(checked) || Date.parse(f.at) > checked));
|
|
86
|
+
}
|
|
87
|
+
/** True when this object is in `toFix` because its fix failed the check. */
|
|
88
|
+
export const fixFailedCheck = (o) => FAILED_CHECK.has(o.state);
|
|
89
|
+
/** My objects planned for a rebuild beside the core — handled separately, never in the fix loop. */
|
|
90
|
+
export function toRedesign(register, me) {
|
|
91
|
+
return myObjects(register, me).filter((o) => o.decision?.classification === 'redesign' && !SETTLED.has(o.state)).sort(byName);
|
|
92
|
+
}
|
|
93
|
+
/** My objects whose fix is claimed and not measured since — a failed fix claimed again included. */
|
|
94
|
+
export function toProve(register, me) {
|
|
95
|
+
return myObjects(register, me).filter((o) => o.state === 'claimed' || (FAILED_CHECK.has(o.state) && claimedSinceCheck(o)));
|
|
96
|
+
}
|
|
97
|
+
/** The newest thing the register knows of, in ms; null when nothing is recorded. */
|
|
98
|
+
export function newestRecordAt(register) {
|
|
99
|
+
let newest = null;
|
|
100
|
+
const take = (iso) => {
|
|
101
|
+
const t = iso ? Date.parse(iso) : NaN;
|
|
102
|
+
if (Number.isFinite(t) && (newest === null || t > newest))
|
|
103
|
+
newest = t;
|
|
104
|
+
};
|
|
105
|
+
take(register.lastMeasuredAt);
|
|
106
|
+
for (const o of register.objects)
|
|
107
|
+
take(o.lastRecordAt);
|
|
108
|
+
return newest;
|
|
109
|
+
}
|
|
110
|
+
/** Why the stage is UNREADABLE, as a reason kind; undefined when it is not. */
|
|
111
|
+
export function unreadableReason(f) {
|
|
112
|
+
if (f.folder.kind === 'unreadable')
|
|
113
|
+
return f.folder.reason;
|
|
114
|
+
// Joined, yet no register was handed in: whatever stopped it is not one of the named cases.
|
|
115
|
+
if (f.folder.kind === 'joined' && !f.register)
|
|
116
|
+
return 'other';
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
export function stageOf(f) {
|
|
120
|
+
if (f.folder.kind === 'unreadable')
|
|
121
|
+
return 'UNREADABLE';
|
|
122
|
+
if (!f.hasDiscovery)
|
|
123
|
+
return 'INVENTORY';
|
|
124
|
+
if (f.folder.kind === 'none')
|
|
125
|
+
return 'START';
|
|
126
|
+
if (f.folder.kind !== 'joined')
|
|
127
|
+
return 'JOIN';
|
|
128
|
+
const r = f.register;
|
|
129
|
+
if (!r)
|
|
130
|
+
return 'UNREADABLE'; // unreadableReason(f) === 'other'
|
|
131
|
+
const skip = f.skip ?? {};
|
|
132
|
+
if (toDecide(r, f.me, skip).length)
|
|
133
|
+
return 'DECIDE';
|
|
134
|
+
if (toFix(r, f.me, skip).length)
|
|
135
|
+
return 'FIX';
|
|
136
|
+
if (!skip.prove && toProve(r, f.me).length)
|
|
137
|
+
return 'PROVE';
|
|
138
|
+
if (!skip.report) {
|
|
139
|
+
const newest = newestRecordAt(r);
|
|
140
|
+
if (f.reportsAt === null || (newest !== null && newest > f.reportsAt))
|
|
141
|
+
return 'REPORT';
|
|
142
|
+
}
|
|
143
|
+
return 'DONE';
|
|
144
|
+
}
|
|
145
|
+
/** Where the folder's project stands for this laptop. Never throws. */
|
|
146
|
+
export function folderKind(cwd, me) {
|
|
147
|
+
if (!fs.existsSync(path.join(cwd, TEAM_FILE)))
|
|
148
|
+
return { kind: 'none' };
|
|
149
|
+
let project;
|
|
150
|
+
try {
|
|
151
|
+
project = readProjectFile(cwd);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return { kind: 'unreadable', reason: 'project-file' };
|
|
155
|
+
}
|
|
156
|
+
const who = { lead: project.lead.name, name: project.name };
|
|
157
|
+
let joined;
|
|
158
|
+
try {
|
|
159
|
+
joined = activeProject();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
joined = null;
|
|
163
|
+
}
|
|
164
|
+
if (joined && joined.projectId === project.projectId && samePath(joined.projectDir, cwd))
|
|
165
|
+
return { kind: 'joined', ...who };
|
|
166
|
+
return same(project.lead.name, me) ? { kind: 'mine-not-joined', ...who } : { kind: 'someone-else', ...who };
|
|
167
|
+
}
|
|
168
|
+
/** mtime of the status page the report step writes, or null. */
|
|
169
|
+
export function reportsAt(cwd, projectName) {
|
|
170
|
+
try {
|
|
171
|
+
return fs.statSync(path.join(cwd, 'reports', reportFileNames(projectName).html)).mtimeMs;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The facts for `stageOf`, read from the folder. The register is built only
|
|
179
|
+
* when this laptop records into the folder's project. Anything that cannot be
|
|
180
|
+
* read — a project file or object list halfway through a sync, a checksum that
|
|
181
|
+
* no longer matches — comes back as `folder.kind === 'unreadable'`, never as
|
|
182
|
+
* a thrown error.
|
|
183
|
+
*/
|
|
184
|
+
export function readJourneyFacts(cwd, me, skip) {
|
|
185
|
+
let hasDiscovery = false;
|
|
186
|
+
try {
|
|
187
|
+
hasDiscovery = fs.existsSync(path.join(cwd, DISCOVERY_DIR)) && readEstate(cwd) !== null;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
hasDiscovery = false;
|
|
191
|
+
}
|
|
192
|
+
const folder = folderKind(cwd, me);
|
|
193
|
+
const facts = { hasDiscovery, folder, me, reportsAt: null, ...(skip ? { skip } : {}) };
|
|
194
|
+
if (folder.kind !== 'joined')
|
|
195
|
+
return facts;
|
|
196
|
+
try {
|
|
197
|
+
// The object list first, on its own: its failures (missing, garbled,
|
|
198
|
+
// checksum) are the everyday mid-sync case and get their own word.
|
|
199
|
+
readBaseline(cwd);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return { ...facts, folder: { kind: 'unreadable', reason: 'object-list' } };
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
const joined = activeProject();
|
|
206
|
+
facts.register = registerFor(joined.projectDir, joined.outboxDir).register;
|
|
207
|
+
facts.reportsAt = reportsAt(cwd, folder.name);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return { ...facts, folder: { kind: 'unreadable', reason: 'other' } };
|
|
211
|
+
}
|
|
212
|
+
return facts;
|
|
213
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guided clean-core journey, Task 3 — the session's journey, keyed on
|
|
3
|
+
* `ctx.session` (a WeakMap, like lane/app-flow.ts): nothing is written to the
|
|
4
|
+
* folder and nothing outlives the session. The stage itself is never kept —
|
|
5
|
+
* it is derived from the folder every time (clean-core.ts).
|
|
6
|
+
*
|
|
7
|
+
* Kept here: the packages the opening sentence named, the model step the
|
|
8
|
+
* journey queued (its exact command, the skill it runs as, and what it was
|
|
9
|
+
* meant to move), the page of objects the triage is shown (Task 4: at most
|
|
10
|
+
* 40 keys, read by team_findings), and the session skips ("Later", "Not now",
|
|
11
|
+
* a row skipped).
|
|
12
|
+
*
|
|
13
|
+
* While a journey is active the guarded-run override is on (its own share,
|
|
14
|
+
* plan-gate.ts setJourneyRunActive), so auto_approve never applies — the
|
|
15
|
+
* queued refactor included. It goes off when the journey ends or pauses; a
|
|
16
|
+
* plan turn's own guarded flag is never touched.
|
|
17
|
+
*/
|
|
18
|
+
import { setJourneyRunActive } from '../commands/plan-gate.js';
|
|
19
|
+
import { shown } from '../register/atc-file.js';
|
|
20
|
+
const flows = new WeakMap();
|
|
21
|
+
/**
|
|
22
|
+
* Sessions whose journey paused (a stray turn, a put-away card, Ctrl-C), with
|
|
23
|
+
* the session's user-turn count at the pause: "carry on" right after the pause
|
|
24
|
+
* line wins over an interrupted or waiting turn; once another turn ran, it
|
|
25
|
+
* does not.
|
|
26
|
+
*/
|
|
27
|
+
const paused = new WeakMap();
|
|
28
|
+
/** The person's turns in a session: user messages of text or images, never a tool result. */
|
|
29
|
+
export function userTurnCount(session) {
|
|
30
|
+
const messages = session.messages ?? [];
|
|
31
|
+
return messages.filter((m) => m.role === 'user' && (typeof m.content === 'string'
|
|
32
|
+
|| (Array.isArray(m.content) && m.content.some((b) => b?.type !== 'tool_result')))).length;
|
|
33
|
+
}
|
|
34
|
+
/** Skips survive a pause, so "carry on" does not ask again what was put off. */
|
|
35
|
+
const skips = new WeakMap();
|
|
36
|
+
/** Start (or restart) the journey for this session. */
|
|
37
|
+
export function startJourneyFlow(session, packages = []) {
|
|
38
|
+
flows.set(session, { packages: [...packages] });
|
|
39
|
+
paused.delete(session);
|
|
40
|
+
setJourneyRunActive(true);
|
|
41
|
+
}
|
|
42
|
+
export function getJourneyFlow(session) {
|
|
43
|
+
const f = flows.get(session);
|
|
44
|
+
return f ? Object.freeze({
|
|
45
|
+
packages: [...f.packages],
|
|
46
|
+
...(f.queued ? { queued: Object.freeze({ ...f.queued }) } : {}),
|
|
47
|
+
...(f.page ? { page: [...f.page] } : {}),
|
|
48
|
+
...(f.pages ? { pages: f.pages } : {}),
|
|
49
|
+
}) : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** The page of objects the next triage is shown; one more page handed out. */
|
|
52
|
+
export function setJourneyPage(session, keys) {
|
|
53
|
+
const f = flows.get(session);
|
|
54
|
+
if (!f)
|
|
55
|
+
return;
|
|
56
|
+
f.page = [...keys];
|
|
57
|
+
f.pages = (f.pages ?? 0) + 1;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The page the queued triage may see (team_findings shows only these), or
|
|
61
|
+
* undefined when no journey triage is queued: then the tool is as it always was.
|
|
62
|
+
*/
|
|
63
|
+
export function journeyPage(session) {
|
|
64
|
+
const f = session ? flows.get(session) : undefined;
|
|
65
|
+
return f?.queued?.stage === 'DECIDE' && f.page ? [...f.page] : undefined;
|
|
66
|
+
}
|
|
67
|
+
export const isJourneyActive = (session) => !!session && flows.has(session);
|
|
68
|
+
/** True when `cmd` is the model step this session's journey queued (no echo for it). */
|
|
69
|
+
export function isJourneyHop(session, cmd) {
|
|
70
|
+
return !!session && flows.get(session)?.queued?.command === cmd;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The one plain line a journey hop shows in place of the "You ›" echo — the
|
|
74
|
+
* person did not type the queued command, so it is never shown as theirs.
|
|
75
|
+
* Null when `cmd` is not this session's queued journey step: the echo stays.
|
|
76
|
+
*/
|
|
77
|
+
export function journeyHopLine(session, cmd) {
|
|
78
|
+
if (!isJourneyHop(session, cmd))
|
|
79
|
+
return null;
|
|
80
|
+
const f = flows.get(session);
|
|
81
|
+
const q = f.queued;
|
|
82
|
+
if (q.stage === 'DECIDE') {
|
|
83
|
+
const n = f.page ? f.page.length : q.left ?? 0;
|
|
84
|
+
return n > 0 ? `CSPeach is looking at your ${n} object${n === 1 ? '' : 's'}…` : 'CSPeach is looking at your objects…';
|
|
85
|
+
}
|
|
86
|
+
const name = q.key ? shown(q.key.slice(q.key.indexOf('|') + 1)).trim() : '';
|
|
87
|
+
return name ? `CSPeach is preparing the fix for ${name}…` : 'CSPeach is preparing the fix…';
|
|
88
|
+
}
|
|
89
|
+
export function setJourneyQueued(session, step) {
|
|
90
|
+
const f = flows.get(session);
|
|
91
|
+
if (!f)
|
|
92
|
+
return;
|
|
93
|
+
if (step)
|
|
94
|
+
f.queued = { ...step };
|
|
95
|
+
else
|
|
96
|
+
delete f.queued;
|
|
97
|
+
}
|
|
98
|
+
/** End the journey: the flow goes, the guarded run ends; skips stay for the session. */
|
|
99
|
+
export function clearJourneyFlow(session) {
|
|
100
|
+
paused.delete(session);
|
|
101
|
+
if (!flows.has(session))
|
|
102
|
+
return;
|
|
103
|
+
flows.delete(session);
|
|
104
|
+
setJourneyRunActive(false);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Pause the journey: ended like clearJourneyFlow, and remembered so "carry on" picks it up.
|
|
108
|
+
* `offset`: user turns about to be counted that still belong to the pause — the
|
|
109
|
+
* gate pauses before the stray message is pushed, so it passes 1.
|
|
110
|
+
*/
|
|
111
|
+
export function pauseJourneyFlow(session, offset = 0) {
|
|
112
|
+
clearJourneyFlow(session);
|
|
113
|
+
paused.set(session, userTurnCount(session) + offset);
|
|
114
|
+
}
|
|
115
|
+
export const isJourneyPaused = (session) => !!session && paused.has(session);
|
|
116
|
+
/** Paused, and no turn of the person's has run since the pause line. */
|
|
117
|
+
export const isJourneyPausedJustNow = (session) => !!session && paused.has(session) && paused.get(session) === userTurnCount(session);
|
|
118
|
+
/** The session skips as the stage machine reads them. */
|
|
119
|
+
export function journeySkips(session) {
|
|
120
|
+
const s = skips.get(session);
|
|
121
|
+
if (!s)
|
|
122
|
+
return {};
|
|
123
|
+
const decide = new Set([...s.decide, ...(s.leftOut ?? [])]);
|
|
124
|
+
return {
|
|
125
|
+
...(decide.size ? { decide } : {}),
|
|
126
|
+
...(s.fix.size ? { fix: new Set(s.fix) } : {}),
|
|
127
|
+
...(s.prove ? { prove: true } : {}),
|
|
128
|
+
...(s.report ? { report: true } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function addJourneySkips(session, add) {
|
|
132
|
+
if (!add)
|
|
133
|
+
return;
|
|
134
|
+
const s = skips.get(session) ?? { decide: new Set(), fix: new Set() };
|
|
135
|
+
for (const k of add.decide ?? [])
|
|
136
|
+
s.decide.add(k);
|
|
137
|
+
for (const k of add.fix ?? [])
|
|
138
|
+
s.fix.add(k);
|
|
139
|
+
if (add.prove)
|
|
140
|
+
s.prove = true;
|
|
141
|
+
if (add.report)
|
|
142
|
+
s.report = true;
|
|
143
|
+
skips.set(session, s);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Page objects the triage left out (Task 4, fix round 1): put off for now, and
|
|
147
|
+
* forgotten by "carry on" like the check and the report.
|
|
148
|
+
*/
|
|
149
|
+
export function addJourneyLeftOut(session, keys) {
|
|
150
|
+
if (!keys.length)
|
|
151
|
+
return;
|
|
152
|
+
const s = skips.get(session) ?? { decide: new Set(), fix: new Set() };
|
|
153
|
+
s.leftOut ??= new Set();
|
|
154
|
+
for (const k of keys)
|
|
155
|
+
s.leftOut.add(k);
|
|
156
|
+
skips.set(session, s);
|
|
157
|
+
}
|
|
158
|
+
/** "Carry on" asks the check, the report and the rows the triage left out again; fixes and rows skipped stay put off. */
|
|
159
|
+
export function forgetNotNow(session) {
|
|
160
|
+
const s = skips.get(session);
|
|
161
|
+
if (!s)
|
|
162
|
+
return;
|
|
163
|
+
delete s.prove;
|
|
164
|
+
delete s.report;
|
|
165
|
+
delete s.leftOut;
|
|
166
|
+
}
|