Skip to content

feat(projects): add write commands to manage the projects manifest (add/update/remove) #756

Closed

Description

@jeff-r2026

Background

manifest/projects.yaml is the single source of truth for a team's projects (the dimension orthogonal to role, introduced in #375). Today the CLI can only read it:

  • teamai projects list / set <ids> / members <id> — all read-only or local-config writes.
  • The manifest writer saveProjectsManifest() already exists (src/projects.ts:122), but no command calls it — only src/__tests__/projects.test.ts exercises it.

So creating or editing a project means an admin hand-edits manifest/projects.yaml, commits, and opens a PR manually. This is error-prone (a project id becomes a directory name skills/<id>/, learnings/<id>/, so a stray / or .. breaks distribution) and asymmetric with roles, which already has a full admin CRUD surface (roles init/add/update/remove, src/roles-cmd.ts).

Proposal

Add three admin write commands under teamai projects, mirroring roles:

teamai projects add <id> \
    --namespaces common,<id> \      # required; applied to knowledge/skills/learnings/agents
    --name "<display name>" \        # optional
    --description "<text>"           # optional

teamai projects update <id> \
    --add-namespaces <a,b> \         # union into existing
    --remove-namespaces <c> \        # subtract; cannot empty the set
    --name "<...>" --description "<...>"

teamai projects remove <id>
  • list / set / members stay exactly as they are.
  • No separate init. projects.yaml is optional by design (a team without partitioning has none, and an empty projects: [] is valid — src/projects.ts:45-50). The first projects add in a repo with no manifest simply creates the file with one entry. This keeps the surface minimal (Occam) while still covering the "start from nothing" path that roles needs a dedicated init for.
  • Every mutation runs pull → write → push branch → PR (reuse pushManifestChange, scoped to manifest/) and supports --dry-run, exactly like roles.

Design notes (implementation is largely a mirror of roles)

  • Reuse existing plumbing. runRolesEdit (self-mode knowledge-worktree handling for single-repo mode, feat: 让项目级 .teamai/ 跟着 git 仓库走(clone 即完成项目初始化) #198) and pushManifestChange in src/roles-cmd.ts are already generic — lift/share them for a projects-cmd.ts writer path so self mode is handled identically.
  • Building blocks already present in src/projects.ts: saveProjectsManifest (re-validates before writing), findProject, listProjectIds, unknownProjectMessage, and the isSafeNamespaceSegment guard that rejects unsafe ids at the manifest boundary.
  • Namespaces. --namespaces sets one namespace set applied across all project resource types (knowledge/skills/learnings/agents), matching how admins actually author them (see Proposal: 一个团队仓库承载多个项目(project 作为与 role 正交的维度) #375 examples where every axis = [<id>]). Per-axis editing stays a hand-edit for now.
  • Guardrails to mirror from roles: duplicate-id on add, unknown-id on update/remove, "cannot remove all namespaces", and a remove warning that members with that project active will fall back to shared learnings + role-only sync on next pull.

Local resource reclamation (the important part)

This is where the write commands meet the existing pull-cleanup machinery, and it needs to be called out explicitly:

  • Already works. resolveResourceNamespaces (src/resource-namespaces.ts:60-88) collects every project's skill namespace from the current manifest (allProjectSkillNamespaces); the active set comes only from the projects this directory selected. A skill in a namespace selected by neither an active role nor an active project lands in inactiveSkillNames and is deleted by pull Step 3's cleanupInactiveNamespaceSkills (src/pull.ts:1209); agents go through cleanupInactiveNamespaces. projects set (including clearing) sets state.lastPullRev = null to force a full sync + cleanup next pull. So a member switching a directory off a project is already covered.

  • ⚠️ Gap to fix. allProjectSkillNamespaces is collected from the current manifest. After an admin runs projects remove <id>, that project and its namespace disappear from the manifest, so the already-deployed skills may fall into neither the active set nor the "known namespace" set — and thus may not land in inactiveSkillNames, so they leak (Step 3b's knownRepoSkillNames.has(dir) likewise depends on the team repo still knowing the skill, and remove usually deletes the source dir too). This matches roles remove's current behavior — it only warns that members fall back, and does not guarantee the old role's skills are cleaned up.

  • Fix direction. Verify the leak first; if confirmed, reuse the existing tombstone mechanism (cleanupTombstonedResources, src/pull.ts:633), or have pull actively prune "projects still active in local config but no longer defined in the current manifest". The data-safety guard stays skillSafeToRemove (src/pull.ts:463): a target with local edits, unpushed files, or a nested .git is kept + warned, never silently deleted.

Scope / non-goals

Relationship to other issues

Acceptance criteria

  • add/update/remove create/modify manifest/projects.yaml and open a PR, in both git and self (single-repo) modes.
  • Invalid ids (/, ..), duplicate add, unknown update/remove, and namespace-emptying are rejected with actionable English messages.
  • --dry-run prints the intended change without writing/pushing.
  • Namespace add/update/remove. After update adds a namespace, pull delivers that namespace's skills/rules to directories bound to the project; after --remove-namespaces or a rename, pull cleans up the now-inactive namespace's local leftovers (reusing cleanupInactiveNamespaceSkills / resolveResourceNamespaces). Add unit + e2e for "add namespace → pull delivers" and "remove/rename namespace → pull cleans up".
  • Reclamation after projects remove (needs verification, likely needs implementation). After an admin runs projects remove <id>, a member directory that previously ran projects set <id> must, on the next pull, have the project's exclusive-namespace skills/rules/agents removed with no leftovers; the data-safety guard stays skillSafeToRemove (keep + warn on local changes/unpushed files). Add unit + e2e for "remove → pull → reclaim".
  • Unit tests cover each command + each guardrail; e2e covers both an add→pull→activate round and a remove→pull→reclaim round.
  • Docs updated: docs/usage-guide.md / .zh-CN.md multi-project section, and README* if the command table lists project subcommands.

Activity

  1. huiq777 commented on Sep 24, 2026

    @huiq777
    Contributor

    I'd like to take this one, if nobody else is on it.

    My plan follows the proposal:

    • projects add/update/remove in projects-cmd.ts, mirroring roles. I'll reuse the runRolesEdit / pushManifestChange plumbing by lifting it into a shared helper instead of copying it.
    • The first add creates manifest/projects.yaml, so there is no separate init.
    • Every command supports --dry-run, and remove warns about members who still have the project active.
    • For reclamation after remove, I'll write the "remove → pull" e2e first to confirm whether the leak is real, both when skills/<id>/ is kept and when it is deleted. If it leaks, I'll fix it in pull, keeping skillSafeToRemove as the guard.
    • Unit tests, e2e, and docs (usage-guide en/zh-CN, the skill-data command reference) go in the same PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions