@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/session/store.js
CHANGED
|
@@ -7,6 +7,7 @@ import { sessionsDir, pinnedSessionsDir } from '../config/paths.js';
|
|
|
7
7
|
import { SCHEMA_VERSION } from './schema.js';
|
|
8
8
|
import { userPromptText, isUserPrompt, promptPreviewText } from './user-prompt.js';
|
|
9
9
|
import { stripUnsignedThinking, dropTrailingThinkingOnly } from '../agent/repair-partial.js';
|
|
10
|
+
import { normDir, sessionMatchesFolder } from './project-match.js';
|
|
10
11
|
export function makeSessionId() {
|
|
11
12
|
return crypto.randomBytes(4).toString('hex');
|
|
12
13
|
}
|
|
@@ -71,6 +72,9 @@ function validateSchema(parsed) {
|
|
|
71
72
|
// Phase 4 chunk 4A: agentMode defaults to 'skill' for sessions written
|
|
72
73
|
// before this chunk landed. Mirrors the lastUserPrompt back-compat above.
|
|
73
74
|
parsed.agentMode ??= 'skill';
|
|
75
|
+
// 2026-10-08: sessions are listed per folder. Older files have no folder.
|
|
76
|
+
parsed.project_dir ??= null;
|
|
77
|
+
parsed.project_id ??= null;
|
|
74
78
|
// Phase 1 cost tracking (2026-05-16): cache_creation_input_tokens added
|
|
75
79
|
// to usage. Pre-2026-05-16 sessions don't have it; default to 0 so the
|
|
76
80
|
// cost calculator can safely treat usage as a TokenCounts shape.
|
|
@@ -130,11 +134,17 @@ export const RESUME_PICKER_LIMIT = 20;
|
|
|
130
134
|
async function recentSessionFiles(dir, limit) {
|
|
131
135
|
// Merge note — Task 18 reader-* child sessions are skipped by NAME here, so a
|
|
132
136
|
// burst of reader files can never crowd real sessions out of the window.
|
|
133
|
-
const files =
|
|
134
|
-
.filter((f) => f.endsWith('.json') && !f.includes('-reader-'));
|
|
137
|
+
const files = await sessionFileNames(dir);
|
|
135
138
|
const cap = Math.max(limit * 3, 50);
|
|
136
139
|
if (files.length <= cap)
|
|
137
140
|
return files;
|
|
141
|
+
return (await newestFirst(dir, files)).slice(0, cap);
|
|
142
|
+
}
|
|
143
|
+
async function sessionFileNames(dir) {
|
|
144
|
+
return (await fs.readdir(dir).catch(() => []))
|
|
145
|
+
.filter((f) => f.endsWith('.json') && !f.includes('-reader-'));
|
|
146
|
+
}
|
|
147
|
+
async function newestFirst(dir, files) {
|
|
138
148
|
const stamped = await Promise.all(files.map(async (f) => {
|
|
139
149
|
try {
|
|
140
150
|
return { f, t: (await fs.stat(path.join(dir, f))).mtimeMs };
|
|
@@ -144,78 +154,113 @@ async function recentSessionFiles(dir, limit) {
|
|
|
144
154
|
}
|
|
145
155
|
}));
|
|
146
156
|
stamped.sort((a, b) => b.t - a.t);
|
|
147
|
-
return stamped.
|
|
157
|
+
return stamped.map((x) => x.f);
|
|
158
|
+
}
|
|
159
|
+
/** 2026-10-08 — every session file, newest first by mtime (no cap): `--continue` / `--resume` scans. */
|
|
160
|
+
async function allSessionFilesNewestFirst(dir) {
|
|
161
|
+
return newestFirst(dir, await sessionFileNames(dir));
|
|
162
|
+
}
|
|
163
|
+
/** One picker row from a session file; null for a reader child or a corrupt file. */
|
|
164
|
+
async function summaryOf(file, filename) {
|
|
165
|
+
try {
|
|
166
|
+
const raw = await fs.readFile(file, 'utf-8');
|
|
167
|
+
const parsed = JSON.parse(raw);
|
|
168
|
+
if (isReaderSessionId(parsed.id))
|
|
169
|
+
return null; // Task 18 — internal child, never resumed
|
|
170
|
+
const messages = parsed.messages ?? [];
|
|
171
|
+
// First user message that's a plain string (not a tool_result array) — the
|
|
172
|
+
// most recent prompt text that initiated a turn. We pick the LAST such
|
|
173
|
+
// message because the picker wants the freshest turn's prompt, not the
|
|
174
|
+
// first one of the session.
|
|
175
|
+
let promptSnippet = null;
|
|
176
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
177
|
+
const m = messages[i];
|
|
178
|
+
const text = m ? userPromptText(m) : null;
|
|
179
|
+
if (text !== null) {
|
|
180
|
+
// Strip <session_context>...</session_context> wrapper that the
|
|
181
|
+
// first turn injects, leaving only the user-typed prompt visible
|
|
182
|
+
// in the picker.
|
|
183
|
+
// (fix round 3: every injected block, e.g. <project_context>, and
|
|
184
|
+
// newlines — they broke the picker's table row.)
|
|
185
|
+
const stripped = promptPreviewText(text);
|
|
186
|
+
promptSnippet = (stripped || '<empty>').slice(0, 80);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const userTurnCount = messages.filter((m) => m && isUserPrompt(m)).length;
|
|
191
|
+
return {
|
|
192
|
+
id: parsed.id,
|
|
193
|
+
startedAt: parsed.started_at,
|
|
194
|
+
lastTurnAt: parsed.last_turn_at,
|
|
195
|
+
skill: parsed.skill,
|
|
196
|
+
sapSystem: parsed.sap_system,
|
|
197
|
+
filename,
|
|
198
|
+
interrupted: parsed.turnInterrupted === true,
|
|
199
|
+
interruptedReason: parsed.turnInterruptedReason ?? null,
|
|
200
|
+
promptSnippet,
|
|
201
|
+
userTurnCount,
|
|
202
|
+
projectDir: parsed.project_dir ?? null,
|
|
203
|
+
projectId: parsed.project_id ?? null,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null; // skip corrupt
|
|
208
|
+
}
|
|
148
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* The most recent sessions, newest first by last_turn_at.
|
|
212
|
+
* - `here`: only the sessions of that folder (sessionMatchesFolder).
|
|
213
|
+
* - `scanAll` (with `here`): walk every file newest-first instead of the
|
|
214
|
+
* startup window, stopping once `limit` matches are found
|
|
215
|
+
* (`--continue`, `--resume`). Without `here` the window is always used.
|
|
216
|
+
*/
|
|
149
217
|
export async function listSessions(limit = 10, opts = {}) {
|
|
150
218
|
// NOTE: bypasses validateSchema() for performance — reads JSON directly via
|
|
151
219
|
// JSON.parse + cast. Any new SessionState field accessed here must be
|
|
152
220
|
// defaulted at the read site (e.g. `?? 'skill'`) because pre-chunk-4A
|
|
153
221
|
// serialised sessions on disk lack the field.
|
|
154
222
|
const dir = sessionsDir();
|
|
155
|
-
const
|
|
223
|
+
const scanAll = !!opts.here && opts.scanAll === true;
|
|
224
|
+
const files = scanAll ? await allSessionFilesNewestFirst(dir) : await recentSessionFiles(dir, limit);
|
|
225
|
+
const hereDirNorm = opts.here ? normDir(opts.here.dir) : undefined; // once per call, not per file
|
|
156
226
|
const results = [];
|
|
157
227
|
for (const f of files) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const text = m ? userPromptText(m) : null;
|
|
172
|
-
if (text !== null) {
|
|
173
|
-
// Strip <session_context>...</session_context> wrapper that the
|
|
174
|
-
// first turn injects, leaving only the user-typed prompt visible
|
|
175
|
-
// in the picker.
|
|
176
|
-
// (fix round 3: every injected block, e.g. <project_context>, and
|
|
177
|
-
// newlines — they broke the picker's table row.)
|
|
178
|
-
const stripped = promptPreviewText(text);
|
|
179
|
-
promptSnippet = (stripped || '<empty>').slice(0, 80);
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
const userTurnCount = messages.filter((m) => m && isUserPrompt(m)).length;
|
|
184
|
-
results.push({
|
|
185
|
-
id: parsed.id,
|
|
186
|
-
startedAt: parsed.started_at,
|
|
187
|
-
lastTurnAt: parsed.last_turn_at,
|
|
188
|
-
skill: parsed.skill,
|
|
189
|
-
sapSystem: parsed.sap_system,
|
|
190
|
-
filename: f,
|
|
191
|
-
interrupted: parsed.turnInterrupted === true,
|
|
192
|
-
interruptedReason: parsed.turnInterruptedReason ?? null,
|
|
193
|
-
promptSnippet,
|
|
194
|
-
userTurnCount,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
catch { /* skip corrupt */ }
|
|
228
|
+
const s = await summaryOf(path.join(dir, f), f);
|
|
229
|
+
if (!s)
|
|
230
|
+
continue;
|
|
231
|
+
if (opts.here && !sessionMatchesFolder(s, opts.here, hereDirNorm))
|
|
232
|
+
continue;
|
|
233
|
+
// Round 6 review M1 — the picker drops 0-turn sessions (unless interrupted)
|
|
234
|
+
// BEFORE the limit, so a pile of start-then-quit files cannot crowd out
|
|
235
|
+
// the sessions worth resuming.
|
|
236
|
+
if (opts.skipEmpty && !(s.userTurnCount > 0 || s.interrupted))
|
|
237
|
+
continue;
|
|
238
|
+
results.push(s);
|
|
239
|
+
if (scanAll && results.length >= limit)
|
|
240
|
+
break;
|
|
198
241
|
}
|
|
199
242
|
results.sort((a, b) => b.lastTurnAt.localeCompare(a.lastTurnAt));
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
243
|
+
return results.slice(0, limit);
|
|
244
|
+
}
|
|
245
|
+
/** 2026-10-08 — the latest non-empty session of this folder (`--continue`), or null. */
|
|
246
|
+
export async function latestSessionHere(here, opts = {}) {
|
|
247
|
+
const [s] = await listSessions(1, { here, skipEmpty: true, scanAll: opts.scanAll ?? false });
|
|
248
|
+
return s ?? null;
|
|
205
249
|
}
|
|
206
250
|
/**
|
|
207
251
|
* Count interrupted sessions for the startup banner — among the sessions the
|
|
208
252
|
* `--resume` picker can show (RESUME_PICKER_LIMIT most recent), so the
|
|
209
253
|
* "! N unfinished sessions" notice never points at sessions the picker does
|
|
210
254
|
* not list (fix round 3, item 11d). Returns 0 on any IO failure.
|
|
255
|
+
* 2026-10-08 — with `here`, only that folder's sessions count.
|
|
211
256
|
*/
|
|
212
|
-
export async function countInterruptedSessions() {
|
|
257
|
+
export async function countInterruptedSessions(here) {
|
|
213
258
|
try {
|
|
214
259
|
// Round 8 item 7 — a turn the USER stopped (Ctrl+C, /exit mid-turn) is
|
|
215
260
|
// not unfinished work: counting it kept "! 1 unfinished session" on every
|
|
216
261
|
// start until that session happened to finish a turn. The picker still
|
|
217
262
|
// lists it as "Stopped by you".
|
|
218
|
-
return (await listSessions(RESUME_PICKER_LIMIT))
|
|
263
|
+
return (await listSessions(RESUME_PICKER_LIMIT, here ? { here } : {}))
|
|
219
264
|
.filter((s) => s.interrupted && !isUserStopReason(s.interruptedReason)).length;
|
|
220
265
|
}
|
|
221
266
|
catch {
|
package/dist/skill-catalog.js
CHANGED
|
@@ -168,8 +168,8 @@ export const SKILL_CATALOG = [
|
|
|
168
168
|
{
|
|
169
169
|
name: 'abap-cca',
|
|
170
170
|
category: 'Upgrade / Migrate',
|
|
171
|
-
description: '
|
|
172
|
-
whenToUse: `pick this
|
|
171
|
+
description: 'Reads the estate inventory the lead pulled; proposes scope, areas, retire candidates and next commands; read-only.',
|
|
172
|
+
whenToUse: `pick this after cspeach team discover, to choose packages, lanes and a first focus (not /abap-migrate — that's per object)`,
|
|
173
173
|
asks: 'which package or namespace? e.g. "Z*" or "package ZTEST_LAS"',
|
|
174
174
|
},
|
|
175
175
|
{
|