Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 8 additions & 8 deletions .github/actions/setup-deps/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ runs:
GH_TOKEN: ${{ inputs.token }}
run: |
clone_with_retry() {
local repo=$1 dest=$2 branch=${3:-dev}
local repo=$1 dest=$2 branch=${3:-main}
for i in 1 2 3; do
git clone --depth=1 --branch "$branch" "https://x-access-token:${GH_TOKEN}@github.com/GrayCodeAI/${repo}.git" "$dest" && return 0
echo "Retry $i for $repo..." && sleep $((i * 5))
done
echo "Failed to clone $repo after 3 attempts" && return 1
}
mkdir -p external
clone_with_retry eyrie external/eyrie dev
clone_with_retry tok external/tok dev
clone_with_retry yaad external/yaad dev
clone_with_retry inspect external/inspect dev
clone_with_retry sight external/sight dev
clone_with_retry trace external/trace dev
clone_with_retry eyrie external/eyrie main
clone_with_retry tok external/tok main
clone_with_retry yaad external/yaad main
clone_with_retry inspect external/inspect main
clone_with_retry sight external/sight main
clone_with_retry trace external/trace main

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ inputs.go-version }}

Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | tr -d '%' | tail -1)
echo "Coverage: ${coverage}%"
echo "COVERAGE=${coverage}" >> "$GITHUB_ENV"
- name: Coverage threshold (minimum 50%)
- name: Coverage threshold (minimum 60%)
run: |
if (( $(echo "${COVERAGE} < 60" | bc -l) )); then
echo "::error::Coverage ${COVERAGE}% is below minimum 60%"
Expand Down Expand Up @@ -253,7 +253,6 @@ jobs:
- name: Run markdownlint-cli2
run: |
npm install -g markdownlint-cli2
printf '%s\n' '{"config":{"default":true,"line-length":false,"no-inline-html":false,"first-line-h1":false,"no-duplicate-heading":false,"no-emphasis-as-heading":false,"blanks-around-headings":false,"blanks-around-lists":false,"blanks-around-fences":false,"fenced-code-language":false,"table-column-style":false,"no-space-in-emphasis":false,"ol-prefix":false,"link-fragments":false,"blanks-around-tables":false,"table-column-count":false,"single-trailing-newline":false}}' > .markdownlint-cli2.jsonc
markdownlint-cli2 '**/*.md'

# -------------------------------------------------------------------------
Expand Down
5 changes: 0 additions & 5 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ linters:
enable-all: true
disable:
- fieldalignment
- shadow
- unusedwrite
- nilness
staticcheck:
checks:
- all
Expand All @@ -32,14 +30,11 @@ linters:
- -ST1020
- -ST1021
- -ST1018
- -SA4006
- -SA5011
- -SA1012
- -SA2001
- -SA4011
- -S1039
- -S1011
- -S1034
- -QF1003
- -QF1011
- -S1008
Expand Down
21 changes: 21 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"config": {
"default": true,
"line-length": false,
"no-inline-html": false,
"first-line-h1": false,
"no-duplicate-heading": false,
"no-emphasis-as-heading": false,
"blanks-around-headings": false,
"blanks-around-lists": false,
"blanks-around-fences": false,
"fenced-code-language": false,
"table-column-style": false,
"no-space-in-emphasis": false,
"ol-prefix": false,
"link-fragments": false,
"blanks-around-tables": false,
"table-column-count": false,
"single-trailing-newline": false
}
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ test: add coverage for guardian

## Anti-Patterns

- **No `os.Getenv` in `internal/`** — use `config.EnvManager` to centralize env access. Exception: `internal/observability/oteltrace/` for telemetry env vars.
- **No `os.Getenv` in `internal/`** — use `env.Getenv` (in `internal/env/`) for simple reads, or `config.Getenv` if the package can import `internal/config` without cycles. `config.EnvManager` is for profile/secret management. Exceptions: `internal/observability/oteltrace/` for telemetry env vars; runtime environment probes (e.g. `TMUX`, `STY`, `TERM_PROGRAM`, `SHELL`, `GOPATH`) which are set by the OS/terminal and not by config.
- **No `panic()` for error handling** — return `error` values. Exception: `init()` functions for package-level assertions.
- **No `fmt.Print` for logging** — use `logger.Logger` with structured fields. Exception: `internal/onboarding/` and `internal/engine/scaffold/` for user-facing CLI output.
- **No API keys in settings.json** — use OS secret store via `credentials` package and `/config` command.
Expand Down
2 changes: 1 addition & 1 deletion cmd/autoinit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestAutoInitRunner_WritesContextFileOnce(t *testing.T) {
}

