Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"schema": 1,
"task": "Fix registered assistants tab showing empty by surfacing codemieAssistants (and codemieSkills) from the MultiProviderConfig top level through ConfigLoader.load().",
"generated": "2026-06-23T00:00:00Z",
"dimensions": {
"component_scope": { "score": 2, "label": "S" },
"requirements_clarity": { "score": 1, "label": "XS" },
"technical_risk": { "score": 1, "label": "XS" },
"file_change_estimate": { "score": 1, "label": "XS" },
"dependencies": { "score": 1, "label": "XS" },
"affected_layers": { "score": 1, "label": "XS" }
},
"total": 7,
"size": "XS",
"routing": "writing-plans",
"key_reasoning": [
{
"dimension": "component_scope",
"reason": "ConfigLoader (src/utils/config.ts) is a core shared utility used across the entire codebase; even though only two return statements change, the scope red flag bumps this from XS to S."
},
{
"dimension": "requirements_clarity",
"reason": "Root cause is fully identified: loadGlobalConfigProfile line 228 and loadLocalConfigProfile line 255 need codemieAssistants and codemieSkills added to their return spreads. Zero ambiguity."
}
],
"red_flags_applied": [
"Component Scope bumped from XS (1) to S (2): touches core shared utility ConfigLoader (src/utils/config.ts) used across the entire codebase"
],
"split_recommendation": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Fix Registered Assistants Empty Tab — 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:** Surface `codemieAssistants` and `codemieSkills` from the `MultiProviderConfig` root through `ConfigLoader.load()` so the Registered Assistants tab is no longer empty after setup.

**Architecture:** Two surgical changes: (1) add `codemieSkills` to `ProviderProfile` so the type accepts the field, then (2) include both top-level fields in the return objects of `loadGlobalConfigProfile` and `loadLocalConfigProfile`. The `load()` method already uses `removeUndefined()` before `Object.assign`, so `undefined` from a local config that lacks these fields will not overwrite values from the global config.

**Tech Stack:** TypeScript, Vitest

## Global Constraints

- ES modules only — all imports use `.js` extensions
- No `any` types
- `CodeMieConfigOptions = ProviderProfile` — adding a field to `ProviderProfile` is sufficient for it to be available on `CodeMieConfigOptions`

---

### Task 1: Add `codemieSkills` to `ProviderProfile` and fix both load helpers

**Files:**
- Modify: `src/env/types.ts` — add `codemieSkills?: CodemieSkill[]` to `ProviderProfile`
- Modify: `src/utils/config.ts:228` — include `codemieAssistants` + `codemieSkills` in `loadGlobalConfigProfile` return
- Modify: `src/utils/config.ts:255` — include `codemieAssistants` + `codemieSkills` in `loadLocalConfigProfile` return
- Test: `src/utils/__tests__/config-project-override.test.ts` — add round-trip tests

**Interfaces:**
- Consumes: `MultiProviderConfig.codemieAssistants`, `MultiProviderConfig.codemieSkills` (both already defined in `src/env/types.ts:172-173`)
- Produces: `ConfigLoader.load()` returning `codemieAssistants` and `codemieSkills` when present in the raw config

- [ ] **Step 1: Write the failing tests**

Add a new `describe` block at the bottom of `src/utils/__tests__/config-project-override.test.ts`:

