Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
85afb2d
Update agent integration skill to E2E-driven development
alishakawaguchi Feb 26, 2026
c58064c
Fix agent integration skill interface/naming drift
alishakawaguchi Feb 27, 2026
9b48a83
Merge branch 'main' into alisha/kiro-oneshot
alishakawaguchi Feb 27, 2026
3e70f85
Merge branch 'main' into alisha/kiro-oneshot
alishakawaguchi Feb 27, 2026
c38e217
Add Kiro agent integration
alishakawaguchi Feb 27, 2026
19acfef
Restructure agent-integration skill for E2E-first TDD
alishakawaguchi Feb 27, 2026
526070a
revert kiro code
alishakawaguchi Feb 27, 2026
09d3006
Add Kiro agent integration with E2E-first TDD
alishakawaguchi Feb 28, 2026
237ba90
Add commit steps to agent-integration skill phases
alishakawaguchi Feb 28, 2026
bf4dac1
Simplify Kiro agent code: remove dead branch, magic number, and dupli…
alishakawaguchi Feb 28, 2026
df1609d
Audit and fix Kiro agent tests: remove dead test, add missing coverage
alishakawaguchi Mar 3, 2026
846147e
Merge branch 'main' into alisha/kiro-oneshot
alishakawaguchi Mar 3, 2026
ec25180
Merge branch 'main' into alisha/kiro-oneshot
alishakawaguchi Mar 3, 2026
3e19f03
Resolve merge conflicts and strip ENTIRE_TEST_TTY from Kiro E2E runner
alishakawaguchi Mar 3, 2026
d82a915
Add Kiro IDE hook support and SIGV4 auth for E2E
alishakawaguchi Mar 3, 2026
d01bbc6
Fix Kiro IDE hooks hanging on stdin read and missing transcript
alishakawaguchi Mar 3, 2026
834b493
Add Kiro TranscriptAnalyzer and fix stop hook exit code on empty repos
alishakawaguchi Mar 3, 2026
7dd0395
Support Kiro IDE transcript format for session metadata
alishakawaguchi Mar 4, 2026
a56a1ec
Extract helpers to deduplicate Kiro transcript parsing and hook prefi…
alishakawaguchi Mar 4, 2026
390be4f
Merge branch 'main' into alisha/kiro-oneshot
alishakawaguchi Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/e2e-isolated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ on:
required: true
default: "gemini-cli"
type: choice
options: [claude-code, opencode, gemini-cli, cursor-cli, factoryai-droid]
options: [claude-code, opencode, gemini-cli, cursor-cli, factoryai-droid, kiro]
test:
description: "Test name filter (regex)"
required: true
default: "TestInteractiveMultiStep"

permissions:
id-token: write
contents: read

jobs:
e2e-isolated:
runs-on: ubuntu-latest
Expand All @@ -38,17 +42,27 @@ jobs:
claude-code) curl -fsSL https://claude.ai/install.sh | bash ;;
opencode) curl -fsSL https://opencode.ai/install | bash ;;
gemini-cli) npm install -g @google/gemini-cli ;;
kiro) curl -fsSL https://cli.kiro.dev/install | bash ;;
cursor-cli) curl https://cursor.com/install -fsS | bash ;;
factoryai-droid) curl -fsSL https://app.factory.ai/cli | sh ;;
esac
echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Configure AWS credentials (Kiro OIDC)
if: inputs.agent == 'kiro'
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1

- name: Bootstrap agent
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
AMAZON_Q_SIGV4: ${{ inputs.agent == 'kiro' && '1' || '' }}
AWS_REGION: ${{ inputs.agent == 'kiro' && 'us-east-1' || '' }}
run: go run ./e2e/bootstrap

- name: Run isolated test
Expand All @@ -57,6 +71,8 @@ jobs:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
AMAZON_Q_SIGV4: ${{ inputs.agent == 'kiro' && '1' || '' }}
AWS_REGION: ${{ inputs.agent == 'kiro' && 'us-east-1' || '' }}
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts
E2E_ENTIRE_BIN: /usr/local/bin/entire
run: |
Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
branches:
- main

