@7n/rules 1.6.0 → 1.7.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.0] - 2026-07-16
4
+
5
+ ### Changed
6
+
7
+ - `skill cursor/codex` тепер виконуються через `@7n/llm-lib/acp` (napi-міст до Rust `llm_cascade::acp`) замість власного JS ACP-клієнта; deprecated `skill claude` лишається окремим JS-шимом (`acp-runner.mjs`), бо Rust-крейт `claude` не моделює
8
+
3
9
  ## [1.6.0] - 2026-07-16
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -85,7 +85,6 @@
85
85
  },
86
86
  "optionalDependencies": {
87
87
  "@agentclientprotocol/claude-agent-acp": "^0.59.0",
88
- "@agentclientprotocol/codex-acp": "^1.1.2",
89
88
  "@earendil-works/pi-ai": "0.80.2",
90
89
  "@earendil-works/pi-coding-agent": "0.80.2"
91
90
  },
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: skills-cli.mjs
4
4
  resource: npm/scripts/skills-cli.mjs
5
5
  docgen:
6
- crc: ce3d7d60
6
+ crc: 1a8f83e4
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -30,7 +30,7 @@ runSkillsCli виконує логіку командного інтерфейс
30
30
 
31
31
  ## Гарантії поведінки
32
32
 
33
- - Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або зовнішньому ACP-агенту `cursor`/`codex`/`claude` через `runAcpRunner` (`./lib/acp-runner.mjs`) — JSON-RPC поверх stdio (`cursor-agent acp`, вбудовані адаптери `@agentclientprotocol/codex-acp` і `@agentclientprotocol/claude-agent-acp`), а не сирий `stdin`/`stdout`-pipe-передавання.
33
+ - Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або зовнішньому ACP-агенту `cursor`/`codex`/`claude` через `runAcpRunner` (`./lib/acp-runner.mjs`) — JSON-RPC поверх stdio (`cursor-agent acp`, бандловані адаптери `@agentclientprotocol/codex-acp` і `@agentclientprotocol/claude-agent-acp`), а не сирий `stdin`/`stdout`-піпінг.
34
34
  - Тира моделі для `pi`-runner береться з `main.json.tier` скіла (дефолт `max`).
35
- - ACP-раннер автоматично схвалює `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
35
+ - ACP-раннер автоапрувляє `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
36
36
  - Лише `claude` — deprecated: друкує попередження й лишається як fallback, доки не налаштовано pi-модель. `cursor`/`codex` — повноцінні раннери без попередження.
@@ -1,18 +1,18 @@
1
1
  /**
2
- * Виконання скіла через зовнішнього ACP-агента (Agent Client Protocol,
3
- * agentclientprotocol.com) — JSON-RPC поверх stdio замість сирого
4
- * `stdin`/`stdout`-передавання через pipe CLI.
2
+ * Deprecated JS ACP-шим для `skill claude` (Agent Client Protocol,
3
+ * agentclientprotocol.com) — JSON-RPC поверх stdio.
5
4
  *
6
- * Заміняє колишній `runLlmCli` (spawnSync + `-p`/`exec -`) для `cursor`/`codex`/`claude`:
7
- * підʼєднання йде через офіційний TS SDK `@zed-industries/agent-client-protocol`,
8
- * а дозволи на tool-calls (`session/request_permission`) автоматично схвалюються без участі
9
- * людини паритет із сьогоднішнім non-interactive `-p`-режимом (full user-trust,
10
- * без write-guard, як задокументовано в `@7n/llm-lib/agent-skill`).
5
+ * `cursor`/`codex` мігрували на `@7n/llm-lib/acp` (napi-міст до
6
+ * `llm_cascade::acp` та сама протокольна логіка, але в Rust, без
7
+ * дублювання в JS). `claude` лишається тут як окремий JS-шим, бо Rust-крейт
8
+ * `AcpAgentKind` його не моделює (лише `Cursor`/`Codex`); підʼєднання йде
9
+ * через офіційний TS SDK `@zed-industries/agent-client-protocol`, а дозволи
10
+ * на tool-calls (`session/request_permission`) автоматично схвалюються без
11
+ * участі людини — паритет із колишнім non-interactive `-p`-режимом
12
+ * (full user-trust, без write-guard, як задокументовано в `@7n/llm-lib/agent-skill`).
11
13
  *
12
- * `cursor` — нативний ACP-режим самого `cursor-agent` (`cursor-agent acp`).
13
- * `codex`/`claude` офіційні адаптери (`@agentclientprotocol/codex-acp`,
14
- * `@agentclientprotocol/claude-agent-acp`), що вбудовують свій рушій — зовнішній
15
- * бінарник `codex`/`claude` у PATH більше не потрібен.
14
+ * `claude` — офіційний адаптер (`@agentclientprotocol/claude-agent-acp`), що
15
+ * вбудовує свій рушій — зовнішній бінарник `claude` у PATH не потрібен.
16
16
  */