ctxFile := filepath.Join(root, autoInitContextFile)
if _, err := os.Stat(ctxFile); err != nil {
if _, statErr := os.Stat(ctxFile); statErr != nil {
t.Fatalf("expected context file %s to be written: %v", autoInitContextFile, err)
}

Expand Down
26 changes: 13 additions & 13 deletions cmd/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting
startup.MarkPhase("newChatModel:configureSession")
syncSessionFromPersistedSelection(sess, settings)
sess.SetLogger(logger.New(io.Discard, logger.Error))
if err := configureSession(sess, settings); err != nil {
return chatModel{}, err
if cfgErr := configureSession(sess, settings); cfgErr != nil {
return chatModel{}, cfgErr
}
startup.EndPhase("newChatModel:configureSession")

Expand Down Expand Up @@ -399,15 +399,15 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting

// Prefetch live models for the active provider so footer ctx/pricing stay current.
go func() {
provider := effectiveProvider
entries, _ := runtime.ListModels(context.Background(), runtime.ListModelsOpts{ProviderID: provider, Source: runtime.ListSourceAuto})
providerName := effectiveProvider
entries, _ := runtime.ListModels(context.Background(), runtime.ListModelsOpts{ProviderID: providerName, Source: runtime.ListSourceAuto})
opts := configModelOptionsFromEyrie(entries)
if len(opts) > 0 {
modelCacheMu.Lock()
modelCache[provider] = opts
modelCache[providerName] = opts
modelCacheMu.Unlock()
if ref != nil {
ref.Send(modelsFetchedMsg{options: opts, provider: provider})
ref.Send(modelsFetchedMsg{options: opts, provider: providerName})
}
}
}()
Expand Down Expand Up @@ -1068,11 +1068,11 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.viewDirty = true
m.updateViewportContent()
cmds := []tea.Cmd{compactTickCmd()}
localCmds := []tea.Cmd{compactTickCmd()}
if !m.input.Focused() {
cmds = append(cmds, m.input.Focus())
localCmds = append(localCmds, m.input.Focus())
}
return m, tea.Batch(cmds...)
return m, tea.Batch(localCmds...)
}
return m, nil

Expand Down Expand Up @@ -1306,7 +1306,7 @@ func autoIndexCodegraph() {
}

dbPath := filepath.Join(cwd, ".codegraph", "codegraph.db")
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
if _, statErr := os.Stat(dbPath); os.IsNotExist(statErr) {
return // Not initialized, skip
}

Expand Down Expand Up @@ -1367,9 +1367,9 @@ func runChat() error {
ctx, cancel := context.WithCancel(context.Background())
_ = cancel // will be cancelled when program exits
go func() {
ch, err := sess.Stream(ctx)
if err != nil {
p.Send(streamErrMsg{err: err})
ch, streamErr := sess.Stream(ctx)
if streamErr != nil {
p.Send(streamErrMsg{err: streamErr})
return
}
pumpStreamEvents(ref, ch)
Expand Down
22 changes: 11 additions & 11 deletions cmd/chat_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,9 +802,9 @@ Generate the recap:`, summary.String())
case "/check":
return m.startPromptCommand("/check", buildCheckPrompt())
case "/design":
parts := strings.Fields(text)
if len(parts) >= 2 {
switch parts[1] {
fields := strings.Fields(text)
if len(fields) >= 2 {
switch fields[1] {
case "screenshot":
path := strings.TrimSpace(strings.TrimPrefix(text, "/design screenshot"))
if path == "" {
Expand Down Expand Up @@ -922,11 +922,11 @@ Generate the recap:`, summary.String())
m.messages = append(m.messages, displayMsg{role: "error", content: err.Error()})
return m, nil
}
model, provider := effectiveModelAndProvider(settings)
if provider == "" {
provider = "auto"
modelName, providerName := effectiveModelAndProvider(settings)
if providerName == "" {
providerName = "auto"
}
m.messages = append(m.messages, displayMsg{role: "system", content: hawkconfig.FormatEcosystemPanel(context.Background(), provider, model)})
m.messages = append(m.messages, displayMsg{role: "system", content: hawkconfig.FormatEcosystemPanel(context.Background(), providerName, modelName)})
return m, nil
case "/path":
m.messages = append(m.messages, displayMsg{role: "system", content: hawkconfig.FormatDeveloperPathReport(context.Background())})
Expand Down Expand Up @@ -1294,11 +1294,11 @@ Generate the recap:`, summary.String())
return
}

text := strings.TrimSpace(string(transcription))
if text != "" {
m.input.SetValue(text)
transcript := strings.TrimSpace(string(transcription))
if transcript != "" {
m.input.SetValue(transcript)
m.input.CursorEnd()
m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Voice input: %s", text)})
m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Voice input: %s", transcript)})
}
}()
}
Expand Down
36 changes: 18 additions & 18 deletions cmd/chat_config_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ func (m chatModel) configKeyDetailView() string {
mutedStyle := configMutedStyle()
accentStyle := configAccentStyle()
activeStyle := configActiveStyle()
provider := strings.TrimSpace(m.configProvider)
displayName := hawkconfig.GatewayDisplayName(provider)
masked := hawkconfig.MaskCredentialForProvider(context.Background(), provider)
providerName := strings.TrimSpace(m.configProvider)
displayName := hawkconfig.GatewayDisplayName(providerName)
masked := hawkconfig.MaskCredentialForProvider(context.Background(), providerName)

var b strings.Builder
b.WriteString(renderConfigBreadcrumb(displayName+" key") + "\n\n")
b.WriteString(mutedStyle.Render(" Gateway: ") + accentStyle.Render(displayName) + "\n")
b.WriteString(mutedStyle.Render(" Key: ") + activeStyle.Render(masked) + "\n")
b.WriteString(mutedStyle.Render(" Stored in: "+credentialsStoreLabel()) + "\n")
if provider == hawkconfig.ProviderXiaomiTokenPlan {
if providerName == hawkconfig.ProviderXiaomiTokenPlan {
reg := hawkconfig.XiaomiTokenPlanRegionLabel()
if reg == "" {
reg = "(not set — press g)"
Expand Down Expand Up @@ -111,61 +111,61 @@ func (m chatModel) clearConfigGatewayKeyRemove() chatModel {
}

func (m chatModel) advanceConfigGatewayKeyRemove() (chatModel, tea.Cmd) {
provider := strings.TrimSpace(m.configKeysPendingRemove)
if provider == "" {
trimmedProvider := strings.TrimSpace(m.configKeysPendingRemove)
if trimmedProvider == "" {
return m, nil
}
if m.configKeysRemoveStep < 2 {
m.configKeysRemoveStep = 2
name := hawkconfig.GatewayDisplayName(provider)
name := hawkconfig.GatewayDisplayName(trimmedProvider)
m.configNotice = configGatewayRemoveNotice(2, name)
return m, nil
}
return m.confirmConfigGatewayKeyRemove()
}

func (m chatModel) confirmConfigGatewayKeyRemove() (chatModel, tea.Cmd) {
provider := strings.TrimSpace(m.configKeysPendingRemove)
if provider == "" {
trimmedProvider := strings.TrimSpace(m.configKeysPendingRemove)
if trimmedProvider == "" {
return m, nil
}
m.configKeysPendingRemove = ""
m.configKeysRemoveStep = 0
m.configSaving = true
m.configNotice = fmt.Sprintf("Removing key for %s…", hawkconfig.GatewayDisplayName(provider))
m.configNotice = fmt.Sprintf("Removing key for %s…", hawkconfig.GatewayDisplayName(trimmedProvider))
if m.configEntry == configEntryKeyView {
m.configEntry = configEntryNone
m.configProvider = ""
}
return m, removeCredentialAsync(provider)
return m, removeCredentialAsync(trimmedProvider)
}

func (m chatModel) handleConfigKeyViewKey(msg tea.KeyMsg) (chatModel, tea.Cmd) {
provider := strings.TrimSpace(m.configProvider)
trimmedProvider := strings.TrimSpace(m.configProvider)
switch msg.Type {
case tea.KeyEsc:
m.configEntry = configEntryNone
m.configProvider = ""
m = m.clearConfigGatewayKeyRemove()
if idx := m.configGatewayRowIndex(provider); idx >= 0 {
if idx := m.configGatewayRowIndex(trimmedProvider); idx >= 0 {
m.configSel = idx
}
return m, nil
case tea.KeyDelete, tea.KeyBackspace:
if provider == "" {
if trimmedProvider == "" {
return m, nil
}
return m.beginConfigGatewayKeyRemove(provider), nil
return m.beginConfigGatewayKeyRemove(trimmedProvider), nil
case tea.KeyEnter:
if m.configKeysPendingRemove != "" {
return m.advanceConfigGatewayKeyRemove()
}
if provider == "" {
if trimmedProvider == "" {
return m, nil
}
return m.startConfigKeyReplace(provider)
return m.startConfigKeyReplace(trimmedProvider)
case tea.KeyRunes:
if provider == hawkconfig.ProviderXiaomiTokenPlan && strings.EqualFold(string(msg.Runes), "g") {
if trimmedProvider == hawkconfig.ProviderXiaomiTokenPlan && strings.EqualFold(string(msg.Runes), "g") {
return m.startConfigXiaomiTokenPlanRegion(), nil
}
default:
Expand Down
Loading
Loading