@cspeach/cli 1.1.29 → 1.1.30

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 (38) hide show
  1. package/dist/agent/loop.js +6 -3
  2. package/dist/auth/auth-file.js +2 -2
  3. package/dist/cli.js +4 -0
  4. package/dist/commands/login.js +1 -1
  5. package/dist/commands/logout.js +1 -1
  6. package/dist/commands/team.js +298 -26
  7. package/dist/commands/whoami.js +1 -1
  8. package/dist/config/paths.js +15 -1
  9. package/dist/one-shot.js +1 -0
  10. package/dist/projects/clean-core-confirm.js +36 -0
  11. package/dist/projects/extract-clean-core.js +79 -0
  12. package/dist/projects/save-command.js +38 -9
  13. package/dist/projects/validate.js +2 -1
  14. package/dist/projects/workspace.js +2 -1
  15. package/dist/register/atc-file.js +9 -1
  16. package/dist/register/findings.js +79 -0
  17. package/dist/register/measure.js +60 -1
  18. package/dist/register/offer.js +33 -0
  19. package/dist/register/picture.js +21 -0
  20. package/dist/register/records.js +32 -0
  21. package/dist/register/status-file/register-map.js +213 -0
  22. package/dist/register/status-file/register-model.js +307 -0
  23. package/dist/register/status-file/register-parts.js +509 -0
  24. package/dist/register/status-file/register-status-file.js +171 -0
  25. package/dist/register/status-file/team-css.js +5 -0
  26. package/dist/register/status-file-sync.js +19 -0
  27. package/dist/register/status-html.js +17 -0
  28. package/dist/register/workbooks.js +104 -0
  29. package/dist/register/write-whole.js +22 -0
  30. package/dist/register/xlsx.js +117 -0
  31. package/dist/renderer/tool-labels.js +2 -1
  32. package/dist/repl.js +1 -0
  33. package/dist/skills/bundled-skills.js +45 -45
  34. package/dist/tools/_filesystem-shared.js +16 -9
  35. package/dist/tools/project/team_findings.js +72 -0
  36. package/dist/tools/sap-write.js +8 -0
  37. package/dist/tools/tool-loading.js +1 -1
  38. package/package.json +2 -2
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import * as path from 'node:path';
11
11
  import { promises as fsPromises, realpathSync, existsSync } from 'node:fs';
12
- import { cspeachRoot } from '../config/paths.js';
12
+ import { cspeachRoot, defaultCspeachRoot } from '../config/paths.js';
13
13
  /**
14
14
  * Phase 3 sensitive in-root directory prefixes — refused by all filesystem
15
15
  * tools. Matched as `rel === prefix` or `rel` starts with `prefix + '/'`.
@@ -229,18 +229,25 @@ function relIsInside(p, rel) {
229
229
  return rel === '' || (!rel.startsWith('..') && !p.isAbsolute(rel));
230
230
  }
231
231
  /**
232
- * True if `absPath` is the CLI home (`cspeachRoot()`, `~/.cspeach`) or below
233
- * it — auth tokens, config, sessions, snapshots, logs. Checked against the
232
+ * True if `absPath` is the CLI home (`cspeachRoot()`) or `~/.cspeach`, or below
233
+ * either — auth tokens, config, sessions, snapshots, logs. Checked against the
234
234
  * spelled and the real home, and through NTFS spellings of the `.cspeach`
235
235
  * segment (`.cspeach.`, `.cspeach `), case-insensitively.
236
236
  */
