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
28 changes: 27 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,32 @@ Runs the engine's local validators against every YAML/MD file in the org without
- End-of-pull summary with counts per direction
- A hard gate (`--resolve` flag) on 3-way conflicts before they silently lose data

When multiple resources are `both-diverged`, you can mix decisions in one pull:

```bash
# Global mode for every both-diverged resource.
npm run pull -- <org> --resolve=ours
npm run pull -- <org> --resolve=theirs
npm run pull -- <org> --resolve=fail

# Per-resource modes in one command.
npm run pull -- <org> \
--resolve=assistants/intake=ours \
--resolve=squads/main=theirs

# Path-level override: choose a resource base, then override selected paths.
# This keeps the git copy of the assistant except for dashboard voice settings.
npm run pull -- <org> \
--resolve=assistants/intake=ours \
--resolve-path=assistants/intake:voice=theirs
```

Path rules use dot paths, with numeric array indexes supported either as
`members.0.assistantId` or `members[0].assistantId`. Assistant markdown bodies
map to `model.messages` because pull parses `.md` resources into the same
object shape used for hashing. A path-level rule requires a per-resource or
global `ours` / `theirs` base so unspecified paths have an explicit owner.

`--force` skips all of this and just overwrites local with dashboard. Use it ONLY when you literally need to nuke local and re-materialize dashboard truth (rare). Plain pull is the DEFAULT for both humans and agents; `--force` is the escape hatch.

**Pull-output icon legend.** Distinct semantics in a single pulled-resource line:
Expand All @@ -110,6 +136,7 @@ Runs the engine's local validators against every YAML/MD file in the org without
| `✏️` | Locally modified file detected by git, preserved as-is |
| `⬆️` | `local-ahead` — local has unpushed edits, needs to flow UP to dashboard (preserved) |
| `⬇️` | `--resolve=theirs` — overwrote local with dashboard (flowed DOWN) |
| `🔀` | Mixed path resolution — one resource merged dashboard and git-selected paths |
| `🔒` | Platform-default resource (read-only, immutable) |
| `🚫` | Matched `.vapi-ignore` (not tracked locally) |
| `🗑️` | Locally deleted (deletion intent recorded in state) |
Expand Down Expand Up @@ -992,4 +1019,3 @@ When transferring to human:
3. Create simulations (pair personality + scenario)
4. Create suites (batch simulations together)
5. Run via Vapi dashboard or API

23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,29 @@ npm run pull -- <org> --bootstrap
npm run pull -- <org> --type assistants --id <uuid>
```

When `pull` reports `both-diverged`, choose a whole-run default, mix decisions
per resource, or merge selected paths:

```bash
# Same decision for every both-diverged resource.
npm run pull -- <org> --resolve=ours
npm run pull -- <org> --resolve=theirs

# Different decisions in one run.
npm run pull -- <org> \
--resolve=assistants/intake=ours \
--resolve=squads/main=theirs

# Keep git as the base, but take dashboard voice settings.
npm run pull -- <org> \
--resolve=assistants/intake=ours \
--resolve-path=assistants/intake:voice=theirs
```

Path rules use the parsed resource object shape. For assistants, the Markdown
body is represented as the system message under `model.messages`; squad arrays
can be addressed with indexes such as `members[0].assistantId`.

### Live testing what you just deployed

```bash
Expand Down
53 changes: 53 additions & 0 deletions improvements.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ you which stack PR closes the row.**
| 19 | No `maxTokens` floor warning for tool-using assistants | `maxTokens: 1` bricks the assistant silently | None | RESOLVED 2026-04-30 (Stack D) |
| 20 | Prompt vocabulary leaks into TTS | `Reason.` becomes verbal contaminant | None | Partial — Stack D heuristic |
| 21 | `.vapi-ignore` was pull-only (push could silently delete) | `--force` push DELETEd dashboard-only opt-outs | None | RESOLVED 2026-05-11 (#TBD) |
| 22 | Granular `both-diverged` resolution | Mix dashboard/git choices without manual hand-merge | #4 | RESOLVED 2026-05-29 (#TBD) |

---

Expand Down Expand Up @@ -1030,6 +1031,58 @@ RESOLVED 2026-05-11 (#TBD — PR number updates when opened).

---

## 22. Granular `both-diverged` resolution was whole-run only

**[RESOLVED 2026-05-29] (#TBD)**

**Discovered:** PRISM-852, from the mudflap-prod iForm drift incident on
2026-05-26. Squad and assistant resources diverged after dashboard pushes
and local bucket edits, and the operator had to use coarse overwrite/manual
merge choices to ship.

### Problem

`pull` could classify resources as `both-diverged`, but conflict resolution
was whole-run and whole-resource only. Operators could not take dashboard
`voice` while keeping git `model.messages`, or resolve one squad from the
dashboard and another assistant from git in a single command.

### Current behavior (Verified)

- Whole-run defaults still work: `--resolve=ours|theirs|fail`.
- Per-resource resolution is supported with repeatable flags:
`--resolve=assistants/intake=ours --resolve=squads/main=theirs`.
- Path-level resolution is supported by choosing a resource base and then
overriding parsed object paths:
`--resolve=assistants/intake=ours --resolve-path=assistants/intake:voice=theirs`.
- Path rules support dot paths and numeric array indexes, including
`members[0].assistantId`. Assistant Markdown bodies are parsed into
`model.messages`, so prompt/body resolution uses the same object shape as
the hash pipeline.
- Path-level mixed writes set `lastPulledHash` to the platform hash observed
during resolution. If the merged local file still differs from the full
dashboard payload, the next pull classifies it as `local-ahead`, not a
phantom `both-diverged`.

### Risk

Without granular resolution, a safe sync can force broad choices: lose
dashboard changes, lose git changes, or hand-merge resource files manually
while preserving the engine's hash invariants by memory.

### Resolution

Added a pure drift-resolution parser/merger and wired it into `pull` after
all `both-diverged` resources are collected, before any conflict writes occur.
If any conflict lacks an explicit mode, the pull exits before applying scoped
resolutions. Test coverage lives in `tests/drift-resolve.test.ts`.

### Status

RESOLVED 2026-05-29 (#TBD — PR number updates when opened).

---

## Out of scope (intentionally not improvements)

- **State file is identity-only and not git-ignored.** It's intentionally
Expand Down
Loading