@cspeach/cli 1.1.27 → 1.1.29

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.
Files changed (68) hide show
  1. package/dist/agent/loop.js +33 -4
  2. package/dist/agent/provider-shape.js +9 -2
  3. package/dist/agent/providers/ai-hub-provider.js +2 -2
  4. package/dist/agent/providers/byok-provider.js +2 -2
  5. package/dist/agent/providers/local-provider.js +4 -4
  6. package/dist/agent/system-prompt.js +34 -7
  7. package/dist/agent/tool-dispatch.js +20 -1
  8. package/dist/agent/turn-error-ux.js +40 -0
  9. package/dist/approvals/grant-claims.js +70 -0
  10. package/dist/approvals/jwt.js +21 -1
  11. package/dist/approvals/plan-grant.js +297 -0
  12. package/dist/commands/config-set.js +12 -0
  13. package/dist/commands/deploy-card.js +10 -5
  14. package/dist/commands/export-audit.js +1 -1
  15. package/dist/commands/plan-audit-evidence.js +47 -1
  16. package/dist/commands/plan-audit.js +23 -8
  17. package/dist/commands/plan-batch-audit.js +349 -0
  18. package/dist/commands/plan-boundary.js +88 -0
  19. package/dist/commands/plan-chain.js +409 -566
  20. package/dist/commands/plan-continue.js +21 -7
  21. package/dist/commands/plan-gate.js +28 -49
  22. package/dist/commands/plan-reground.js +174 -0
  23. package/dist/commands/plan-resume.js +227 -103
  24. package/dist/commands/plan-sap-pause.js +105 -0
  25. package/dist/commands/plan-startup.js +218 -0
  26. package/dist/config/loader.js +9 -0
  27. package/dist/doctor/checks/forge-rules.js +67 -85
  28. package/dist/doctor/run.js +4 -4
  29. package/dist/lane/app-chain.js +88 -9
  30. package/dist/one-shot.js +1 -0
  31. package/dist/projects/extract-plan.js +34 -1
  32. package/dist/projects/handover-md.js +1 -1
  33. package/dist/projects/plan-objects.js +75 -0
  34. package/dist/projects/plan-run.js +74 -18
  35. package/dist/projects/plan-schema.js +6 -0
  36. package/dist/projects/plan-sources.js +199 -0
  37. package/dist/projects/promote-command.js +1 -1
  38. package/dist/projects/save-command.js +11 -13
  39. package/dist/projects/saved-lines.js +36 -0
  40. package/dist/renderer/checklist-format.js +5 -5
  41. package/dist/renderer/machine-blocks.js +141 -0
  42. package/dist/renderer/tool-labels.js +3 -0
  43. package/dist/repl/plan-hop.js +22 -0
  44. package/dist/repl/rule8-detector.js +16 -1
  45. package/dist/repl.js +320 -55
  46. package/dist/router/router-core.js +2 -2
  47. package/dist/sap/reachability-state.js +25 -0
  48. package/dist/session/audit-export.js +10 -16
  49. package/dist/session/recap.js +16 -26
  50. package/dist/skills/bundled-skills.js +81 -81
  51. package/dist/tools/_filesystem-shared.js +37 -0
  52. package/dist/tools/approval.js +48 -2
  53. package/dist/tools/dispatch-skill.js +3 -2
  54. package/dist/tools/filesystem/file-read.js +5 -3
  55. package/dist/tools/filesystem/glob.js +27 -4
  56. package/dist/tools/fiori/tools.js +6 -0
  57. package/dist/tools/lane/app-lane-resolve.js +1 -1
  58. package/dist/tools/sap-read.js +37 -3
  59. package/dist/tools/sap-write.js +65 -0
  60. package/dist/tools/text-elements/pool.js +250 -0
  61. package/dist/tools/text-elements/tools.js +433 -0
  62. package/dist/tools/tool-loading.js +3 -0
  63. package/dist/tools/transport.js +26 -5
  64. package/dist/tools/write-mode.js +4 -3
  65. package/dist/ui/app.js +4 -0
  66. package/dist/ui/widgets/hold-raw-mode.js +26 -0
  67. package/package.json +120 -121
  68. package/dist/projects/email-template.js +0 -56
@@ -36,7 +36,7 @@ export const ROUTER_SCHEMA_VERSION = 1;
36
36
  * its own bundled copy — so a stale published manifest never rolls a newer
37
37
  * CLI back to older routing. router-revision-pin.test.ts fails until bumped.
38
38
  */
39
- export const ROUTER_REVISION = 1;
39
+ export const ROUTER_REVISION = 2;
40
40
  // 2026-06-12: keep in lockstep with .claude/skills (35 abap skills).
41
41
  // abap-dump replaced abap-dump-analyze + abap-dump-triage (portfolio merge);
42
42
  // added previously-missing abap-plan, abap-fiori-build, abap-cca-merge, abap-upgrade-merge.
