@teammates/cli 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -114,8 +114,9 @@ export class CliProxyAdapter {
114
114
  // Create session file inside .teammates/.tmp so sandboxed agents can access it
115
115
  if (!this.sessionsDir) {
116
116
  const tmpBase = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp");
117
- this.sessionsDir = join(tmpBase, "sessions");
118
- await mkdir(this.sessionsDir, { recursive: true });
117
+ const dir = join(tmpBase, "sessions");
118
+ await mkdir(dir, { recursive: true });
119
+ this.sessionsDir = dir;
119
120
  // Ensure .tmp is gitignored
120
121
  const gitignorePath = join(tmpBase, "..", ".gitignore");
121
122
  const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
@@ -39,8 +39,9 @@ export class CopilotAdapter {
39
39
  // Create session file inside .teammates/.tmp so the agent can access it
40
40
  if (!this.sessionsDir) {
41
41
  const tmpBase = join(teammate.cwd ?? process.cwd(), ".teammates", ".tmp");
42
- this.sessionsDir = join(tmpBase, "sessions");
43
- await mkdir(this.sessionsDir, { recursive: true });
42
+ const dir = join(tmpBase, "sessions");
43
+ await mkdir(dir, { recursive: true });
44
+ this.sessionsDir = dir;
44
45
  const gitignorePath = join(tmpBase, "..", ".gitignore");
45
46
  const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
46
47
  if (!existing.includes(".tmp/")) {
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import { spawn as cpSpawn, exec as execCb, execSync, } from "node:child_process"
11
11
  import { appendFileSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { mkdir, readdir, rm, stat, unlink } from "node:fs/promises";
13
13
  import { tmpdir } from "node:os";
14
- import { join, resolve } from "node:path";
14
+ import { dirname, join, resolve } from "node:path";
15
15
  import { createInterface } from "node:readline";
16
16
  import { promisify } from "node:util";
17
17
  const execAsync = promisify(execCb);
@@ -1431,16 +1431,22 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1431
1431
  const teammatesDir = join(projectDir, ".teammates");
1432
1432
  console.log();
1433
1433
  try {
1434
- const { teammates, files } = await importTeammates(sourceDir, teammatesDir);
1435
- if (teammates.length === 0) {
1434
+ const { teammates, skipped, files } = await importTeammates(sourceDir, teammatesDir);
1435
+ const allTeammates = [...teammates, ...skipped];
1436
+ if (allTeammates.length === 0) {
1436
1437
  console.log(chalk.yellow(" No teammates found at ") + chalk.white(sourceDir));
1437
1438
  console.log(chalk.gray(" The directory should contain teammate folders (each with a SOUL.md)."));
1438
1439
  return;
1439
1440
  }
1440
- console.log(chalk.green(" ✔") +
1441
- chalk.white(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: `) +
1442
- chalk.cyan(teammates.join(", ")));
1443
- console.log(chalk.gray(` (${files.length} files copied)`));
1441
+ if (teammates.length > 0) {
1442
+ console.log(chalk.green("") +
1443
+ chalk.white(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: `) +
1444
+ chalk.cyan(teammates.join(", ")));
1445
+ console.log(chalk.gray(` (${files.length} files copied)`));
1446
+ }
1447
+ if (skipped.length > 0) {
1448
+ console.log(chalk.gray(` ${skipped.length} already present: ${skipped.join(", ")} (will re-adapt)`));
1449
+ }
1444
1450
  console.log();
1445
1451
  // Copy framework files so the agent has TEMPLATE.md etc. available
1446
1452
  await copyTemplateFiles(teammatesDir);
@@ -1452,7 +1458,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1452
1458
  console.log();
1453
1459
  const adapt = await this.askChoice("Adapt now? (y/n): ", ["y", "n"]);
1454
1460
  if (adapt === "y") {
1455
- await this.runAdaptationAgent(this.adapter, projectDir, teammates, sourceDir);
1461
+ await this.runAdaptationAgent(this.adapter, projectDir, allTeammates, sourceDir);
1456
1462
  }
1457
1463
  else {
1458
1464
  console.log(chalk.gray(" Skipped adaptation. Run /init to adapt later."));
@@ -1974,6 +1980,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
1974
1980
  weeklyLogs: [],
1975
1981
  ownership: { primary: [], secondary: [] },
1976
1982
  routingKeywords: [],
1983
+ cwd: dirname(this.teammatesDir),
1977
1984
  });
1978
1985
  // Add status entry (init() already ran, so we add it manually)
1979
1986
  this.orchestrator.getAllStatuses().set(this.adapterName, { state: "idle" });
@@ -3010,18 +3017,25 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
3010
3017
  sourceDir = resolved;
3011
3018
  }
3012
3019
  try {
3013
- const { teammates, files } = await importTeammates(sourceDir, teammatesDir);
3014
- if (teammates.length === 0) {
3020
+ const { teammates, skipped, files } = await importTeammates(sourceDir, teammatesDir);
3021
+ // Combine newly imported + already existing for adaptation
3022
+ const allTeammates = [...teammates, ...skipped];
3023
+ if (allTeammates.length === 0) {
3015
3024
  this.feedLine(tp.warning(` No teammates found at ${sourceDir}`));
3016
3025
  this.refreshView();
3017
3026
  return;
3018
3027
  }
3019
- this.feedLine(tp.success(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: ${teammates.join(", ")} (${files.length} files)`));
3028
+ if (teammates.length > 0) {
3029
+ this.feedLine(tp.success(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: ${teammates.join(", ")} (${files.length} files)`));
3030
+ }
3031
+ if (skipped.length > 0) {
3032
+ this.feedLine(tp.muted(` ${skipped.length} already present: ${skipped.join(", ")} (will re-adapt)`));
3033
+ }
3020
3034
  // Copy framework files so the agent has TEMPLATE.md etc. available
3021
3035
  await copyTemplateFiles(teammatesDir);
3022
3036
  // Queue a single adaptation task that handles all teammates
3023
3037
  this.feedLine(tp.muted(` Queuing ${this.adapterName} to scan this project and adapt the team...`));
3024
- const prompt = await buildImportAdaptationPrompt(teammatesDir, teammates, sourceDir);
3038
+ const prompt = await buildImportAdaptationPrompt(teammatesDir, allTeammates, sourceDir);
3025
3039
  this.taskQueue.push({
3026
3040
  type: "agent",
3027
3041
  teammate: this.adapterName,
package/dist/onboard.d.ts CHANGED
@@ -25,16 +25,13 @@ export declare function copyTemplateFiles(teammatesDir: string): Promise<string[
25
25
  */
26
26
  export declare function importTeammates(sourceDir: string, targetDir: string): Promise<{
27
27
  teammates: string[];
28
+ skipped: string[];
28
29
  files: string[];
29
30
  }>;
30
31
  /**
31
- * Build a comprehensive import-adaptation prompt that runs as a single agent session.
32
- * The agent will:
33
- * 1. Scan the current project
34
- * 2. Evaluate which imported teammates are needed
35
- * 3. Adapt kept teammates (add Previous Projects section + rewrite for new project)
36
- * 4. Create any new teammates the project needs
37
- * 5. Remove teammates that don't apply
32
+ * Build an import-adaptation prompt that runs as a single non-interactive agent session.
33
+ * The agent scans the target project and adapts all imported teammates in one pass.
34
+ * No pauses or approval gates — the agent must complete all work autonomously.
38
35
  *
39
36
  * @param teammatesDir - The .teammates/ directory in the target project
40
37
  * @param teammateNames - Names of all imported teammates
package/dist/onboard.js CHANGED
@@ -129,6 +129,7 @@ export async function importTeammates(sourceDir, targetDir) {
129
129
  }
130
130
  await mkdir(targetDir, { recursive: true });
131
131
  const teammates = [];
132
+ const skipped = [];
132
133
  const files = [];
133
134
  const entries = await readdir(sourceDir, { withFileTypes: true });
134
135
  for (const entry of entries) {
@@ -140,6 +141,7 @@ export async function importTeammates(sourceDir, targetDir) {
140
141
  // Skip if teammate already exists in target
141
142
  try {
142
143
  await stat(destPath);
144
+ skipped.push(entry.name);
143
145
  continue;
144
146
  }
145
147
  catch {
@@ -185,16 +187,12 @@ export async function importTeammates(sourceDir, targetDir) {
185
187
  await writeFile(gitignoreDest, "USER.md\n.*/\n", "utf-8");
186
188
  files.push(".gitignore");
187
189
  }
188
- return { teammates, files };
190
+ return { teammates, skipped, files };
189
191
  }
190
192
  /**
191
- * Build a comprehensive import-adaptation prompt that runs as a single agent session.
192
- * The agent will:
193
- * 1. Scan the current project
194
- * 2. Evaluate which imported teammates are needed
195
- * 3. Adapt kept teammates (add Previous Projects section + rewrite for new project)
196
- * 4. Create any new teammates the project needs
197
- * 5. Remove teammates that don't apply
193
+ * Build an import-adaptation prompt that runs as a single non-interactive agent session.
194
+ * The agent scans the target project and adapts all imported teammates in one pass.
195
+ * No pauses or approval gates — the agent must complete all work autonomously.
198
196
  *
199
197
  * @param teammatesDir - The .teammates/ directory in the target project
200
198
  * @param teammateNames - Names of all imported teammates
@@ -227,101 +225,83 @@ export async function buildImportAdaptationPrompt(teammatesDir, teammateNames, s
227
225
  teammateSections.push(`### @${name}\n${soulBlock}${wisdomBlock}`);
228
226
  }
229
227
  const projectDir = dirname(teammatesDir);
230
- return `You are adapting an imported team to a new project.
228
+ return `You are adapting an imported team to a new project. This is a non-interactive session — complete ALL work without pausing. Do not ask for confirmation or wait for user input.
231
229
 
232
230
  **Source project:** \`${sourceProjectPath}\`
233
231
  **Target project:** \`${projectDir}\`
234
232
  **Target .teammates/ directory:** \`${teammatesDir}\`
235
233
  **Imported teammates:** ${teammateNames.map((n) => `@${n}`).join(", ")}
236
234
 
235
+ > **IMPORTANT:** The \`example/\` directory inside \`.teammates/\` is a **template reference**, NOT a teammate. Do not adapt it, rename it, or treat it as a teammate. When creating new teammates, never use "example" as a folder name.
236
+
237
237
  ## Imported Teammates (from source project)
238
238
 
239
239
  ${teammateSections.join("\n\n---\n\n")}
240
240
 
241
241
  ## Instructions
242
242
 
243
- Work through these phases in order. **Pause after Phase 1 and Phase 2** to present your analysis and get user approval before making changes.
244
-
245
- ### Phase 1: Scan This Project
246
-
247
- Analyze the current project to understand its structure:
248
- - Read the project root: package manifest, README, config files
249
- - Identify major subsystems, languages, frameworks, file patterns
250
- - Understand the dependency flow and architecture
251
-
252
- **Present your analysis to the user and wait for confirmation.**
243
+ Complete these steps in order. Do NOT pause, ask questions, or wait for approval. Make all changes directly.
253
244
 
254
- ### Phase 2: Evaluate Imported Teammates
245
+ ### Step 1: Scan This Project
255
246
 
256
- For each imported teammate, decide:
257
- - **KEEP** their domain or expertise is relevant to this project (even if specific details need updating)
258
- - **DROP** their domain doesn't exist here and their skills aren't transferable
247
+ Read the project root to understand its structure:
248
+ - Package manifest, README, config files
249
+ - Major subsystems, languages, frameworks, file patterns
250
+ - Dependency flow and architecture
259
251
 
260
- Also identify **gaps** major subsystems in this project that none of the imported teammates cover. Propose new teammates for these gaps.
252
+ ### Step 2: Adapt EVERY Imported Teammate
261
253
 
262
- **Present your evaluation as a structured plan and wait for user approval:**
263
- \`\`\`
264
- KEEP: @name1, @name2
265
- DROP: @name3
266
- CREATE: @newname (role description)
267
- \`\`\`
254
+ This is the most important step. For EACH imported teammate listed above, you MUST edit their SOUL.md and WISDOM.md to reflect THIS project, not the source project.
268
255
 
269
- ### Phase 3: Adapt Kept Teammates
256
+ For each teammate's **SOUL.md**:
270
257
 
271
- For each KEEP teammate, edit their SOUL.md and WISDOM.md:
272
-
273
- 1. **Add a "Previous Projects" section** to SOUL.md (place it after Ethics, before any appendix). Compress what the teammate did in the source project into a portable summary:
258
+ 1. **Add a "Previous Projects" section** (place it after Ethics). Compress what the teammate did in the source project:
274
259
  \`\`\`markdown
275
260
  ## Previous Projects
276
261
 
277
- ### <project-name>
262
+ ### <source-project-name>
278
263
  - **Role**: <one-line summary of what they did>
279
264
  - **Stack**: <key technologies they worked with>
280
265
  - **Domains**: <what they owned — file patterns or subsystem names>
281
266
  - **Key learnings**: <1-3 bullets of notable patterns, decisions, or lessons>
282
267
  \`\`\`
283
268
 
284
- 2. **Rewrite the rest of SOUL.md** for this project:
269
+ 2. **Rewrite project-specific sections** for THIS project:
285
270
  - **Preserve**: Identity (name, personality), Core Principles, Ethics
286
- - **Rewrite**: Ownership (primary/secondary file globs for THIS project), Boundaries, Capabilities (commands, file patterns, technologies), Routing keywords, Quality Bar
287
- - **Update**: Any codebase-specific references (paths, package names, tools, teammate names)
288
-
289
- 3. **Update WISDOM.md**:
290
- - **Add** a "Previous Projects" section at the top with a compressed note:
291
- \`\`\`markdown
292
- ## Previous Projects
293
-
294
- ### <project-name>
295
- - Carried over universal wisdom entries from the source project
296
- - Project-specific entries removed or adapted
297
- \`\`\`
298
- - **Keep** wisdom entries that are universal (general principles, patterns, lessons)
299
- - **Remove** entries that reference source project paths, architecture, or tools not used here
300
- - **Adapt** entries with transferable knowledge but old-project-specific details
301
-
302
- ### Phase 4: Handle Dropped Teammates
271
+ - **Rewrite completely**: Ownership (primary/secondary file globs for THIS project's actual files), Boundaries, Capabilities (commands, file patterns, technologies for THIS project), Routing keywords, Quality Bar
272
+ - **Update**: All codebase-specific references paths, package names, tools, teammate names must reference THIS project
303
273
 
304
- For each DROP teammate, delete their directory under \`${teammatesDir}\`.
274
+ For each teammate's **WISDOM.md**:
275
+ - Add a "Previous Projects" note at the top
276
+ - Keep universal wisdom entries (general principles, patterns)
277
+ - Remove entries that reference source project paths, architecture, or tools not used here
278
+ - Adapt entries with transferable knowledge but old-project-specific details
305
279
 
306
- ### Phase 5: Create New Teammates
280
+ ### Step 3: Evaluate Gaps and Create New Teammates
307
281
 
308
- For each new teammate proposed in Phase 2 (after user approval):
282
+ After adapting all existing teammates, check if THIS project has major subsystems that no teammate covers. If so, create new teammates:
309
283
  - Create \`${teammatesDir}/<name>/\` with SOUL.md, WISDOM.md, and \`memory/\`
310
284
  - Use the template at \`${teammatesDir}/TEMPLATE.md\` for structure
311
285
  - WISDOM.md starts with one creation entry
312
286
 
313
- ### Phase 6: Update Framework Files
287
+ If a teammate's domain doesn't exist at all in this project and their skills aren't transferable, delete their directory under \`${teammatesDir}\`.
288
+
289
+ ### Step 4: Update Framework Files
314
290
 
315
291
  - Update \`${teammatesDir}/README.md\` with the final roster
316
292
  - Update \`${teammatesDir}/CROSS-TEAM.md\` ownership table
317
293
 
318
- ### Phase 7: Verify
294
+ ### Step 5: Verify
319
295
 
320
- - Every kept/new teammate has SOUL.md and WISDOM.md
321
- - Ownership globs cover the codebase without major gaps
322
- - Boundaries reference the correct owning teammate
296
+ - Every teammate has SOUL.md and WISDOM.md adapted to THIS project
297
+ - Ownership globs reference actual files in THIS project
298
+ - Boundaries reference correct teammate names
323
299
  - Previous Projects sections are present for all imported teammates
324
- - CROSS-TEAM.md has one row per teammate`;
300
+ - CROSS-TEAM.md has one row per teammate
301
+
302
+ ## Critical Reminder
303
+
304
+ The PRIMARY goal is adapting the imported teammates. Every SOUL.md must be rewritten so the teammate understands THIS project's codebase, not the source project's. If you only have time for one thing, adapt the existing teammates — that is more important than creating new ones.`;
325
305
  }
326
306
  /**
327
307
  * Load ONBOARDING.md from the project dir, package root, or built-in fallback.
@@ -368,7 +348,9 @@ function wrapPrompt(onboardingContent, projectDir) {
368
348
 
369
349
  You do NOT need to create the framework files listed above — they're already there.
370
350
 
371
- Follow the onboarding instructions below. Work through each step, pausing after Step 1 and Step 2 to present your analysis and proposed roster to the user for approval before proceeding.
351
+ > **IMPORTANT:** The \`example/\` directory is a **template reference**, NOT a teammate. Do not modify it or treat it as a teammate. Never name a new teammate "example".
352
+
353
+ Follow the onboarding instructions below. This is a non-interactive session — complete ALL work without pausing. Do not ask for confirmation or wait for user input. Work through each step and make all changes directly.
372
354
 
373
355
  ---
374
356
 
@@ -392,26 +374,22 @@ Identify:
392
374
  3. **Key technologies** — languages, frameworks, tools per area
393
375
  4. **File patterns** — glob patterns for each domain
394
376
 
395
- **Present your analysis to the user and get confirmation before proceeding.**
396
-
397
377
  ## Step 2: Design the Team
398
378
 
399
- Propose a roster of teammates:
379
+ Based on your analysis, design a roster of teammates:
400
380
  - **Aim for 3–7 teammates.** Fewer for small projects, more for monorepos.
401
381
  - **Each teammate owns a distinct domain** with minimal overlap.
402
382
  - **Pick short, memorable names** — one word, evocative of the domain.
403
383
 
404
- For each proposed teammate, define:
384
+ For each teammate, define:
405
385
  - Name and one-line persona
406
386
  - Primary ownership (file patterns)
407
387
  - Key technologies
408
388
  - Boundaries (what they do NOT own)
409
389
 
410
- **Present the proposed roster to the user for approval.**
411
-
412
390
  ## Step 3: Create the Directory Structure
413
391
 
414
- Once approved, create teammate folders under \`.teammates/\`:
392
+ Create teammate folders under \`.teammates/\`:
415
393
 
416
394
  ### Teammate folders
417
395
  For each teammate, create \`.teammates/<name>/\` with:
@@ -9,6 +9,8 @@ export declare class Registry {
9
9
  private teammatesDir;
10
10
  private teammates;
11
11
  constructor(teammatesDir: string);
12
+ /** Names that are never teammates (template references, config files) */
13
+ private static NON_TEAMMATE_NAMES;
12
14
  /** Discover and load all teammates from .teammates/ */
13
15
  loadAll(): Promise<Map<string, TeammateConfig>>;
14
16
  /** Load a single teammate by name */
package/dist/registry.js CHANGED
@@ -12,10 +12,15 @@ export class Registry {
12
12
  constructor(teammatesDir) {
13
13
  this.teammatesDir = teammatesDir;
14
14
  }
15
+ /** Names that are never teammates (template references, config files) */
16
+ static NON_TEAMMATE_NAMES = new Set(["example"]);
15
17
  /** Discover and load all teammates from .teammates/ */
16
18
  async loadAll() {
17
19
  const entries = await readdir(this.teammatesDir, { withFileTypes: true });
18
- const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
20
+ const dirs = entries.filter((e) => e.isDirectory() &&
21
+ !e.name.startsWith(".") &&
22
+ !e.name.startsWith("_") &&
23
+ !Registry.NON_TEAMMATE_NAMES.has(e.name));
19
24
  for (const dir of dirs) {
20
25
  const config = await this.loadTeammate(dir.name);
21
26
  if (config) {
@@ -56,6 +56,13 @@ describe("Registry.loadAll", () => {
56
56
  await registry.loadAll();
57
57
  expect(registry.list()).toEqual(["beacon"]);
58
58
  });
59
+ it("skips the example directory even if it has SOUL.md", async () => {
60
+ await createTeammate("beacon", "# Beacon\n\nPlatform engineer.");
61
+ await createTeammate("example", "# Example\n\nTemplate reference.");
62
+ const registry = new Registry(tempDir);
63
+ await registry.loadAll();
64
+ expect(registry.list()).toEqual(["beacon"]);
65
+ });
59
66
  });
60
67
  describe("Registry.loadTeammate", () => {
61
68
  it("loads soul content", async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammates/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Agent-agnostic CLI for teammates. Routes tasks, manages handoffs, and plugs into any coding agent backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",