17
17
 
18
18
  import { spawn } from 'node:child_process'
@@ -25,13 +25,11 @@ import { Readable, Writable } from 'node:stream'
25
25
  const require = createRequire(import.meta.url)
26
26
 
27
27
  /**
28
- * Команда запуску ACP-агента на провайдер. `cursor` зовнішній бінарник у PATH;
29
- * `codex`/`claude` — резолвляться з `bin`-запису вбудованого npm-пакета-адаптера.
30
- * @type {Record<'cursor' | 'codex' | 'claude', { bin: string, args: string[] } | { adapterPackage: string }>}
28
+ * Команда запуску ACP-агента на провайдер. Резолвиться з `bin`-запису
29
+ * вбудованого npm-пакета-адаптера.
30
+ * @type {Record<'claude', { adapterPackage: string }>}
31
31
  */
32
32
  export const ACP_AGENT_COMMANDS = {
33
- cursor: { bin: 'cursor-agent', args: ['acp'] },
34
- codex: { adapterPackage: '@agentclientprotocol/codex-acp' },
35
33
  claude: { adapterPackage: '@agentclientprotocol/claude-agent-acp' }
36
34
  }
37
35
 
@@ -46,7 +44,7 @@ export function stopReasonToExitCode(stopReason) {
46
44
 
47
45
  /**
48
46
  * Резолвить абсолютний шлях до bin-файлу вбудованого ACP-адаптера з його `package.json`.
49
- * @param {string} adapterPackage назва npm-пакета адаптера (`@agentclientprotocol/codex-acp`, …)
47
+ * @param {string} adapterPackage назва npm-пакета адаптера (`@agentclientprotocol/claude-agent-acp`)
50
48
  * @returns {string} абсолютний шлях до виконуваного файлу адаптера
51
49
  */
52
50
  export function resolveAdapterBin(adapterPackage) {
@@ -121,12 +119,13 @@ class AcpSkillClient {
121
119
  }
122
120
 
123
121
  /**
124
- * Виконує скіл через зовнішнього ACP-агента (`cursor`/`codex`/`claude`).
125
- * @param {'cursor' | 'codex' | 'claude'} kind провайдер
122
+ * Виконує скіл через зовнішнього ACP-агента. Єдиний лишений провайдер —
123
+ * deprecated `claude` (`cursor`/`codex` див. `@7n/llm-lib/acp`).
124
+ * @param {'claude'} kind провайдер
126
125
  * @param {string} prompt готовий промпт скіла
127
126
  * @param {string} projectDir робочий каталог сесії агента
128
127
  * @param {(line: string) => void} logError вивід помилок
129
- * @param {{ acp?: object, spawnFn?: typeof spawn, out?: (chunk: string) => void, resolveAdapterBin?: (pkg: string) => string, isBinaryInPath?: (bin: string) => boolean }} [deps] інжекти для тестів
128
+ * @param {{ acp?: object, spawnFn?: typeof spawn, out?: (chunk: string) => void, resolveAdapterBin?: (pkg: string) => string }} [deps] інжекти для тестів
130
129
  * @returns {Promise<number>} exit code (0 — `end_turn`, 1 — інакше)
131
130
  */
132
131
  export async function runAcpRunner(kind, prompt, projectDir, logError, deps = {}) {
@@ -134,13 +133,9 @@ export async function runAcpRunner(kind, prompt, projectDir, logError, deps = {}
134
133
  const out = deps.out ?? (chunk => stdout.write(chunk))
135
134
  const resolveBin = deps.resolveAdapterBin ?? resolveAdapterBin
136
135
 
137
- const command = ACP_AGENT_COMMANDS[kind]
138
- const { bin, args } =
139
- 'adapterPackage' in command ? { bin: process.execPath, args: [resolveBin(command.adapterPackage)] } : command
140
-
141
- if ('bin' in command && deps.isBinaryInPath && !deps.isBinaryInPath(command.bin)) {
142
- throw new Error(`\`${command.bin}\` not found in PATH. Install ${kind} CLI (ACP mode) or use \`skill pi\`.`)
143
- }
136
+ const { adapterPackage } = ACP_AGENT_COMMANDS[kind]
137
+ const bin = process.execPath
138
+ const args = [resolveBin(adapterPackage)]
144
139
 
145
140
  const acp = deps.acp ?? (await import('@zed-industries/agent-client-protocol'))
146
141
  const child = spawnFn(bin, args, { cwd: projectDir, stdio: ['pipe', 'pipe', 'inherit'], env: process.env })
@@ -4,7 +4,9 @@
4
4
  * Скіли читаються з `npm/skills/<id>/SKILL.md` установленого пакета (або кешу `npx`).
5
5
  * Промпт збирає інструкцію скілу + контекст поточного CWD (`package.json`, `tsconfig.json`,
6
6
  * `.n-rules.json`) — далі stdout або виконання через один з раннерів: вбудований
7
- * pi-агент, чи зовнішній ACP-агент (`cursor-agent acp`, `codex-acp`, або deprecated `claude`).
7
+ * pi-агент, чи зовнішній ACP-агент. `cursor`/`codex` через `@7n/llm-lib/acp`
8
+ * (napi-міст до `llm_cascade::acp`, без власного JSON-RPC у JS); deprecated
9
+ * `claude` — окремий JS-шим (`./lib/acp-runner.mjs`), бо Rust-крейт його не моделює.
8
10
  *
9
11
  * Підтримувані формати:
10
12
  * `npx \@7n/rules skill list`
@@ -12,14 +14,14 @@
12
14
  * `npx \@7n/rules skill pi taze` — виконати через вбудований pi-агент (рекомендовано)
13
15
  * `npx \@7n/rules skill pi taze "онови залежності"`
14
16
  * `npx \@7n/rules skill cursor taze` — Cursor CLI через ACP (`cursor-agent acp`)
15
- * `npx \@7n/rules skill codex taze` — Codex через ACP-адаптер (`@agentclientprotocol/codex-acp`)
17
+ * `npx \@7n/rules skill codex taze` — Codex через ACP (napi-міст `@7n/llm-lib/acp`, Rust спавнить `npx \@agentclientprotocol/codex-acp`)
16
18
  * `npx \@7n/rules skill claude taze` — deprecated: Claude Code через ACP-адаптер
17
19
  */
18
20
 
19
- import { spawnSync } from 'node:child_process'
21
+ import { runAcpAgent } from '@7n/llm-lib/acp'
20
22
  import { existsSync, readFileSync, readdirSync } from 'node:fs'
21
23
  import { dirname, join } from 'node:path'
22
- import { cwd } from 'node:process'
24
+ import { cwd, stdout } from 'node:process'
23
25
  import { fileURLToPath } from 'node:url'
24
26
 
25
27
  import { runAcpRunner } from './lib/acp-runner.mjs'
@@ -28,7 +30,11 @@ import { readSkillMetaRaw, skillTier } from './lib/skill-meta.mjs'
28
30
  /** Виконавці скіла. `pi` — вбудований (рекомендований); `cursor`/`codex`/`claude` — зовнішні ACP-агенти (`claude` — deprecated). */
29
31
  const RUNNERS = new Set(['pi', 'cursor', 'codex', 'claude'])
30
32
 
31
- /** Раннери, що йдуть через зовнішнього ACP-агента (`runAcpRunner`), і чи deprecated (друкує попередження перед запуском). */
33
+ /**
34
+ * Раннери, що йдуть через зовнішнього ACP-агента, і чи deprecated (друкує попередження
35
+ * перед запуском). `cursor`/`codex` — napi-міст `@7n/llm-lib/acp`; `claude` — окремий
36
+ * JS-шим `runAcpRunner` (Rust-крейт `llm_cascade::acp` `claude` не моделює).
37
+ */
32
38
  const ACP_RUNNERS = {
33
39
  claude: { deprecated: true },
34
40
  cursor: { deprecated: false },
@@ -47,17 +53,6 @@ const USAGE_LINES = [
47
53
  'Skill id: каталог у пакеті (lint, taze, …) або з префіксом n- (n-lint → lint).'
48
54
  ]
49
55
 
50
- /**
51
- * @param {string} name ім'я бінарника
52
- * @returns {boolean} чи знайдено бінарник у PATH
53
- */
54
- function isBinaryInPath(name) {
55
- // Явний `env: process.env` — Bun без нього дає дітям snapshot оточення зі старту процесу
56
- // і не бачить runtime-змін `process.env.PATH` (та сама причина, що в resolve-cmd.mjs).
57
- const probe = spawnSync('command', ['-v', name], { shell: true, encoding: 'utf8', env: process.env })
58
- return probe.status === 0
59
- }
60
-
61
56
  /**
62
57
  * @param {string} name ім'я скілу з CLI або каталогу `.cursor/skills/n-*`
63
58
  * @returns {string} id каталогу в `npm/skills/<id>/`
@@ -170,27 +165,41 @@ async function runPiRunner(prompt, rawSkillName, skillsRoot, projectDir, logErro
170
165
  }
171
166
 
172
167
  /**
173
- * Делегує виконання скіла зовнішньому ACP-агенту (`cursor-agent acp`, `codex-acp`,
174
- * або deprecated `claude`-адаптер) через `runAcpRunner` — JSON-RPC поверх stdio,
175
- * дозволи на tool-calls автоматично схвалюються (без інтерактивних питань, паритет із
176
- * колишнім non-interactive `-p`). `claude` лишається як deprecated fallback для
177
- * тих, у кого pi-модель ще не налаштована; буде прибрано (мігруй на `skill pi`).
168
+ * Делегує виконання скіла зовнішньому ACP-агенту. `cursor`/`codex` через
169
+ * `@7n/llm-lib/acp` (napi-міст до `llm_cascade::acp`: спавн, `session/prompt`,
170
+ * автоапрув дозволів усе в Rust, без JSON-RPC у JS). `claude` — deprecated
171
+ * JS-шим `runAcpRunner` (Rust його не моделює); буде прибрано (мігруй на `skill pi`).
172
+ * На відміну від колишнього стрімінгу по чанках, napi-шлях повертає повний текст
173
+ * лише по завершенню ходу — Rust-функція не стрімить проміжні токени в JS.
178
174
  * @param {'claude' | 'cursor' | 'codex'} kind якого ACP-агента запускати
179
175
  * @param {string} prompt промпт скіла
180
176
  * @param {string} projectDir робочий каталог агента
181
177
  * @param {(line: string) => void} logError вивід попередження/помилок
182
- * @param {{ runAcpRunner?: typeof runAcpRunner }} [deps] інжект для тестів
183
- * @returns {Promise<number>} exit code (0 — `end_turn`, 1 — інакше)
178
+ * @param {{ runAcpRunner?: typeof runAcpRunner, runAcpAgent?: typeof runAcpAgent, out?: (chunk: string) => void }} [deps] інжекти для тестів
179
+ * @returns {Promise<number>} exit code (0 — успіх, 1 — інакше)
184
180
  */
185
- function runLlmCli(kind, prompt, projectDir, logError, deps = {}) {
181
+ async function runLlmCli(kind, prompt, projectDir, logError, deps = {}) {
186
182
  const runner = ACP_RUNNERS[kind]
187
- const runAcp = deps.runAcpRunner ?? runAcpRunner
188
183
 
189
184
  if (runner.deprecated) {
190
185
  logError(`[deprecated] skill ${kind} → use 'skill pi'; зовнішній ACP-агент буде прибрано`)
191
186
  }
192
187
 
193
- return runAcp(kind, prompt, projectDir, logError, { isBinaryInPath })
188
+ if (kind === 'claude') {
189
+ const runAcp = deps.runAcpRunner ?? runAcpRunner
190
+ return runAcp(kind, prompt, projectDir, logError)
191
+ }
192
+
193
+ const runNativeAcp = deps.runAcpAgent ?? runAcpAgent
194
+ const out = deps.out ?? (chunk => stdout.write(chunk))
195
+
196
+ try {
197
+ out(await runNativeAcp(kind, prompt, projectDir))
198
+ return 0
199
+ } catch (error) {
200
+ logError(error instanceof Error ? error.message : String(error))
201
+ return 1
202
+ }
194
203
  }
195
204
 
196
205
  /**