```typescript
describe('ConfigLoader - top-level fields (codemieAssistants / codemieSkills)', () => {
it('load() returns codemieAssistants from global MultiProviderConfig root', async () => {
const workingDir = path.join(TEST_DIR, 'project');
const globalConfig: MultiProviderConfig = {
version: 2,
activeProfile: 'default',
codemieAssistants: [
{
id: 'ast-1',
name: 'Brianna',
slug: 'brianna',
description: 'Jira assistant',
registeredAt: '2026-06-23T00:00:00.000Z',
},
],
profiles: {
default: { provider: 'openai', model: 'gpt-4o', apiKey: 'test-key' },
},
};
await fs.writeFile(GLOBAL_CONFIG_PATH, JSON.stringify(globalConfig));

const config = await ConfigLoader.load(workingDir);

expect(config.codemieAssistants).toHaveLength(1);
expect(config.codemieAssistants![0].slug).toBe('brianna');
});

it('load() returns codemieSkills from global MultiProviderConfig root', async () => {
const workingDir = path.join(TEST_DIR, 'project');
const globalConfig: MultiProviderConfig = {
version: 2,
activeProfile: 'default',
codemieSkills: [
{
id: 'sk-1',
name: 'My Skill',
slug: 'my-skill',
description: 'A test skill',
registeredAt: '2026-06-23T00:00:00.000Z',
},
],
profiles: {
default: { provider: 'openai', model: 'gpt-4o', apiKey: 'test-key' },
},
};
await fs.writeFile(GLOBAL_CONFIG_PATH, JSON.stringify(globalConfig));

const config = await ConfigLoader.load(workingDir);

expect(config.codemieSkills).toHaveLength(1);
expect(config.codemieSkills![0].slug).toBe('my-skill');
});

it('local config without codemieAssistants does not overwrite global values', async () => {
const workingDir = path.join(TEST_DIR, 'project');
const globalConfig: MultiProviderConfig = {
version: 2,
activeProfile: 'default',
codemieAssistants: [
{
id: 'ast-1',
name: 'Brianna',
slug: 'brianna',
description: 'Jira assistant',
registeredAt: '2026-06-23T00:00:00.000Z',
},
],
profiles: {
default: { provider: 'openai', model: 'gpt-4o', apiKey: 'global-key' },
},
};
const localConfig: MultiProviderConfig = {
version: 2,
activeProfile: 'default',
// no codemieAssistants
profiles: {
default: { provider: 'openai', model: 'gpt-4o-mini' },
},
};
await fs.writeFile(GLOBAL_CONFIG_PATH, JSON.stringify(globalConfig));
await fs.writeFile(LOCAL_CONFIG_PATH, JSON.stringify(localConfig));

const config = await ConfigLoader.load(workingDir);

// Global codemieAssistants must survive the local config overlay
expect(config.codemieAssistants).toHaveLength(1);
expect(config.codemieAssistants![0].slug).toBe('brianna');
});
});
```

- [ ] **Step 2: Run tests to verify they fail**

```bash
npx vitest run src/utils/__tests__/config-project-override.test.ts --reporter=verbose 2>&1 | tail -30
```

Expected: 3 failures — `config.codemieAssistants` is `undefined` and `config.codemieSkills` is `undefined`.

- [ ] **Step 3: Add `codemieSkills` to `ProviderProfile` in `src/env/types.ts`**

Locate the existing `codemieAssistants` entry in `ProviderProfile` (around line 123) and add `codemieSkills` directly below it:

```typescript
// In-memory assistants/skills state (not persisted here; stored at MultiProviderConfig level)
codemieAssistants?: CodemieAssistant[];
codemieSkills?: CodemieSkill[];
```

- [ ] **Step 4: Fix `loadGlobalConfigProfile` return in `src/utils/config.ts`**

Replace the return statement at line 228 (inside the `if (isMultiProviderConfig(rawConfig))` block):

Before:
```typescript
// Return profile with name included
return { ...rawConfig.profiles[profile], name: profile };
```

After:
```typescript
// Return profile with name included; codemieAssistants and codemieSkills live at
// MultiProviderConfig root (not inside a profile) so they must be forwarded explicitly.
return {
...rawConfig.profiles[profile],
name: profile,
codemieAssistants: rawConfig.codemieAssistants,
codemieSkills: rawConfig.codemieSkills,
};
```

- [ ] **Step 5: Fix `loadLocalConfigProfile` return in `src/utils/config.ts`**

Replace the return statement at line 255–256 (inside `if (profile && rawConfig.profiles[profile])`):

Before:
```typescript
return { ...rawConfig.profiles[profile], name: profile };
```

