feat(bridge): workspace-backed project registration + adapter improvements - #2533
Conversation
…ments into projects/commitments/
[cherry-pick-target: feat/projects-workspace-backed]
Replace the parallel `.system/engine/projects/*.json` schema with
workspace-backed project registration. Writing any file under
`projects/<slug>/` is now the declaration that the project exists —
the engine auto-registers it on `memory_write`, and `mission_create`
can reference it by slug. The model reasons about projects through
normal workspace APIs instead of a hidden sidecar schema.
Engine + bridge
- `ProjectId::from_slug(user_id, slug)` derives a stable v5 UUID;
`Project::new` routes through it so constructing the same project
twice returns the same ID (no duplicates).
- `slugify_simple` in `ironclaw_engine::types` — pure slug, no UUID
suffix, reverses cleanly from a `projects/<slug>/` directory name.
- Project metadata moves from `.system/engine/projects/{slug}--{id8}/
project.json` to user-facing `projects/<slug>/.project.json`.
One-shot startup migration copies legacy files over, idempotent.
- `HybridStore::load_projects_from_workspace` scans `projects/*/` and
synthesizes a stub `Project` for bare directories, so a write under
`projects/foo/` surfaces immediately on restart.
- `EffectBridgeAdapter::ensure_project_for_memory_write` hook runs
after a successful `memory_write`: if the target is under
`projects/<slug>/...`, finds-or-creates the project and splices
`project_id` into the tool output (enables
`{{call-N.project_id}}` template refs).
- Extract `resolve_project_ref` helper from the inline block in
`handle_mission_call` — now used by both `mission_create`'s
`project_id` param and future project-aware tools.
Skills (13 files)
- Mechanical `commitments/` → `projects/commitments/` across the nine
commitment-domain skills (commitment-setup, -triage, -digest,
decision-capture, delegation-tracker, idea-parking,
tech-debt-tracker, product-prioritization, security-review).
- Four persona setup skills (ceo-setup, developer-setup,
trader-setup, content-creator-setup) gain an explicit "declare the
project" step (write `projects/commitments/AGENTS.md` with
persona-specific operating principles) and pass
`project_id: "commitments"` on every `mission_create`. Setup
markers move to `projects/commitments/.<persona>-setup-complete`.
- `ceo-setup` gets a v0.4.0 rewrite that also installs two dashboard
widgets under `projects/commitments/.system/widgets/`:
`commitments-this-week` (overdue / due / completed counts) and
`delegations-waiting` (delegation list with stale-at-2-days flag).
Both poll `projects/commitments/widgets/state.json`, refreshed by
the triage mission each run.
Tests
- Three new unit tests in `bridge::effect_adapter::tests`:
`extract_project_slug_recognizes_project_paths`,
`extract_project_slug_rejects_degenerate_targets`,
`project_new_is_deterministic_from_user_and_slug`.
- Update `tests/e2e_live_personas.rs` path assertions
(`workspace_paths`, `read_under`, `verify_setup_landed`,
`DEV_SETUP_CHECKS` needles, two workflow turn messages) to the new
`projects/commitments/` prefix.
- Add a diagnostic dump in `run_turn` when a persona workflow turn
times out with no response, so live-test hangs surface the
captured status events instead of an opaque panic.
No backcompat for the old flat `commitments/` layout — pre-production
deployment, nothing in the wild depends on it.
Add missing struct fields (engine_store, skill_registry) and setter methods to EffectBridgeAdapter, expose MissionManager::store() accessor, add sync_v1_skill_to_store to skill_migration, and remove references to fields/methods not yet on staging (Project::goals/metrics, LiveTestHarnessBuilder::with_skills_dir, V2SkillMetadata::bundle_path). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request transitions the engine to a project-centric workspace model where projects are implicitly defined by their directory structure under projects/. Key updates include deterministic project ID generation using UUID v5, auto-registration of projects during memory writes, and a migration of all persona skills to the new pathing conventions. The bridge layer was also enhanced to sync runtime skill installations to the v2 store and resolve project references by slug or name. Feedback identifies a potential ambiguity in project resolution due to prefix-based slug matching and suggests adopting more idiomatic Rust string trimming methods in the slugification utility.
| let matched = projects.iter().find(|p| { | ||
| let name_lower = p.name.to_lowercase(); | ||
| let name_slug = ironclaw_engine::types::slugify_simple(&p.name); | ||
| name_lower == needle | ||
| || name_slug == needle | ||
| || name_slug.starts_with(&format!("{needle}-")) | ||
| }); |
There was a problem hiding this comment.
The slug matching logic name_slug.starts_with(&format!("{needle}-")) allows for prefix matching of slugs. This could lead to ambiguity if multiple projects have slugs with the same prefix (e.g., my-project-a and my-project-b would both match my-project). Since find() returns the first match, this could result in selecting the wrong project. Use exact matching or ensure proper word boundaries are checked to avoid false positives.
References
- When detecting commands or keywords in a string, use token-based or word-boundary checks instead of simple substring containment to avoid false positives.
| while out.ends_with('-') { | ||
| out.pop(); | ||
| } |
There was a problem hiding this comment.
This loop for removing trailing dashes can be made more idiomatic and potentially more efficient by using trim_end_matches and truncate to handle character boundaries correctly and improve readability.
let new_len = out.trim_end_matches('-').len();
out.truncate(new_len);References
- Prefer idiomatic string trimming methods like trim_matches or trim_end_matches over manual loops or chained prefix/suffix stripping for better readability and performance.
henrypark133
left a comment
There was a problem hiding this comment.
- High –
src/bridge/effect_adapter.rs:1302-1308now resolves a project reference by accepting any slug whose normalized name merely starts with"{needle}-". That makes references ambiguous as soon as one project slug is a prefix of another, so a write intended for one project can be rebound to a different project owned by the same user. This path is used to resolve project-backed memory writes, so the failure mode is silent misrouting rather than a clean "multiple matches" error.
I would keep the exact-name / exact-slug matches, but drop the prefix fallback unless the API also detects and rejects ambiguous matches explicitly.
Residual risk: the workspace-backed registration flow is broad, so I also spot-checked the synthetic-project path and migration adapter behavior. I did not find a second verified blocker there.
zmanian
left a comment
There was a problem hiding this comment.
Summary
Large PR (+1041/-194 across 21 files) that makes projects/<slug>/ the user-facing project layout with deterministic ProjectId::from_slug(user_id, slug) via UUID v5, auto-registers a Project entity on memory_write to projects/<slug>/..., syncs live-installed v1 skills into the v2 engine doc store, and renames several persona skills (ceo-assistant → ceo-setup, etc.) with setup_marker-gated activation. Core design is sound; concerns are mostly around slug resolution ambiguity, a breaking skill rename, and scope creep.
Findings
Blocking
None, but the slug-prefix matching and skill rename deserve explicit sign-off before merge.
Suggestions
- Slug-prefix resolution is ambiguous (
src/bridge/effect_adapter.rs:1307-1322,resolve_project_ref). The slug-matching branch usesname_slug.starts_with(&format!("{needle}-")). If a user has bothcommitandcommitmentsprojects,mission_create(project_id: "commit")matchescommit(exact) — fine. Butmission_create(project_id: "comm")matchescommitmentsfirst iff it sorts first inlist_projects, and ordering is storage-backend-dependent. Prefer exact-match-only with a fallback to "ambiguous prefix — please specify" when multiple projects match. Resolving silently to the wrong project is a correctness footgun. - Bare-project visibility leak (
src/bridge/store_adapter.rs:674-709,synth_bare_project). Aprojects/<slug>/directory without.project.jsongets synthesized withuser_id = shared_owner_id(). Thenlist_projects(real_user_id)won't return it (filters by user_id). Two issues: (a) the user who owns the workspace can't see their own bare project in the dashboard; (b) the bare project leaks into the shared-owner namespace, so it becomes globally visible. If the workspace always flows throughensure_project_for_memory_write(which correctly uses the real user_id), bare projects only arise from the migration path. Worth either (i) deriving user_id from the workspace owner when synthesizing, or (ii) documenting that bare projects are a migration/cleanup artifact and filtering them out oflist_projectsunless the caller opts in. - Path traversal on
projects/<slug>/...(src/bridge/effect_adapter.rs:1252-1269,extract_project_slug_from_target). Rejects.,..,.hiddencorrectly. But this runs AFTER a successfulmemory_write, so the path has already been resolved by the workspace layer. Ifmemory_writeever passes through URL-encoded or Unicode-normalized variants (projects/%2e%2e/evil/foo.md,projects/⋯/evil.md), the slug extractor here sees the pre-resolved form. Verify the workspace layer canonicalizes first — if not, we have a project-namespace confusion vector. Low probability but worth pinning with a test. - Breaking skill rename without migration (
skills/ceo-assistant/SKILL.md→ceo-setup,content-creator-assistant→content-creator-setup,developer-assistant→developer-setup). Users or workspaces that reference the old skill names (via/ceo-assistantslash commands, saved routines, scheduled jobs, MEMORY.md pointers) get silently broken. Thesetup_markeractivation gate assumes clean installs. Either (a) leave compatibility shims for the old names that re-point, or (b) document the migration explicitly and add a startup check that warns if the old names are referenced anywhere. This is the item most likely to surprise users post-merge. sync_v1_skill_to_storeusesshared_owner_id()(src/bridge/skill_migration.rs:130). Intentional per comment ("registry-sourced"), but means every user sees the installed skill as a shared doc in their project. If an attacker installs a skill viaskill_installfrom user A's session, user B now has it in their v2 doc space too. For trusted-registry skills this is fine; for arbitrary names-as-keys across users this could be surprising. Confirmskill_installapproval gating prevents cross-user pollution before merge.- Scope creep. This PR bundles: project storage migration + skill v2 sync + 5 persona skill rewrites +
MissionManager::store()accessor + UUIDv5 feature flag. Any one of these is a PR on its own. Future cherry-picks from #2504 would land better as smaller slices. - Migration idempotence corner case (
src/bridge/store_adapter.rs:663-707,migrate_legacy_project_jsons). Ifws.write(new_path)succeeds butws.delete(legacy_path)silently fails (let _ = ws.delete(...)), next startup sees both files. The code guards withws.read(&new_path).await.is_ok()and tries the delete again — correct — but a persistent delete failure leaves the legacy path forever. Consider loggingdebug!on the delete failure so you notice if this gets stuck in prod. - Dual-backend compliance. All new persistence goes through the
Storetrait (list_projects,save_project,save_memory_doc,list_shared_memory_docs) — no new raw SQL, so Postgres and libSQL both work. Good.
Nits
crates/ironclaw_engine/src/types/project.rs:77— thePROJECT_ID_NAMESPACEUUID is load-bearing ("burning this value would rotate every user's project IDs"). Worth a#[test] fn namespace_is_stablethat asserts the UUID hasn't changed andProject::new("user-1", "commitments", "")produces a pinned UUID. CI guard against accidental edits.src/bridge/effect_adapter.rs:1256— slug extractor doesn't explicitly reject trailing whitespace or path separators inside the slug.slugify_simplewould collapse them, but the directrest.split_once('/')split doesn't. A target likeprojects/ foo /bar.mdyields slug" foo ". Likely rejected downstream, but worth a test.crates/ironclaw_engine/Cargo.toml:29— addingv5to theuuidcrate features is the right minimal change.
Verdict
COMMENT — core design (workspace as project source of truth, deterministic IDs, auto-registration) is right. The slug-prefix ambiguity and skill-rename breaking change are the two items I'd resolve (or explicitly accept) before merge. Everything else is incremental hardening and scope-reduction feedback for future PRs.
Cross-cutting concern (across #2530, #2531, this PR)Not specific to this PR — posting here because it's the freshest example. Flagging a pattern that emerged while reviewing the six-PR batch cherry-picked from #2504. Three of the six PRs add a "convenience shortcut" that moves a security-relevant decision from an explicit approval gate to a downstream predicate:
None of these is catastrophic in isolation, and each has a sensible UX motivation (don't nag about an already-loaded skill, let the user name skills directly, tolerate abbreviated project names). Individually I approved each with a callout. The pattern worth discussing: the safety properties of the skill/project/tool system are increasingly "gated by a predicate downstream" rather than "gated by approval at the use site". That works as long as every downstream predicate stays airtight. It scales poorly — each new convenience shortcut adds an implicit contract that the next refactor has to preserve, and you can't Two questions worth a short async thread before this batch lands:
Happy to split this into a separate design issue if you'd rather not thread on a PR. Pinging here because the reviewers of all three PRs (and the author of #2504) are already watching. cc @serrrfirat |
henrypark133
left a comment
There was a problem hiding this comment.
Review: project reference resolution is still ambiguous
The workspace-backed registration work is useful, but the slug-resolution blocker is still present in the current head.
Concerning: prefix fallback can still bind a write to the wrong project
File: src/bridge/effect_adapter.rs:1302
resolve_project_ref() still accepts name_slug.starts_with(&format!("{needle}-")) as a match. Once one project slug is a prefix of another, a partial reference can silently resolve to whichever project happens to appear first in list_projects(...). This path is used to resolve project-backed memory and mission actions, so the failure mode is silent misrouting rather than a clean ambiguity error.
Suggested fix: keep exact UUID / exact name / exact slug matches, but drop the prefix fallback unless the API also detects and rejects multiple matches explicitly.
Summary:
- Recommended verdict: Request changes
- Prior feedback status: partially unresolved; the slug-prefix ambiguity remains in the current head
- Residual risk: I also spot-checked the bare-project synthesis path and it still carries shared-owner fallback semantics worth revisiting, but the prefix match is the concrete blocker.
…#2533) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Merged Addressed in this push
Intentionally not changed
Verification
🤖 Generated with Claude Code |
Brings in ~80 PRs merged into staging since the original cherry-pick base (a34bba2). Notable absorbed work: engine-v2 sandbox + mounts, capability registry plumbing, gateway module refactor (ironclaw#2599), portfolio tooling, workspace-mount threading on EffectBridgeAdapter, v2 skill migration fields (bundle_path, source_url), and the list_skills_global() cross-project skill dedup. Conflicts resolved: - crates/ironclaw_engine/src/runtime/mission.rs: kept staging's more specific docstring on MissionManager::store(). - skills/{ceo,commitment,content-creator,developer,trader}-setup/SKILL.md: kept PR's projects/commitments/... marker paths (the migration IS the point of this PR) while preserving staging's target: param name and /plan tip additions. - src/bridge/effect_adapter.rs: kept PR's ensure_project_for_memory_write, resolve_project_ref (post-review fix from c88ad2a: no prefix fallback) and extract_project_slug_from_target; also took staging's WorkspaceMounts + CapabilityRegistry struct fields, mission_create guardrail validation, and mod mission_store caller-level tests. - src/bridge/skill_migration.rs: kept staging's list_skills_global() lookup (prevents duplicate shared skill docs across projects) and async v1_skill_to_memory_doc — addresses zmanian's cross-user skill-visibility concern. - src/bridge/store_adapter.rs: merged PROJECTS_PREFIX + PROJECTS_ROOT + PROJECT_METADATA_FILENAME constants; kept both PR's synth_bare_project and staging's is_globally_shared + validate_orchestrator_content. Dropped staging's now-unused UUID-suffixed project_dir in favor of PR's user-facing variant. Review follow-ups applied in this merge: - synth_bare_project now takes the workspace owner's user_id instead of shared_owner_id(), so bare projects/<slug>/ directories surface in list_projects(real_user_id) and don't leak into the shared namespace (addresses zmanian's "Bare-project visibility leak" finding). - namespace_uuid_is_stable test now also pins the derived UUID for Project::new("user-1", "commitments", "") so any drift in ProjectId::from_slug is a compile-checkable failure, not a silent re-ID of every workspace-backed project (addresses zmanian's nit #1). Verified with: cargo check --all-features, cargo clippy --all --tests --all-features -- -D warnings (clean), cargo test --lib -p ironclaw_engine (437 passed), cargo test --lib bridge:: (298 passed). Pre-existing failures in tools::builtin::skill_tools::tests::test_zip_* reproduce on pristine origin/staging and are unrelated to this merge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d2c040c to
736c8ff
Compare
ilblackdragon
left a comment
There was a problem hiding this comment.
Review — workspace-backed project registration + adapter improvements
Summary: Promotes projects/<slug>/ from an engine-internal path to the user-facing declaration gesture, adds deterministic ProjectId::from_slug (UUID v5), and auto-registers projects on memory_write. Also extracts resolve_project_ref for mission_create and adds live-install sync for skills.
What's good
ProjectId::from_slug+ pinnedPROJECT_ID_NAMESPACEtest (project.rs:119-125) is the right shape — idempotency is enforced at the ID layer, so duplicatesave_projectcalls under concurrent writes can't fork the project entity.- Extracting
resolve_project_refout of the inlinemission_createblock is a clean win; de-dupes the slug-resolution with the existing UUID validator. extract_project_slug_from_targetrejects.,.., and.hiddenprefixes — path traversal is blocked.- IDOR:
ensure_project_for_memory_writescopeslist_projects(user_id)to context; cannot synthesize cross-user projects.
Correctness issues
1. Inconsistent project ID derivation between the two creation paths. (likely bug)
- `ensure_project_for_memory_write` (effect_adapter.rs:1261) constructs via `Project::new(user_id, slug, "")`, which internally does `slugify_simple(name)` before `ProjectId::from_slug`.
- `synth_bare_project` (store_adapter.rs:1755-1769) constructs directly with `ProjectId::from_slug(user_id, slug)` using the raw directory name as slug, bypassing `slugify_simple`.
For a path like `projects/My Project/foo.md` (or any slug that `slugify_simple` would normalize), the two code paths produce different `ProjectId`s for the same directory. Workspace reload via `load_projects_from_workspace` sees ID A; the next `memory_write` creates ID B. A duplicate project entity materializes.
The common `projects/commitments/...` case works because `slugify_simple("commitments") == "commitments"`. A non-canonical slug is the trigger.
Fix: have `synth_bare_project` call `ProjectId::from_slug(user_id, &slugify_simple(slug))` (and store `name: slugify_simple(slug)`), or add a `Project::from_workspace_dir(user_id, raw_slug)` helper so both paths go through the same normalization. Add a regression test that drives a non-canonical slug through both paths and asserts the IDs match.
2. `project_slug` on `HybridStore` (store_adapter.rs:830-840) still uses the old `slugify(name, uuid)` with UUID suffix, while `project_dir`/`project_path` now use `project_slug_for_name` (no UUID). Confirm there's no call site that combines the two — e.g. a mission written to `{project_slug}/missions/...` using the old scheme while the project metadata lives at `projects/{new-slug}/.project.json`. If `project_slug` still targets `.system/engine/projects/...` for missions only, leave a comment on the function clarifying scope; otherwise you'll get silently orphaned missions on rename.
3. Migration failure mode silently drops projects. `migrate_legacy_project_jsons` logs at `debug!` when parse/write/delete fails (store_adapter.rs:1714, 1727, 1733). A user whose legacy `project.json` fails to migrate loses their project metadata with no visible warning. Upgrade to `warn!` and — per CLAUDE.md — ensure the migration is retried on the next load (it already is, because the legacy file is only deleted after successful write — good). But a parse failure (line 1714) never retries. Consider moving the bad file aside (`.broken.json`) rather than leaving it to re-fail silently on every boot.
4. Race on auto-register. `ensure_project_for_memory_write` does list→find→create with no lock. Two concurrent `memory_write`s to the same project slug both miss the existing project. Because IDs are deterministic, `save_project` becomes a repeat upsert with the same ID — tolerable, but document this. If `save_project` clobbers `created_at`, that's a real issue. Worth confirming in the `Store` impl.
Testing gaps (per `.claude/rules/testing.md` — "Test Through the Caller, Not Just the Helper")
`extract_project_slug_from_target` is a predicate that gates a side effect (`save_project`) with a wrapper (`ensure_project_for_memory_write`) between them, and the wrapper computes additional inputs (user_id from context). All three conditions from the rule apply:
- Add an integration-tier test that drives `execute_action("memory_write", ...)` with `target = "projects/newproj/AGENTS.md"` and asserts (a) the output includes `project_id`, (b) a project named `newproj` exists in the store afterward, (c) a second write to the same slug returns the same `project_id` and does not create a duplicate.
- Add a test for the slug-mismatch bug in (1): write to `projects/ foo /bar.md` (or any slug that `slugify_simple` normalizes), reload the store, and assert the in-memory project ID equals a subsequent `Project::new` with the same inputs.
Style nits
- `project_dir(name, _project_id)` (store_adapter.rs:1796) takes an unused `ProjectId`. Either drop the param and update call sites or keep it typed for future use with a comment — leaving `_project_id` unused is drift bait.
- The `Project::new` docstring (project.rs:81-91) runs eight lines; CLAUDE.md pushes toward one-line doc comments. Not a blocker, but worth trimming to the invariant ("ID is deterministic from `(user_id, slug(name))`; use `ProjectId::new()` for throwaway fixtures").
- `slugify_simple` is in `types/mod.rs` but not re-exported at the crate root. `effect_adapter` accesses it via `ironclaw_engine::types::slugify_simple` — fine, but inconsistent with `Project` / `ProjectId` which are at the crate root. Either bring both to the same level or leave a comment.
Security
No new concerns beyond what's above. Path traversal, IDOR, and auth surfaces are all properly gated.
Verdict
Solid direction — workspace-as-declaration is a clean abstraction. Blocking issues are #1 (ID mismatch between `synth_bare_project` and `Project::new`) and the missing caller-level test that would have caught it. The migration fallback behavior and `project_slug` scoping are worth clarifying before merge.
…rdening, caller-level tests
- synth_bare_project now normalizes the raw dir name via slugify_simple
before ProjectId::from_slug, matching Project::new. Returns Option so
unsluggable dirs (`---`, `!!!`) don't produce phantom projects.
- migrate_legacy_project_jsons upgraded to warn! and moves unparseable
legacy project.json aside as project.broken.json so the user can
recover instead of the engine masking the loss on every boot.
- Document project_slug's engine-internal (mission-path, UUID-suffixed)
scope vs project_dir's user-facing (no-UUID) scope so the two slug
schemes aren't conflated in future edits.
- Drop unused ProjectId param from project_dir / project_path.
- Trim Project::new docstring per CLAUDE.md style.
Tests added (19):
- types::project: slug variant collapse, unicode, empty-slug stability,
run/edge normalization
- store_adapter unit: project_slug_for_name contract, project_dir/path,
synth_bare_project↔Project::new ID equivalence across 12 weird names,
unsluggable-dir rejection, cross-user isolation
- store_adapter migration_tests (libsql): bare-dir load, metadata over
synth, non-canonical skip, weird-slug collapse, user-edit preservation,
broken-JSON move-aside
- effect_adapter caller-level: drives execute_action("memory_write")
for canonical / idempotent / non-projects / nested / weird-slug /
cross-user / pathological targets per .claude/rules/testing.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Pushed 9838c01 addressing the review. Quick walk-through: 1. Slug mismatch between 2. 3. Migration silent failure mode — upgraded 4. Auto-register race — deterministic Style nits — also addressed:
Tests added (19) — per
|
…ments (nearai#2533) * feat(projects): workspace-backed project registration; migrate commitments into projects/commitments/ [cherry-pick-target: feat/projects-workspace-backed] Replace the parallel `.system/engine/projects/*.json` schema with workspace-backed project registration. Writing any file under `projects/<slug>/` is now the declaration that the project exists — the engine auto-registers it on `memory_write`, and `mission_create` can reference it by slug. The model reasons about projects through normal workspace APIs instead of a hidden sidecar schema. Engine + bridge - `ProjectId::from_slug(user_id, slug)` derives a stable v5 UUID; `Project::new` routes through it so constructing the same project twice returns the same ID (no duplicates). - `slugify_simple` in `ironclaw_engine::types` — pure slug, no UUID suffix, reverses cleanly from a `projects/<slug>/` directory name. - Project metadata moves from `.system/engine/projects/{slug}--{id8}/ project.json` to user-facing `projects/<slug>/.project.json`. One-shot startup migration copies legacy files over, idempotent. - `HybridStore::load_projects_from_workspace` scans `projects/*/` and synthesizes a stub `Project` for bare directories, so a write under `projects/foo/` surfaces immediately on restart. - `EffectBridgeAdapter::ensure_project_for_memory_write` hook runs after a successful `memory_write`: if the target is under `projects/<slug>/...`, finds-or-creates the project and splices `project_id` into the tool output (enables `{{call-N.project_id}}` template refs). - Extract `resolve_project_ref` helper from the inline block in `handle_mission_call` — now used by both `mission_create`'s `project_id` param and future project-aware tools. Skills (13 files) - Mechanical `commitments/` → `projects/commitments/` across the nine commitment-domain skills (commitment-setup, -triage, -digest, decision-capture, delegation-tracker, idea-parking, tech-debt-tracker, product-prioritization, security-review). - Four persona setup skills (ceo-setup, developer-setup, trader-setup, content-creator-setup) gain an explicit "declare the project" step (write `projects/commitments/AGENTS.md` with persona-specific operating principles) and pass `project_id: "commitments"` on every `mission_create`. Setup markers move to `projects/commitments/.<persona>-setup-complete`. - `ceo-setup` gets a v0.4.0 rewrite that also installs two dashboard widgets under `projects/commitments/.system/widgets/`: `commitments-this-week` (overdue / due / completed counts) and `delegations-waiting` (delegation list with stale-at-2-days flag). Both poll `projects/commitments/widgets/state.json`, refreshed by the triage mission each run. Tests - Three new unit tests in `bridge::effect_adapter::tests`: `extract_project_slug_recognizes_project_paths`, `extract_project_slug_rejects_degenerate_targets`, `project_new_is_deterministic_from_user_and_slug`. - Update `tests/e2e_live_personas.rs` path assertions (`workspace_paths`, `read_under`, `verify_setup_landed`, `DEV_SETUP_CHECKS` needles, two workflow turn messages) to the new `projects/commitments/` prefix. - Add a diagnostic dump in `run_turn` when a persona workflow turn times out with no response, so live-test hangs surface the captured status events instead of an opaque panic. No backcompat for the old flat `commitments/` layout — pre-production deployment, nothing in the wild depends on it. * fix: adapt cherry-picked project registration to staging API surface Add missing struct fields (engine_store, skill_registry) and setter methods to EffectBridgeAdapter, expose MissionManager::store() accessor, add sync_v1_skill_to_store to skill_migration, and remove references to fields/methods not yet on staging (Project::goals/metrics, LiveTestHarnessBuilder::with_skills_dir, V2SkillMetadata::bundle_path). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(bridge): address review — drop slug-prefix fallback, harden tests (nearai#2533) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(bridge): address PR nearai#2533 review — slug-consistency, migration hardening, caller-level tests - synth_bare_project now normalizes the raw dir name via slugify_simple before ProjectId::from_slug, matching Project::new. Returns Option so unsluggable dirs (`---`, `!!!`) don't produce phantom projects. - migrate_legacy_project_jsons upgraded to warn! and moves unparseable legacy project.json aside as project.broken.json so the user can recover instead of the engine masking the loss on every boot. - Document project_slug's engine-internal (mission-path, UUID-suffixed) scope vs project_dir's user-facing (no-UUID) scope so the two slug schemes aren't conflated in future edits. - Drop unused ProjectId param from project_dir / project_path. - Trim Project::new docstring per CLAUDE.md style. Tests added (19): - types::project: slug variant collapse, unicode, empty-slug stability, run/edge normalization - store_adapter unit: project_slug_for_name contract, project_dir/path, synth_bare_project↔Project::new ID equivalence across 12 weird names, unsluggable-dir rejection, cross-user isolation - store_adapter migration_tests (libsql): bare-dir load, metadata over synth, non-canonical skip, weird-slug collapse, user-edit preservation, broken-JSON move-aside - effect_adapter caller-level: drives execute_action("memory_write") for canonical / idempotent / non-projects / nested / weird-slug / cross-user / pathological targets per .claude/rules/testing.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Illia Polosukhin <ilblackdragon@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
MissionManager::store()accessor for project auto-registrationsync_v1_skill_to_storefor live skill install sync to v2 engineCherry-picked from #2504.
Test plan
cargo checkpassescargo clippy --all --all-featuresclean🤖 Generated with Claude Code