permissions:
id-token: write
contents: read

# Concurrency: only one E2E job runs at a time
concurrency:
group: e2e-tests
Expand All @@ -18,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
agent: [claude-code, opencode, gemini-cli, factoryai-droid, cursor-cli]
agent: [claude-code, opencode, gemini-cli, factoryai-droid, cursor-cli, kiro]

steps:
- name: Checkout repository
Expand All @@ -36,17 +40,27 @@ jobs:
claude-code) curl -fsSL https://claude.ai/install.sh | bash ;;
opencode) curl -fsSL https://opencode.ai/install | bash ;;
gemini-cli) npm install -g @google/gemini-cli ;;
kiro) curl -fsSL https://cli.kiro.dev/install | bash ;;
cursor-cli) curl https://cursor.com/install -fsS | bash ;;
factoryai-droid) curl -fsSL https://app.factory.ai/cli | sh ;;
esac
echo "$HOME/.local/bin" >> $GITHUB_PATH
Comment on lines 24 to 47
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding kiro to the E2E matrix will cause the main-branch workflow to run Kiro E2E on every push. The Kiro runner’s Bootstrap() fails on CI when not authenticated (kiro-cli whoami), and this workflow doesn’t provide any Kiro credentials/env to log in, so the kiro job is likely to fail consistently. Consider gating the kiro matrix entry behind a repo secret/variable (or making the job conditional), and/or setting E2E_AGENT=${{ matrix.agent }} for the bootstrap step so bootstrap only runs for the selected agent.

Copilot uses AI. Check for mistakes.

- name: Configure AWS credentials (Kiro OIDC)
if: matrix.agent == 'kiro'
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1

- name: Bootstrap agent
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
AMAZON_Q_SIGV4: ${{ matrix.agent == 'kiro' && '1' || '' }}
AWS_REGION: ${{ matrix.agent == 'kiro' && 'us-east-1' || '' }}
run: go run ./e2e/bootstrap

- name: Run E2E Tests
Expand All @@ -55,6 +69,8 @@ jobs:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
AMAZON_Q_SIGV4: ${{ matrix.agent == 'kiro' && '1' || '' }}
AWS_REGION: ${{ matrix.agent == 'kiro' && 'us-east-1' || '' }}
E2E_CONCURRENT_TEST_LIMIT: ${{ matrix.agent == 'gemini-cli' && '6' || matrix.agent == 'factoryai-droid' && '1' || '' }}
run: mise run test:e2e --agent ${{ matrix.agent }}

Expand Down
40 changes: 36 additions & 4 deletions cmd/entire/cli/agent/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,47 @@ type Event struct {
Metadata map[string]string
}

// ErrEmptyHookInput is returned by ReadAndParseHookInput when stdin is empty.
// Agents that support empty stdin (e.g., Kiro IDE mode) can check for this
// with errors.Is and handle it gracefully.
var ErrEmptyHookInput = errors.New("empty hook input")

// stdinReadTimeout is how long ReadAndParseHookInput waits for stdin data
// before treating the input as empty. IDEs like Kiro keep stdin pipes open
// without sending EOF, so io.ReadAll blocks forever. Piped data is available
// immediately, so 500ms is generous.
const stdinReadTimeout = 500 * time.Millisecond

// readResult holds the outcome of an io.ReadAll goroutine.
type readResult struct {
data []byte
err error
}

