From f2f5a393fa0f260095883ab2027c607718665ec7 Mon Sep 17 00:00:00 2001 From: billowshen <2528273456@qq.com> Date: Tue, 12 May 2026 15:14:07 +0800 Subject: [PATCH 1/3] docs(specs): add Kiro CLI support design --- .../2026-05-12-kiro-cli-support-design.md | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md diff --git a/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md b/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md new file mode 100644 index 00000000..6066825b --- /dev/null +++ b/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md @@ -0,0 +1,111 @@ +# Kiro CLI Support Design + +**Date**: 2026-05-12 +**Status**: Draft +**Scope**: Add Kiro CLI as a first-class supported client in the AIOS workspace + +## Problem + +The repo currently treats `codex`, `claude`, `gemini`, and `opencode` as first-class clients. Kiro CLI is not represented in the client registry, shell bridge, native sync manifest, or docs. That means Kiro cannot receive the same ContextDB, steering, and MCP bootstrap behavior that the other clients get. + +## Evidence + +- `scripts/lib/lifecycle/options.mjs` hard-codes `CLIENT_NAMES = ['all', 'codex', 'claude', 'gemini', 'opencode']`. +- `scripts/lib/platform/paths.mjs` only defines homes for those four clients. +- `scripts/lib/components/shell.mjs` and `scripts/contextdb-shell.{zsh,ps1}` only wrap those four commands. +- `config/native-sync-manifest.json` only emits native artifacts for those four clients. +- `scripts/lib/harness/subagent-runtime.mjs` only accepts `codex-cli`, `claude-code`, `gemini-cli`, `opencode-cli` as live subagent clients. + +Kiro CLI docs indicate a terminal CLI entrypoint plus workspace steering and MCP support, so it fits the same client-adapter pattern, but not necessarily the same live subagent runtime contract. + +## Recommendation + +Implement Kiro in two stages. + +### Stage 1: Compatibility client + +Add Kiro to the repo's client surface so it can receive the same project memory, shell bridge, steering sync, and MCP bootstrap behavior as the other clients. + +Kiro does not use the repo's existing skill-pack layout as-is, so stage 1 should treat `skills` as unsupported for Kiro unless a Kiro-specific discoverable format is added later. + +### Stage 2: Runtime execution + +Only add Kiro to `team` / `subagent` / `harness` live execution if the CLI can produce a stable structured handoff that matches the current runtime contract. + +## Options + +### Option A: Compatibility client only + +Add `kiro` / `kiro-cli` to the registry, shell bridge, native sync, and docs. Keep live team/subagent execution out of scope for now. + +Trade-off: lowest risk, delivers immediate value, and matches what the repo already does for other compatibility clients. + +### Option B: Full runtime client + +Add Kiro to the compatibility layer and also to live `team` / `subagent` / `harness` execution paths. + +Trade-off: more complete, but riskier because the current runtime assumes specific agent command shapes and structured outputs. + +### Option C: Alias-only support + +Expose a shell alias for Kiro and stop there. + +Trade-off: too shallow; it would not make Kiro a real supported client in the repo's registry or native sync flow. + +## File Map + +- `scripts/lib/lifecycle/options.mjs` +- `scripts/lib/platform/paths.mjs` +- `scripts/lib/components/shell.mjs` +- `scripts/contextdb-shell.zsh` +- `scripts/contextdb-shell.ps1` +- `scripts/contextdb-shell-bridge.mjs` +- `scripts/lib/components/native.mjs` +- `scripts/lib/native/source-tree.mjs` +- `config/native-sync-manifest.json` +- `scripts/lib/components/browser.mjs` if Kiro should inherit MCP bootstrap discovery +- `scripts/lib/cli/help.mjs` +- `scripts/tests/*` for registry, wrapper, and native-sync coverage +- `README-zh.md` and `CLAUDE.md` for user-facing client lists + +## Proposed Behavior + +### Client registration + +- Add `kiro` to the client lists used by setup/update/uninstall and home-dir resolution. +- Treat Kiro home as `~/.kiro` by default, with optional `KIRO_HOME` override. +- Keep `skills` as a warning/skip path for Kiro in stage 1; steering should come from native sync instead. + +### Shell bridge + +- Add a `kiro` shell function and a `kiro-cli` passthrough mapping. +- The passthrough command should prefer `kiro-cli` rather than `kiro`, because `kiro` may be reserved by the IDE launcher. + +### Native sync + +- Emit Kiro workspace steering files under `.kiro/steering/`. +- Emit Kiro MCP settings under `.kiro/settings/mcp.json` if we want the same repo-local browser MCP bootstrap path to be visible to Kiro. +- Keep root `AGENTS.md` as the authoritative shared policy file. + +### Docs and help + +- Update supported-client lists and examples to mention Kiro where the repo describes supported clients. +- Keep the wording explicit that Kiro is supported as a compatibility client first, not yet as a live subagent runtime. + +## Acceptance Criteria + +- `setup`, `update`, and `uninstall` accept `--client kiro`. +- The shell bridge can wrap Kiro CLI without breaking existing commands. +- Native sync can materialize Kiro workspace files. +- Existing clients still pass their current tests unchanged. +- The docs clearly state Kiro support level and any runtime limits. + +## Out of Scope For Stage 1 + +- Adding Kiro to `AIOS_SUBAGENT_CLIENT`. +- Adding Kiro to `team` / `subagent` live execution providers. +- Changing the existing codex/claude/gemini/opencode behavior. + +## Open Assumption + +This design assumes Kiro CLI is invoked as `kiro-cli` in terminal mode, while `kiro` may remain reserved for the IDE-oriented launcher. From 94a80ba029a7f67085f7b87e5f48ace04e926012 Mon Sep 17 00:00:00 2001 From: billowshen <2528273456@qq.com> Date: Wed, 13 May 2026 17:21:24 +0800 Subject: [PATCH 2/3] feat(kiro): full Kiro CLI native integration across AIOS stack Add Kiro CLI as a first-class AIOS client alongside Claude, Codex, Gemini, and OpenCode: - native sync writes Kiro steering docs and merges MCP settings without clobbering existing servers - native doctor detects and reports Kiro MCP drift and missing agents with recovery commands - skills catalog, sync manifest, and agent-sources manifest now include kiro as a canonical client - solo-profiles and subagent-runtime route kiro-cli headless/solo/live subagent sessions - contextdb-shell-bridge and shell wrappers expose kiro-cli and kiro alias via passthrough - model-router, handoff, ctx-agent-core, and team-ops recognize kiro as a provider - TUI setup options, HUD, native-preview, and help text surface kiro alongside other clients - package-release scripts include .kiro assets in stable release archives - 137 tests pass covering native sync/doctor/repairs, skills, agents, components, harness profiles, release pipeline, and wrappers Co-Authored-By: Claude Sonnet 4.6 --- .claude/.aios-native-sync.json | 2 +- .../assets/template/harness/run.mjs | 2 +- .codex/.aios-native-sync.json | 2 +- .../assets/template/harness/run.mjs | 2 +- .gemini/.aios-native-sync.json | 2 +- .gemini/AIOS.md | 2 +- .../assets/template/harness/run.mjs | 2 +- .opencode/.aios-native-sync.json | 2 +- .opencode/AIOS.md | 2 +- .../assets/template/harness/run.mjs | 2 +- AGENTS.md | 8 +- CLAUDE.md | 2 +- README-zh.md | 8 +- agent-sources/manifest.json | 3 +- .../native-base/codex/project/AGENTS.md | 2 +- .../native-base/shared/partials/contextdb.md | 2 +- config/native-sync-manifest.json | 10 ++ config/skills-catalog.json | 40 +++---- config/skills-sync-manifest.json | 43 ++++---- .../2026-05-12-kiro-cli-support-design.md | 40 ++----- scripts/contextdb-shell-bridge.mjs | 32 ++++-- scripts/contextdb-shell.ps1 | 14 ++- scripts/contextdb-shell.zsh | 10 +- scripts/ctx-agent-core.mjs | 82 +++++++++++--- scripts/lib/agents/emitters/kiro.mjs | 94 ++++++++++++++++ scripts/lib/agents/source-tree.mjs | 6 +- scripts/lib/agents/sync.mjs | 49 +++++++-- scripts/lib/cli/help.mjs | 24 ++-- scripts/lib/cli/parse-args.mjs | 5 +- scripts/lib/components/browser.mjs | 1 + scripts/lib/components/shell.mjs | 18 ++- scripts/lib/components/skills.mjs | 6 +- scripts/lib/contextdb/handoff.mjs | 1 + scripts/lib/harness/hindsight-eval.mjs | 2 +- scripts/lib/harness/solo-profiles.mjs | 17 +++ scripts/lib/harness/subagent-runtime.mjs | 32 +++++- scripts/lib/hud/state.mjs | 5 +- scripts/lib/lifecycle/options.mjs | 4 +- scripts/lib/lifecycle/team-ops.mjs | 2 +- scripts/lib/model-router.mjs | 1 + scripts/lib/native/doctor.mjs | 61 +++++++++- scripts/lib/native/emitters/kiro.mjs | 38 +++++++ scripts/lib/native/source-tree.mjs | 2 +- scripts/lib/native/sync.mjs | 104 +++++++++++++++++- scripts/lib/platform/fs.mjs | 1 + scripts/lib/platform/paths.mjs | 1 + scripts/lib/platform/process.mjs | 2 +- scripts/lib/skills/sync.mjs | 21 +++- scripts/lib/tui-ink/cli.tsx | 1 + scripts/lib/tui-ink/hooks/useSetupOptions.ts | 2 +- scripts/lib/tui-ink/index.tsx | 3 +- scripts/lib/tui-ink/native-preview.ts | 8 +- scripts/lib/tui-ink/screens/HudScreen.tsx | 5 +- scripts/lib/tui-ink/tests/tui-ink.test.ts | 10 ++ scripts/lib/tui-ink/types.ts | 2 +- scripts/package-release.ps1 | 7 +- scripts/package-release.sh | 5 + scripts/tests/agents-source-tree.test.mjs | 3 +- scripts/tests/agents-sync.test.mjs | 82 ++++++++++++++ scripts/tests/aios-cli.test.mjs | 35 +++++- scripts/tests/aios-components.test.mjs | 60 ++++++++++ scripts/tests/aios-orchestrator.test.mjs | 29 ++++- scripts/tests/aios-wrappers.test.mjs | 12 ++ ...contextdb-shell-bridge-codex-home.test.mjs | 53 +++++++++ scripts/tests/ctx-agent-core.test.mjs | 58 ++++++++++ scripts/tests/handoff.test.mjs | 11 ++ scripts/tests/harness-profiles.test.mjs | 26 +++++ scripts/tests/model-router.test.mjs | 22 ++++ scripts/tests/native-doctor.test.mjs | 63 ++++++++++- scripts/tests/native-repairs.test.mjs | 5 + scripts/tests/native-source-tree.test.mjs | 12 +- scripts/tests/native-sync.test.mjs | 49 ++++++++- scripts/tests/release-pipeline.test.mjs | 28 ++++- scripts/tests/skills-component.test.mjs | 60 ++++++++++ .../assets/template/harness/run.mjs | 2 +- 75 files changed, 1276 insertions(+), 190 deletions(-) create mode 100644 scripts/lib/agents/emitters/kiro.mjs create mode 100644 scripts/lib/native/emitters/kiro.mjs create mode 100644 scripts/tests/model-router.test.mjs diff --git a/.claude/.aios-native-sync.json b/.claude/.aios-native-sync.json index 984ea983..51663dc4 100644 --- a/.claude/.aios-native-sync.json +++ b/.claude/.aios-native-sync.json @@ -10,6 +10,6 @@ ".claude/agents", ".claude/skills" ], - "generatedAt": "2026-05-09T23:04:55.841Z", + "generatedAt": "2026-05-13T05:41:17.980Z", "aiosVersion": "" } diff --git a/.claude/skills/harness-init-runner/assets/template/harness/run.mjs b/.claude/skills/harness-init-runner/assets/template/harness/run.mjs index 5239b51f..bcbec362 100644 --- a/.claude/skills/harness-init-runner/assets/template/harness/run.mjs +++ b/.claude/skills/harness-init-runner/assets/template/harness/run.mjs @@ -71,7 +71,7 @@ function buildProviderCommand(providerConfig, vars) { function usage() { return [ 'Usage:', - ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', + ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', '', 'Notes:', ' - If --task is omitted, the runner reads task text from stdin.', diff --git a/.codex/.aios-native-sync.json b/.codex/.aios-native-sync.json index 5171080a..c32ef52f 100644 --- a/.codex/.aios-native-sync.json +++ b/.codex/.aios-native-sync.json @@ -9,6 +9,6 @@ ".codex/agents", ".codex/skills" ], - "generatedAt": "2026-05-09T23:04:55.313Z", + "generatedAt": "2026-05-13T05:41:17.912Z", "aiosVersion": "" } diff --git a/.codex/skills/harness-init-runner/assets/template/harness/run.mjs b/.codex/skills/harness-init-runner/assets/template/harness/run.mjs index 5239b51f..bcbec362 100644 --- a/.codex/skills/harness-init-runner/assets/template/harness/run.mjs +++ b/.codex/skills/harness-init-runner/assets/template/harness/run.mjs @@ -71,7 +71,7 @@ function buildProviderCommand(providerConfig, vars) { function usage() { return [ 'Usage:', - ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', + ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', '', 'Notes:', ' - If --task is omitted, the runner reads task text from stdin.', diff --git a/.gemini/.aios-native-sync.json b/.gemini/.aios-native-sync.json index add80fab..47c407bf 100644 --- a/.gemini/.aios-native-sync.json +++ b/.gemini/.aios-native-sync.json @@ -8,6 +8,6 @@ ".gemini/AIOS.md", ".gemini/skills" ], - "generatedAt": "2026-05-09T23:04:55.931Z", + "generatedAt": "2026-05-13T05:41:18.017Z", "aiosVersion": "" } diff --git a/.gemini/AIOS.md b/.gemini/AIOS.md index ff599e32..d74ee90d 100644 --- a/.gemini/AIOS.md +++ b/.gemini/AIOS.md @@ -22,7 +22,7 @@ Use repo-local skills, agents, and bootstrap docs before falling back to ad-hoc ContextDB remains the shared runtime layer for memory, checkpoints, and execution evidence. -Wrapped `codex` / `claude` / `gemini` / `opencode` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. +Wrapped `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. Persona and user profile memory are part of the same runtime layer: - `aios memo persona ...` manages the global agent identity file (`~/.aios/SOUL.md` by default). diff --git a/.gemini/skills/harness-init-runner/assets/template/harness/run.mjs b/.gemini/skills/harness-init-runner/assets/template/harness/run.mjs index 5239b51f..bcbec362 100644 --- a/.gemini/skills/harness-init-runner/assets/template/harness/run.mjs +++ b/.gemini/skills/harness-init-runner/assets/template/harness/run.mjs @@ -71,7 +71,7 @@ function buildProviderCommand(providerConfig, vars) { function usage() { return [ 'Usage:', - ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', + ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', '', 'Notes:', ' - If --task is omitted, the runner reads task text from stdin.', diff --git a/.opencode/.aios-native-sync.json b/.opencode/.aios-native-sync.json index f3e6d910..bc3f1f25 100644 --- a/.opencode/.aios-native-sync.json +++ b/.opencode/.aios-native-sync.json @@ -8,6 +8,6 @@ ".opencode/AIOS.md", ".opencode/skills" ], - "generatedAt": "2026-05-09T23:04:55.951Z", + "generatedAt": "2026-05-13T05:41:18.055Z", "aiosVersion": "" } diff --git a/.opencode/AIOS.md b/.opencode/AIOS.md index bcc0a259..2ce1ac64 100644 --- a/.opencode/AIOS.md +++ b/.opencode/AIOS.md @@ -22,7 +22,7 @@ Use repo-local skills, agents, and bootstrap docs before falling back to ad-hoc ContextDB remains the shared runtime layer for memory, checkpoints, and execution evidence. -Wrapped `codex` / `claude` / `gemini` / `opencode` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. +Wrapped `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. Persona and user profile memory are part of the same runtime layer: - `aios memo persona ...` manages the global agent identity file (`~/.aios/SOUL.md` by default). diff --git a/.opencode/skills/harness-init-runner/assets/template/harness/run.mjs b/.opencode/skills/harness-init-runner/assets/template/harness/run.mjs index 5239b51f..bcbec362 100644 --- a/.opencode/skills/harness-init-runner/assets/template/harness/run.mjs +++ b/.opencode/skills/harness-init-runner/assets/template/harness/run.mjs @@ -71,7 +71,7 @@ function buildProviderCommand(providerConfig, vars) { function usage() { return [ 'Usage:', - ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', + ' node harness/run.mjs --provider [--task "…"] [--name "…"] [--config path] [--allow-risk]', '', 'Notes:', ' - If --task is omitted, the runner reads task text from stdin.', diff --git a/AGENTS.md b/AGENTS.md index f531081e..64462d32 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,7 +57,7 @@ npm run typecheck && npm run build - File names: kebab-case for action modules (for example `auth-check.ts`), `index.ts` for module entry points. - For `mcp-server` internals, tool names follow `browser_*`. For default runtime (browser-use), use `chrome.launch_cdp` / `browser.connect_cdp` / `page.*`. - Keep configuration JSON keys stable; prefer additive changes over renaming. -- Repo-local discoverable skills must live under `.codex/skills/` or `.claude/skills/` (optionally `.agents/skills/` only when the target client actually supports it). Do not invent parallel skill roots such as `.baoyu-skills/*/SKILL.md`; those are non-discoverable and should be plain docs or extension config only. +- Repo-local discoverable skills must live under `.codex/skills/`, `.claude/skills/`, or `.kiro/skills/` (optionally `.agents/skills/` only when the target client actually supports it). Do not invent parallel skill roots such as `.baoyu-skills/*/SKILL.md`; those are non-discoverable and should be plain docs or extension config only. ## Testing Guidelines Automated suites are available for both root AIOS workflows and `mcp-server`. @@ -117,7 +117,7 @@ For long tasks, announce the chosen route in the first progress update. - Trigger: when the user message is exactly `cap`, execute this flow in the current repo. - Required flow: 1. `git status --short` and confirm there are changes. - 2. If behavior/commands/workflow changed, sync impacted skill docs first (keep `.codex/skills/*` and `.claude/skills/*` aligned). + 2. If behavior/commands/workflow changed, sync impacted skill docs first (keep `.codex/skills/*`, `.claude/skills/*`, and `.kiro/skills/*` aligned). 3. `git add -A`. 4. Commit with a Conventional Commit message from current task context. 5. If no clear message is available, use fallback `chore: cap snapshot `. @@ -148,7 +148,7 @@ Use repo-local skills, agents, and bootstrap docs before falling back to ad-hoc ContextDB remains the shared runtime layer for memory, checkpoints, and execution evidence. -Wrapped `codex` / `claude` / `gemini` / `opencode` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. +Wrapped `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. Persona and user profile memory are part of the same runtime layer: - `aios memo persona ...` manages the global agent identity file (`~/.aios/SOUL.md` by default). @@ -172,6 +172,6 @@ For browser tasks, use this operating pattern unless the user explicitly asks ot ## AIOS Native Codex Layer -- Prefer repo-local `.codex/skills` and `.codex/agents`. +- Prefer repo-local `.codex/skills` and `.codex/agents` for Codex-specific workflows; use the matching `.claude/skills`, `.claude/agents`, and `.kiro/skills` / `.kiro/agents` roots when working in those clients. - Keep work grounded in the AIOS runtime and verification flow. diff --git a/CLAUDE.md b/CLAUDE.md index 6ea80239..f6c51a0d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -221,7 +221,7 @@ Use repo-local skills, agents, and bootstrap docs before falling back to ad-hoc ContextDB remains the shared runtime layer for memory, checkpoints, and execution evidence. -Wrapped `codex` / `claude` / `gemini` / `opencode` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. +Wrapped `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. Persona and user profile memory are part of the same runtime layer: - `aios memo persona ...` manages the global agent identity file (`~/.aios/SOUL.md` by default). diff --git a/README-zh.md b/README-zh.md index 02d06b20..b8a7d11f 100644 --- a/README-zh.md +++ b/README-zh.md @@ -1,6 +1,6 @@ # RexCLI (AIOS) -> 给 `codex` / `claude` / `gemini` / `opencode` 加上记忆、协作和验证能力的本地 Agent 工作流层。 +> 给 `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` 加上记忆、协作和验证能力的本地 Agent 工作流层。 [文档站](https://cli.rexai.top) | [快速开始](https://cli.rexai.top/zh/getting-started/) | [官方案例库](https://cli.rexai.top/zh/case-library/) | [GitHub](https://github.com/rexleimo/rex-cli) @@ -28,7 +28,7 @@ aios | 能力 | 说明 | 命令 | |------|------|------| -| **ContextDB** | 跨会话项目记忆,事件/检查点/上下文包持久化 | `codex` / `claude` 自动加载 | +| **ContextDB** | 跨会话项目记忆,事件/检查点/上下文包持久化 | `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` 自动加载 | | **Model Router** | Agent Team 智能多模型调度 — 按能力、成本、成功率匹配最优模型 | `node scripts/aios.mjs model-router route --task "..."` | | **Agent Team** | 多 Agent 并行协作,HUD 可视化追踪 | `aios team 3:codex "任务描述"` | | **Solo Harness** | 单 Agent 过夜长任务,可恢复、有运行日志 | `aios harness run --objective "目标" --worktree` | @@ -62,7 +62,7 @@ aios team status --provider codex --watch ## 工作原理 ```text -用户 → codex / claude / gemini +用户 → codex / claude / gemini / opencode / kiro-cli → zsh wrapper(透明包装) → ctx-agent.mjs(ContextDB 集成) → contextdb CLI(记忆持久化) @@ -70,7 +70,7 @@ aios team status --provider codex --watch → browser MCP(可选浏览器自动化) ``` -安装后,直接使用 `codex`、`claude`、`gemini` 命令即可,RexCLI 自动在后台加载项目记忆。 +安装后,直接使用 `codex`、`claude`、`gemini`、`opencode`、`kiro-cli` 命令即可,RexCLI 自动在后台加载项目记忆。Kiro 现在接入 deep native surface(steering、MCP、skills、agents、headless/solo);live route preview 现在会保持 `kiro-cli`,不再回退到 `codex`。 ## 文档 diff --git a/agent-sources/manifest.json b/agent-sources/manifest.json index e7ff6b75..f749eb10 100644 --- a/agent-sources/manifest.json +++ b/agent-sources/manifest.json @@ -2,6 +2,7 @@ "schemaVersion": 1, "generatedTargets": [ "claude", - "codex" + "codex", + "kiro" ] } diff --git a/client-sources/native-base/codex/project/AGENTS.md b/client-sources/native-base/codex/project/AGENTS.md index 061467db..008dd8c5 100644 --- a/client-sources/native-base/codex/project/AGENTS.md +++ b/client-sources/native-base/codex/project/AGENTS.md @@ -1,4 +1,4 @@ ## AIOS Native Codex Layer -- Prefer repo-local `.codex/skills` and `.codex/agents`. +- Prefer repo-local `.codex/skills` and `.codex/agents` for Codex-specific workflows; use the matching `.claude/skills`, `.claude/agents`, and `.kiro/skills` / `.kiro/agents` roots when working in those clients. - Keep work grounded in the AIOS runtime and verification flow. diff --git a/client-sources/native-base/shared/partials/contextdb.md b/client-sources/native-base/shared/partials/contextdb.md index c668b8cc..a45a8c70 100644 --- a/client-sources/native-base/shared/partials/contextdb.md +++ b/client-sources/native-base/shared/partials/contextdb.md @@ -1,6 +1,6 @@ ContextDB remains the shared runtime layer for memory, checkpoints, and execution evidence. -Wrapped `codex` / `claude` / `gemini` / `opencode` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. +Wrapped `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. Persona and user profile memory are part of the same runtime layer: - `aios memo persona ...` manages the global agent identity file (`~/.aios/SOUL.md` by default). diff --git a/config/native-sync-manifest.json b/config/native-sync-manifest.json index 75b311ff..06a3c9cb 100644 --- a/config/native-sync-manifest.json +++ b/config/native-sync-manifest.json @@ -40,6 +40,16 @@ ".opencode/AIOS.md", ".opencode/skills" ] + }, + "kiro": { + "tier": "deep", + "metadataRoot": ".kiro", + "outputs": [ + ".kiro/steering/AIOS.md", + ".kiro/settings/mcp.json", + ".kiro/agents", + ".kiro/skills" + ] } } } diff --git a/config/skills-catalog.json b/config/skills-catalog.json index 4607f747..c39e150b 100644 --- a/config/skills-catalog.json +++ b/config/skills-catalog.json @@ -5,7 +5,7 @@ "name": "aios-workflow-router", "description": "Route tasks to appropriate workflows. TRIGGERS: 分析,设计,实现,调试,并发,并行,agent team,长任务,harness,plan,brainstorm,debug,multi-step", "source": "skill-sources/aios-workflow-router", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -17,7 +17,7 @@ "name": "find-skills", "description": "Discover installable skills.", "source": "skill-sources/find-skills", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -29,7 +29,7 @@ "name": "debug-hub", "description": "Evidence-first debugging: inject zero-dependency log calls, collect via debug-hub HTTP API, analyze through MCP tools.", "source": "skill-sources/debug-hub", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": false, @@ -41,7 +41,7 @@ "name": "search-first", "description": "Research-before-building workflow.", "source": "skill-sources/search-first", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -53,7 +53,7 @@ "name": "awesome-design-md", "description": "Apply DESIGN.md templates from VoltAgent awesome-design-md via getdesign CLI.", "source": "skill-sources/awesome-design-md", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -65,7 +65,7 @@ "name": "frontend-design", "description": "Build distinctive production-grade frontend UI, including no-design-draft workflows with DESIGN.md integration.", "source": "skill-sources/frontend-design", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -77,7 +77,7 @@ "name": "security-scan", "description": "Lightweight security hygiene for agent configs.", "source": "skill-sources/security-scan", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -89,7 +89,7 @@ "name": "verification-loop", "description": "Evidence-before-assertions workflow.", "source": "skill-sources/verification-loop", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -101,7 +101,7 @@ "name": "versioning-by-impact", "description": "Decide semantic versioning impact before release.", "source": "skill-sources/versioning-by-impact", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -113,7 +113,7 @@ "name": "seo-geo-page-optimization", "description": "Optimize a page for SEO and GEO.", "source": "skill-sources/seo-geo-page-optimization", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": false, @@ -125,7 +125,7 @@ "name": "skill-constraints", "description": "Operational constraints and best practices for skill execution.", "source": "skill-sources/skill-constraints", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -137,7 +137,7 @@ "name": "aios-project-system", "description": "Canonical architecture and operating constraints for aios.", "source": "skill-sources/aios-project-system", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -149,7 +149,7 @@ "name": "aios-long-running-harness", "description": "Harness controls for long-running AIOS jobs.", "source": "skill-sources/aios-long-running-harness", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -161,7 +161,7 @@ "name": "harness-init-runner", "description": "Initialize a lightweight Node.js harness runner (harness/ + .harness/) to drive codex/claude/gemini/opencode CLIs with logs + checkpoints.", "source": "skill-sources/harness-init-runner", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": false, @@ -173,7 +173,7 @@ "name": "contextdb-autopilot", "description": "Automatic ContextDB persistence workflow.", "source": "skill-sources/contextdb-autopilot", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -185,7 +185,7 @@ "name": "cap-commit-push", "description": "Fast commit-and-push shortcut for this repository.", "source": "skill-sources/cap-commit-push", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, @@ -197,7 +197,7 @@ "name": "aios-jimeng-image-ops", "description": "Jimeng image generation workflow for aios.", "source": "skill-sources/aios-jimeng-image-ops", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": false, @@ -209,7 +209,7 @@ "name": "seed2-manga-drama", "description": "Seed2 style manga-drama generation workflow.", "source": "skill-sources/seed2-manga-drama", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": false, @@ -221,7 +221,7 @@ "name": "xhs-ops-methods", "description": "Reusable Xiaohongshu operations workflow.", "source": "skill-sources/xhs-ops-methods", - "clients": ["codex", "claude"], + "clients": ["codex", "claude", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": false, @@ -233,7 +233,7 @@ "name": "model-router", "description": "Intelligent multi-model dispatch for Agent Teams — match tasks to optimal model by capability, cost, and success rate. TRIGGERS: 模型调度, model dispatch, 选模型, 路由, routing, dispatch to model.", "source": "skill-sources/model-router", - "clients": ["codex", "claude", "gemini", "opencode"], + "clients": ["codex", "claude", "gemini", "opencode", "kiro"], "scopes": ["global", "project"], "defaultInstall": { "global": true, diff --git a/config/skills-sync-manifest.json b/config/skills-sync-manifest.json index 3510cc0e..28c7dbd7 100644 --- a/config/skills-sync-manifest.json +++ b/config/skills-sync-manifest.json @@ -5,113 +5,114 @@ "claude": ".claude/skills", "gemini": ".gemini/skills", "opencode": ".opencode/skills", + "kiro": ".kiro/skills", "agents": ".agents/skills" }, "skills": [ { "relativeSkillPath": "aios-workflow-router", "installCatalogName": "aios-workflow-router", - "repoTargets": ["codex", "claude", "gemini", "opencode", "agents"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro", "agents"] }, { "relativeSkillPath": "find-skills", "installCatalogName": "find-skills", - "repoTargets": ["codex", "claude", "gemini", "opencode", "agents"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro", "agents"] }, { "relativeSkillPath": "debug-hub", "installCatalogName": "debug-hub", - "repoTargets": ["codex", "claude", "gemini", "opencode", "agents"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro", "agents"] }, { "relativeSkillPath": "search-first", "installCatalogName": "search-first", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "awesome-design-md", "installCatalogName": "awesome-design-md", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "frontend-design", "installCatalogName": "frontend-design", - "repoTargets": ["codex", "gemini", "opencode"] + "repoTargets": ["codex", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "security-scan", "installCatalogName": "security-scan", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "verification-loop", "installCatalogName": "verification-loop", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "versioning-by-impact", "installCatalogName": "versioning-by-impact", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "seo-geo-page-optimization", "installCatalogName": "seo-geo-page-optimization", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "skill-constraints", "installCatalogName": "skill-constraints", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "aios-project-system", "installCatalogName": "aios-project-system", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "aios-long-running-harness", "installCatalogName": "aios-long-running-harness", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "contextdb-autopilot", "installCatalogName": "contextdb-autopilot", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "cap-commit-push", "installCatalogName": "cap-commit-push", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "aios-jimeng-image-ops", "installCatalogName": "aios-jimeng-image-ops", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "seed2-manga-drama", "installCatalogName": "seed2-manga-drama", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "xhs-ops-methods", "installCatalogName": "xhs-ops-methods", - "repoTargets": ["codex", "claude"] + "repoTargets": ["codex", "claude", "kiro"] }, { "relativeSkillPath": "harness-init-runner", "installCatalogName": "harness-init-runner", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": "model-router", "installCatalogName": "model-router", - "repoTargets": ["codex", "claude", "gemini", "opencode"] + "repoTargets": ["codex", "claude", "gemini", "opencode", "kiro"] }, { "relativeSkillPath": ".system/skill-creator", "installCatalogName": null, - "repoTargets": ["codex", "claude"], + "repoTargets": ["codex", "claude", "kiro"], "targetRelativePathBySurface": { "codex": ".system/skill-creator", "claude": "skill-creator" diff --git a/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md b/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md index 6066825b..126f1ea2 100644 --- a/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md +++ b/docs/superpowers/specs/2026-05-12-kiro-cli-support-design.md @@ -2,11 +2,11 @@ **Date**: 2026-05-12 **Status**: Draft -**Scope**: Add Kiro CLI as a first-class supported client in the AIOS workspace +**Scope**: Add Kiro CLI as a deep supported client in the AIOS workspace ## Problem -The repo currently treats `codex`, `claude`, `gemini`, and `opencode` as first-class clients. Kiro CLI is not represented in the client registry, shell bridge, native sync manifest, or docs. That means Kiro cannot receive the same ContextDB, steering, and MCP bootstrap behavior that the other clients get. +The repo currently treats `codex`, `claude`, `gemini`, `opencode`, and `kiro` as first-class clients. Kiro CLI is now represented across the client registry, shell bridge, native sync manifest, skills sync, and docs so it can receive the same ContextDB, steering, MCP bootstrap, skills, and agent behavior. ## Evidence @@ -14,37 +14,21 @@ The repo currently treats `codex`, `claude`, `gemini`, and `opencode` as first-c - `scripts/lib/platform/paths.mjs` only defines homes for those four clients. - `scripts/lib/components/shell.mjs` and `scripts/contextdb-shell.{zsh,ps1}` only wrap those four commands. - `config/native-sync-manifest.json` only emits native artifacts for those four clients. -- `scripts/lib/harness/subagent-runtime.mjs` only accepts `codex-cli`, `claude-code`, `gemini-cli`, `opencode-cli` as live subagent clients. +- `scripts/lib/harness/subagent-runtime.mjs` accepts `kiro-cli` for explicit live subagent execution. -Kiro CLI docs indicate a terminal CLI entrypoint plus workspace steering and MCP support, so it fits the same client-adapter pattern, but not necessarily the same live subagent runtime contract. +Kiro CLI docs indicate a terminal CLI entrypoint plus workspace steering, MCP support, custom agents, hooks, and skills. The repository maps those capabilities into the same deep client-adapter pattern used by Codex and Claude. ## Recommendation -Implement Kiro in two stages. - -### Stage 1: Compatibility client - -Add Kiro to the repo's client surface so it can receive the same project memory, shell bridge, steering sync, and MCP bootstrap behavior as the other clients. - -Kiro does not use the repo's existing skill-pack layout as-is, so stage 1 should treat `skills` as unsupported for Kiro unless a Kiro-specific discoverable format is added later. - -### Stage 2: Runtime execution - -Only add Kiro to `team` / `subagent` / `harness` live execution if the CLI can produce a stable structured handoff that matches the current runtime contract. +Add Kiro to the repo's client surface so it can receive the same project memory, shell bridge, steering sync, MCP bootstrap, skills, agent generation, and explicit runtime execution behavior as the other clients. ## Options -### Option A: Compatibility client only - -Add `kiro` / `kiro-cli` to the registry, shell bridge, native sync, and docs. Keep live team/subagent execution out of scope for now. - -Trade-off: lowest risk, delivers immediate value, and matches what the repo already does for other compatibility clients. - -### Option B: Full runtime client +### Option A: Deep native client -Add Kiro to the compatibility layer and also to live `team` / `subagent` / `harness` execution paths. +Add `kiro` / `kiro-cli` to the registry, shell bridge, native sync, agent generation, skills sync, and live runtime paths. -Trade-off: more complete, but riskier because the current runtime assumes specific agent command shapes and structured outputs. +Trade-off: more work up front, but it aligns the implementation with how Codex and Claude are treated. ### Option C: Alias-only support @@ -74,7 +58,7 @@ Trade-off: too shallow; it would not make Kiro a real supported client in the re - Add `kiro` to the client lists used by setup/update/uninstall and home-dir resolution. - Treat Kiro home as `~/.kiro` by default, with optional `KIRO_HOME` override. -- Keep `skills` as a warning/skip path for Kiro in stage 1; steering should come from native sync instead. +- Generate `.kiro/agents/*.json`, `.kiro/steering/AIOS.md`, `.kiro/settings/mcp.json`, and `.kiro/skills`. ### Shell bridge @@ -90,20 +74,18 @@ Trade-off: too shallow; it would not make Kiro a real supported client in the re ### Docs and help - Update supported-client lists and examples to mention Kiro where the repo describes supported clients. -- Keep the wording explicit that Kiro is supported as a compatibility client first, not yet as a live subagent runtime. +- Keep the wording explicit that Kiro is supported as a deep client with explicit live runtime selection. ## Acceptance Criteria - `setup`, `update`, and `uninstall` accept `--client kiro`. - The shell bridge can wrap Kiro CLI without breaking existing commands. -- Native sync can materialize Kiro workspace files. +- Native sync can materialize Kiro workspace files, including agents and skills. - Existing clients still pass their current tests unchanged. - The docs clearly state Kiro support level and any runtime limits. ## Out of Scope For Stage 1 -- Adding Kiro to `AIOS_SUBAGENT_CLIENT`. -- Adding Kiro to `team` / `subagent` live execution providers. - Changing the existing codex/claude/gemini/opencode behavior. ## Open Assumption diff --git a/scripts/contextdb-shell-bridge.mjs b/scripts/contextdb-shell-bridge.mjs index 3a702fc3..3f86e68e 100644 --- a/scripts/contextdb-shell-bridge.mjs +++ b/scripts/contextdb-shell-bridge.mjs @@ -21,10 +21,11 @@ const KNOWN_ENDPOINT_ENV_NAMES = new Set([ 'CLAUDE_BASE_URL', 'CLAUDE_CODE_BASE_URL', 'OPENCODE_BASE_URL', + 'KIRO_BASE_URL', 'OPENROUTER_BASE_URL', ]); -const MODEL_ENDPOINT_NAME_RE = /(?:OPENAI|ANTHROPIC|GOOGLE|GEMINI|CODEX|CLAUDE|OPENCODE|OPENROUTER|LLM|MODEL).*(?:BASE_URL|API_BASE|API_URL|ENDPOINT)$/u; +const MODEL_ENDPOINT_NAME_RE = /(?:OPENAI|ANTHROPIC|GOOGLE|GEMINI|CODEX|CLAUDE|OPENCODE|KIRO|OPENROUTER|LLM|MODEL).*(?:BASE_URL|API_BASE|API_URL|ENDPOINT)$/u; const OFFICIAL_ENDPOINT_SUFFIXES = [ 'api.openai.com', 'openai.com', @@ -56,11 +57,16 @@ const BLOCKED_SUBCOMMANDS = { 'uninstall', 'serve', 'web', 'models', 'stats', 'export', 'import', 'github', 'pr', 'session', 'db', '-h', '--help', '-v', '--version', ]), + 'kiro-cli': new Set([ + 'agent', 'integrations', 'inline', 'mcp', 'settings', 'login', 'logout', 'auth', + 'doctor', 'diagnostic', 'install', 'update', 'upgrade', 'completion', + '-h', '--help', '-v', '--version', + ]), }; function usage() { console.log(`Usage: - node scripts/contextdb-shell-bridge.mjs --agent --command [--cwd ] [-- ] + node scripts/contextdb-shell-bridge.mjs --agent --command [--cwd ] [-- ] Environment: ROOTPATH Repo root containing scripts/ctx-agent.mjs @@ -70,7 +76,7 @@ Environment: CTXDB_MARKER_FILE Marker filename for opt-in mode (default: .contextdb-enable) CTXDB_AUTO_CREATE_MARKER 1/true/yes/on to auto-create marker in opt-in mode (default: on) CTXDB_INTERACTIVE_AUTO_ROUTE 1/true/yes/on to inject route auto prompt in interactive mode (default: on) - CTXDB_HARNESS_PROVIDER codex|claude|gemini|opencode for injected harness route (default: current CLI) + CTXDB_HARNESS_PROVIDER codex|claude|gemini|opencode|kiro for injected harness route (default: current CLI) CTXDB_HARNESS_MAX_ITERATIONS Positive integer for injected harness route (default: 8) CTXDB_PRIVACY_BANNER 0/false/off to hide the interactive privacy banner (default: on) CTXDB_PRIVACY_COLOR 0/false/off to disable banner ANSI color (default: on unless NO_COLOR is set) @@ -519,12 +525,14 @@ function normalizeTeamProvider(value) { function inferTeamProviderFromCommand(command) { if (command === 'claude') return 'claude'; if (command === 'gemini') return 'gemini'; + if (command === 'kiro-cli') return 'kiro'; return 'codex'; } function inferSubagentClientFromProvider(provider) { if (provider === 'claude') return 'claude-code'; if (provider === 'gemini') return 'gemini-cli'; + if (provider === 'kiro') return 'kiro-cli'; return 'codex-cli'; } @@ -533,12 +541,13 @@ function inferSubagentClientFromCommand(command) { if (command === 'gemini') return 'gemini-cli'; if (command === 'codex') return 'codex-cli'; if (command === 'opencode') return 'opencode-cli'; + if (command === 'kiro-cli') return 'kiro-cli'; return ''; } function normalizeSubagentClient(value) { const normalized = String(value || '').trim().toLowerCase(); - if (normalized === 'codex-cli' || normalized === 'claude-code' || normalized === 'gemini-cli' || normalized === 'opencode-cli') { + if (normalized === 'codex-cli' || normalized === 'claude-code' || normalized === 'gemini-cli' || normalized === 'opencode-cli' || normalized === 'kiro-cli') { return normalized; } return ''; @@ -554,6 +563,10 @@ function resolveSubagentClientForPrompt(command, provider, env) { return inferSubagentClientFromProvider(provider); } +function resolveRoutePreviewAgent(agent) { + return agent; +} + function buildInteractiveAutoPrompt({ agent = 'codex-cli', command = 'codex', @@ -574,8 +587,9 @@ function buildInteractiveAutoPrompt({ || normalizeHarnessProvider(env.AIOS_HARNESS_PROVIDER) || inferHarnessProviderFromCommand(command); const harnessMaxIterations = parsePositiveInteger(env.CTXDB_HARNESS_MAX_ITERATIONS || env.AIOS_HARNESS_MAX_ITERATIONS, 8); + const routePreviewAgent = resolveRoutePreviewAgent(agent); const teamCommand = buildCtxAgentRoutePreview({ - agent, + agent: routePreviewAgent, workspaceRoot, project, routeMode: 'team', @@ -690,15 +704,15 @@ function spawnInherited(command, args, cwd, env) { } function validateOptions(opts) { - const validAgents = new Set(['codex-cli', 'claude-code', 'gemini-cli', 'opencode-cli']); - const validCommands = new Set(['codex', 'claude', 'gemini', 'opencode']); + const validAgents = new Set(['codex-cli', 'claude-code', 'gemini-cli', 'opencode-cli', 'kiro-cli']); + const validCommands = new Set(['codex', 'claude', 'gemini', 'opencode', 'kiro-cli']); if (!validAgents.has(opts.agent)) { - throw new Error('--agent must be one of: codex-cli, claude-code, gemini-cli, opencode-cli'); + throw new Error('--agent must be one of: codex-cli, claude-code, gemini-cli, opencode-cli, kiro-cli'); } if (!validCommands.has(opts.command)) { - throw new Error('--command must be one of: codex, claude, gemini, opencode'); + throw new Error('--command must be one of: codex, claude, gemini, opencode, kiro-cli'); } } diff --git a/scripts/contextdb-shell.ps1 b/scripts/contextdb-shell.ps1 index b7616f0d..63eb2d77 100644 --- a/scripts/contextdb-shell.ps1 +++ b/scripts/contextdb-shell.ps1 @@ -1,5 +1,5 @@ # ContextDB transparent command wrappers for PowerShell. -# Source this file in PowerShell profile to make codex/claude/gemini/opencode auto-load context packets. +# Source this file in PowerShell profile to make codex/claude/gemini/opencode/kiro-cli auto-load context packets. # Optional env vars: # - ROOTPATH # - CTXDB_SHELL_BRIDGE @@ -149,6 +149,18 @@ function opencode { $global:LASTEXITCODE = Invoke-BridgeOrPassthrough -Agent "opencode-cli" -Passthrough "opencode" -Arguments $Args } +function kiro-cli { + param([Parameter(ValueFromRemainingArguments = $true)] [string[]]$Args) + + $global:LASTEXITCODE = Invoke-BridgeOrPassthrough -Agent "kiro-cli" -Passthrough "kiro-cli" -Arguments $Args +} + +function kiro { + param([Parameter(ValueFromRemainingArguments = $true)] [string[]]$Args) + + kiro-cli @Args +} + function aios { param([Parameter(ValueFromRemainingArguments = $true)] [string[]]$Args) diff --git a/scripts/contextdb-shell.zsh b/scripts/contextdb-shell.zsh index c1e2c433..aec794ac 100644 --- a/scripts/contextdb-shell.zsh +++ b/scripts/contextdb-shell.zsh @@ -1,5 +1,5 @@ # ContextDB transparent command wrappers for zsh. -# Source this file in ~/.zshrc to make codex/claude/gemini/opencode auto-load context packets. +# Source this file in ~/.zshrc to make codex/claude/gemini/opencode/kiro-cli auto-load context packets. # # Optional overrides: # - ROOTPATH: repo root where scripts/contextdb-shell-bridge.mjs lives @@ -108,6 +108,14 @@ opencode() { ctxdb_invoke_bridge_or_passthrough opencode-cli opencode "$@" } +kiro-cli() { + ctxdb_invoke_bridge_or_passthrough kiro-cli kiro-cli "$@" +} + +kiro() { + kiro-cli "$@" +} + aios() { local sub="${1:-}" shift || true diff --git a/scripts/ctx-agent-core.mjs b/scripts/ctx-agent-core.mjs index 5b1c6995..64d26b9e 100755 --- a/scripts/ctx-agent-core.mjs +++ b/scripts/ctx-agent-core.mjs @@ -27,10 +27,10 @@ const MCP_DIR = path.join(ROOT_DIR, 'mcp-server'); const CTX_AGENT_CLI_PATH = path.join(ROOT_DIR, 'scripts', 'ctx-agent.mjs'); const ROUTE_MODES = new Set(['auto', 'single', 'team', 'subagent', 'harness']); const ROUTE_EXECUTION_MODES = new Set(['dry-run', 'live']); -const TEAM_ROUTE_PROVIDERS = new Set(['auto', 'codex', 'claude', 'gemini']); -const HARNESS_ROUTE_PROVIDERS = new Set(['auto', 'codex', 'claude', 'gemini', 'opencode']); +const TEAM_ROUTE_PROVIDERS = new Set(['auto', 'codex', 'claude', 'gemini', 'kiro']); +const HARNESS_ROUTE_PROVIDERS = new Set(['auto', 'codex', 'claude', 'gemini', 'opencode', 'kiro']); const ORCHESTRATE_BLUEPRINTS = new Set(['feature', 'bugfix', 'refactor', 'security']); -const SUPPORTED_SUBAGENT_CLIENT_IDS = new Set(['codex-cli', 'claude-code', 'gemini-cli', 'opencode-cli']); +const SUPPORTED_SUBAGENT_CLIENT_IDS = new Set(['codex-cli', 'claude-code', 'gemini-cli', 'opencode-cli', 'kiro-cli']); const CTXDB_CODEX_DISABLE_MCP_ENV = 'CTXDB_CODEX_DISABLE_MCP'; const TEAM_ROUTE_KEYWORD_PATTERNS = [ /并行|并发|同时推进|拆分|多模块|跨模块|跨系统|多阶段/u, @@ -41,10 +41,10 @@ const HARNESS_ROUTE_KEYWORD_PATTERN = /\b(harness|overnight|long[-\s]?running|re function usage() { console.log(`Usage: - scripts/ctx-agent.mjs --agent [options] [-- ] + scripts/ctx-agent.mjs --agent [options] [-- ] Options: - --agent Agent name: claude-code | gemini-cli | codex-cli | opencode-cli + --agent Agent name: claude-code | gemini-cli | codex-cli | opencode-cli | kiro-cli --workspace Workspace root to store context-db (default: current git root, else current dir) --project Project name (default: current directory name) --goal Session goal (used when creating a new session) @@ -170,7 +170,7 @@ function normalizeRouteExecutionMode(rawValue = 'dry-run') { function normalizeTeamRouteProvider(rawValue = 'auto') { const value = String(rawValue || 'auto').trim().toLowerCase(); if (!TEAM_ROUTE_PROVIDERS.has(value)) { - throw new Error('--team-provider must be one of: auto, codex, claude, gemini'); + throw new Error('--team-provider must be one of: auto, codex, claude, gemini, kiro'); } return value; } @@ -186,7 +186,7 @@ function normalizeOrchestrateBlueprint(rawValue = 'feature') { function normalizeHarnessRouteProvider(rawValue = 'auto') { const value = String(rawValue || 'auto').trim().toLowerCase(); if (!HARNESS_ROUTE_PROVIDERS.has(value)) { - throw new Error('--harness-provider must be one of: auto, codex, claude, gemini, opencode'); + throw new Error('--harness-provider must be one of: auto, codex, claude, gemini, opencode, kiro'); } return value; } @@ -196,6 +196,7 @@ function inferHarnessProviderFromAgent(agent = '') { if (normalized === 'claude-code') return 'claude'; if (normalized === 'gemini-cli') return 'gemini'; if (normalized === 'opencode-cli') return 'opencode'; + if (normalized === 'kiro-cli') return 'kiro'; return 'codex'; } @@ -203,12 +204,18 @@ function inferTeamProviderFromAgent(agent = '') { const normalized = String(agent || '').trim().toLowerCase(); if (normalized === 'claude-code') return 'claude'; if (normalized === 'gemini-cli') return 'gemini'; + if (normalized === 'kiro-cli') return 'kiro'; return 'codex'; } +export function resolveRoutePreviewAgent(agent = '') { + return String(agent || '').trim().toLowerCase(); +} + function inferSubagentClientFromProvider(provider = 'codex') { if (provider === 'claude') return 'claude-code'; if (provider === 'gemini') return 'gemini-cli'; + if (provider === 'kiro') return 'kiro-cli'; return 'codex-cli'; } @@ -245,6 +252,20 @@ function buildCodexMcpDisableArgs(env = process.env) { return ['-c', 'mcp_servers={}', '-c', 'features.rmcp_client=false']; } +export function buildKiroChatArgs(extraArgs = [], { headless = false } = {}) { + const values = Array.isArray(extraArgs) ? [...extraArgs] : [extraArgs]; + const passthroughArgs = values[0] === 'chat' ? values.slice(1) : values; + const args = ['chat']; + if (headless) { + args.push('--no-interactive'); + } + args.push('--trust-all-tools'); + if (passthroughArgs.length > 0) { + args.push(...passthroughArgs); + } + return args; +} + function formatShellArg(value = '') { const text = String(value ?? ''); return /^[A-Za-z0-9_./:@=-]+$/u.test(text) ? text : JSON.stringify(text); @@ -262,7 +283,7 @@ function buildCtxAgentRoutePreview({ blueprint = 'feature', taskPrompt = '', } = {}) { - const args = [CTX_AGENT_CLI_PATH, '--agent', agent]; + const args = [CTX_AGENT_CLI_PATH, '--agent', resolveRoutePreviewAgent(agent)]; if (String(workspaceRoot || '').trim()) { args.push('--workspace', String(workspaceRoot).trim()); } @@ -363,13 +384,14 @@ function buildTaskRouterGuide({ const workers = parsePositiveInteger(teamWorkers, 3); const resolvedBlueprint = normalizeOrchestrateBlueprint(blueprint); const resolvedRouteMode = normalizeRouteMode(routeMode); + const routePreviewAgent = resolveRoutePreviewAgent(agent); const subagentClient = resolveRoutedSubagentClient({ agent, teamProvider: provider, env: process.env, }); const teamCommand = buildCtxAgentRoutePreview({ - agent, + agent: routePreviewAgent, workspaceRoot, project, sessionId, @@ -449,13 +471,14 @@ function buildInteractiveRouteAutoPrompt({ : normalizeTeamRouteProvider(teamProvider); const workers = parsePositiveInteger(teamWorkers, 3); const resolvedBlueprint = normalizeOrchestrateBlueprint(blueprint); + const routePreviewAgent = resolveRoutePreviewAgent(agent); const subagentClient = resolveRoutedSubagentClient({ agent, teamProvider: provider, env: process.env, }); const teamCommand = buildCtxAgentRoutePreview({ - agent, + agent: routePreviewAgent, workspaceRoot, project, sessionId, @@ -1211,9 +1234,9 @@ function validateOpts(opts) { if (!opts.agent) { throw new Error('Missing required --agent'); } - const validAgents = new Set(['claude-code', 'gemini-cli', 'codex-cli', 'opencode-cli']); + const validAgents = new Set(['claude-code', 'gemini-cli', 'codex-cli', 'opencode-cli', 'kiro-cli']); if (!validAgents.has(opts.agent)) { - throw new Error('--agent must be one of: claude-code, gemini-cli, codex-cli, opencode-cli'); + throw new Error('--agent must be one of: claude-code, gemini-cli, codex-cli, opencode-cli, kiro-cli'); } const validStatus = new Set(['running', 'blocked', 'done']); if (!validStatus.has(opts.checkpointStatus)) { @@ -1334,7 +1357,7 @@ function extractCreatedSessionId(jsonText) { return parseJsonValue(jsonText, (x) => x?.data?.sessionId || x?.sessionId); } -function runOneShotAgent(agent, contextText, prompt, extraArgs, { injectContext = true, contextPacketPath = '' } = {}) { +export function runOneShotAgent(agent, contextText, prompt, extraArgs, { injectContext = true, contextPacketPath = '' } = {}) { let cmd = ''; let args = []; @@ -1368,6 +1391,12 @@ function runOneShotAgent(agent, contextText, prompt, extraArgs, { injectContext const exitCode = result.status ?? 1; return { output, exitCode }; } + } else if (agent === 'kiro-cli') { + cmd = 'kiro-cli'; + const fullPrompt = injectContext + ? `${contextText}\n\n## New User Request\n${prompt}` + : prompt; + args = [...buildKiroChatArgs(extraArgs, { headless: true }), fullPrompt]; } else { cmd = 'opencode'; const fullPrompt = buildOpenCodePrompt({ @@ -1737,6 +1766,33 @@ function runInteractiveAgent( console.log(`Auto prompt: enabled (${promptSource})`); } args = combinedPrompt ? [...codexConfigArgs, ...extraArgs, combinedPrompt] : [...codexConfigArgs, ...extraArgs]; + } else if (agent === 'kiro-cli') { + cmd = 'kiro-cli'; + const effectiveAutoPrompt = explicitAutoPrompt + ? explicitAutoPrompt + : shouldInject + ? '' + : (autoPrompt || buildInteractiveRouteAutoPrompt({ + agent, + workspaceRoot, + project, + teamProvider, + teamWorkers, + harnessProvider, + harnessMaxIterations, + blueprint, + sessionId, + })); + let combinedPrompt = shouldInject ? contextText : ''; + if (effectiveAutoPrompt) { + combinedPrompt = combinedPrompt + ? `${combinedPrompt}\n\n## Auto Prompt\n${effectiveAutoPrompt}` + : effectiveAutoPrompt; + const promptSource = explicitAutoPrompt ? 'env' : 'context handoff'; + console.log(`Auto prompt: enabled (${promptSource})`); + } + const chatArgs = buildKiroChatArgs(extraArgs, { headless: false }); + args = combinedPrompt ? [...chatArgs, combinedPrompt] : chatArgs; } else { cmd = 'opencode'; const promptText = buildOpenCodePrompt({ diff --git a/scripts/lib/agents/emitters/kiro.mjs b/scripts/lib/agents/emitters/kiro.mjs new file mode 100644 index 00000000..41f7f87a --- /dev/null +++ b/scripts/lib/agents/emitters/kiro.mjs @@ -0,0 +1,94 @@ +import { + ORCHESTRATOR_AGENT_MARKER, + ORCHESTRATOR_AGENT_MARKER_END, +} from './shared.mjs'; + +const TOOL_MAP = { + Bash: 'shell', + Edit: 'write', + Glob: 'glob', + Grep: 'grep', + Read: 'read', +}; + +function normalizeId(value) { + return String(value || '').trim(); +} + +function normalizeRoleId(value) { + return normalizeId(value).toLowerCase(); +} + +function normalizeAgentId(value) { + const id = normalizeId(value).toLowerCase(); + return id.length > 0 ? id : ''; +} + +function normalizeTools(value) { + const rawTools = Array.isArray(value) ? value : [value]; + const normalized = rawTools + .map((tool) => TOOL_MAP[normalizeId(tool)] || normalizeId(tool).toLowerCase()) + .filter(Boolean); + return [...new Set(normalized)]; +} + +function renderPrompt(agent) { + return [ + ORCHESTRATOR_AGENT_MARKER, + '', + `Role: ${agent.role || '(unknown)'}`, + '', + agent.systemPrompt || 'You are a role-based subagent for AIOS orchestrations.', + '', + 'Output Contract', + 'Output a single JSON object (no surrounding text) that conforms to `memory/specs/agent-handoff.schema.json`.', + '', + 'Required fields:', + '- schemaVersion', + '- status', + '- fromRole', + '- toRole', + '- taskTitle', + '- contextSummary', + '- findings', + '- filesTouched', + '- openQuestions', + '- recommendations', + '', + `Set \`fromRole=${agent.role || 'unknown'}\` and \`toRole=${agent.handoffTarget || 'next-phase'}\`.`, + '', + ORCHESTRATOR_AGENT_MARKER_END, + ].join('\n'); +} + +export function renderKiroAgent(rawAgent = {}) { + const agent = { + id: normalizeAgentId(rawAgent?.id || rawAgent?.name), + name: normalizeAgentId(rawAgent?.name || rawAgent?.id), + description: normalizeId(rawAgent?.description), + tools: normalizeTools(rawAgent?.tools), + role: normalizeRoleId(rawAgent?.role), + handoffTarget: normalizeId(rawAgent?.handoffTarget || 'next-phase'), + systemPrompt: normalizeId(rawAgent?.systemPrompt), + }; + const tools = agent.tools.length > 0 ? agent.tools : ['read', 'grep', 'glob']; + + const config = { + name: agent.name || agent.id, + description: agent.description, + prompt: renderPrompt(agent), + resources: [ + 'file://.kiro/steering/**/*.md', + 'skill://.kiro/skills/**/SKILL.md', + 'skill://~/.kiro/skills/**/SKILL.md', + ], + includeMcpJson: true, + tools, + allowedTools: tools, + }; + + return { + targetRelPath: `.kiro/agents/${agent.id || agent.name}.json`, + content: `${JSON.stringify(config, null, 2)}\n`, + }; +} diff --git a/scripts/lib/agents/source-tree.mjs b/scripts/lib/agents/source-tree.mjs index c44f9166..a6763c04 100644 --- a/scripts/lib/agents/source-tree.mjs +++ b/scripts/lib/agents/source-tree.mjs @@ -57,13 +57,13 @@ export function validateManifest(raw = {}) { assertCondition(raw.schemaVersion === 1, 'manifest schemaVersion must be 1'); assertCondition(Array.isArray(raw.generatedTargets), 'manifest generatedTargets must be an array'); assertCondition( - JSON.stringify(raw.generatedTargets) === JSON.stringify(['claude', 'codex']), - 'manifest generatedTargets must equal ["claude", "codex"]' + JSON.stringify(raw.generatedTargets) === JSON.stringify(['claude', 'codex', 'kiro']), + 'manifest generatedTargets must equal ["claude", "codex", "kiro"]' ); return { schemaVersion: 1, - generatedTargets: ['claude', 'codex'], + generatedTargets: ['claude', 'codex', 'kiro'], }; } diff --git a/scripts/lib/agents/sync.mjs b/scripts/lib/agents/sync.mjs index d826457e..62dc567b 100644 --- a/scripts/lib/agents/sync.mjs +++ b/scripts/lib/agents/sync.mjs @@ -3,6 +3,7 @@ import path from 'node:path'; import { renderCompatibilityExport } from './compat-export.mjs'; import { renderClaudeAgent } from './emitters/claude.mjs'; +import { renderKiroAgent } from './emitters/kiro.mjs'; import { writeFileAtomic } from '../fs/atomic-write.mjs'; import { ORCHESTRATOR_AGENT_MARKER, @@ -14,6 +15,13 @@ import { loadCanonicalAgents } from './source-tree.mjs'; const TARGET_ROOTS = { claude: '.claude/agents', codex: '.codex/agents', + kiro: '.kiro/agents', +}; + +const TARGET_EXTENSIONS = { + claude: '.md', + codex: '.md', + kiro: '.json', }; function assertCondition(condition, message) { @@ -52,11 +60,11 @@ async function readOptional(absPath) { } } -async function listMarkdownFiles(absDir) { +async function listTargetFiles(absDir, extension) { try { const entries = await readdir(absDir, { withFileTypes: true }); return entries - .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md')) + .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(extension)) .map((entry) => entry.name); } catch (error) { if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') { @@ -74,6 +82,7 @@ function buildEmitterMap(emitters = {}) { return { claude: emitters.claude || renderClaudeAgent, codex: emitters.codex || renderCodexAgent, + kiro: emitters.kiro || renderKiroAgent, }; } @@ -81,7 +90,8 @@ export function resolveAgentTargets(client = 'all') { const normalized = String(client || 'all').trim().toLowerCase(); if (normalized === 'claude') return ['claude']; if (normalized === 'codex') return ['codex']; - return ['claude', 'codex']; + if (normalized === 'kiro') return ['kiro']; + return ['claude', 'codex', 'kiro']; } export function isManagedAgentMarkdown(content, expectedId) { @@ -104,6 +114,30 @@ export function isManagedAgentMarkdown(content, expectedId) { && nonEmptyLines[nonEmptyLines.length - 1] === ORCHESTRATOR_AGENT_MARKER_END; } +export function isManagedAgentJson(content, expectedId) { + let parsed; + try { + parsed = JSON.parse(normalizeNewlines(content)); + } catch { + return false; + } + + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false; + if (parsed.name !== expectedId) return false; + if (typeof parsed.prompt !== 'string') return false; + + const prompt = normalizeNewlines(parsed.prompt); + return prompt.includes(ORCHESTRATOR_AGENT_MARKER) + && prompt.includes(ORCHESTRATOR_AGENT_MARKER_END); +} + +function isManagedAgentFile(target, content, expectedId) { + if (target === 'kiro') { + return isManagedAgentJson(content, expectedId); + } + return isManagedAgentMarkdown(content, expectedId); +} + function buildExpectedFiles({ source, targets, mode, emitters }) { const emitterMap = buildEmitterMap(emitters); const byTarget = new Map(targets.map((target) => [target, new Map()])); @@ -147,7 +181,7 @@ function createDefaultFsOps() { export async function syncCanonicalAgents({ rootDir, - targets = ['claude', 'codex'], + targets = ['claude', 'codex', 'kiro'], mode = 'install', writeCompatibilityExport = true, io = console, @@ -186,15 +220,16 @@ export async function syncCanonicalAgents({ assertCondition(targetRoot, `unsupported target: ${target}`); const absDir = path.join(rootDir, targetRoot); - const existingFiles = await listMarkdownFiles(absDir); + const targetExtension = TARGET_EXTENSIONS[target] || '.md'; + const existingFiles = await listTargetFiles(absDir, targetExtension); const expectedForTarget = expectedFiles.get(target) || new Map(); for (const fileName of existingFiles) { const relPath = path.join(targetRoot, fileName); const absPath = path.join(rootDir, relPath); const existing = await readFile(absPath, 'utf8'); - const expectedId = path.basename(fileName, '.md'); - const managed = isManagedAgentMarkdown(existing, expectedId); + const expectedId = path.basename(fileName, targetExtension); + const managed = isManagedAgentFile(target, existing, expectedId); if (hasManagedMarker(existing) && !managed) { throw new Error(`malformed managed file: ${relPath}`); diff --git a/scripts/lib/cli/help.mjs b/scripts/lib/cli/help.mjs index 9fd7a0a8..37690aab 100644 --- a/scripts/lib/cli/help.mjs +++ b/scripts/lib/cli/help.mjs @@ -14,7 +14,7 @@ Commands: perception Content outcome recording, insight generation, and perception summary quality-gate Run repo quality checks with harness profiles orchestrate Preview reusable subagent workflow blueprints - team One-click multi-client live team runtime (codex/claude/gemini) + team One-click multi-client live team runtime (codex/claude/gemini/kiro) harness Solo overnight harness with run journal + resume controls hud Show ContextDB + dispatch HUD (CLI/TUI) learn-eval Turn checkpoint telemetry into operator recommendations @@ -65,7 +65,7 @@ export function getCommandHelpText(command) { Options: --components Comma list: browser,shell,skills,native,agents,superpowers (default: browser,shell,skills,native,superpowers) --mode - --client + --client --scope Skills install scope (default: global) --install-mode Skills install mode (default: copy) --skills Comma list of skill names to install @@ -80,7 +80,7 @@ Options: Options: --components Comma list: browser,shell,skills,native,agents,superpowers (default: browser,shell,skills,native,superpowers) --mode - --client + --client --scope Skills install scope (default: global) --install-mode Skills install mode (default: copy) --skills Comma list of skill names to install @@ -94,7 +94,7 @@ Options: Options: --components Comma list: shell,skills,native,agents,browser,superpowers (default: shell,skills) - --client + --client --scope Skills uninstall scope (default: global) --skills Comma list of skill names to uninstall -h, --help @@ -173,7 +173,7 @@ Options: --limit Number of checkpoints to inspect when loading learn-eval --recommendation Pin a specific learn-eval recommendation to the overlay --dispatch Build a local dispatch skeleton (defaults to local when omitted) - --execute Execute dispatch through the selected runtime (defaults to dry-run; live is opt-in via AIOS_EXECUTE_LIVE=1 + AIOS_SUBAGENT_CLIENT=) + --execute Execute dispatch through the selected runtime (defaults to dry-run; live is opt-in via AIOS_EXECUTE_LIVE=1 + AIOS_SUBAGENT_CLIENT=) --force Override live safety guards (retry-blocked instability and unknown capability surfaces) --preflight Run supported local gate/runbook actions before final DAG selection AIOS_SUBAGENT_PRE_MUTATION_SNAPSHOT=1 (env) In live mode, capture pre-mutation backups for editable phase owned paths before each subagent run @@ -202,7 +202,7 @@ Examples: Options: --workers Team worker concurrency (default: 3) - --provider + --provider --blueprint --task --context <summary> @@ -259,7 +259,7 @@ Options: --objective <text> (run) Required objective for a new solo harness run --session <id> Explicit ContextDB session id --workspace <path> Workspace root for ContextDB session artifacts (default: current directory) - --provider <codex|claude|gemini|opencode> (run) Provider used by the solo harness + --provider <codex|claude|gemini|opencode|kiro> (run) Provider used by the solo harness --profile <minimal|standard|strict> (run) Harness profile for surrounding checks --worktree (run) Execute inside an isolated git worktree --base-ref <ref> (run) Git ref used to seed worktree mode (default: HEAD) @@ -277,7 +277,7 @@ Options: Options: --session <id> Explicit ContextDB session id --workspace <path> Workspace root for ContextDB session artifacts (default: current directory) - --provider <codex|claude|gemini> + --provider <codex|claude|gemini|kiro> --preset <minimal|focused|full> Rendering preset (default: focused; with --watch defaults to minimal unless --preset provided) --watch Refresh display on an interval (TTY-only) --fast In --watch + minimal preset, skip heavy reads and throttle state refresh to ~1s @@ -410,25 +410,25 @@ export function getInternalHelpText(target, action) { if (target === 'skills' && (action === 'install' || action === 'update')) { return `Usage: - node scripts/aios.mjs internal skills ${action} [--client <all|codex|claude|gemini|opencode>] [--scope <global|project>] [--install-mode <copy|link>] [--skills <list>] [--force] + node scripts/aios.mjs internal skills ${action} [--client <all|codex|claude|gemini|opencode|kiro>] [--scope <global|project>] [--install-mode <copy|link>] [--skills <list>] [--force] `; } if (target === 'skills' && (action === 'uninstall' || action === 'doctor')) { return `Usage: - node scripts/aios.mjs internal skills ${action} [--client <all|codex|claude|gemini|opencode>] [--scope <global|project>] [--skills <list>] + node scripts/aios.mjs internal skills ${action} [--client <all|codex|claude|gemini|opencode|kiro>] [--scope <global|project>] [--skills <list>] `; } if (target === 'native' && (action === 'install' || action === 'update' || action === 'uninstall')) { return `Usage: - node scripts/aios.mjs internal native ${action} [--client <all|codex|claude|gemini|opencode>] + node scripts/aios.mjs internal native ${action} [--client <all|codex|claude|gemini|opencode|kiro>] `; } if (target === 'native' && action === 'doctor') { return `Usage: - node scripts/aios.mjs internal native doctor [--client <all|codex|claude|gemini|opencode>] [--verbose] [--fix] [--dry-run] + node scripts/aios.mjs internal native doctor [--client <all|codex|claude|gemini|opencode|kiro>] [--verbose] [--fix] [--dry-run] `; } diff --git a/scripts/lib/cli/parse-args.mjs b/scripts/lib/cli/parse-args.mjs index d477b135..0061317a 100644 --- a/scripts/lib/cli/parse-args.mjs +++ b/scripts/lib/cli/parse-args.mjs @@ -36,13 +36,14 @@ import { normalizeOrchestratorBlueprint, normalizeOrchestratorFormat } from '../ const INTERNAL_TARGETS = new Set(['shell', 'skills', 'native', 'superpowers', 'browser', 'privacy']); const PRIVACY_MODES = new Set(['regex', 'ollama', 'hybrid']); -const TEAM_PROVIDERS = new Set(['codex', 'claude', 'gemini']); +const TEAM_PROVIDERS = new Set(['codex', 'claude', 'gemini', 'kiro']); const HUD_PRESETS = new Set(['minimal', 'focused', 'full']); const SKILL_CANDIDATE_VIEWS = new Set(['inline', 'detail', 'list']); const TEAM_PROVIDER_CLIENT_MAP = Object.freeze({ codex: 'codex-cli', claude: 'claude-code', gemini: 'gemini-cli', + kiro: 'kiro-cli', }); const HARNESS_SUBCOMMANDS = new Set(['run', 'status', 'resume', 'stop']); @@ -108,7 +109,7 @@ function normalizeTeamProvider(raw = 'codex') { function parseTeamSpec(raw = '') { const value = String(raw || '').trim().toLowerCase(); - const match = /^(\d+):(codex|claude|gemini)$/u.exec(value); + const match = /^(\d+):(codex|claude|gemini|kiro)$/u.exec(value); if (!match) { return null; } diff --git a/scripts/lib/components/browser.mjs b/scripts/lib/components/browser.mjs index 2c5bca43..e36db60a 100644 --- a/scripts/lib/components/browser.mjs +++ b/scripts/lib/components/browser.mjs @@ -138,6 +138,7 @@ export async function migrateBrowserMcpConfig({ rootDir, io = console, dryRun = { path: path.join(homes.claude || '', 'mcp.json'), createIfMissing: false }, { path: path.join(homes.gemini || '', 'mcp.json'), createIfMissing: false }, { path: path.join(homes.opencode || '', 'mcp.json'), createIfMissing: false }, + { path: path.join(homes.kiro || '', 'settings', 'mcp.json'), createIfMissing: false }, ]; const seen = new Set(); diff --git a/scripts/lib/components/shell.mjs b/scripts/lib/components/shell.mjs index 90c4f180..3d6bd761 100644 --- a/scripts/lib/components/shell.mjs +++ b/scripts/lib/components/shell.mjs @@ -16,17 +16,25 @@ const BEGIN_MARK = '# >>> contextdb-shell >>>'; const END_MARK = '# <<< contextdb-shell <<<'; function buildPosixBlock(rootDir, mode) { - return `${BEGIN_MARK}\n# ContextDB transparent CLI wrappers (codex/claude/gemini/opencode)\nexport ROOTPATH="\${ROOTPATH:-${rootDir}}"\nexport CTXDB_WRAP_MODE="\${CTXDB_WRAP_MODE:-${mode}}"\nif [[ -f "\$ROOTPATH/scripts/contextdb-shell.zsh" ]]; then\n source "\$ROOTPATH/scripts/contextdb-shell.zsh"\nfi\n${END_MARK}\n`; + return `${BEGIN_MARK}\n# ContextDB transparent CLI wrappers (codex/claude/gemini/opencode/kiro-cli)\nexport ROOTPATH="\${ROOTPATH:-${rootDir}}"\nexport CTXDB_WRAP_MODE="\${CTXDB_WRAP_MODE:-${mode}}"\nif [[ -f "\$ROOTPATH/scripts/contextdb-shell.zsh" ]]; then\n source "\$ROOTPATH/scripts/contextdb-shell.zsh"\nfi\n${END_MARK}\n`; } function buildPowerShellBlock(rootDir, mode) { - return `${BEGIN_MARK}\n# ContextDB transparent CLI wrappers (codex/claude/gemini/opencode, PowerShell)\nif (-not $env:ROOTPATH) { $env:ROOTPATH = "${rootDir}" }\nif (-not $env:CTXDB_WRAP_MODE) { $env:CTXDB_WRAP_MODE = "${mode}" }\n$ctxShell = Join-Path $env:ROOTPATH "scripts/contextdb-shell.ps1"\nif (Test-Path $ctxShell) {\n . $ctxShell\n}\n${END_MARK}\n`; + return `${BEGIN_MARK}\n# ContextDB transparent CLI wrappers (codex/claude/gemini/opencode/kiro-cli, PowerShell)\nif (-not $env:ROOTPATH) { $env:ROOTPATH = "${rootDir}" }\nif (-not $env:CTXDB_WRAP_MODE) { $env:CTXDB_WRAP_MODE = "${mode}" }\n$ctxShell = Join-Path $env:ROOTPATH "scripts/contextdb-shell.ps1"\nif (Test-Path $ctxShell) {\n . $ctxShell\n}\n${END_MARK}\n`; } function getShellPatterns(platform) { return platform === 'win32' - ? [/^\.\s+.*scripts\/contextdb-shell\.ps1\s*$/u, /^# ContextDB transparent CLI wrappers \(codex\/claude\/gemini\/opencode, PowerShell\)$/u] - : [/^source ".*\/scripts\/contextdb-shell\.zsh"$/u, /^# ContextDB transparent CLI wrappers \(codex\/claude\/gemini\/opencode\)$/u]; + ? [ + /^\.\s+.*scripts\/contextdb-shell\.ps1\s*$/u, + /^# ContextDB transparent CLI wrappers \(codex\/claude\/gemini\/opencode, PowerShell\)$/u, + /^# ContextDB transparent CLI wrappers \(codex\/claude\/gemini\/opencode\/kiro(?:-cli)?, PowerShell\)$/u, + ] + : [ + /^source ".*\/scripts\/contextdb-shell\.zsh"$/u, + /^# ContextDB transparent CLI wrappers \(codex\/claude\/gemini\/opencode\)$/u, + /^# ContextDB transparent CLI wrappers \(codex\/claude\/gemini\/opencode\/kiro(?:-cli)?\)$/u, + ]; } function resolveTargetFiles({ platform = process.platform, rcFile, env = process.env, homeDir = os.homedir() } = {}) { @@ -223,7 +231,7 @@ export async function doctorContextDbShell({ } } - for (const command of ['codex', 'claude', 'gemini', 'opencode']) { + for (const command of ['codex', 'claude', 'gemini', 'opencode', 'kiro-cli']) { if (commandExists(command, { platform, env })) { io.log(`[ok] ${command} found in PATH`); } else { diff --git a/scripts/lib/components/skills.mjs b/scripts/lib/components/skills.mjs index 21688c67..9390e831 100644 --- a/scripts/lib/components/skills.mjs +++ b/scripts/lib/components/skills.mjs @@ -26,7 +26,7 @@ import { resolveGeneratedTargetRelativePath, } from '../skills/source-tree.mjs'; -const ALL_CLIENTS = ['codex', 'claude', 'gemini', 'opencode']; +const ALL_CLIENTS = ['codex', 'claude', 'gemini', 'opencode', 'kiro']; const ALL_SCOPES = ['global', 'project']; const INSTALL_MODES = ['copy', 'link']; @@ -171,6 +171,8 @@ function resolveProjectSkillRoot(rootDir, client) { return path.join(rootDir, '.gemini', 'skills'); case 'opencode': return path.join(rootDir, '.opencode', 'skills'); + case 'kiro': + return path.join(rootDir, '.kiro', 'skills'); default: return ''; } @@ -585,7 +587,7 @@ export async function doctorContextDbSkills({ for (const file of finding.files) { io.log(` move or convert: ${file}`); } - io.log(' repo-local discoverable skills must live under .codex/skills or .claude/skills'); + io.log(' repo-local discoverable skills must live under .codex/skills, .claude/skills, or .kiro/skills'); warnings += 1; } diff --git a/scripts/lib/contextdb/handoff.mjs b/scripts/lib/contextdb/handoff.mjs index 5f2af5d3..6777d8d6 100644 --- a/scripts/lib/contextdb/handoff.mjs +++ b/scripts/lib/contextdb/handoff.mjs @@ -9,6 +9,7 @@ const VALID_AGENT_TYPES = [ 'codex-cli', 'gemini-cli', 'opencode-cli', + 'kiro-cli', ]; const VALID_ROLES = ['planner', 'implementer', 'reviewer', 'orchestrator']; diff --git a/scripts/lib/harness/hindsight-eval.mjs b/scripts/lib/harness/hindsight-eval.mjs index 2fc91ba5..5cea849a 100644 --- a/scripts/lib/harness/hindsight-eval.mjs +++ b/scripts/lib/harness/hindsight-eval.mjs @@ -242,7 +242,7 @@ function buildSuggestedCommands({ sessionId, provider, kind } = {}) { commands.push(`node scripts/aios.mjs orchestrate --session ${id} --dispatch local --execute dry-run --format json`); const effectiveProvider = normalizeText(provider); - if ((kind === 'repeat-blocked' || kind === 'regression') && (effectiveProvider === 'codex' || effectiveProvider === 'claude' || effectiveProvider === 'gemini')) { + if ((kind === 'repeat-blocked' || kind === 'regression') && (effectiveProvider === 'codex' || effectiveProvider === 'claude' || effectiveProvider === 'gemini' || effectiveProvider === 'kiro')) { commands.push(`node scripts/aios.mjs team --resume ${id} --retry-blocked --provider ${effectiveProvider} --workers 2 --dry-run`); } diff --git a/scripts/lib/harness/solo-profiles.mjs b/scripts/lib/harness/solo-profiles.mjs index b82d278b..65b54308 100644 --- a/scripts/lib/harness/solo-profiles.mjs +++ b/scripts/lib/harness/solo-profiles.mjs @@ -23,6 +23,11 @@ const PROVIDER_MAP = Object.freeze({ clientId: 'opencode-cli', command: 'opencode', }, + kiro: { + provider: 'kiro', + clientId: 'kiro-cli', + command: 'kiro-cli', + }, }); const RESERVED_FLAGS = new Set([ @@ -82,6 +87,18 @@ export async function checkSoloHarnessProfileReadiness({ }; } + if (profile.provider === 'kiro' && !String(env?.KIRO_API_KEY || '').trim()) { + return { + ok: false, + profile, + reason: 'KIRO_API_KEY is required for headless kiro-cli runs', + nextActions: [ + 'Set KIRO_API_KEY for unattended Kiro runs or switch --provider to another installed client.', + 'Run node scripts/aios.mjs doctor --native --verbose', + ], + }; + } + return { ok: true, profile, diff --git a/scripts/lib/harness/subagent-runtime.mjs b/scripts/lib/harness/subagent-runtime.mjs index 2ddc8cb4..e378052c 100644 --- a/scripts/lib/harness/subagent-runtime.mjs +++ b/scripts/lib/harness/subagent-runtime.mjs @@ -31,12 +31,13 @@ export const SUBAGENT_PRE_MUTATION_SNAPSHOT_ENV = 'AIOS_SUBAGENT_PRE_MUTATION_SN export const SUBAGENT_CODEX_DISABLE_MCP_ENV = 'AIOS_SUBAGENT_CODEX_DISABLE_MCP'; export const SUBAGENT_CODEX_UNATTENDED_ENV = 'AIOS_SUBAGENT_CODEX_UNATTENDED'; -const SUPPORTED_CLIENTS = new Set(['codex-cli', 'claude-code', 'gemini-cli', 'opencode-cli']); +const SUPPORTED_CLIENTS = new Set(['codex-cli', 'claude-code', 'gemini-cli', 'opencode-cli', 'kiro-cli']); const CLIENT_COMMAND = { 'codex-cli': 'codex', 'claude-code': 'claude', 'gemini-cli': 'gemini', 'opencode-cli': 'opencode', + 'kiro-cli': 'kiro-cli', }; const CODEX_OUTPUT_SCHEMA_REL = path.join('memory', 'specs', 'agent-handoff.schema.json'); @@ -180,6 +181,20 @@ function buildCodexUnattendedArgs(env = process.env) { return ['--dangerously-bypass-approvals-and-sandbox']; } +function buildKiroChatArgs(extraArgs = [], { headless = false } = {}) { + const values = Array.isArray(extraArgs) ? [...extraArgs] : [extraArgs]; + const passthroughArgs = values[0] === 'chat' ? values.slice(1) : values; + const args = ['chat']; + if (headless) { + args.push('--no-interactive'); + } + args.push('--trust-all-tools'); + if (passthroughArgs.length > 0) { + args.push(...passthroughArgs); + } + return args; +} + function buildRoutedExtraArgs(clientId = '', modelRouting = null, env = process.env) { if (!isModelRouterEnabled(env)) return []; return buildClientModelArgs(clientId, modelRouting); @@ -876,6 +891,19 @@ export async function runOneShot(clientId, { systemPrompt, userPrompt, timeoutMs ? `${systemText}\n\n## New User Request\n${promptText}` : promptText; args = ['run', fullPrompt]; + } else if (clientId === 'kiro-cli') { + const fullPrompt = systemText + ? `${systemText}\n\n## New User Request\n${promptText}` + : promptText; + if (!String(env?.KIRO_API_KEY || '').trim()) { + return { + exitCode: 1, + stdout: '', + stderr: '', + error: 'KIRO_API_KEY is required for headless kiro-cli runs', + }; + } + args = [...buildKiroChatArgs(routedExtraArgs, { headless: true }), fullPrompt]; } else { const fullPrompt = systemText ? `${systemText}\n\n## New User Request\n${promptText}` @@ -1455,7 +1483,7 @@ export async function executeSubagentDispatchPlan( const normalizedClient = normalizeText(env?.[SUBAGENT_CLIENT_ENV]).toLowerCase(); const clientId = normalizedClient || ''; if (!SUPPORTED_CLIENTS.has(clientId)) { - const supportedHint = `Set ${SUBAGENT_CLIENT_ENV} to one of: codex-cli, claude-code, gemini-cli, opencode-cli.`; + const supportedHint = `Set ${SUBAGENT_CLIENT_ENV} to one of: codex-cli, claude-code, gemini-cli, opencode-cli, kiro-cli.`; return { mode: 'live', ok: false, diff --git a/scripts/lib/hud/state.mjs b/scripts/lib/hud/state.mjs index 8bd72604..5c4c7aac 100644 --- a/scripts/lib/hud/state.mjs +++ b/scripts/lib/hud/state.mjs @@ -13,6 +13,7 @@ export const HUD_PROVIDER_AGENT_MAP = Object.freeze({ claude: 'claude-code', gemini: 'gemini-cli', opencode: 'opencode-cli', + kiro: 'kiro-cli', }); const AGENT_PROVIDER_MAP = Object.freeze( @@ -111,7 +112,7 @@ function computeCompletionRatio(done = 0, total = 0) { function normalizeProvider(raw = '') { const value = normalizeText(raw).toLowerCase(); if (!value) return ''; - if (value === 'codex' || value === 'claude' || value === 'gemini' || value === 'opencode') return value; + if (value === 'codex' || value === 'claude' || value === 'gemini' || value === 'opencode' || value === 'kiro') return value; return ''; } @@ -1116,7 +1117,7 @@ function buildSuggestedCommands({ sessionId, provider, latestDispatch, latestSki } const effectiveProvider = provider || inferProviderFromAgent(latestDispatch?.raw?.dispatchEvidence?.agent) || ''; - if (latestDispatch?.blockedJobs > 0 && (effectiveProvider === 'codex' || effectiveProvider === 'claude' || effectiveProvider === 'gemini')) { + if (latestDispatch?.blockedJobs > 0 && (effectiveProvider === 'codex' || effectiveProvider === 'claude' || effectiveProvider === 'gemini' || effectiveProvider === 'kiro')) { commands.push( `node scripts/aios.mjs team --resume ${sessionId} --retry-blocked --provider ${effectiveProvider} --workers 2 --dry-run` ); diff --git a/scripts/lib/lifecycle/options.mjs b/scripts/lib/lifecycle/options.mjs index b01dd182..64981c48 100644 --- a/scripts/lib/lifecycle/options.mjs +++ b/scripts/lib/lifecycle/options.mjs @@ -2,7 +2,7 @@ import { HARNESS_PROFILE_NAMES, normalizeHarnessProfile } from '../harness/profi export const COMPONENT_NAMES = ['browser', 'shell', 'skills', 'native', 'agents', 'superpowers']; export const WRAP_MODES = ['all', 'repo-only', 'opt-in', 'off']; -export const CLIENT_NAMES = ['all', 'codex', 'claude', 'gemini', 'opencode']; +export const CLIENT_NAMES = ['all', 'codex', 'claude', 'gemini', 'opencode', 'kiro']; export const SKILL_SCOPE_NAMES = ['global', 'project']; export const SKILL_INSTALL_MODE_NAMES = ['copy', 'link']; export const QUALITY_GATE_MODES = ['quick', 'full', 'pre-pr']; @@ -17,7 +17,7 @@ export const ENTROPY_GC_FORMAT_NAMES = ['text', 'json']; export const SNAPSHOT_ROLLBACK_FORMAT_NAMES = ['text', 'json']; export const RELEASE_STATUS_FORMAT_NAMES = ['text', 'json']; export const RELEASE_STATUS_HISTORY_FORMAT_NAMES = ['csv', 'ndjson']; -export const SOLO_HARNESS_PROVIDER_NAMES = ['codex', 'claude', 'gemini', 'opencode']; +export const SOLO_HARNESS_PROVIDER_NAMES = ['codex', 'claude', 'gemini', 'opencode', 'kiro']; export function normalizeWrapMode(raw = 'opt-in') { const value = String(raw || 'opt-in').trim().toLowerCase(); diff --git a/scripts/lib/lifecycle/team-ops.mjs b/scripts/lib/lifecycle/team-ops.mjs index 32535899..b4baf31b 100644 --- a/scripts/lib/lifecycle/team-ops.mjs +++ b/scripts/lib/lifecycle/team-ops.mjs @@ -59,7 +59,7 @@ function normalizeWatchStalledMs(rawValue, fallback = DEFAULT_WATCH_STALLED_MS) function normalizeProvider(value) { const normalized = normalizeText(value).toLowerCase(); - if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini') { + if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'kiro') { return normalized; } return 'codex'; diff --git a/scripts/lib/model-router.mjs b/scripts/lib/model-router.mjs index cd7d1ccc..e1b0a120 100644 --- a/scripts/lib/model-router.mjs +++ b/scripts/lib/model-router.mjs @@ -19,6 +19,7 @@ const PROVIDER_CLIENT_MAP = Object.freeze({ codex: 'codex-cli', claude: 'claude-code', gemini: 'gemini-cli', + kiro: 'kiro-cli', }); let _registryCache = null; diff --git a/scripts/lib/native/doctor.mjs b/scripts/lib/native/doctor.mjs index 23f7eacf..19b63fa7 100644 --- a/scripts/lib/native/doctor.mjs +++ b/scripts/lib/native/doctor.mjs @@ -8,6 +8,7 @@ import { createNativeRepairSession, finalizeNativeRepairSession } from './repair import { renderClaudeNativeOutputs } from './emitters/claude.mjs'; import { renderCodexNativeOutputs } from './emitters/codex.mjs'; import { renderGeminiNativeOutputs } from './emitters/gemini.mjs'; +import { renderKiroNativeOutputs } from './emitters/kiro.mjs'; import { renderOpencodeNativeOutputs } from './emitters/opencode.mjs'; import { AIOS_NATIVE_JSON_KEY, @@ -21,6 +22,7 @@ const EMITTERS = { codex: renderCodexNativeOutputs, claude: renderClaudeNativeOutputs, gemini: renderGeminiNativeOutputs, + kiro: renderKiroNativeOutputs, opencode: renderOpencodeNativeOutputs, }; @@ -62,9 +64,37 @@ function formatOperationTarget(operation) { if (operation.kind === 'json-merge') { return `${operation.targetPath}#${AIOS_NATIVE_JSON_KEY}`; } + if (operation.kind === 'json-merge-object') { + return `${operation.targetPath}#${operation.targetKey}`; + } return operation.targetPath; } +function isPlainObject(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function isSubsetObject(currentObject, fragment) { + if (!isPlainObject(currentObject) || !isPlainObject(fragment)) { + return false; + } + + for (const [key, value] of Object.entries(fragment)) { + const currentValue = currentObject[key]; + if (isPlainObject(value)) { + if (!isSubsetObject(currentValue, value)) { + return false; + } + continue; + } + if (JSON.stringify(currentValue) !== JSON.stringify(value)) { + return false; + } + } + + return true; +} + function parseIssueTargetFromMessage(message = '') { const match = String(message || '').match(/^\[[^\]]+\]\s+([^\s].*)$/u); if (!match) { @@ -165,6 +195,22 @@ async function inspectOperation({ rootDir, client, operation, fixCommand, issues return; } + if (operation.kind === 'json-merge-object') { + let parsedObject; + try { + parsedObject = parseJsonObject(current, targetPath); + } catch { + issues.push(withIssueTarget(buildIssue({ client, status: 'error', message: `[invalid json] ${operation.targetPath}`, fix: fixCommand }), operationTarget)); + return; + } + + const currentNode = parsedObject[operation.targetKey]; + if (!isPlainObject(currentNode) || !isSubsetObject(currentNode, operation.content)) { + issues.push(withIssueTarget(buildIssue({ client, message: `[drift] ${operation.targetPath}#${operation.targetKey}`, fix: fixCommand }), operationTarget)); + } + return; + } + let parsed; try { parsed = parseJsonObject(current, targetPath); @@ -179,6 +225,8 @@ async function inspectOperation({ rootDir, client, operation, fixCommand, issues if (JSON.stringify(parsed[AIOS_NATIVE_JSON_KEY]) !== JSON.stringify(operation.content)) { issues.push(withIssueTarget(buildIssue({ client, message: `[drift] ${operation.targetPath}#${AIOS_NATIVE_JSON_KEY}`, fix: fixCommand }), operationTarget)); } + + return; } async function inspectClient({ rootDir, manifest, client }) { @@ -208,6 +256,17 @@ async function inspectClient({ rootDir, manifest, client }) { } continue; } + if (operation.kind === 'json-merge-object') { + try { + const parsed = parseJsonObject(current, targetPath); + if (isPlainObject(parsed[operation.targetKey])) { + hasManagedFootprint = true; + } + } catch { + hasManagedFootprint = true; + } + continue; + } try { if (hasManagedMarkdownBlock(current)) { hasManagedFootprint = true; @@ -270,7 +329,7 @@ async function inspectClient({ rootDir, manifest, client }) { } } - if (client === 'codex' || client === 'claude') { + if (client === 'codex' || client === 'claude' || client === 'kiro') { const agentRoot = path.join(rootDir, `.${client}`, 'agents'); if (!(await pathExists(agentRoot))) { issues.push(withIssueTarget(buildIssue({ client, message: `[missing] .${client}/agents`, fix: fixCommand }), `.${client}/agents`)); diff --git a/scripts/lib/native/emitters/kiro.mjs b/scripts/lib/native/emitters/kiro.mjs new file mode 100644 index 00000000..f83d0422 --- /dev/null +++ b/scripts/lib/native/emitters/kiro.mjs @@ -0,0 +1,38 @@ +import { joinMarkdownSections, readClientJsonSource, readClientMarkdownSource, readSharedMarkdownParts } from './shared.mjs'; + +function replaceRootPlaceholders(value, rootDir) { + if (Array.isArray(value)) { + return value.map((item) => replaceRootPlaceholders(item, rootDir)); + } + if (value && typeof value === 'object') { + return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, replaceRootPlaceholders(item, rootDir)])); + } + if (typeof value === 'string') { + return value.replace(/\{\{ROOT_DIR\}\}/g, rootDir); + } + return value; +} + +export function renderKiroNativeOutputs({ rootDir }) { + const mcpTemplate = replaceRootPlaceholders(readClientJsonSource(rootDir, 'kiro', 'mcp.json'), rootDir); + + return { + operations: [ + { + kind: 'managed-file', + targetPath: '.kiro/steering/AIOS.md', + content: joinMarkdownSections([ + ...readSharedMarkdownParts(rootDir), + readClientMarkdownSource(rootDir, 'kiro', 'steering.md'), + ]), + }, + { + kind: 'json-merge-object', + targetPath: '.kiro/settings/mcp.json', + targetKey: 'mcpServers', + content: mcpTemplate, + }, + ], + managedTargets: ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills'], + }; +} diff --git a/scripts/lib/native/source-tree.mjs b/scripts/lib/native/source-tree.mjs index 623d9c50..a4a348d3 100644 --- a/scripts/lib/native/source-tree.mjs +++ b/scripts/lib/native/source-tree.mjs @@ -5,7 +5,7 @@ import { NATIVE_SYNC_META_FILE } from './install-metadata.mjs'; const MANIFEST_PATH = path.join('config', 'native-sync-manifest.json'); const CLIENT_SOURCE_ROOT = path.join('client-sources', 'native-base'); -const ALLOWED_CLIENTS = ['codex', 'claude', 'gemini', 'opencode']; +const ALLOWED_CLIENTS = ['codex', 'claude', 'gemini', 'opencode', 'kiro']; const ALLOWED_TIERS = ['deep', 'compatibility']; function assertCondition(condition, message) { diff --git a/scripts/lib/native/sync.mjs b/scripts/lib/native/sync.mjs index 258ed9c4..bc527986 100644 --- a/scripts/lib/native/sync.mjs +++ b/scripts/lib/native/sync.mjs @@ -10,6 +10,7 @@ import { import { renderClaudeNativeOutputs } from './emitters/claude.mjs'; import { renderCodexNativeOutputs } from './emitters/codex.mjs'; import { renderGeminiNativeOutputs } from './emitters/gemini.mjs'; +import { renderKiroNativeOutputs } from './emitters/kiro.mjs'; import { renderOpencodeNativeOutputs } from './emitters/opencode.mjs'; import { AIOS_NATIVE_BEGIN_MARK, @@ -29,6 +30,7 @@ const EMITTERS = { codex: renderCodexNativeOutputs, claude: renderClaudeNativeOutputs, gemini: renderGeminiNativeOutputs, + kiro: renderKiroNativeOutputs, opencode: renderOpencodeNativeOutputs, }; const SYNC_LOCK_NAME = 'native-skills-sync'; @@ -165,6 +167,98 @@ async function applyJsonMergeOperation(targetPath, fragment, fsOps, backups, rep return summarizeMutation(existsBefore, true); } +function isPlainObject(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function mergeObjectFragment(existingObject, fragment) { + return { + ...(isPlainObject(existingObject) ? existingObject : {}), + ...(isPlainObject(fragment) ? fragment : {}), + }; +} + +async function applyJsonMergeObjectOperation(targetPath, targetKey, fragment, fsOps, backups, repair) { + const previous = await fsOps.readTextTarget(targetPath); + const existsBefore = previous.length > 0 || await pathExists(targetPath); + let parsed; + try { + parsed = parseJsonObject(previous, targetPath); + } catch { + if (!repair.resetInvalidJson) { + throw new Error(`invalid json: ${path.basename(targetPath)}`); + } + parsed = {}; + } + + const currentNode = parsed[targetKey]; + if (currentNode !== undefined && !isPlainObject(currentNode)) { + if (!repair.resetInvalidJson) { + throw new Error(`invalid json: ${path.basename(targetPath)}#${targetKey}`); + } + parsed[targetKey] = {}; + } + + const next = stringifyJsonObject({ + ...parsed, + [targetKey]: mergeObjectFragment(parsed[targetKey], fragment), + }); + if (normalizeText(previous) === normalizeText(next)) { + return 'reused'; + } + await backupTarget(targetPath, fsOps, backups); + await fsOps.writeTextTarget(targetPath, next); + return summarizeMutation(existsBefore, true); +} + +async function removeJsonMergeObjectOperation(targetPath, targetKey, fragment, fsOps, backups) { + const previous = await fsOps.readTextTarget(targetPath); + if (!previous && !(await pathExists(targetPath))) { + return 'reused'; + } + + let parsed; + try { + parsed = parseJsonObject(previous, targetPath); + } catch { + return 'reused'; + } + + const currentNode = parsed[targetKey]; + if (!isPlainObject(currentNode)) { + return 'reused'; + } + + const nextNode = { ...currentNode }; + let changed = false; + for (const key of Object.keys(fragment || {})) { + if (key in nextNode) { + delete nextNode[key]; + changed = true; + } + } + + if (!changed) { + return 'reused'; + } + + const nextObject = { ...parsed }; + if (Object.keys(nextNode).length > 0) { + nextObject[targetKey] = nextNode; + } else { + delete nextObject[targetKey]; + } + + const nextText = Object.keys(nextObject).length > 0 ? stringifyJsonObject(nextObject) : ''; + await backupTarget(targetPath, fsOps, backups); + if (nextText) { + await fsOps.writeTextTarget(targetPath, nextText); + } else { + await fsOps.removeTarget(targetPath); + } + return 'removed'; +} + async function removeOperation(targetPath, kind, fsOps, backups) { const previous = await fsOps.readTextTarget(targetPath); if (!previous && !(await pathExists(targetPath))) { @@ -230,13 +324,19 @@ async function applyRenderedOperations({ rootDir, client, mode, rendered, plan, let status = 'reused'; if (mode === 'uninstall') { - status = await removeOperation(targetPath, operation.kind, fsOps, backups); + if (operation.kind === 'json-merge-object') { + status = await removeJsonMergeObjectOperation(targetPath, operation.targetKey, operation.content, fsOps, backups); + } else { + status = await removeOperation(targetPath, operation.kind, fsOps, backups); + } } else if (operation.kind === 'markdown-block') { status = await applyMarkdownBlockOperation(targetPath, operation.content, fsOps, backups); } else if (operation.kind === 'managed-file') { status = await applyManagedFileOperation(targetPath, operation.content, fsOps, backups, repair); } else if (operation.kind === 'json-merge') { status = await applyJsonMergeOperation(targetPath, operation.content, fsOps, backups, repair); + } else if (operation.kind === 'json-merge-object') { + status = await applyJsonMergeObjectOperation(targetPath, operation.targetKey, operation.content, fsOps, backups, repair); } else { throw new Error(`unsupported native operation: ${operation.kind}`); } @@ -288,7 +388,7 @@ async function syncNativeEnhancementsUnlocked({ surfaces: [currentClient], withLock: false, }); - if (currentClient === 'codex' || currentClient === 'claude') { + if (currentClient === 'codex' || currentClient === 'claude' || currentClient === 'kiro') { await syncCanonicalAgents({ rootDir, io, targets: [currentClient], mode: 'install' }); } } diff --git a/scripts/lib/platform/fs.mjs b/scripts/lib/platform/fs.mjs index fe41b781..80c2a043 100644 --- a/scripts/lib/platform/fs.mjs +++ b/scripts/lib/platform/fs.mjs @@ -181,6 +181,7 @@ export function collectSkillEntries(sourceRoots) { const REPO_DISCOVERABLE_SKILL_ROOTS = new Set([ '.codex/skills', '.claude/skills', + '.kiro/skills', '.agents/skills', '.gemini/skills', '.opencode/skills', diff --git a/scripts/lib/platform/paths.mjs b/scripts/lib/platform/paths.mjs index a35a55ee..e4a4ff23 100644 --- a/scripts/lib/platform/paths.mjs +++ b/scripts/lib/platform/paths.mjs @@ -30,6 +30,7 @@ export function getClientHomes(env = process.env, homeDir = os.homedir()) { claude: normalizeHomeDir(env.CLAUDE_HOME, path.join(homeDir, '.claude'), homeDir), gemini: normalizeHomeDir(env.GEMINI_HOME, path.join(homeDir, '.gemini'), homeDir), opencode: normalizeHomeDir(env.OPENCODE_HOME, path.join(xdgConfigHome, 'opencode'), homeDir), + kiro: normalizeHomeDir(env.KIRO_HOME, path.join(homeDir, '.kiro'), homeDir), }; } diff --git a/scripts/lib/platform/process.mjs b/scripts/lib/platform/process.mjs index c6f3382f..76803a48 100644 --- a/scripts/lib/platform/process.mjs +++ b/scripts/lib/platform/process.mjs @@ -2,7 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { spawn, spawnSync } from 'node:child_process'; -const WINDOWS_SHELL_COMMANDS = new Set(['codex', 'claude', 'gemini', 'opencode']); +const WINDOWS_SHELL_COMMANDS = new Set(['codex', 'claude', 'gemini', 'opencode', 'kiro-cli']); function getEnvCaseInsensitive(env, key) { if (!env) return ''; diff --git a/scripts/lib/skills/sync.mjs b/scripts/lib/skills/sync.mjs index 074794de..4f9feeee 100644 --- a/scripts/lib/skills/sync.mjs +++ b/scripts/lib/skills/sync.mjs @@ -116,14 +116,20 @@ async function syncGeneratedSkillsUnlocked({ rootDir, io = console, manifest = n const selectedSurfaces = Array.isArray(surfaces) && surfaces.length > 0 ? [...new Set(surfaces.map((surface) => String(surface || '').trim()).filter(Boolean))] : Object.keys(resolvedManifest.generatedRoots); + const supportedSurfaces = selectedSurfaces.filter((surface) => Boolean(resolvedManifest.generatedRoots[surface])); + const skippedSurfaces = selectedSurfaces.filter((surface) => !resolvedManifest.generatedRoots[surface]); const expectedBySurface = new Map(selectedSurfaces.map((surface) => [surface, new Map()])); const results = []; const legacyUnmanaged = new Set(resolvedManifest.legacyUnmanaged.map((item) => path.resolve(rootDir, item))); const legacyReplaceable = new Set((resolvedManifest.legacyReplaceable || []).map((item) => path.resolve(rootDir, item))); + for (const surface of skippedSurfaces) { + io.log(`[skills] skip unsupported surface: ${surface}`); + } + for (const entry of canonicalSkills) { for (const surface of entry.repoTargets) { - if (!expectedBySurface.has(surface)) { + if (!expectedBySurface.has(surface) || !resolvedManifest.generatedRoots[surface]) { continue; } const targetPath = resolveGeneratedTargetPath({ rootDir, entry, surface, manifest: resolvedManifest }); @@ -131,7 +137,7 @@ async function syncGeneratedSkillsUnlocked({ rootDir, io = console, manifest = n } } - for (const surface of selectedSurfaces) { + for (const surface of supportedSurfaces) { const rootRel = resolvedManifest.generatedRoots[surface]; const rootAbs = path.join(rootDir, rootRel); const expected = expectedBySurface.get(surface) || new Map(); @@ -259,12 +265,17 @@ export async function checkGeneratedSkillsSync({ rootDir, io = console, manifest const selectedSurfaces = Array.isArray(surfaces) && surfaces.length > 0 ? [...new Set(surfaces.map((surface) => String(surface || '').trim()).filter(Boolean))] : Object.keys(resolvedManifest.generatedRoots); - const expectedBySurface = new Map(selectedSurfaces.map((surface) => [surface, new Map()])); + const supportedSurfaces = selectedSurfaces.filter((surface) => Boolean(resolvedManifest.generatedRoots[surface])); + const skippedSurfaces = selectedSurfaces.filter((surface) => !resolvedManifest.generatedRoots[surface]); + for (const surface of skippedSurfaces) { + io.log(`[skills] skip unsupported surface: ${surface}`); + } + const expectedBySurface = new Map(supportedSurfaces.map((surface) => [surface, new Map()])); const issues = []; for (const entry of canonicalSkills) { for (const surface of entry.repoTargets) { - if (!expectedBySurface.has(surface)) { + if (!expectedBySurface.has(surface) || !resolvedManifest.generatedRoots[surface]) { continue; } const targetPath = resolveGeneratedTargetPath({ rootDir, entry, surface, manifest: resolvedManifest }); @@ -272,7 +283,7 @@ export async function checkGeneratedSkillsSync({ rootDir, io = console, manifest } } - for (const surface of selectedSurfaces) { + for (const surface of supportedSurfaces) { const rootAbs = path.join(rootDir, resolvedManifest.generatedRoots[surface]); const expected = expectedBySurface.get(surface) || new Map(); diff --git a/scripts/lib/tui-ink/cli.tsx b/scripts/lib/tui-ink/cli.tsx index b7432af3..173578d3 100644 --- a/scripts/lib/tui-ink/cli.tsx +++ b/scripts/lib/tui-ink/cli.tsx @@ -66,6 +66,7 @@ function getClientHomes(): Record<Client, string> { claude: path.join(home, '.claude'), gemini: path.join(home, '.gemini'), opencode: path.join(home, '.opencode'), + kiro: path.join(home, '.kiro'), all: home, }; } diff --git a/scripts/lib/tui-ink/hooks/useSetupOptions.ts b/scripts/lib/tui-ink/hooks/useSetupOptions.ts index 4a4df2ca..a8fdacc8 100644 --- a/scripts/lib/tui-ink/hooks/useSetupOptions.ts +++ b/scripts/lib/tui-ink/hooks/useSetupOptions.ts @@ -12,7 +12,7 @@ import type { } from '../types'; const MODE_OPTIONS: WrapMode[] = ['all', 'repo-only', 'opt-in', 'off']; -const CLIENT_OPTIONS: Client[] = ['all', 'codex', 'claude', 'gemini', 'opencode']; +const CLIENT_OPTIONS: Client[] = ['all', 'codex', 'claude', 'gemini', 'opencode', 'kiro']; const SCOPE_OPTIONS: Scope[] = ['global', 'project']; type CycleDirection = 'next' | 'prev'; diff --git a/scripts/lib/tui-ink/index.tsx b/scripts/lib/tui-ink/index.tsx index 3e5c1ece..0a0f1017 100644 --- a/scripts/lib/tui-ink/index.tsx +++ b/scripts/lib/tui-ink/index.tsx @@ -67,6 +67,7 @@ function getClientHomes(): Record<Client, string> { claude: path.join(home, '.claude'), gemini: path.join(home, '.gemini'), opencode: path.join(home, '.opencode'), + kiro: path.join(home, '.kiro'), all: home, // Not used for 'all' }; } @@ -134,4 +135,4 @@ export async function runInteractiveSession({ ); await waitUntilExit(); -} \ No newline at end of file +} diff --git a/scripts/lib/tui-ink/native-preview.ts b/scripts/lib/tui-ink/native-preview.ts index 4dc34353..66f09384 100644 --- a/scripts/lib/tui-ink/native-preview.ts +++ b/scripts/lib/tui-ink/native-preview.ts @@ -9,22 +9,24 @@ function summarizeClient(client: Exclude<Client, 'all'>): string { if (client === 'codex') return 'codex: AGENTS.md + .codex/agents + .codex/skills'; if (client === 'claude') return 'claude: CLAUDE.md + .claude/settings.local.json + .claude/agents + .claude/skills'; if (client === 'gemini') return 'gemini: .gemini/AIOS.md + .gemini/skills'; - return 'opencode: .opencode/AIOS.md + .opencode/skills'; + if (client === 'opencode') return 'opencode: .opencode/AIOS.md + .opencode/skills'; + return 'kiro: .kiro/steering/AIOS.md + .kiro/settings/mcp.json + .kiro/agents + .kiro/skills'; } export function getNativePreview(client: Client): NativePreview { if (client === 'all') { return { - tier: 'deep(codex/claude) + compatibility(gemini/opencode)', + tier: 'deep(codex/claude/kiro) + compatibility(gemini/opencode)', lines: [ summarizeClient('codex'), summarizeClient('claude'), summarizeClient('gemini'), summarizeClient('opencode'), + summarizeClient('kiro'), ], }; } - const tier = client === 'codex' || client === 'claude' ? 'deep' : 'compatibility'; + const tier = client === 'codex' || client === 'claude' || client === 'kiro' ? 'deep' : 'compatibility'; return { tier, lines: [summarizeClient(client)] }; } diff --git a/scripts/lib/tui-ink/screens/HudScreen.tsx b/scripts/lib/tui-ink/screens/HudScreen.tsx index 56b1ad4f..99536c20 100644 --- a/scripts/lib/tui-ink/screens/HudScreen.tsx +++ b/scripts/lib/tui-ink/screens/HudScreen.tsx @@ -15,10 +15,10 @@ import { } from '../../hud/state.mjs'; import { normalizeHudPreset, renderHud } from '../../hud/render.mjs'; -type Provider = 'codex' | 'claude' | 'gemini'; +type Provider = 'codex' | 'claude' | 'gemini' | 'opencode' | 'kiro'; type Preset = 'minimal' | 'focused' | 'full'; -const PROVIDERS: Provider[] = ['codex', 'claude', 'gemini']; +const PROVIDERS: Provider[] = ['codex', 'claude', 'gemini', 'opencode', 'kiro']; const PRESETS: Preset[] = ['minimal', 'focused', 'full']; function clamp(value: number, min: number, max: number) { @@ -215,4 +215,3 @@ export function HudScreen({ rootDir }: HudScreenProps) { </Box> ); } - diff --git a/scripts/lib/tui-ink/tests/tui-ink.test.ts b/scripts/lib/tui-ink/tests/tui-ink.test.ts index 96549129..7433c99f 100644 --- a/scripts/lib/tui-ink/tests/tui-ink.test.ts +++ b/scripts/lib/tui-ink/tests/tui-ink.test.ts @@ -48,6 +48,16 @@ test('native preview helper can be imported', async () => { assert.equal(typeof preview.getNativePreview, 'function', 'getNativePreview should be a function'); }); +test('native preview includes Kiro deep outputs', async () => { + const preview = await import('../native-preview.ts'); + assert.deepEqual(preview.getNativePreview('kiro'), { + tier: 'deep', + lines: ['kiro: .kiro/steering/AIOS.md + .kiro/settings/mcp.json + .kiro/agents + .kiro/skills'], + }); + assert.match(preview.getNativePreview('all').tier, /kiro/); + assert.ok(preview.getNativePreview('all').lines.some((line: string) => line.startsWith('kiro: '))); +}); + test('App and runInteractiveSession can be imported', async () => { const app = await import('../App.tsx'); const index = await import('../index.tsx'); diff --git a/scripts/lib/tui-ink/types.ts b/scripts/lib/tui-ink/types.ts index 01005758..ae6e7545 100644 --- a/scripts/lib/tui-ink/types.ts +++ b/scripts/lib/tui-ink/types.ts @@ -2,7 +2,7 @@ export type WrapMode = 'all' | 'repo-only' | 'opt-in' | 'off'; export type Scope = 'global' | 'project'; -export type Client = 'all' | 'codex' | 'claude' | 'gemini' | 'opencode'; +export type Client = 'all' | 'codex' | 'claude' | 'gemini' | 'opencode' | 'kiro'; export type Action = 'setup' | 'update' | 'uninstall' | 'doctor'; export interface ComponentsConfig { diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 index b0f09a5a..9a287172 100644 --- a/scripts/package-release.ps1 +++ b/scripts/package-release.ps1 @@ -42,7 +42,12 @@ try { ".codex/skills", ".codex/agents", ".claude/skills", - ".agents/skills" + ".agents/skills", + ".kiro/.aios-native-sync.json", + ".kiro/steering", + ".kiro/settings", + ".kiro/agents", + ".kiro/skills" ) $installSh = Join-Path $RootDir "scripts/aios-install.sh" diff --git a/scripts/package-release.sh b/scripts/package-release.sh index 99d3400b..4c472bc4 100755 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -97,6 +97,11 @@ paths=( ".codex/agents" ".claude/skills" ".agents/skills" + ".kiro/.aios-native-sync.json" + ".kiro/steering" + ".kiro/settings" + ".kiro/agents" + ".kiro/skills" ) git -C "$ROOT_DIR" archive --format=tar --prefix="rex-cli/" HEAD "${paths[@]}" | gzip -9 > "$OUT_DIR/rex-cli.tar.gz" diff --git a/scripts/tests/agents-source-tree.test.mjs b/scripts/tests/agents-source-tree.test.mjs index eff601fc..5157d18f 100644 --- a/scripts/tests/agents-source-tree.test.mjs +++ b/scripts/tests/agents-source-tree.test.mjs @@ -17,7 +17,7 @@ async function writeJson(rootDir, relativePath, value) { function buildManifest() { return { schemaVersion: 1, - generatedTargets: ['claude', 'codex'], + generatedTargets: ['claude', 'codex', 'kiro'], }; } @@ -180,6 +180,7 @@ test('loadCanonicalAgents validates manifest and returns four role-bound agents' const result = await mod.loadCanonicalAgents({ rootDir }); assert.equal(result.manifest.schemaVersion, 1); + assert.deepEqual(result.manifest.generatedTargets, ['claude', 'codex', 'kiro']); assert.deepEqual(Object.keys(result.agentsById), [ 'rex-implementer', 'rex-planner', diff --git a/scripts/tests/agents-sync.test.mjs b/scripts/tests/agents-sync.test.mjs index 058f7bb6..59bb8c99 100644 --- a/scripts/tests/agents-sync.test.mjs +++ b/scripts/tests/agents-sync.test.mjs @@ -92,6 +92,88 @@ test('renderCodexAgent matches the same deterministic template contract', async assert.match(md, /^---\nname: rex-planner\n/); }); +test('renderKiroAgent emits deterministic Kiro custom agent JSON', async () => { + const source = await canonicalAgent('planner'); + const mod = await import('../lib/agents/emitters/kiro.mjs'); + const rendered = mod.renderKiroAgent(source); + const config = JSON.parse(rendered.content); + + assert.equal(rendered.targetRelPath, '.kiro/agents/rex-planner.json'); + assert.equal(rendered.content.endsWith('\n'), true); + assert.equal(config.name, 'rex-planner'); + assert.equal(config.description, source.description); + assert.match(config.prompt, /<!-- AIOS-GENERATED: orchestrator-agents v1 -->/); + assert.match(config.prompt, /Role: planner/); + assert.deepEqual(config.resources, [ + 'file://.kiro/steering/**/*.md', + 'skill://.kiro/skills/**/SKILL.md', + 'skill://~/.kiro/skills/**/SKILL.md', + ]); + assert.equal(config.includeMcpJson, true); + assert.deepEqual(config.tools, ['read', 'grep', 'glob']); + assert.deepEqual(config.allowedTools, ['read', 'grep', 'glob']); +}); + +test('resolveAgentTargets supports kiro and includes it in all', async () => { + const mod = await import('../lib/agents/sync.mjs'); + + assert.deepEqual(mod.resolveAgentTargets('kiro'), ['kiro']); + assert.deepEqual(mod.resolveAgentTargets('all'), ['claude', 'codex', 'kiro']); +}); + +test('syncCanonicalAgents defaults to all canonical targets including Kiro', async () => { + const rootDir = await makeRootDir(); + await copyCanonicalSource(rootDir); + + const mod = await import('../lib/agents/sync.mjs'); + const result = await mod.syncCanonicalAgents({ + rootDir, + writeCompatibilityExport: false, + io: { log() {} }, + }); + + assert.deepEqual(result.targets, ['claude', 'codex', 'kiro']); + assert.equal(result.results.length, 3); + assert.equal(result.results.find((item) => item.target === 'kiro')?.installed, 4); + const agentJson = JSON.parse(await readFile(path.join(rootDir, '.kiro/agents/rex-planner.json'), 'utf8')); + assert.equal(agentJson.name, 'rex-planner'); +}); + +test('syncCanonicalAgents installs managed Kiro agent JSON files', async () => { + const rootDir = await makeRootDir(); + await copyCanonicalSource(rootDir); + + const mod = await import('../lib/agents/sync.mjs'); + const result = await mod.syncCanonicalAgents({ + rootDir, + targets: ['kiro'], + writeCompatibilityExport: false, + io: { log() {} }, + }); + + const agentJson = JSON.parse(await readFile(path.join(rootDir, '.kiro/agents/rex-planner.json'), 'utf8')); + assert.equal(result.results[0].targetRel, '.kiro/agents'); + assert.equal(result.results[0].installed, 4); + assert.equal(agentJson.name, 'rex-planner'); + assert.match(agentJson.prompt, /<!-- END AIOS-GENERATED -->/); +}); + +test('syncCanonicalAgents rejects unmanaged Kiro JSON conflicts', async () => { + const rootDir = await makeRootDir(); + await copyCanonicalSource(rootDir); + await writeText(rootDir, '.kiro/agents/rex-planner.json', '{"name":"rex-planner"}\n'); + + const mod = await import('../lib/agents/sync.mjs'); + await assert.rejects( + () => mod.syncCanonicalAgents({ + rootDir, + targets: ['kiro'], + writeCompatibilityExport: false, + }), + /unmanaged conflict/i + ); +}); + test('syncCanonicalAgents aborts before write on unmanaged conflict', async () => { const rootDir = await makeRootDir(); await copyCanonicalSource(rootDir); diff --git a/scripts/tests/aios-cli.test.mjs b/scripts/tests/aios-cli.test.mjs index b615ad61..ad15cf02 100644 --- a/scripts/tests/aios-cli.test.mjs +++ b/scripts/tests/aios-cli.test.mjs @@ -6,7 +6,7 @@ import path from 'node:path'; import test from 'node:test'; import { parseArgs } from '../lib/cli/parse-args.mjs'; -import { getCommandHelpText } from '../lib/cli/help.mjs'; +import { getCommandHelpText, getInternalHelpText, getRootHelpText } from '../lib/cli/help.mjs'; import { runReleaseStatus } from '../lib/lifecycle/release-status.mjs'; import { runSnapshotRollback } from '../lib/lifecycle/snapshot-rollback.mjs'; @@ -200,10 +200,17 @@ test('parseArgs accepts team shorthand and runtime overrides', () => { assert.equal(shorthand.options.taskTitle, 'Ship team runtime'); assert.equal(shorthand.options.executionMode, 'live'); + const kiroShorthand = parseArgs(['team', '2:kiro', 'Ship Kiro team runtime']); + assert.equal(kiroShorthand.command, 'team'); + assert.equal(kiroShorthand.options.workers, 2); + assert.equal(kiroShorthand.options.provider, 'kiro'); + assert.equal(kiroShorthand.options.clientId, 'kiro-cli'); + assert.equal(kiroShorthand.options.taskTitle, 'Ship Kiro team runtime'); + const explicit = parseArgs([ 'team', '--provider', - 'gemini', + 'kiro', '--workers', '4', '--task', @@ -215,8 +222,8 @@ test('parseArgs accepts team shorthand and runtime overrides', () => { 'json', ]); assert.equal(explicit.command, 'team'); - assert.equal(explicit.options.provider, 'gemini'); - assert.equal(explicit.options.clientId, 'gemini-cli'); + assert.equal(explicit.options.provider, 'kiro'); + assert.equal(explicit.options.clientId, 'kiro-cli'); assert.equal(explicit.options.workers, 4); assert.equal(explicit.options.executionMode, 'dry-run'); assert.equal(explicit.options.planPath, 'docs/plans/refactor-team.md'); @@ -249,6 +256,10 @@ test('parseArgs accepts hud command options', () => { assert.equal(jsonResult.options.provider, 'codex'); assert.equal(jsonResult.options.json, true); + const kiroResult = parseArgs(['hud', '--provider', 'kiro']); + assert.equal(kiroResult.command, 'hud'); + assert.equal(kiroResult.options.provider, 'kiro'); + const watchdogResult = parseArgs(['hud', '--watchdog']); assert.equal(watchdogResult.command, 'hud'); assert.equal(watchdogResult.options.watchdog, true); @@ -333,6 +344,11 @@ test('parseArgs accepts harness subcommands', () => { assert.equal(run.options.maxIterations, 3); assert.equal(run.options.provider, 'codex'); + const kiroRun = parseArgs(['harness', 'run', '--objective', 'Ship X', '--provider', 'kiro', '--dry-run']); + assert.equal(kiroRun.command, 'harness'); + assert.equal(kiroRun.options.subcommand, 'run'); + assert.equal(kiroRun.options.provider, 'kiro'); + const status = parseArgs(['harness', 'status', '--session', 's1', '--workspace', '/tmp/demo', '--json']); assert.equal(status.command, 'harness'); assert.equal(status.options.subcommand, 'status'); @@ -420,6 +436,17 @@ test('getCommandHelpText includes harness usage and examples', () => { assert.match(text, /--workspace <path>/); }); +test('getCommandHelpText includes kiro client and provider lists', () => { + assert.match(getCommandHelpText('setup'), /--client <all\|codex\|claude\|gemini\|opencode\|kiro>/); + assert.match(getCommandHelpText('update'), /--client <all\|codex\|claude\|gemini\|opencode\|kiro>/); + assert.match(getCommandHelpText('uninstall'), /--client <all\|codex\|claude\|gemini\|opencode\|kiro>/); + assert.match(getCommandHelpText('hud'), /--provider <codex\|claude\|gemini\|kiro>/); + assert.match(getRootHelpText(), /One-click multi-client live team runtime \(codex\/claude\/gemini\/kiro\)/); + assert.match(getCommandHelpText('team'), /--provider <codex\|claude\|gemini\|kiro>/); + assert.match(getInternalHelpText('skills', 'install'), /--client <all\|codex\|claude\|gemini\|opencode\|kiro>/); + assert.match(getInternalHelpText('native', 'doctor'), /--client <all\|codex\|claude\|gemini\|opencode\|kiro>/); +}); + test('parseArgs accepts team status/history subcommands', () => { const status = parseArgs(['team', 'status', '--provider', 'codex', '--json']); assert.equal(status.command, 'team'); diff --git a/scripts/tests/aios-components.test.mjs b/scripts/tests/aios-components.test.mjs index 3a433511..8764136e 100644 --- a/scripts/tests/aios-components.test.mjs +++ b/scripts/tests/aios-components.test.mjs @@ -18,6 +18,8 @@ import { uninstallOrchestratorAgents, } from '../lib/components/agents.mjs'; import { installBrowserMcp, migrateBrowserMcpConfig } from '../lib/components/browser.mjs'; +import { normalizeClient } from '../lib/lifecycle/options.mjs'; +import { getClientHomes } from '../lib/platform/paths.mjs'; import { syncClaudeSkillPermissions } from '../lib/components/superpowers.mjs'; import { commandExists, @@ -201,6 +203,40 @@ test('shell install reuses existing ContextDB runtime without reinstall', async assert.equal(called, false); }); +test('kiro is accepted as a deep client with a generated skills surface', async () => { + const rootDir = await makeTemp('aios-kiro-client-root-'); + const kiroHome = await makeTemp('aios-kiro-home-'); + await writeSkillsCatalog(rootDir, [ + { + name: 'sample-skill', + description: 'sample', + source: 'skill-sources/sample-skill', + clients: ['codex'], + scopes: ['global'], + defaultInstall: { global: true, project: false }, + tags: ['sample'], + }, + ]); + await mkdir(path.join(rootDir, 'skill-sources', 'sample-skill'), { recursive: true }); + await writeFile(path.join(rootDir, 'skill-sources', 'sample-skill', 'SKILL.md'), '# sample\n', 'utf8'); + + assert.equal(normalizeClient('kiro'), 'kiro'); + assert.equal( + getClientHomes({ KIRO_HOME: kiroHome, XDG_CONFIG_HOME: path.join(rootDir, '.config') }, rootDir).kiro, + kiroHome + ); + + const logs = []; + await installContextDbSkills({ + rootDir, + client: 'kiro', + homeMap: { kiro: kiroHome }, + io: { log: (line) => logs.push(String(line)) }, + }); + + assert.match(logs.join('\n'), /kiro/i); +}); + test('skills install copies repo-managed skills by default and uninstall removes them', async () => { const rootDir = await makeTemp('aios-skills-root-'); const codexSkillDir = path.join(rootDir, 'skill-sources', 'sample-skill'); @@ -391,6 +427,7 @@ test('browser mcp-migrate updates local and client mcp json configs', async () = const claudeHome = await makeTemp('aios-browser-migrate-claude-'); const geminiHome = await makeTemp('aios-browser-migrate-gemini-'); const opencodeHome = await makeTemp('aios-browser-migrate-opencode-'); + const kiroHome = await makeTemp('aios-browser-migrate-kiro-'); await mkdir(scriptsDir, { recursive: true }); await mkdir(mcpServerDir, { recursive: true }); @@ -421,6 +458,8 @@ test('browser mcp-migrate updates local and client mcp json configs', async () = await writeFile(path.join(mcpServerDir, '.mcp.json'), `${JSON.stringify(legacyConfig, null, 2)}\n`, 'utf8'); await mkdir(claudeHome, { recursive: true }); await writeFile(path.join(claudeHome, 'mcp.json'), `${JSON.stringify(legacyConfig, null, 2)}\n`, 'utf8'); + await mkdir(path.join(kiroHome, 'settings'), { recursive: true }); + await writeFile(path.join(kiroHome, 'settings', 'mcp.json'), `${JSON.stringify(legacyConfig, null, 2)}\n`, 'utf8'); const logs = []; const result = await migrateBrowserMcpConfig({ @@ -431,6 +470,7 @@ test('browser mcp-migrate updates local and client mcp json configs', async () = claude: claudeHome, gemini: geminiHome, opencode: opencodeHome, + kiro: kiroHome, }, }); @@ -448,6 +488,9 @@ test('browser mcp-migrate updates local and client mcp json configs', async () = const claudeMcp = JSON.parse(await readFile(path.join(claudeHome, 'mcp.json'), 'utf8')); assert.equal(claudeMcp.mcpServers['puppeteer-stealth'].command, 'bash'); assert.equal(claudeMcp.mcpServers['playwright-browser-mcp'], undefined); + const kiroMcp = JSON.parse(await readFile(path.join(kiroHome, 'settings', 'mcp.json'), 'utf8')); + assert.equal(kiroMcp.mcpServers['puppeteer-stealth'].command, 'bash'); + assert.equal(kiroMcp.mcpServers['playwright-browser-mcp'], undefined); assert.match(logs.join('\n'), /mcp-migrate summary:/); }); @@ -586,6 +629,23 @@ test('windows claude, gemini, and opencode resolve npm-style cmd launchers to di assert.equal(opencodeSpec.shell, false); }); +test('windows kiro-cli resolves npm-style cmd launcher to direct node execution', async () => { + const kiro = await makeFakeWindowsAgentLauncher( + 'kiro-cli', + 'node_modules/kiro-cli/bin/kiro-cli.js' + ); + + const spec = getCommandSpawnSpec('kiro-cli', ['--version'], { + platform: 'win32', + execPath: kiro.execPath, + env: { PATH: kiro.binDir, PATHEXT: '.EXE;.CMD' }, + }); + + assert.equal(spec.command, kiro.execPath); + assert.deepEqual(spec.args, [kiro.scriptPath, '--version']); + assert.equal(spec.shell, false); +}); + test('skills doctor warns on non-discoverable repo skill roots', async () => { const rootDir = await makeTemp('aios-skills-doctor-root-'); diff --git a/scripts/tests/aios-orchestrator.test.mjs b/scripts/tests/aios-orchestrator.test.mjs index 1552e5f2..9299d67a 100644 --- a/scripts/tests/aios-orchestrator.test.mjs +++ b/scripts/tests/aios-orchestrator.test.mjs @@ -896,7 +896,34 @@ test('dispatch runtime registry rejects unsupported subagent client in live mode assert.equal(result.ok, false); assert.equal(Array.isArray(result.jobRuns), true); assert.match(String(result.error || ''), /Unsupported AIOS_SUBAGENT_CLIENT/i); - assert.match(String(result.error || ''), /codex-cli, claude-code, gemini-cli, opencode-cli/i); + assert.match(String(result.error || ''), /codex-cli, claude-code, gemini-cli, opencode-cli, kiro-cli/i); +}); + +test('dispatch runtime registry accepts kiro-cli as a live subagent client when there are no jobs', async () => { + const runtimes = await importDispatchRuntimes(); + assert.ok(runtimes, 'expected runtime registry module'); + + const registry = runtimes.createDispatchRuntimeRegistry({ executeDryRunPlan: () => ({ mode: 'dry-run', ok: true, jobRuns: [] }) }); + const runtime = runtimes.resolveDispatchRuntime({ runtimeId: 'subagent-runtime', executionMode: 'live' }, registry); + + const result = await runtime.execute({ + plan: { phases: [] }, + dispatchPlan: { + jobs: [], + executorRegistry: [], + executorDetails: [], + }, + dispatchPolicy: null, + env: { + AIOS_EXECUTE_LIVE: '1', + AIOS_SUBAGENT_CLIENT: 'kiro-cli', + }, + }); + + assert.equal(result.mode, 'live'); + assert.equal(result.ok, true); + assert.equal(Array.isArray(result.jobRuns), true); + assert.equal(result.jobRuns.length, 0); }); test('dispatch runtime registry can simulate the subagent runtime when explicitly enabled', async () => { diff --git a/scripts/tests/aios-wrappers.test.mjs b/scripts/tests/aios-wrappers.test.mjs index 224f45ae..5be12c70 100644 --- a/scripts/tests/aios-wrappers.test.mjs +++ b/scripts/tests/aios-wrappers.test.mjs @@ -97,3 +97,15 @@ test('status-browser-cdp.ps1 is a thin wrapper', async () => { const content = await readFile(path.join(repoRoot, 'scripts', 'status-browser-cdp.ps1'), 'utf8'); assert.match(content, /internal browser cdp-status/); }); + +test('contextdb shell wrappers expose kiro-cli and kiro alias through kiro-cli passthrough', async () => { + const zsh = await readFile(path.join(repoRoot, 'scripts', 'contextdb-shell.zsh'), 'utf8'); + const ps1 = await readFile(path.join(repoRoot, 'scripts', 'contextdb-shell.ps1'), 'utf8'); + + assert.match(zsh, /function\s+kiro-cli|kiro-cli\(\)/); + assert.match(zsh, /function\s+kiro|kiro\(\)/); + assert.match(zsh, /ctxdb_invoke_bridge_or_passthrough kiro-cli kiro-cli/); + assert.match(ps1, /function kiro-cli/); + assert.match(ps1, /function kiro/); + assert.match(ps1, /Invoke-BridgeOrPassthrough -Agent "kiro-cli" -Passthrough "kiro-cli"/); +}); diff --git a/scripts/tests/contextdb-shell-bridge-codex-home.test.mjs b/scripts/tests/contextdb-shell-bridge-codex-home.test.mjs index 3c13f280..aa444fb7 100644 --- a/scripts/tests/contextdb-shell-bridge-codex-home.test.mjs +++ b/scripts/tests/contextdb-shell-bridge-codex-home.test.mjs @@ -565,3 +565,56 @@ test('opencode run subcommand passes through without wrapping', async () => { assert.deepEqual(payload.argv, ['run', 'hello']); assert.equal(parseRunnerWorkspace(result.stdout), ''); }); + +test('kiro-cli interactive runs are wrapped with kiro agent identity', async () => { + const cwd = await mkdtemp(path.join(os.tmpdir(), 'aios-bridge-kiro-interactive-')); + const fakeBin = await createFakePassthroughCommand('kiro-cli', 'FAKE_KIRO'); + const fakeRunner = await createFakeRunner(); + + const result = runBridge({ + cwd, + pathPrefix: fakeBin, + agent: 'kiro-cli', + command: 'kiro-cli', + args: [], + env: { + CTXDB_RUNNER: fakeRunner, + CTXDB_WRAP_MODE: 'all', + }, + }); + + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.equal(parseRunnerWorkspace(result.stdout), cwd); + const runnerArgs = parseRunnerArgs(result.stdout); + const agentIndex = runnerArgs.indexOf('--agent'); + assert.equal(agentIndex >= 0, true); + assert.equal(runnerArgs[agentIndex + 1], 'kiro-cli'); + + const autoPrompt = parseRunnerAutoPrompt(result.stdout); + assert.match(autoPrompt, /--agent kiro-cli .*--route team/u); + assert.match(autoPrompt, /--agent kiro-cli .*--route subagent/u); +}); + +test('kiro-cli admin subcommands pass through without wrapping', async () => { + const cwd = await mkdtemp(path.join(os.tmpdir(), 'aios-bridge-kiro-agent-')); + const fakeBin = await createFakePassthroughCommand('kiro-cli', 'FAKE_KIRO'); + const fakeRunner = await createFakeRunner(); + + const result = runBridge({ + cwd, + pathPrefix: fakeBin, + agent: 'kiro-cli', + command: 'kiro-cli', + args: ['agent', 'list'], + env: { + CTXDB_RUNNER: fakeRunner, + CTXDB_WRAP_MODE: 'all', + }, + }); + + assert.equal(result.status, 0, result.stderr || result.stdout); + const payload = parseLastJsonPayload(result.stdout); + assert.equal(payload.marker, 'FAKE_KIRO'); + assert.deepEqual(payload.argv, ['agent', 'list']); + assert.equal(parseRunnerWorkspace(result.stdout), ''); +}); diff --git a/scripts/tests/ctx-agent-core.test.mjs b/scripts/tests/ctx-agent-core.test.mjs index 833e04fd..bd75b0ea 100644 --- a/scripts/tests/ctx-agent-core.test.mjs +++ b/scripts/tests/ctx-agent-core.test.mjs @@ -7,11 +7,14 @@ import test from 'node:test'; import { buildWorkspaceMemoryOverlay, + buildKiroChatArgs, classifyOneShotFailure, isBetterSqlite3AbiMismatch, resolveRoutedSubagentClient, + resolveRoutePreviewAgent, resolveTaskRouteDecision, shouldAutoRebuildNative, + runOneShotAgent, } from '../ctx-agent-core.mjs'; import { runContextDbCli } from '../lib/contextdb-cli.mjs'; @@ -63,6 +66,10 @@ async function createFakeOpenCodeCommand(marker = 'FAKE_OPENCODE_OK') { return createFakeCliCommand('opencode', marker); } +async function createFakeKiroCommand(marker = 'FAKE_KIRO_OK') { + return createFakeCliCommand('kiro-cli', marker); +} + function parseLastJsonPayload(stdout) { const line = String(stdout || '').trim().split(/\r?\n/).at(-1) || '{}'; return JSON.parse(line); @@ -200,6 +207,20 @@ test('resolveRoutedSubagentClient falls back to provider-supported runtimes', () }), 'gemini-cli' ); + assert.equal( + resolveRoutedSubagentClient({ agent: 'kiro-cli', teamProvider: 'auto', env: {} }), + 'kiro-cli' + ); + assert.equal( + resolveRoutedSubagentClient({ agent: '', teamProvider: 'kiro', env: {} }), + 'kiro-cli' + ); +}); + +test('resolveRoutePreviewAgent preserves kiro-cli for live route previews', () => { + assert.equal(resolveRoutePreviewAgent('kiro-cli'), 'kiro-cli'); + assert.equal(resolveRoutePreviewAgent('codex-cli'), 'codex-cli'); + assert.equal(resolveRoutePreviewAgent('claude-code'), 'claude-code'); }); test('buildWorkspaceMemoryOverlay reads pinned and recent memos', async () => { @@ -430,6 +451,7 @@ test('ctx-agent tolerates context:pack failures by running without a context pac encoding: 'utf8', env: { ...process.env, + AIOS_PROJECT_NODE_ACTIVE: '1', CTXDB_PACK_STRICT: '0', }, } @@ -1114,6 +1136,34 @@ test('ctx-agent one-shot OpenCode mode uses file-backed context handoff', async } }); +test('ctx-agent one-shot Kiro mode uses headless chat with trust-all-tools', async () => { + const fakeBin = await createFakeKiroCommand(); + const previousPath = process.env.PATH; + + try { + process.env.PATH = `${fakeBin}${path.delimiter}${previousPath || ''}`; + const result = runOneShotAgent( + 'kiro-cli', + 'Injected Kiro context', + 'Summarize the current status.', + [], + { injectContext: true } + ); + + assert.equal(result.exitCode, 0); + const payload = parseLastJsonPayload(result.output); + assert.equal(payload.marker, 'FAKE_KIRO_OK'); + assert.equal(payload.argv[0], 'chat'); + assert.equal(payload.argv.includes('--no-interactive'), true); + assert.equal(payload.argv.includes('--trust-all-tools'), true); + assert.match(payload.argv.at(-1), /Injected Kiro context/u); + assert.match(payload.argv.at(-1), /Summarize the current status\./u); + } finally { + process.env.PATH = previousPath; + await rm(fakeBin, { recursive: true, force: true }); + } +}); + test('ctx-agent interactive OpenCode mode sends auto prompt via context packet file reference', async () => { const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), 'aios-ctx-agent-opencode-interactive-')); const sessionId = 'ctx-opencode-interactive'; @@ -1176,3 +1226,11 @@ test('ctx-agent interactive OpenCode mode sends auto prompt via context packet f await rm(workspaceRoot, { recursive: true, force: true }); } }); + +test('ctx-agent Kiro chat args add trust-all-tools in interactive and headless modes', () => { + assert.deepEqual(buildKiroChatArgs(), ['chat', '--trust-all-tools']); + assert.deepEqual( + buildKiroChatArgs(['--model', 'kiro-model'], { headless: true }), + ['chat', '--no-interactive', '--trust-all-tools', '--model', 'kiro-model'] + ); +}); diff --git a/scripts/tests/handoff.test.mjs b/scripts/tests/handoff.test.mjs index 03726537..95d30bc1 100644 --- a/scripts/tests/handoff.test.mjs +++ b/scripts/tests/handoff.test.mjs @@ -31,6 +31,17 @@ test('normalizeHandoffPacket produces valid v2 packet from minimal input', async assert.deepEqual(packet.assumptions, []); }); +test('normalizeHandoffPacket accepts kiro-cli agentType', async () => { + const packet = normalizeHandoffPacket({ + fromSessionId: 'session-kiro', + agentType: 'kiro-cli', + role: 'planner', + }); + + assert.equal(packet.fromAgent.agentType, 'kiro-cli'); + assert.equal(packet.fromAgent.role, 'planner'); +}); + test('normalizeHandoffPacket rejects invalid agentType', async () => { assert.throws( () => normalizeHandoffPacket({ diff --git a/scripts/tests/harness-profiles.test.mjs b/scripts/tests/harness-profiles.test.mjs index 8056a2b3..c69f84d1 100644 --- a/scripts/tests/harness-profiles.test.mjs +++ b/scripts/tests/harness-profiles.test.mjs @@ -16,6 +16,10 @@ test('resolveSoloHarnessProfile maps codex and opencode providers', () => { const opencode = resolveSoloHarnessProfile({ provider: 'opencode' }); assert.equal(opencode.clientId, 'opencode-cli'); assert.equal(opencode.command, 'opencode'); + + const kiro = resolveSoloHarnessProfile({ provider: 'kiro' }); + assert.equal(kiro.clientId, 'kiro-cli'); + assert.equal(kiro.command, 'kiro-cli'); }); test('validateSoloHarnessExtraArgs rejects reserved harness flags', () => { @@ -35,6 +39,28 @@ test('checkSoloHarnessProfileReadiness reports missing provider binaries clearly assert.match(readiness.reason, /codex/i); }); +test('checkSoloHarnessProfileReadiness requires KIRO_API_KEY for kiro', async () => { + const readiness = await checkSoloHarnessProfileReadiness({ + provider: 'kiro', + env: {}, + commandExistsImpl: async () => true, + }); + + assert.equal(readiness.ok, false); + assert.match(readiness.reason, /KIRO_API_KEY/i); +}); + +test('checkSoloHarnessProfileReadiness accepts kiro when command and key are present', async () => { + const readiness = await checkSoloHarnessProfileReadiness({ + provider: 'kiro', + env: { KIRO_API_KEY: 'test-key' }, + commandExistsImpl: async () => true, + }); + + assert.equal(readiness.ok, true); + assert.equal(readiness.profile.clientId, 'kiro-cli'); +}); + test('buildSoloHarnessCommand routes one-shot runs through ctx-agent', () => { const built = buildSoloHarnessCommand({ rootDir: '/tmp/aios', diff --git a/scripts/tests/model-router.test.mjs b/scripts/tests/model-router.test.mjs new file mode 100644 index 00000000..bc106426 --- /dev/null +++ b/scripts/tests/model-router.test.mjs @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { normalizeModelRouting, providerToClientId } from '../lib/model-router.mjs'; + +test('providerToClientId maps kiro to kiro-cli', () => { + assert.equal(providerToClientId('kiro'), 'kiro-cli'); + assert.equal(providerToClientId('claude'), 'claude-code'); +}); + +test('normalizeModelRouting derives kiro clientId from provider', () => { + const routing = normalizeModelRouting({ + modelId: 'kiro-model', + taskType: 'general', + provider: 'kiro', + reason: 'test', + }); + + assert.equal(routing.modelId, 'kiro-model'); + assert.equal(routing.provider, 'kiro'); + assert.equal(routing.clientId, 'kiro-cli'); +}); diff --git a/scripts/tests/native-doctor.test.mjs b/scripts/tests/native-doctor.test.mjs index 0f89d59f..36c07e60 100644 --- a/scripts/tests/native-doctor.test.mjs +++ b/scripts/tests/native-doctor.test.mjs @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises'; +import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; @@ -30,6 +30,7 @@ async function writeNativeManifest(rootDir) { claude: { tier: 'deep', metadataRoot: '.claude', outputs: ['CLAUDE.md', '.claude/settings.local.json', '.claude/agents', '.claude/skills'] }, gemini: { tier: 'compatibility', metadataRoot: '.gemini', outputs: ['.gemini/AIOS.md', '.gemini/skills'] }, opencode: { tier: 'compatibility', metadataRoot: '.opencode', outputs: ['.opencode/AIOS.md', '.opencode/skills'] }, + kiro: { tier: 'deep', metadataRoot: '.kiro', outputs: ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills'] }, }, }); } @@ -40,6 +41,7 @@ async function writeNativeSources(rootDir) { await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'claude', 'project'), { recursive: true }); await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'gemini', 'project'), { recursive: true }); await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'opencode', 'project'), { recursive: true }); + await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'kiro', 'project'), { recursive: true }); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'shared', 'partials', 'core-instructions.md'), 'Shared native instructions.\n', 'utf8'); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'shared', 'partials', 'contextdb.md'), 'ContextDB bridge enabled.\n', 'utf8'); @@ -64,6 +66,17 @@ For browser tasks, use this operating pattern unless the user explicitly asks ot }); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'gemini', 'project', 'AIOS.md'), 'Gemini compatibility instructions.\n', 'utf8'); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'opencode', 'project', 'AIOS.md'), 'Opencode compatibility instructions.\n', 'utf8'); + await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'kiro', 'project', 'steering.md'), 'Kiro steering instructions.\n', 'utf8'); + await writeJson(path.join(rootDir, 'client-sources', 'native-base', 'kiro', 'project', 'mcp.json'), { + 'puppeteer-stealth': { + type: 'stdio', + command: 'bash', + args: ['{{ROOT_DIR}}/scripts/run-browser-use-mcp.sh'], + env: { + BROWSER_USE_CDP_URL: 'http://127.0.0.1:9222', + }, + }, + }); } async function writeSkillSources(rootDir) { @@ -87,7 +100,7 @@ async function writeSkillSources(rootDir) { async function writeAgentSources(rootDir) { await writeJson(path.join(rootDir, 'agent-sources', 'manifest.json'), { schemaVersion: 1, - generatedTargets: ['claude', 'codex'], + generatedTargets: ['claude', 'codex', 'kiro'], }); const roles = [ @@ -199,6 +212,52 @@ test('native doctor reports sync drift when repo-local generated skills change', assert.match(await readFile(path.join(rootDir, '.gemini', 'skills', 'find-skills', 'SKILL.md'), 'utf8'), /drifted/); }); +test('native doctor reports Kiro MCP drift with a concrete recovery command', async () => { + const rootDir = await makeTemp('aios-native-doctor-kiro-mcp-root-'); + await seedNativeRoot(rootDir); + await syncNativeEnhancements({ rootDir, client: 'kiro' }); + await writeJson(path.join(rootDir, '.kiro', 'settings', 'mcp.json'), { + mcpServers: { + 'puppeteer-stealth': { + command: 'node', + args: ['wrong.js'], + }, + }, + }); + + const logs = []; + const result = await runDoctorSuite({ + rootDir, + nativeOnly: true, + io: { log: (line) => logs.push(String(line)) }, + env: {}, + }); + + assert.equal(result.exitCode, 1); + assert.match(logs.join('\n'), /kiro/); + assert.match(logs.join('\n'), /\.kiro\/settings\/mcp\.json#mcpServers/); + assert.match(logs.join('\n'), /node scripts\/aios\.mjs update --components native --client kiro/); +}); + +test('native doctor reports missing Kiro agents with a concrete recovery command', async () => { + const rootDir = await makeTemp('aios-native-doctor-kiro-agents-root-'); + await seedNativeRoot(rootDir); + await syncNativeEnhancements({ rootDir, client: 'kiro' }); + await rm(path.join(rootDir, '.kiro', 'agents'), { recursive: true, force: true }); + + const logs = []; + const result = await runDoctorSuite({ + rootDir, + nativeOnly: true, + io: { log: (line) => logs.push(String(line)) }, + env: {}, + }); + + assert.equal(result.exitCode, 1); + assert.match(logs.join('\n'), /\[missing\] \.kiro\/agents/); + assert.match(logs.join('\n'), /node scripts\/aios\.mjs update --components native --client kiro/); +}); + test('doctor --native --fix repairs unmanaged compatibility docs and exits cleanly', async () => { const rootDir = await makeTemp('aios-native-doctor-fix-managed-file-root-'); await seedNativeRoot(rootDir); diff --git a/scripts/tests/native-repairs.test.mjs b/scripts/tests/native-repairs.test.mjs index eea3b1c3..e073cb9b 100644 --- a/scripts/tests/native-repairs.test.mjs +++ b/scripts/tests/native-repairs.test.mjs @@ -50,6 +50,11 @@ async function writeNativeManifest(rootDir) { metadataRoot: '.opencode', outputs: ['.opencode/AIOS.md', '.opencode/skills'], }, + kiro: { + tier: 'deep', + metadataRoot: '.kiro', + outputs: ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills'], + }, }, }); } diff --git a/scripts/tests/native-source-tree.test.mjs b/scripts/tests/native-source-tree.test.mjs index 28038e81..93776666 100644 --- a/scripts/tests/native-source-tree.test.mjs +++ b/scripts/tests/native-source-tree.test.mjs @@ -34,18 +34,20 @@ test('native manifest resolves deep and compatibility tiers by client', async () claude: { tier: 'deep', metadataRoot: '.claude', outputs: ['CLAUDE.md', '.claude/settings.local.json', '.claude/agents', '.claude/skills'] }, gemini: { tier: 'compatibility', metadataRoot: '.gemini', outputs: ['.gemini/AIOS.md', '.gemini/skills'] }, opencode: { tier: 'compatibility', metadataRoot: '.opencode', outputs: ['.opencode/AIOS.md', '.opencode/skills'] }, + kiro: { tier: 'deep', metadataRoot: '.kiro', outputs: ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills'] }, }, }); const manifest = loadNativeSyncManifest(rootDir); - assert.deepEqual(resolveNativeClients('all'), ['codex', 'claude', 'gemini', 'opencode']); + assert.deepEqual(resolveNativeClients('all'), ['codex', 'claude', 'gemini', 'opencode', 'kiro']); assert.equal(manifest.clients.codex.tier, 'deep'); assert.equal(manifest.clients.claude.tier, 'deep'); assert.equal(manifest.clients.gemini.tier, 'compatibility'); assert.equal(manifest.clients.opencode.tier, 'compatibility'); + assert.equal(manifest.clients.kiro.tier, 'deep'); }); -test('native output plan maps codex and claude repo outputs with per-client metadata roots', async () => { +test('native output plan maps repo outputs with per-client metadata roots', async () => { const rootDir = await makeTemp('aios-native-plan-root-'); await writeJson(path.join(rootDir, 'config', 'native-sync-manifest.json'), { schemaVersion: 1, @@ -59,12 +61,14 @@ test('native output plan maps codex and claude repo outputs with per-client meta claude: { tier: 'deep', metadataRoot: '.claude', outputs: ['CLAUDE.md', '.claude/settings.local.json', '.claude/agents', '.claude/skills'] }, gemini: { tier: 'compatibility', metadataRoot: '.gemini', outputs: ['.gemini/AIOS.md', '.gemini/skills'] }, opencode: { tier: 'compatibility', metadataRoot: '.opencode', outputs: ['.opencode/AIOS.md', '.opencode/skills'] }, + kiro: { tier: 'deep', metadataRoot: '.kiro', outputs: ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills'] }, }, }); const manifest = loadNativeSyncManifest(rootDir); const codexPlan = buildNativeOutputPlan({ rootDir, manifest, client: 'codex' }); const claudePlan = buildNativeOutputPlan({ rootDir, manifest, client: 'claude' }); + const kiroPlan = buildNativeOutputPlan({ rootDir, manifest, client: 'kiro' }); assert.equal(codexPlan.client, 'codex'); assert.equal(codexPlan.metadataPath, path.join(rootDir, '.codex', NATIVE_SYNC_META_FILE)); @@ -73,4 +77,8 @@ test('native output plan maps codex and claude repo outputs with per-client meta assert.equal(claudePlan.client, 'claude'); assert.equal(claudePlan.metadataPath, path.join(rootDir, '.claude', NATIVE_SYNC_META_FILE)); assert.deepEqual(claudePlan.outputs, ['CLAUDE.md', '.claude/settings.local.json', '.claude/agents', '.claude/skills']); + + assert.equal(kiroPlan.client, 'kiro'); + assert.equal(kiroPlan.metadataPath, path.join(rootDir, '.kiro', NATIVE_SYNC_META_FILE)); + assert.deepEqual(kiroPlan.outputs, ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills']); }); diff --git a/scripts/tests/native-sync.test.mjs b/scripts/tests/native-sync.test.mjs index 52eef08a..30dfd08f 100644 --- a/scripts/tests/native-sync.test.mjs +++ b/scripts/tests/native-sync.test.mjs @@ -29,6 +29,7 @@ async function writeNativeManifest(rootDir) { claude: { tier: 'deep', metadataRoot: '.claude', outputs: ['CLAUDE.md', '.claude/settings.local.json', '.claude/agents', '.claude/skills'] }, gemini: { tier: 'compatibility', metadataRoot: '.gemini', outputs: ['.gemini/AIOS.md', '.gemini/skills'] }, opencode: { tier: 'compatibility', metadataRoot: '.opencode', outputs: ['.opencode/AIOS.md', '.opencode/skills'] }, + kiro: { tier: 'deep', metadataRoot: '.kiro', outputs: ['.kiro/steering/AIOS.md', '.kiro/settings/mcp.json', '.kiro/agents', '.kiro/skills'] }, }, }); } @@ -39,6 +40,7 @@ async function writeNativeSources(rootDir) { await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'claude', 'project'), { recursive: true }); await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'gemini', 'project'), { recursive: true }); await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'opencode', 'project'), { recursive: true }); + await mkdir(path.join(rootDir, 'client-sources', 'native-base', 'kiro', 'project'), { recursive: true }); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'shared', 'partials', 'core-instructions.md'), 'Shared native instructions.\n', 'utf8'); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'shared', 'partials', 'contextdb.md'), 'ContextDB bridge enabled.\n', 'utf8'); @@ -63,6 +65,17 @@ For browser tasks, use this operating pattern unless the user explicitly asks ot }); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'gemini', 'project', 'AIOS.md'), 'Gemini compatibility instructions.\n', 'utf8'); await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'opencode', 'project', 'AIOS.md'), 'Opencode compatibility instructions.\n', 'utf8'); + await writeFile(path.join(rootDir, 'client-sources', 'native-base', 'kiro', 'project', 'steering.md'), 'Kiro steering instructions.\n', 'utf8'); + await writeJson(path.join(rootDir, 'client-sources', 'native-base', 'kiro', 'project', 'mcp.json'), { + 'puppeteer-stealth': { + type: 'stdio', + command: 'bash', + args: ['{{ROOT_DIR}}/scripts/run-browser-use-mcp.sh'], + env: { + BROWSER_USE_CDP_URL: 'http://127.0.0.1:9222', + }, + }, + }); } async function writeSkillSources(rootDir) { @@ -73,9 +86,10 @@ async function writeSkillSources(rootDir) { claude: '.claude/skills', gemini: '.gemini/skills', opencode: '.opencode/skills', + kiro: '.kiro/skills', }, skills: [ - { relativeSkillPath: 'find-skills', installCatalogName: 'find-skills', repoTargets: ['codex', 'claude', 'gemini', 'opencode'] }, + { relativeSkillPath: 'find-skills', installCatalogName: 'find-skills', repoTargets: ['codex', 'claude', 'gemini', 'opencode', 'kiro'] }, ], legacyUnmanaged: [], }); @@ -86,7 +100,7 @@ async function writeSkillSources(rootDir) { async function writeAgentSources(rootDir) { await writeJson(path.join(rootDir, 'agent-sources', 'manifest.json'), { schemaVersion: 1, - generatedTargets: ['claude', 'codex'], + generatedTargets: ['claude', 'codex', 'kiro'], }); const roles = [ @@ -172,6 +186,37 @@ test('native sync writes compatibility docs for gemini and opencode', async () = assert.equal(readNativeSyncMetadata(path.join(rootDir, '.opencode')).tier, 'compatibility'); }); +test('native sync writes Kiro steering and merges MCP settings without clobbering existing servers', async () => { + const rootDir = await makeTemp('aios-native-sync-kiro-root-'); + await seedNativeRoot(rootDir); + await writeJson(path.join(rootDir, '.kiro', 'settings', 'mcp.json'), { + mcpServers: { + existing: { + command: 'node', + args: ['server.js'], + }, + }, + keepMe: true, + }); + + await syncNativeEnhancements({ rootDir, client: 'kiro' }); + + const steering = await readFile(path.join(rootDir, '.kiro', 'steering', 'AIOS.md'), 'utf8'); + const mcp = JSON.parse(await readFile(path.join(rootDir, '.kiro', 'settings', 'mcp.json'), 'utf8')); + + assert.match(steering, /AIOS NATIVE BEGIN/); + assert.match(steering, /Shared native instructions/); + assert.match(steering, /Kiro steering instructions/); + assert.equal(mcp.keepMe, true); + assert.equal(mcp.mcpServers.existing.command, 'node'); + assert.equal(mcp.mcpServers['puppeteer-stealth'].command, 'bash'); + assert.deepEqual(mcp.mcpServers['puppeteer-stealth'].args, [path.join(rootDir, 'scripts', 'run-browser-use-mcp.sh')]); + assert.equal(JSON.parse(await readFile(path.join(rootDir, '.kiro', 'agents', 'rex-planner.json'), 'utf8')).name, 'rex-planner'); + assert.match(await readFile(path.join(rootDir, '.kiro', 'skills', 'find-skills', 'SKILL.md'), 'utf8'), /native skill/); + assert.equal(readNativeSyncMetadata(path.join(rootDir, '.kiro')).client, 'kiro'); + assert.equal(readNativeSyncMetadata(path.join(rootDir, '.kiro')).tier, 'deep'); +}); + test('native sync repair mode can replace unmanaged compatibility docs', async () => { const rootDir = await makeTemp('aios-native-sync-repair-managed-file-root-'); await seedNativeRoot(rootDir); diff --git a/scripts/tests/release-pipeline.test.mjs b/scripts/tests/release-pipeline.test.mjs index d8a3d831..87ef81c8 100644 --- a/scripts/tests/release-pipeline.test.mjs +++ b/scripts/tests/release-pipeline.test.mjs @@ -41,13 +41,15 @@ async function seedFixtureRepo(rootDir, { await writeFixtureFile(rootDir, 'README-zh.md', '# README-ZH\n'); await writeFixtureFile(rootDir, 'skills-lock.json', '{}\n'); await writeFixtureFile(rootDir, 'config/skills-catalog.json', '{"version":1,"skills":[]}\n'); - await writeFixtureFile(rootDir, 'config/native-sync-manifest.json', '{"schemaVersion":1,"managedBy":"aios","markers":{"markdownBegin":"<!-- AIOS NATIVE BEGIN -->","markdownEnd":"<!-- AIOS NATIVE END -->"},"clients":{"codex":{"tier":"deep","metadataRoot":".codex","outputs":["AGENTS.md",".codex/agents",".codex/skills"]},"claude":{"tier":"deep","metadataRoot":".claude","outputs":["CLAUDE.md",".claude/settings.local.json",".claude/agents",".claude/skills"]},"gemini":{"tier":"compatibility","metadataRoot":".gemini","outputs":[".gemini/AIOS.md",".gemini/skills"]},"opencode":{"tier":"compatibility","metadataRoot":".opencode","outputs":[".opencode/AIOS.md",".opencode/skills"]}}}\n'); + await writeFixtureFile(rootDir, 'config/native-sync-manifest.json', '{"schemaVersion":1,"managedBy":"aios","markers":{"markdownBegin":"<!-- AIOS NATIVE BEGIN -->","markdownEnd":"<!-- AIOS NATIVE END -->"},"clients":{"codex":{"tier":"deep","metadataRoot":".codex","outputs":["AGENTS.md",".codex/agents",".codex/skills"]},"claude":{"tier":"deep","metadataRoot":".claude","outputs":["CLAUDE.md",".claude/settings.local.json",".claude/agents",".claude/skills"]},"gemini":{"tier":"compatibility","metadataRoot":".gemini","outputs":[".gemini/AIOS.md",".gemini/skills"]},"opencode":{"tier":"compatibility","metadataRoot":".opencode","outputs":[".opencode/AIOS.md",".opencode/skills"]},"kiro":{"tier":"deep","metadataRoot":".kiro","outputs":[".kiro/steering/AIOS.md",".kiro/settings/mcp.json",".kiro/agents",".kiro/skills"]}}}\n'); await writeFixtureFile(rootDir, 'mcp-server/package.json', '{"name":"fixture-mcp"}\n'); await writeFixtureFile(rootDir, 'memory/README.md', '# memory\n'); await writeFixtureFile(rootDir, 'skill-sources/sample-skill/SKILL.md', '# canonical\n'); await writeFixtureFile(rootDir, 'client-sources/native-base/gemini/project/AIOS.md', '# native gemini\n'); + await writeFixtureFile(rootDir, 'client-sources/native-base/kiro/project/steering.md', '# native kiro\n'); + await writeFixtureFile(rootDir, 'client-sources/native-base/kiro/project/mcp.json', '{}\n'); await writeFixtureFile(rootDir, 'memory/specs/orchestrator-agents.json', '{}\n'); - await writeFixtureFile(rootDir, 'agent-sources/manifest.json', '{"schemaVersion":1,"generatedTargets":["claude","codex"]}\n'); + await writeFixtureFile(rootDir, 'agent-sources/manifest.json', '{"schemaVersion":1,"generatedTargets":["claude","codex","kiro"]}\n'); await writeFixtureFile(rootDir, 'agent-sources/roles/rex-planner.json', '{"schemaVersion":1,"id":"rex-planner","role":"planner","name":"rex-planner","description":"planner","tools":["Read"],"model":"sonnet","handoffTarget":"next-phase","systemPrompt":"plan"}\n'); await writeFixtureFile(rootDir, 'agent-sources/roles/rex-implementer.json', '{"schemaVersion":1,"id":"rex-implementer","role":"implementer","name":"rex-implementer","description":"implement","tools":["Read","Edit"],"model":"sonnet","handoffTarget":"next-phase","systemPrompt":"implement"}\n'); await writeFixtureFile(rootDir, 'agent-sources/roles/rex-reviewer.json', '{"schemaVersion":1,"id":"rex-reviewer","role":"reviewer","name":"rex-reviewer","description":"review","tools":["Read"],"model":"sonnet","handoffTarget":"merge-gate","systemPrompt":"review"}\n'); @@ -57,6 +59,11 @@ async function seedFixtureRepo(rootDir, { await writeFixtureFile(rootDir, '.claude/skills/sample-skill/SKILL.md', '# claude\n'); await writeFixtureFile(rootDir, '.claude/agents/rex.md', '# claude agent\n'); await writeFixtureFile(rootDir, '.agents/skills/sample-skill/SKILL.md', '# agents\n'); + await writeFixtureFile(rootDir, '.kiro/.aios-native-sync.json', '{"client":"kiro","tier":"deep","managedTargets":[".kiro/steering/AIOS.md",".kiro/settings/mcp.json",".kiro/agents",".kiro/skills"]}\n'); + await writeFixtureFile(rootDir, '.kiro/steering/AIOS.md', '# kiro steering\n'); + await writeFixtureFile(rootDir, '.kiro/settings/mcp.json', '{}\n'); + await writeFixtureFile(rootDir, '.kiro/agents/rex-planner.json', '{"name":"rex-planner","prompt":"<!-- AIOS-GENERATED: orchestrator-agents v1 -->\\n<!-- END AIOS-GENERATED -->"}\n'); + await writeFixtureFile(rootDir, '.kiro/skills/sample-skill/SKILL.md', '# kiro skill\n'); await writeFixtureFile(rootDir, 'scripts/package-release.sh', await readFile(path.join(workspaceRoot, 'scripts', 'package-release.sh'), 'utf8')); await writeFixtureFile(rootDir, 'scripts/package-release.ps1', await readFile(path.join(workspaceRoot, 'scripts', 'package-release.ps1'), 'utf8')); @@ -75,6 +82,7 @@ async function seedFixtureRepo(rootDir, { await writeFixtureFile(rootDir, 'scripts/lib/agents/emitters/shared.mjs', await readFile(path.join(workspaceRoot, 'scripts', 'lib', 'agents', 'emitters', 'shared.mjs'), 'utf8')); await writeFixtureFile(rootDir, 'scripts/lib/agents/emitters/claude.mjs', await readFile(path.join(workspaceRoot, 'scripts', 'lib', 'agents', 'emitters', 'claude.mjs'), 'utf8')); await writeFixtureFile(rootDir, 'scripts/lib/agents/emitters/codex.mjs', await readFile(path.join(workspaceRoot, 'scripts', 'lib', 'agents', 'emitters', 'codex.mjs'), 'utf8')); + await writeFixtureFile(rootDir, 'scripts/lib/agents/emitters/kiro.mjs', await readFile(path.join(workspaceRoot, 'scripts', 'lib', 'agents', 'emitters', 'kiro.mjs'), 'utf8')); await writeFixtureFile(rootDir, 'scripts/lib/harness/orchestrator-agents.mjs', await readFile(path.join(workspaceRoot, 'scripts', 'lib', 'harness', 'orchestrator-agents.mjs'), 'utf8')); assertOk(run('git', ['init'], { cwd: rootDir }), 'git init failed'); @@ -114,6 +122,22 @@ test('package-release.sh emits stable assets that include native, skill, and age run('test', ['-f', path.join(extractDir, 'rex-cli', 'client-sources', 'native-base', 'gemini', 'project', 'AIOS.md')]), 'rex-cli.tar.gz did not include client-sources/native-base/gemini/project/AIOS.md' ); + assertOk( + run('test', ['-f', path.join(extractDir, 'rex-cli', 'client-sources', 'native-base', 'kiro', 'project', 'steering.md')]), + 'rex-cli.tar.gz did not include client-sources/native-base/kiro/project/steering.md' + ); + assertOk( + run('test', ['-f', path.join(extractDir, 'rex-cli', '.kiro', 'steering', 'AIOS.md')]), + 'rex-cli.tar.gz did not include generated .kiro/steering/AIOS.md' + ); + assertOk( + run('test', ['-f', path.join(extractDir, 'rex-cli', '.kiro', 'agents', 'rex-planner.json')]), + 'rex-cli.tar.gz did not include generated Kiro agent JSON' + ); + assertOk( + run('test', ['-f', path.join(extractDir, 'rex-cli', '.kiro', 'skills', 'sample-skill', 'SKILL.md')]), + 'rex-cli.tar.gz did not include generated Kiro skills' + ); assertOk( run('test', ['-f', path.join(extractDir, 'rex-cli', 'package.json')]), 'rex-cli.tar.gz did not include root package.json for direct release installs' diff --git a/scripts/tests/skills-component.test.mjs b/scripts/tests/skills-component.test.mjs index e5a46e79..55d3c956 100644 --- a/scripts/tests/skills-component.test.mjs +++ b/scripts/tests/skills-component.test.mjs @@ -320,6 +320,66 @@ test('doctor and uninstall respect project scope targets', async () => { assert.equal(missing, true); }); +test('project scope installs and removes Kiro skills the same way as other clients', async () => { + const rootDir = await makeTemp('aios-skills-kiro-project-root-'); + const projectRoot = await makeTemp('aios-skills-kiro-project-workspace-'); + const kiroHome = await makeTemp('aios-skills-kiro-project-home-'); + await writeSkill(rootDir, 'skill-sources/xhs-ops-methods'); + await writeCanonicalCatalog(rootDir, [ + { + name: 'xhs-ops-methods', + description: 'project only', + source: 'skill-sources/xhs-ops-methods', + clients: ['kiro'], + scopes: ['project'], + defaultInstall: { global: false, project: false }, + tags: ['xhs'], + }, + ]); + + await installContextDbSkills({ + rootDir, + projectRoot, + client: 'kiro', + scope: 'project', + selectedSkills: ['xhs-ops-methods'], + homeMap: { kiro: kiroHome }, + }); + + const installedPath = path.join(projectRoot, '.kiro', 'skills', 'xhs-ops-methods', 'SKILL.md'); + assert.match(await readFile(installedPath, 'utf8'), /sample/); + + const logs = []; + await doctorContextDbSkills({ + rootDir, + projectRoot, + client: 'kiro', + scope: 'project', + selectedSkills: ['xhs-ops-methods'], + homeMap: { kiro: kiroHome }, + io: { log: (line) => logs.push(String(line)) }, + }); + assert.match(logs.join('\n'), /\.kiro\/skills/); + assert.match(logs.join('\n'), /managed copy install/); + + await uninstallContextDbSkills({ + rootDir, + projectRoot, + client: 'kiro', + scope: 'project', + selectedSkills: ['xhs-ops-methods'], + homeMap: { kiro: kiroHome }, + }); + + let missing = false; + try { + await readFile(installedPath, 'utf8'); + } catch { + missing = true; + } + assert.equal(missing, true); +}); + test('project scope can target a workspace that differs from the catalog source repo', async () => { const rootDir = await makeTemp('aios-skills-source-root-'); const projectRoot = await makeTemp('aios-skills-workspace-root-'); diff --git a/skill-sources/harness-init-runner/assets/template/harness/run.mjs b/skill-sources/harness-init-runner/assets/template/harness/run.mjs index 5239b51f..bcbec362 100644 --- a/skill-sources/harness-init-runner/assets/template/harness/run.mjs +++ b/skill-sources/harness-init-runner/assets/template/harness/run.mjs @@ -71,7 +71,7 @@ function buildProviderCommand(providerConfig, vars) { function usage() { return [ 'Usage:', - ' node harness/run.mjs --provider <codex|claude|gemini|opencode> [--task "…"] [--name "…"] [--config path] [--allow-risk]', + ' node harness/run.mjs --provider <codex|claude|gemini|opencode|kiro> [--task "…"] [--name "…"] [--config path] [--allow-risk]', '', 'Notes:', ' - If --task is omitted, the runner reads task text from stdin.', From 228c6fec18a525046f033eb02d3b5b072905e1db Mon Sep 17 00:00:00 2001 From: billowshen <2528273456@qq.com> Date: Thu, 14 May 2026 14:08:15 +0800 Subject: [PATCH 3/3] chore(kiro): sync native workspace outputs --- .kiro/.aios-native-sync.json | 15 + .kiro/agents/rex-implementer.json | 25 ++ .kiro/agents/rex-planner.json | 21 + .kiro/agents/rex-reviewer.json | 21 + .kiro/agents/rex-security-reviewer.json | 21 + .kiro/settings/mcp.json | 25 ++ .../skill-creator/.aios-skill-sync.json | 9 + .kiro/skills/.system/skill-creator/SKILL.md | 368 ++++++++++++++++ .../.system/skill-creator/agents/openai.yaml | 5 + .../assets/skill-creator-small.svg | 3 + .../skills/.system/skill-creator/license.txt | 202 +++++++++ .../skill-creator/references/openai_yaml.md | 49 +++ .../scripts/generate_openai_yaml.py | 226 ++++++++++ .../skill-creator/scripts/init_skill.py | 397 ++++++++++++++++++ .../skill-creator/scripts/quick_validate.py | 101 +++++ .../.aios-skill-sync.json | 9 + .kiro/skills/aios-jimeng-image-ops/SKILL.md | 52 +++ .../references/run-report-2026-03-02.md | 23 + .../references/selectors-and-errors.md | 48 +++ .../.aios-skill-sync.json | 9 + .../skills/aios-long-running-harness/SKILL.md | 54 +++ .../references/anthropic-mapping.md | 18 + .../references/harness-checklist.md | 24 ++ .../aios-project-system/.aios-skill-sync.json | 9 + .kiro/skills/aios-project-system/SKILL.md | 47 +++ .../references/file-index.md | 26 ++ .../references/system-map.md | 27 ++ .../.aios-skill-sync.json | 9 + .kiro/skills/aios-workflow-router/SKILL.md | 129 ++++++ .../awesome-design-md/.aios-skill-sync.json | 9 + .kiro/skills/awesome-design-md/SKILL.md | 115 +++++ .kiro/skills/awesome-design-md/UPSTREAM.md | 10 + .../cap-commit-push/.aios-skill-sync.json | 9 + .kiro/skills/cap-commit-push/SKILL.md | 35 ++ .../contextdb-autopilot/.aios-skill-sync.json | 9 + .kiro/skills/contextdb-autopilot/SKILL.md | 104 +++++ .kiro/skills/debug-hub/.aios-skill-sync.json | 9 + .kiro/skills/debug-hub/SKILL.md | 62 +++ .../skills/find-skills/.aios-skill-sync.json | 9 + .kiro/skills/find-skills/SKILL.md | 133 ++++++ .../frontend-design/.aios-skill-sync.json | 9 + .kiro/skills/frontend-design/SKILL.md | 105 +++++ .kiro/skills/frontend-design/UPSTREAM.md | 14 + .../harness-init-runner/.aios-skill-sync.json | 9 + .kiro/skills/harness-init-runner/SKILL.md | 56 +++ .../assets/template/gitignore-snippet.txt | 2 + .../assets/template/harness.config.json | 39 ++ .../assets/template/harness/config.schema.mjs | 33 ++ .../assets/template/harness/doctor.mjs | 63 +++ .../template/harness/lib/checkpoint.mjs | 58 +++ .../template/harness/lib/human-gate.mjs | 44 ++ .../assets/template/harness/lib/io.mjs | 12 + .../assets/template/harness/lib/paths.mjs | 39 ++ .../template/harness/providers/claude.mjs | 10 + .../template/harness/providers/codex.mjs | 10 + .../template/harness/providers/gemini.mjs | 10 + .../template/harness/providers/index.mjs | 14 + .../template/harness/providers/opencode.mjs | 10 + .../assets/template/harness/run.mjs | 215 ++++++++++ .../skills/model-router/.aios-skill-sync.json | 9 + .kiro/skills/model-router/SKILL.md | 95 +++++ .../skills/search-first/.aios-skill-sync.json | 9 + .kiro/skills/search-first/SKILL.md | 47 +++ .../security-scan/.aios-skill-sync.json | 9 + .kiro/skills/security-scan/SKILL.md | 48 +++ .../seed2-manga-drama/.aios-skill-sync.json | 9 + .kiro/skills/seed2-manga-drama/SKILL.md | 70 +++ .../references/output-schema.md | 26 ++ .../references/style-presets.md | 30 ++ .../.aios-skill-sync.json | 9 + .../skills/seo-geo-page-optimization/SKILL.md | 52 +++ .../skill-constraints/.aios-skill-sync.json | 9 + .kiro/skills/skill-constraints/SKILL.md | 119 ++++++ .../verification-loop/.aios-skill-sync.json | 9 + .kiro/skills/verification-loop/SKILL.md | 35 ++ .../.aios-skill-sync.json | 9 + .kiro/skills/versioning-by-impact/SKILL.md | 36 ++ .../xhs-ops-methods/.aios-skill-sync.json | 9 + .kiro/skills/xhs-ops-methods/SKILL.md | 89 ++++ .../references/ops-framework.md | 49 +++ .../references/prompt-templates.md | 46 ++ .kiro/steering/AIOS.md | 52 +++ .../native-base/kiro/project/mcp.json | 18 + .../native-base/kiro/project/steering.md | 5 + .../plans/2026-05-12-kiro-cli-support.md | 146 +++++++ 85 files changed, 4272 insertions(+) create mode 100644 .kiro/.aios-native-sync.json create mode 100644 .kiro/agents/rex-implementer.json create mode 100644 .kiro/agents/rex-planner.json create mode 100644 .kiro/agents/rex-reviewer.json create mode 100644 .kiro/agents/rex-security-reviewer.json create mode 100644 .kiro/settings/mcp.json create mode 100644 .kiro/skills/.system/skill-creator/.aios-skill-sync.json create mode 100644 .kiro/skills/.system/skill-creator/SKILL.md create mode 100644 .kiro/skills/.system/skill-creator/agents/openai.yaml create mode 100644 .kiro/skills/.system/skill-creator/assets/skill-creator-small.svg create mode 100644 .kiro/skills/.system/skill-creator/license.txt create mode 100644 .kiro/skills/.system/skill-creator/references/openai_yaml.md create mode 100644 .kiro/skills/.system/skill-creator/scripts/generate_openai_yaml.py create mode 100644 .kiro/skills/.system/skill-creator/scripts/init_skill.py create mode 100644 .kiro/skills/.system/skill-creator/scripts/quick_validate.py create mode 100644 .kiro/skills/aios-jimeng-image-ops/.aios-skill-sync.json create mode 100644 .kiro/skills/aios-jimeng-image-ops/SKILL.md create mode 100644 .kiro/skills/aios-jimeng-image-ops/references/run-report-2026-03-02.md create mode 100644 .kiro/skills/aios-jimeng-image-ops/references/selectors-and-errors.md create mode 100644 .kiro/skills/aios-long-running-harness/.aios-skill-sync.json create mode 100644 .kiro/skills/aios-long-running-harness/SKILL.md create mode 100644 .kiro/skills/aios-long-running-harness/references/anthropic-mapping.md create mode 100644 .kiro/skills/aios-long-running-harness/references/harness-checklist.md create mode 100644 .kiro/skills/aios-project-system/.aios-skill-sync.json create mode 100644 .kiro/skills/aios-project-system/SKILL.md create mode 100644 .kiro/skills/aios-project-system/references/file-index.md create mode 100644 .kiro/skills/aios-project-system/references/system-map.md create mode 100644 .kiro/skills/aios-workflow-router/.aios-skill-sync.json create mode 100644 .kiro/skills/aios-workflow-router/SKILL.md create mode 100644 .kiro/skills/awesome-design-md/.aios-skill-sync.json create mode 100644 .kiro/skills/awesome-design-md/SKILL.md create mode 100644 .kiro/skills/awesome-design-md/UPSTREAM.md create mode 100644 .kiro/skills/cap-commit-push/.aios-skill-sync.json create mode 100644 .kiro/skills/cap-commit-push/SKILL.md create mode 100644 .kiro/skills/contextdb-autopilot/.aios-skill-sync.json create mode 100644 .kiro/skills/contextdb-autopilot/SKILL.md create mode 100644 .kiro/skills/debug-hub/.aios-skill-sync.json create mode 100644 .kiro/skills/debug-hub/SKILL.md create mode 100644 .kiro/skills/find-skills/.aios-skill-sync.json create mode 100644 .kiro/skills/find-skills/SKILL.md create mode 100644 .kiro/skills/frontend-design/.aios-skill-sync.json create mode 100644 .kiro/skills/frontend-design/SKILL.md create mode 100644 .kiro/skills/frontend-design/UPSTREAM.md create mode 100644 .kiro/skills/harness-init-runner/.aios-skill-sync.json create mode 100644 .kiro/skills/harness-init-runner/SKILL.md create mode 100644 .kiro/skills/harness-init-runner/assets/template/gitignore-snippet.txt create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness.config.json create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/config.schema.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/doctor.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/lib/checkpoint.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/lib/human-gate.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/lib/io.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/lib/paths.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/providers/claude.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/providers/codex.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/providers/gemini.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/providers/index.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/providers/opencode.mjs create mode 100644 .kiro/skills/harness-init-runner/assets/template/harness/run.mjs create mode 100644 .kiro/skills/model-router/.aios-skill-sync.json create mode 100644 .kiro/skills/model-router/SKILL.md create mode 100644 .kiro/skills/search-first/.aios-skill-sync.json create mode 100644 .kiro/skills/search-first/SKILL.md create mode 100644 .kiro/skills/security-scan/.aios-skill-sync.json create mode 100644 .kiro/skills/security-scan/SKILL.md create mode 100644 .kiro/skills/seed2-manga-drama/.aios-skill-sync.json create mode 100644 .kiro/skills/seed2-manga-drama/SKILL.md create mode 100644 .kiro/skills/seed2-manga-drama/references/output-schema.md create mode 100644 .kiro/skills/seed2-manga-drama/references/style-presets.md create mode 100644 .kiro/skills/seo-geo-page-optimization/.aios-skill-sync.json create mode 100644 .kiro/skills/seo-geo-page-optimization/SKILL.md create mode 100644 .kiro/skills/skill-constraints/.aios-skill-sync.json create mode 100644 .kiro/skills/skill-constraints/SKILL.md create mode 100644 .kiro/skills/verification-loop/.aios-skill-sync.json create mode 100644 .kiro/skills/verification-loop/SKILL.md create mode 100644 .kiro/skills/versioning-by-impact/.aios-skill-sync.json create mode 100644 .kiro/skills/versioning-by-impact/SKILL.md create mode 100644 .kiro/skills/xhs-ops-methods/.aios-skill-sync.json create mode 100644 .kiro/skills/xhs-ops-methods/SKILL.md create mode 100644 .kiro/skills/xhs-ops-methods/references/ops-framework.md create mode 100644 .kiro/skills/xhs-ops-methods/references/prompt-templates.md create mode 100644 .kiro/steering/AIOS.md create mode 100644 client-sources/native-base/kiro/project/mcp.json create mode 100644 client-sources/native-base/kiro/project/steering.md create mode 100644 docs/superpowers/plans/2026-05-12-kiro-cli-support.md diff --git a/.kiro/.aios-native-sync.json b/.kiro/.aios-native-sync.json new file mode 100644 index 00000000..96d9f7d4 --- /dev/null +++ b/.kiro/.aios-native-sync.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "native-sync", + "client": "kiro", + "tier": "deep", + "managedTargets": [ + ".kiro/steering/AIOS.md", + ".kiro/settings/mcp.json", + ".kiro/agents", + ".kiro/skills" + ], + "generatedAt": "2026-05-13T08:38:44.802Z", + "aiosVersion": "" +} diff --git a/.kiro/agents/rex-implementer.json b/.kiro/agents/rex-implementer.json new file mode 100644 index 00000000..72aa6eb3 --- /dev/null +++ b/.kiro/agents/rex-implementer.json @@ -0,0 +1,25 @@ +{ + "name": "rex-implementer", + "description": "Implementer role card for AIOS orchestrations (code changes + verification).", + "prompt": "<!-- AIOS-GENERATED: orchestrator-agents v1 -->\n\nRole: implementer\n\nYou are the Implementer. Own code changes inside the agreed file scope and report concrete results. Prefer minimal diffs and include verification evidence.\n\nOutput Contract\nOutput a single JSON object (no surrounding text) that conforms to `memory/specs/agent-handoff.schema.json`.\n\nRequired fields:\n- schemaVersion\n- status\n- fromRole\n- toRole\n- taskTitle\n- contextSummary\n- findings\n- filesTouched\n- openQuestions\n- recommendations\n\nSet `fromRole=implementer` and `toRole=next-phase`.\n\n<!-- END AIOS-GENERATED -->", + "resources": [ + "file://.kiro/steering/**/*.md", + "skill://.kiro/skills/**/SKILL.md", + "skill://~/.kiro/skills/**/SKILL.md" + ], + "includeMcpJson": true, + "tools": [ + "read", + "grep", + "glob", + "shell", + "write" + ], + "allowedTools": [ + "read", + "grep", + "glob", + "shell", + "write" + ] +} diff --git a/.kiro/agents/rex-planner.json b/.kiro/agents/rex-planner.json new file mode 100644 index 00000000..96211d76 --- /dev/null +++ b/.kiro/agents/rex-planner.json @@ -0,0 +1,21 @@ +{ + "name": "rex-planner", + "description": "Planner role card for AIOS orchestrations (scope, risks, ordering).", + "prompt": "<!-- AIOS-GENERATED: orchestrator-agents v1 -->\n\nRole: planner\n\nYou are the Planner. Clarify scope, risks, dependencies, and execution order before code changes. Produce a concrete plan that an implementer can follow.\n\nOutput Contract\nOutput a single JSON object (no surrounding text) that conforms to `memory/specs/agent-handoff.schema.json`.\n\nRequired fields:\n- schemaVersion\n- status\n- fromRole\n- toRole\n- taskTitle\n- contextSummary\n- findings\n- filesTouched\n- openQuestions\n- recommendations\n\nSet `fromRole=planner` and `toRole=next-phase`.\n\n<!-- END AIOS-GENERATED -->", + "resources": [ + "file://.kiro/steering/**/*.md", + "skill://.kiro/skills/**/SKILL.md", + "skill://~/.kiro/skills/**/SKILL.md" + ], + "includeMcpJson": true, + "tools": [ + "read", + "grep", + "glob" + ], + "allowedTools": [ + "read", + "grep", + "glob" + ] +} diff --git a/.kiro/agents/rex-reviewer.json b/.kiro/agents/rex-reviewer.json new file mode 100644 index 00000000..d6c852c2 --- /dev/null +++ b/.kiro/agents/rex-reviewer.json @@ -0,0 +1,21 @@ +{ + "name": "rex-reviewer", + "description": "Reviewer role card for AIOS orchestrations (correctness, regressions, tests).", + "prompt": "<!-- AIOS-GENERATED: orchestrator-agents v1 -->\n\nRole: reviewer\n\nYou are the Reviewer. Review correctness, regressions, maintainability, and test coverage. Do not modify code; report findings and recommendations.\n\nOutput Contract\nOutput a single JSON object (no surrounding text) that conforms to `memory/specs/agent-handoff.schema.json`.\n\nRequired fields:\n- schemaVersion\n- status\n- fromRole\n- toRole\n- taskTitle\n- contextSummary\n- findings\n- filesTouched\n- openQuestions\n- recommendations\n\nSet `fromRole=reviewer` and `toRole=merge-gate`.\n\n<!-- END AIOS-GENERATED -->", + "resources": [ + "file://.kiro/steering/**/*.md", + "skill://.kiro/skills/**/SKILL.md", + "skill://~/.kiro/skills/**/SKILL.md" + ], + "includeMcpJson": true, + "tools": [ + "read", + "grep", + "glob" + ], + "allowedTools": [ + "read", + "grep", + "glob" + ] +} diff --git a/.kiro/agents/rex-security-reviewer.json b/.kiro/agents/rex-security-reviewer.json new file mode 100644 index 00000000..f3ecde78 --- /dev/null +++ b/.kiro/agents/rex-security-reviewer.json @@ -0,0 +1,21 @@ +{ + "name": "rex-security-reviewer", + "description": "Security reviewer role card for AIOS orchestrations (auth, secrets, unsafe automation).", + "prompt": "<!-- AIOS-GENERATED: orchestrator-agents v1 -->\n\nRole: security-reviewer\n\nYou are the Security Reviewer. Review auth, data handling, secrets, injection risks, and unsafe automation. Do not modify code; report security findings and mitigations.\n\nOutput Contract\nOutput a single JSON object (no surrounding text) that conforms to `memory/specs/agent-handoff.schema.json`.\n\nRequired fields:\n- schemaVersion\n- status\n- fromRole\n- toRole\n- taskTitle\n- contextSummary\n- findings\n- filesTouched\n- openQuestions\n- recommendations\n\nSet `fromRole=security-reviewer` and `toRole=merge-gate`.\n\n<!-- END AIOS-GENERATED -->", + "resources": [ + "file://.kiro/steering/**/*.md", + "skill://.kiro/skills/**/SKILL.md", + "skill://~/.kiro/skills/**/SKILL.md" + ], + "includeMcpJson": true, + "tools": [ + "read", + "grep", + "glob" + ], + "allowedTools": [ + "read", + "grep", + "glob" + ] +} diff --git a/.kiro/settings/mcp.json b/.kiro/settings/mcp.json new file mode 100644 index 00000000..e489b5be --- /dev/null +++ b/.kiro/settings/mcp.json @@ -0,0 +1,25 @@ +{ + "mcpServers": { + "puppeteer-stealth": { + "type": "stdio", + "command": "bash", + "args": [ + "/Users/billow/Documents/opensource/rex-cli/scripts/run-browser-use-mcp.sh" + ], + "env": { + "BROWSER_USE_CDP_URL": "http://127.0.0.1:9222" + } + }, + "aios-auth-tools": { + "type": "stdio", + "command": "python3", + "args": [ + "-u", + "/Users/billow/Documents/opensource/rex-cli/scripts/auth-tools-server.py" + ], + "env": { + "BROWSER_USE_CDP_URL": "http://127.0.0.1:9222" + } + } + } +} diff --git a/.kiro/skills/.system/skill-creator/.aios-skill-sync.json b/.kiro/skills/.system/skill-creator/.aios-skill-sync.json new file mode 100644 index 00000000..dc17f201 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": ".system/skill-creator", + "targetSurface": "kiro", + "targetRelativePath": ".system/skill-creator", + "source": "skill-sources/.system/skill-creator" +} \ No newline at end of file diff --git a/.kiro/skills/.system/skill-creator/SKILL.md b/.kiro/skills/.system/skill-creator/SKILL.md new file mode 100644 index 00000000..72bc0b97 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/SKILL.md @@ -0,0 +1,368 @@ +--- +name: skill-creator +description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations. +metadata: + short-description: Create or update a skill +--- + +# Skill Creator + +This skill provides guidance for creating effective skills. + +## About Skills + +Skills are modular, self-contained folders that extend Codex's capabilities by providing +specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific +domains or tasks—they transform Codex from a general-purpose agent into a specialized agent +equipped with procedural knowledge that no model can fully possess. + +### What Skills Provide + +1. Specialized workflows - Multi-step procedures for specific domains +2. Tool integrations - Instructions for working with specific file formats or APIs +3. Domain expertise - Company-specific knowledge, schemas, business logic +4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks + +## Core Principles + +### Concise is Key + +The context window is a public good. Skills share the context window with everything else Codex needs: system prompt, conversation history, other Skills' metadata, and the actual user request. + +**Default assumption: Codex is already very smart.** Only add context Codex doesn't already have. Challenge each piece of information: "Does Codex really need this explanation?" and "Does this paragraph justify its token cost?" + +Prefer concise examples over verbose explanations. + +### Set Appropriate Degrees of Freedom + +Match the level of specificity to the task's fragility and variability: + +**High freedom (text-based instructions)**: Use when multiple approaches are valid, decisions depend on context, or heuristics guide the approach. + +**Medium freedom (pseudocode or scripts with parameters)**: Use when a preferred pattern exists, some variation is acceptable, or configuration affects behavior. + +**Low freedom (specific scripts, few parameters)**: Use when operations are fragile and error-prone, consistency is critical, or a specific sequence must be followed. + +Think of Codex as exploring a path: a narrow bridge with cliffs needs specific guardrails (low freedom), while an open field allows many routes (high freedom). + +### Anatomy of a Skill + +Every skill consists of a required SKILL.md file and optional bundled resources: + +``` +skill-name/ +├── SKILL.md (required) +│ ├── YAML frontmatter metadata (required) +│ │ ├── name: (required) +│ │ └── description: (required) +│ └── Markdown instructions (required) +├── agents/ (recommended) +│ └── openai.yaml - UI metadata for skill lists and chips +└── Bundled Resources (optional) + ├── scripts/ - Executable code (Python/Bash/etc.) + ├── references/ - Documentation intended to be loaded into context as needed + └── assets/ - Files used in output (templates, icons, fonts, etc.) +``` + +#### SKILL.md (required) + +Every SKILL.md consists of: + +- **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that Codex reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used. +- **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all). + +#### Agents metadata (recommended) + +- UI-facing metadata for skill lists and chips +- Read references/openai_yaml.md before generating values and follow its descriptions and constraints +- Create: human-facing `display_name`, `short_description`, and `default_prompt` by reading the skill +- Generate deterministically by passing the values as `--interface key=value` to `scripts/generate_openai_yaml.py` or `scripts/init_skill.py` +- On updates: validate `agents/openai.yaml` still matches SKILL.md; regenerate if stale +- Only include other optional interface fields (icons, brand color) if explicitly provided +- See references/openai_yaml.md for field definitions and examples + +#### Bundled Resources (optional) + +##### Scripts (`scripts/`) + +Executable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten. + +- **When to include**: When the same code is being rewritten repeatedly or deterministic reliability is needed +- **Example**: `scripts/rotate_pdf.py` for PDF rotation tasks +- **Benefits**: Token efficient, deterministic, may be executed without loading into context +- **Note**: Scripts may still need to be read by Codex for patching or environment-specific adjustments + +##### References (`references/`) + +Documentation and reference material intended to be loaded as needed into context to inform Codex's process and thinking. + +- **When to include**: For documentation that Codex should reference while working +- **Examples**: `references/finance.md` for financial schemas, `references/mnda.md` for company NDA template, `references/policies.md` for company policies, `references/api_docs.md` for API specifications +- **Use cases**: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides +- **Benefits**: Keeps SKILL.md lean, loaded only when Codex determines it's needed +- **Best practice**: If files are large (>10k words), include grep search patterns in SKILL.md +- **Avoid duplication**: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files. + +##### Assets (`assets/`) + +Files not intended to be loaded into context, but rather used within the output Codex produces. + +- **When to include**: When the skill needs files that will be used in the final output +- **Examples**: `assets/logo.png` for brand assets, `assets/slides.pptx` for PowerPoint templates, `assets/frontend-template/` for HTML/React boilerplate, `assets/font.ttf` for typography +- **Use cases**: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified +- **Benefits**: Separates output resources from documentation, enables Codex to use files without loading them into context + +#### What to Not Include in a Skill + +A skill should only contain essential files that directly support its functionality. Do NOT create extraneous documentation or auxiliary files, including: + +- README.md +- INSTALLATION_GUIDE.md +- QUICK_REFERENCE.md +- CHANGELOG.md +- etc. + +The skill should only contain the information needed for an AI agent to do the job at hand. It should not contain auxiliary context about the process that went into creating it, setup and testing procedures, user-facing documentation, etc. Creating additional documentation files just adds clutter and confusion. + +### Progressive Disclosure Design Principle + +Skills use a three-level loading system to manage context efficiently: + +1. **Metadata (name + description)** - Always in context (~100 words) +2. **SKILL.md body** - When skill triggers (<5k words) +3. **Bundled resources** - As needed by Codex (Unlimited because scripts can be executed without reading into context window) + +#### Progressive Disclosure Patterns + +Keep SKILL.md body to the essentials and under 500 lines to minimize context bloat. Split content into separate files when approaching this limit. When splitting out content into other files, it is very important to reference them from SKILL.md and describe clearly when to read them, to ensure the reader of the skill knows they exist and when to use them. + +**Key principle:** When a skill supports multiple variations, frameworks, or options, keep only the core workflow and selection guidance in SKILL.md. Move variant-specific details (patterns, examples, configuration) into separate reference files. + +**Pattern 1: High-level guide with references** + +```markdown +# PDF Processing + +## Quick start + +Extract text with pdfplumber: +[code example] + +## Advanced features + +- **Form filling**: See [FORMS.md](FORMS.md) for complete guide +- **API reference**: See [REFERENCE.md](REFERENCE.md) for all methods +- **Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns +``` + +Codex loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when needed. + +**Pattern 2: Domain-specific organization** + +For Skills with multiple domains, organize content by domain to avoid loading irrelevant context: + +``` +bigquery-skill/ +├── SKILL.md (overview and navigation) +└── reference/ + ├── finance.md (revenue, billing metrics) + ├── sales.md (opportunities, pipeline) + ├── product.md (API usage, features) + └── marketing.md (campaigns, attribution) +``` + +When a user asks about sales metrics, Codex only reads sales.md. + +Similarly, for skills supporting multiple frameworks or variants, organize by variant: + +``` +cloud-deploy/ +├── SKILL.md (workflow + provider selection) +└── references/ + ├── aws.md (AWS deployment patterns) + ├── gcp.md (GCP deployment patterns) + └── azure.md (Azure deployment patterns) +``` + +When the user chooses AWS, Codex only reads aws.md. + +**Pattern 3: Conditional details** + +Show basic content, link to advanced content: + +```markdown +# DOCX Processing + +## Creating documents + +Use docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md). + +## Editing documents + +For simple edits, modify the XML directly. + +**For tracked changes**: See [REDLINING.md](REDLINING.md) +**For OOXML details**: See [OOXML.md](OOXML.md) +``` + +Codex reads REDLINING.md or OOXML.md only when the user needs those features. + +**Important guidelines:** + +- **Avoid deeply nested references** - Keep references one level deep from SKILL.md. All reference files should link directly from SKILL.md. +- **Structure longer reference files** - For files longer than 100 lines, include a table of contents at the top so Codex can see the full scope when previewing. + +## Skill Creation Process + +Skill creation involves these steps: + +1. Understand the skill with concrete examples +2. Plan reusable skill contents (scripts, references, assets) +3. Initialize the skill (run init_skill.py) +4. Edit the skill (implement resources and write SKILL.md) +5. Validate the skill (run quick_validate.py) +6. Iterate based on real usage + +Follow these steps in order, skipping only if there is a clear reason why they are not applicable. + +### Skill Naming + +- Use lowercase letters, digits, and hyphens only; normalize user-provided titles to hyphen-case (e.g., "Plan Mode" -> `plan-mode`). +- When generating names, generate a name under 64 characters (letters, digits, hyphens). +- Prefer short, verb-led phrases that describe the action. +- Namespace by tool when it improves clarity or triggering (e.g., `gh-address-comments`, `linear-address-issue`). +- Name the skill folder exactly after the skill name. + +### Step 1: Understanding the Skill with Concrete Examples + +Skip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill. + +To create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback. + +For example, when building an image-editor skill, relevant questions include: + +- "What functionality should the image-editor skill support? Editing, rotating, anything else?" +- "Can you give some examples of how this skill would be used?" +- "I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?" +- "What would a user say that should trigger this skill?" + +To avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness. + +Conclude this step when there is a clear sense of the functionality the skill should support. + +### Step 2: Planning the Reusable Skill Contents + +To turn concrete examples into an effective skill, analyze each example by: + +1. Considering how to execute on the example from scratch +2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly + +Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows: + +1. Rotating a PDF requires re-writing the same code each time +2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill + +Example: When designing a `frontend-webapp-builder` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows: + +1. Writing a frontend webapp requires the same boilerplate HTML/React each time +2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill + +Example: When building a `big-query` skill to handle queries like "How many users have logged in today?" the analysis shows: + +1. Querying BigQuery requires re-discovering the table schemas and relationships each time +2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill + +To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets. + +### Step 3: Initializing the Skill + +At this point, it is time to actually create the skill. + +Skip this step only if the skill being developed already exists. In this case, continue to the next step. + +When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable. + +Usage: + +```bash +scripts/init_skill.py <skill-name> --path <output-directory> [--resources scripts,references,assets] [--examples] +``` + +Examples: + +```bash +scripts/init_skill.py my-skill --path skills/public +scripts/init_skill.py my-skill --path skills/public --resources scripts,references +scripts/init_skill.py my-skill --path skills/public --resources scripts --examples +``` + +The script: + +- Creates the skill directory at the specified path +- Generates a SKILL.md template with proper frontmatter and TODO placeholders +- Creates `agents/openai.yaml` using agent-generated `display_name`, `short_description`, and `default_prompt` passed via `--interface key=value` +- Optionally creates resource directories based on `--resources` +- Optionally adds example files when `--examples` is set + +After initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files. + +Generate `display_name`, `short_description`, and `default_prompt` by reading the skill, then pass them as `--interface key=value` to `init_skill.py` or regenerate with: + +```bash +scripts/generate_openai_yaml.py <path/to/skill-folder> --interface key=value +``` + +Only include other optional interface fields when the user explicitly provides them. For full field descriptions and examples, see references/openai_yaml.md. + +### Step 4: Edit the Skill + +When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Codex to use. Include information that would be beneficial and non-obvious to Codex. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Codex instance execute these tasks more effectively. + +#### Start with Reusable Skill Contents + +To begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`. + +Added scripts must be tested by actually running them to ensure there are no bugs and that the output matches what is expected. If there are many similar scripts, only a representative sample needs to be tested to ensure confidence that they all work while balancing time to completion. + +If you used `--examples`, delete any placeholder files that are not needed for the skill. Only create resource directories that are actually required. + +#### Update SKILL.md + +**Writing Guidelines:** Always use imperative/infinitive form. + +##### Frontmatter + +Write the YAML frontmatter with `name` and `description`: + +- `name`: The skill name +- `description`: This is the primary triggering mechanism for your skill, and helps Codex understand when to use the skill. + - Include both what the Skill does and specific triggers/contexts for when to use it. + - Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to Codex. + - Example description for a `docx` skill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Codex needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks" + +Do not include any other fields in YAML frontmatter. + +##### Body + +Write instructions for using the skill and its bundled resources. + +### Step 5: Validate the Skill + +Once development of the skill is complete, validate the skill folder to catch basic issues early: + +```bash +scripts/quick_validate.py <path/to/skill-folder> +``` + +The validation script checks YAML frontmatter format, required fields, and naming rules. If validation fails, fix the reported issues and run the command again. + +### Step 6: Iterate + +After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed. + +**Iteration workflow:** + +1. Use the skill on real tasks +2. Notice struggles or inefficiencies +3. Identify how SKILL.md or bundled resources should be updated +4. Implement changes and test again diff --git a/.kiro/skills/.system/skill-creator/agents/openai.yaml b/.kiro/skills/.system/skill-creator/agents/openai.yaml new file mode 100644 index 00000000..3095c600 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Skill Creator" + short_description: "Create or update a skill" + icon_small: "./assets/skill-creator-small.svg" + icon_large: "./assets/skill-creator.png" diff --git a/.kiro/skills/.system/skill-creator/assets/skill-creator-small.svg b/.kiro/skills/.system/skill-creator/assets/skill-creator-small.svg new file mode 100644 index 00000000..c6e4f67c --- /dev/null +++ b/.kiro/skills/.system/skill-creator/assets/skill-creator-small.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 20 20"> + <path fill="#0D0D0D" d="M12.03 4.113a3.612 3.612 0 0 1 5.108 5.108l-6.292 6.29c-.324.324-.56.561-.791.752l-.235.176c-.205.14-.422.261-.65.36l-.229.093a4.136 4.136 0 0 1-.586.16l-.764.134-2.394.4c-.142.024-.294.05-.423.06-.098.007-.232.01-.378-.026l-.149-.05a1.081 1.081 0 0 1-.521-.474l-.046-.093a1.104 1.104 0 0 1-.075-.527c.01-.129.035-.28.06-.422l.398-2.394c.1-.602.162-.987.295-1.35l.093-.23c.1-.228.22-.445.36-.65l.176-.235c.19-.232.428-.467.751-.79l6.292-6.292Zm-5.35 7.232c-.35.35-.534.535-.66.688l-.11.147a2.67 2.67 0 0 0-.24.433l-.062.154c-.08.22-.124.462-.232 1.112l-.398 2.394-.001.001h.003l2.393-.399.717-.126a2.63 2.63 0 0 0 .394-.105l.154-.063a2.65 2.65 0 0 0 .433-.24l.147-.11c.153-.126.339-.31.688-.66l4.988-4.988-3.227-3.226-4.987 4.988Zm9.517-6.291a2.281 2.281 0 0 0-3.225 0l-.364.362 3.226 3.227.363-.364c.89-.89.89-2.334 0-3.225ZM4.583 1.783a.3.3 0 0 1 .294.241c.117.585.347 1.092.707 1.48.357.385.859.668 1.549.783a.3.3 0 0 1 0 .592c-.69.115-1.192.398-1.549.783-.315.34-.53.77-.657 1.265l-.05.215a.3.3 0 0 1-.588 0c-.117-.585-.347-1.092-.707-1.48-.357-.384-.859-.668-1.549-.783a.3.3 0 0 1 0-.592c.69-.115 1.192-.398 1.549-.783.36-.388.59-.895.707-1.48l.015-.05a.3.3 0 0 1 .279-.19Z"/> +</svg> diff --git a/.kiro/skills/.system/skill-creator/license.txt b/.kiro/skills/.system/skill-creator/license.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.kiro/skills/.system/skill-creator/references/openai_yaml.md b/.kiro/skills/.system/skill-creator/references/openai_yaml.md new file mode 100644 index 00000000..90f9e8e8 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/references/openai_yaml.md @@ -0,0 +1,49 @@ +# openai.yaml fields (full example + descriptions) + +`agents/openai.yaml` is an extended, product-specific config intended for the machine/harness to read, not the agent. Other product-specific config can also live in the `agents/` folder. + +## Full example + +```yaml +interface: + display_name: "Optional user-facing name" + short_description: "Optional user-facing description" + icon_small: "./assets/small-400px.png" + icon_large: "./assets/large-logo.svg" + brand_color: "#3B82F6" + default_prompt: "Optional surrounding prompt to use the skill with" + +dependencies: + tools: + - type: "mcp" + value: "github" + description: "GitHub MCP server" + transport: "streamable_http" + url: "https://api.githubcopilot.com/mcp/" + +policy: + allow_implicit_invocation: true +``` + +## Field descriptions and constraints + +Top-level constraints: + +- Quote all string values. +- Keep keys unquoted. +- For `interface.default_prompt`: generate a helpful, short (typically 1 sentence) example starting prompt based on the skill. It must explicitly mention the skill as `$skill-name` (e.g., "Use $skill-name-here to draft a concise weekly status update."). + +- `interface.display_name`: Human-facing title shown in UI skill lists and chips. +- `interface.short_description`: Human-facing short UI blurb (25–64 chars) for quick scanning. +- `interface.icon_small`: Path to a small icon asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder. +- `interface.icon_large`: Path to a larger logo asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder. +- `interface.brand_color`: Hex color used for UI accents (e.g., badges). +- `interface.default_prompt`: Default prompt snippet inserted when invoking the skill. +- `dependencies.tools[].type`: Dependency category. Only `mcp` is supported for now. +- `dependencies.tools[].value`: Identifier of the tool or dependency. +- `dependencies.tools[].description`: Human-readable explanation of the dependency. +- `dependencies.tools[].transport`: Connection type when `type` is `mcp`. +- `dependencies.tools[].url`: MCP server URL when `type` is `mcp`. +- `policy.allow_implicit_invocation`: When false, the skill is not injected into + the model context by default, but can still be invoked explicitly via `$skill`. + Defaults to true. diff --git a/.kiro/skills/.system/skill-creator/scripts/generate_openai_yaml.py b/.kiro/skills/.system/skill-creator/scripts/generate_openai_yaml.py new file mode 100644 index 00000000..3fd74053 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/scripts/generate_openai_yaml.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +""" +OpenAI YAML Generator - Creates agents/openai.yaml for a skill folder. + +Usage: + generate_openai_yaml.py <skill_dir> [--name <skill_name>] [--interface key=value] +""" + +import argparse +import re +import sys +from pathlib import Path + +ACRONYMS = { + "GH", + "MCP", + "API", + "CI", + "CLI", + "LLM", + "PDF", + "PR", + "UI", + "URL", + "SQL", +} + +BRANDS = { + "openai": "OpenAI", + "openapi": "OpenAPI", + "github": "GitHub", + "pagerduty": "PagerDuty", + "datadog": "DataDog", + "sqlite": "SQLite", + "fastapi": "FastAPI", +} + +SMALL_WORDS = {"and", "or", "to", "up", "with"} + +ALLOWED_INTERFACE_KEYS = { + "display_name", + "short_description", + "icon_small", + "icon_large", + "brand_color", + "default_prompt", +} + + +def yaml_quote(value): + escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + return f'"{escaped}"' + + +def format_display_name(skill_name): + words = [word for word in skill_name.split("-") if word] + formatted = [] + for index, word in enumerate(words): + lower = word.lower() + upper = word.upper() + if upper in ACRONYMS: + formatted.append(upper) + continue + if lower in BRANDS: + formatted.append(BRANDS[lower]) + continue + if index > 0 and lower in SMALL_WORDS: + formatted.append(lower) + continue + formatted.append(word.capitalize()) + return " ".join(formatted) + + +def generate_short_description(display_name): + description = f"Help with {display_name} tasks" + + if len(description) < 25: + description = f"Help with {display_name} tasks and workflows" + if len(description) < 25: + description = f"Help with {display_name} tasks with guidance" + + if len(description) > 64: + description = f"Help with {display_name}" + if len(description) > 64: + description = f"{display_name} helper" + if len(description) > 64: + description = f"{display_name} tools" + if len(description) > 64: + suffix = " helper" + max_name_length = 64 - len(suffix) + trimmed = display_name[:max_name_length].rstrip() + description = f"{trimmed}{suffix}" + if len(description) > 64: + description = description[:64].rstrip() + + if len(description) < 25: + description = f"{description} workflows" + if len(description) > 64: + description = description[:64].rstrip() + + return description + + +def read_frontmatter_name(skill_dir): + skill_md = Path(skill_dir) / "SKILL.md" + if not skill_md.exists(): + print(f"[ERROR] SKILL.md not found in {skill_dir}") + return None + content = skill_md.read_text() + match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) + if not match: + print("[ERROR] Invalid SKILL.md frontmatter format.") + return None + frontmatter_text = match.group(1) + + import yaml + + try: + frontmatter = yaml.safe_load(frontmatter_text) + except yaml.YAMLError as exc: + print(f"[ERROR] Invalid YAML frontmatter: {exc}") + return None + if not isinstance(frontmatter, dict): + print("[ERROR] Frontmatter must be a YAML dictionary.") + return None + name = frontmatter.get("name", "") + if not isinstance(name, str) or not name.strip(): + print("[ERROR] Frontmatter 'name' is missing or invalid.") + return None + return name.strip() + + +def parse_interface_overrides(raw_overrides): + overrides = {} + optional_order = [] + for item in raw_overrides: + if "=" not in item: + print(f"[ERROR] Invalid interface override '{item}'. Use key=value.") + return None, None + key, value = item.split("=", 1) + key = key.strip() + value = value.strip() + if not key: + print(f"[ERROR] Invalid interface override '{item}'. Key is empty.") + return None, None + if key not in ALLOWED_INTERFACE_KEYS: + allowed = ", ".join(sorted(ALLOWED_INTERFACE_KEYS)) + print(f"[ERROR] Unknown interface field '{key}'. Allowed: {allowed}") + return None, None + overrides[key] = value + if key not in ("display_name", "short_description") and key not in optional_order: + optional_order.append(key) + return overrides, optional_order + + +def write_openai_yaml(skill_dir, skill_name, raw_overrides): + overrides, optional_order = parse_interface_overrides(raw_overrides) + if overrides is None: + return None + + display_name = overrides.get("display_name") or format_display_name(skill_name) + short_description = overrides.get("short_description") or generate_short_description(display_name) + + if not (25 <= len(short_description) <= 64): + print( + "[ERROR] short_description must be 25-64 characters " + f"(got {len(short_description)})." + ) + return None + + interface_lines = [ + "interface:", + f" display_name: {yaml_quote(display_name)}", + f" short_description: {yaml_quote(short_description)}", + ] + + for key in optional_order: + value = overrides.get(key) + if value is not None: + interface_lines.append(f" {key}: {yaml_quote(value)}") + + agents_dir = Path(skill_dir) / "agents" + agents_dir.mkdir(parents=True, exist_ok=True) + output_path = agents_dir / "openai.yaml" + output_path.write_text("\n".join(interface_lines) + "\n") + print(f"[OK] Created agents/openai.yaml") + return output_path + + +def main(): + parser = argparse.ArgumentParser( + description="Create agents/openai.yaml for a skill directory.", + ) + parser.add_argument("skill_dir", help="Path to the skill directory") + parser.add_argument( + "--name", + help="Skill name override (defaults to SKILL.md frontmatter)", + ) + parser.add_argument( + "--interface", + action="append", + default=[], + help="Interface override in key=value format (repeatable)", + ) + args = parser.parse_args() + + skill_dir = Path(args.skill_dir).resolve() + if not skill_dir.exists(): + print(f"[ERROR] Skill directory not found: {skill_dir}") + sys.exit(1) + if not skill_dir.is_dir(): + print(f"[ERROR] Path is not a directory: {skill_dir}") + sys.exit(1) + + skill_name = args.name or read_frontmatter_name(skill_dir) + if not skill_name: + sys.exit(1) + + result = write_openai_yaml(skill_dir, skill_name, args.interface) + if result: + sys.exit(0) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.kiro/skills/.system/skill-creator/scripts/init_skill.py b/.kiro/skills/.system/skill-creator/scripts/init_skill.py new file mode 100644 index 00000000..f90703ec --- /dev/null +++ b/.kiro/skills/.system/skill-creator/scripts/init_skill.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +""" +Skill Initializer - Creates a new skill from template + +Usage: + init_skill.py <skill-name> --path <path> [--resources scripts,references,assets] [--examples] [--interface key=value] + +Examples: + init_skill.py my-new-skill --path skills/public + init_skill.py my-new-skill --path skills/public --resources scripts,references + init_skill.py my-api-helper --path skills/private --resources scripts --examples + init_skill.py custom-skill --path /custom/location + init_skill.py my-skill --path skills/public --interface short_description="Short UI label" +""" + +import argparse +import re +import sys +from pathlib import Path + +from generate_openai_yaml import write_openai_yaml + +MAX_SKILL_NAME_LENGTH = 64 +ALLOWED_RESOURCES = {"scripts", "references", "assets"} + +SKILL_TEMPLATE = """--- +name: {skill_name} +description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.] +--- + +# {skill_title} + +## Overview + +[TODO: 1-2 sentences explaining what this skill enables] + +## Structuring This Skill + +[TODO: Choose the structure that best fits this skill's purpose. Common patterns: + +**1. Workflow-Based** (best for sequential processes) +- Works well when there are clear step-by-step procedures +- Example: DOCX skill with "Workflow Decision Tree" -> "Reading" -> "Creating" -> "Editing" +- Structure: ## Overview -> ## Workflow Decision Tree -> ## Step 1 -> ## Step 2... + +**2. Task-Based** (best for tool collections) +- Works well when the skill offers different operations/capabilities +- Example: PDF skill with "Quick Start" -> "Merge PDFs" -> "Split PDFs" -> "Extract Text" +- Structure: ## Overview -> ## Quick Start -> ## Task Category 1 -> ## Task Category 2... + +**3. Reference/Guidelines** (best for standards or specifications) +- Works well for brand guidelines, coding standards, or requirements +- Example: Brand styling with "Brand Guidelines" -> "Colors" -> "Typography" -> "Features" +- Structure: ## Overview -> ## Guidelines -> ## Specifications -> ## Usage... + +**4. Capabilities-Based** (best for integrated systems) +- Works well when the skill provides multiple interrelated features +- Example: Product Management with "Core Capabilities" -> numbered capability list +- Structure: ## Overview -> ## Core Capabilities -> ### 1. Feature -> ### 2. Feature... + +Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations). + +Delete this entire "Structuring This Skill" section when done - it's just guidance.] + +## [TODO: Replace with the first main section based on chosen structure] + +[TODO: Add content here. See examples in existing skills: +- Code samples for technical skills +- Decision trees for complex workflows +- Concrete examples with realistic user requests +- References to scripts/templates/references as needed] + +## Resources (optional) + +Create only the resource directories this skill actually needs. Delete this section if no resources are required. + +### scripts/ +Executable code (Python/Bash/etc.) that can be run directly to perform specific operations. + +**Examples from other skills:** +- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation +- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing + +**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations. + +**Note:** Scripts may be executed without loading into context, but can still be read by Codex for patching or environment adjustments. + +### references/ +Documentation and reference material intended to be loaded into context to inform Codex's process and thinking. + +**Examples from other skills:** +- Product management: `communication.md`, `context_building.md` - detailed workflow guides +- BigQuery: API reference documentation and query examples +- Finance: Schema documentation, company policies + +**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Codex should reference while working. + +### assets/ +Files not intended to be loaded into context, but rather used within the output Codex produces. + +**Examples from other skills:** +- Brand styling: PowerPoint template files (.pptx), logo files +- Frontend builder: HTML/React boilerplate project directories +- Typography: Font files (.ttf, .woff2) + +**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output. + +--- + +**Not every skill requires all three types of resources.** +""" + +EXAMPLE_SCRIPT = '''#!/usr/bin/env python3 +""" +Example helper script for {skill_name} + +This is a placeholder script that can be executed directly. +Replace with actual implementation or delete if not needed. + +Example real scripts from other skills: +- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields +- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images +""" + +def main(): + print("This is an example script for {skill_name}") + # TODO: Add actual script logic here + # This could be data processing, file conversion, API calls, etc. + +if __name__ == "__main__": + main() +''' + +EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title} + +This is a placeholder for detailed reference documentation. +Replace with actual reference content or delete if not needed. + +Example real reference docs from other skills: +- product-management/references/communication.md - Comprehensive guide for status updates +- product-management/references/context_building.md - Deep-dive on gathering context +- bigquery/references/ - API references and query examples + +## When Reference Docs Are Useful + +Reference docs are ideal for: +- Comprehensive API documentation +- Detailed workflow guides +- Complex multi-step processes +- Information too lengthy for main SKILL.md +- Content that's only needed for specific use cases + +## Structure Suggestions + +### API Reference Example +- Overview +- Authentication +- Endpoints with examples +- Error codes +- Rate limits + +### Workflow Guide Example +- Prerequisites +- Step-by-step instructions +- Common patterns +- Troubleshooting +- Best practices +""" + +EXAMPLE_ASSET = """# Example Asset File + +This placeholder represents where asset files would be stored. +Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed. + +Asset files are NOT intended to be loaded into context, but rather used within +the output Codex produces. + +Example asset files from other skills: +- Brand guidelines: logo.png, slides_template.pptx +- Frontend builder: hello-world/ directory with HTML/React boilerplate +- Typography: custom-font.ttf, font-family.woff2 +- Data: sample_data.csv, test_dataset.json + +## Common Asset Types + +- Templates: .pptx, .docx, boilerplate directories +- Images: .png, .jpg, .svg, .gif +- Fonts: .ttf, .otf, .woff, .woff2 +- Boilerplate code: Project directories, starter files +- Icons: .ico, .svg +- Data files: .csv, .json, .xml, .yaml + +Note: This is a text placeholder. Actual assets can be any file type. +""" + + +def normalize_skill_name(skill_name): + """Normalize a skill name to lowercase hyphen-case.""" + normalized = skill_name.strip().lower() + normalized = re.sub(r"[^a-z0-9]+", "-", normalized) + normalized = normalized.strip("-") + normalized = re.sub(r"-{2,}", "-", normalized) + return normalized + + +def title_case_skill_name(skill_name): + """Convert hyphenated skill name to Title Case for display.""" + return " ".join(word.capitalize() for word in skill_name.split("-")) + + +def parse_resources(raw_resources): + if not raw_resources: + return [] + resources = [item.strip() for item in raw_resources.split(",") if item.strip()] + invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES}) + if invalid: + allowed = ", ".join(sorted(ALLOWED_RESOURCES)) + print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}") + print(f" Allowed: {allowed}") + sys.exit(1) + deduped = [] + seen = set() + for resource in resources: + if resource not in seen: + deduped.append(resource) + seen.add(resource) + return deduped + + +def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples): + for resource in resources: + resource_dir = skill_dir / resource + resource_dir.mkdir(exist_ok=True) + if resource == "scripts": + if include_examples: + example_script = resource_dir / "example.py" + example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name)) + example_script.chmod(0o755) + print("[OK] Created scripts/example.py") + else: + print("[OK] Created scripts/") + elif resource == "references": + if include_examples: + example_reference = resource_dir / "api_reference.md" + example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title)) + print("[OK] Created references/api_reference.md") + else: + print("[OK] Created references/") + elif resource == "assets": + if include_examples: + example_asset = resource_dir / "example_asset.txt" + example_asset.write_text(EXAMPLE_ASSET) + print("[OK] Created assets/example_asset.txt") + else: + print("[OK] Created assets/") + + +def init_skill(skill_name, path, resources, include_examples, interface_overrides): + """ + Initialize a new skill directory with template SKILL.md. + + Args: + skill_name: Name of the skill + path: Path where the skill directory should be created + resources: Resource directories to create + include_examples: Whether to create example files in resource directories + + Returns: + Path to created skill directory, or None if error + """ + # Determine skill directory path + skill_dir = Path(path).resolve() / skill_name + + # Check if directory already exists + if skill_dir.exists(): + print(f"[ERROR] Skill directory already exists: {skill_dir}") + return None + + # Create skill directory + try: + skill_dir.mkdir(parents=True, exist_ok=False) + print(f"[OK] Created skill directory: {skill_dir}") + except Exception as e: + print(f"[ERROR] Error creating directory: {e}") + return None + + # Create SKILL.md from template + skill_title = title_case_skill_name(skill_name) + skill_content = SKILL_TEMPLATE.format(skill_name=skill_name, skill_title=skill_title) + + skill_md_path = skill_dir / "SKILL.md" + try: + skill_md_path.write_text(skill_content) + print("[OK] Created SKILL.md") + except Exception as e: + print(f"[ERROR] Error creating SKILL.md: {e}") + return None + + # Create agents/openai.yaml + try: + result = write_openai_yaml(skill_dir, skill_name, interface_overrides) + if not result: + return None + except Exception as e: + print(f"[ERROR] Error creating agents/openai.yaml: {e}") + return None + + # Create resource directories if requested + if resources: + try: + create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_examples) + except Exception as e: + print(f"[ERROR] Error creating resource directories: {e}") + return None + + # Print next steps + print(f"\n[OK] Skill '{skill_name}' initialized successfully at {skill_dir}") + print("\nNext steps:") + print("1. Edit SKILL.md to complete the TODO items and update the description") + if resources: + if include_examples: + print("2. Customize or delete the example files in scripts/, references/, and assets/") + else: + print("2. Add resources to scripts/, references/, and assets/ as needed") + else: + print("2. Create resource directories only if needed (scripts/, references/, assets/)") + print("3. Update agents/openai.yaml if the UI metadata should differ") + print("4. Run the validator when ready to check the skill structure") + + return skill_dir + + +def main(): + parser = argparse.ArgumentParser( + description="Create a new skill directory with a SKILL.md template.", + ) + parser.add_argument("skill_name", help="Skill name (normalized to hyphen-case)") + parser.add_argument("--path", required=True, help="Output directory for the skill") + parser.add_argument( + "--resources", + default="", + help="Comma-separated list: scripts,references,assets", + ) + parser.add_argument( + "--examples", + action="store_true", + help="Create example files inside the selected resource directories", + ) + parser.add_argument( + "--interface", + action="append", + default=[], + help="Interface override in key=value format (repeatable)", + ) + args = parser.parse_args() + + raw_skill_name = args.skill_name + skill_name = normalize_skill_name(raw_skill_name) + if not skill_name: + print("[ERROR] Skill name must include at least one letter or digit.") + sys.exit(1) + if len(skill_name) > MAX_SKILL_NAME_LENGTH: + print( + f"[ERROR] Skill name '{skill_name}' is too long ({len(skill_name)} characters). " + f"Maximum is {MAX_SKILL_NAME_LENGTH} characters." + ) + sys.exit(1) + if skill_name != raw_skill_name: + print(f"Note: Normalized skill name from '{raw_skill_name}' to '{skill_name}'.") + + resources = parse_resources(args.resources) + if args.examples and not resources: + print("[ERROR] --examples requires --resources to be set.") + sys.exit(1) + + path = args.path + + print(f"Initializing skill: {skill_name}") + print(f" Location: {path}") + if resources: + print(f" Resources: {', '.join(resources)}") + if args.examples: + print(" Examples: enabled") + else: + print(" Resources: none (create as needed)") + print() + + result = init_skill(skill_name, path, resources, args.examples, args.interface) + + if result: + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.kiro/skills/.system/skill-creator/scripts/quick_validate.py b/.kiro/skills/.system/skill-creator/scripts/quick_validate.py new file mode 100644 index 00000000..0547b404 --- /dev/null +++ b/.kiro/skills/.system/skill-creator/scripts/quick_validate.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +Quick validation script for skills - minimal version +""" + +import re +import sys +from pathlib import Path + +import yaml + +MAX_SKILL_NAME_LENGTH = 64 + + +def validate_skill(skill_path): + """Basic validation of a skill""" + skill_path = Path(skill_path) + + skill_md = skill_path / "SKILL.md" + if not skill_md.exists(): + return False, "SKILL.md not found" + + content = skill_md.read_text() + if not content.startswith("---"): + return False, "No YAML frontmatter found" + + match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) + if not match: + return False, "Invalid frontmatter format" + + frontmatter_text = match.group(1) + + try: + frontmatter = yaml.safe_load(frontmatter_text) + if not isinstance(frontmatter, dict): + return False, "Frontmatter must be a YAML dictionary" + except yaml.YAMLError as e: + return False, f"Invalid YAML in frontmatter: {e}" + + allowed_properties = {"name", "description", "license", "allowed-tools", "metadata"} + + unexpected_keys = set(frontmatter.keys()) - allowed_properties + if unexpected_keys: + allowed = ", ".join(sorted(allowed_properties)) + unexpected = ", ".join(sorted(unexpected_keys)) + return ( + False, + f"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}", + ) + + if "name" not in frontmatter: + return False, "Missing 'name' in frontmatter" + if "description" not in frontmatter: + return False, "Missing 'description' in frontmatter" + + name = frontmatter.get("name", "") + if not isinstance(name, str): + return False, f"Name must be a string, got {type(name).__name__}" + name = name.strip() + if name: + if not re.match(r"^[a-z0-9-]+$", name): + return ( + False, + f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)", + ) + if name.startswith("-") or name.endswith("-") or "--" in name: + return ( + False, + f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens", + ) + if len(name) > MAX_SKILL_NAME_LENGTH: + return ( + False, + f"Name is too long ({len(name)} characters). " + f"Maximum is {MAX_SKILL_NAME_LENGTH} characters.", + ) + + description = frontmatter.get("description", "") + if not isinstance(description, str): + return False, f"Description must be a string, got {type(description).__name__}" + description = description.strip() + if description: + if "<" in description or ">" in description: + return False, "Description cannot contain angle brackets (< or >)" + if len(description) > 1024: + return ( + False, + f"Description is too long ({len(description)} characters). Maximum is 1024 characters.", + ) + + return True, "Skill is valid!" + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python quick_validate.py <skill_directory>") + sys.exit(1) + + valid, message = validate_skill(sys.argv[1]) + print(message) + sys.exit(0 if valid else 1) diff --git a/.kiro/skills/aios-jimeng-image-ops/.aios-skill-sync.json b/.kiro/skills/aios-jimeng-image-ops/.aios-skill-sync.json new file mode 100644 index 00000000..9163408a --- /dev/null +++ b/.kiro/skills/aios-jimeng-image-ops/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "aios-jimeng-image-ops", + "targetSurface": "kiro", + "targetRelativePath": "aios-jimeng-image-ops", + "source": "skill-sources/aios-jimeng-image-ops" +} \ No newline at end of file diff --git a/.kiro/skills/aios-jimeng-image-ops/SKILL.md b/.kiro/skills/aios-jimeng-image-ops/SKILL.md new file mode 100644 index 00000000..3a96d0a4 --- /dev/null +++ b/.kiro/skills/aios-jimeng-image-ops/SKILL.md @@ -0,0 +1,52 @@ +--- +name: aios-jimeng-image-ops +description: Use when generating images on jimeng.jianying.com through browser automation, or when diagnosing unstable Jimeng execution in aios. +--- + +# AIOS Jimeng Image Ops + +## Overview +Use this runbook for stable image generation on Jimeng. It includes selectors, completion signals, policy-failure handling, and evidence requirements verified in a live run. + +## Preconditions +- Browser profile has valid Jimeng login session. +- `default` profile should connect to fingerprint browser via CDP (port `9222` by default). +- MCP tools available: `chrome.launch_cdp`, `browser.connect_cdp`, `page.goto`, `page.click`, `page.type`, `page.extract_text`, `page.get_html`, `page.screenshot`, `browser.close`. +- For page understanding, prefer `page.extract_text` first, then `page.get_html` for structure details. +- Use `page.screenshot` only when text/HTML evidence is insufficient, and prefer target-area capture over full-page screenshots when supported. +- Prompt is policy-safe (avoid risky terms, political/person-identifiable/sensitive wording). + +## Execution Flow (Updated 2026-03-13) +1. Open generation page: + - Navigate to home: `https://jimeng.jianying.com/ai-tool/home/` + - Click "图片生成" button (selector: `button.button-RNHVcx:has-text("图片生成")`) + - Or use direct URL: `https://jimeng.jianying.com/ai-tool/generate?enter_from=ai_feature&from_page=explore&ai_feature_name=image` +2. Use `page.extract_text` / `page.get_html` to detect login/challenge markers (captcha, risk check, 2FA, login wall); if detected, ask user to complete human action and resume. +3. Confirm prompt box exists: + - Selector: `div.tiptap.ProseMirror` (NOT textarea, this is a rich text editor) +4. Click prompt box to activate, then fill prompt. +5. **Model & Ratio Selection (Bottom Toolbar)**: + - Model selector: `div.lv-select:has-text("图片")` (first one, position x:344) + - Ratio selector: Second `div.lv-select` (position x:458, shows "图片 4.1" or "图片 5.0") + - Note: These may show `clickable: false` in snapshot but are still interactive +6. **Generate Button**: + - Primary: `button.lv-btn.lv-btn-primary` (position x:976, y:647) + - Note: Button may show as `collapsed-submit-button` class, enabled only after prompt is entered +7. Poll `page.extract_text` (and `page.get_html` when needed) until completion: + - Success markers: latest task has image tiles + `重新编辑` and `再次生成` + - Policy failure: `你输入的文字不符合平台规则,请修改后重试` + - Timeout: still `生成中` after budget + +## Error Handling +- Policy failure: rewrite prompt to neutral scene/style terms and retry once. +- Timeout: refresh generation page and retry with same prompt once. +- Selector failure: recapture snapshot and update selectors from visible controls. + +## Evidence Standard +- Keep at least one snapshot proving success or failure. +- Save one screenshot for run summary. +- Record prompt, outcome, and retry path in final doc. + +## Resources +- `references/run-report-2026-03-02.md`: verified live-run record. +- `references/selectors-and-errors.md`: selector cookbook and failure taxonomy. diff --git a/.kiro/skills/aios-jimeng-image-ops/references/run-report-2026-03-02.md b/.kiro/skills/aios-jimeng-image-ops/references/run-report-2026-03-02.md new file mode 100644 index 00000000..cab851ff --- /dev/null +++ b/.kiro/skills/aios-jimeng-image-ops/references/run-report-2026-03-02.md @@ -0,0 +1,23 @@ +# Jimeng Live Run Report (2026-03-02) + +## Environment +- URL: `https://jimeng.jianying.com/ai-tool/home` then generation page +- Tooling: browser automation via devtools MCP for live verification +- Session: logged-in account available during run + +## Run A (Failed by policy) +- Prompt: "小红书封面,清新治愈风,淡粉和暖黄色渐变背景,简约留白,手写风标题区域,无文字,无人物,3:4竖图" +- Result: rejected with message `你输入的文字不符合平台规则,请修改后重试` +- Classification: content-policy rejection + +## Run B (Success) +- Prompt: "春日花园插画风景,樱花树、草地、小路、蓝天白云,柔和阳光,高清,3:4竖版,无人物无文字" +- Result: task produced image tiles and preview dialog with `下载` button +- Evidence files: + - `images/jimeng-run-2026-03-02.png` + - `images/jimeng-run-modal-2026-03-02.png` + +## Conclusions +- Core flow is valid. +- Biggest non-determinism is policy rejection and dynamic UI selectors. +- Stable automation must include policy-aware retry and snapshot-based completion checks. diff --git a/.kiro/skills/aios-jimeng-image-ops/references/selectors-and-errors.md b/.kiro/skills/aios-jimeng-image-ops/references/selectors-and-errors.md new file mode 100644 index 00000000..37f02395 --- /dev/null +++ b/.kiro/skills/aios-jimeng-image-ops/references/selectors-and-errors.md @@ -0,0 +1,48 @@ +# Jimeng Selectors and Error Patterns (Updated 2026-03-13) + +## Current UI Structure + +### Prompt Input +- Selector: `div.tiptap.ProseMirror` +- Note: NOT a textarea, it's a rich text editor div + +### Bottom Toolbar (Generation Options) +Located at bottom of viewport (y:647), contains: +- Model selector (x:344): `div.lv-select.lv-select-single:has-text("图片")` or `div.lv-select:first-child` +- Version selector (x:458): Second `div.lv-select.lv-select-single`, shows "图片 4.1" or "图片 5.0" +- Ratio button (x:553): `button:has-text("1:1")`, `button:has-text("3:4")`, etc. +- Generate button (x:976): `button.lv-btn.lv-btn-primary` + +### Selector Priority +1. Prompt: `div.tiptap.ProseMirror` +2. Model/Version: `div.lv-select.lv-select-single` +3. Generate: `button.lv-btn.lv-btn-primary` +4. Ratio: `button.lv-btn.lv-btn-secondary:has-text("3:4")` + +## Known Issues & Workarounds + +### Element Not Visible Error +- Elements at y:647 may show `clickable: false` in snapshot +- Workaround: Click anyway, or try clicking prompt box first to "wake up" the toolbar +- The tool may report timeout but the click still succeeds + +### Collapsed Submit Button +- Button has class `collapsed-submit-button-o26OIS` +- Only enabled after prompt is entered +- May show as disabled initially, becomes enabled after prompt + +### Navigation +- Use home page first: `https://jimeng.jianying.com/ai-tool/home/` +- Click "图片生成" button: `button.button-RNHVcx:has-text("图片生成")` + +## Completion Signals +- Success: new task card with prompt text + image tiles + `重新编辑`/`再次生成` actions. +- Policy reject: explicit warning string about platform rules. +- Still running: `生成中` or `智能创意中`. + +## Common Failure Modes +- `No active page`: browser not launched or profile lost. +- Selector timeout: page changed layout or hidden composer state. +- Policy block: prompt contains disallowed phrasing. +- Session expired: redirected state or login wall appears. +- Element not visible: common for bottom toolbar, try alternative selectors or force click diff --git a/.kiro/skills/aios-long-running-harness/.aios-skill-sync.json b/.kiro/skills/aios-long-running-harness/.aios-skill-sync.json new file mode 100644 index 00000000..b66b6eb0 --- /dev/null +++ b/.kiro/skills/aios-long-running-harness/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "aios-long-running-harness", + "targetSurface": "kiro", + "targetRelativePath": "aios-long-running-harness", + "source": "skill-sources/aios-long-running-harness" +} \ No newline at end of file diff --git a/.kiro/skills/aios-long-running-harness/SKILL.md b/.kiro/skills/aios-long-running-harness/SKILL.md new file mode 100644 index 00000000..a4058260 --- /dev/null +++ b/.kiro/skills/aios-long-running-harness/SKILL.md @@ -0,0 +1,54 @@ +--- +name: aios-long-running-harness +description: Use when running multi-step or long-running agent jobs in aios that require checkpointing, retries, evidence capture, and safe human handoff. +--- + +# AIOS Long-Running Harness + +## Overview +Use this harness to keep long tasks stable under UI drift, model variability, and partial failures. It maps Anthropic's long-running-agent harness ideas into this repository's file-based workflow. + +## Harness Loop +1. Preflight: lock objective, stop conditions, budgets, and required artifacts. +2. Plan: split into idempotent steps with explicit success/failure evidence. +3. Execute: run one step at a time with tool output capture. +4. Verify: assert completion from page evidence, not assumptions. +5. Checkpoint: persist current state, artifacts, and next action. +6. Recover: on failure, classify and retry only with a changed hypothesis. +7. Complete: run final verification and write summary doc. + +## Pairing with Superpowers Skills +- Plan step should be produced through `superpowers:writing-plans` (or `superpowers:brainstorming` first when scope is unclear). +- For 2+ independent domains, use `superpowers:dispatching-parallel-agents`; for coupled domains, run sequentially. +- If the runtime has no true subagent tool, emulate dispatch with explicit per-domain task queues and only parallelize safe independent reads/checks. +- Always finish with `superpowers:verification-before-completion` before claiming run success. + +## Orchestrate Live Notes +- `aios orchestrate --execute live` currently supports `AIOS_SUBAGENT_CLIENT=codex-cli` only. +- Codex CLI v0.114+ structured exec outputs (`--output-schema`, `--output-last-message`, stdin) are required for handoff parsing; schema fallback to raw stdout is rejected. +- Transient `upstream_error`/`server_error` failures are retried with exponential backoff via `AIOS_SUBAGENT_UPSTREAM_MAX_ATTEMPTS` and `AIOS_SUBAGENT_UPSTREAM_BACKOFF_MS`. + +## Required Controls +- Time budget per step and per run. +- Retry budget per failure class. +- Human-gate checkpoints for login, payment, or policy-sensitive actions. +- Solo harness checkpoints should include the current stage (`research`, `requirements`, `planning`, `development`, `validation`, `handoff`) and concrete evidence. +- Structured logs for every major transition. + +## Failure Classes +- Selector/UI drift. +- Authentication/session loss. +- Policy rejection/content moderation. +- Network/transient failures. +- Tool/runtime errors. + +## Completion Gate +Declare success only when all are true: +- Target action succeeded. +- Expected artifact exists. +- Evidence snapshot/log exists. +- Updated runbook reflects newly observed drift. + +## Resources +- `references/harness-checklist.md`: operational checklist template. +- `references/anthropic-mapping.md`: principle-to-project mapping. diff --git a/.kiro/skills/aios-long-running-harness/references/anthropic-mapping.md b/.kiro/skills/aios-long-running-harness/references/anthropic-mapping.md new file mode 100644 index 00000000..471723f1 --- /dev/null +++ b/.kiro/skills/aios-long-running-harness/references/anthropic-mapping.md @@ -0,0 +1,18 @@ +# Anthropic Harness Mapping (Applied to AIOS) + +## Source +- https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents + +## Mapped Principles +- Treat long-running execution as a systems problem, not a single prompt problem. +- Externalize state so runs can resume and be audited. +- Use bounded loops with explicit checks between steps. +- Prefer deterministic wrappers around nondeterministic model behavior. +- Build observability first (logs/checkpoints) to debug failures quickly. +- Include human gates for sensitive or high-uncertainty transitions. + +## Repository-Level Translation +- External state: `tasks/*`, `memory/*`, `docs/plans/*`. +- Deterministic wrappers: browser-use MCP chain (`chrome.launch_cdp` / `browser.connect_cdp` / `page.*`) + selector standards. +- Observability: `page.extract_text` / `page.get_html` evidence + run report documents. +- Human gates: login/captcha/content-policy rewrite approvals. diff --git a/.kiro/skills/aios-long-running-harness/references/harness-checklist.md b/.kiro/skills/aios-long-running-harness/references/harness-checklist.md new file mode 100644 index 00000000..7b394971 --- /dev/null +++ b/.kiro/skills/aios-long-running-harness/references/harness-checklist.md @@ -0,0 +1,24 @@ +# Long-Running Harness Checklist + +## Preflight +- Objective and non-goals are explicit. +- Absolute deadline and retry budget are set. +- Required permissions/login steps are identified. +- Artifact output path is defined. + +## Per-Step Contract +- Input state is documented. +- Exact action is deterministic and idempotent. +- Success signal is machine-checkable. +- Failure signal and fallback path are defined. + +## Recovery Rules +- Never apply two fixes at once. +- On each retry, change only one variable. +- After two failed retries in same class, escalate for human decision. +- Persist context before any manual handoff. + +## End-of-Run +- Final evidence captured. +- Skills/runbooks patched if drift was discovered. +- Summary doc written with root cause and fix. diff --git a/.kiro/skills/aios-project-system/.aios-skill-sync.json b/.kiro/skills/aios-project-system/.aios-skill-sync.json new file mode 100644 index 00000000..dd280031 --- /dev/null +++ b/.kiro/skills/aios-project-system/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "aios-project-system", + "targetSurface": "kiro", + "targetRelativePath": "aios-project-system", + "source": "skill-sources/aios-project-system" +} \ No newline at end of file diff --git a/.kiro/skills/aios-project-system/SKILL.md b/.kiro/skills/aios-project-system/SKILL.md new file mode 100644 index 00000000..eece38f8 --- /dev/null +++ b/.kiro/skills/aios-project-system/SKILL.md @@ -0,0 +1,47 @@ +--- +name: aios-project-system +description: Use when operating in the aios repository and needing the canonical architecture, memory schema, MCP browser tool behavior, and execution constraints before changing workflows. +--- + +# AIOS Project System + +## Overview +Use this skill as the repository map for `aios`. It explains where state lives, how automation actually runs, and which files are authoritative before you edit tasks, workflows, or browser operations. + +## Core Topology +- `CLAUDE.md`: project-level behavior contract and architecture overview. +- `memory/skills/*.json`: operational playbooks for recurring tasks (not deterministic executors). +- `memory/specs/*.json`: safety constraints and limits. +- `tasks/{pending,done,failed}`: task queue and outcomes. +- `scripts/run-browser-use-mcp.sh`: default browser MCP launcher (bridges to `ai-browser-book/mcp-browser-use`). +- `mcp-server/`: legacy Playwright MCP implementation retained for compatibility workflows. +- `docs/plans/`: design, implementation, and postmortem documents. + +## Runtime Truths (Do Not Skip) +- MCP server label may still be `puppeteer-stealth`, but default runtime now routes to browser-use tools (`chrome.launch_cdp`, `browser.connect_cdp`, `page.*`) via `scripts/run-browser-use-mcp.sh`. +- `mcp-server/src/index.ts` still exposes Playwright `browser_*` tools, but treat that path as legacy/compatibility. +- If both `puppeteer-stealth` and `chrome-devtools` are available, use `puppeteer-stealth` for normal browser automation and reserve `chrome-devtools` for debugging only. +- For interactive runs, explicitly prefer `chrome.launch_cdp { port: 9222, user_data_dir: '~/.chrome-cdp-profile' }`, then `browser.connect_cdp`. +- `memory/skills/*.json` can drift from site UI; treat them as runbooks that require live verification. +- Prefer `page.extract_text` / `page.get_html` evidence before using `page.screenshot`. +- Repo-local discoverable skills must live in `.codex/skills/` or `.claude/skills/`; do not create ad-hoc skill roots such as `.baoyu-skills/*/SKILL.md`. `.baoyu-skills/` is extension-config territory, not a Codex/Claude skill root. +- Keep safety constraints aligned with `memory/specs` and `memory/skills/技能使用约束.json`. + +## Superpowers Route Bridge +When requests are substantial, chain process skills and harness controls in this order: +1. Choose process: `superpowers:brainstorming` / `superpowers:writing-plans` / `superpowers:systematic-debugging`. +2. Produce plan artifact in `docs/plans/YYYY-MM-DD-<topic>.md`. +3. Apply `aios-long-running-harness` preflight, evidence gates, and retry policy. +4. Persist run state via ContextDB checkpoints. +5. If work splits into independent domains, use `superpowers:dispatching-parallel-agents`; if domains are coupled, stay sequential. +6. End only with `superpowers:verification-before-completion`. + +## Default Operating Order +1. Read task context and matching `memory/skills` + `memory/specs` files. +2. Confirm available MCP tools, CDP session strategy, and selector strategy. +3. Execute with evidence checkpoints (`page.extract_text` / `page.get_html` logs per key step; `page.screenshot` only for visual fallback). +4. Write outcome to docs/history and patch skills if drift is found. + +## Resources +- `references/system-map.md`: concise architecture and data flow map. +- `references/file-index.md`: fast file lookup by change intent. diff --git a/.kiro/skills/aios-project-system/references/file-index.md b/.kiro/skills/aios-project-system/references/file-index.md new file mode 100644 index 00000000..7bd73c3a --- /dev/null +++ b/.kiro/skills/aios-project-system/references/file-index.md @@ -0,0 +1,26 @@ +# File Index by Intent + +## Understand architecture +- `CLAUDE.md` +- `docs/plans/2026-03-01-xiaohongshu-assistant-design.md` + +## Edit browser automation +- `mcp-server/src/index.ts` +- `mcp-server/src/browser/actions/*.ts` +- `config/browser-profiles.json` +- `config/settings.json` + +## Adjust operational behavior +- `memory/skills/*.json` +- `memory/specs/*.json` +- `memory/skills/技能使用约束.json` + +## Diagnose and document failures +- `tasks/failed/` +- `memory/history/` +- `docs/plans/*.md` + +## Jimeng-specific files +- `memory/skills/即梦AI生成图片.json` +- `docs/plans/2026-03-01-jimeng-ai-image-gen-design.md` +- `docs/plans/2026-03-01-jimeng-ai-image-gen-implementation-plan.md` diff --git a/.kiro/skills/aios-project-system/references/system-map.md b/.kiro/skills/aios-project-system/references/system-map.md new file mode 100644 index 00000000..279aee7d --- /dev/null +++ b/.kiro/skills/aios-project-system/references/system-map.md @@ -0,0 +1,27 @@ +# AIOS System Map + +## Purpose +AIOS is a browser-automation assistant for Xiaohongshu operations plus related content tooling (including Jimeng image generation). + +## End-to-End Flow +User intent -> skill retrieval (`memory/skills`) -> MCP browser-use/CDP actions -> platform result -> evidence capture -> memory/docs updates. + +## Main State Surfaces +- Process memory: `memory/skills`, `memory/specs`, `memory/history`, `memory/knowledge` +- Task lifecycle: `tasks/pending`, `tasks/done`, `tasks/failed` +- Artifact output: `images/`, `temp/` +- Automation engine: default browser-use MCP launcher (`scripts/run-browser-use-mcp.sh`) + legacy `mcp-server/` Playwright compatibility server + +## Automation Contract +- Launch/attach fingerprint browser profile with `chrome.launch_cdp`. +- Establish browser session with `browser.connect_cdp`. +- Navigate and act with `page.goto` / `page.click` / `page.type`. +- Capture text/DOM evidence with `page.extract_text` / `page.get_html`. +- Use `page.screenshot` only when visual fallback is required. +- Detect auth/challenge markers and branch (retry/manual handoff). +- Record final status and artifact path. + +## High-Risk Drift Zones +- Dynamic CSS class names on target websites. +- Alias mismatch (`puppeteer-stealth` server name vs browser-use tool namespace `chrome.*` / `browser.*` / `page.*`). +- Skill JSON assumptions that are no longer valid for latest UI. diff --git a/.kiro/skills/aios-workflow-router/.aios-skill-sync.json b/.kiro/skills/aios-workflow-router/.aios-skill-sync.json new file mode 100644 index 00000000..11028a93 --- /dev/null +++ b/.kiro/skills/aios-workflow-router/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "aios-workflow-router", + "targetSurface": "kiro", + "targetRelativePath": "aios-workflow-router", + "source": "skill-sources/aios-workflow-router" +} \ No newline at end of file diff --git a/.kiro/skills/aios-workflow-router/SKILL.md b/.kiro/skills/aios-workflow-router/SKILL.md new file mode 100644 index 00000000..90b0ee41 --- /dev/null +++ b/.kiro/skills/aios-workflow-router/SKILL.md @@ -0,0 +1,129 @@ +--- +name: aios-workflow-router +description: "Route tasks to appropriate workflows. TRIGGER: 分析、设计、实现、调试、并发、并行、agent team、长任务、harness、plan、计划、brainstorm、头脑风暴、debug、调试、multi-step、多步骤" +--- + +# AIOS Workflow Router + +**This skill routes your task to the appropriate workflow. Invoke this BEFORE any other action.** + +## Quick Decision Tree + +``` +用户请求 → 任务类型判断 → 调用相应技能/流程 +``` + +## Task Type Detection + +### 1. Design/Creative Tasks (设计/创意任务) +**Keywords**: 设计、创意、新功能、新特性、build、create、implement、设计、创意、brainstorm、头脑风暴 + +**Route to**: Planning workflow +1. Use `superpowers:brainstorming` if available, otherwise follow the brainstorming process below +2. Create design doc in `docs/plans/` or `docs/superpowers/specs/` +3. Get user approval before implementation + +### 2. Debug/Failure Tasks (调试/故障任务) +**Keywords**: 调试、bug、错误、失败、error、fail、debug、修复、fix、不工作、broken + +**Route to**: Debugging workflow +1. Use `superpowers:systematic-debugging` if available +2. Gather evidence first (logs, error messages, screenshots) +3. Form hypothesis, test, iterate + +### 3. Multi-step/Long-running Tasks (多步骤/长任务) +**Keywords**: 长任务、多步骤、harness、checkpoint、evidence、long-running、multi-step、复杂任务、orchestrat + +**Route to**: Harness workflow +1. Use `aios-long-running-harness` skill +2. Set preflight budgets and stop conditions +3. Create checkpoints with evidence + +### 4. Parallel/Agent Team Tasks (并行/团队任务) +**Keywords**: 并发、并行、agent team、团队、多agent、多个独立、dispatch、parallel、concurrent + +**Route to**: Parallel dispatch workflow +1. Identify independent problem domains +2. Confirm there are at least 2 truly independent domains before dispatching +3. Use `superpowers:dispatching-parallel-agents` if available +4. If no subagent tool available, emulate with explicit task queues +5. While waiting, emit heartbeat progress every ~30s; if no worker completes after ~120s, stop waiting and fall back to sequential execution + +### 5. Implementation Tasks (实现任务) +**Keywords**: 实现、implement、开发、develop、编码、code、写代码 + +**Route to**: Implementation workflow +1. Check if plan exists in `docs/plans/` +2. If no plan, go through brainstorming first +3. Use `superpowers:test-driven-development` if available +4. Implement with evidence checkpoints + +### 6. Analysis Tasks (分析任务) +**Keywords**: 分析、analysis、研究、research、investigate、调查、为什么、why + +**Route to**: Analysis workflow +1. Gather information from codebase, logs, history +2. Document findings +3. Present recommendations +4. Default to single-agent execution; do not dispatch explorer/parallel agents unless the user explicitly asks for delegation or parallel work + +## Workflow Execution + +### Standard Flow + +``` +1. Route → 2. Plan → 3. Execute → 4. Verify → 5. Complete +``` + +### Mandatory Steps + +1. **Route**: Identify task type (this skill) +2. **Plan**: Create or reference plan document +3. **Execute**: Follow plan with checkpoints +4. **Verify**: Assert completion with evidence +5. **Complete**: Update docs and commit + +## Fallback Behaviors + +If `superpowers:*` skills are not available (plugin not installed): + +- **brainstorming**: Use the built-in brainstorming process in this skill +- **systematic-debugging**: Use the debugging process below +- **dispatching-parallel-agents**: Execute sequentially or emulate with task queues +- **verification-before-completion**: Manually verify artifacts exist + +## Built-in Brainstorming Process + +When `superpowers:brainstorming` is not available: + +1. **Explore context** - Check files, docs, recent commits +2. **Ask clarifying questions** - One at a time, understand purpose +3. **Propose approaches** - 2-3 options with trade-offs +4. **Present design** - Get user approval +5. **Write plan** - Save to `docs/plans/YYYY-MM-DD-<topic>.md` + +## Built-in Debugging Process + +When `superpowers:systematic-debugging` is not available: + +1. **Gather evidence** - Logs, errors, screenshots +2. **Form hypothesis** - Based on evidence +3. **Test hypothesis** - Make minimal change +4. **Verify fix** - Confirm with evidence +5. **Document** - Update runbook if needed + +## Completion Gate + +Before claiming any task complete: + +- [ ] Target action succeeded +- [ ] Expected artifacts exist +- [ ] Evidence documented +- [ ] No regressions introduced + +## Resource Links + +- `memory/specs/行为规范.json` - Safety specifications +- `memory/specs/风险检测.json` - Risk detection rules +- `docs/plans/` - Implementation plans +- `memory/history/` - Operation records diff --git a/.kiro/skills/awesome-design-md/.aios-skill-sync.json b/.kiro/skills/awesome-design-md/.aios-skill-sync.json new file mode 100644 index 00000000..2707b4d0 --- /dev/null +++ b/.kiro/skills/awesome-design-md/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "awesome-design-md", + "targetSurface": "kiro", + "targetRelativePath": "awesome-design-md", + "source": "skill-sources/awesome-design-md" +} \ No newline at end of file diff --git a/.kiro/skills/awesome-design-md/SKILL.md b/.kiro/skills/awesome-design-md/SKILL.md new file mode 100644 index 00000000..f8165b80 --- /dev/null +++ b/.kiro/skills/awesome-design-md/SKILL.md @@ -0,0 +1,115 @@ +--- +name: awesome-design-md +description: Use when users need a project-level DESIGN.md baseline, especially when they want to borrow a proven style quickly or have no design draft but still need consistent UI direction. +--- + +# Awesome DESIGN.md + +Use this skill when you want to apply a proven visual style to a project by adding a `DESIGN.md` file from the VoltAgent `awesome-design-md` collection. + +## Trigger + +- User wants UI to look like a known product/site style. +- User asks to add or refresh `DESIGN.md` for coding-agent UI output. +- User wants consistent typography, colors, spacing, and component styling before UI implementation. +- User has no design稿 and needs a fast, high-quality visual starting point. + +## Preconditions + +- Run from the target project root. +- `npx` is available. +- If `DESIGN.md` already exists, decide whether to overwrite (`--force`) or write to another path (`--out`). + +## Workflow + +1. List available design slugs: + +```bash +npx --yes getdesign@latest list +``` + +2. Install a selected style: + +```bash +npx --yes getdesign@latest add <slug> +``` + +Behavior: +- First install writes `./DESIGN.md`. +- If `DESIGN.md` already exists, CLI writes `./<slug>/DESIGN.md`. + +3. Overwrite the active `DESIGN.md` when required: + +```bash +npx --yes getdesign@latest add <slug> --force +``` + +4. Write to a custom output path (optional): + +```bash +npx --yes getdesign@latest add <slug> --out ./docs/DESIGN.md +``` + +5. Verify output exists and is readable: + +```bash +ls -la DESIGN.md <slug>/DESIGN.md 2>/dev/null +``` + +6. Tell the coding agent to follow the file before UI tasks. + +## No-Design-Draft Fast Path + +When user has no design draft, pick one baseline slug by product intent: + +- `B2B/SaaS dashboard`: `linear`, `vercel`, `supabase` +- `Marketing landing page`: `framer`, `stripe`, `notion` +- `Documentation`: `mintlify`, `hashicorp`, `mongodb` +- `E-commerce/consumer`: `airbnb`, `shopify`, `nike` +- `Media/editorial`: `theverge`, `wired`, `spotify` + +Then run: + +```bash +npx --yes getdesign@latest add <slug> --force +``` + +This gives the agent an explicit style anchor immediately, avoiding generic defaults. + +## Fuzzy Style Request Mapping + +When user only gives a vague style intent, map it to a usable slug quickly: + +- `极简专业 / SaaS 感`: `linear`, `vercel` +- `增长营销 / 品牌展示`: `framer`, `stripe` +- `文档知识库 / 开发者文档`: `mintlify`, `hashicorp`, `mongodb` +- `电商消费 / 商品导向`: `shopify`, `airbnb`, `nike` +- `媒体杂志 / 视觉冲击`: `theverge`, `wired`, `spotify` + +Execution rule: + +1. Pick one primary slug from user intent. +2. Install with overwrite for deterministic output: + +```bash +npx --yes getdesign@latest add <slug> --force +``` + +3. If user gives no domain cue, default to `linear` for generic SaaS web apps. + +## Prompt Pattern + +- `Use DESIGN.md as the source of truth for UI decisions in this task.` +- `Follow color roles, typography hierarchy, spacing scale, and component states from DESIGN.md.` +- `If a UI decision is unclear, prefer consistency with DESIGN.md over introducing new styles.` + +## Guardrails + +- Treat templates as inspiration and implementation guidance, not official brand endorsement. +- Keep accessibility checks (contrast, keyboard focus, touch targets) even when following a template. + +## Upstream + +- Repository: `https://github.com/VoltAgent/awesome-design-md` +- License: MIT +- CLI usage shown by `npx --yes getdesign@latest --help` diff --git a/.kiro/skills/awesome-design-md/UPSTREAM.md b/.kiro/skills/awesome-design-md/UPSTREAM.md new file mode 100644 index 00000000..6a953f02 --- /dev/null +++ b/.kiro/skills/awesome-design-md/UPSTREAM.md @@ -0,0 +1,10 @@ +# Upstream Reference + +This skill wraps usage of VoltAgent's DESIGN.md ecosystem. + +- Repository: `https://github.com/VoltAgent/awesome-design-md` +- Website/CLI docs: `https://getdesign.md` +- License: MIT +- Retrieved: 2026-04-18 + +Integration mode: guidance-only wrapper that uses `npx --yes getdesign@latest` to install a selected `DESIGN.md` into a user project. diff --git a/.kiro/skills/cap-commit-push/.aios-skill-sync.json b/.kiro/skills/cap-commit-push/.aios-skill-sync.json new file mode 100644 index 00000000..19ebff64 --- /dev/null +++ b/.kiro/skills/cap-commit-push/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "cap-commit-push", + "targetSurface": "kiro", + "targetRelativePath": "cap-commit-push", + "source": "skill-sources/cap-commit-push" +} \ No newline at end of file diff --git a/.kiro/skills/cap-commit-push/SKILL.md b/.kiro/skills/cap-commit-push/SKILL.md new file mode 100644 index 00000000..6e9a4120 --- /dev/null +++ b/.kiro/skills/cap-commit-push/SKILL.md @@ -0,0 +1,35 @@ +--- +name: cap-commit-push +description: Use when the user sends `cap` or asks for a fast commit-and-push shortcut for current repository changes. +--- + +# Cap Commit Push + +## Overview +`cap` is a shorthand command that means: commit current changes and push to remote. + +## Trigger +- User message is exactly `cap`. +- Or user explicitly asks for "commit + push". + +## Required Workflow +1. Preflight + - Run `git status --short`. + - If there are no changes, report no-op and stop. +2. Sync Gate + - If this task changes behavior/commands/workflow, update impacted skills first. + - Keep `.codex/skills/*` and `.claude/skills/*` in sync for shared skills. +3. Stage + - Run `git add -A`. +4. Commit + - Prefer a Conventional Commit message based on current task context. + - Fallback message: `chore: cap snapshot <YYYY-MM-DD>`. +5. Push + - Run `git push`. + - If upstream is missing, run `git push --set-upstream origin <current-branch>`. +6. Report + - Return commit hash, branch, and push result. + +## Safety Rules +- Do not amend commits unless the user explicitly asks. +- Do not run destructive git commands (`reset --hard`, forced checkout). diff --git a/.kiro/skills/contextdb-autopilot/.aios-skill-sync.json b/.kiro/skills/contextdb-autopilot/.aios-skill-sync.json new file mode 100644 index 00000000..fb7aa7f9 --- /dev/null +++ b/.kiro/skills/contextdb-autopilot/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "contextdb-autopilot", + "targetSurface": "kiro", + "targetRelativePath": "contextdb-autopilot", + "source": "skill-sources/contextdb-autopilot" +} \ No newline at end of file diff --git a/.kiro/skills/contextdb-autopilot/SKILL.md b/.kiro/skills/contextdb-autopilot/SKILL.md new file mode 100644 index 00000000..1371ea62 --- /dev/null +++ b/.kiro/skills/contextdb-autopilot/SKILL.md @@ -0,0 +1,104 @@ +--- +name: contextdb-autopilot +description: Use when running tasks in Codex CLI, Claude Code, Gemini CLI, or opencode and you need automatic context persistence (init/session/event/checkpoint/context-pack) plus interactive auto-routing without manual contextdb commands. +--- + +# ContextDB Autopilot + +## Overview +Use this skill to run a task with full filesystem context DB automation in one command. + +Script path: `scripts/ctx-agent.sh` +Runtime core: `scripts/ctx-agent-core.mjs` (single source for sh/mjs wrappers) + +## When to Use +- You want cross-CLI memory continuity (`codex`, `claude`, `gemini`, `opencode`) in the same project. +- You need zero-manual context DB operations per task run. +- You want each run to auto-write user event, assistant event, checkpoint, and refreshed context packet. +- You want interactive wrappers to auto-seed route-aware startup prompts so the agent can classify `single/subagent/team` and trigger AIOS route commands without user-entered trigger commands. + +## Required Pattern +Use one-shot mode (`--prompt`) for full automation. + +```bash +scripts/ctx-agent.sh --agent codex-cli --project rex-cli --prompt "继续上次任务,先做最小变更" +scripts/ctx-agent.sh --agent claude-code --project rex-cli --prompt "延续当前会话并输出下一步" +scripts/ctx-agent.sh --agent gemini-cli --project rex-cli --prompt "基于已有上下文继续执行" +scripts/ctx-agent.sh --agent opencode-cli --project rex-cli --prompt "继续当前任务并执行下一步" +``` + +## Session Control +- Continue same session: `--session <session_id>` +- Mark terminal step: `--status done` +- Disable auto bootstrap for current run: `--no-bootstrap` +- Disable checkpoint (rare): `--no-checkpoint` +- Disable auto bootstrap globally: `export AIOS_BOOTSTRAP_AUTO=0` +- `CODEX_HOME` can be relative; wrappers normalize it against current working directory at runtime. + +Interactive wrapper route defaults: +- Direct `codex`/`claude`/`gemini`/`opencode` startup now injects an auto-route prompt (`single/subagent/team`) by default. +- Route execution defaults to live for team/subagent command templates. +- `opencode` interactive flow falls back to a supported subagent runtime (`codex-cli` by default). +- Override subagent runtime for routed commands with `CTXDB_ROUTE_SUBAGENT_CLIENT=<codex-cli|claude-code|gemini-cli>`. + +Bootstrap note: +- On first run in a workspace, `ctx-agent` may auto-create `tasks/pending/task_<timestamp>_bootstrap_guidelines/*` and `tasks/.current-task` if both are empty. + +Example: +```bash +scripts/ctx-agent.sh \ + --agent codex-cli \ + --project rex-cli \ + --session codex-cli-20260303T010101-abcd1234 \ + --status done \ + --prompt "所有改动完成,给最终总结" +``` + +## Verification +- Context packet output: `memory/context-db/exports/<session_id>-context.md` +- Session files: `memory/context-db/sessions/<session_id>/` + +## New ContextDB Retrieval Commands + +Use these for index-first retrieval before full packet expansion: + +```bash +cd mcp-server +npm run contextdb -- search --query "auth race" --project rex-cli --kinds response --refs auth.ts +npm run contextdb -- timeline --session <session_id> --limit 30 +npm run contextdb -- event:get --id <session_id>#<seq> +npm run contextdb -- index:rebuild +``` + +Optional semantic rerank: + +```bash +export CONTEXTDB_SEMANTIC=1 +export CONTEXTDB_SEMANTIC_PROVIDER=token +npm run contextdb -- search --query "issue auth" --project rex-cli --semantic +``` + +Recovery: +- If retrieval returns empty unexpectedly, run `index:rebuild` once. +- Source-of-truth remains `memory/context-db/sessions/*`; sidecar is rebuildable cache. + +## Packet Budget and Filters + +Use token-aware packet export to control cost: + +```bash +cd mcp-server +npm run contextdb -- context:pack \ + --session <session_id> \ + --limit 60 \ + --token-budget 1200 \ + --token-strategy balanced \ + --kinds prompt,response,error \ + --refs core.ts,cli.ts +``` + +Defaults: +- Event dedupe in packet view is enabled. +- `--token-strategy` supports `legacy|balanced|aggressive` (recommended: `balanced`). +- You can disable with `--no-dedupe` when needed for debugging. +- For orchestrate/subagent runtime, you can set `AIOS_SUBAGENT_CONTEXT_TOKEN_STRATEGY=balanced`. diff --git a/.kiro/skills/debug-hub/.aios-skill-sync.json b/.kiro/skills/debug-hub/.aios-skill-sync.json new file mode 100644 index 00000000..5f1322ca --- /dev/null +++ b/.kiro/skills/debug-hub/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "debug-hub", + "targetSurface": "kiro", + "targetRelativePath": "debug-hub", + "source": "skill-sources/debug-hub" +} \ No newline at end of file diff --git a/.kiro/skills/debug-hub/SKILL.md b/.kiro/skills/debug-hub/SKILL.md new file mode 100644 index 00000000..91345aa5 --- /dev/null +++ b/.kiro/skills/debug-hub/SKILL.md @@ -0,0 +1,62 @@ +--- +name: debug-hub +description: Use when debugging a bug, investigating a regression, or diagnosing a runtime failure and you need runtime evidence before applying a fix +--- + +# Debug-Hub + +Evidence-first debugging: inject zero-dependency log calls, collect via debug-hub HTTP API, analyze through MCP tools, strip all injected code when done. + +## Inline Reporter + +Inject once at the top of the first file you modify. Node 18+ / modern browsers, zero npm deps: + +```js +const __dh=async(m,d)=>{try{await fetch('http://127.0.0.1:39200/api/logs/single',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:crypto.randomUUID(),timestamp:Date.now(),level:'debug',message:m,data:d,sdk:{name:'dh',version:'1',runtime:'node'}})})}catch{}} +``` + +## Marker Convention + +Every injected debug line carries `DH:<sessionId>`. Cleanup strips any line containing this string. + +```js +__dh('DH:sess-abc user state', {user}); +__dh('DH:sess-abc payment duration', {ms: Date.now()-t0}); +``` + +## MCP Tools + +| Tool | What it does | +|------|--------------| +| `debug_hub.start_session { objective }` | Start session, returns `sessionId` | +| `debug_hub.instrument { sessionId, files: [{path, lineCount}] }` | Track files with injected debug code | +| `debug_hub.search_logs { keyword, level, since, traceId }` | Query collected runtime logs | +| `debug_hub.get_stats` | Error counts, level breakdown | +| `debug_hub.cleanup_instruments { sessionId, dryRun?, workspace? }` | Remove all injected debug lines | + +Other tools: `list_traces`, `get_trace`, `compact_context`, `health` — see MCP tool list. + +## Workflow + +1. **Start**: `debug_hub.start_session { objective: "..." }` — note the `sessionId` +2. **Hypothesize**: 3-5 testable hypotheses about root cause +3. **Inject**: write `__dh` helper + marked log calls, one per hypothesis +4. **Record**: `debug_hub.instrument { sessionId, files: [{path, lineCount}] }` — use absolute paths +5. **Reproduce**: ask user to run the failing scenario +6. **Analyze**: `debug_hub.search_logs { keyword: "<sessionId>" }` → CONFIRMED/REJECTED/INCONCLUSIVE +7. **Fix**: apply only after logs prove root cause; keep instrumentation +8. **Verify**: user reproduces again, compare before/after +9. **Cleanup**: `dryRun: true` first, then execute `debug_hub.cleanup_instruments { sessionId }` + +Cleanup has two modes: +- **Explicit**: uses instrument records → fast, precise +- **Discovery**: `{ sessionId, workspace: "/project/root" }` → greps filesystem, fallback when agent forgot `instrument` + +## Guardrails + +- Never log secrets, tokens, passwords, API keys, PII, or session cookies +- 2-10 log calls max, each mapped to a hypothesis +- Always `dryRun: true` before executing cleanup +- Never remove instrumentation before post-fix verification succeeds +- Never leave injected code behind; the `__dh` helper and every call site must go +- `try/catch` in `__dh` = target app keeps running if debug-hub is down diff --git a/.kiro/skills/find-skills/.aios-skill-sync.json b/.kiro/skills/find-skills/.aios-skill-sync.json new file mode 100644 index 00000000..5297381c --- /dev/null +++ b/.kiro/skills/find-skills/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "find-skills", + "targetSurface": "kiro", + "targetRelativePath": "find-skills", + "source": "skill-sources/find-skills" +} \ No newline at end of file diff --git a/.kiro/skills/find-skills/SKILL.md b/.kiro/skills/find-skills/SKILL.md new file mode 100644 index 00000000..c797184e --- /dev/null +++ b/.kiro/skills/find-skills/SKILL.md @@ -0,0 +1,133 @@ +--- +name: find-skills +description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill. +--- + +# Find Skills + +This skill helps you discover and install skills from the open agent skills ecosystem. + +## When to Use This Skill + +Use this skill when the user: + +- Asks "how do I do X" where X might be a common task with an existing skill +- Says "find a skill for X" or "is there a skill for X" +- Asks "can you do X" where X is a specialized capability +- Expresses interest in extending agent capabilities +- Wants to search for tools, templates, or workflows +- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.) + +## What is the Skills CLI? + +The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools. + +**Key commands:** + +- `npx skills find [query]` - Search for skills interactively or by keyword +- `npx skills add <package>` - Install a skill from GitHub or other sources +- `npx skills check` - Check for skill updates +- `npx skills update` - Update all installed skills + +**Browse skills at:** https://skills.sh/ + +## How to Help Users Find Skills + +### Step 1: Understand What They Need + +When a user asks for help with something, identify: + +1. The domain (e.g., React, testing, design, deployment) +2. The specific task (e.g., writing tests, creating animations, reviewing PRs) +3. Whether this is a common enough task that a skill likely exists + +### Step 2: Search for Skills + +Run the find command with a relevant query: + +```bash +npx skills find [query] +``` + +For example: + +- User asks "how do I make my React app faster?" → `npx skills find react performance` +- User asks "can you help me with PR reviews?" → `npx skills find pr review` +- User asks "I need to create a changelog" → `npx skills find changelog` + +The command will return results like: + +``` +Install with npx skills add <owner/repo@skill> + +vercel-labs/agent-skills@vercel-react-best-practices +└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices +``` + +### Step 3: Present Options to the User + +When you find relevant skills, present them to the user with: + +1. The skill name and what it does +2. The install command they can run +3. A link to learn more at skills.sh + +Example response: + +``` +I found a skill that might help! The "vercel-react-best-practices" skill provides +React and Next.js performance optimization guidelines from Vercel Engineering. + +To install it: +npx skills add vercel-labs/agent-skills@vercel-react-best-practices + +Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices +``` + +### Step 4: Offer to Install + +If the user wants to proceed, you can install the skill for them: + +```bash +npx skills add <owner/repo@skill> -g -y +``` + +The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts. + +## Common Skill Categories + +When searching, consider these common categories: + +| Category | Example Queries | +| --------------- | ---------------------------------------- | +| Web Development | react, nextjs, typescript, css, tailwind | +| Testing | testing, jest, playwright, e2e | +| DevOps | deploy, docker, kubernetes, ci-cd | +| Documentation | docs, readme, changelog, api-docs | +| Code Quality | review, lint, refactor, best-practices | +| Design | ui, ux, design-system, accessibility | +| Productivity | workflow, automation, git | + +## Tips for Effective Searches + +1. **Use specific keywords**: "react testing" is better than just "testing" +2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd" +3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills` + +## When No Skills Are Found + +If no relevant skills exist: + +1. Acknowledge that no existing skill was found +2. Offer to help with the task directly using your general capabilities +3. Suggest the user could create their own skill with `npx skills init` + +Example: + +``` +I searched for skills related to "xyz" but didn't find any matches. +I can still help you with this task directly! Would you like me to proceed? + +If this is something you do often, you could create your own skill: +npx skills init my-xyz-skill +``` diff --git a/.kiro/skills/frontend-design/.aios-skill-sync.json b/.kiro/skills/frontend-design/.aios-skill-sync.json new file mode 100644 index 00000000..fa850d02 --- /dev/null +++ b/.kiro/skills/frontend-design/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "frontend-design", + "targetSurface": "kiro", + "targetRelativePath": "frontend-design", + "source": "skill-sources/frontend-design" +} \ No newline at end of file diff --git a/.kiro/skills/frontend-design/SKILL.md b/.kiro/skills/frontend-design/SKILL.md new file mode 100644 index 00000000..769e122b --- /dev/null +++ b/.kiro/skills/frontend-design/SKILL.md @@ -0,0 +1,105 @@ +--- +name: frontend-design +description: Use when users need to build or polish web UI without a complete design handoff, including cases where no mockup/design稿 is available and the agent must produce a strong visual direction plus production-ready code. +--- + +# Frontend Design + +Build distinctive, production-grade frontend UI with clear visual direction and avoid generic AI-looking interfaces. + +## Trigger + +- User asks to build or beautify pages/components/frontends. +- User has rough requirements but no design稿. +- Existing UI works functionally but looks generic. + +## Fuzzy Prompt Autopilot + +If user prompt is vague, do not stall. Convert it into one explicit mode, then build: + +- `Patch`: small element-level change (example: "改一下这个页面某个元素") +- `Restyle`: keep structure, change visual direction (example: "参考某种风格做一下") +- `Flow`: complete SaaS workflow UI (example: "做一个完整后台") + +Before coding, write a short assumption block: + +- `Goal`: what should improve now +- `Primary user`: role/persona +- `Platform`: web desktop first or mobile first +- `Scope`: this task edits one view / one module / full flow + +Only ask one clarifying question if a blocker is truly critical. Otherwise continue with reasonable defaults. + +## Operating Mode + +1. If `DESIGN.md` exists: treat it as style source of truth. +2. If `DESIGN.md` is missing: + - If network + `npx` are available, first use `awesome-design-md` workflow to install one baseline style. + - If not available, create a compact local `DESIGN.md` with: + - visual theme + - color roles + - typography pair + - spacing scale + - component states + - motion principles + +For `Restyle` and `Flow`, always establish `DESIGN.md` (remote baseline or local compact version) before implementation. + +## No-Design-Draft Protocol + +Before coding, lock these 6 choices in one short block: + +- `Audience`: consumer / prosumer / enterprise +- `Tone`: minimal / editorial / playful / cinematic / technical +- `Density`: airy / balanced / dense +- `Contrast`: soft / medium / high +- `Motion`: none / subtle / expressive +- `Memorable element`: one signature motif (shape, accent, type treatment, motion pattern) + +Do not proceed with implementation until these choices are explicit. + +## Delivery Contract by Mode + +For vague prompts, the implementation output must still be concrete: + +- `Patch`: + - Preserve layout intent, update only required scope. + - Include complete interaction states (default/hover/focus/active/disabled). + - Verify desktop + mobile behavior for touched area. +- `Restyle`: + - Keep information architecture stable unless explicitly changed. + - Apply a consistent style system (type/color/spacing/radius/shadow/motion). + - Avoid one-off visual overrides that bypass design tokens. +- `Flow`: + - Define SaaS flow map first: `entry -> navigation -> key tasks -> feedback -> empty/error`. + - Deliver connected screens, not isolated mock sections. + - Cover at least: dashboard, list, detail, create/edit form, settings/billing (or equivalent). + - Include loading/empty/error/success states for core actions. + +## Implementation Rules + +- Avoid generic defaults: Inter/Roboto + purple gradient + template layouts. +- Use CSS variables (or design tokens) for colors, spacing, radius, shadow, type. +- Keep hierarchy clear: first screen must communicate value + primary CTA. +- Match complexity to direction: + - maximal style -> richer motion/background systems + - minimal style -> stricter spacing/typography precision +- Maintain accessibility: + - keyboard focus visible + - text contrast adequate + - interaction states complete (hover/focus/active/disabled) + +## Quality Checklist + +- Visual style is recognizable in one glance. +- Typography scale is coherent across sections. +- Color accents are intentional, not scattered. +- Layout rhythm is consistent on desktop and mobile. +- UI is production-functional, not just a static mockup. + +## Prompt Pattern + +- `Implement this UI using DESIGN.md as the style contract.` +- `If DESIGN.md is missing, establish a 6-choice design direction block first, then build.` +- `Prioritize visual distinctiveness and usability; avoid generic template aesthetics.` +- `When prompt is vague, classify into Patch/Restyle/Flow, state assumptions briefly, then implement end-to-end.` diff --git a/.kiro/skills/frontend-design/UPSTREAM.md b/.kiro/skills/frontend-design/UPSTREAM.md new file mode 100644 index 00000000..6eac54a9 --- /dev/null +++ b/.kiro/skills/frontend-design/UPSTREAM.md @@ -0,0 +1,14 @@ +# Upstream Reference + +This skill is an AIOS adaptation inspired by Anthropic's `frontend-design` skill. + +- Upstream repository: `https://github.com/anthropics/skills` +- Upstream path: `skills/frontend-design/SKILL.md` +- Upstream license file: `skills/frontend-design/LICENSE.txt` (Apache License 2.0) +- Retrieved: 2026-04-18 + +Adaptation goals: + +- Add explicit no-design-draft protocol for users without design handoff. +- Add `DESIGN.md`-first integration path with `awesome-design-md`. +- Keep output focused on production-ready, distinctive, and accessible UI. diff --git a/.kiro/skills/harness-init-runner/.aios-skill-sync.json b/.kiro/skills/harness-init-runner/.aios-skill-sync.json new file mode 100644 index 00000000..2908758b --- /dev/null +++ b/.kiro/skills/harness-init-runner/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "harness-init-runner", + "targetSurface": "kiro", + "targetRelativePath": "harness-init-runner", + "source": "skill-sources/harness-init-runner" +} \ No newline at end of file diff --git a/.kiro/skills/harness-init-runner/SKILL.md b/.kiro/skills/harness-init-runner/SKILL.md new file mode 100644 index 00000000..a79cf05d --- /dev/null +++ b/.kiro/skills/harness-init-runner/SKILL.md @@ -0,0 +1,56 @@ +--- +name: harness-init-runner +description: Initialize a lightweight Node.js long-running agent harness (harness/ + .harness/) with a cross-provider runner that can drive codex/claude/gemini/opencode CLIs, capturing prompts, logs, and checkpoints. +--- + +# Harness Init Runner (Node.js) + +Create a portable long-running-agent harness inside the **current repo** (Node.js), without pulling in the full AIOS workspace layout. + +## When to use (even if AIOS is installed) + +- Use this when you want a **repo-local**, lightweight `harness/` runner that works across Codex / Claude Code / Gemini / opencode. +- If you already use AIOS, you may not need this. However it can coexist: this skill creates `harness/` + `/.harness/` and does not depend on `scripts/aios.mjs`. + +## What this skill generates (repo root) + +- `harness/` (runner code) +- `harness.config.json` (provider command templates) +- `/.harness/` (runtime artifacts root; gitignored) +- `package.json` scripts: `harness:run`, `harness:doctor` +- dependency: `zod` + +Runtime artifacts are written under `./.harness/runs/*` and must not be committed. + +## Preconditions + +- Run from (or inside) a Node.js repo that has a `package.json`. +- If `package.json` is missing, stop and ask the user whether to create a Node project first. + +## Init Steps (deterministic) + +1. Locate repo root by searching upward for `package.json`. +2. Copy the bundled templates from `assets/template/` into the target repo root: + - copy `assets/template/harness/` → `<repoRoot>/harness/` + - copy `assets/template/harness.config.json` → `<repoRoot>/harness.config.json` (do not overwrite if user has edits; merge instead) +3. Append `/.harness/` to `<repoRoot>/.gitignore` (create file if missing). +4. Update `<repoRoot>/package.json` (additive only): + - Add scripts: + - `harness:run`: `node harness/run.mjs` + - `harness:doctor`: `node harness/doctor.mjs` + - Add dependency `zod` (use `dependencies` unless the repo clearly wants `devDependencies`). +5. Run `npm install`. +6. Verify: + - `npm run harness:doctor` + - `npm run harness:run -- --provider codex --task "hello harness"` + +## Safety defaults + +- The runner performs a lightweight “human gate” check on the task text for auth/payment/policy + sensitive command keywords. +- If blocked, it exits with a non-zero code and prints reasons. +- Operator can bypass using `--allow-risk`. + +## Notes for multi-client compatibility + +- The runner shells out to provider CLIs; exact CLI flags vary by tool/version. +- Default provider configs are intentionally minimal; users should adjust `harness.config.json` for their environment. diff --git a/.kiro/skills/harness-init-runner/assets/template/gitignore-snippet.txt b/.kiro/skills/harness-init-runner/assets/template/gitignore-snippet.txt new file mode 100644 index 00000000..4d5567a8 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/gitignore-snippet.txt @@ -0,0 +1,2 @@ +/.harness/ + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness.config.json b/.kiro/skills/harness-init-runner/assets/template/harness.config.json new file mode 100644 index 00000000..01bb56d2 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness.config.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "runRootDir": ".harness", + "runsDir": "runs", + "humanGate": { + "enabled": true + }, + "providers": { + "codex": { + "cmd": "codex", + "args": [], + "stdin": true, + "output": "text", + "env": {} + }, + "claude": { + "cmd": "claude", + "args": [], + "stdin": true, + "output": "text", + "env": {} + }, + "gemini": { + "cmd": "gemini", + "args": [], + "stdin": true, + "output": "text", + "env": {} + }, + "opencode": { + "cmd": "opencode", + "args": [], + "stdin": true, + "output": "text", + "env": {} + } + } +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/config.schema.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/config.schema.mjs new file mode 100644 index 00000000..ee77ddf3 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/config.schema.mjs @@ -0,0 +1,33 @@ +import fs from 'node:fs/promises'; +import { z } from 'zod'; + +import { defaultProviders } from './providers/index.mjs'; + +const ProviderSchema = z.object({ + cmd: z.string().min(1), + args: z.array(z.string()).default([]), + stdin: z.boolean().default(true), + output: z.enum(['text', 'json']).default('text'), + env: z.record(z.string()).default({}), +}); + +export const HarnessConfigSchema = z.object({ + schemaVersion: z.number().int().positive().default(1), + runRootDir: z.string().min(1).default('.harness'), + runsDir: z.string().min(1).default('runs'), + humanGate: z.object({ + enabled: z.boolean().default(true), + }).default({ enabled: true }), + providers: z.record(ProviderSchema).default(() => defaultProviders()), +}); + +export async function loadHarnessConfig({ configPath } = {}) { + const rawText = await fs.readFile(configPath, 'utf8'); + const json = JSON.parse(rawText); + const parsed = HarnessConfigSchema.safeParse(json); + if (!parsed.success) { + const message = parsed.error.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`).join('\n'); + throw new Error(`Invalid harness config at ${configPath}:\n${message}`); + } + return parsed.data; +} diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/doctor.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/doctor.mjs new file mode 100644 index 00000000..749dd0f5 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/doctor.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +import path from 'node:path'; +import { spawn } from 'node:child_process'; + +import { loadHarnessConfig } from './config.schema.mjs'; +import { findRepoRoot } from './lib/paths.mjs'; + +function spawnOnce(cmd, args, { cwd, env } = {}) { + return new Promise((resolve) => { + const child = spawn(cmd, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] }); + let stderr = ''; + child.stderr.on('data', (chunk) => { + stderr += chunk.toString('utf8'); + if (stderr.length > 2000) { + stderr = `${stderr.slice(0, 1999)}…`; + } + }); + child.on('error', (error) => resolve({ ok: false, error })); + child.on('close', (code) => resolve({ ok: true, code, stderr })); + }); +} + +async function main() { + const repoRoot = findRepoRoot(process.cwd()); + const configPath = path.join(repoRoot, 'harness.config.json'); + const config = await loadHarnessConfig({ configPath }); + + const providers = Object.entries(config.providers || {}); + if (providers.length === 0) { + process.stderr.write('[harness] no providers configured in harness.config.json\n'); + process.exitCode = 1; + return; + } + + let ok = true; + for (const [providerId, provider] of providers) { + const cmd = String(provider?.cmd || '').trim(); + if (!cmd) { + ok = false; + process.stderr.write(`[harness] provider ${providerId}: missing cmd\n`); + continue; + } + const result = await spawnOnce(cmd, ['--help'], { cwd: repoRoot, env: process.env }); + if (!result.ok) { + ok = false; + const code = result.error?.code ? ` code=${result.error.code}` : ''; + process.stderr.write(`[harness] provider ${providerId}: failed to spawn "${cmd}"${code}\n`); + continue; + } + process.stdout.write(`[harness] provider ${providerId}: cmd="${cmd}" ok (exit=${result.code})\n`); + } + + if (!ok) { + process.stderr.write('[harness] doctor failed. Fix harness.config.json provider commands and try again.\n'); + process.exitCode = 1; + } +} + +main().catch((err) => { + process.stderr.write(`[harness] doctor error: ${err?.stack || err?.message || String(err)}\n`); + process.exitCode = 1; +}); + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/lib/checkpoint.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/lib/checkpoint.mjs new file mode 100644 index 00000000..c20c6a93 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/lib/checkpoint.mjs @@ -0,0 +1,58 @@ +import path from 'node:path'; + +import { writeText } from './io.mjs'; + +export async function writeCheckpointArtifacts({ + runDir, + providerId, + taskText, + exitCode, + elapsedMs, +} = {}) { + const checkpointJsonPath = path.join(runDir, 'checkpoint.json'); + const checkpointMdPath = path.join(runDir, 'checkpoint.md'); + const now = new Date().toISOString(); + + const checkpoint = { + schemaVersion: 1, + ts: now, + status: exitCode === 0 ? 'completed' : 'failed', + provider: providerId, + taskSummary: String(taskText || '').slice(0, 280), + exitCode, + elapsedMs, + nextActions: [ + 'Review stdout.txt and stderr.txt', + 'If needed, refine harness.config.json provider args', + 'Rerun with a narrower task or add a manual plan checkpoint', + ], + artifacts: [ + 'prompt.md', + 'stdout.txt', + 'stderr.txt', + 'run.json', + ], + }; + + await writeText(checkpointJsonPath, `${JSON.stringify(checkpoint, null, 2)}\n`); + await writeText(checkpointMdPath, [ + `# Checkpoint (${checkpoint.status})`, + '', + `- Time: ${checkpoint.ts}`, + `- Provider: ${checkpoint.provider}`, + `- Exit: ${checkpoint.exitCode}`, + `- Elapsed: ${checkpoint.elapsedMs}ms`, + '', + '## Task', + '', + taskText, + '', + '## Next Actions', + checkpoint.nextActions.map((item) => `- ${item}`).join('\n'), + '', + '## Artifacts', + checkpoint.artifacts.map((item) => `- ${item}`).join('\n'), + '', + ].join('\n')); +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/lib/human-gate.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/lib/human-gate.mjs new file mode 100644 index 00000000..d558abad --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/lib/human-gate.mjs @@ -0,0 +1,44 @@ +const DEFAULT_BOUNDARY_PATTERNS = Object.freeze([ + { label: 'auth boundary', pattern: /\b(auth|authentication|authorize|authorization|login|oauth|token|credential|api[- ]?key|session cookie|secret)\b/i }, + { label: 'payment boundary', pattern: /\b(payment|billing|invoice|charge|refund|payout|stripe|paypal|card)\b/i }, + { label: 'policy boundary', pattern: /\b(policy|compliance|privacy|legal|regulation|gdpr|hipaa|soc2|pci)\b/i }, +]); + +const DEFAULT_SENSITIVE_COMMAND_PATTERNS = Object.freeze([ + { label: 'sudo command', pattern: /\bsudo\s+\S+/i }, + { label: 'rm -rf command', pattern: /\brm\s+-rf\b/i }, + { label: 'git push', pattern: /\bgit\s+push\b/i }, + { label: 'npm publish', pattern: /\bnpm\s+publish\b/i }, +]); + +function normalizeText(value) { + return String(value ?? '').replace(/\s+/g, ' ').trim(); +} + +export function evaluateHumanGate({ taskText, enabled = true, allowRisk = false } = {}) { + const text = normalizeText(taskText); + if (!enabled) { + return { allowed: true, reasons: [] }; + } + if (allowRisk) { + return { allowed: true, reasons: [] }; + } + const reasons = []; + + for (const boundary of DEFAULT_BOUNDARY_PATTERNS) { + if (boundary.pattern.test(text)) { + reasons.push(`potential ${boundary.label} detected in task text`); + } + } + for (const command of DEFAULT_SENSITIVE_COMMAND_PATTERNS) { + if (command.pattern.test(text)) { + reasons.push(`potential ${command.label} detected in task text`); + } + } + + return { + allowed: reasons.length === 0, + reasons, + }; +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/lib/io.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/lib/io.mjs new file mode 100644 index 00000000..ecb71188 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/lib/io.mjs @@ -0,0 +1,12 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +export async function ensureDir(dirPath) { + await fs.mkdir(dirPath, { recursive: true }); +} + +export async function writeText(filePath, text) { + await ensureDir(path.dirname(filePath)); + await fs.writeFile(filePath, text, 'utf8'); +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/lib/paths.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/lib/paths.mjs new file mode 100644 index 00000000..5346a9a5 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/lib/paths.mjs @@ -0,0 +1,39 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +export function findRepoRoot(startDir = process.cwd()) { + let current = path.resolve(startDir); + while (true) { + const candidate = path.join(current, 'package.json'); + if (fs.existsSync(candidate)) { + return current; + } + const parent = path.dirname(current); + if (parent === current) { + return path.resolve(startDir); + } + current = parent; + } +} + +export function formatRunTimestamp(date) { + const iso = date.toISOString(); + return iso.replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z'); +} + +export function slugify(value, { maxLength = 48 } = {}) { + const normalized = String(value || '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .trim(); + const fallback = normalized || 'run'; + return fallback.length <= maxLength ? fallback : fallback.slice(0, maxLength); +} + +export function resolveRunRoot({ repoRoot, config }) { + const rootName = String(config?.runRootDir || '.harness').trim() || '.harness'; + const runsName = String(config?.runsDir || 'runs').trim() || 'runs'; + return path.join(repoRoot, rootName, runsName); +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/providers/claude.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/providers/claude.mjs new file mode 100644 index 00000000..e1d93836 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/providers/claude.mjs @@ -0,0 +1,10 @@ +export function defaultClaudeProvider() { + return { + cmd: 'claude', + args: [], + stdin: true, + output: 'text', + env: {}, + }; +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/providers/codex.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/providers/codex.mjs new file mode 100644 index 00000000..e892145c --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/providers/codex.mjs @@ -0,0 +1,10 @@ +export function defaultCodexProvider() { + return { + cmd: 'codex', + args: [], + stdin: true, + output: 'text', + env: {}, + }; +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/providers/gemini.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/providers/gemini.mjs new file mode 100644 index 00000000..cec82a0e --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/providers/gemini.mjs @@ -0,0 +1,10 @@ +export function defaultGeminiProvider() { + return { + cmd: 'gemini', + args: [], + stdin: true, + output: 'text', + env: {}, + }; +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/providers/index.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/providers/index.mjs new file mode 100644 index 00000000..2c3b97cf --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/providers/index.mjs @@ -0,0 +1,14 @@ +import { defaultCodexProvider } from './codex.mjs'; +import { defaultClaudeProvider } from './claude.mjs'; +import { defaultGeminiProvider } from './gemini.mjs'; +import { defaultOpencodeProvider } from './opencode.mjs'; + +export function defaultProviders() { + return { + codex: defaultCodexProvider(), + claude: defaultClaudeProvider(), + gemini: defaultGeminiProvider(), + opencode: defaultOpencodeProvider(), + }; +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/providers/opencode.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/providers/opencode.mjs new file mode 100644 index 00000000..46102c85 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/providers/opencode.mjs @@ -0,0 +1,10 @@ +export function defaultOpencodeProvider() { + return { + cmd: 'opencode', + args: [], + stdin: true, + output: 'text', + env: {}, + }; +} + diff --git a/.kiro/skills/harness-init-runner/assets/template/harness/run.mjs b/.kiro/skills/harness-init-runner/assets/template/harness/run.mjs new file mode 100644 index 00000000..bcbec362 --- /dev/null +++ b/.kiro/skills/harness-init-runner/assets/template/harness/run.mjs @@ -0,0 +1,215 @@ +#!/usr/bin/env node +import fsSync from 'node:fs'; +import fs from 'node:fs/promises'; +import { once } from 'node:events'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; + +import { loadHarnessConfig } from './config.schema.mjs'; +import { ensureDir, writeText } from './lib/io.mjs'; +import { writeCheckpointArtifacts } from './lib/checkpoint.mjs'; +import { evaluateHumanGate } from './lib/human-gate.mjs'; +import { findRepoRoot, formatRunTimestamp, slugify, resolveRunRoot } from './lib/paths.mjs'; + +function parseArgs(argv) { + const args = { provider: '', task: '', name: '', config: '', allowRisk: false }; + const raw = argv.slice(2); + for (let i = 0; i < raw.length; i += 1) { + const token = raw[i]; + if (token === '--provider' && raw[i + 1]) { + args.provider = raw[i + 1]; + i += 1; + continue; + } + if (token === '--task' && raw[i + 1]) { + args.task = raw[i + 1]; + i += 1; + continue; + } + if (token === '--name' && raw[i + 1]) { + args.name = raw[i + 1]; + i += 1; + continue; + } + if (token === '--config' && raw[i + 1]) { + args.config = raw[i + 1]; + i += 1; + continue; + } + if (token === '--allow-risk') { + args.allowRisk = true; + continue; + } + if (token === '--help' || token === '-h') { + return { ...args, help: true }; + } + } + return args; +} + +async function readStdinText() { + try { + return await fs.readFile(0, 'utf8'); + } catch { + return ''; + } +} + +function expandPlaceholders(value, vars) { + return String(value || '').replace(/\$\{(\w+)\}/g, (_, key) => String(vars[key] ?? '')); +} + +function buildProviderCommand(providerConfig, vars) { + const cmd = String(providerConfig?.cmd || '').trim(); + const args = Array.isArray(providerConfig?.args) ? providerConfig.args.map((arg) => expandPlaceholders(arg, vars)) : []; + const env = providerConfig?.env && typeof providerConfig.env === 'object' ? providerConfig.env : {}; + const stdin = Boolean(providerConfig?.stdin); + const output = providerConfig?.output === 'json' ? 'json' : 'text'; + return { cmd, args, env, stdin, output }; +} + +function usage() { + return [ + 'Usage:', + ' node harness/run.mjs --provider <codex|claude|gemini|opencode|kiro> [--task "…"] [--name "…"] [--config path] [--allow-risk]', + '', + 'Notes:', + ' - If --task is omitted, the runner reads task text from stdin.', + ' - Runtime artifacts are written under ./.harness/runs/* by default.', + ].join('\n'); +} + +async function main() { + const args = parseArgs(process.argv); + if (args.help) { + process.stdout.write(`${usage()}\n`); + return; + } + + const repoRoot = findRepoRoot(process.cwd()); + const configPath = args.config + ? path.resolve(process.cwd(), args.config) + : path.join(repoRoot, 'harness.config.json'); + const config = await loadHarnessConfig({ configPath }); + + const providerId = String(args.provider || '').trim(); + if (!providerId) { + throw new Error('Missing required --provider'); + } + + const taskText = String(args.task || '').trim() || (await readStdinText()).trim(); + if (!taskText) { + throw new Error('Missing task text. Provide --task "..." or pipe text via stdin.'); + } + + const gate = evaluateHumanGate({ taskText, enabled: config.humanGate.enabled, allowRisk: args.allowRisk }); + if (!gate.allowed) { + process.stderr.write('[harness] blocked by human gate:\n'); + for (const reason of gate.reasons) { + process.stderr.write(`- ${reason}\n`); + } + process.stderr.write('Use --allow-risk to proceed.\n'); + process.exitCode = 2; + return; + } + + const runTimestamp = formatRunTimestamp(new Date()); + const slug = slugify(args.name || taskText, { maxLength: 48 }); + const runRoot = resolveRunRoot({ repoRoot, config }); + const runDir = path.join(runRoot, `${runTimestamp}-${providerId}-${slug}`); + await ensureDir(runDir); + + const promptFile = path.join(runDir, 'prompt.md'); + const stdoutFile = path.join(runDir, 'stdout.txt'); + const stderrFile = path.join(runDir, 'stderr.txt'); + const runMetaFile = path.join(runDir, 'run.json'); + + const prompt = [ + '# Task', + '', + taskText, + '', + '---', + '', + '## Harness Notes', + '', + '- Keep output concise and actionable.', + '- If the task is ambiguous or blocked on auth/payment/policy actions, say so clearly.', + ].join('\n'); + await writeText(promptFile, `${prompt}\n`); + + const providerConfig = config.providers[providerId]; + if (!providerConfig) { + throw new Error(`Unknown provider "${providerId}". Configure it in harness.config.json under providers.${providerId}`); + } + + const vars = { + repoRoot, + runDir, + promptFile, + }; + const command = buildProviderCommand(providerConfig, vars); + if (!command.cmd) { + throw new Error(`Provider "${providerId}" is missing cmd in harness.config.json`); + } + + const startedAt = Date.now(); + const child = spawn(command.cmd, command.args, { + cwd: repoRoot, + env: { ...process.env, ...command.env }, + stdio: ['pipe', 'pipe', 'pipe'], + }); + + const stdoutStream = fsSync.createWriteStream(stdoutFile, { flags: 'w' }); + const stderrStream = fsSync.createWriteStream(stderrFile, { flags: 'w' }); + child.stdout.pipe(stdoutStream); + child.stderr.pipe(stderrStream); + + if (command.stdin) { + child.stdin.write(`${prompt}\n`); + child.stdin.end(); + } else { + child.stdin.end(); + } + + const exitCode = await new Promise((resolve, reject) => { + child.on('error', reject); + child.on('close', resolve); + }); + + stdoutStream.end(); + stderrStream.end(); + await Promise.allSettled([ + once(stdoutStream, 'close'), + once(stderrStream, 'close'), + ]); + + const elapsedMs = Date.now() - startedAt; + const runMeta = { + schemaVersion: 1, + provider: providerId, + cmd: command.cmd, + args: command.args, + cwd: repoRoot, + exitCode, + elapsedMs, + startedAt: new Date(startedAt).toISOString(), + finishedAt: new Date().toISOString(), + }; + await writeText(runMetaFile, `${JSON.stringify(runMeta, null, 2)}\n`); + + await writeCheckpointArtifacts({ + runDir, + providerId, + taskText, + exitCode, + elapsedMs, + }); + + process.stdout.write(`[harness] run saved: ${path.relative(repoRoot, runDir)}\n`); +} + +main().catch((err) => { + process.stderr.write(`[harness] error: ${err?.stack || err?.message || String(err)}\n`); + process.exitCode = 1; +}); diff --git a/.kiro/skills/model-router/.aios-skill-sync.json b/.kiro/skills/model-router/.aios-skill-sync.json new file mode 100644 index 00000000..f5d01982 --- /dev/null +++ b/.kiro/skills/model-router/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "model-router", + "targetSurface": "kiro", + "targetRelativePath": "model-router", + "source": "skill-sources/model-router" +} \ No newline at end of file diff --git a/.kiro/skills/model-router/SKILL.md b/.kiro/skills/model-router/SKILL.md new file mode 100644 index 00000000..2c1a85dc --- /dev/null +++ b/.kiro/skills/model-router/SKILL.md @@ -0,0 +1,95 @@ +--- +name: model-router +description: "当需要调度不同模型执行子任务时使用。根据任务类型匹配模型能力,自动选择最优模型并生成调用指令。TRIGGER: 模型调度、model dispatch、选模型、分派任务、多模型协作、路由到模型" +--- + +# Model Router + +你是 Agent Team 的调度中枢。阅读模型能力表,根据子任务类型匹配最优模型。 + +## CLI 协议 + +| 协议 | CLI | 使用场景 | +|------|-----|---------| +| **codex** | `codex exec --dangerously-bypass-approvals-and-sandbox -m <model> "<prompt>"` | 仅 GPT-5.5 | +| **gemini** | `gemini -m gemini-3-pro -p "<prompt>"` | 仅 Gemini-3-Pro | +| **claude** | `claude --model <model> -p "<prompt>"` | 其余所有模型 | + +## 模型能力表 + +| 模型 | 协议 | 最擅长 | 成本 | +|------|------|--------|------| +| **Claude Opus 4.7** | claude | 代码审查、架构设计、安全审计 | 最高 | +| **Claude Sonnet 4.6** | claude | 日常开发、RAG、快速原型 | 中 | +| **GPT-5.5** | codex | 六边形战士,通用推理、浏览器自动化、代码执行全能 | 最高 | +| **DeepSeek-V4-Pro** | claude | 算法实现、核心逻辑、批处理 | 最低 | +| **GLM-5.1** | claude | 数学推理、自主循环、系统规划 | 低 | +| **Kimi K2.6** | claude | 多Agent编排、前端UI、长周期执行 | 低 | +| **MiniMax-M2.7** | claude | 自愈运维、生产恢复 | 低 | +| **Gemini-3-Pro** | gemini | 多模态分析、长文档研究、1M上下文 | 中 | + +## 路由规则 + +| 任务类型 | 首选模型 | 降级链 | +|----------|----------|--------| +| 代码审查 | **Claude Opus** | GPT-5.5 → GLM-5.1 | +| 安全审计 | **Claude Opus** | GPT-5.5 → GLM-5.1 | +| 架构设计 | **Claude Opus** | GPT-5.5 → GLM-5.1 | +| 写代码/实现 | **DeepSeek-V4** | GPT-5.5 → Claude Sonnet | +| 浏览器自动化 | **GPT-5.5** | Kimi K2.6 → Claude Sonnet | +| 调研/长文档 | **Gemini-3-Pro** | GPT-5.5 → Kimi K2.6 | +| 规划/方案 | **GLM-5.1** | GPT-5.5 → Claude Opus | +| 测试/QA | **Claude Sonnet** | GPT-5.5 → DeepSeek-V4 | +| 文档/README | **Claude Sonnet** | GPT-5.5 → Kimi K2.6 | +| 前端/UI | **Kimi K2.6** | GPT-5.5 → Claude Sonnet | +| 故障恢复 | **MiniMax-M2.7** | GLM-5.1 → GPT-5.5 | +| 通用兜底 | **GPT-5.5** | Claude Sonnet → DeepSeek-V4 | + +## Agent Team 集成 + +- `aios team` / `aios orchestrate --dispatch local --execute live` 默认启用 per-phase model routing,并为 planner / implementer / reviewer / security-reviewer 分别解析模型。 +- 每个 phase job 的 `launchSpec.modelRouting` 会包含 `role`、`taskType`、`modelId`、`provider`、`clientId`、`cliCommand`、`reason` 和 `fallback`,merge-gate 保持 `requiresModel=false`。 +- live subagent / GroupChat 运行时会按 `clientId` 切换 CLI 协议并附加模型参数;Codex 子进程默认附加 `--dangerously-bypass-approvals-and-sandbox`,避免后台 approval/sandbox prompt 卡死;同时在 prompt 中加入 `## Model Router` 段落,便于子 Agent 自检。 +- 每个 phase / speaker 完成或阻塞后写入 ContextDB `kind=model.dispatch` 事件,`turn.environment=model-router`,refs 包含 model/task/role,供 `model-router stats` 汇总。 +- 如需只使用外层 `AIOS_SUBAGENT_CLIENT`,设置 `AIOS_MODEL_ROUTER=0`(也支持 `false` / `off` / `no`);dry-run 仍可展示计划中的 routing metadata。 + +## 决策流程 + +1. 分析子任务类型(写代码?审查?研究?规划?) +2. 按路由规则选首选模型 +3. 按 CLI 协议生成命令派发 +4. 记录结果:`node scripts/aios.mjs model-router stats` + +## 降级策略 + +首选模型不可用时: +1. 按降级链依次尝试 +2. 全部失败则按成本从低到高:DeepSeek → Kimi → MiniMax → GLM → Sonnet → Gemini → GPT-5.5 → Opus + +## 命令工具 + +```bash +node scripts/aios.mjs model-router list # 查看注册表 +node scripts/aios.mjs model-router route --task "..." # 路由决策 +node scripts/aios.mjs model-router stats # 调度统计 +``` + +## 环境变量 + +```bash +export AIOS_MODEL_ROUTER=0 # 关闭 live 执行期模型覆盖;metadata 仍可生成 +export AIOS_MODEL_PLANNER=claude-opus # 按角色覆盖 +export AIOS_MODEL_IMPLEMENTER=deepseek-v4 +export AIOS_MODEL_REVIEWER=claude-opus +export AIOS_MODEL_SECURITY_REVIEWER=claude-opus +export AIOS_MODEL_CODE_REVIEW=claude-opus # 按任务类型覆盖 +export AIOS_MODEL_IMPLEMENTATION=deepseek-v4 +export AIOS_MODEL_PLANNING=glm-5.1 +export AIOS_SUBAGENT_CODEX_UNATTENDED=0 # 关闭 Codex 子进程 yolo/bypass(默认开启) +``` + +## 注意 + +- Orchestrator 是你当前正在使用的 coding agent(不固定为某个模型) +- 你负责拆解任务和选模型,CLI 命令由路由表自动生成 +- 所有交付代码默认经过审查模型把关 diff --git a/.kiro/skills/search-first/.aios-skill-sync.json b/.kiro/skills/search-first/.aios-skill-sync.json new file mode 100644 index 00000000..688f9a50 --- /dev/null +++ b/.kiro/skills/search-first/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "search-first", + "targetSurface": "kiro", + "targetRelativePath": "search-first", + "source": "skill-sources/search-first" +} \ No newline at end of file diff --git a/.kiro/skills/search-first/SKILL.md b/.kiro/skills/search-first/SKILL.md new file mode 100644 index 00000000..3b2acd67 --- /dev/null +++ b/.kiro/skills/search-first/SKILL.md @@ -0,0 +1,47 @@ +--- +name: search-first +description: Research-before-building workflow. Use when adding new behavior, dependencies, or integrations to avoid reinventing and to stay compatible across codex/claude/gemini/opencode. +--- + +# Search First + +## Trigger +Use this skill before you: +- Add a new feature or workflow step +- Introduce a new dependency, MCP server, or CLI integration +- Write a new utility/helper that might already exist +- Copy patterns from another repo (porting) + +## Outcome +Pick one of: **Adopt**, **Extend**, **Compose**, **Build**, with a short justification and evidence. + +## Workflow (Fast) +1. **Define the need** + - What is the smallest outcome that solves the problem? + - Constraints: platform (macOS/Linux/Windows), clients (codex/claude/gemini/opencode), offline needs, security posture. + +2. **Search locally (before the internet)** + - Code: `rg -n "<keyword>"` in the repo. + - Scripts: check `scripts/` for existing lifecycle/doctor commands. + - Docs: check `README*` and `docs/`. + +3. **Search existing skills** + - Repo-local: `.codex/skills`, `.claude/skills`, `.agents/skills`. + - Global: `~/.codex/skills`, `~/.claude/skills`, `~/.gemini/skills`, `~/.config/opencode/skills`. + - Prefer reusing an existing skill name over creating a near-duplicate. + +4. **Search the ecosystem (when needed)** + - Prefer official docs and primary sources (project docs, RFCs, standards). + - Prefer maintained, well-scoped libraries over bespoke code. + - For agent tooling, check if an MCP server already provides the capability. + +5. **Decide** + - **Adopt**: exact match, maintained, acceptable license. + - **Extend**: mostly fits; add thin wrapper/adapter. + - **Compose**: 2-3 small pieces together beat 1 big framework. + - **Build**: no good option; implement minimal + document why. + +6. **Record evidence** + - Note what you searched, what you found, and why you chose the approach. + - If a decision affects cross-CLI behavior, add it to a `docs/plans/` artifact. + diff --git a/.kiro/skills/security-scan/.aios-skill-sync.json b/.kiro/skills/security-scan/.aios-skill-sync.json new file mode 100644 index 00000000..bd01f20d --- /dev/null +++ b/.kiro/skills/security-scan/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "security-scan", + "targetSurface": "kiro", + "targetRelativePath": "security-scan", + "source": "skill-sources/security-scan" +} \ No newline at end of file diff --git a/.kiro/skills/security-scan/SKILL.md b/.kiro/skills/security-scan/SKILL.md new file mode 100644 index 00000000..23cd39be --- /dev/null +++ b/.kiro/skills/security-scan/SKILL.md @@ -0,0 +1,48 @@ +--- +name: security-scan +description: Lightweight security hygiene for agent configs (skills/hooks/MCP/settings). Use before enabling automation, after config changes, and before release. +--- + +# Security Scan + +## Trigger +Use this skill when: +- Onboarding a new repo with agent automation enabled +- Editing skills, hooks, MCP config, or client settings +- Before publishing/releasing changes that affect other projects + +## Mandatory Privacy Rule (No Bypass) +- For config-like files that may contain keys/tokens/passwords, **do not read raw file content directly**. +- Always read through Privacy Guard first: + - `aios privacy read --file <path>` (preferred) + - or `node scripts/privacy-guard.mjs read --file <path>` +- If Privacy Guard reports guard-disabled for sensitive files, enable it first: + - `aios privacy enable` + +## Quick Path +- Ensure strict mode is on: `aios privacy enforce-on` +- Run the repo verifier: `aios doctor` (preferred when shell integration is installed). +- Or run: `scripts/doctor-security-config.sh` / `scripts/doctor-security-config.ps1`. + +## What To Check (Minimum) +1. **Secrets** + - No API keys/tokens/cookies committed to git. + - Prefer env vars + local-only config files ignored by git. + - Use Privacy Guard read output when inspection is required. + - If scanning finds secrets, rotate them, then remove from history if needed. + +2. **Tool/permission scope** + - Avoid wildcard allowlists (when the client supports allow/deny config). + - Prefer least-privilege tool access and explicit denies for destructive actions. + +3. **Hooks safety** + - Avoid `curl | bash`, silent error suppression, and untrusted string interpolation. + - Treat any network egress from hooks as high risk; make it opt-in. + +4. **MCP / supply chain** + - Prefer pinned versions and explicit installs over ad-hoc `npx` execution. + - Review MCP servers for network + filesystem access and log behavior. + +## Output Discipline +- Never paste secret values into chat logs or commits. +- Capture only redacted findings + exact file paths and remediation steps. diff --git a/.kiro/skills/seed2-manga-drama/.aios-skill-sync.json b/.kiro/skills/seed2-manga-drama/.aios-skill-sync.json new file mode 100644 index 00000000..63696889 --- /dev/null +++ b/.kiro/skills/seed2-manga-drama/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "seed2-manga-drama", + "targetSurface": "kiro", + "targetRelativePath": "seed2-manga-drama", + "source": "skill-sources/seed2-manga-drama" +} \ No newline at end of file diff --git a/.kiro/skills/seed2-manga-drama/SKILL.md b/.kiro/skills/seed2-manga-drama/SKILL.md new file mode 100644 index 00000000..7a55a371 --- /dev/null +++ b/.kiro/skills/seed2-manga-drama/SKILL.md @@ -0,0 +1,70 @@ +--- +name: seed2-manga-drama +description: 当用户要把单图或创意脚本做成AI漫剧短视频时使用。提供Seed2.0风格的四阶段流程:分镜脚本、主角设定、分镜生成、视频验收,并输出可直接投喂生成模型的结构化提示词。 +--- + +# Seed2 Manga Drama + +## 适用场景 +在以下请求触发本技能: +- “一张图做成漫剧短片” +- “按 Seed2.0 的方法做漫画分镜视频” +- “给我一套可批量复用的漫剧提示词工作流” + +## 目标 +把“想法/图片”转成可执行的漫剧生产包,默认输出: +- 分镜脚本 +- 主角设定与风格参数 +- 每镜头生成提示词 +- 质检清单与修订建议 + +## 输入要求 +至少收集这些输入: +- 题材与情绪:热血、治愈、悬疑等 +- 主角来源:用户给图 or 文本设定 +- 目标时长:建议 8-30 秒 +- 输出比例:`9:16`、`16:9` 或 `1:1` +- 平台约束:分辨率、大小、禁词 + +## 四阶段执行流 +1. 阶段一:剧本逻辑编排 +- 先给出 5 幕分镜骨架:`开场`、`冲突`、`动作/转折`、`情感停顿`、`结尾定格` +- 每幕写清:场景、主体动作、镜头语言、光影、情绪、时长 + +2. 阶段二:主角设定 + 风格选择 +- 主角三元组:`外形特征`、`标志物`、`情绪基线` +- 风格从 `references/style-presets.md` 选择一个主风格,最多一个副风格 +- 写出“角色一致性约束”:发型、服饰主色、道具、脸部特征不能漂移 + +3. 阶段三:漫剧分镜提示词生成 +- 每幕生成一条独立提示词,统一结构: + - 主体 + 动作 + - 场景与时间 + - 镜头(景别/运镜/角度) + - 画风与材质 + - 光影与氛围 + - 负面约束(不要穿帮、不要多余肢体、不要文字水印) +- 连贯性要求:前后镜头必须引用上一幕的角色锚点 + +4. 阶段四:效果验收 +- 按 `references/output-schema.md` 输出质检项: + - 角色一致性 + - 场景衔接 + - 情绪表达 + - 镜头可读性 + - 平台合规 +- 若任一项不达标,给出“最小改动修订方案”,只改必要字段 + +## 输出格式(必须) +按以下顺序输出,避免自由散文: +1. `Project Brief` +2. `Storyboard(5 shots)` +3. `Character Sheet` +4. `Generation Prompts` +5. `QA Report` +6. `Revision Patch` + +## 约束 +- 不依赖 OpenClaw 专有命令。 +- 优先产出通用提示词和结构化结果,便于接入不同生成端。 +- 遇到登录墙、付费墙、权限不足时立即暂停并请求用户协作。 diff --git a/.kiro/skills/seed2-manga-drama/references/output-schema.md b/.kiro/skills/seed2-manga-drama/references/output-schema.md new file mode 100644 index 00000000..c6707de6 --- /dev/null +++ b/.kiro/skills/seed2-manga-drama/references/output-schema.md @@ -0,0 +1,26 @@ +# 输出与验收 Schema + +## Storyboard Shot Schema +- shot_id: `S1..S5` +- duration_sec: number +- objective: 本镜头叙事目的 +- visual: 画面主体与动作 +- camera: 景别/角度/运镜 +- mood: 情绪词 +- continuity_anchor: 与上一镜头保持一致的角色锚点 +- prompt: 生成提示词 +- negative_prompt: 负面约束 + +## QA Checklist +- identity_consistency: PASS/FAIL + 说明 +- scene_transition: PASS/FAIL + 说明 +- emotion_delivery: PASS/FAIL + 说明 +- visual_readability: PASS/FAIL + 说明 +- policy_compliance: PASS/FAIL + 说明 + +## Revision Patch +当 QA 出现 FAIL 时,按最小改动给出: +- target_shot: 受影响镜头 +- field_to_change: 仅列出需要修改的字段 +- patch_reason: 修改原因 +- patched_prompt: 修改后提示词 diff --git a/.kiro/skills/seed2-manga-drama/references/style-presets.md b/.kiro/skills/seed2-manga-drama/references/style-presets.md new file mode 100644 index 00000000..a93f920e --- /dev/null +++ b/.kiro/skills/seed2-manga-drama/references/style-presets.md @@ -0,0 +1,30 @@ +# 风格预设(8种) + +## 1. 日式治愈 +关键词:soft anime, warm lighting, pastel palette, calm motion + +## 2. 吉卜力氛围 +关键词:ghibli-inspired, hand-painted texture, poetic wind, cinematic stillness + +## 3. 国风水墨 +关键词:ink wash, xuan-paper texture, mountain mist, restrained color + +## 4. 美式卡通 +关键词:bold outline, saturated color, dynamic pose, comic timing + +## 5. 铅笔素描 +关键词:pencil sketch, cross-hatching, monochrome tone, rough texture + +## 6. 水彩插画 +关键词:watercolor bleed, soft edge, paper grain, transparent layering + +## 7. Q版萌系 +关键词:chibi proportion, cute expression, simplified anatomy, bright tone + +## 8. 热血战斗漫 +关键词:shonen energy, speed lines, impact frame, dramatic contrast + +## 风格使用规则 +- 只选 1 个主风格,可叠加 1 个副风格。 +- 副风格只影响材质与配色,不改角色骨相。 +- 同一项目中禁止频繁切换主风格。 diff --git a/.kiro/skills/seo-geo-page-optimization/.aios-skill-sync.json b/.kiro/skills/seo-geo-page-optimization/.aios-skill-sync.json new file mode 100644 index 00000000..66a5ba05 --- /dev/null +++ b/.kiro/skills/seo-geo-page-optimization/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "seo-geo-page-optimization", + "targetSurface": "kiro", + "targetRelativePath": "seo-geo-page-optimization", + "source": "skill-sources/seo-geo-page-optimization" +} \ No newline at end of file diff --git a/.kiro/skills/seo-geo-page-optimization/SKILL.md b/.kiro/skills/seo-geo-page-optimization/SKILL.md new file mode 100644 index 00000000..4758b5ad --- /dev/null +++ b/.kiro/skills/seo-geo-page-optimization/SKILL.md @@ -0,0 +1,52 @@ +--- +name: seo-geo-page-optimization +description: Use when improving a single page for SEO and AI answer-engine visibility (GEO), especially when you need faster first-screen conversion and clearer traffic funnels. +--- + +# SEO + GEO Page Optimization + +## Trigger +Use this skill when a page has one or more symptoms: +- High bounce rate on first visit +- Weak click-through to key actions (signup, GitHub, docs, install) +- Blog/project/friend links are hard to discover +- The page explains too much before giving a direct answer + +## Goal +Make the page easy to consume in this order: +1. `Quick Answer` +2. `Do it now` action +3. `Why it works` detail + +## Workflow +1. Define one conversion target for the page. +2. Add a first-screen `Quick Answer` block (3-5 sentences). +3. Add a copy-paste command/action block right after it. +4. Ensure primary links are visible in top navigation: + - Project + - Blog + - Friend links / ecosystem links +5. Keep title/meta aligned with one main intent. +6. Add FAQ-style Q/A sections for answer-engine extraction. +7. Verify internal links and canonical URL are clear. + +## Copy Template +Use this section pattern: +- `Problem` +- `Quick Answer` +- `Action Steps` +- `Examples` +- `FAQ` + +## Minimum Checklist +- `title` includes main intent +- `meta description` includes value + action +- H1/H2 structure is explicit and scannable +- At least one visible CTA above the fold +- Project URL is visible without scrolling deep +- Blog and friend links are not hidden in footer only + +## Validation +- Build the docs/site and confirm no broken nav links. +- Manual check on desktop and mobile first screen. +- Confirm key CTA can be completed in under 30 seconds. diff --git a/.kiro/skills/skill-constraints/.aios-skill-sync.json b/.kiro/skills/skill-constraints/.aios-skill-sync.json new file mode 100644 index 00000000..f9dfcf5e --- /dev/null +++ b/.kiro/skills/skill-constraints/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "skill-constraints", + "targetSurface": "kiro", + "targetRelativePath": "skill-constraints", + "source": "skill-sources/skill-constraints" +} \ No newline at end of file diff --git a/.kiro/skills/skill-constraints/SKILL.md b/.kiro/skills/skill-constraints/SKILL.md new file mode 100644 index 00000000..a010b8e8 --- /dev/null +++ b/.kiro/skills/skill-constraints/SKILL.md @@ -0,0 +1,119 @@ +--- +name: skill-constraints +description: Use when executing any skill or browser automation task - enforces operational constraints and best practices +--- + +# 技能使用约束 + +## Overview + +所有技能操作必须遵循此约束规范,确保安全、高效、可追溯。 + +## When to Use + +- 执行任何浏览器自动化操作时 +- 使用 MCP 工具(`page.extract_text`, `page.get_html`, `page.screenshot` 等)时 +- 进行任何运营操作(发布笔记、点赞、评论等)时 + +## Core Pattern + +### 浏览器操作 + +```markdown +0. 启动浏览器时默认要求可见界面并走 CDP + - 优先:`chrome.launch_cdp { port: 9222, user_data_dir: '~/.chrome-cdp-profile' }` + - 随后:`browser.connect_cdp { cdp_url: 'http://127.0.0.1:9222' }` + - 仅在无图形环境或后台 smoke test 时才允许 headless 模式 + - 当同一 `userDataDir` 被其他进程占用时,优先复用已启动浏览器,不共享被锁目录 + - 若目标是“多个 agent 共享同一登录态”,统一连接同一个 CDP endpoint + +1. 优先使用文本/DOM证据做决策 + - 先读 `page.extract_text`,必要时补充 `page.get_html` + - 比整页截图更高效,可快速定位按钮文案和页面状态 + - 不使用 `chrome-devtools` 工具链执行业务流程,统一走 `puppeteer-stealth` 的 browser-use 工具链(`chrome.*` / `browser.*` / `page.*`) + +2. 只有视觉信息不足时才截图,并保存到 temp/ 目录 + - 条件:文本/HTML 证据不足以判断状态 + - 使用:`page.screenshot` + - 路径:`aios/temp/{操作类型}_{时间戳}.png` + - 示例:`login_20240301_120000.png` +``` + +### 操作间隔 + +```bash +# 随机等待 5-30 秒 +sleep $((RANDOM % 26 + 5)) +``` + +## Rules + +### 禁止行为 + +| 禁止 | 说明 | +|------|------| +| 直接在对话中粘贴大段截图 | 浪费 token,必须保存到文件 | +| 跳过反检测脚本 | 每次操作前必须执行 | +| 忽略操作间隔 | 必须随机 5-30 秒 | +| 在非 temp 目录保存截图 | 必须保存到 aios/temp/ | +| 自动化执行第三方平台登录 | 登录必须人工完成(含 2FA) | + +### 必需行为 + +| 必须 | 说明 | +|------|------| +| 操作前执行反检测 | 使用 skill/反检测脚本.json | +| 截图保存到 temp/ | 路径固定为 aios/temp/ | +| 先读文本/DOM | `page.extract_text -> page.get_html` | +| 使用 grep 搜索快照 | 而非目视查看截图 | +| 记录到历史 | 关键操作写入 memory/history/ | +| 登录态检测 | 识别到登录页/验证码/2FA 时先提示用户协作登录 | + +### MCP 工具优先级 + +1. **page.extract_text** - 首选,获取页面文本证据并做主决策 +2. **page.get_html** - 补充 DOM 结构和属性信息 +3. **page.screenshot** - 仅在视觉降级时使用 +4. **page.click** - 通过 selector 操作 +5. **page.type** - 通过 selector 输入 + +## Examples + +### Good + +```json +// 启动并连接 CDP 浏览器 +chrome.launch_cdp { port: 9222, user_data_dir: '~/.chrome-cdp-profile' } +browser.connect_cdp { cdp_url: 'http://127.0.0.1:9222' } + +// 获取文本/DOM证据 +page.extract_text { session_id: '<session_id>' } +page.get_html { session_id: '<session_id>' } + +// 搜索内容 +grep "关注" snapshot.txt + +// 仅视觉降级时截图,并保存到正确位置 +page.screenshot { session_id: '<session_id>', path: 'aios/temp/publish_20240301_120000.png' } +``` + +### Bad + +``` +// 在对话中直接展示截图 +[直接在回复中嵌入截图] + +// 跳过间隔 +page.click() // 立即执行 +page.click() // 没有等待 +``` + +## Common Mistakes + +| 错误 | 正确做法 | +|------|----------| +| 截图直接在对话中显示 | 保存到 temp/,用 Read 工具查看 | +| 忽略随机间隔 | 每次操作后 sleep 5-30 秒 | +| 默认先看整页截图 | 先读布局字段,必要时再局部截图 | +| 用眼睛看截图找内容 | 用 grep 从快照搜索 | +| 在项目根目录放临时文件 | 统一放 aios/temp/ | diff --git a/.kiro/skills/verification-loop/.aios-skill-sync.json b/.kiro/skills/verification-loop/.aios-skill-sync.json new file mode 100644 index 00000000..dff9f18a --- /dev/null +++ b/.kiro/skills/verification-loop/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "verification-loop", + "targetSurface": "kiro", + "targetRelativePath": "verification-loop", + "source": "skill-sources/verification-loop" +} \ No newline at end of file diff --git a/.kiro/skills/verification-loop/SKILL.md b/.kiro/skills/verification-loop/SKILL.md new file mode 100644 index 00000000..7391159a --- /dev/null +++ b/.kiro/skills/verification-loop/SKILL.md @@ -0,0 +1,35 @@ +--- +name: verification-loop +description: Evidence-before-assertions workflow. Use before claiming work is done, before release, and after any behavior change in scripts/skills/MCP. +--- + +# Verification Loop + +## Trigger +Use this skill when: +- You changed runtime behavior (scripts, wrappers, MCP server, install flows) +- You are about to say "done", "fixed", "works", or "passes" +- You are about to bump version / release + +## Rules +- Prefer commands with deterministic exit codes over "it looks fine". +- If you cannot run verification, say exactly what you could not run and why. + +## Baseline Checks (AIOS) +1. Run the verifier: + - `aios doctor` + - Or: `node scripts/aios.mjs doctor` + - Compatibility wrappers: `scripts/verify-aios.sh` / `scripts/verify-aios.ps1` + +2. MCP server changes (minimum): + - `cd mcp-server && npm run typecheck` + - `cd mcp-server && npm run build` + - Manual smoke: `chrome.launch_cdp` -> `browser.connect_cdp` -> `page.goto` -> `page.extract_text`/`page.screenshot` -> `browser.close` + +3. Install/wrapper changes: + - Re-run install/update on a clean-ish shell session. + - Confirm new commands are visible and resolve to `ROOTPATH` scripts. + +## Evidence Capture +- Record the exact commands run and whether they succeeded. +- For failures: include the first actionable error line and the remediation you applied. diff --git a/.kiro/skills/versioning-by-impact/.aios-skill-sync.json b/.kiro/skills/versioning-by-impact/.aios-skill-sync.json new file mode 100644 index 00000000..64551160 --- /dev/null +++ b/.kiro/skills/versioning-by-impact/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "versioning-by-impact", + "targetSurface": "kiro", + "targetRelativePath": "versioning-by-impact", + "source": "skill-sources/versioning-by-impact" +} \ No newline at end of file diff --git a/.kiro/skills/versioning-by-impact/SKILL.md b/.kiro/skills/versioning-by-impact/SKILL.md new file mode 100644 index 00000000..6632357e --- /dev/null +++ b/.kiro/skills/versioning-by-impact/SKILL.md @@ -0,0 +1,36 @@ +--- +name: versioning-by-impact +description: Use when completing a task and deciding whether repository changes require a semantic version bump and changelog entry before commit/push. +--- + +# Versioning by Impact + +## Overview +Apply Semantic Versioning decisions from actual change impact, not from task size. + +## Impact Rules + +- `none`: no repository file changes. +- `patch`: backward-compatible fixes, docs updates, translation/content updates, non-breaking refactors. +- `minor`: backward-compatible new features or new capabilities. +- `major`: any breaking change to CLI behavior, config contract, file layout contract, or documented usage. + +## Required Output Format + +Always report these 4 fields in final handoff: + +1. `Version Impact: none|patch|minor|major` +2. `Recommended Version: vX.Y.Z -> vA.B.C` (or `no change`) +3. `Why: <one-sentence reason>` +4. `Release Notes:` short bullet list + +## Repository Commands + +- Read current version: `cat VERSION` +- Bump version + changelog entry: `scripts/release-version.sh <patch|minor|major> "summary"` +- Preview only: `scripts/release-version.sh --dry-run <patch|minor|major> "summary"` + +## Default Behavior + +If impact is `none`, do not bump version. +If impact is `patch|minor|major`, update both `VERSION` and `CHANGELOG.md` before commit. diff --git a/.kiro/skills/xhs-ops-methods/.aios-skill-sync.json b/.kiro/skills/xhs-ops-methods/.aios-skill-sync.json new file mode 100644 index 00000000..715a5c28 --- /dev/null +++ b/.kiro/skills/xhs-ops-methods/.aios-skill-sync.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "managedBy": "aios", + "kind": "generated-skill", + "relativeSkillPath": "xhs-ops-methods", + "targetSurface": "kiro", + "targetRelativePath": "xhs-ops-methods", + "source": "skill-sources/xhs-ops-methods" +} \ No newline at end of file diff --git a/.kiro/skills/xhs-ops-methods/SKILL.md b/.kiro/skills/xhs-ops-methods/SKILL.md new file mode 100644 index 00000000..520e4fa3 --- /dev/null +++ b/.kiro/skills/xhs-ops-methods/SKILL.md @@ -0,0 +1,89 @@ +--- +name: xhs-ops-methods +description: 当用户要学习或执行小红书运营方法时使用。提供可复用的“定方向-定人设-写排发互-复盘增长”流程,支持多账号协作,并强调人工审核与合规发布。 +--- + +# XHS Ops Methods + +## 适用场景 +当用户提出以下需求时触发本技能: +- “帮我做小红书运营方法” +- “怎么运营多个小红书账号” +- “如何做爆款拆解、发帖、评论互动和复盘” + +## 核心目标 +把“灵感”转成可执行运营动作,输出: +1. 账号策略与人设定义 +2. 选题与爆款拆解 +3. 发布与互动执行计划 +4. 数据复盘与下一轮优化 + +## 先收集输入 +执行前至少确认: +- 账号数与账号定位(如主号/细分号) +- 每个账号目标受众与转化目标 +- 人设语气与禁忌表达 +- 每日发帖频率与审核规则 +- 内容素材来源(原创/改写/复盘) + +## 运营流程(方法论) +1. 定方向 +- 每个账号只做 1-2 个垂直主题。 +- 先定义目标:涨粉、咨询、私域转化、品牌曝光。 + +2. 定人设 +- 为每个账号建立固定人设:身份、语气、价值观、禁忌词。 +- 任何自动回复都必须遵守该人设,不可漂移。 + +3. 学爆款 +- 对标学习默认只从小红书站内 `搜索结果页` 或 `发现页` 进入。 +- 先看真实分发入口里的标题、封面、账号名、时间、互动量,再决定要不要点进笔记。 +- 不要绕开站内入口做奇怪抓取;爆款判断必须尽量贴近真实用户看到的界面。 + +4. 写与排 +- 先做爆款拆解:选题、标题钩子、结构、互动点。 +- 再做内容重组:不照抄原文,重写为原创表达。 +- 输出统一结构:标题候选、正文、封面文案、标签建议。 + +5. 发 +- 默认“先私密后公开”:先私密发布并人工检查。 +- 人工检查通过后再公开,避免低质排版直接曝光。 +- 控制频率:建议每账号每天 1-2 篇。 + +6. 互 +- 定时检查评论并按人设语气回复。 +- 对高价值评论优先回复,形成二次互动。 +- 保留需要人工介入的评论(争议、敏感、交易)。 + +7. 复盘 +- 每天记录内容表现:曝光、点击、互动、收藏、转化。 +- 输出“继续做/停止做/加倍投入”的选题清单。 +- 下一轮选题基于数据而非主观偏好。 + +## 爆款学习与对标规则 +- 优先拆:高互动、强问题导向、强决策导向的笔记。 +- 先记录标题、封面信息、前两段结构、互动诱因,再判断为什么会有人点、藏、评、关。 +- 不抄原文,不复刻同一句式;只提炼结构和机制。 + +## 多账号协作规范 +- 每个账号使用独立浏览器 profile 与独立会话。 +- 每个账号维护独立人设与内容边界。 +- 指令必须显式指定账号,避免误发。 + +## 合规与安全 +- 禁止批量刷量、诱导互动、违规搬运。 +- 涉及账号登录、发布确认、外链投放时,必须用户确认。 +- 遇到风控提示、登录失效、权限异常,立即暂停并请求人工处理。 + +## 输出格式 +按以下结构输出,避免散文式回复: +1. `Account Strategy` +2. `Persona Rules` +3. `Topic Pipeline` +4. `Post Plan` +5. `Comment Plan` +6. `Daily Review` + +## 参考文件 +- `references/ops-framework.md`:运营框架与检查清单 +- `references/prompt-templates.md`:可直接复用的提示词模板 diff --git a/.kiro/skills/xhs-ops-methods/references/ops-framework.md b/.kiro/skills/xhs-ops-methods/references/ops-framework.md new file mode 100644 index 00000000..5e8e1ded --- /dev/null +++ b/.kiro/skills/xhs-ops-methods/references/ops-framework.md @@ -0,0 +1,49 @@ +# 小红书运营框架(可执行版) + +## A. 账号策略模板 +- account_name: +- audience: +- goal: +- core_topics: +- forbidden_topics: +- CTA_type: + +## B. 发布前检查清单 +- 标题是否有明确钩子(利益点/反差/时效) +- 首屏前两行是否能让用户继续读 +- 封面与正文核心信息是否一致 +- 标签是否与内容强相关 +- 是否先私密发布并人工预览 + +## C. 评论互动分层 +- P1: 高意向/高价值评论(优先回复) +- P2: 普通互动评论(标准模板回复) +- P3: 争议或敏感评论(人工接管) + +## D. 日复盘指标 +- 曝光量 +- 互动率(赞+评+藏 / 曝光) +- 收藏率 +- 评论质量(有效评论占比) +- 主页访问/私信/咨询(如可观测) + +## E. 周度决策 +- Continue: 指标稳定上升的选题 +- Improve: 有潜力但表现不稳的选题 +- Drop: 连续低表现的选题 + +## F. 素材与发布防错规则 +- 用户本次明确给出的素材包路径,就是当天内容的真源;先看这包图,不先沿用旧 `covers/` 或旧草稿。 +- 上传前必须打开实际要发的封面图或发布联系图,不能只信文件名、路径名或历史 markdown 引用。 +- 标题和正文要跟着这包图的真实视觉重点走;如果图片重点变了,就重定题,不硬套旧选题。 +- 默认浏览器已登录时,优先复用当前 `default` 会话,不额外切换到别的浏览器或持久化 profile。 +- 发布核验优先级:`笔记管理中的标题/时间/状态` > `发布后返回页截图` > `URL`。 + +## G. 发布前 30 秒自检清单 +- `真源`:这次发布的图片是否来自用户本次明确给出的素材路径,而不是旧 `covers/` 或旧草稿。 +- `实图`:是否真的打开过要上传的封面图或联系图,确认内容没串。 +- `同题`:标题、封面文案、正文、配图是否都在讲同一个决策点。 +- `合规`:是否避开擦边、未成年化、诱导互动,以及近期已发近邻话题。 +- `会话`:是否处在正确的默认浏览器登录态和正确账号下。 +- `核验`:是否记得发布后先看 `笔记管理` 的标题、时间、状态。 + diff --git a/.kiro/skills/xhs-ops-methods/references/prompt-templates.md b/.kiro/skills/xhs-ops-methods/references/prompt-templates.md new file mode 100644 index 00000000..57e12421 --- /dev/null +++ b/.kiro/skills/xhs-ops-methods/references/prompt-templates.md @@ -0,0 +1,46 @@ +# Prompt 模板 + +## 1) 爆款拆解模板 +你是小红书内容策略师。请拆解这条内容的爆点并给出可复用策略。 +输入:{爆款链接或原文} +输出: +- 选题定位 +- 标题钩子 +- 内容结构 +- 互动设计 +- 可迁移点(哪些可复用) +- 风险点(哪些不能照搬) + +## 2) 选题生成模板 +基于账号定位 {账号定位}、受众 {受众}、目标 {目标},生成 10 个选题。 +每个选题输出: +- 标题候选 3 条 +- 角度说明 +- 预估互动点 +- 风险提醒 + +## 3) 笔记生成模板 +按人设 {人设描述} 生成一篇小红书笔记。 +约束: +- 语气一致 +- 开头 2 行有钩子 +- 正文分段易读 +- 结尾有自然互动引导 +输出:标题、正文、封面文案、标签建议。 + +## 4) 评论回复模板 +根据人设 {人设} 回复以下评论: +{评论列表} +要求: +- 每条回复简洁自然 +- 不重复套话 +- 遇到敏感或争议评论标记 `NEED_HUMAN` + +## 5) 日复盘模板 +请基于今日数据生成复盘: +输入:{帖子表现数据} +输出: +- 今日有效动作 +- 低效动作 +- 明日计划(3条) +- 需要人工决策的事项 diff --git a/.kiro/steering/AIOS.md b/.kiro/steering/AIOS.md new file mode 100644 index 00000000..17a1b70a --- /dev/null +++ b/.kiro/steering/AIOS.md @@ -0,0 +1,52 @@ +<!-- AIOS NATIVE BEGIN --> +AIOS native enhancements are active in this repository. + +Use repo-local skills, agents, and bootstrap docs before falling back to ad-hoc behavior. + + +## AIOS Self-Trigger Routing + +- Continue normally in the active coding client for single-domain work. +- If the user asks for delegation, parallel work, or 2+ clearly independent domains, trigger AIOS directly instead of asking the user to run it manually: `aios team ...` or `node <AIOS_ROOT>/scripts/ctx-agent.mjs --route team|subagent ...`. +- If the user asks for a long-running, overnight, resumable, checkpoint-heavy objective, trigger the solo harness directly: `aios harness run --objective "<task>" --worktree --max-iterations 8`. +- Use `aios harness status --session <id>`, `aios hud --session <id>`, `aios harness stop --session <id> --reason "<why>"`, and `aios harness resume --session <id>` for handoff and recovery. +- Do not ask the user to manually trigger AIOS commands unless they requested dry-run/preview or the environment lacks permission to run shell commands. + +## Privacy & Relay Safety + +- Before sending context to any model or relay service, assume prompts, code snippets, diffs, logs, screenshots, MCP output, and browser-extracted text may leave this machine. +- Never paste or expose API keys, tokens, cookies, sessions, private keys, `.env` files, credential configs, customer data, browser profiles, or unredacted authorization logs. +- For sensitive files, use `aios privacy read --file <path>` and share only the redacted output. +- If a custom model endpoint or relay is detected, warn the user before continuing and avoid sending secrets or proprietary data. +- LLM privacy instructions are advisory; do not claim strict privacy compliance unless deterministic AIOS gates verified the relevant checks. + +ContextDB remains the shared runtime layer for memory, checkpoints, and execution evidence. + +Wrapped `codex` / `claude` / `gemini` / `opencode` / `kiro-cli` sessions receive an AIOS startup route prompt. The agent should self-select `single`, `subagent`, `team`, or `harness` and run the matching AIOS command when the request warrants it. + +Persona and user profile memory are part of the same runtime layer: +- `aios memo persona ...` manages the global agent identity file (`~/.aios/SOUL.md` by default). +- `aios memo user ...` manages the global operator preference file (`~/.aios/USER.md` by default). +- `ctx-agent` injects persona and user profile content into the Memory prelude before workspace memo content. +- Treat these files as stable guidance, not task facts; project-specific facts should still go through ContextDB events, checkpoints, or workspace memo. + +Browser MCP is available through the repo-local AIOS server and should be preferred for browser work. + +For browser tasks, use this operating pattern unless the user explicitly asks otherwise: +- Connect to a visible CDP browser first: `chrome.launch_cdp` then `browser.connect_cdp`. +- On dense or dynamic pages, prefer `page.semantic_snapshot` first for compact headings/actions before choosing the next step. +- Before acting, read the page state with `page.extract_text`; use `page.get_html` only when text is insufficient. +- Work in short read -> act -> verify loops. Do not chain multiple blind browser actions. +- For clear button/link labels, prefer `page.click_text` before constructing low-level locators. +- Prefer visible text or role-based targets. If a locator is not unique, inspect again and narrow the target instead of guessing. +- After navigation or major actions, use `page.wait` when a state transition is expected, then re-read the page. +- Use `page.screenshot` only as a visual fallback when text/HTML evidence is not enough. +- For complex browser tasks, first summarize the current page, then state the next single action, then execute it. +- When `puppeteer-stealth` is available, use its browser-use toolchain (`chrome.*` / `browser.*` / `page.*`) for normal business flows instead of `chrome-devtools`. + +# AIOS Native Kiro Layer + +- Prefer repo-local `.kiro/steering/` for project guidance. +- Keep workspace memory, browser MCP, and verification flow aligned with the AIOS runtime. +- Kiro CLI support includes native steering, MCP, skills, agents, and explicit headless/solo execution. +<!-- AIOS NATIVE END --> diff --git a/client-sources/native-base/kiro/project/mcp.json b/client-sources/native-base/kiro/project/mcp.json new file mode 100644 index 00000000..52fe3f93 --- /dev/null +++ b/client-sources/native-base/kiro/project/mcp.json @@ -0,0 +1,18 @@ +{ + "puppeteer-stealth": { + "type": "stdio", + "command": "bash", + "args": ["{{ROOT_DIR}}/scripts/run-browser-use-mcp.sh"], + "env": { + "BROWSER_USE_CDP_URL": "http://127.0.0.1:9222" + } + }, + "aios-auth-tools": { + "type": "stdio", + "command": "python3", + "args": ["-u", "{{ROOT_DIR}}/scripts/auth-tools-server.py"], + "env": { + "BROWSER_USE_CDP_URL": "http://127.0.0.1:9222" + } + } +} diff --git a/client-sources/native-base/kiro/project/steering.md b/client-sources/native-base/kiro/project/steering.md new file mode 100644 index 00000000..929cbcf9 --- /dev/null +++ b/client-sources/native-base/kiro/project/steering.md @@ -0,0 +1,5 @@ +# AIOS Native Kiro Layer + +- Prefer repo-local `.kiro/steering/` for project guidance. +- Keep workspace memory, browser MCP, and verification flow aligned with the AIOS runtime. +- Kiro CLI support includes native steering, MCP, skills, agents, and explicit headless/solo execution. diff --git a/docs/superpowers/plans/2026-05-12-kiro-cli-support.md b/docs/superpowers/plans/2026-05-12-kiro-cli-support.md new file mode 100644 index 00000000..2c247e88 --- /dev/null +++ b/docs/superpowers/plans/2026-05-12-kiro-cli-support.md @@ -0,0 +1,146 @@ +# Kiro CLI Support Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add Kiro CLI as a supported AIOS client at the deep native layer, shell bridge, and runtime paths. + +**Architecture:** Extend the existing client registry and home-directory resolution so Kiro is treated like the other supported clients. Add a Kiro shell bridge entry so interactive CLI usage still inherits ContextDB auto-load behavior. Add Kiro native emitters and agent sync so the workspace can materialize `.kiro` steering, MCP settings, agents, and skills. + +**Tech Stack:** Node.js 22 ESM, zsh/PowerShell wrappers, native sync emitters, Node test runner. + +--- + +### Task 1: Client registry and wrapper tests + +**Files:** +- Modify: `scripts/tests/aios-components.test.mjs` +- Modify: `scripts/tests/native-source-tree.test.mjs` +- Modify: `scripts/tests/native-sync.test.mjs` + +- [x] **Step 1: Write the failing test** + +Add assertions that: +- `getClientHomes()` includes `kiro` +- setup/update/uninstall client selection accepts `kiro` +- native manifest/source-tree accepts `kiro` + +- [x] **Step 2: Run test to verify it fails** + +Run: +```bash +node --test scripts/tests/aios-components.test.mjs scripts/tests/native-source-tree.test.mjs scripts/tests/native-sync.test.mjs +``` + +- [x] **Step 3: Write minimal implementation** + +Add `kiro` to the client white-lists and home-dir mapping. + +- [x] **Step 4: Run test to verify it passes** + +Run: +```bash +node --test scripts/tests/aios-components.test.mjs scripts/tests/native-source-tree.test.mjs scripts/tests/native-sync.test.mjs +``` + +### Task 2: ContextDB shell bridge support + +**Files:** +- Modify: `scripts/contextdb-shell.zsh` +- Modify: `scripts/contextdb-shell.ps1` +- Modify: `scripts/contextdb-shell-bridge.mjs` +- Modify: `scripts/ctx-agent-core.mjs` +- Modify: `scripts/lib/components/shell.mjs` +- Modify: `scripts/tests/aios-wrappers.test.mjs` +- Modify: `scripts/tests/contextdb-shell-bridge-codex-home.test.mjs` +- Modify: `scripts/tests/ctx-agent-core.test.mjs` + +- [x] **Step 1: Write the failing test** + +Add coverage for: +- a `kiro` wrapper path +- `kiro-cli` agent identity +- blocked admin subcommands still bypass wrapping + +- [x] **Step 2: Run test to verify it fails** + +Run: +```bash +node --test scripts/tests/aios-wrappers.test.mjs scripts/tests/contextdb-shell-bridge-codex-home.test.mjs scripts/tests/ctx-agent-core.test.mjs +``` + +- [x] **Step 3: Write minimal implementation** + +Add the Kiro command/function path and keep routed execution falling back to existing supported subagent runtimes. + +- [x] **Step 4: Run test to verify it passes** + +Run: +```bash +node --test scripts/tests/aios-wrappers.test.mjs scripts/tests/contextdb-shell-bridge-codex-home.test.mjs scripts/tests/ctx-agent-core.test.mjs +``` + +### Task 3: Native sync for Kiro steering and MCP + +**Files:** +- Create: `scripts/lib/native/emitters/kiro.mjs` +- Modify: `scripts/lib/native/sync.mjs` +- Modify: `scripts/lib/native/doctor.mjs` +- Modify: `scripts/lib/native/repairs.mjs` +- Modify: `scripts/lib/native/source-tree.mjs` +- Modify: `config/native-sync-manifest.json` +- Create: `client-sources/native-base/kiro/project/mcp.json` +- Create: `client-sources/native-base/kiro/project/steering.md` +- Modify: `scripts/tests/native-sync.test.mjs` +- Modify: `scripts/tests/native-doctor.test.mjs` +- Modify: `scripts/tests/native-source-tree.test.mjs` + +- [x] **Step 1: Write the failing test** + +Add coverage for: +- Kiro native sync output paths under `.kiro/` +- Kiro metadata/doctor drift reporting +- Kiro native source-tree plan resolution + +- [x] **Step 2: Run test to verify it fails** + +Run: +```bash +node --test scripts/tests/native-source-tree.test.mjs scripts/tests/native-sync.test.mjs scripts/tests/native-doctor.test.mjs +``` + +- [x] **Step 3: Write minimal implementation** + +Render `.kiro/steering/AIOS.md` and `.kiro/settings/mcp.json` from `client-sources/native-base/kiro/project`. + +- [x] **Step 4: Run test to verify it passes** + +Run: +```bash +node --test scripts/tests/native-source-tree.test.mjs scripts/tests/native-sync.test.mjs scripts/tests/native-doctor.test.mjs +``` + +### Task 4: Docs and verification + +**Files:** +- Modify: `README-zh.md` +- Modify: `CLAUDE.md` +- Modify: `AGENTS.md` + +- [x] **Step 1: Update user-facing client lists** + +Mention Kiro as a supported compatibility client and note that live `team/subagent/harness` execution still uses the existing supported runtimes. + +- [ ] **Step 2: Run full verification** + +Run: +```bash +node --test scripts/tests/aios-components.test.mjs scripts/tests/aios-wrappers.test.mjs scripts/tests/contextdb-shell-bridge-codex-home.test.mjs scripts/tests/ctx-agent-core.test.mjs scripts/tests/native-source-tree.test.mjs scripts/tests/native-sync.test.mjs scripts/tests/native-doctor.test.mjs +node --test --import tsx scripts/lib/tui-ink/tests/tui-ink.test.ts +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A +git commit -m "feat(kiro): add Kiro CLI compatibility support" +```