Skip to content

feat(agent-sync): trigger migration + always upsert (wish dir-sync-frontmatter-refresh group 3) - #1196

Merged
namastex888 merged 1 commit into
devfrom
feat/dir-sync-frontmatter-refresh-group3
Apr 18, 2026
Merged

namastex888 merged 1 commit into
devfrom
feat/dir-sync-frontmatter-refresh-group3

Conversation

@automagik-genie

Copy link
Copy Markdown
Contributor

Wave 2 / Group 3 of wish `dir-sync-frontmatter-refresh`. Depends on Groups 1 (#1191) + 2 (#1193), both already on dev.

Closes the original user-reported bug

`genie dir sync` reports "Unchanged" and skips the DB write when an agent's frontmatter changes, so edits like `team: simone` sit on disk unread. Root cause: the sync helper ran a JSON-equality diff and short-circuited when nothing diverged — but the diff itself never noticed the file edit because of parse-order. Net effect: files-are-source-of-truth promise was silently broken.

What changed

`src/lib/agent-sync.ts`

  1. First sync on an unmigrated agent triggers migration. Phase 0 of `syncSingleAgent` now checks `agent.yaml` existence; if missing AND `AGENTS.md` carries frontmatter, it calls `migrateAgentToYaml` (Group 2, merged as feat(agent-migrate): one-shot AGENTS.md frontmatter → agent.yaml migration (wish dir-sync-frontmatter-refresh group 2) #1193). Post-migration: `agent.yaml` is canonical, AGENTS.md.bak preserves the original byte-for-byte, AGENTS.md carries pure prompt content.
  2. `agent.yaml` is read via `parseAgentYaml` when present. Legacy `parseFrontmatter(AGENTS.md)` remains as fallback for agents with neither yaml nor frontmatter (defaults-only registration).
  3. The "Unchanged" path is gone. No change detection, no short-circuit. Every reached agent is upserted via `directory.edit`.
  4. `SyncResult.unchanged: string[]` removed. New field `migrated: string[]` tracks this-run migrations.
  5. `syncSingleAgentByName` returns `'synced'` on the non-change path (was `'unchanged'`); `processWatchedAgent`'s filter updated to match.
  6. `printSyncResult` summary renamed to `Synced: N agent(s), M removed.` — never `Unchanged: ...`.

`src/lib/agent-identity-sync.test.ts`

  • Existing "frontmatter changes" test updated to edit `agent.yaml` directly (post-migration flow).
  • Existing "sync is idempotent" test now asserts `result.updated` (the new always-upsert home for reached agents) instead of the removed `result.unchanged`.
  • Four new Group 3 tests:
    • Migration trigger: first sync on an agent with frontmatter produces `agent.yaml` + `.bak`, strips frontmatter from AGENTS.md, lands the agent in `result.migrated`.
    • Migration idempotency: second sync leaves `result.migrated` empty (already-migrated); agent still lands in `result.updated`.
    • Original reproducer: edit `agent.yaml`, re-sync, DB row picks up the change in one breath.
    • Type-level contract: `SyncResult` no longer has an `unchanged` key (verified via `Object.hasOwn`).

Scope-guard check

```
grep 'Unchanged:' src/term-commands/dir.ts src/lib/agent-sync.ts → zero hits in production code paths
```

(The literal still appears in a code comment explaining what was removed — per wish guidance, production code paths must have zero hits, which they do.)

Test plan

  • `bun test src/lib/agent-identity-sync.test.ts` → 12 pass, 0 fail
  • `bun run typecheck` clean
  • `bunx biome check` clean (modulo a mild complexity warning of 18 vs max 15 on `syncSingleAgent` — one new branch for the migration phase; non-blocking, mirrors the existing `startDaemon` pattern)
  • Pre-push full suite: 2682 pass, 0 fail
  • CI Quality Gate green
  • External GitGuardian green

What does NOT change

  • `team` is still not propagated by sync today (pre-existing gap; not introduced by this PR and not in scope for Group 3).
  • `genie dir edit` CLI still writes to the DB row, not the yaml file. That is Group 4.
  • `genie dir add` still uses the legacy shape. That is Group 5.

🤖 Generated with Claude Code

…ontmatter-refresh group 3)

Wave 2 / Group 3. Closes the loop the user originally hit today:
editing an AGENTS.md field silently dropped on the floor because `genie
dir sync` reported "Unchanged" and skipped the DB write.

Core behavior change:

1. **First sync on an unmigrated agent triggers migration** via
   `migrateAgentToYaml` (Group 2, already on dev as #1193). The agent's
   AGENTS.md frontmatter is moved into `agents/<name>/agent.yaml`;
   AGENTS.md.bak preserves the original byte-for-byte; AGENTS.md loses
   its frontmatter.
2. **agent.yaml becomes the canonical source post-migration.** Sync
   reads config via `parseAgentYaml` and drops back to legacy
   `parseFrontmatter(AGENTS.md)` only when neither yaml nor frontmatter
   exists (defaults-only registration).
3. **"Unchanged" path eliminated.** No change detection, no
   short-circuit — sync always upserts every reached agent's row. Lost
   edits are expensive; per-run writes are cheap.

Surface changes in `src/lib/agent-sync.ts`:

- `SyncResult.unchanged: string[]` removed. A new
  `SyncResult.migrated: string[]` tracks this-run migrations.
- `syncSingleAgent` gains a Phase 0 migration trigger. Its dbRow
  fallback is derived from the current DirectoryEntry via a small
  `dbRowFromEntry` helper (honors the frontmatter-wins precedence from
  Group 2's migrator).
- `syncSingleAgentByName` returns `'synced'` (was `'unchanged'`) for
  the non-change path; `processWatchedAgent`'s filter updated to match.
- `printSyncResult` drops the "Unchanged: ..." line and renames the
  closing summary to `Synced: N agent(s), M removed.`

`src/lib/agent-identity-sync.test.ts`:

- Old "sync detects AGENTS.md frontmatter changes" test updated to
  edit agent.yaml instead (post-migration canonical source).
- Old `expect(result.unchanged)` assertion replaced with
  `expect(result.updated)` (the existing agent still lands there).
- Four new Group 3 acceptance tests: migration trigger, migration
  idempotency, edit-yaml-then-resync reproducer, SyncResult shape no
  longer carries `unchanged`.

Scope guard verified: `grep 'Unchanged:' src/term-commands/dir.ts
src/lib/agent-sync.ts` returns zero hits in production code paths.

Full suite: 2678 pass / 0 fail. typecheck clean. Biome clean modulo a
complexity warning on `syncSingleAgent` (18 vs max 15) — the function
grew by one branch for the migration phase; pre-existing
`startDaemon`-style warning pattern, not a lint failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5df8de53-f4fb-4e00-869f-c75321fa5b86

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dir-sync-frontmatter-refresh-group3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements the dir-sync-frontmatter-refresh feature, which transitions agent configuration from AGENTS.md frontmatter to agent.yaml as the canonical source. It eliminates the "unchanged" skip path in the sync process, ensuring that the database is always updated to reflect the current state of files on disk. The changes include a migration trigger that converts existing frontmatter to YAML and updates to the sync logic to prioritize agent.yaml. Feedback identifies two areas for improvement: a block of code calling computeResolvedMetadata that appears to be dead code as its result is ignored, and an unreachable return statement in syncSingleAgentByName due to the new always-upsert logic.

Comment thread src/lib/agent-sync.ts
Comment on lines 413 to 416
if (workspaceRoot) {
const ctx = buildResolveContext(workspaceRoot, agent.name);
_resolvedMeta = computeResolvedMetadata(fm as Record<string, unknown>, ctx);
computeResolvedMetadata(configFields.rawForResolution, ctx);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The call to computeResolvedMetadata is a pure function call whose result is ignored. Since it has no side effects, this block (including the context building) appears to be dead code. If the intention was to validate the configuration, note that computeResolvedMetadata does not throw on resolution errors. If this logic is no longer needed for the sync process, it should be removed to improve maintainability. Removing this would also allow for the removal of the rawForResolution field from the ConfigFields interface and the computeResolvedMetadata and buildResolveContext helper functions.

Comment thread src/lib/agent-sync.ts
if (result.migrated.length > 0) return 'migrated';
if (result.updated.length > 0) return 'updated';
return 'unchanged';
return 'synced';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This return statement is unreachable. syncSingleAgent always populates either result.registered or result.updated for any agent it processes (see lines 440 and 460). Consequently, the preceding if conditions will always catch the result, and this line will never be reached. You might want to simplify the logic or ensure that syncSingleAgent can indeed reach a state where neither list is populated if that was the intention.

@namastex888
namastex888 merged commit 4495e4a into dev Apr 18, 2026
6 checks passed
@namastex888
namastex888 deleted the feat/dir-sync-frontmatter-refresh-group3 branch April 18, 2026 17:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f36d12e09

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/lib/agent-sync.ts
Comment on lines +395 to +398
if (frontmatter !== null) {
const existingEntry = await directory.resolve(agent.name);
const dbRow = existingEntry && !existingEntry.builtin ? dbRowFromEntry(existingEntry.entry) : undefined;
const migrationResult = await migrateAgentToYaml(agent.dir, dbRow);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard migration against non-parseable frontmatter

This branch now migrates whenever extractFrontmatterFromAgentsMd finds any frontmatter fence, but migrateAgentToYaml throws on malformed or empty frontmatter mappings; when that happens, syncSingleAgent records an error and skips the agent entirely. Before this change, sync used parseFrontmatter, which is intentionally tolerant and kept such agents discoverable with defaults, so this is a regression for workspaces that contain invalid/blank AGENTS.md frontmatter (for example ---\n---).

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants