[bug] Namespace resolution is inconsistent across resource types: push ignores --role/--project for rules and agents, and root-level claudemd never ships
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4.8k
- Forks
- 342
- Avg merge
- 13h 48m
- Merged PRs (30d)
- 211
Description
Description
Three code paths implement the namespace convention differently. filterRulesByKnowledgeNamespaces is the reference implementation: it matches the first path segment against the active namespace set, and ships any name without a segment to every member.
// src/pull.ts:249-255
if (slashIndex === -1) return true;
const namespace = rule.name.slice(0, slashIndex);
return knowledgeNamespaces.includes(namespace);
Two paths diverge from it. Neither emits a message when it does.
1. push does not apply --role or --project to rules or agents
--project resolves to the project's skills namespace and collapses into options.role (src/push.ts:292-320). Step 4 then applies that namespace to skills alone:
// src/push.ts:799-802
selectedItems.filter(i => i.type === 'skills' && i.status === 'new' && !i.namespace)
A rule pushed with --project front-app is written to rules/<item.name>.md (src/resources/rules.ts:154-155). That path carries no first segment, so the next teamai pull ships the rule to every member of the team.
<repo>/.claude/rules/my-rule.md project scope scans projectRoot (src/types.ts:1521-1532)
-> teamai push --project front-app flag applies to skills only (src/push.ts:799-802)
-> rules/my-rule.md written with no first path segment
-> teamai pull slashIndex === -1 returns true (src/pull.ts:252)
-> every member of the team
A new agent lands at the agents/ root (src/resources/agents.ts:339-350) and reaches every member through the !agent.namespace || branch (src/pull.ts:302).
The namespace an author needs for a rule is the project's knowledge namespace. ProjectResourceNamespacesSchema permits that namespace to differ from the skills namespace the flag resolved, so the flag targets the wrong directory even where a rule is placed by hand first.
2. collectClaudemdFiles has no root-level branch
Its only reads are path.join(claudemdDir, ns), once per active namespace (src/pull.ts:1543-1553). A file directly under claudemd/ is never opened.
A member with no role receives every namespace, because a null roleContext falls through to listDirs (src/pull.ts:1537-1540). Assigning a role therefore removes content from that member's CLAUDE.md.
Where the convention holds and where it breaks
| Resource | Namespaced on pull | Root ships to everyone | --role/--project honoured on push |
|---|---|---|---|
| rules | yes (knowledge) |
yes (src/pull.ts:252) |
no |
| agents | yes (agents) |
yes (src/pull.ts:302) |
no |
| claudemd | yes (knowledge) |
no | not pushable |
skills is the only type the flag places, and only for an item that is new and carries no namespace already. pushCore iterates ['skills','rules','env','agents'] (src/push.ts:512-519), so claudemd has no push path at all.
Reproduction
Rules and agents ignoring the flag:
- Declare a project
front-appinmanifest/projects.yamlwith aknowledgenamespace and askillsnamespace. - In the code repository, run
teamai projects set front-app. The working directory is now bound to that project in project scope. - Create a rule at
.claude/rules/my-rule.mdinside that repository. Project scope resolves tool directories against the project root, not against$HOME(src/types.ts:1521-1532). - Run
teamai push --project front-app --all. - Open the resulting MR. The rule is at
rules/my-rule.md, not under the project'sknowledgenamespace.
Root-level claudemd not shipping:
- Add
claudemd/shared.mdto the team repo, directly underclaudemd/. - As a member with a role, run
teamai pull --force. - Read the
TEAMAI_CLAUDEMD_STARTblock in the tool'sCLAUDE.md. It does not contain the content ofshared.md. - Clear the member's role and repeat step 2. The content appears.
Expected behavior
teamai push --project <id> places a rule under the project's knowledge namespace and an agent under its agents namespace. Where a type has no namespace to resolve for the named project, the command fails and names that type, rather than writing to the shared root.
collectClaudemdFiles ships claudemd/*.md to every member, matching the root-level branch that filterRulesByKnowledgeNamespaces already implements.
Suggested fix
Extend the push filter to the two types that already namespace on pull, and resolve each destination from its own axis:
selectedItems.filter(i =>
- i.type === 'skills' && i.status === 'new' && !i.namespace
+ (i.type === 'skills' || i.type === 'rules' || i.type === 'agents')
+ && i.status === 'new' && !i.namespace
)
skills resolves from resources.skills, rules from resources.knowledge, and agents from resources.agents. Where the named project declares no namespace for a selected type, fail and name that type rather than writing to the shared root.
Give collectClaudemdFiles the root branch that filterRulesByKnowledgeNamespaces already has:
collectClaudemdFiles(claudemdDir, roleContext)
for ns of activeNamespaces.knowledge
read path.join(claudemdDir, ns)
+ read the .md files directly under claudemdDir
Environment
Both findings are read from source at main (97a0277, package version 0.22.0). The installed CLI is 0.24.0, the current npm latest.
- OS: macOS 26.5.2
- Node.js: v22.22.2
- teamai: 0.24.0
- Provider: GitHub
- AI tool(s): Claude Code
Logs
No --verbose output applies. Both findings come from reading source at the commit above, not from a captured run.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with filterRulesByKnowledgeNamespaces and collectClaudemdFiles in src/pull.ts, then trace namespace resolution in src/push.ts and the destination writers in src/resources/rules.ts and src/resources/agents.ts. Reproduce the project-scoped push and root-level claudemd cases described in the issue. Done means rules and agents use their declared project namespaces, missing destinations fail explicitly, and root-level claudemd files ship to every member.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 64/100