@@ -74,7 +74,7 @@ export const SKILL_DESCRIPTIONS = {
74
74
  'abap-incident': 'End-to-end incident pipeline — from short dump to fix deployed and documented.',
75
75
  'abap-migrate': 'ECC→S/4HANA migration assessment for custom objects — API analysis, successor lookup, per-object effort.',
76
76
  'abap-modernize': 'Transform a CLASSIC program into a modern architecture (e.g. report → Fiori Elements / RAP) for the release level.',
77
- 'abap-plan': 'Plan and run a multi-session SAP project — phase envelope carries state across sessions, one phase per session.',
77
+ 'abap-plan': 'Plan and run a multi-step SAP project — one approval of the plan and its object list, then the steps run back to back; the plan file carries state across sessions.',
78
78
  'abap-preflight': 'Pre-release checklist for a change set — dependencies, tests, rollback, readiness.',
79
79
  'abap-rap': 'Scaffold a complete RAP stack from scratch — table, CDS interface+projection, BDEF, behavior class, service definition+binding.',
80
80
  'abap-refactor': 'Modify / improve / add a feature to EXISTING custom ABAP code in place — restructure, clean up, or change a Z/Y program/class (incl. small feature changes like adding output coloring).',
@@ -0,0 +1,25 @@
1
+ // cspeach-cli/src/sap/reachability-state.ts
2
+ //
3
+ // 2026-10-04 (continuous run, spec §3.5) — set when a SAP tool fails because
4
+ // the SAP host is not reachable from this machine; read (and cleared) by the
5
+ // REPL after a plan turn so the build pauses instead of failing.
6
+ let unreachable = null;
7
+ const CODE_RE = /\b(ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ENETUNREACH|ECONNRESET|EAI_AGAIN)\b/;
8
+ export const SAP_UNREACHABLE_DIRECTIVE = 'SAP is not reachable from this machine right now. Stop here: do not retry SAP tools and do not write the plan manifest. '
9
+ + 'End the turn with one short sentence. CSPeach pauses the build and runs this step again when the network is back.';
10
+ export function noteSapUnreachable(code) {
11
+ unreachable = code;
12
+ }
13
+ /** True while a SAP-unreachable failure is noted for this turn (does not clear it). */
14
+ export function isSapUnreachableNoted() {
15
+ return unreachable !== null;
16
+ }
17
+ export function getAndResetSapUnreachable() {
18
+ const c = unreachable;
19
+ unreachable = null;
20
+ return c;
21
+ }
22
+ export function unreachableCodeInText(text) {
23
+ const m = CODE_RE.exec(String(text ?? ''));
24
+ return m ? m[1] : null;
25
+ }
@@ -45,6 +45,7 @@ const WRITE_TOOLS = new Set([
45
45
  'sap_delete_object',
46
46
  'sap_create_object',
47
47
  'sap_set_class_include',
48
+ 'sap_set_text_elements',
48
49
  'sap_create_domain',
49
50
  'sap_create_data_element',
50
51
  'sap_activate',
@@ -277,18 +278,11 @@ function outcomeCell(c) {
277
278
  }
278
279
  // ── Operator identity ─────────────────────────────────────────────────────
279
280
  //
280
- // SessionState carries NO SAP logon user (only the sap_system alias). The one
281
- // identity available is the local CSPeach operator, resolved the same way as
282
- // agent/loop.ts getAuthorIdentity (CSPEACH_AUTHOR_NAME → USER → USERNAME). It
283
- // is the person who RAN CSPeach, not the SAP account writes landed under — the
284
- // header labels it "Operator" and shows the SAP system separately so the two
285
- // are never conflated. (Inlined rather than importing agent/loop.js to keep
286
- // this a dependency-light pure builder.)
287
- function resolveOperator() {
288
- return (process.env.CSPEACH_AUTHOR_NAME ||
289
- process.env.USER ||
290
- process.env.USERNAME ||
291
- 'unknown');
281
+ // 2026-10-04 — the person who RAN CSPeach, as CSPeach knows them: the signed-in
282
+ // account (from /v1/me), else an explicit CSPEACH_AUTHOR_NAME, else "not
283
+ // signed in". NEVER the OS account name (USER/USERNAME printed "Admin").
284
+ function resolveOperator(operator) {
285
+ return operator?.trim() || process.env.CSPEACH_AUTHOR_NAME?.trim() || 'not signed in (local run)';
292
286
  }
293
287
  // ── Transports seen ───────────────────────────────────────────────────────
294
288
  function transportsSeen(toolCalls, sessionTransport) {
@@ -320,7 +314,7 @@ function transportsSeen(toolCalls, sessionTransport) {
320
314
  }
321
315
  // ── Main builder ──────────────────────────────────────────────────────────
322
316
  export function buildAuditReport(input) {
323
- const { session, costEntries, transport, credits } = input;
317
+ const { session, costEntries, transport, credits, operator } = input;
324
318
  const calls = session.toolCalls;
325
319
  const completed = calls.filter((c) => c.completed === true);
326
320
  const approvals = parseApprovals(calls);
@@ -344,14 +338,14 @@ export function buildAuditReport(input) {
344
338
  L();
345
339
  L(`- **Session:** \`${session.id}\``);
346
340
  L(`- **SAP system:** ${session.sap_system ?? '—'}`);
347
- L(`- **Operator (local):** ${resolveOperator()}`);
341
+ L(`- **Operator (CSPeach sign-in):** ${resolveOperator(operator)}`);
348
342
  L(`- **Period:** ${dateFrom} → ${dateTo}`);
349
343
  const trs = transportsSeen(calls, transport);
350
344
  L(`- **Transport(s):** ${trs.length ? trs.map((t) => `\`${t}\``).join(', ') : '—'}`);
351
345
  L();
352
346
  L('> Scope: this record covers SAP-system mutations only — local file and');
353
- L('> shell operations are excluded by design. Operator is the local CSPeach');
354
- L('> user (who ran the session); the SAP logon user is not recorded in');
347
+ L('> shell operations are excluded by design. Operator is the CSPeach sign-in');
348
+ L('> of the person who ran the session; the SAP logon user is not recorded in');
355
349
  L('> session state. Plan audit states, if any, live in the plan handover,');
356
350
  L('> not this report.');
357
351
  L();
@@ -90,25 +90,18 @@ export function buildRecap(session, cwd, opts) {
90
90
  return lines;
91
91
  }
92
92
  /**
93
- * Scan `workspaceDir` for the newest plan family and, when it still has
94
- * unsatisfied phases, produce the plan recap fact. Returns null when there
95
- * is no plan, the newest plan family is fully satisfied, or its envelope is
96
- * invalid (silent skip by design).
97
- *
98
- * "Newest family": envelope files group into families by filename base
99
- * (`<base>-v<N>[-<sub>].cspeach.json` — same convention as
100
- * pickNewestPlanVersion / findNewestVersion in commands/plan-resume.ts);
101
- * within a family the highest version wins; families rank by their newest
102
- * file's mtime. Non-plan envelopes (design, spec-gap, …) are skipped so the
103
- * newest PLAN family is found even when a newer non-plan artefact exists.
93
+ * Newest file per envelope family, families newest first (by that file's
94
+ * mtime). [] when the folder is missing. Within a family the highest version
95
+ * wins, then the collision sub, then mtime. A version-less file is its own
96
+ * single-file family. Shared by the recap and the startup Continue offer.
104
97
  */
105
- function buildPlanPart(workspaceDir) {
98
+ export function newestPlanFamilyFiles(workspaceDir) {
106
99
  let names;
107
100
  try {
108
101
  names = readdirSync(workspaceDir);
109
102
  }
110
103
  catch {
111
- return null; // workspace missing — nothing to recap
104
+ return []; // workspace missing
112
105
  }
113
106
  const re = /^(.+)-v(\d+)(?:-(\d+))?\.cspeach\.json$/;
114
107
  const cands = [];
@@ -124,17 +117,10 @@ function buildPlanPart(workspaceDir) {
124
117
  continue;
125
118
  }
126
119
  const m = re.exec(name);
127
- if (m) {
128
- cands.push({ path, family: m[1], version: Number(m[2]), sub: m[3] ? Number(m[3]) : 0, mtimeMs });
129
- }
130
- else {
131
- // Hand-renamed / version-less envelope: its own single-file family.
132
- cands.push({ path, family: name.slice(0, -'.cspeach.json'.length), version: 0, sub: 0, mtimeMs });
133
- }
120
+ cands.push(m
121
+ ? { path, family: m[1], version: Number(m[2]), sub: m[3] ? Number(m[3]) : 0, mtimeMs }
122
+ : { path, family: name.slice(0, -'.cspeach.json'.length), version: 0, sub: 0, mtimeMs });
134
123
  }
135
- if (cands.length === 0)
136
- return null;
137
- // Newest version per family (version, then collision sub, then mtime).
138
124
  const byFamily = new Map();
139
125
  for (const c of cands) {
140
126
  const cur = byFamily.get(c.family);
@@ -144,9 +130,13 @@ function buildPlanPart(workspaceDir) {
144
130
  byFamily.set(c.family, c);
145
131
  }
146
132
  }
147
- // Families newest-first by their representative file's mtime; the first
148
- // family whose envelope IS a plan is "the newest plan family".
149
- const familiesNewestFirst = [...byFamily.values()].sort((a, b) => b.mtimeMs - a.mtimeMs);
133
+ return [...byFamily.values()].sort((a, b) => b.mtimeMs - a.mtimeMs);
134
+ }
135
+ function buildPlanPart(workspaceDir) {
136
+ // The first family whose envelope IS a plan is "the newest plan family".
137
+ const familiesNewestFirst = newestPlanFamilyFiles(workspaceDir);
138
+ if (familiesNewestFirst.length === 0)
139
+ return null;
150
140
  for (const cand of familiesNewestFirst) {
151
141
  let env;
152
142
  try {