@cspeach/cli 1.1.4 → 1.1.6

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.
@@ -25,7 +25,13 @@ export async function dispatchTool(name, args, ctx) {
25
25
  // Rule 8 — no batch without plan. 2nd+ mutating call this turn requires
26
26
  // explicit confirmation. Fires regardless of /safety-mode toggle (batch
27
27
  // detection is unconditional per Plan 3 §6.6).
28
- if (tool.isMutating && shouldGateRule8()) {
28
+ // Scratch-output exemption (2026-09-16): files under ./cspeach-out/ are the
29
+ // standalone Manual Implementation Guide outputs (NN-<OBJECT>.<ext>,
30
+ // GUIDE.md) — local scratch, not SAP objects. Rule 8 protects SAP batches;
31
+ // applying it here made the 2nd+ file (GUIDE.md) hit the batch card and,
32
+ // headless, get refused. Exempt from the gate AND from the batch count.
33
+ const scratchWrite = isScratchOutputWrite(tool, args);
34
+ if (tool.isMutating && !scratchWrite && shouldGateRule8()) {
29
35
  const r = await presentSafetyConfirmation({
30
36
  rule: 'RULE_8_BATCH_PLAN',
31
37
  op: tool.name,
@@ -67,7 +73,7 @@ export async function dispatchTool(name, args, ctx) {
67
73
  // Always record AFTER successful mutating dispatch — applies to 1st and
68
74
  // 2nd+ writes alike. The 1st write seeds the counter; without this the
69
75
  // gate would never fire on the 2nd write.
70
- if (tool.isMutating && !(result.is_error ?? false)) {
76
+ if (tool.isMutating && !scratchWrite && !(result.is_error ?? false)) {
71
77
  recordWriteOp(tool.name, args);
72
78
  }
73
79
  return { content: result.content, is_error: result.is_error ?? false };
@@ -76,3 +82,22 @@ export async function dispatchTool(name, args, ctx) {
76
82
  return { content: JSON.stringify({ error: String(err) }), is_error: true };
77
83
  }
78
84
  }
85
+ /**
86
+ * True for a mutating filesystem tool whose target sits under ./cspeach-out/
87
+ * (relative to cwd). Accepts `cspeach-out/…`, `./cspeach-out/…` and the
88
+ * Windows backslash form; rejects anything that climbs out (`..`) or is
89
+ * absolute. Deliberately narrow: only this one folder is exempt.
90
+ */
91
+ export function isScratchOutputWrite(tool, args) {
92
+ if (!tool.isMutating || tool.category !== 'filesystem')
93
+ return false;
94
+ const raw = args?.path;
95
+ if (typeof raw !== 'string' || raw.length === 0)
96
+ return false;
97
+ const p = raw.replace(/\\/g, '/').replace(/^\.\//, '');
98
+ if (p.startsWith('/') || /^[A-Za-z]:\//.test(p))
99
+ return false;
100
+ if (p.split('/').some((seg) => seg === '..'))
101
+ return false;
102
+ return p === 'cspeach-out' || p.startsWith('cspeach-out/');
103
+ }
@@ -108,7 +108,12 @@ export function formatFactBlock(facts, sapSystemBlock = '') {
108
108
  if (facts.objectNames && facts.objectNames.length > 0) {
109
109
  lines.push(` <object_names>${facts.objectNames.join(', ')}</object_names>`);
110
110
  }
111
- if (facts.environmentHint) {
111
+ // 2026-09-16: the environment hint is a regex guess over the prompt text.
112
+ // When a real system block is present (live probe, or the standalone
113
+ // profile the user answered), that block is authoritative — a stray
114
+ // "ABAP Cloud" in the prompt must not contradict it. Only emit the hint
115
+ // when there is no <sap_system …/> element to defer to.
116
+ if (facts.environmentHint && !sapSystemBlock.includes('<sap_system')) {
112
117
  lines.push(` <environment_hint>${facts.environmentHint}</environment_hint>`);
113
118
  }
114
119
  if (facts.createIntent)
@@ -218,13 +218,13 @@ export function renderStandaloneSapSystemBlock(profile) {
218
218
  */
219
219
  export const STANDALONE_NOTE = '<standalone_note>No SAP system is connected (standalone mode). Do not call SAP tools. ' +
220
220
  'Work from code the user pastes or describes; when a task needs system data (source, table definitions, ATC output, dumps), ask the user to paste it. ' +
221
- 'Target the platform and release in sap_system exactly — use only syntax and APIs available on that release. ' +
221
+ 'Package, transport and system facts the user states are confirmed facts in standalone mode: record them, never re-validate them, and never treat "validated to exist" as a gate to check. When resuming a plan, do not re-open a phase whose gate only lacks a system check; note the waiver once and move to the next phase. Target the platform and release in sap_system exactly — use only syntax and APIs available on that release. ' +
222
222
  'Because nothing can be created or activated here, every task that would normally write to SAP must instead end with a "Manual Implementation Guide": ' +
223
223
  'numbered steps in dependency order, one object per step, each stating the object type and name, where to create it (ADT wizard path, or SE80/SE11/SE24/SE38 transaction for SE80 teams), ' +
224
224
  'the exact source to paste, the activation step, and any prerequisite (package, transport, number range, message class). ' +
225
225
  'Finish with a short verification checklist (syntax check, activation, ATC, unit test) the user can run themselves.\n' +
226
- 'File outputs: when file tools are available (local build is on), also write every produced object to the working folder as ./cspeach-out/<yyyy-mm-dd>-<short-task-name>/NN-<OBJECT_NAME>.<abap|ddls|bdef|srvd|srvb|ddlx|dcls|txt> in dependency order, and write the Manual Implementation Guide to GUIDE.md in the same folder, with each step pointing at its file. Tell the user the folder path once. If file tools are not available, say so once and keep everything in the reply.\n' +
227
- 'Step failures: when the user reports a failure at a step ("step 3 failed" plus an error message or screenshot), ask only for what is missing to diagnose it, fix only the affected object, rewrite its file, re-issue that step and any step that depends on it, and state which steps are unchanged. Never restart the whole guide.</standalone_note>';
226
+ 'File outputs: when file tools are available (local build is on), you MUST write every produced object to the working folder as ./cspeach-out/<yyyy-mm-dd>-<short-task-name>/NN-<OBJECT_NAME>.<abap|ddls|bdef|srvd|srvb|ddlx|dcls|txt> in dependency order, and you MUST write the Manual Implementation Guide to GUIDE.md in the same folder before finishing the reply — each step pointing at its file and carrying a status (pending, done, failed, fixed). Tell the user the folder path once. If file tools are not available, say so once and keep everything in the reply. Analysis outputs (spec-gap reports, designs, estimates, reviews, explanations) stay IN THE REPLY in their full standard structure — CSPeach saves their canonical envelope files itself from the reply; write them to a file only as an additional copy, never instead, and never hand-write a .cspeach.json envelope.\n' +
227
+ 'Step failures: when the user reports a failure at a step ("step 3 failed" plus an error message or screenshot), first re-read GUIDE.md and the affected object file from disk so you work from the exact current state, then ask only for what is missing to diagnose it, fix only the affected object, rewrite its file, update that step in GUIDE.md (status and what changed), re-issue that step and any step that depends on it, and state which steps are unchanged. If the user pastes an error without naming a step, identify the object from the error and confirm it in one line. Never restart the whole guide.</standalone_note>';
228
228
  /**
229
229
  * The full standalone contribution to `<session_context>`: the sap_system
230
230
  * element followed by the hand-off note.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cspeach/cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, 34 skills, safety gates.",
5
5
  "keywords": [
6
6
  "abap",