diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4dd29ed --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - uses: actions/setup-go@v6 + with: + go-version: "stable" + + - name: Unit tests (agent-kiro) + working-directory: agents/entire-agent-kiro + run: go test -race -count=1 ./... + + - name: Build (agent-kiro) + working-directory: agents/entire-agent-kiro + run: go build -o /dev/null ./cmd/entire-agent-kiro diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..509821d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,44 @@ +name: Lint + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - uses: actions/setup-go@v6 + with: + go-version: "stable" + + - name: Check formatting + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "The following files are not formatted:" + echo "$unformatted" + exit 1 + fi + + - name: Run golangci-lint (agent-kiro) + uses: golangci/golangci-lint-action@v9 + with: + version: "v2.11.3" + working-directory: agents/entire-agent-kiro + + - name: Run golangci-lint (e2e) + uses: golangci/golangci-lint-action@v9 + with: + version: "v2.11.3" + working-directory: e2e diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8c813d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Entire CLI runtime data +.entire/ + +# Built binaries +/agents/entire-agent-kiro/entire-agent-kiro diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..dcbbc01 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,93 @@ +version: "2" + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + +linters: + default: standard + enable: + - asciicheck + - bidichk + - copyloopvar + - decorder + - durationcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exptostd + - forcetypeassert + - gocheckcompilerdirectives + - gochecknoinits + - goconst + - gocritic + - gosec + - govet + - grouper + - importas + - ineffassign + - intrange + - makezero + - mirror + - misspell + - nakedret + - nilerr + - nilnesserr + - nolintlint + - perfsprint + - reassign + - recvcheck + - revive + - staticcheck + - testableexamples + - testifylint + - tparallel + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - wastedassign + - whitespace + settings: + errcheck: + check-type-assertions: true + goconst: + min-occurrences: 5 + gosec: + excludes: + - G204 # subprocess with variables is expected for CLI tools + - G304 # file inclusion via variable is expected for CLI tools + - G703 # path traversal via taint — paths are constructed from controlled inputs + - G705 # XSS not applicable to CLI stderr output + govet: + enable-all: true + disable: + - fieldalignment + - shadow + nolintlint: + require-explanation: true + require-specific: true + testifylint: + enable-all: true + exclusions: + presets: + - comments + - std-error-handling + rules: + - path: _test\.go + linters: + - errchkjson + - gosec + - goconst + - revive + - unparam + - path: ^e2e/ + linters: + - errcheck + - gochecknoinits + - goconst + - gosec + - revive + - usetesting diff --git a/agents/entire-agent-kiro/internal/kiro/agent.go b/agents/entire-agent-kiro/internal/kiro/agent.go index 2f55faf..d64b638 100644 --- a/agents/entire-agent-kiro/internal/kiro/agent.go +++ b/agents/entire-agent-kiro/internal/kiro/agent.go @@ -9,6 +9,8 @@ import ( "github.com/entireio/external-agents/agents/entire-agent-kiro/internal/protocol" ) +const stubSessionID = "stub-session-000" + type Agent struct{} func New() *Agent { @@ -48,7 +50,7 @@ func (a *Agent) GetSessionID(input *protocol.HookInputJSON) string { if input != nil && input.SessionID != "" { return input.SessionID } - return "stub-session-000" + return stubSessionID } func (a *Agent) ReadSession(input *protocol.HookInputJSON) (protocol.AgentSessionJSON, error) { @@ -58,7 +60,7 @@ func (a *Agent) ReadSession(input *protocol.HookInputJSON) (protocol.AgentSessio if err != nil { return protocol.AgentSessionJSON{}, err } - sessionRef := "" + var sessionRef string if input != nil && input.SessionRef != "" { sessionRef = input.SessionRef } else { diff --git a/agents/entire-agent-kiro/internal/kiro/hooks.go b/agents/entire-agent-kiro/internal/kiro/hooks.go index a6947f5..77de4a1 100644 --- a/agents/entire-agent-kiro/internal/kiro/hooks.go +++ b/agents/entire-agent-kiro/internal/kiro/hooks.go @@ -26,8 +26,8 @@ const ( localDevCommandBase = "go run ${KIRO_PROJECT_DIR}/cmd/entire/main.go hooks kiro " localDevTrustedCmd = "sh -c 'go run ${KIRO_PROJECT_DIR}/cmd/entire/main.go hooks *" prodHookCommandBase = "entire hooks kiro " - sessionIDFile = "kiro-active-session" - toolCallsFile = "kiro-tool-calls.jsonl" + sessionIDFile = "kiro-active-session" + toolCallsFile = "kiro-tool-calls.jsonl" ) type ideHookDef struct { @@ -504,7 +504,6 @@ func (a *Agent) sessionIDCachePath() string { return filepath.Join(protocol.RepoRoot(), ".entire", "tmp", sessionIDFile) } - func fallbackStopSessionID() string { return generateSessionID() } diff --git a/agents/entire-agent-kiro/internal/kiro/lifecycle_test.go b/agents/entire-agent-kiro/internal/kiro/lifecycle_test.go index 639af30..2d509be 100644 --- a/agents/entire-agent-kiro/internal/kiro/lifecycle_test.go +++ b/agents/entire-agent-kiro/internal/kiro/lifecycle_test.go @@ -75,7 +75,7 @@ func TestParseHookUserPromptSubmitSupportsIDEFallback(t *testing.T) { if event.Type != 2 { t.Fatalf("event.Type = %d, want %d", event.Type, 2) } - if event.SessionID == "" || event.SessionID == "stub-session-000" { + if event.SessionID == "" || event.SessionID == stubSessionID { t.Fatalf("session_id = %q, want generated stable ID", event.SessionID) } if event.Prompt != "ide prompt" { @@ -158,7 +158,7 @@ func TestParseHookStopWithoutCachedSessionIDUsesNonPredictableFallback(t *testin if event.SessionID == "" { t.Fatal("session_id should not be empty") } - if event.SessionID == "my-repo" || event.SessionID == "stub-session-000" { + if event.SessionID == "my-repo" || event.SessionID == stubSessionID { t.Fatalf("session_id = %q, want generated non-predictable fallback", event.SessionID) } diff --git a/agents/entire-agent-kiro/internal/kiro/transcript.go b/agents/entire-agent-kiro/internal/kiro/transcript.go index a29ebcc..50aee71 100644 --- a/agents/entire-agent-kiro/internal/kiro/transcript.go +++ b/agents/entire-agent-kiro/internal/kiro/transcript.go @@ -850,7 +850,10 @@ func convertExecutionActionsToHistoryEntries(actions []kiroExecutionAction) []ki if toolName, ok := execActionToToolName[action.ActionType]; ok { filePath := extractFilePath(action.Input) if filePath != "" { - args, _ := json.Marshal(map[string]string{"path": filePath}) + args, err := json.Marshal(map[string]string{"path": filePath}) + if err != nil { + continue + } toolCalls = append(toolCalls, kiroToolCall{ Name: toolName, Args: args, @@ -864,26 +867,32 @@ func convertExecutionActionsToHistoryEntries(actions []kiroExecutionAction) []ki if err := json.Unmarshal(action.Output, &out); err == nil && out.Message != "" { // Flush any pending tool calls first if len(toolCalls) > 0 { - toolUseJSON, _ := json.Marshal(kiroToolUseContent{ + toolUseJSON, err := json.Marshal(kiroToolUseContent{ ToolUse: kiroToolUsePayload{ToolUses: toolCalls}, }) - entries = append(entries, kiroHistoryEntry{Assistant: toolUseJSON}) + if err == nil { + entries = append(entries, kiroHistoryEntry{Assistant: toolUseJSON}) + } toolCalls = nil } - responseJSON, _ := json.Marshal(kiroResponseContent{ + responseJSON, err := json.Marshal(kiroResponseContent{ Response: kiroResponsePayload{Content: out.Message}, }) - entries = append(entries, kiroHistoryEntry{Assistant: responseJSON}) + if err == nil { + entries = append(entries, kiroHistoryEntry{Assistant: responseJSON}) + } } } } // Flush remaining tool calls without a trailing say if len(toolCalls) > 0 { - toolUseJSON, _ := json.Marshal(kiroToolUseContent{ + toolUseJSON, err := json.Marshal(kiroToolUseContent{ ToolUse: kiroToolUsePayload{ToolUses: toolCalls}, }) - entries = append(entries, kiroHistoryEntry{Assistant: toolUseJSON}) + if err == nil { + entries = append(entries, kiroHistoryEntry{Assistant: toolUseJSON}) + } } return entries @@ -924,9 +933,7 @@ func enrichIDETranscriptWithExecutionLogs(ideData []byte, execLogs map[string]*k if len(assistantEntries) > 0 { userEntry.Assistant = assistantEntries[0].Assistant transcript.History = append(transcript.History, userEntry) - for _, extra := range assistantEntries[1:] { - transcript.History = append(transcript.History, extra) - } + transcript.History = append(transcript.History, assistantEntries[1:]...) pendingUser = nil continue } diff --git a/agents/entire-agent-kiro/internal/kiro/types.go b/agents/entire-agent-kiro/internal/kiro/types.go index 8c65247..10e7bea 100644 --- a/agents/entire-agent-kiro/internal/kiro/types.go +++ b/agents/entire-agent-kiro/internal/kiro/types.go @@ -134,18 +134,6 @@ type kiroIDEHistoryMeta struct { // from the session files and contain the full action trace (tool calls, // agent responses, file modifications) for each agent turn. -type kiroExecutionIndex struct { - Executions []kiroExecutionIndexEntry `json:"executions"` -} - -type kiroExecutionIndexEntry struct { - ExecutionID string `json:"executionId"` - Type string `json:"type"` - Status string `json:"status"` - StartTime int64 `json:"startTime"` - EndTime int64 `json:"endTime"` -} - type kiroExecutionLog struct { ExecutionID string `json:"executionId"` ChatSessionID string `json:"chatSessionId"` diff --git a/agents/entire-agent-kiro/internal/protocol/protocol.go b/agents/entire-agent-kiro/internal/protocol/protocol.go index ae93d3b..39ff7fb 100644 --- a/agents/entire-agent-kiro/internal/protocol/protocol.go +++ b/agents/entire-agent-kiro/internal/protocol/protocol.go @@ -3,7 +3,6 @@ package protocol import ( "encoding/json" "flag" - "fmt" "io" "os" "path/filepath" @@ -309,5 +308,5 @@ func DefaultSessionDir(repoPath string) string { } func ResolveSessionFile(sessionDir, sessionID string) string { - return filepath.Join(sessionDir, fmt.Sprintf("%s.json", sessionID)) + return filepath.Join(sessionDir, sessionID+".json") }