// ReadAndParseHookInput reads all bytes from stdin and unmarshals JSON into the given type.
// This is a shared helper for agent ParseHookEvent implementations.
//
// A 500ms timeout prevents hanging when the IDE keeps stdin open without EOF.
func ReadAndParseHookInput[T any](stdin io.Reader) (*T, error) {
data, err := io.ReadAll(stdin)
if err != nil {
return nil, fmt.Errorf("failed to read hook input: %w", err)
ch := make(chan readResult, 1)
go func() {
data, err := io.ReadAll(stdin)
ch <- readResult{data, err}
}()

var data []byte
select {
case res := <-ch:
if res.err != nil {
return nil, fmt.Errorf("failed to read hook input: %w", res.err)
}
data = res.data
case <-time.After(stdinReadTimeout):
return nil, ErrEmptyHookInput
}

if len(data) == 0 {
return nil, errors.New("empty hook input")
return nil, ErrEmptyHookInput
}
var result T
if err := json.Unmarshal(data, &result); err != nil {
Expand Down
186 changes: 186 additions & 0 deletions cmd/entire/cli/agent/kiro/AGENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Kiro Agent Integration

Implementation one-pager for the Kiro (Amazon AI coding CLI) agent integration.

## Identity

| Field | Value |
|-------|-------|
| Package | `kiro` |
| Registry Key | `kiro` |
| Agent Type | `Kiro` |
| Binary | `kiro-cli` |
| Preview | Yes |
| Protected Dir | `.kiro` |

## Hook Events (5 CLI + 4 IDE)

| Kiro Hook (camelCase) | CLI Subcommand (kebab-case) | EventType | Notes |
|----------------------|----------------------------|-----------|-------|
| `agentSpawn` | `agent-spawn` | `SessionStart` | Agent initializes |
| `userPromptSubmit` | `user-prompt-submit` | `TurnStart` | stdin includes `prompt` field |
| `preToolUse` | `pre-tool-use` | `nil, nil` | Pass-through |
| `postToolUse` | `post-tool-use` | `nil, nil` | Pass-through |
| `stop` | `stop` | `TurnEnd` | Checkpoint trigger |

No `SessionEnd` hook exists — sessions end implicitly (similar to Cursor).

## Hook Configuration

### CLI Agent Hooks

**File:** `.kiro/agents/entire.json`

We own the entire file — no round-trip preservation needed (unlike Cursor's shared `hooks.json`).

Comment on lines +28 to +35
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions Kiro hook installation via .kiro/settings/hooks.json, but the integration (and this doc) uses .kiro/agents/entire.json. Please reconcile this so the PR description matches the actual implementation (or adjust the implementation if .kiro/settings/hooks.json is the intended target).

Copilot uses AI. Check for mistakes.
**Format:**
```json
{
"name": "entire",
"tools": ["read", "write", "shell", "grep", "glob", "aws", "report",
"introspect", "knowledge", "thinking", "todo", "delegate"],
"hooks": {
"agentSpawn": [{"command": "entire hooks kiro agent-spawn"}],
"userPromptSubmit": [{"command": "entire hooks kiro user-prompt-submit"}],
"preToolUse": [{"command": "entire hooks kiro pre-tool-use"}],
"postToolUse": [{"command": "entire hooks kiro post-tool-use"}],
"stop": [{"command": "entire hooks kiro stop"}]
}
}
```

Note: The file is a Kiro agent definition. Hooks must be nested under the `hooks` field.
Required top-level fields: `name`. Optional: `$schema`, `description`, `prompt`, `mcpServers`,
`tools`, `toolAliases`, `allowedTools`, `resources`, `hooks`, `toolsSettings`, `model`, etc.

**Important:** The `tools` array must include all default Kiro tools. Without it, `--agent entire`
restricts the model to zero tools. The tool names come from `~/.kiro/agents/agent_config.json.example`.

### IDE Hook Configuration

**Directory:** `.kiro/hooks/`

The Kiro IDE (VS Code extension) reads hooks from individual `.kiro/hooks/*.kiro.hook` files.
Unlike CLI hooks (nested in `entire.json`), each IDE hook is a standalone JSON file.

**IDE hook files installed (4 total):**

| File | `when.type` | Command |
|------|-------------|---------|
| `entire-prompt-submit.kiro.hook` | `promptSubmit` | `entire hooks kiro user-prompt-submit` |
| `entire-stop.kiro.hook` | `agentStop` | `entire hooks kiro stop` |
| `entire-pre-tool-use.kiro.hook` | `preToolUse` | `entire hooks kiro pre-tool-use` |
| `entire-post-tool-use.kiro.hook` | `postToolUse` | `entire hooks kiro post-tool-use` |

No `agentSpawn` IDE hook — the IDE has no such trigger. The first `promptSubmit` serves as session start.

**Format:**
```json
{
"enabled": true,
"name": "entire-prompt-submit",
"description": "Entire CLI promptSubmit hook",
"version": "1",
"when": {
"type": "promptSubmit"
},
"then": {
"type": "runCommand",
"command": "entire hooks kiro user-prompt-submit"
}
}
```

**Key difference from CLI hooks:** IDE hooks deliver data via environment variables (e.g.,
`USER_PROMPT` for the user's prompt) rather than JSON on stdin. The lifecycle parsers handle
empty stdin gracefully by reading from environment variables as a fallback.

## Agent Activation

Hooks only fire when `--agent entire` is passed to `kiro-cli chat`. Without this flag,
`.kiro/agents/entire.json` is not loaded and hooks do not execute.

IDE hooks in `.kiro/hooks/` are loaded automatically by the Kiro IDE without requiring
an explicit agent flag.

**`--no-interactive` mode:** Does not fire agent hooks. All E2E tests use interactive (tmux) mode.

**TUI prompt indicator:** `!>` in trust-all mode (with `-a` flag). The `Credits:` line
appears after each agent response and serves as a reliable completion marker.

## Hook Data Delivery

### CLI Mode (stdin JSON)

CLI hooks (`kiro-cli chat --agent entire`) receive a JSON structure on stdin:
```json
{
"hook_event_name": "userPromptSubmit",
"cwd": "/path/to/repo",
"prompt": "user message",
"tool_name": "fs_write",
"tool_input": "...",
"tool_response": "..."
}
```

Fields are populated based on the hook event — `prompt` only for `userPromptSubmit`, tool fields only for tool hooks.

### IDE Mode (environment variables)

IDE hooks (`.kiro/hooks/*.kiro.hook`) receive **no stdin**. Data is delivered via environment variables:
- `USER_PROMPT` — the user's prompt text (for `promptSubmit` hooks)

The lifecycle parsers handle empty stdin gracefully: if stdin is empty, they fall back to reading
environment variables for prompt data and use `paths.WorktreeRoot()` for CWD.

## Transcript Storage

**Source:** SQLite database at `~/Library/Application Support/kiro-cli/data.sqlite3` (macOS)
or `~/.local/share/kiro-cli/data.sqlite3` (Linux).

**Table:** `conversations_v2`
- `key` column: CWD path (used for lookup)
- `value` column: JSON blob with conversation data
- `updated_at` column: timestamp for ordering

**Conversation JSON structure:**
```json
{
"conversation_id": "uuid-v4",
"history": [
{"role": "user", "content": [{"type": "text", "text": "..."}]},
{"role": "assistant", "content": [{"type": "text", "text": "..."}, {"type": "tool_use", "name": "fs_write", "input": {...}}]},
{"role": "request_metadata", "input_tokens": 150, "output_tokens": 80}
]
}
```

## Session ID Discovery

Hook stdin does not include session ID. We query SQLite by CWD:
```sql
SELECT json_extract(value, '$.conversation_id')
FROM conversations_v2
WHERE key = '<cwd>'
ORDER BY updated_at DESC LIMIT 1
```

## SQLite Access Strategy

Uses `sqlite3` CLI (pre-installed on macOS/Linux) rather than a Go library to avoid CGO dependencies. Same approach as OpenCode's `opencode export` CLI.

**Test mock:** `ENTIRE_TEST_KIRO_MOCK_DB=1` env var causes the agent to skip SQLite queries and use pre-written mock files.

## File Modification Tools

Tools that modify files on disk:
- `fs_write` — write file content
- `str_replace` — string replacement in files
- `create_file` — create new files
- `write_file` — write/overwrite files
- `edit_file` — edit existing files

## Caching

Transcript data is cached to `.entire/tmp/<sessionID>.json` (same pattern as OpenCode).
Loading