@teammates/cli 0.5.0 → 0.5.2
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/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +146 -108
- package/dist/adapter.test.js +29 -16
- package/dist/adapters/cli-proxy.js +59 -2
- package/dist/adapters/copilot.js +14 -2
- package/dist/adapters/echo.js +3 -1
- 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 +301 -163
- 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 +2 -1
- package/dist/index.js +2 -1
- package/dist/onboard.js +165 -165
- package/dist/orchestrator.js +7 -1
- 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 +7 -0
- package/package.json +3 -3
- package/personas/architect.md +4 -0
- package/personas/backend.md +4 -0
- package/personas/data-engineer.md +4 -0
- package/personas/designer.md +4 -0
- package/personas/devops.md +4 -0
- package/personas/frontend.md +4 -0
- package/personas/ml-ai.md +4 -0
- package/personas/mobile.md +4 -0
- package/personas/performance.md +4 -0
- package/personas/pm.md +4 -0
- package/personas/prompt-engineer.md +122 -0
- package/personas/qa.md +4 -0
- package/personas/security.md +4 -0
- package/personas/sre.md +4 -0
- package/personas/swe.md +4 -0
- package/personas/tech-writer.md +4 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
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) {
|
|
@@ -79,6 +82,9 @@ export class Orchestrator {
|
|
|
79
82
|
const result = await this.adapter.executeTask(sessionId, teammate, prompt, {
|
|
80
83
|
raw: assignment.raw,
|
|
81
84
|
});
|
|
85
|
+
// Propagate system flag so event handlers can distinguish system vs user tasks
|
|
86
|
+
if (assignment.system)
|
|
87
|
+
result.system = true;
|
|
82
88
|
this.onEvent({ type: "task_completed", result });
|
|
83
89
|
// Update status (preserve presence)
|
|
84
90
|
const postPresence = this.statuses.get(assignment.teammate)?.presence ?? "online";
|
|
@@ -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
|
+
});
|
package/dist/registry.test.js
CHANGED
|
@@ -142,18 +142,18 @@ describe("Registry role parsing", () => {
|
|
|
142
142
|
});
|
|
143
143
|
describe("Registry ownership parsing", () => {
|
|
144
144
|
it("parses primary ownership patterns", async () => {
|
|
145
|
-
const soul = `# Beacon
|
|
146
|
-
|
|
147
|
-
## Ownership
|
|
148
|
-
|
|
149
|
-
### Primary
|
|
150
|
-
|
|
151
|
-
- \`recall/src/**\` — All source files
|
|
152
|
-
- \`recall/package.json\` — Package manifest
|
|
153
|
-
|
|
154
|
-
### Secondary
|
|
155
|
-
|
|
156
|
-
- \`.teammates/.index/**\` — Vector indexes
|
|
145
|
+
const soul = `# Beacon
|
|
146
|
+
|
|
147
|
+
## Ownership
|
|
148
|
+
|
|
149
|
+
### Primary
|
|
150
|
+
|
|
151
|
+
- \`recall/src/**\` — All source files
|
|
152
|
+
- \`recall/package.json\` — Package manifest
|
|
153
|
+
|
|
154
|
+
### Secondary
|
|
155
|
+
|
|
156
|
+
- \`.teammates/.index/**\` — Vector indexes
|
|
157
157
|
`;
|
|
158
158
|
await createTeammate("beacon", soul);
|
|
159
159
|
const registry = new Registry(tempDir);
|
|
@@ -174,17 +174,17 @@ describe("Registry ownership parsing", () => {
|
|
|
174
174
|
});
|
|
175
175
|
describe("Registry routing keyword parsing", () => {
|
|
176
176
|
it("parses routing keywords from ### Routing section", async () => {
|
|
177
|
-
const soul = `# Beacon
|
|
178
|
-
|
|
179
|
-
## Ownership
|
|
180
|
-
|
|
181
|
-
### Primary
|
|
182
|
-
|
|
183
|
-
- \`recall/src/**\` — All source files
|
|
184
|
-
|
|
185
|
-
### Routing
|
|
186
|
-
|
|
187
|
-
- \`search\`, \`embeddings\`, \`vector\`, \`semantic\`
|
|
177
|
+
const soul = `# Beacon
|
|
178
|
+
|
|
179
|
+
## Ownership
|
|
180
|
+
|
|
181
|
+
### Primary
|
|
182
|
+
|
|
183
|
+
- \`recall/src/**\` — All source files
|
|
184
|
+
|
|
185
|
+
### Routing
|
|
186
|
+
|
|
187
|
+
- \`search\`, \`embeddings\`, \`vector\`, \`semantic\`
|
|
188
188
|
`;
|
|
189
189
|
await createTeammate("beacon", soul);
|
|
190
190
|
const registry = new Registry(tempDir);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { colorToHex, DEFAULT_THEME, setTheme, theme, tp } from "./theme.js";
|
|
3
|
+
describe("theme", () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
// Restore default theme after each test
|
|
6
|
+
setTheme(DEFAULT_THEME);
|
|
7
|
+
});
|
|
8
|
+
it("returns the default theme initially", () => {
|
|
9
|
+
const t = theme();
|
|
10
|
+
expect(t.accent).toEqual(DEFAULT_THEME.accent);
|
|
11
|
+
expect(t.text).toEqual(DEFAULT_THEME.text);
|
|
12
|
+
expect(t.success).toEqual(DEFAULT_THEME.success);
|
|
13
|
+
});
|
|
14
|
+
it("setTheme replaces the active theme", () => {
|
|
15
|
+
const custom = {
|
|
16
|
+
...DEFAULT_THEME,
|
|
17
|
+
accent: { r: 255, g: 0, b: 0, a: 255 },
|
|
18
|
+
};
|
|
19
|
+
setTheme(custom);
|
|
20
|
+
expect(theme().accent).toEqual({ r: 255, g: 0, b: 0, a: 255 });
|
|
21
|
+
});
|
|
22
|
+
it("setTheme creates a copy (not a reference)", () => {
|
|
23
|
+
const custom = { ...DEFAULT_THEME };
|
|
24
|
+
setTheme(custom);
|
|
25
|
+
custom.accent = { r: 0, g: 0, b: 0, a: 255 };
|
|
26
|
+
// Should not be affected by mutation
|
|
27
|
+
expect(theme().accent).toEqual(DEFAULT_THEME.accent);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe("colorToHex", () => {
|
|
31
|
+
it("converts RGB to uppercase hex string", () => {
|
|
32
|
+
expect(colorToHex({ r: 58, g: 150, b: 221, a: 255 })).toBe("#3A96DD");
|
|
33
|
+
});
|
|
34
|
+
it("pads single-digit hex values with zero", () => {
|
|
35
|
+
expect(colorToHex({ r: 0, g: 0, b: 0, a: 255 })).toBe("#000000");
|
|
36
|
+
});
|
|
37
|
+
it("converts white correctly", () => {
|
|
38
|
+
expect(colorToHex({ r: 255, g: 255, b: 255, a: 255 })).toBe("#FFFFFF");
|
|
39
|
+
});
|
|
40
|
+
it("converts mid-range values", () => {
|
|
41
|
+
expect(colorToHex({ r: 128, g: 64, b: 32, a: 255 })).toBe("#804020");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("tp (themed pen shortcuts)", () => {
|
|
45
|
+
// tp functions return StyledSpan (StyledSegment[] with __brand)
|
|
46
|
+
// Each span contains segments with { text, style } entries
|
|
47
|
+
it("accent returns a styled span with correct text", () => {
|
|
48
|
+
const result = tp.accent("hello");
|
|
49
|
+
expect(Array.isArray(result)).toBe(true);
|
|
50
|
+
expect(result).toHaveLength(1);
|
|
51
|
+
expect(result[0].text).toBe("hello");
|
|
52
|
+
});
|
|
53
|
+
it("muted returns a styled span with correct text", () => {
|
|
54
|
+
const result = tp.muted("test");
|
|
55
|
+
expect(Array.isArray(result)).toBe(true);
|
|
56
|
+
expect(result[0].text).toBe("test");
|
|
57
|
+
});
|
|
58
|
+
it("success returns a styled span with correct text", () => {
|
|
59
|
+
const result = tp.success("ok");
|
|
60
|
+
expect(Array.isArray(result)).toBe(true);
|
|
61
|
+
expect(result[0].text).toBe("ok");
|
|
62
|
+
});
|
|
63
|
+
it("error returns a styled span with correct text", () => {
|
|
64
|
+
const result = tp.error("fail");
|
|
65
|
+
expect(Array.isArray(result)).toBe(true);
|
|
66
|
+
expect(result[0].text).toBe("fail");
|
|
67
|
+
});
|
|
68
|
+
it("bold returns a styled span with correct text", () => {
|
|
69
|
+
const result = tp.bold("strong");
|
|
70
|
+
expect(Array.isArray(result)).toBe(true);
|
|
71
|
+
expect(result[0].text).toBe("strong");
|
|
72
|
+
});
|
|
73
|
+
it("accent uses the current theme color", () => {
|
|
74
|
+
const result = tp.accent("x");
|
|
75
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.accent);
|
|
76
|
+
});
|
|
77
|
+
it("picks up theme changes", () => {
|
|
78
|
+
const custom = { ...DEFAULT_THEME, error: { r: 1, g: 2, b: 3, a: 255 } };
|
|
79
|
+
setTheme(custom);
|
|
80
|
+
const result = tp.error("x");
|
|
81
|
+
expect(result[0].style.fg).toEqual({ r: 1, g: 2, b: 3, a: 255 });
|
|
82
|
+
});
|
|
83
|
+
it("accentBright returns a styled span", () => {
|
|
84
|
+
const result = tp.accentBright("x");
|
|
85
|
+
expect(result[0].text).toBe("x");
|
|
86
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.accentBright);
|
|
87
|
+
});
|
|
88
|
+
it("accentDim returns a styled span", () => {
|
|
89
|
+
const result = tp.accentDim("x");
|
|
90
|
+
expect(result[0].text).toBe("x");
|
|
91
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.accentDim);
|
|
92
|
+
});
|
|
93
|
+
it("text returns a styled span", () => {
|
|
94
|
+
const result = tp.text("x");
|
|
95
|
+
expect(result[0].text).toBe("x");
|
|
96
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.text);
|
|
97
|
+
});
|
|
98
|
+
it("dim returns a styled span", () => {
|
|
99
|
+
const result = tp.dim("x");
|
|
100
|
+
expect(result[0].text).toBe("x");
|
|
101
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.textDim);
|
|
102
|
+
});
|
|
103
|
+
it("info returns a styled span", () => {
|
|
104
|
+
const result = tp.info("x");
|
|
105
|
+
expect(result[0].text).toBe("x");
|
|
106
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.info);
|
|
107
|
+
});
|
|
108
|
+
it("warning returns a styled span", () => {
|
|
109
|
+
const result = tp.warning("x");
|
|
110
|
+
expect(result[0].text).toBe("x");
|
|
111
|
+
expect(result[0].style.fg).toEqual(DEFAULT_THEME.warning);
|
|
112
|
+
});
|
|
113
|
+
});
|