After:
```typescript
// codemieAssistants and codemieSkills live at MultiProviderConfig root; forward them
// so load() can overlay them. removeUndefined() in load() strips undefined before
// Object.assign, so a local config without these fields won't overwrite global values.
return {
...rawConfig.profiles[profile],
name: profile,
codemieAssistants: rawConfig.codemieAssistants,
codemieSkills: rawConfig.codemieSkills,
};
```

- [ ] **Step 6: Run tests to verify they pass**

```bash
npx vitest run src/utils/__tests__/config-project-override.test.ts --reporter=verbose 2>&1 | tail -30
```

Expected: all tests in the file pass, including the 3 new ones.

- [ ] **Step 7: Typecheck**

```bash
npm run typecheck 2>&1 | tail -20
```

Expected: no errors.

- [ ] **Step 8: Commit**

```bash
git add src/env/types.ts src/utils/config.ts src/utils/__tests__/config-project-override.test.ts
git commit -m "fix(config): surface codemieAssistants and codemieSkills from MultiProviderConfig root through ConfigLoader.load()"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# QA Gate Report — fix/registered-assistants-empty-tab

**Branch**: fix/registered-assistants-empty-tab
**Runner**: npm
**Started**: 2026-06-23T16:33:00Z
**Status**: PASSED

## Gates

| Gate | Status | Duration | Command | Notes |
|---------------|---------|----------|--------------------------------|---------------------------------------|
| license-check | PASS | ~3s | `npm run license-check` | 457 MIT + 110 other packages OK |
| lint | PASS | ~4s | `npm run lint` | Zero errors, zero warnings |
| typecheck | PASS | ~8s | `npm run typecheck` | No diagnostics |
| build | PASS | ~15s | `npm run build` | `dist/` rebuilt, copy-plugin OK |
| unit | PASS | ~27s | `npm run test:unit` | 2095 passed, 3 skipped (local-only transcripts) |
| integration | PASS | ~200s | `npm run test:integration` | 220 passed, 1 skipped |
| ui | SKIPPED | — | (n/a) | No UI surface changed |

## Failure detail

None.

## Drift signal

no
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Spec — Fix Registered Assistants Empty Tab

## Problem

`ConfigLoader.load()` calls `loadGlobalConfigProfile()` and `loadLocalConfigProfile()`, both of which return only the selected `ProviderProfile` spread. `codemieAssistants` and `codemieSkills` are stored at the `MultiProviderConfig` root (not inside any profile), so they are silently dropped on every `load()` call.

The save path (`saveAssistantsToProjectConfig`) writes to the correct root location — saves are correct. Reads are broken. As a result, `setupAssistants()` always receives `codemieAssistants: undefined`, the Registered tab shows empty, and any assistants that were registered appear to be gone.

## Acceptance Criteria

- After setup, navigating to Registered Assistants shows the previously registered assistants.
- `ConfigLoader.load()` returns `codemieAssistants` and `codemieSkills` from the `MultiProviderConfig` root when they are present.
- Local config without these fields does not overwrite global values (existing `removeUndefined()` behaviour in `load()` guarantees this).
- No regression to profile selection, env-var priority, or CLI-override behaviour.

## Changes

### `src/env/types.ts`

Add `codemieSkills?: CodemieSkill[]` to `ProviderProfile` alongside the existing `codemieAssistants` entry:

```typescript
// In-memory assistants/skills state (not persisted here; stored at MultiProviderConfig level)
codemieAssistants?: CodemieAssistant[];
codemieSkills?: CodemieSkill[];
```

This is required because `CodeMieConfigOptions = ProviderProfile`, so the field must exist on the type to survive the return from `loadGlobalConfigProfile` / `loadLocalConfigProfile`.

### `src/utils/config.ts`

**`loadGlobalConfigProfile` (line 228)** — include top-level fields in the return:

```typescript
// codemieAssistants and codemieSkills live at MultiProviderConfig root, not inside a profile
return {
...rawConfig.profiles[profile],
name: profile,
codemieAssistants: rawConfig.codemieAssistants,
codemieSkills: rawConfig.codemieSkills,
};
```

**`loadLocalConfigProfile` (line 255)** — same change:

```typescript
// codemieAssistants and codemieSkills live at MultiProviderConfig root, not inside a profile
return {
...rawConfig.profiles[profile],
name: profile,
codemieAssistants: rawConfig.codemieAssistants,
codemieSkills: rawConfig.codemieSkills,
};
```

When local config does not define these fields the values are `undefined`. `removeUndefined()` in `load()` strips `undefined` values before `Object.assign`, so a local config without these fields will not overwrite values loaded from global config.

## Out of Scope

- Moving `codemieAssistants`/`codemieSkills` into profiles (data-migration scope).
- Changes to save paths — they already write to the correct root location.
- Changes to `setupAssistants`, `fetchRegisteredFromConfig`, or the UI layer — they already handle the data correctly once `ConfigLoader.load()` returns it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Technical Analysis — Registered Assistants Empty Tab

## Codebase Findings

### Root Cause

`ConfigLoader.loadGlobalConfigProfile()` (line 228) and `loadLocalConfigProfile()` (line 255–256) in `src/utils/config.ts` spread only the per-profile data when returning:

```typescript
return { ...rawConfig.profiles[profile], name: profile };
```

`codemieAssistants` is stored at the **top level** of `MultiProviderConfig` (not inside a profile), so it is silently dropped on every `ConfigLoader.load()` call. The field is always `undefined` in the returned `CodeMieConfigOptions`.

### Save Path (correct, unaffected)

`saveAssistantsToProjectConfig()` (line 858) loads the raw `MultiProviderConfig` via `loadConfigByScope()`, sets `config.codemieAssistants = assistants`, and writes it back. This is correct — saves land in the right place.

### Read Path (broken)

`setupAssistants()` calls `ConfigLoader.load()` → receives `codemieAssistants: undefined` → `registeredAssistants = []` → passes empty config to `createDataFetcher()` → `fetchRegisteredFromConfig()` returns `[]` → Registered tab appears empty.

### Affected Files

| File | Role | Change needed |
|---|---|---|
| `src/utils/config.ts:228` | `loadGlobalConfigProfile` return | Add `codemieAssistants: rawConfig.codemieAssistants` |
| `src/utils/config.ts:255` | `loadLocalConfigProfile` return | Add `codemieAssistants: rawConfig.codemieAssistants` |
| `src/cli/commands/assistants/setup/data.ts:106` | `fetchRegisteredFromConfig` | No change needed |
| `src/cli/commands/assistants/setup/index.ts:69` | `setupAssistants` | No change needed |

### Secondary: codemieSkills

`MultiProviderConfig.codemieSkills` (line 172, `src/env/types.ts`) has the same structural gap — also a top-level field not returned by either load helper. Fix both fields together.

### Type Safety

`ProviderProfile.codemieAssistants` already exists with comment "In-memory assistants/skills state (not persisted here; stored at MultiProviderConfig level)" (`src/env/types.ts:123–124`). No type changes required.

### Testing Landscape

- No existing test exercises `ConfigLoader.load()` → `codemieAssistants` propagation end-to-end.
- Data-layer tests mock config directly (bypassing `ConfigLoader.load()`), so the bug was invisible to them.
- A round-trip test (`saveAssistantsToProjectConfig` → `ConfigLoader.load()` → assert field present) in `config-project-override.test.ts` would prevent recurrence.

## Risk Indicators

1. `codemieSkills` has the identical gap — fix both together
2. `loadLocalConfigProfile`: `rawConfig.codemieAssistants` may be `undefined` when local config doesn't define it; `removeUndefined()` in `ConfigLoader.load()` safely strips it before `Object.assign`
3. No regression test guards this path — easy to re-break
4. Once fixed, the selection UI will correctly default to the Registered panel (not Project) when assistants exist — behavioral change reviewers should note
Loading
Loading