237
237
  function isUnderCliHome(absPath, p, native) {
238
- const home = cspeachRoot();
239
- const homes = [p.resolve(home)];
240
- if (native) {
241
- const real = realDirOrNull(home);
242
- if (real && real !== homes[0])
243
- homes.push(real);
238
+ // Both homes: the one CSPEACH_HOME chose AND ~/.cspeach, which still holds
239
+ // history, logs, streams — and maybe an auth.json from before the move.
240
+ const chosen = [cspeachRoot(), defaultCspeachRoot()];
241
+ const homes = [];
242
+ for (const home of chosen) {
243
+ const spelled = p.resolve(home);
244
+ if (!homes.includes(spelled))
245
+ homes.push(spelled);
246
+ if (native) {
247
+ const real = realDirOrNull(home);
248
+ if (real && !homes.includes(real))
249
+ homes.push(real);
250
+ }
244
251
  }
245
252
  for (const h of homes) {
246
253
  const fromParent = p.relative(p.dirname(h), absPath).replace(/\\/g, '/');
@@ -0,0 +1,72 @@
1
+ /**
2
+ * team_findings — read-only (spec 2026-10-06 §4.2, triage).
3
+ *
4
+ * What a /abap-clean-core triage proposes from: the objects of a lane (or of
5
+ * package patterns) with the findings measurement zero recorded for them, read
6
+ * from the team project this laptop joined. No SAP call, no second ATC run, no
7
+ * write: every decision is still recorded by program code, one Yes per row.
8
+ */
9
+ import { dispositionOf, DISPOSITION_WORD, inLane } from '@cspeach/register-core';
10
+ import { registerTool } from '../index.js';
11
+ import { findingsByObject, shownUpTo } from '../../register/findings.js';
12
+ import { registerFor } from '../../register/picture.js';
13
+ import { activeProject, readProjectFile } from '../../register/store.js';
14
+ const CAP = 200;
15
+ /** A decision's reason was written on another laptop and goes to the model: cleaned like a finding title, kept whole up to 300. */
16
+ const REASON_CAP = 300;
17
+ const match = (pattern, value) => new RegExp(`^${pattern.trim().replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`, 'i').test(value);
18
+ export async function teamFindingsHandler(args, _ctx) {
19
+ const joined = activeProject();
20
+ if (!joined)
21
+ return { content: 'This laptop has not joined a team project. The developer runs: cspeach team join <folder>', is_error: true };
22
+ try {
23
+ const project = readProjectFile(joined.projectDir);
24
+ const { register } = registerFor(joined.projectDir, joined.outboxDir);
25
+ const laneId = args.lane?.trim();
26
+ const patterns = (args.packages ?? '').split(',').map((s) => s.trim()).filter(Boolean);
27
+ let objects = register.objects;
28
+ let scope = 'all objects';
29
+ if (laneId) {
30
+ const lane = register.lanes.find((l) => l.id.toLowerCase() === laneId.toLowerCase());
31
+ if (!lane)
32
+ return { content: `There is no lane "${laneId}". Lanes: ${register.lanes.map((l) => l.id).join(', ') || 'none yet'}.`, is_error: true };
33
+ objects = objects.filter((o) => inLane(o, lane.id));
34
+ scope = `lane ${lane.id}`;
35
+ }
36
+ else if (patterns.length) {
37
+ objects = objects.filter((o) => patterns.some((p) => match(p, o.package)));
38
+ scope = `packages ${patterns.join(', ')}`;
39
+ }
40
+ const findings = findingsByObject(joined.projectDir, joined.outboxDir);
41
+ const shown = objects.slice(0, CAP).map((o) => {
42
+ const f = findings.get(o.key);
43
+ const d = dispositionOf(o.decision);
44
+ return {
45
+ type: o.type, name: o.name, package: o.package,
46
+ owner: register.lanes.find((l) => l.id === o.lane)?.owner ?? null,
47
+ decided: d ? { disposition: DISPOSITION_WORD[d], level: o.decision.level ?? null, targetLevel: o.decision.targetLevel ?? null, reason: typeof o.decision.reason === 'string' ? shownUpTo(o.decision.reason, REASON_CAP) : null } : null,
48
+ baselineAt: f?.baselineAt ?? null, baseline: f?.baseline ?? [], openNow: o.openFindings,
49
+ };
50
+ });
51
+ return { content: JSON.stringify({ teamProject: project.name, scope, objects: shown, ...(objects.length > CAP ? { more: objects.length - CAP } : {}) }) };
52
+ }
53
+ catch (e) {
54
+ return { content: `The team project could not be read: ${e instanceof Error ? e.message : String(e)}`, is_error: true };
55
+ }
56
+ }
57
+ registerTool({
58
+ name: 'team_findings',
59
+ description: 'Read-only. The objects of a lane (or package patterns) in the team project this laptop joined, with the findings '
60
+ + 'measurement zero recorded for each and any decision already recorded. Use it for a /abap-clean-core triage. No SAP call.',
61
+ isMutating: false,
62
+ category: 'project',
63
+ requiresSap: false,
64
+ input_schema: {
65
+ type: 'object',
66
+ properties: {
67
+ lane: { type: 'string', description: 'Lane id, e.g. "fin".' },
68
+ packages: { type: 'string', description: 'Package patterns, comma-separated, e.g. "ZCC_DEMO_*". Ignored when lane is given.' },
69
+ },
70
+ },
71
+ handler: (args, ctx) => teamFindingsHandler(args, ctx),
72
+ });
@@ -34,6 +34,7 @@ import { getLook } from '../renderer/look.js';
34
34
  import { getEffectiveBodyWidth } from '../renderer/tty.js';
35
35
  import { verifySyntax, verifyActive } from './verify.js';
36
36
  import { hasRecordedSyntaxErrorsByName, clearSyntaxErrorsAfterActivation } from './syntax-state.js';
37
+ import { teamFixOffers } from '../register/offer.js';
37
38
  import { appendPending, finalizeToolCall } from '../session/pending.js';
38
39
  import { auditLog } from '@cspeach/sap-client';
39
40
  import { ERR } from '../errors/codes.js';
@@ -1314,6 +1315,7 @@ registerTool({
1314
1315
  // still inactive with syntax errors" incident.
1315
1316
  const stillInactive = [];
1316
1317
  const unverifiable = [];
1318
+ const verified = [];
1317
1319
  for (const obj of args.objects) {
1318
1320
  const v = await verifyActive(ctx.adt, obj.name, obj.type);
1319
1321
  if (v.verifyError) {
@@ -1325,6 +1327,7 @@ registerTool({
1325
1327
  else if (activationResults.find((r) => r.name === obj.name && r.type === obj.type)?.success) {
1326
1328
  // Final review A M-2 — activated AND verified: the source compiles.
1327
1329
  clearSyntaxErrorsAfterActivation(obj.name);
1330
+ verified.push({ name: obj.name, type: obj.type });
1328
1331
  }
1329
1332
  }
1330
1333
  const overallSuccess = !anyError && stillInactive.length === 0 && unverifiable.length === 0;
@@ -1342,6 +1345,11 @@ registerTool({
1342
1345
  // successful write op. Without this, Rule 8 counts a failed activate
1343
1346
  // as if it had run, inflating the "batch in progress" count and
1344
1347
  // confusing the user about how many real writes have happened.
1348
+ // 2026-10-06 — an object of the team project this laptop joined: offer the
1349
+ // claim in one line. Nothing is recorded; only the developer claims a fix.
1350
+ if (overallSuccess)
1351
+ for (const line of teamFixOffers(verified))
1352
+ ctx.chunkEmitter?.emit('info', line);
1345
1353
  return {
1346
1354
  content: JSON.stringify({
1347
1355
  success: overallSuccess,
@@ -57,7 +57,7 @@ export const SKILL_STARTERS = Object.freeze({
57
57
  'abap-app': ['app_lane_resolve', 'read_document'],
58
58
  'abap-atc-fix': ['sap_atc_run'],
59
59
  'abap-cca': ['sap_atc_run', 'sap_usage_references', 'sap_api_state'],
60
- 'abap-clean-core': ['sap_api_state', 'sap_atc_run', 'sap_usage_references'],
60
+ 'abap-clean-core': ['sap_api_state', 'sap_atc_run', 'sap_usage_references', 'team_findings'],
61
61
  'abap-data-model': ['sap_create_domain', 'sap_create_data_element'],
62
62
  'abap-document': ['sap_usage_references'],
63
63
  'abap-handover': ['sap_usage_references'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cspeach/cli",
3
- "version": "1.1.29",
3
+ "version": "1.1.30",
4
4
  "description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, 34 skills, safety gates.",
5
5
  "keywords": [
6
6
  "abap",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@anthropic-ai/sdk": "^0.90.0",
55
- "@cspeach/register-core": "0.1.0",
55
+ "@cspeach/register-core": "0.2.0",
56
56
  "@cspeach/sap-client": "0.1.16",
57
57
  "@iarna/toml": "^2.2.5",
58
58
  "@inkjs/ui": "^2.0.0",