docs(plugins): mark skill mirroring plan complete
This commit is contained in:
@ -82,7 +82,7 @@ Add tests:
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_hashing.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_config_loader.py`
|
||||
|
||||
- [ ] **Step 1: Write failing manifest validation tests**
|
||||
- [x] **Step 1: Write failing manifest validation tests**
|
||||
|
||||
Create tests covering:
|
||||
|
||||
@ -156,7 +156,7 @@ def test_skill_tree_hash_changes_when_supporting_file_changes(tmp_path: Path) ->
|
||||
Also verify path changes and executable-bit changes affect `skill_tree_hash`, while mtime
|
||||
and non-executable permission changes do not.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
Run:
|
||||
|
||||
@ -167,7 +167,7 @@ pytest tests/unit/test_plugin_manifest.py tests/unit/test_plugin_hashing.py test
|
||||
|
||||
Expected: FAIL because `beaver.plugins` and `PluginsConfig` do not exist.
|
||||
|
||||
- [ ] **Step 3: Implement immutable plugin models and config**
|
||||
- [x] **Step 3: Implement immutable plugin models and config**
|
||||
|
||||
Put plugin package models in `beaver/plugins/models.py`:
|
||||
|
||||
@ -229,7 +229,7 @@ def _parse_plugins(raw: Any) -> PluginsConfig:
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Implement strict JSON manifest loading**
|
||||
- [x] **Step 4: Implement strict JSON manifest loading**
|
||||
|
||||
`load_plugin_manifest()` must:
|
||||
|
||||
@ -242,7 +242,7 @@ def _parse_plugins(raw: Any) -> PluginsConfig:
|
||||
7. initialize `display_path` without exposing an absolute path;
|
||||
8. return frozen dataclasses.
|
||||
|
||||
- [ ] **Step 5: Implement deterministic dual hashing**
|
||||
- [x] **Step 5: Implement deterministic dual hashing**
|
||||
|
||||
`hash_plugin_skill_tree(root)` must:
|
||||
|
||||
@ -258,7 +258,7 @@ def _parse_plugins(raw: Any) -> PluginsConfig:
|
||||
Use length-prefixed binary fields in the digest input instead of ambiguous string
|
||||
concatenation.
|
||||
|
||||
- [ ] **Step 6: Run focused tests**
|
||||
- [x] **Step 6: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -267,7 +267,7 @@ pytest tests/unit/test_plugin_manifest.py tests/unit/test_plugin_hashing.py test
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins app-instance/backend/beaver/foundation/config app-instance/backend/tests/unit/test_plugin_manifest.py app-instance/backend/tests/unit/test_plugin_hashing.py app-instance/backend/tests/unit/test_config_loader.py
|
||||
@ -287,7 +287,7 @@ git commit -m "feat(plugins): add declarative skill manifest"
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_state.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_workspace_write_lock.py`
|
||||
|
||||
- [ ] **Step 1: Write failing discovery and state tests**
|
||||
- [x] **Step 1: Write failing discovery and state tests**
|
||||
|
||||
Cover workspace discovery, configured search paths, duplicate plugin IDs, malformed
|
||||
manifests reported as errors instead of crashing the full scan, and state round trips:
|
||||
@ -321,7 +321,7 @@ Add a multiprocess lock test in which two processes enter the same workspace loc
|
||||
assert their critical sections never overlap. Add a reentrancy test in which nested
|
||||
acquisitions in one process complete without deadlock.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -330,7 +330,7 @@ pytest tests/unit/test_plugin_state.py tests/unit/test_workspace_write_lock.py -
|
||||
|
||||
Expected: FAIL because discovery and state stores are missing.
|
||||
|
||||
- [ ] **Step 3: Implement state dataclasses**
|
||||
- [x] **Step 3: Implement state dataclasses**
|
||||
|
||||
Add backward-compatible `to_dict()` and `from_dict()` methods for:
|
||||
|
||||
@ -358,7 +358,7 @@ class PluginState:
|
||||
skills: dict[str, PluginSkillBinding] = field(default_factory=dict)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Implement atomic state persistence**
|
||||
- [x] **Step 4: Implement atomic state persistence**
|
||||
|
||||
Store data at `<workspace>/.beaver/plugins/state.json`. Write a complete JSON document to
|
||||
`state.json.tmp`, flush it, then replace `state.json`. Public methods:
|
||||
@ -371,7 +371,7 @@ upsert_plugin(plugin_state)
|
||||
update_skill_binding(plugin_id, skill_name, binding)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Implement the shared workspace write lock**
|
||||
- [x] **Step 5: Implement the shared workspace write lock**
|
||||
|
||||
Add:
|
||||
|
||||
@ -395,7 +395,7 @@ Requirements:
|
||||
- raise `WorkspaceWriteLockBusy` on timeout/contention;
|
||||
- keep the lock file separate from atomically replaced data files.
|
||||
|
||||
- [ ] **Step 6: Implement discovery**
|
||||
- [x] **Step 6: Implement discovery**
|
||||
|
||||
Scan:
|
||||
|
||||
@ -409,7 +409,7 @@ manifest display path when possible and a redacted
|
||||
`<external>/<plugin-dir>/beaver.plugin.json` path otherwise; absolute paths remain
|
||||
internal.
|
||||
|
||||
- [ ] **Step 7: Run focused tests**
|
||||
- [x] **Step 7: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -418,7 +418,7 @@ pytest tests/unit/test_plugin_state.py tests/unit/test_workspace_write_lock.py t
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins app-instance/backend/beaver/foundation/utils/file_lock.py app-instance/backend/tests/unit/test_plugin_state.py app-instance/backend/tests/unit/test_workspace_write_lock.py
|
||||
@ -436,7 +436,7 @@ git commit -m "feat(plugins): discover packages and persist state"
|
||||
- Modify: `app-instance/backend/beaver/skills/specs/__init__.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_skill_storage.py`
|
||||
|
||||
- [ ] **Step 1: Write failing snapshot storage tests**
|
||||
- [x] **Step 1: Write failing snapshot storage tests**
|
||||
|
||||
Test exact content, supporting files, idempotence, symlink rejection, and source
|
||||
immutability:
|
||||
@ -478,7 +478,7 @@ Also test:
|
||||
- promoting a staged snapshot uses `os.replace()` and is idempotent;
|
||||
- a failed metadata write leaves no current pointer to the staged version.
|
||||
|
||||
- [ ] **Step 2: Run test and verify failure**
|
||||
- [x] **Step 2: Run test and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -487,7 +487,7 @@ pytest tests/unit/test_plugin_skill_storage.py -q
|
||||
|
||||
Expected: FAIL because upstream snapshot APIs do not exist.
|
||||
|
||||
- [ ] **Step 3: Add upstream snapshot models**
|
||||
- [x] **Step 3: Add upstream snapshot models**
|
||||
|
||||
Add:
|
||||
|
||||
@ -510,7 +510,7 @@ Add `LoadedSkillUpstreamSnapshot(snapshot, content, root)` for storage reads. Ex
|
||||
complete version-tree hash, while `read_published_skill()` derives it for legacy metadata
|
||||
that lacks the field.
|
||||
|
||||
- [ ] **Step 4: Add safe tree-copy helper**
|
||||
- [x] **Step 4: Add safe tree-copy helper**
|
||||
|
||||
Refactor a private `SkillSpecStore._copy_regular_tree(source_root, target_root)` that:
|
||||
|
||||
@ -522,7 +522,7 @@ Refactor a private `SkillSpecStore._copy_regular_tree(source_root, target_root)`
|
||||
|
||||
Use it for transaction staging now; Task 4 will reuse it for mirrored versions.
|
||||
|
||||
- [ ] **Step 5: Implement same-filesystem staging and promotion**
|
||||
- [x] **Step 5: Implement same-filesystem staging and promotion**
|
||||
|
||||
`PluginSkillTransaction` creates:
|
||||
|
||||
@ -542,7 +542,7 @@ cleanup()
|
||||
`promote_directory()` uses `os.replace()` and never replaces an existing non-identical
|
||||
immutable directory. Cleanup removes only the transaction's staging root.
|
||||
|
||||
- [ ] **Step 6: Implement snapshot APIs**
|
||||
- [x] **Step 6: Implement snapshot APIs**
|
||||
|
||||
Write snapshots to:
|
||||
|
||||
@ -561,14 +561,14 @@ promote_upstream_snapshot(transaction, snapshot)
|
||||
read_upstream_snapshot(skill_name, source_id, skill_tree_hash)
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Make JSON/current/index writes atomic**
|
||||
- [x] **Step 7: Make JSON/current/index writes atomic**
|
||||
|
||||
Change `SkillSpecStore._write_json()` and current/index pointer writes to create a temporary
|
||||
file in the target directory, flush and `fsync`, then `os.replace()`. Immutable version
|
||||
directories are promoted first; runtime visibility changes only when `current.json`,
|
||||
`skill.json`, and the published index are atomically replaced under the workspace lock.
|
||||
|
||||
- [ ] **Step 8: Run focused and existing storage tests**
|
||||
- [x] **Step 8: Run focused and existing storage tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -577,7 +577,7 @@ pytest tests/unit/test_plugin_skill_storage.py tests/unit/test_phase5_skills_run
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
- [x] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins/transaction.py app-instance/backend/beaver/skills/specs app-instance/backend/tests/unit/test_plugin_skill_storage.py
|
||||
@ -595,7 +595,7 @@ git commit -m "feat(skills): store immutable plugin upstream snapshots"
|
||||
- Modify: `app-instance/backend/beaver/skills/specs/storage.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_skill_sync.py`
|
||||
|
||||
- [ ] **Step 1: Write failing initial mirror tests**
|
||||
- [x] **Step 1: Write failing initial mirror tests**
|
||||
|
||||
Cover:
|
||||
|
||||
@ -626,7 +626,7 @@ assert loaded.version.provenance["upstream_skill_content_hash"]
|
||||
assert loaded.version.provenance["upstream_skill_tree_hash"]
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -635,7 +635,7 @@ pytest tests/unit/test_plugin_skill_sync.py -q
|
||||
|
||||
Expected: FAIL because `PluginManager` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement `PluginManager` constructor and discovery view**
|
||||
- [x] **Step 3: Implement `PluginManager` constructor and discovery view**
|
||||
|
||||
Constructor dependencies:
|
||||
|
||||
@ -659,7 +659,7 @@ class PluginManager:
|
||||
|
||||
Keep all filesystem and lifecycle dependencies injectable for tests.
|
||||
|
||||
- [ ] **Step 4: Implement exact initial mirror publication**
|
||||
- [x] **Step 4: Implement exact initial mirror publication**
|
||||
|
||||
Acquire the workspace write lock before reading state, allocating versions, or writing
|
||||
candidates. For each declared skill:
|
||||
@ -689,7 +689,7 @@ Use provenance:
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Promote the complete staged transaction**
|
||||
- [x] **Step 5: Promote the complete staged transaction**
|
||||
|
||||
After every declared skill passes validation:
|
||||
|
||||
@ -704,7 +704,7 @@ metadata write fails, those directories remain unreferenced and harmless; the pr
|
||||
current pointers remain authoritative. Add startup cleanup for staging directories older
|
||||
than 24 hours.
|
||||
|
||||
- [ ] **Step 6: Run focused and loader tests**
|
||||
- [x] **Step 6: Run focused and loader tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -713,7 +713,7 @@ pytest tests/unit/test_plugin_skill_sync.py tests/unit/test_phase5_skills_runtim
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins app-instance/backend/beaver/skills/specs/storage.py app-instance/backend/tests/unit/test_plugin_skill_sync.py
|
||||
@ -731,7 +731,7 @@ git commit -m "feat(plugins): mirror enabled plugin skills"
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_skill_sync.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_skill_learning_candidate_state.py`
|
||||
|
||||
- [ ] **Step 1: Write failing upgrade classification tests**
|
||||
- [x] **Step 1: Write failing upgrade classification tests**
|
||||
|
||||
Create four tree-hash fixtures representing `B`, `L`, and `U`:
|
||||
|
||||
@ -758,7 +758,7 @@ Also test:
|
||||
- legacy candidate payloads still parse.
|
||||
- two processes syncing the same update append only one candidate record.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -767,7 +767,7 @@ pytest tests/unit/test_plugin_skill_sync.py tests/unit/test_skill_learning_candi
|
||||
|
||||
Expected: FAIL because update classification and candidate kind are missing.
|
||||
|
||||
- [ ] **Step 3: Add `plugin_skill_update` candidate support**
|
||||
- [x] **Step 3: Add `plugin_skill_update` candidate support**
|
||||
|
||||
Do not add a special status. Existing candidate statuses remain sufficient. Ensure
|
||||
`SkillLearningCandidate.from_dict()` accepts the new `kind` without changing legacy
|
||||
@ -789,7 +789,7 @@ Use evidence:
|
||||
|
||||
Set `priority=10`, `confidence=1.0`, `trigger_reason="plugin_update"`.
|
||||
|
||||
- [ ] **Step 4: Implement update classification and candidate creation**
|
||||
- [x] **Step 4: Implement update classification and candidate creation**
|
||||
|
||||
Use canonical hashes and deterministic IDs:
|
||||
|
||||
@ -804,7 +804,7 @@ For `already_applied`, advance state without a candidate. For `fast_forward` and
|
||||
`three_way`, record an open candidate. If the same ID exists in any status, do not append
|
||||
another JSONL record.
|
||||
|
||||
- [ ] **Step 5: Make candidate mutation atomic under the shared lock**
|
||||
- [x] **Step 5: Make candidate mutation atomic under the shared lock**
|
||||
|
||||
Add an optional `WorkspaceWriteLock` to `SkillLearningStore`; EngineLoader supplies the
|
||||
shared workspace instance, while isolated unit-test construction falls back to a
|
||||
@ -818,7 +818,7 @@ Inside one lock acquisition, read current candidates, check the deterministic ID
|
||||
atomically rewrite or append the JSONL record. Apply the same lock to candidate update and
|
||||
transition methods. Nested calls from `PluginManager` reuse the reentrant lock.
|
||||
|
||||
- [ ] **Step 6: Supersede stale pending updates**
|
||||
- [x] **Step 6: Supersede stale pending updates**
|
||||
|
||||
When a different pending candidate exists for the same plugin skill:
|
||||
|
||||
@ -831,7 +831,7 @@ learning_store.transition_learning_candidate(
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Run focused tests**
|
||||
- [x] **Step 7: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -840,7 +840,7 @@ pytest tests/unit/test_plugin_skill_sync.py tests/unit/test_skill_learning_candi
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins/skills.py app-instance/backend/beaver/memory/skills/models.py app-instance/backend/beaver/memory/skills/store.py app-instance/backend/tests/unit/test_plugin_skill_sync.py app-instance/backend/tests/unit/test_skill_learning_candidate_state.py
|
||||
@ -859,7 +859,7 @@ git commit -m "feat(plugins): enqueue skill upgrade candidates"
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_skill_learning.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_skill_learning_pipeline.py`
|
||||
|
||||
- [ ] **Step 1: Write failing model and fast-forward tests**
|
||||
- [x] **Step 1: Write failing model and fast-forward tests**
|
||||
|
||||
Test backward-compatible draft parsing and exact upstream fast-forward:
|
||||
|
||||
@ -877,7 +877,7 @@ assert provider.calls == []
|
||||
After publish, assert the new version contains the new upstream supporting files even when
|
||||
`SKILL.md` did not change.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -887,7 +887,7 @@ pytest tests/unit/test_plugin_skill_learning.py tests/unit/test_skill_learning_p
|
||||
Expected: FAIL because drafts have no provenance and the learning service has no plugin
|
||||
update branch.
|
||||
|
||||
- [ ] **Step 3: Add backward-compatible draft provenance**
|
||||
- [x] **Step 3: Add backward-compatible draft provenance**
|
||||
|
||||
Extend `SkillDraft`:
|
||||
|
||||
@ -897,7 +897,7 @@ provenance: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
Include it in `to_dict()` and parse missing values as `{}` in `from_dict()`.
|
||||
|
||||
- [ ] **Step 4: Add a focused draft constructor**
|
||||
- [x] **Step 4: Add a focused draft constructor**
|
||||
|
||||
Add:
|
||||
|
||||
@ -918,7 +918,7 @@ def create_plugin_update_draft(
|
||||
|
||||
It writes `proposal_kind="plugin_skill_update"`.
|
||||
|
||||
- [ ] **Step 5: Implement fast-forward synthesis**
|
||||
- [x] **Step 5: Implement fast-forward synthesis**
|
||||
|
||||
In `SkillLearningService.synthesize_draft()`, branch before ordinary revision:
|
||||
|
||||
@ -930,7 +930,7 @@ if candidate.kind == "plugin_skill_update":
|
||||
For `merge_mode == "fast_forward"`, load `U` from `SkillSpecStore`, parse its
|
||||
frontmatter/body, and create a draft exactly equal to `U`. Do not call the provider.
|
||||
|
||||
- [ ] **Step 6: Serialize all skill publication**
|
||||
- [x] **Step 6: Serialize all skill publication**
|
||||
|
||||
Add an optional `WorkspaceWriteLock` to `SkillPublisher`; EngineLoader supplies the shared
|
||||
workspace instance and isolated tests use a publisher-local fallback. Hold it across
|
||||
@ -938,14 +938,14 @@ workspace instance and isolated tests use a publisher-local fallback. Hold it ac
|
||||
and disable. This protects ordinary learned skills as well as plugin-origin skills from
|
||||
racing with boot or explicit plugin sync.
|
||||
|
||||
- [ ] **Step 7: Materialize referenced supporting files during publish**
|
||||
- [x] **Step 7: Materialize referenced supporting files during publish**
|
||||
|
||||
For `proposal_kind="plugin_skill_update"`, resolve the snapshot and supporting-file plan
|
||||
from draft provenance. Stage the complete next version directory, including `SKILL.md`
|
||||
and supporting files, before promoting it. Reject missing snapshots, path conflicts, or
|
||||
tree-hash mismatches. Ordinary skill publication keeps its current behavior.
|
||||
|
||||
- [ ] **Step 8: Preserve draft provenance on publish**
|
||||
- [x] **Step 8: Preserve draft provenance on publish**
|
||||
|
||||
Change `SkillPublisher.publish()` provenance construction to:
|
||||
|
||||
@ -959,7 +959,7 @@ provenance={
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 9: Run focused tests**
|
||||
- [x] **Step 9: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -968,7 +968,7 @@ pytest tests/unit/test_plugin_skill_learning.py tests/unit/test_skill_learning_p
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
- [x] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/skills app-instance/backend/tests/unit/test_plugin_skill_learning.py app-instance/backend/tests/unit/test_skill_learning_pipeline.py
|
||||
@ -986,7 +986,7 @@ git commit -m "feat(skill-learning): create plugin update drafts"
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_skill_learning.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_skill_learning_synthesizer_preservation.py`
|
||||
|
||||
- [ ] **Step 1: Write failing three-way prompt and parse tests**
|
||||
- [x] **Step 1: Write failing three-way prompt and parse tests**
|
||||
|
||||
Assert the prompt contains labeled `OLD UPSTREAM`, `CURRENT LOCAL`, and `NEW UPSTREAM`
|
||||
sections and does not confuse the current local version with the merge base.
|
||||
@ -1019,7 +1019,7 @@ def test_supporting_file_merge_blocks_divergent_edits() -> None:
|
||||
assert plan.conflicts[0].path == "a.txt"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1028,7 +1028,7 @@ pytest tests/unit/test_plugin_skill_learning.py tests/unit/test_skill_learning_s
|
||||
|
||||
Expected: FAIL because three-way synthesis does not exist.
|
||||
|
||||
- [ ] **Step 3: Add `synthesize_plugin_update()`**
|
||||
- [x] **Step 3: Add `synthesize_plugin_update()`**
|
||||
|
||||
Signature:
|
||||
|
||||
@ -1054,7 +1054,7 @@ The system message must require JSON only and state:
|
||||
- list every intentional drop;
|
||||
- leave `resolved_conflicts` empty only when no semantic conflict exists.
|
||||
|
||||
- [ ] **Step 4: Load all three snapshots in the learning service**
|
||||
- [x] **Step 4: Load all three snapshots in the learning service**
|
||||
|
||||
Resolve:
|
||||
|
||||
@ -1065,7 +1065,7 @@ Resolve:
|
||||
Raise a specific `ValueError` when any referenced snapshot/version is missing. Do not
|
||||
fallback to a two-way merge.
|
||||
|
||||
- [ ] **Step 5: Build the deterministic supporting-file merge plan**
|
||||
- [x] **Step 5: Build the deterministic supporting-file merge plan**
|
||||
|
||||
Compare files by path and content/executable digest:
|
||||
|
||||
@ -1078,7 +1078,7 @@ Compare files by path and content/executable digest:
|
||||
Exclude `SKILL.md` because the synthesizer handles it. Store selected source references
|
||||
and conflict records in draft provenance; do not duplicate file bytes in JSON.
|
||||
|
||||
- [ ] **Step 6: Create the plugin update draft**
|
||||
- [x] **Step 6: Create the plugin update draft**
|
||||
|
||||
Store merge decisions in draft provenance:
|
||||
|
||||
@ -1097,7 +1097,7 @@ Store merge decisions in draft provenance:
|
||||
If the supporting-file plan contains conflicts, the draft may be inspected but cannot be
|
||||
published. V1 does not ask the LLM to merge arbitrary or binary files.
|
||||
|
||||
- [ ] **Step 7: Run focused tests**
|
||||
- [x] **Step 7: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1106,7 +1106,7 @@ pytest tests/unit/test_plugin_skill_learning.py tests/unit/test_skill_learning_s
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins/tree_merge.py app-instance/backend/beaver/skills/learning app-instance/backend/tests/unit/test_plugin_skill_learning.py app-instance/backend/tests/unit/test_skill_learning_synthesizer_preservation.py
|
||||
@ -1125,7 +1125,7 @@ git commit -m "feat(skill-learning): synthesize three-way plugin updates"
|
||||
- Test: `app-instance/backend/tests/unit/test_skill_learning_eval.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_skill_learning_pipeline.py`
|
||||
|
||||
- [ ] **Step 1: Write failing plugin merge preservation tests**
|
||||
- [x] **Step 1: Write failing plugin merge preservation tests**
|
||||
|
||||
Cover:
|
||||
|
||||
@ -1148,7 +1148,7 @@ assert report.preservation_report == {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1157,7 +1157,7 @@ pytest tests/unit/test_skill_learning_preservation.py tests/unit/test_skill_lear
|
||||
|
||||
Expected: FAIL because preservation only checks one base skill.
|
||||
|
||||
- [ ] **Step 3: Add plugin merge preservation helper**
|
||||
- [x] **Step 3: Add plugin merge preservation helper**
|
||||
|
||||
Add:
|
||||
|
||||
@ -1174,13 +1174,13 @@ def check_plugin_merge_preservation(
|
||||
It calls existing `check_preservation()` for local and upstream content, gives Safety and
|
||||
Required Tools sections blocking weight, and reports unresolved conflicts separately.
|
||||
|
||||
- [ ] **Step 4: Use current local as replay baseline**
|
||||
- [x] **Step 4: Use current local as replay baseline**
|
||||
|
||||
When `draft.proposal_kind == "plugin_skill_update"`, load `draft.base_version` as the
|
||||
baseline skill. Continue to run the candidate arm with the draft context. Do not use raw
|
||||
upstream `B` or `U` as the replay baseline.
|
||||
|
||||
- [ ] **Step 5: Tighten publish gate**
|
||||
- [x] **Step 5: Tighten publish gate**
|
||||
|
||||
Add:
|
||||
|
||||
@ -1197,7 +1197,7 @@ if draft.proposal_kind == "plugin_skill_update":
|
||||
|
||||
The existing `passed is False` gate remains active.
|
||||
|
||||
- [ ] **Step 6: Run focused tests**
|
||||
- [x] **Step 6: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1206,7 +1206,7 @@ pytest tests/unit/test_skill_learning_preservation.py tests/unit/test_skill_lear
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/skills/learning app-instance/backend/tests/unit/test_skill_learning_preservation.py app-instance/backend/tests/unit/test_skill_learning_eval.py app-instance/backend/tests/unit/test_skill_learning_pipeline.py
|
||||
@ -1224,7 +1224,7 @@ git commit -m "feat(skill-learning): gate plugin merge preservation"
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_skill_sync.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_skill_learning_pipeline.py`
|
||||
|
||||
- [ ] **Step 1: Write failing lifecycle tests**
|
||||
- [x] **Step 1: Write failing lifecycle tests**
|
||||
|
||||
Test:
|
||||
|
||||
@ -1242,7 +1242,7 @@ Test:
|
||||
active;
|
||||
- adopt changes `source_kind` to `managed`, removes binding, and keeps the skill active.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1251,7 +1251,7 @@ pytest tests/unit/test_plugin_skill_sync.py tests/unit/test_skill_learning_pipel
|
||||
|
||||
Expected: FAIL because publication has no plugin acknowledgement callback.
|
||||
|
||||
- [ ] **Step 3: Add a narrow publication observer**
|
||||
- [x] **Step 3: Add a narrow publication observer**
|
||||
|
||||
Extend pipeline construction with:
|
||||
|
||||
@ -1265,7 +1265,7 @@ or turn the publish API response into a failure. Mark the learning candidate pub
|
||||
before invoking the best-effort observer so clients do not retry a successful publish.
|
||||
The next sync is responsible for reconciliation.
|
||||
|
||||
- [ ] **Step 4: Implement `PluginManager.on_skill_published()`**
|
||||
- [x] **Step 4: Implement `PluginManager.on_skill_published()`**
|
||||
|
||||
For `proposal_kind="plugin_skill_update"`:
|
||||
|
||||
@ -1277,7 +1277,7 @@ For `proposal_kind="plugin_skill_update"`:
|
||||
6. clear `pending_candidate_id`;
|
||||
7. set status `synced`.
|
||||
|
||||
- [ ] **Step 5: Implement sync-time reconciliation**
|
||||
- [x] **Step 5: Implement sync-time reconciliation**
|
||||
|
||||
At the beginning of `sync_enabled()`, inspect each linked skill's current published
|
||||
version. When provenance contains:
|
||||
@ -1294,7 +1294,7 @@ and the referenced upstream snapshot exists, advance state only if the current v
|
||||
number is newer than `accepted_beaver_version`. Clear only the matching pending candidate.
|
||||
Never regress state when the runtime current pointer was rolled back to an older version.
|
||||
|
||||
- [ ] **Step 6: Implement pause, resume, disable, missing, and adopt**
|
||||
- [x] **Step 6: Implement pause, resume, disable, missing, and adopt**
|
||||
|
||||
`pause(plugin_id)` sets `updates_paused=True` and leaves linked skills unchanged.
|
||||
`resume(plugin_id)` clears the flag and performs reconciliation/sync.
|
||||
@ -1313,7 +1313,7 @@ When discovery cannot find a previously known plugin, set status `missing`, pres
|
||||
`enabled` and `updates_paused`, skip update generation, and do not disable any linked
|
||||
skill.
|
||||
|
||||
- [ ] **Step 7: Run focused tests**
|
||||
- [x] **Step 7: Run focused tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1322,7 +1322,7 @@ pytest tests/unit/test_plugin_skill_sync.py tests/unit/test_skill_learning_pipel
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/plugins/skills.py app-instance/backend/beaver/skills/learning/pipeline.py app-instance/backend/beaver/skills/publisher/service.py app-instance/backend/tests/unit/test_plugin_skill_sync.py app-instance/backend/tests/unit/test_skill_learning_pipeline.py
|
||||
@ -1339,7 +1339,7 @@ git commit -m "feat(plugins): track published updates and ownership"
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_runtime.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_phase5_skills_runtime.py`
|
||||
|
||||
- [ ] **Step 1: Write failing runtime assembly tests**
|
||||
- [x] **Step 1: Write failing runtime assembly tests**
|
||||
|
||||
Test:
|
||||
|
||||
@ -1352,7 +1352,7 @@ Test:
|
||||
workspace lock;
|
||||
- `EngineLoadResult.plugin_manager` and plugin summaries are available.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1361,7 +1361,7 @@ pytest tests/unit/test_plugin_runtime.py tests/unit/test_phase5_skills_runtime.p
|
||||
|
||||
Expected: FAIL because `EngineLoader` does not assemble plugin services.
|
||||
|
||||
- [ ] **Step 3: Extend `EngineLoadResult` and loader injection**
|
||||
- [x] **Step 3: Extend `EngineLoadResult` and loader injection**
|
||||
|
||||
Add:
|
||||
|
||||
@ -1372,7 +1372,7 @@ plugins: list[dict] = field(default_factory=list)
|
||||
|
||||
Allow `plugin_manager` injection in `EngineLoader.__init__()` for tests.
|
||||
|
||||
- [ ] **Step 4: Assemble in dependency order**
|
||||
- [x] **Step 4: Assemble in dependency order**
|
||||
|
||||
Required order:
|
||||
|
||||
@ -1390,7 +1390,7 @@ Do not use `SkillsLoader.extra_dirs` for plugin skills. Explicit API enable/sync
|
||||
bounded blocking lock timeout; Engine boot uses a non-blocking attempt and proceeds with
|
||||
the current published skill set if another writer owns the lock.
|
||||
|
||||
- [ ] **Step 5: Run runtime tests**
|
||||
- [x] **Step 5: Run runtime tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1399,7 +1399,7 @@ pytest tests/unit/test_plugin_runtime.py tests/unit/test_phase5_skills_runtime.p
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
- [x] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/engine/loader.py app-instance/backend/beaver/plugins app-instance/backend/tests/unit/test_plugin_runtime.py app-instance/backend/tests/unit/test_phase5_skills_runtime.py
|
||||
@ -1414,7 +1414,7 @@ git commit -m "feat(runtime): sync declarative plugins at boot"
|
||||
- Modify: `app-instance/backend/beaver/interfaces/web/app.py`
|
||||
- Test: `app-instance/backend/tests/unit/test_plugin_web_api.py`
|
||||
|
||||
- [ ] **Step 1: Write failing API tests**
|
||||
- [x] **Step 1: Write failing API tests**
|
||||
|
||||
Cover:
|
||||
|
||||
@ -1433,7 +1433,7 @@ manifest/sync errors. Assert lock timeout maps to `409 plugin_write_busy`. Asser
|
||||
payload contains the real absolute workspace or external search-root path. Assert disable
|
||||
without `{"disable_linked_skills": true}` is rejected.
|
||||
|
||||
- [ ] **Step 2: Run tests and verify failure**
|
||||
- [x] **Step 2: Run tests and verify failure**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1442,7 +1442,7 @@ pytest tests/unit/test_plugin_web_api.py -q
|
||||
|
||||
Expected: FAIL with missing routes.
|
||||
|
||||
- [ ] **Step 3: Add normalized plugin payload helper**
|
||||
- [x] **Step 3: Add normalized plugin payload helper**
|
||||
|
||||
Return:
|
||||
|
||||
@ -1473,12 +1473,12 @@ Return:
|
||||
|
||||
Never return arbitrary plugin file content, secrets, or absolute server paths.
|
||||
|
||||
- [ ] **Step 4: Implement routes**
|
||||
- [x] **Step 4: Implement routes**
|
||||
|
||||
Each mutating endpoint boots one runtime, invokes its `plugin_manager`, and returns the
|
||||
updated plugin payload. Map `ValueError` messages to stable HTTP status codes.
|
||||
|
||||
- [ ] **Step 5: Run focused and existing web tests**
|
||||
- [x] **Step 5: Run focused and existing web tests**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1487,7 +1487,7 @@ pytest tests/unit/test_plugin_web_api.py tests/unit/test_skill_learning_web_api.
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
- [x] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/beaver/interfaces/web/app.py app-instance/backend/tests/unit/test_plugin_web_api.py
|
||||
@ -1504,12 +1504,12 @@ git commit -m "feat(api): manage declarative plugins"
|
||||
- Modify: `app-instance/frontend/app/(app)/skills/page.tsx`
|
||||
- Test: `app-instance/frontend/lib/plugin-api.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write failing API client tests**
|
||||
- [x] **Step 1: Write failing API client tests**
|
||||
|
||||
Test URL, method, and response typing for list, sync, enable, pause, resume, disable, and
|
||||
adopt.
|
||||
|
||||
- [ ] **Step 2: Run frontend test and verify failure**
|
||||
- [x] **Step 2: Run frontend test and verify failure**
|
||||
|
||||
Run the repository's existing frontend test command targeting:
|
||||
|
||||
@ -1520,7 +1520,7 @@ npx vitest run lib/plugin-api.test.ts
|
||||
|
||||
Expected: FAIL because plugin API functions do not exist.
|
||||
|
||||
- [ ] **Step 3: Add frontend types**
|
||||
- [x] **Step 3: Add frontend types**
|
||||
|
||||
Add:
|
||||
|
||||
@ -1549,7 +1549,7 @@ export interface BeaverPlugin {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add API functions**
|
||||
- [x] **Step 4: Add API functions**
|
||||
|
||||
Implement:
|
||||
|
||||
@ -1563,7 +1563,7 @@ disablePlugin(pluginId, { disable_linked_skills: true })
|
||||
adoptPluginSkill(pluginId, skillName)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Add a `plugins` Skills tab**
|
||||
- [x] **Step 5: Add a `plugins` Skills tab**
|
||||
|
||||
Extend `SkillsTab` and render a compact table with:
|
||||
|
||||
@ -1578,7 +1578,7 @@ Extend `SkillsTab` and render a compact table with:
|
||||
|
||||
Do not add a separate marketing-style page or nested cards.
|
||||
|
||||
- [ ] **Step 6: Label plugin-origin skills and update candidates**
|
||||
- [x] **Step 6: Label plugin-origin skills and update candidates**
|
||||
|
||||
In existing Published/Candidates/Drafts views:
|
||||
|
||||
@ -1586,7 +1586,7 @@ In existing Published/Candidates/Drafts views:
|
||||
- render `plugin_skill_update` as `插件升级合并 / Plugin update merge`;
|
||||
- show `fast_forward` or `three_way` from candidate evidence/provenance.
|
||||
|
||||
- [ ] **Step 7: Run frontend tests and type checks**
|
||||
- [x] **Step 7: Run frontend tests and type checks**
|
||||
|
||||
```bash
|
||||
cd app-instance/frontend
|
||||
@ -1597,7 +1597,7 @@ npx tsc --noEmit
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
- [x] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/frontend/types/index.ts app-instance/frontend/lib/api.ts app-instance/frontend/lib/plugin-api.test.ts 'app-instance/frontend/app/(app)/skills/page.tsx'
|
||||
@ -1613,7 +1613,7 @@ git commit -m "feat(skills-ui): manage plugin skill mirrors"
|
||||
- Create: `docs/plugins/skill-plugins.md`
|
||||
- Modify: `docs/product-discovery/beaver/README.md`
|
||||
|
||||
- [ ] **Step 1: Write the end-to-end lifecycle test**
|
||||
- [x] **Step 1: Write the end-to-end lifecycle test**
|
||||
|
||||
The test must:
|
||||
|
||||
@ -1634,7 +1634,7 @@ The test must:
|
||||
remains active;
|
||||
15. run two sync processes and assert no duplicate version or candidate is created.
|
||||
|
||||
- [ ] **Step 2: Run the integration test and fix only lifecycle defects**
|
||||
- [x] **Step 2: Run the integration test and fix only lifecycle defects**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1643,7 +1643,7 @@ pytest tests/integration/test_plugin_skill_lifecycle.py -v
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 3: Write operator documentation**
|
||||
- [x] **Step 3: Write operator documentation**
|
||||
|
||||
Document:
|
||||
|
||||
@ -1658,7 +1658,7 @@ Document:
|
||||
- workspace locking, deferred boot sync, and publication reconciliation;
|
||||
- why plugin Python code is not executed in V1.
|
||||
|
||||
- [ ] **Step 4: Run the complete relevant backend suite**
|
||||
- [x] **Step 4: Run the complete relevant backend suite**
|
||||
|
||||
```bash
|
||||
cd app-instance/backend
|
||||
@ -1683,7 +1683,7 @@ pytest \
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Run frontend verification**
|
||||
- [x] **Step 5: Run frontend verification**
|
||||
|
||||
```bash
|
||||
cd app-instance/frontend
|
||||
@ -1694,7 +1694,7 @@ npx tsc --noEmit
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Run a dirty-worktree-safe diff review**
|
||||
- [x] **Step 6: Run a dirty-worktree-safe diff review**
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
@ -1708,7 +1708,7 @@ Expected:
|
||||
- only plugin/skill lifecycle files and planned docs/tests are included in this feature;
|
||||
- unrelated pre-existing user changes remain untouched.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
- [x] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add app-instance/backend/tests/integration/test_plugin_skill_lifecycle.py docs/plugins/skill-plugins.md docs/product-discovery/beaver/README.md
|
||||
|
||||
Reference in New Issue
Block a user