@teammates/cli 0.4.1 → 0.5.1
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.
- package/README.md +36 -4
- package/dist/adapter.d.ts +19 -3
- package/dist/adapter.js +168 -96
- package/dist/adapter.test.js +29 -16
- package/dist/adapters/cli-proxy.d.ts +3 -1
- package/dist/adapters/cli-proxy.js +65 -6
- package/dist/adapters/copilot.d.ts +3 -1
- package/dist/adapters/copilot.js +16 -3
- package/dist/adapters/echo.d.ts +3 -1
- package/dist/adapters/echo.js +4 -2
- package/dist/banner.js +5 -1
- package/dist/cli-args.js +23 -23
- package/dist/cli-args.test.d.ts +1 -0
- package/dist/cli-args.test.js +125 -0
- package/dist/cli.js +486 -220
- package/dist/compact.d.ts +23 -0
- package/dist/compact.js +181 -11
- package/dist/compact.test.js +323 -7
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/onboard.js +165 -165
- package/dist/orchestrator.js +7 -2
- package/dist/personas.d.ts +42 -0
- package/dist/personas.js +108 -0
- package/dist/personas.test.d.ts +1 -0
- package/dist/personas.test.js +88 -0
- package/dist/registry.test.js +23 -23
- package/dist/theme.test.d.ts +1 -0
- package/dist/theme.test.js +113 -0
- package/dist/types.d.ts +2 -0
- package/package.json +4 -3
- package/personas/architect.md +95 -0
- package/personas/backend.md +97 -0
- package/personas/data-engineer.md +96 -0
- package/personas/designer.md +96 -0
- package/personas/devops.md +97 -0
- package/personas/frontend.md +98 -0
- package/personas/ml-ai.md +100 -0
- package/personas/mobile.md +97 -0
- package/personas/performance.md +96 -0
- package/personas/pm.md +93 -0
- package/personas/prompt-engineer.md +122 -0
- package/personas/qa.md +96 -0
- package/personas/security.md +96 -0
- package/personas/sre.md +97 -0
- package/personas/swe.md +92 -0
- package/personas/tech-writer.md +97 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { AgentAdapter, InstalledService, RecallContext, RosterEntry, } from "./adapter.js";
|
|
2
|
-
export { buildTeammatePrompt, formatHandoffContext, queryRecallContext, syncRecallIndex, } from "./adapter.js";
|
|
2
|
+
export { buildTeammatePrompt, DAILY_LOG_BUDGET_TOKENS, formatHandoffContext, queryRecallContext, syncRecallIndex, } from "./adapter.js";
|
|
3
|
+
export { autoCompactForBudget } from "./compact.js";
|
|
3
4
|
export { type AgentPreset, CliProxyAdapter, type CliProxyOptions, PRESETS, } from "./adapters/cli-proxy.js";
|
|
4
5
|
export { EchoAdapter } from "./adapters/echo.js";
|
|
5
6
|
export type { BannerInfo, ServiceInfo, ServiceStatus } from "./banner.js";
|
|
@@ -7,6 +8,8 @@ export { AnimatedBanner } from "./banner.js";
|
|
|
7
8
|
export type { CliArgs } from "./cli-args.js";
|
|
8
9
|
export { findTeammatesDir, PKG_VERSION, parseCliArgs } from "./cli-args.js";
|
|
9
10
|
export { Orchestrator, type OrchestratorConfig, type TeammateStatus, } from "./orchestrator.js";
|
|
11
|
+
export type { Persona } from "./personas.js";
|
|
12
|
+
export { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
10
13
|
export { Registry } from "./registry.js";
|
|
11
14
|
export { tp } from "./theme.js";
|
|
12
15
|
export type { DailyLog, HandoffEnvelope, OrchestratorEvent, OwnershipRules, PresenceState, QueueEntry, SandboxLevel, SlashCommand, TaskAssignment, TaskResult, TeammateConfig, TeammateType, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Public API for @teammates/cli
|
|
2
|
-
export { buildTeammatePrompt, formatHandoffContext, queryRecallContext, syncRecallIndex, } from "./adapter.js";
|
|
2
|
+
export { buildTeammatePrompt, DAILY_LOG_BUDGET_TOKENS, formatHandoffContext, queryRecallContext, syncRecallIndex, } from "./adapter.js";
|
|
3
|
+
export { autoCompactForBudget } from "./compact.js";
|
|
3
4
|
export { CliProxyAdapter, PRESETS, } from "./adapters/cli-proxy.js";
|
|
4
5
|
export { EchoAdapter } from "./adapters/echo.js";
|
|
5
6
|
export { AnimatedBanner } from "./banner.js";
|
|
6
7
|
export { findTeammatesDir, PKG_VERSION, parseCliArgs } from "./cli-args.js";
|
|
7
8
|
export { Orchestrator, } from "./orchestrator.js";
|
|
9
|
+
export { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
8
10
|
export { Registry } from "./registry.js";
|
|
9
11
|
export { tp } from "./theme.js";
|
package/dist/onboard.js
CHANGED
|
@@ -225,82 +225,82 @@ export async function buildImportAdaptationPrompt(teammatesDir, teammateNames, s
|
|
|
225
225
|
teammateSections.push(`### @${name}\n${soulBlock}${wisdomBlock}`);
|
|
226
226
|
}
|
|
227
227
|
const projectDir = dirname(teammatesDir);
|
|
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.
|
|
229
|
-
|
|
230
|
-
**Source project:** \`${sourceProjectPath}\`
|
|
231
|
-
**Target project:** \`${projectDir}\`
|
|
232
|
-
**Target .teammates/ directory:** \`${teammatesDir}\`
|
|
233
|
-
**Imported teammates:** ${teammateNames.map((n) => `@${n}`).join(", ")}
|
|
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
|
-
## Imported Teammates (from source project)
|
|
238
|
-
|
|
239
|
-
${teammateSections.join("\n\n---\n\n")}
|
|
240
|
-
|
|
241
|
-
## Instructions
|
|
242
|
-
|
|
243
|
-
Complete these steps in order. Do NOT pause, ask questions, or wait for approval. Make all changes directly.
|
|
244
|
-
|
|
245
|
-
### Step 1: Scan This Project
|
|
246
|
-
|
|
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
|
|
251
|
-
|
|
252
|
-
### Step 2: Adapt EVERY Imported Teammate
|
|
253
|
-
|
|
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.
|
|
255
|
-
|
|
256
|
-
For each teammate's **SOUL.md**:
|
|
257
|
-
|
|
258
|
-
1. **Add a "Previous Projects" section** (place it after Ethics). Compress what the teammate did in the source project:
|
|
259
|
-
\`\`\`markdown
|
|
260
|
-
## Previous Projects
|
|
261
|
-
|
|
262
|
-
### <source-project-name>
|
|
263
|
-
- **Role**: <one-line summary of what they did>
|
|
264
|
-
- **Stack**: <key technologies they worked with>
|
|
265
|
-
- **Domains**: <what they owned — file patterns or subsystem names>
|
|
266
|
-
- **Key learnings**: <1-3 bullets of notable patterns, decisions, or lessons>
|
|
267
|
-
\`\`\`
|
|
268
|
-
|
|
269
|
-
2. **Rewrite project-specific sections** for THIS project:
|
|
270
|
-
- **Preserve**: Identity (name, personality), Core Principles, Ethics
|
|
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
|
|
273
|
-
|
|
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
|
|
279
|
-
|
|
280
|
-
### Step 3: Evaluate Gaps and Create New Teammates
|
|
281
|
-
|
|
282
|
-
After adapting all existing teammates, check if THIS project has major subsystems that no teammate covers. If so, create new teammates:
|
|
283
|
-
- Create \`${teammatesDir}/<name>/\` with SOUL.md, WISDOM.md, and \`memory/\`
|
|
284
|
-
- Use the template at \`${teammatesDir}/TEMPLATE.md\` for structure
|
|
285
|
-
- WISDOM.md starts with one creation entry
|
|
286
|
-
|
|
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
|
|
290
|
-
|
|
291
|
-
- Update \`${teammatesDir}/README.md\` with the final roster
|
|
292
|
-
- Update \`${teammatesDir}/CROSS-TEAM.md\` ownership table
|
|
293
|
-
|
|
294
|
-
### Step 5: Verify
|
|
295
|
-
|
|
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
|
|
299
|
-
- Previous Projects sections are present for all imported teammates
|
|
300
|
-
- CROSS-TEAM.md has one row per teammate
|
|
301
|
-
|
|
302
|
-
## Critical Reminder
|
|
303
|
-
|
|
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.
|
|
229
|
+
|
|
230
|
+
**Source project:** \`${sourceProjectPath}\`
|
|
231
|
+
**Target project:** \`${projectDir}\`
|
|
232
|
+
**Target .teammates/ directory:** \`${teammatesDir}\`
|
|
233
|
+
**Imported teammates:** ${teammateNames.map((n) => `@${n}`).join(", ")}
|
|
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
|
+
## Imported Teammates (from source project)
|
|
238
|
+
|
|
239
|
+
${teammateSections.join("\n\n---\n\n")}
|
|
240
|
+
|
|
241
|
+
## Instructions
|
|
242
|
+
|
|
243
|
+
Complete these steps in order. Do NOT pause, ask questions, or wait for approval. Make all changes directly.
|
|
244
|
+
|
|
245
|
+
### Step 1: Scan This Project
|
|
246
|
+
|
|
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
|
|
251
|
+
|
|
252
|
+
### Step 2: Adapt EVERY Imported Teammate
|
|
253
|
+
|
|
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.
|
|
255
|
+
|
|
256
|
+
For each teammate's **SOUL.md**:
|
|
257
|
+
|
|
258
|
+
1. **Add a "Previous Projects" section** (place it after Ethics). Compress what the teammate did in the source project:
|
|
259
|
+
\`\`\`markdown
|
|
260
|
+
## Previous Projects
|
|
261
|
+
|
|
262
|
+
### <source-project-name>
|
|
263
|
+
- **Role**: <one-line summary of what they did>
|
|
264
|
+
- **Stack**: <key technologies they worked with>
|
|
265
|
+
- **Domains**: <what they owned — file patterns or subsystem names>
|
|
266
|
+
- **Key learnings**: <1-3 bullets of notable patterns, decisions, or lessons>
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
2. **Rewrite project-specific sections** for THIS project:
|
|
270
|
+
- **Preserve**: Identity (name, personality), Core Principles, Ethics
|
|
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
|
|
273
|
+
|
|
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
|
|
279
|
+
|
|
280
|
+
### Step 3: Evaluate Gaps and Create New Teammates
|
|
281
|
+
|
|
282
|
+
After adapting all existing teammates, check if THIS project has major subsystems that no teammate covers. If so, create new teammates:
|
|
283
|
+
- Create \`${teammatesDir}/<name>/\` with SOUL.md, WISDOM.md, and \`memory/\`
|
|
284
|
+
- Use the template at \`${teammatesDir}/TEMPLATE.md\` for structure
|
|
285
|
+
- WISDOM.md starts with one creation entry
|
|
286
|
+
|
|
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
|
|
290
|
+
|
|
291
|
+
- Update \`${teammatesDir}/README.md\` with the final roster
|
|
292
|
+
- Update \`${teammatesDir}/CROSS-TEAM.md\` ownership table
|
|
293
|
+
|
|
294
|
+
### Step 5: Verify
|
|
295
|
+
|
|
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
|
|
299
|
+
- Previous Projects sections are present for all imported teammates
|
|
300
|
+
- CROSS-TEAM.md has one row per teammate
|
|
301
|
+
|
|
302
|
+
## Critical Reminder
|
|
303
|
+
|
|
304
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.`;
|
|
305
305
|
}
|
|
306
306
|
/**
|
|
@@ -326,95 +326,95 @@ export async function getOnboardingPrompt(projectDir) {
|
|
|
326
326
|
return wrapPrompt(BUILTIN_ONBOARDING, projectDir);
|
|
327
327
|
}
|
|
328
328
|
function wrapPrompt(onboardingContent, projectDir) {
|
|
329
|
-
return `You are setting up the teammates framework for a project.
|
|
330
|
-
|
|
331
|
-
**Target project directory:** ${projectDir}
|
|
332
|
-
|
|
333
|
-
**Framework files have already been copied** into \`${projectDir}/.teammates/\` from the template. The following files are already in place:
|
|
334
|
-
- CROSS-TEAM.md — fill in the Ownership Scopes table as you create teammates
|
|
335
|
-
- PROTOCOL.md — team protocol (ready to use)
|
|
336
|
-
- TEMPLATE.md — reference for creating teammate SOUL.md and WISDOM.md files
|
|
337
|
-
- USER.md — user profile (gitignored, user fills in later)
|
|
338
|
-
- README.md — update with project-specific roster and info
|
|
339
|
-
- .gitignore — configured for USER.md and .index/
|
|
340
|
-
- example/ — example SOUL.md and WISDOM.md for reference
|
|
341
|
-
|
|
342
|
-
**Your job is to:**
|
|
343
|
-
1. Analyze the codebase (Step 1)
|
|
344
|
-
2. Design the team roster (Step 2)
|
|
345
|
-
3. Create teammate folders with SOUL.md and WISDOM.md (Step 3) — use TEMPLATE.md for the structure
|
|
346
|
-
4. Update README.md and CROSS-TEAM.md with the roster info (Step 3)
|
|
347
|
-
5. Verify everything is in place (Step 4)
|
|
348
|
-
|
|
349
|
-
You do NOT need to create the framework files listed above — they're already there.
|
|
350
|
-
|
|
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.
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
329
|
+
return `You are setting up the teammates framework for a project.
|
|
330
|
+
|
|
331
|
+
**Target project directory:** ${projectDir}
|
|
332
|
+
|
|
333
|
+
**Framework files have already been copied** into \`${projectDir}/.teammates/\` from the template. The following files are already in place:
|
|
334
|
+
- CROSS-TEAM.md — fill in the Ownership Scopes table as you create teammates
|
|
335
|
+
- PROTOCOL.md — team protocol (ready to use)
|
|
336
|
+
- TEMPLATE.md — reference for creating teammate SOUL.md and WISDOM.md files
|
|
337
|
+
- USER.md — user profile (gitignored, user fills in later)
|
|
338
|
+
- README.md — update with project-specific roster and info
|
|
339
|
+
- .gitignore — configured for USER.md and .index/
|
|
340
|
+
- example/ — example SOUL.md and WISDOM.md for reference
|
|
341
|
+
|
|
342
|
+
**Your job is to:**
|
|
343
|
+
1. Analyze the codebase (Step 1)
|
|
344
|
+
2. Design the team roster (Step 2)
|
|
345
|
+
3. Create teammate folders with SOUL.md and WISDOM.md (Step 3) — use TEMPLATE.md for the structure
|
|
346
|
+
4. Update README.md and CROSS-TEAM.md with the roster info (Step 3)
|
|
347
|
+
5. Verify everything is in place (Step 4)
|
|
348
|
+
|
|
349
|
+
You do NOT need to create the framework files listed above — they're already there.
|
|
350
|
+
|
|
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.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
357
|
${onboardingContent}`;
|
|
358
358
|
}
|
|
359
|
-
const BUILTIN_ONBOARDING = `# Teammates Onboarding
|
|
360
|
-
|
|
361
|
-
You are going to analyze a codebase and create a set of AI teammates — persistent personas that each own a slice of the project. Follow these steps in order.
|
|
362
|
-
|
|
363
|
-
## Step 1: Analyze the Codebase
|
|
364
|
-
|
|
365
|
-
Read the project's entry points to understand its structure:
|
|
366
|
-
- README, CONTRIBUTING, or similar docs
|
|
367
|
-
- Package manifest (package.json, Cargo.toml, pyproject.toml, go.mod, etc.)
|
|
368
|
-
- Top-level directory structure
|
|
369
|
-
- Key configuration files
|
|
370
|
-
|
|
371
|
-
Identify:
|
|
372
|
-
1. **Major domains/subsystems** — distinct areas of the codebase
|
|
373
|
-
2. **Dependency flow** — which layers depend on which
|
|
374
|
-
3. **Key technologies** — languages, frameworks, tools per area
|
|
375
|
-
4. **File patterns** — glob patterns for each domain
|
|
376
|
-
|
|
377
|
-
## Step 2: Design the Team
|
|
378
|
-
|
|
379
|
-
Based on your analysis, design a roster of teammates:
|
|
380
|
-
- **Aim for 3–7 teammates.** Fewer for small projects, more for monorepos.
|
|
381
|
-
- **Each teammate owns a distinct domain** with minimal overlap.
|
|
382
|
-
- **Pick short, memorable names** — one word, evocative of the domain.
|
|
383
|
-
|
|
384
|
-
For each teammate, define:
|
|
385
|
-
- Name and one-line persona
|
|
386
|
-
- Primary ownership (file patterns)
|
|
387
|
-
- Key technologies
|
|
388
|
-
- Boundaries (what they do NOT own)
|
|
389
|
-
|
|
390
|
-
## Step 3: Create the Directory Structure
|
|
391
|
-
|
|
392
|
-
Create teammate folders under \`.teammates/\`:
|
|
393
|
-
|
|
394
|
-
### Teammate folders
|
|
395
|
-
For each teammate, create \`.teammates/<name>/\` with:
|
|
396
|
-
|
|
397
|
-
**SOUL.md** — Use the template from \`.teammates/TEMPLATE.md\`. Fill in identity, core principles, boundaries, capabilities, ownership, ethics.
|
|
398
|
-
|
|
399
|
-
**WISDOM.md** — Start with one entry recording creation and key decisions.
|
|
400
|
-
|
|
401
|
-
**memory/** — Empty directory for daily logs.
|
|
402
|
-
|
|
403
|
-
### Update framework files
|
|
404
|
-
- Update \`.teammates/README.md\` with the roster table, dependency flow, and routing guide
|
|
405
|
-
- Update \`.teammates/CROSS-TEAM.md\` Ownership Scopes table with one row per teammate
|
|
406
|
-
|
|
407
|
-
## Step 4: Verify
|
|
408
|
-
|
|
409
|
-
Check:
|
|
410
|
-
- Every roster teammate has a folder with SOUL.md and WISDOM.md
|
|
411
|
-
- Ownership globs cover the codebase without major gaps
|
|
412
|
-
- Boundaries reference the correct owning teammate
|
|
413
|
-
- CROSS-TEAM.md Ownership Scopes table has one row per teammate with correct paths
|
|
414
|
-
- .gitignore is in place (USER.md not committed)
|
|
415
|
-
|
|
416
|
-
## Tips
|
|
417
|
-
- Small projects are fine with 2–3 teammates
|
|
418
|
-
- WISDOM.md starts light — just one creation entry
|
|
419
|
-
- Prompt the user to fill in USER.md after setup
|
|
359
|
+
const BUILTIN_ONBOARDING = `# Teammates Onboarding
|
|
360
|
+
|
|
361
|
+
You are going to analyze a codebase and create a set of AI teammates — persistent personas that each own a slice of the project. Follow these steps in order.
|
|
362
|
+
|
|
363
|
+
## Step 1: Analyze the Codebase
|
|
364
|
+
|
|
365
|
+
Read the project's entry points to understand its structure:
|
|
366
|
+
- README, CONTRIBUTING, or similar docs
|
|
367
|
+
- Package manifest (package.json, Cargo.toml, pyproject.toml, go.mod, etc.)
|
|
368
|
+
- Top-level directory structure
|
|
369
|
+
- Key configuration files
|
|
370
|
+
|
|
371
|
+
Identify:
|
|
372
|
+
1. **Major domains/subsystems** — distinct areas of the codebase
|
|
373
|
+
2. **Dependency flow** — which layers depend on which
|
|
374
|
+
3. **Key technologies** — languages, frameworks, tools per area
|
|
375
|
+
4. **File patterns** — glob patterns for each domain
|
|
376
|
+
|
|
377
|
+
## Step 2: Design the Team
|
|
378
|
+
|
|
379
|
+
Based on your analysis, design a roster of teammates:
|
|
380
|
+
- **Aim for 3–7 teammates.** Fewer for small projects, more for monorepos.
|
|
381
|
+
- **Each teammate owns a distinct domain** with minimal overlap.
|
|
382
|
+
- **Pick short, memorable names** — one word, evocative of the domain.
|
|
383
|
+
|
|
384
|
+
For each teammate, define:
|
|
385
|
+
- Name and one-line persona
|
|
386
|
+
- Primary ownership (file patterns)
|
|
387
|
+
- Key technologies
|
|
388
|
+
- Boundaries (what they do NOT own)
|
|
389
|
+
|
|
390
|
+
## Step 3: Create the Directory Structure
|
|
391
|
+
|
|
392
|
+
Create teammate folders under \`.teammates/\`:
|
|
393
|
+
|
|
394
|
+
### Teammate folders
|
|
395
|
+
For each teammate, create \`.teammates/<name>/\` with:
|
|
396
|
+
|
|
397
|
+
**SOUL.md** — Use the template from \`.teammates/TEMPLATE.md\`. Fill in identity, core principles, boundaries, capabilities, ownership, ethics.
|
|
398
|
+
|
|
399
|
+
**WISDOM.md** — Start with one entry recording creation and key decisions.
|
|
400
|
+
|
|
401
|
+
**memory/** — Empty directory for daily logs.
|
|
402
|
+
|
|
403
|
+
### Update framework files
|
|
404
|
+
- Update \`.teammates/README.md\` with the roster table, dependency flow, and routing guide
|
|
405
|
+
- Update \`.teammates/CROSS-TEAM.md\` Ownership Scopes table with one row per teammate
|
|
406
|
+
|
|
407
|
+
## Step 4: Verify
|
|
408
|
+
|
|
409
|
+
Check:
|
|
410
|
+
- Every roster teammate has a folder with SOUL.md and WISDOM.md
|
|
411
|
+
- Ownership globs cover the codebase without major gaps
|
|
412
|
+
- Boundaries reference the correct owning teammate
|
|
413
|
+
- CROSS-TEAM.md Ownership Scopes table has one row per teammate with correct paths
|
|
414
|
+
- .gitignore is in place (USER.md not committed)
|
|
415
|
+
|
|
416
|
+
## Tips
|
|
417
|
+
- Small projects are fine with 2–3 teammates
|
|
418
|
+
- WISDOM.md starts light — just one creation entry
|
|
419
|
+
- Prompt the user to fill in USER.md after setup
|
|
420
420
|
`;
|
package/dist/orchestrator.js
CHANGED
|
@@ -63,7 +63,10 @@ export class Orchestrator {
|
|
|
63
63
|
}
|
|
64
64
|
this.onEvent({ type: "task_assigned", assignment });
|
|
65
65
|
const prevPresence = this.statuses.get(assignment.teammate)?.presence ?? "online";
|
|
66
|
-
this.statuses.set(assignment.teammate, {
|
|
66
|
+
this.statuses.set(assignment.teammate, {
|
|
67
|
+
state: "working",
|
|
68
|
+
presence: prevPresence,
|
|
69
|
+
});
|
|
67
70
|
// Get or create session
|
|
68
71
|
let sessionId = this.sessions.get(assignment.teammate);
|
|
69
72
|
if (!sessionId) {
|
|
@@ -76,7 +79,9 @@ export class Orchestrator {
|
|
|
76
79
|
prompt = `${assignment.extraContext}\n\n---\n\n${prompt}`;
|
|
77
80
|
}
|
|
78
81
|
// Execute
|
|
79
|
-
const result = await this.adapter.executeTask(sessionId, teammate, prompt
|
|
82
|
+
const result = await this.adapter.executeTask(sessionId, teammate, prompt, {
|
|
83
|
+
raw: assignment.raw,
|
|
84
|
+
});
|
|
80
85
|
this.onEvent({ type: "task_completed", result });
|
|
81
86
|
// Update status (preserve presence)
|
|
82
87
|
const postPresence = this.statuses.get(assignment.teammate)?.presence ?? "online";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona loader — reads bundled persona templates from the personas/ directory.
|
|
3
|
+
*
|
|
4
|
+
* Each persona file is a markdown file with YAML frontmatter:
|
|
5
|
+
* ---
|
|
6
|
+
* persona: Software Engineer
|
|
7
|
+
* alias: beacon
|
|
8
|
+
* tier: 1
|
|
9
|
+
* description: Architecture, implementation, and code quality
|
|
10
|
+
* ---
|
|
11
|
+
* # <Name> — Software Engineer
|
|
12
|
+
* ...body (SOUL.md scaffold)...
|
|
13
|
+
*
|
|
14
|
+
* The `<Name>` placeholder in the body is replaced with the user's chosen
|
|
15
|
+
* teammate name during scaffolding.
|
|
16
|
+
*/
|
|
17
|
+
export interface Persona {
|
|
18
|
+
/** Display name, e.g. "Software Engineer" */
|
|
19
|
+
persona: string;
|
|
20
|
+
/** Suggested alias, e.g. "beacon" */
|
|
21
|
+
alias: string;
|
|
22
|
+
/** Tier for ordering: 1 = core, 2 = specialized */
|
|
23
|
+
tier: number;
|
|
24
|
+
/** One-line description shown in selection UI */
|
|
25
|
+
description: string;
|
|
26
|
+
/** Raw SOUL.md body (everything after the closing ---) */
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load all personas from the bundled personas/ directory.
|
|
31
|
+
* Returns sorted by tier (ascending), then alphabetically.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadPersonas(): Promise<Persona[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Scaffold a teammate folder from a persona template.
|
|
36
|
+
*
|
|
37
|
+
* @param teammatesDir - The .teammates/ directory
|
|
38
|
+
* @param name - The teammate name (used as folder name and replaces <Name>)
|
|
39
|
+
* @param persona - The persona to scaffold from
|
|
40
|
+
* @returns The path to the created teammate folder
|
|
41
|
+
*/
|
|
42
|
+
export declare function scaffoldFromPersona(teammatesDir: string, name: string, persona: Persona): Promise<string>;
|
package/dist/personas.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona loader — reads bundled persona templates from the personas/ directory.
|
|
3
|
+
*
|
|
4
|
+
* Each persona file is a markdown file with YAML frontmatter:
|
|
5
|
+
* ---
|
|
6
|
+
* persona: Software Engineer
|
|
7
|
+
* alias: beacon
|
|
8
|
+
* tier: 1
|
|
9
|
+
* description: Architecture, implementation, and code quality
|
|
10
|
+
* ---
|
|
11
|
+
* # <Name> — Software Engineer
|
|
12
|
+
* ...body (SOUL.md scaffold)...
|
|
13
|
+
*
|
|
14
|
+
* The `<Name>` placeholder in the body is replaced with the user's chosen
|
|
15
|
+
* teammate name during scaffolding.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
18
|
+
import { dirname, join, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the bundled personas/ directory.
|
|
23
|
+
* Works from both dist/ (compiled) and src/ (dev).
|
|
24
|
+
*/
|
|
25
|
+
function getPersonasDir() {
|
|
26
|
+
const candidates = [
|
|
27
|
+
resolve(__dirname, "../personas"), // dist/ → cli/personas
|
|
28
|
+
resolve(__dirname, "../../personas"), // src/ → cli/personas (dev)
|
|
29
|
+
];
|
|
30
|
+
return candidates[0]; // both resolve to cli/personas
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse a persona file's frontmatter and body.
|
|
34
|
+
*/
|
|
35
|
+
function parsePersonaFile(content) {
|
|
36
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
37
|
+
if (!match)
|
|
38
|
+
return null;
|
|
39
|
+
const frontmatter = match[1];
|
|
40
|
+
const body = match[2].trim();
|
|
41
|
+
const persona = extractField(frontmatter, "persona");
|
|
42
|
+
const alias = extractField(frontmatter, "alias");
|
|
43
|
+
const tierStr = extractField(frontmatter, "tier");
|
|
44
|
+
const description = extractField(frontmatter, "description");
|
|
45
|
+
if (!persona || !alias || !description)
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
persona,
|
|
49
|
+
alias,
|
|
50
|
+
tier: tierStr ? parseInt(tierStr, 10) : 2,
|
|
51
|
+
description,
|
|
52
|
+
body,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function extractField(frontmatter, field) {
|
|
56
|
+
const re = new RegExp(`^${field}:\\s*(.+)$`, "m");
|
|
57
|
+
const m = frontmatter.match(re);
|
|
58
|
+
return m?.[1]?.trim();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load all personas from the bundled personas/ directory.
|
|
62
|
+
* Returns sorted by tier (ascending), then alphabetically.
|
|
63
|
+
*/
|
|
64
|
+
export async function loadPersonas() {
|
|
65
|
+
const dir = getPersonasDir();
|
|
66
|
+
const personas = [];
|
|
67
|
+
try {
|
|
68
|
+
const files = await readdir(dir);
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if (!file.endsWith(".md"))
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const content = await readFile(join(dir, file), "utf-8");
|
|
74
|
+
const persona = parsePersonaFile(content);
|
|
75
|
+
if (persona)
|
|
76
|
+
personas.push(persona);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* skip unreadable files */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* personas dir missing — return empty */
|
|
85
|
+
}
|
|
86
|
+
personas.sort((a, b) => a.tier - b.tier || a.persona.localeCompare(b.persona));
|
|
87
|
+
return personas;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Scaffold a teammate folder from a persona template.
|
|
91
|
+
*
|
|
92
|
+
* @param teammatesDir - The .teammates/ directory
|
|
93
|
+
* @param name - The teammate name (used as folder name and replaces <Name>)
|
|
94
|
+
* @param persona - The persona to scaffold from
|
|
95
|
+
* @returns The path to the created teammate folder
|
|
96
|
+
*/
|
|
97
|
+
export async function scaffoldFromPersona(teammatesDir, name, persona) {
|
|
98
|
+
const folderName = name.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
99
|
+
const teamDir = join(teammatesDir, folderName);
|
|
100
|
+
await mkdir(teamDir, { recursive: true });
|
|
101
|
+
await mkdir(join(teamDir, "memory"), { recursive: true });
|
|
102
|
+
// Replace <Name> placeholder with the chosen name (capitalize first letter)
|
|
103
|
+
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
104
|
+
const soulContent = persona.body.replace(/<Name>/g, displayName);
|
|
105
|
+
await writeFile(join(teamDir, "SOUL.md"), soulContent, "utf-8");
|
|
106
|
+
await writeFile(join(teamDir, "WISDOM.md"), `# ${displayName} — Wisdom\n\nDistilled principles. Read this first every session (after SOUL.md).\n\nLast compacted: never\n\n---\n\n*No entries yet — wisdom is distilled from experience.*\n`, "utf-8");
|
|
107
|
+
return teamDir;
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { loadPersonas, scaffoldFromPersona } from "./personas.js";
|
|
6
|
+
// ── scaffoldFromPersona ─────────────────────────────────────────────
|
|
7
|
+
let testDir;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
testDir = join(tmpdir(), `personas-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
10
|
+
await mkdir(testDir, { recursive: true });
|
|
11
|
+
});
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await rm(testDir, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
const samplePersona = {
|
|
16
|
+
persona: "Software Engineer",
|
|
17
|
+
alias: "beacon",
|
|
18
|
+
tier: 1,
|
|
19
|
+
description: "Architecture and implementation",
|
|
20
|
+
body: "# <Name> — Software Engineer\n\n## Identity\n<Name> is the team's SWE.",
|
|
21
|
+
};
|
|
22
|
+
describe("scaffoldFromPersona", () => {
|
|
23
|
+
it("creates teammate folder with SOUL.md and WISDOM.md", async () => {
|
|
24
|
+
const teamDir = await scaffoldFromPersona(testDir, "beacon", samplePersona);
|
|
25
|
+
const soul = await readFile(join(teamDir, "SOUL.md"), "utf-8");
|
|
26
|
+
const wisdom = await readFile(join(teamDir, "WISDOM.md"), "utf-8");
|
|
27
|
+
expect(soul).toContain("# Beacon — Software Engineer");
|
|
28
|
+
expect(soul).toContain("Beacon is the team's SWE.");
|
|
29
|
+
expect(wisdom).toContain("# Beacon — Wisdom");
|
|
30
|
+
expect(wisdom).toContain("Last compacted: never");
|
|
31
|
+
});
|
|
32
|
+
it("normalizes folder name to lowercase with safe chars", async () => {
|
|
33
|
+
const teamDir = await scaffoldFromPersona(testDir, "My Cool Bot!", samplePersona);
|
|
34
|
+
// Should strip spaces, uppercase, and special chars
|
|
35
|
+
expect(teamDir).toContain("mycoolbot");
|
|
36
|
+
});
|
|
37
|
+
it("replaces all <Name> placeholders in SOUL.md", async () => {
|
|
38
|
+
const teamDir = await scaffoldFromPersona(testDir, "atlas", samplePersona);
|
|
39
|
+
const soul = await readFile(join(teamDir, "SOUL.md"), "utf-8");
|
|
40
|
+
expect(soul).not.toContain("<Name>");
|
|
41
|
+
expect(soul).toContain("Atlas");
|
|
42
|
+
});
|
|
43
|
+
it("creates memory subdirectory", async () => {
|
|
44
|
+
const teamDir = await scaffoldFromPersona(testDir, "test", samplePersona);
|
|
45
|
+
// Should not throw — directory exists
|
|
46
|
+
const memDir = join(teamDir, "memory");
|
|
47
|
+
await mkdir(memDir, { recursive: true }); // no-op if exists
|
|
48
|
+
});
|
|
49
|
+
it("capitalizes display name in WISDOM.md", async () => {
|
|
50
|
+
const teamDir = await scaffoldFromPersona(testDir, "forge", samplePersona);
|
|
51
|
+
const wisdom = await readFile(join(teamDir, "WISDOM.md"), "utf-8");
|
|
52
|
+
expect(wisdom).toContain("# Forge — Wisdom");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
// ── loadPersonas ────────────────────────────────────────────────────
|
|
56
|
+
describe("loadPersonas", () => {
|
|
57
|
+
it("loads persona files from the bundled directory", async () => {
|
|
58
|
+
const personas = await loadPersonas();
|
|
59
|
+
// Should find at least the built-in personas
|
|
60
|
+
expect(personas.length).toBeGreaterThan(0);
|
|
61
|
+
});
|
|
62
|
+
it("sorts by tier then alphabetically", async () => {
|
|
63
|
+
const personas = await loadPersonas();
|
|
64
|
+
// Verify ordering: all tier 1 before tier 2
|
|
65
|
+
let lastTier = 0;
|
|
66
|
+
let lastName = "";
|
|
67
|
+
for (const p of personas) {
|
|
68
|
+
if (p.tier > lastTier) {
|
|
69
|
+
lastTier = p.tier;
|
|
70
|
+
lastName = "";
|
|
71
|
+
}
|
|
72
|
+
if (p.tier === lastTier && lastName) {
|
|
73
|
+
expect(p.persona.localeCompare(lastName)).toBeGreaterThanOrEqual(0);
|
|
74
|
+
}
|
|
75
|
+
lastName = p.persona;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
it("each persona has required fields", async () => {
|
|
79
|
+
const personas = await loadPersonas();
|
|
80
|
+
for (const p of personas) {
|
|
81
|
+
expect(p.persona).toBeTruthy();
|
|
82
|
+
expect(p.alias).toBeTruthy();
|
|
83
|
+
expect(p.description).toBeTruthy();
|
|
84
|
+
expect(typeof p.tier).toBe("number");
|
|
85
|
+
expect(p.body.length).toBeGreaterThan(0);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|