kyro-ai 3.3.2 → 3.4.1
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/.claude-plugin/plugin.json +1 -1
- package/WORKFLOW.yaml +1 -1
- package/dist/cli/adapters/claude.d.ts +3 -0
- package/dist/cli/adapters/claude.d.ts.map +1 -0
- package/dist/cli/adapters/claude.js +98 -0
- package/dist/cli/adapters/claude.js.map +1 -0
- package/dist/cli/adapters/cursor.d.ts +3 -0
- package/dist/cli/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/adapters/cursor.js +74 -0
- package/dist/cli/adapters/cursor.js.map +1 -0
- package/dist/cli/adapters/registry.d.ts.map +1 -1
- package/dist/cli/adapters/registry.js +4 -52
- package/dist/cli/adapters/registry.js.map +1 -1
- package/dist/cli/app.d.ts.map +1 -1
- package/dist/cli/app.js +11 -0
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/adapter.d.ts +4 -0
- package/dist/cli/commands/adapter.d.ts.map +1 -0
- package/dist/cli/commands/adapter.js +41 -0
- package/dist/cli/commands/adapter.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +70 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +40 -4
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +2 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +93 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/preflight.d.ts +3 -1
- package/dist/cli/commands/preflight.d.ts.map +1 -1
- package/dist/cli/commands/preflight.js +3 -2
- package/dist/cli/commands/preflight.js.map +1 -1
- package/dist/cli/commands/tui.js +2 -0
- package/dist/cli/commands/tui.js.map +1 -1
- package/dist/cli/drift.js +3 -1
- package/dist/cli/drift.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +16 -4
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/cli/options.js +10 -0
- package/dist/cli/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/docs/adapter-matrix.md +15 -0
- package/docs/adr/0002-agent-platform-v2.md +41 -0
- package/docs/host-smoke-tests.md +15 -0
- package/docs/mcp.md +19 -0
- package/docs/migration.md +16 -0
- package/docs/release-3.4.0.md +42 -0
- package/docs/release-agent-platform-v2.md +21 -0
- package/package.json +3 -2
- package/scripts/check-adapter-fixtures.mjs +88 -18
- package/scripts/smoke-adapters.mjs +144 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Kyro AI 3.4.0 Release Notes
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Kyro 3.4.0 stabilizes the adapter harness v1 release. It adds concrete OpenCode and Codex adapter projections, keeps Claude plugin support first-class, and hardens sync/uninstall behavior around managed files, shared config, drift, prune, purge, and dry-run safety.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
- Adapter harness v1 for `standard`, `opencode`, and `codex`.
|
|
10
|
+
- `kyro detect` and adapter inventory through `kyro doctor --adapters`.
|
|
11
|
+
- OpenCode native skills, `/kyro/*` command files, and a Kyro-owned `agent.kyro-orchestrator` overlay.
|
|
12
|
+
- Codex command skills plus a managed root `AGENTS.md` block.
|
|
13
|
+
- Global runtime under `~/.agents/kyro/versions/{version}` with `current` symlink.
|
|
14
|
+
- Project-local state under `.agents/kyro/`.
|
|
15
|
+
- `kyro sync` refreshes installed adapters without silently adding `standard`.
|
|
16
|
+
- `kyro sync --prune` removes stale runtime versions and obsolete Kyro-owned adapter entrypoints.
|
|
17
|
+
- Shared config such as `~/.config/opencode/opencode.json` is preserved and reported separately.
|
|
18
|
+
- `kyro uninstall --purge-adapter-assets` explicitly removes adapter entrypoints while preserving shared config.
|
|
19
|
+
- Dry-run behavior is covered for prune and purge paths.
|
|
20
|
+
- Pipeline rollback and managed block/JSON merge behavior are covered by fixtures.
|
|
21
|
+
|
|
22
|
+
## Non-Goals
|
|
23
|
+
|
|
24
|
+
- No generic adapter is provided.
|
|
25
|
+
- Cursor remains planned until concrete projection behavior is defined.
|
|
26
|
+
- Claude plugin support is not replaced by the CLI adapter path.
|
|
27
|
+
- MCP remains modeled but inactive until there is a concrete Kyro MCP server contract.
|
|
28
|
+
|
|
29
|
+
## Validation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run build
|
|
33
|
+
npm run check
|
|
34
|
+
npm run check:adapters
|
|
35
|
+
npm_config_cache=/tmp/kyro-npm-cache npm pack --dry-run
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Additional smoke coverage was run with an isolated `HOME` and workspace under `/tmp`, covering install, doctor, sync dry-run, prune dry-run, uninstall, purge, and host binary detection for OpenCode and Codex.
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
|
|
42
|
+
`doctor --adapters` now treats the global runtime as the files under `~/.agents/kyro/`. Adapter entrypoints are validated through installed adapter projection checks, so purging adapter assets after uninstall does not make the preserved global runtime appear broken.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Agent Platform V2 Release Notes
|
|
2
|
+
|
|
3
|
+
This scope expands Kyro from adapter harness v1 into a host-aware agent platform slice.
|
|
4
|
+
|
|
5
|
+
Added:
|
|
6
|
+
|
|
7
|
+
- Cursor workspace adapter at `.cursor/rules/kyro-ai.mdc`
|
|
8
|
+
- Claude CLI-native workspace skills under `.claude/skills/kyro-*`
|
|
9
|
+
- `kyro adapter matrix` and `kyro adapter matrix --json`
|
|
10
|
+
- `kyro doctor --migration` and `kyro doctor --migration --json`
|
|
11
|
+
- JSON output for install/sync planning
|
|
12
|
+
- read-only `kyro mcp serve`
|
|
13
|
+
- `npm run smoke:adapters`
|
|
14
|
+
- adapter, migration, MCP, and smoke test docs
|
|
15
|
+
|
|
16
|
+
Safety behavior:
|
|
17
|
+
|
|
18
|
+
- Normal install/sync does not enable MCP host config.
|
|
19
|
+
- Cursor uninstall removes only the Kyro-owned rule file.
|
|
20
|
+
- Claude CLI-native install does not modify `.claude-plugin/`.
|
|
21
|
+
- `sync --prune` preserves shared host config.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kyro-ai",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Portable sprint workflow kit for AI coding agents, with markdown artifacts, adapter guides, and formal debt tracking",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"check:tokens": "node dist/cli.js doctor --tokens",
|
|
17
17
|
"check:artifacts": "node dist/cli.js doctor --artifacts",
|
|
18
18
|
"check:artifact-fixtures": "node scripts/check-artifact-fixtures.mjs",
|
|
19
|
-
"check:adapters": "node scripts/check-adapter-fixtures.mjs"
|
|
19
|
+
"check:adapters": "node scripts/check-adapter-fixtures.mjs",
|
|
20
|
+
"smoke:adapters": "node scripts/smoke-adapters.mjs"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"ai-agents",
|
|
@@ -70,6 +70,8 @@ function cliOptions(overrides = {}) {
|
|
|
70
70
|
json: false,
|
|
71
71
|
purgeAdapterAssets: false,
|
|
72
72
|
prune: false,
|
|
73
|
+
migration: false,
|
|
74
|
+
enableMcp: false,
|
|
73
75
|
...overrides,
|
|
74
76
|
};
|
|
75
77
|
}
|
|
@@ -113,11 +115,15 @@ function assertStandardCommandSkills(plan, name) {
|
|
|
113
115
|
const standardPlan = dryRunPlan('standard');
|
|
114
116
|
const openCodePlan = dryRunPlan('opencode');
|
|
115
117
|
const codexPlan = dryRunPlan('codex');
|
|
116
|
-
const
|
|
118
|
+
const cursorPlan = dryRunPlan('cursor');
|
|
119
|
+
const claudePlan = dryRunPlan('claude');
|
|
120
|
+
const combinedPlan = dryRunPlan('standard,opencode,codex,cursor,claude');
|
|
117
121
|
|
|
118
122
|
assertCommonPlan(standardPlan, 'standard');
|
|
119
123
|
assertCommonPlan(openCodePlan, 'opencode');
|
|
120
124
|
assertCommonPlan(codexPlan, 'codex');
|
|
125
|
+
assertCommonPlan(cursorPlan, 'cursor');
|
|
126
|
+
assertCommonPlan(claudePlan, 'claude');
|
|
121
127
|
assertCommonPlan(combinedPlan, 'combined');
|
|
122
128
|
assertStandardCommandSkills(standardPlan, 'standard');
|
|
123
129
|
assertStandardCommandSkills(codexPlan, 'codex');
|
|
@@ -127,10 +133,15 @@ assert(!standardPlan.includes('- upsert-block AGENTS.md # agents-md'), 'standard
|
|
|
127
133
|
assert(!openCodePlan.includes('- upsert-block AGENTS.md # agents-md'), 'opencode: should not manage AGENTS.md block');
|
|
128
134
|
assert(codexPlan.includes('- upsert-block AGENTS.md # agents-md'), 'codex: should manage AGENTS.md block');
|
|
129
135
|
assert(combinedPlan.includes('- upsert-block AGENTS.md # agents-md'), 'combined: should manage AGENTS.md block once');
|
|
136
|
+
assert(cursorPlan.includes('- write .cursor/rules/kyro-ai.mdc'), 'cursor: missing workspace rule projection');
|
|
137
|
+
assert(claudePlan.includes('- write .claude/skills/kyro-forge/SKILL.md'), 'claude: missing CLI-native skill projection');
|
|
138
|
+
assert(!claudePlan.includes('.claude-plugin'), 'claude: CLI-native projection should not touch .claude-plugin');
|
|
130
139
|
|
|
131
140
|
assert(standardPlan.includes('- standard: status=implemented;'), 'standard: missing preflight status');
|
|
132
141
|
assert(openCodePlan.includes('- opencode: status=implemented;'), 'opencode: missing preflight status');
|
|
133
142
|
assert(codexPlan.includes('- codex: status=implemented;'), 'codex: missing preflight status');
|
|
143
|
+
assert(cursorPlan.includes('- cursor: status=implemented;'), 'cursor: missing preflight status');
|
|
144
|
+
assert(claudePlan.includes('- claude: status=implemented;'), 'claude: missing preflight status');
|
|
134
145
|
assert(openCodePlan.includes('- write ~/.config/opencode/skills/kyro-forge/SKILL.md'), 'opencode: missing native forge skill projection');
|
|
135
146
|
assert(openCodePlan.includes('- write ~/.config/opencode/commands/kyro/forge.md'), 'opencode: missing native forge command projection');
|
|
136
147
|
assert(openCodePlan.includes('- merge-json ~/.config/opencode/opencode.json'), 'opencode: missing native settings overlay');
|
|
@@ -138,20 +149,6 @@ assert(!openCodePlan.includes('- write ~/.agents/skills/kyro-forge/SKILL.md'), '
|
|
|
138
149
|
assert(countIncludes(combinedPlan, '- write ~/.agents/skills/kyro-forge/SKILL.md') === 1, 'combined: forge skill should be projected once');
|
|
139
150
|
assert(countIncludes(combinedPlan, '- upsert-block AGENTS.md # agents-md') === 1, 'combined: AGENTS.md block should be projected once');
|
|
140
151
|
|
|
141
|
-
withWorkspace('kyro-adapter-preflight-', () => {
|
|
142
|
-
const { parseAgent } = require(join(repo, 'dist/cli/options.js'));
|
|
143
|
-
const { install } = require(join(repo, 'dist/cli/commands/install.js'));
|
|
144
|
-
let failed = false;
|
|
145
|
-
try {
|
|
146
|
-
captureLogs(() => install(cliOptions({ agents: [parseAgent('claude')], dryRun: true })));
|
|
147
|
-
} catch (error) {
|
|
148
|
-
failed = true;
|
|
149
|
-
assert(String(error).includes('not implemented yet: claude'), 'preflight: planned adapter failure should name claude');
|
|
150
|
-
assert(String(error).includes('native projection'), 'preflight: planned adapter failure should mention native projection');
|
|
151
|
-
}
|
|
152
|
-
assert(failed, 'preflight: expected planned adapter install to fail');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
152
|
{
|
|
156
153
|
const { mergeJsonObjectContent } = require(join(repo, 'dist/cli/injectors/json-merge.js'));
|
|
157
154
|
const merged = mergeJsonObjectContent(`{
|
|
@@ -268,9 +265,11 @@ withWorkspace('kyro-adapter-contract-', (cwd) => {
|
|
|
268
265
|
assert(byAgent.codex.mcpStrategy() === 'toml-file', 'codex: unexpected MCP strategy');
|
|
269
266
|
assert(byAgent.codex.detect({ homeDir, envPath: '' }).configFound === true, 'codex: expected config detection');
|
|
270
267
|
|
|
271
|
-
assert(byAgent.claude.status === '
|
|
268
|
+
assert(byAgent.claude.status === 'implemented', 'claude: expected implemented status');
|
|
269
|
+
assert(byAgent.claude.capabilities().includes('command-skills'), 'claude: missing command skill capability');
|
|
272
270
|
assert(byAgent.claude.paths(homeDir).subAgentsDir.endsWith('/.claude/agents'), 'claude: unexpected sub-agent path');
|
|
273
|
-
assert(byAgent.cursor.status === '
|
|
271
|
+
assert(byAgent.cursor.status === 'implemented', 'cursor: expected implemented status');
|
|
272
|
+
assert(byAgent.cursor.capabilities().includes('system-prompt'), 'cursor: missing rule/system prompt capability');
|
|
274
273
|
assert(byAgent.cursor.systemPromptStrategy() === 'instructions-file', 'cursor: unexpected system prompt strategy');
|
|
275
274
|
});
|
|
276
275
|
|
|
@@ -309,10 +308,65 @@ withWorkspace('kyro-adapter-install-', (installDir) => {
|
|
|
309
308
|
assert(agentsText.includes('Keep this user content.'), 'uninstall: user AGENTS.md content was not preserved');
|
|
310
309
|
});
|
|
311
310
|
|
|
311
|
+
withWorkspace('kyro-adapter-cursor-install-', (installDir) => {
|
|
312
|
+
const { parseAgent } = require(join(repo, 'dist/cli/options.js'));
|
|
313
|
+
const { install, sync } = require(join(repo, 'dist/cli/commands/install.js'));
|
|
314
|
+
const { uninstall } = require(join(repo, 'dist/cli/commands/uninstall.js'));
|
|
315
|
+
const { doctor } = require(join(repo, 'dist/cli/commands/doctor.js'));
|
|
316
|
+
const cursor = parseAgent('cursor');
|
|
317
|
+
const userRulePath = join(installDir, '.cursor', 'rules', 'user-owned.mdc');
|
|
318
|
+
mkdirSync(join(installDir, '.cursor', 'rules'), { recursive: true });
|
|
319
|
+
writeFileSync(userRulePath, '# User rule\n\nKeep this rule.\n', 'utf-8');
|
|
320
|
+
|
|
321
|
+
const dryRun = captureLogs(() => install(cliOptions({ agents: [cursor], dryRun: true })));
|
|
322
|
+
assert(dryRun.includes('- write .cursor/rules/kyro-ai.mdc'), 'cursor dry-run: missing Kyro rule write');
|
|
323
|
+
assert(!existsSync(join(installDir, '.cursor', 'rules', 'kyro-ai.mdc')), 'cursor dry-run: should not write Kyro rule');
|
|
324
|
+
|
|
325
|
+
captureLogs(() => install(cliOptions({ agents: [cursor] })));
|
|
326
|
+
const kyroRulePath = join(installDir, '.cursor', 'rules', 'kyro-ai.mdc');
|
|
327
|
+
assert(existsSync(kyroRulePath), 'cursor install: missing Kyro rule');
|
|
328
|
+
assert(readFileSync(kyroRulePath, 'utf-8').includes('~/.agents/kyro/current'), 'cursor install: rule should reference global runtime');
|
|
329
|
+
assert(readFileSync(userRulePath, 'utf-8').includes('Keep this rule.'), 'cursor install: user rule should be preserved');
|
|
330
|
+
|
|
331
|
+
const doctorOutput = captureLogs(() => doctor(cliOptions({ adapters: true })));
|
|
332
|
+
assert(doctorOutput.includes('[PASS] Cursor adapter'), 'cursor doctor: adapter should pass after install');
|
|
333
|
+
|
|
334
|
+
captureLogs(() => sync(cliOptions({ agents: [cursor] })));
|
|
335
|
+
assert(readFileSync(userRulePath, 'utf-8').includes('Keep this rule.'), 'cursor sync: user rule should be preserved');
|
|
336
|
+
|
|
337
|
+
captureLogs(() => uninstall(cliOptions({ purgeAdapterAssets: true })));
|
|
338
|
+
assert(!existsSync(kyroRulePath), 'cursor uninstall purge: Kyro rule should be removed');
|
|
339
|
+
assert(existsSync(userRulePath), 'cursor uninstall purge: user rule should be preserved');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
withWorkspace('kyro-adapter-claude-install-', (installDir) => {
|
|
343
|
+
const { parseAgent } = require(join(repo, 'dist/cli/options.js'));
|
|
344
|
+
const { install } = require(join(repo, 'dist/cli/commands/install.js'));
|
|
345
|
+
const { uninstall } = require(join(repo, 'dist/cli/commands/uninstall.js'));
|
|
346
|
+
const claude = parseAgent('claude');
|
|
347
|
+
const pluginPath = join(installDir, '.claude-plugin', 'plugin.json');
|
|
348
|
+
mkdirSync(join(installDir, '.claude-plugin'), { recursive: true });
|
|
349
|
+
writeFileSync(pluginPath, '{ "name": "user-plugin-fixture" }\n', 'utf-8');
|
|
350
|
+
|
|
351
|
+
captureLogs(() => install(cliOptions({ agents: [claude] })));
|
|
352
|
+
for (const command of ['forge', 'status', 'wrap-up']) {
|
|
353
|
+
const skillPath = join(installDir, '.claude', 'skills', `kyro-${command}`, 'SKILL.md');
|
|
354
|
+
assert(existsSync(skillPath), `claude install: missing CLI-native skill ${command}`);
|
|
355
|
+
}
|
|
356
|
+
assert(readFileSync(pluginPath, 'utf-8').includes('user-plugin-fixture'), 'claude install: .claude-plugin should be untouched');
|
|
357
|
+
|
|
358
|
+
captureLogs(() => uninstall(cliOptions({ purgeAdapterAssets: true })));
|
|
359
|
+
for (const command of ['forge', 'status', 'wrap-up']) {
|
|
360
|
+
assert(!existsSync(join(installDir, '.claude', 'skills', `kyro-${command}`, 'SKILL.md')), `claude uninstall purge: skill ${command} should be removed`);
|
|
361
|
+
}
|
|
362
|
+
assert(readFileSync(pluginPath, 'utf-8').includes('user-plugin-fixture'), 'claude uninstall purge: .claude-plugin should be untouched');
|
|
363
|
+
});
|
|
364
|
+
|
|
312
365
|
withWorkspace('kyro-adapter-opencode-install-', (installDir) => {
|
|
313
366
|
const { parseAgent } = require(join(repo, 'dist/cli/options.js'));
|
|
314
367
|
const { install, sync } = require(join(repo, 'dist/cli/commands/install.js'));
|
|
315
368
|
const { uninstall } = require(join(repo, 'dist/cli/commands/uninstall.js'));
|
|
369
|
+
const { doctor } = require(join(repo, 'dist/cli/commands/doctor.js'));
|
|
316
370
|
const opencode = parseAgent('opencode');
|
|
317
371
|
const home = join(installDir, '.home');
|
|
318
372
|
const settingsPath = join(home, '.config', 'opencode', 'opencode.json');
|
|
@@ -397,6 +451,10 @@ withWorkspace('kyro-adapter-opencode-install-', (installDir) => {
|
|
|
397
451
|
}
|
|
398
452
|
assert(!existsSync(join(home, '.config', 'opencode', 'commands', 'kyro')), 'opencode purge: empty command namespace still present');
|
|
399
453
|
assert(existsSync(join(home, '.config', 'opencode')), 'opencode purge: shared OpenCode config directory should remain');
|
|
454
|
+
|
|
455
|
+
const doctorAfterPurgeOutput = captureLogs(() => doctor(cliOptions({ adapters: true })));
|
|
456
|
+
assert(doctorAfterPurgeOutput.includes('[PASS] global runtime'), 'opencode purge doctor: global runtime should remain valid after purging adapter assets');
|
|
457
|
+
assert(!doctorAfterPurgeOutput.includes('[FAIL]'), 'opencode purge doctor: should not fail after purging adapter assets');
|
|
400
458
|
});
|
|
401
459
|
|
|
402
460
|
withWorkspace('kyro-pipeline-rollback-', (cwd) => {
|
|
@@ -455,7 +513,6 @@ withWorkspace('kyro-adapter-doctor-', () => {
|
|
|
455
513
|
assert(output.includes(`adapter inventory: ${agent}`), `doctor --adapters: missing ${agent}`);
|
|
456
514
|
}
|
|
457
515
|
assert(output.includes('status=implemented'), 'doctor --adapters: missing implemented status');
|
|
458
|
-
assert(output.includes('status=planned'), 'doctor --adapters: missing planned status');
|
|
459
516
|
assert(output.includes('capabilities=command-skills'), 'doctor --adapters: missing command-skills capability');
|
|
460
517
|
assert(output.includes('workspace-agents-block'), 'doctor --adapters: missing workspace AGENTS block capability');
|
|
461
518
|
});
|
|
@@ -574,7 +631,9 @@ withWorkspace('kyro-sync-shared-config-only-', (cwd) => {
|
|
|
574
631
|
|
|
575
632
|
withWorkspace('kyro-adapter-detect-', () => {
|
|
576
633
|
const { parseAgent } = require(join(repo, 'dist/cli/options.js'));
|
|
634
|
+
const { adapterMatrix } = require(join(repo, 'dist/cli/commands/adapter.js'));
|
|
577
635
|
const { detect } = require(join(repo, 'dist/cli/commands/detect.js'));
|
|
636
|
+
const { doctor } = require(join(repo, 'dist/cli/commands/doctor.js'));
|
|
578
637
|
|
|
579
638
|
const textOutput = captureLogs(() => detect(cliOptions()));
|
|
580
639
|
for (const agent of ['standard', 'opencode', 'codex', 'claude', 'cursor']) {
|
|
@@ -590,6 +649,17 @@ withWorkspace('kyro-adapter-detect-', () => {
|
|
|
590
649
|
assert(payload.adapters[0].agent === 'codex', 'detect --json: expected codex adapter');
|
|
591
650
|
assert(payload.adapters[0].systemPromptStrategy === 'managed-block', 'detect --json: missing codex system prompt strategy');
|
|
592
651
|
assert(payload.adapters[0].mcpStrategy === 'toml-file', 'detect --json: missing codex MCP strategy');
|
|
652
|
+
|
|
653
|
+
const matrixOutput = captureLogs(() => adapterMatrix(cliOptions({ json: true })));
|
|
654
|
+
const matrixPayload = JSON.parse(matrixOutput);
|
|
655
|
+
assert(matrixPayload.schemaVersion === 1, 'adapter matrix --json: missing schema version');
|
|
656
|
+
assert(matrixPayload.adapters.some((adapter) => adapter.agent === 'cursor' && adapter.installable === true), 'adapter matrix --json: missing installable cursor');
|
|
657
|
+
assert(matrixPayload.adapters.some((adapter) => adapter.agent === 'claude' && adapter.systemPromptStrategy === 'instructions-file'), 'adapter matrix --json: missing claude strategy');
|
|
658
|
+
|
|
659
|
+
const migrationOutput = captureLogs(() => doctor(cliOptions({ migration: true, json: true })));
|
|
660
|
+
const migrationPayload = JSON.parse(migrationOutput);
|
|
661
|
+
assert(migrationPayload.schemaVersion === 1, 'doctor --migration --json: missing schema version');
|
|
662
|
+
assert(migrationPayload.migration.availableAdapters.includes('cursor'), 'doctor --migration --json: missing cursor availability');
|
|
593
663
|
});
|
|
594
664
|
|
|
595
665
|
console.log('Adapter fixtures passed');
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const repo = resolve(new URL('..', import.meta.url).pathname);
|
|
8
|
+
const distRoot = join(repo, 'dist');
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const requestedAgents = parseAgents(process.argv.slice(2));
|
|
11
|
+
const agents = requestedAgents.length > 0 ? requestedAgents : ['standard', 'opencode', 'codex', 'cursor', 'claude'];
|
|
12
|
+
const hostBinaryByAgent = {
|
|
13
|
+
standard: null,
|
|
14
|
+
opencode: 'opencode',
|
|
15
|
+
codex: 'codex',
|
|
16
|
+
cursor: 'cursor',
|
|
17
|
+
claude: 'claude',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (!existsSync(join(distRoot, 'cli.js'))) {
|
|
21
|
+
throw new Error('dist/cli.js not found. Run npm run build before npm run smoke:adapters.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const results = [];
|
|
25
|
+
|
|
26
|
+
for (const agentName of agents) {
|
|
27
|
+
const workspace = mkdtempSync(join(tmpdir(), `kyro-smoke-${agentName}-`));
|
|
28
|
+
const previousCwd = process.cwd();
|
|
29
|
+
const previousHome = process.env.HOME;
|
|
30
|
+
try {
|
|
31
|
+
process.chdir(workspace);
|
|
32
|
+
process.env.HOME = join(workspace, '.home');
|
|
33
|
+
clearDistCache();
|
|
34
|
+
|
|
35
|
+
const { parseAgent } = require(join(distRoot, 'cli', 'options.js'));
|
|
36
|
+
const { install, sync } = require(join(distRoot, 'cli', 'commands', 'install.js'));
|
|
37
|
+
const { doctor } = require(join(distRoot, 'cli', 'commands', 'doctor.js'));
|
|
38
|
+
const { uninstall } = require(join(distRoot, 'cli', 'commands', 'uninstall.js'));
|
|
39
|
+
const agent = parseAgent(agentName);
|
|
40
|
+
|
|
41
|
+
captureLogs(() => install(options({ agents: [agent], dryRun: true })));
|
|
42
|
+
captureLogs(() => install(options({ agents: [agent] })));
|
|
43
|
+
captureLogs(() => doctor(options({ adapters: true })));
|
|
44
|
+
captureLogs(() => sync(options({ agents: [agent], dryRun: true })));
|
|
45
|
+
const syncJson = captureLogs(() => sync(options({ agents: [agent], prune: true, dryRun: true, json: true })));
|
|
46
|
+
assert(JSON.parse(syncJson).command === 'sync', `${agentName}: sync --json did not return sync payload`);
|
|
47
|
+
captureLogs(() => uninstall(options({ dryRun: true })));
|
|
48
|
+
captureLogs(() => uninstall(options({ purgeAdapterAssets: true, dryRun: true })));
|
|
49
|
+
|
|
50
|
+
const hostBinary = hostBinaryByAgent[agentName];
|
|
51
|
+
const hostStatus = hostBinary ? checkHostBinary(hostBinary) : 'skipped:no-host-binary';
|
|
52
|
+
results.push({ agent: agentName, status: 'pass', hostStatus });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
results.push({ agent: agentName, status: 'fail', detail: error instanceof Error ? error.message : String(error) });
|
|
55
|
+
} finally {
|
|
56
|
+
process.chdir(previousCwd);
|
|
57
|
+
if (previousHome === undefined) {
|
|
58
|
+
delete process.env.HOME;
|
|
59
|
+
} else {
|
|
60
|
+
process.env.HOME = previousHome;
|
|
61
|
+
}
|
|
62
|
+
clearDistCache();
|
|
63
|
+
rmSync(workspace, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const result of results) {
|
|
68
|
+
if (result.status === 'pass') {
|
|
69
|
+
console.log(`[PASS] ${result.agent}: lifecycle ok; host=${result.hostStatus}`);
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`[FAIL] ${result.agent}: ${result.detail}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (results.some((result) => result.status === 'fail')) process.exit(1);
|
|
76
|
+
|
|
77
|
+
function options(overrides = {}) {
|
|
78
|
+
return {
|
|
79
|
+
agents: [],
|
|
80
|
+
scope: 'workspace',
|
|
81
|
+
dryRun: false,
|
|
82
|
+
yes: true,
|
|
83
|
+
help: false,
|
|
84
|
+
tokens: false,
|
|
85
|
+
artifacts: false,
|
|
86
|
+
adapters: false,
|
|
87
|
+
kyroScope: null,
|
|
88
|
+
json: false,
|
|
89
|
+
purgeAdapterAssets: false,
|
|
90
|
+
prune: false,
|
|
91
|
+
migration: false,
|
|
92
|
+
enableMcp: false,
|
|
93
|
+
...overrides,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function captureLogs(callback) {
|
|
98
|
+
const logs = [];
|
|
99
|
+
const originalLog = console.log;
|
|
100
|
+
try {
|
|
101
|
+
console.log = (...args) => logs.push(args.join(' '));
|
|
102
|
+
callback();
|
|
103
|
+
} finally {
|
|
104
|
+
console.log = originalLog;
|
|
105
|
+
}
|
|
106
|
+
return `${logs.join('\n')}\n`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function clearDistCache() {
|
|
110
|
+
for (const key of Object.keys(require.cache)) {
|
|
111
|
+
if (key.startsWith(distRoot)) delete require.cache[key];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function assert(condition, message) {
|
|
116
|
+
if (!condition) throw new Error(message);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseAgents(args) {
|
|
120
|
+
const agentArgIndex = args.findIndex((arg) => arg === '--agent' || arg === '--agents');
|
|
121
|
+
if (agentArgIndex >= 0) {
|
|
122
|
+
return (args[agentArgIndex + 1] ?? '').split(',').map((agent) => agent.trim()).filter(Boolean);
|
|
123
|
+
}
|
|
124
|
+
const inlineArg = args.find((arg) => arg.startsWith('--agent=') || arg.startsWith('--agents='));
|
|
125
|
+
if (inlineArg) {
|
|
126
|
+
return inlineArg.slice(inlineArg.indexOf('=') + 1).split(',').map((agent) => agent.trim()).filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function checkHostBinary(binary) {
|
|
132
|
+
try {
|
|
133
|
+
const command = process.platform === 'win32' ? 'where' : 'command';
|
|
134
|
+
const args = process.platform === 'win32' ? [binary] : ['-v', binary];
|
|
135
|
+
const found = spawnSync(command, args, { encoding: 'utf-8', shell: process.platform !== 'win32' });
|
|
136
|
+
if (found.error || found.status !== 0) return 'skipped:not-installed';
|
|
137
|
+
|
|
138
|
+
const version = spawnSync(binary, ['--version'], { encoding: 'utf-8' });
|
|
139
|
+
if (version.error || version.status !== 0) return 'detected:version-check-unavailable';
|
|
140
|
+
return `detected:${version.stdout.trim() || version.stderr.trim() || 'version-ok'}`;
|
|
141
|
+
} catch {
|
|
142
|
+
return 'skipped:host-probe-unavailable';
|
|
143
|
+
}
|
|
144
|
+
}
|