@teammates/cli 0.2.2 → 0.2.4
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 +2 -0
- package/dist/adapters/cli-proxy.d.ts +1 -0
- package/dist/adapters/cli-proxy.js +6 -2
- package/dist/adapters/copilot.d.ts +1 -0
- package/dist/adapters/copilot.js +6 -2
- package/dist/cli.js +220 -147
- package/dist/onboard.d.ts +6 -5
- package/dist/onboard.js +109 -53
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +8 -2
- package/dist/registry.test.js +7 -0
- package/package.json +1 -1
package/dist/adapter.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface AgentAdapter {
|
|
|
24
24
|
* Falls back to startSession if not implemented.
|
|
25
25
|
*/
|
|
26
26
|
resumeSession?(teammate: TeammateConfig, sessionId: string): Promise<string>;
|
|
27
|
+
/** Get the session file path for a teammate (if session is active). */
|
|
28
|
+
getSessionFile?(teammateName: string): string | undefined;
|
|
27
29
|
/** Clean up a session. */
|
|
28
30
|
destroySession?(sessionId: string): Promise<void>;
|
|
29
31
|
/**
|
|
@@ -70,6 +70,7 @@ export declare class CliProxyAdapter implements AgentAdapter {
|
|
|
70
70
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
71
71
|
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string): Promise<TaskResult>;
|
|
72
72
|
routeTask(task: string, roster: RosterEntry[]): Promise<string | null>;
|
|
73
|
+
getSessionFile(teammateName: string): string | undefined;
|
|
73
74
|
destroySession(_sessionId: string): Promise<void>;
|
|
74
75
|
/**
|
|
75
76
|
* Spawn the agent, stream its output live, and capture it.
|
|
@@ -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
|
-
|
|
118
|
-
await mkdir(
|
|
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(() => "");
|
|
@@ -269,6 +270,9 @@ export class CliProxyAdapter {
|
|
|
269
270
|
await unlink(promptFile).catch(() => { });
|
|
270
271
|
}
|
|
271
272
|
}
|
|
273
|
+
getSessionFile(teammateName) {
|
|
274
|
+
return this.sessionFiles.get(teammateName);
|
|
275
|
+
}
|
|
272
276
|
async destroySession(_sessionId) {
|
|
273
277
|
// Clean up any leaked temp prompt files
|
|
274
278
|
for (const file of this.pendingTempFiles) {
|
|
@@ -42,6 +42,7 @@ export declare class CopilotAdapter implements AgentAdapter {
|
|
|
42
42
|
startSession(teammate: TeammateConfig): Promise<string>;
|
|
43
43
|
executeTask(_sessionId: string, teammate: TeammateConfig, prompt: string): Promise<TaskResult>;
|
|
44
44
|
routeTask(task: string, roster: RosterEntry[]): Promise<string | null>;
|
|
45
|
+
getSessionFile(teammateName: string): string | undefined;
|
|
45
46
|
destroySession(_sessionId: string): Promise<void>;
|
|
46
47
|
/**
|
|
47
48
|
* Ensure the CopilotClient is started.
|
package/dist/adapters/copilot.js
CHANGED
|
@@ -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
|
-
|
|
43
|
-
await mkdir(
|
|
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/")) {
|
|
@@ -173,6 +174,9 @@ export class CopilotAdapter {
|
|
|
173
174
|
await session.disconnect().catch(() => { });
|
|
174
175
|
}
|
|
175
176
|
}
|
|
177
|
+
getSessionFile(teammateName) {
|
|
178
|
+
return this.sessionFiles.get(teammateName);
|
|
179
|
+
}
|
|
176
180
|
async destroySession(_sessionId) {
|
|
177
181
|
// Disconnect all sessions
|
|
178
182
|
for (const [, session] of this.sessions) {
|
package/dist/cli.js
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* teammates --dir <path> Override .teammates/ location
|
|
9
9
|
*/
|
|
10
10
|
import { spawn as cpSpawn, exec as execCb, execSync, } from "node:child_process";
|
|
11
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
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);
|
|
@@ -24,7 +24,7 @@ import { findAtMention, isImagePath, relativeTime, wrapLine, } from "./cli-utils
|
|
|
24
24
|
import { compactEpisodic } from "./compact.js";
|
|
25
25
|
import { PromptInput } from "./console/prompt-input.js";
|
|
26
26
|
import { buildTitle } from "./console/startup.js";
|
|
27
|
-
import {
|
|
27
|
+
import { buildImportAdaptationPrompt, copyTemplateFiles, getOnboardingPrompt, importTeammates, } from "./onboard.js";
|
|
28
28
|
import { Orchestrator } from "./orchestrator.js";
|
|
29
29
|
import { colorToHex, theme } from "./theme.js";
|
|
30
30
|
// ─── Version ─────────────────────────────────────────────────────────
|
|
@@ -465,6 +465,28 @@ class TeammatesREPL {
|
|
|
465
465
|
lastCleanedOutput = ""; // last teammate output for clipboard copy
|
|
466
466
|
dispatching = false;
|
|
467
467
|
autoApproveHandoffs = false;
|
|
468
|
+
/** Read .teammates/settings.json (returns { version, services, ... } or defaults). */
|
|
469
|
+
readSettings() {
|
|
470
|
+
try {
|
|
471
|
+
const raw = JSON.parse(readFileSync(join(this.teammatesDir, "settings.json"), "utf-8"));
|
|
472
|
+
return {
|
|
473
|
+
version: raw.version ?? 1,
|
|
474
|
+
services: Array.isArray(raw.services) ? raw.services : [],
|
|
475
|
+
...raw,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return { version: 1, services: [] };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/** Write .teammates/settings.json. */
|
|
483
|
+
writeSettings(settings) {
|
|
484
|
+
writeFileSync(join(this.teammatesDir, "settings.json"), `${JSON.stringify(settings, null, 2)}\n`);
|
|
485
|
+
}
|
|
486
|
+
/** Check whether a specific service is installed. */
|
|
487
|
+
isServiceInstalled(name) {
|
|
488
|
+
return this.readSettings().services.some((s) => s.name === name);
|
|
489
|
+
}
|
|
468
490
|
/** Pending handoffs awaiting user approval. */
|
|
469
491
|
pendingHandoffs = [];
|
|
470
492
|
/** Pending retro proposals awaiting user approval. */
|
|
@@ -1420,14 +1442,17 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1420
1442
|
chalk.cyan(teammates.join(", ")));
|
|
1421
1443
|
console.log(chalk.gray(` (${files.length} files copied)`));
|
|
1422
1444
|
console.log();
|
|
1445
|
+
// Copy framework files so the agent has TEMPLATE.md etc. available
|
|
1446
|
+
await copyTemplateFiles(teammatesDir);
|
|
1423
1447
|
// Ask if user wants the agent to adapt teammates to this codebase
|
|
1424
1448
|
console.log(chalk.white(" Adapt teammates to this codebase?"));
|
|
1425
|
-
console.log(chalk.gray(" The agent will
|
|
1449
|
+
console.log(chalk.gray(" The agent will scan this project, evaluate which teammates are needed,"));
|
|
1450
|
+
console.log(chalk.gray(" adapt their files, and create any new teammates the project needs."));
|
|
1426
1451
|
console.log(chalk.gray(" You can also do this later with /init."));
|
|
1427
1452
|
console.log();
|
|
1428
1453
|
const adapt = await this.askChoice("Adapt now? (y/n): ", ["y", "n"]);
|
|
1429
1454
|
if (adapt === "y") {
|
|
1430
|
-
await this.runAdaptationAgent(this.adapter, projectDir, teammates);
|
|
1455
|
+
await this.runAdaptationAgent(this.adapter, projectDir, teammates, sourceDir);
|
|
1431
1456
|
}
|
|
1432
1457
|
else {
|
|
1433
1458
|
console.log(chalk.gray(" Skipped adaptation. Run /init to adapt later."));
|
|
@@ -1439,53 +1464,52 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1439
1464
|
console.log();
|
|
1440
1465
|
}
|
|
1441
1466
|
/**
|
|
1442
|
-
* Run the agent to adapt imported teammates
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1467
|
+
* Run the agent to adapt imported teammates to the current codebase.
|
|
1468
|
+
* Uses a single comprehensive session that scans the project, evaluates
|
|
1469
|
+
* which teammates to keep/drop/create, adapts kept teammates (with
|
|
1470
|
+
* Previous Projects sections), and creates any new teammates needed.
|
|
1445
1471
|
*/
|
|
1446
|
-
async runAdaptationAgent(adapter, projectDir, teammateNames) {
|
|
1472
|
+
async runAdaptationAgent(adapter, projectDir, teammateNames, sourceProjectPath) {
|
|
1447
1473
|
const teammatesDir = join(projectDir, ".teammates");
|
|
1448
1474
|
console.log();
|
|
1449
|
-
console.log(chalk.blue("
|
|
1450
|
-
chalk.gray(` ${this.adapterName} will adapt
|
|
1475
|
+
console.log(chalk.blue(" Starting adaptation...") +
|
|
1476
|
+
chalk.gray(` ${this.adapterName} will scan this project and adapt the team`));
|
|
1451
1477
|
console.log();
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
console.log(chalk.green(` ✔ @${name} adaptation complete!`));
|
|
1476
|
-
}
|
|
1477
|
-
else {
|
|
1478
|
-
console.log(chalk.yellow(` ⚠ @${name} adaptation finished with issues: ${result.summary}`));
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
catch (err) {
|
|
1482
|
-
spinner.fail(chalk.red(`@${name} adaptation failed: ${err.message}`));
|
|
1478
|
+
const prompt = await buildImportAdaptationPrompt(teammatesDir, teammateNames, sourceProjectPath);
|
|
1479
|
+
const tempConfig = {
|
|
1480
|
+
name: this.adapterName,
|
|
1481
|
+
role: "Adaptation agent",
|
|
1482
|
+
soul: "",
|
|
1483
|
+
wisdom: "",
|
|
1484
|
+
dailyLogs: [],
|
|
1485
|
+
weeklyLogs: [],
|
|
1486
|
+
ownership: { primary: [], secondary: [] },
|
|
1487
|
+
routingKeywords: [],
|
|
1488
|
+
};
|
|
1489
|
+
const sessionId = await adapter.startSession(tempConfig);
|
|
1490
|
+
const spinner = ora({
|
|
1491
|
+
text: chalk.blue(this.adapterName) +
|
|
1492
|
+
chalk.gray(" is scanning the project and adapting teammates..."),
|
|
1493
|
+
spinner: "dots",
|
|
1494
|
+
}).start();
|
|
1495
|
+
try {
|
|
1496
|
+
const result = await adapter.executeTask(sessionId, tempConfig, prompt);
|
|
1497
|
+
spinner.stop();
|
|
1498
|
+
this.printAgentOutput(result.rawOutput);
|
|
1499
|
+
if (result.success) {
|
|
1500
|
+
console.log(chalk.green(" ✔ Team adaptation complete!"));
|
|
1483
1501
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1502
|
+
else {
|
|
1503
|
+
console.log(chalk.yellow(` ⚠ Adaptation finished with issues: ${result.summary}`));
|
|
1486
1504
|
}
|
|
1487
|
-
console.log();
|
|
1488
1505
|
}
|
|
1506
|
+
catch (err) {
|
|
1507
|
+
spinner.fail(chalk.red(`Adaptation failed: ${err.message}`));
|
|
1508
|
+
}
|
|
1509
|
+
if (adapter.destroySession) {
|
|
1510
|
+
await adapter.destroySession(sessionId);
|
|
1511
|
+
}
|
|
1512
|
+
console.log();
|
|
1489
1513
|
}
|
|
1490
1514
|
/**
|
|
1491
1515
|
* Simple blocking prompt — reads one line from stdin and validates.
|
|
@@ -1950,6 +1974,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1950
1974
|
weeklyLogs: [],
|
|
1951
1975
|
ownership: { primary: [], secondary: [] },
|
|
1952
1976
|
routingKeywords: [],
|
|
1977
|
+
cwd: dirname(this.teammatesDir),
|
|
1953
1978
|
});
|
|
1954
1979
|
// Add status entry (init() already ran, so we add it manually)
|
|
1955
1980
|
this.orchestrator.getAllStatuses().set(this.adapterName, { state: "idle" });
|
|
@@ -1963,21 +1988,15 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
1963
1988
|
return { name: t.name, role: t.role, ownership: t.ownership };
|
|
1964
1989
|
});
|
|
1965
1990
|
}
|
|
1966
|
-
// Detect installed services from
|
|
1991
|
+
// Detect installed services from settings.json and tell the adapter
|
|
1967
1992
|
if ("services" in this.adapter) {
|
|
1968
1993
|
const services = [];
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
usage: 'teammates-recall search "your query" --dir .teammates',
|
|
1976
|
-
});
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
catch {
|
|
1980
|
-
/* no services.json or invalid */
|
|
1994
|
+
if (this.isServiceInstalled("recall")) {
|
|
1995
|
+
services.push({
|
|
1996
|
+
name: "recall",
|
|
1997
|
+
description: "Local semantic search across teammate memories and daily logs. Use this to find relevant context before starting a task.",
|
|
1998
|
+
usage: 'teammates-recall search "your query" --dir .teammates',
|
|
1999
|
+
});
|
|
1981
2000
|
}
|
|
1982
2001
|
this.adapter.services = services;
|
|
1983
2002
|
}
|
|
@@ -2034,14 +2053,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2034
2053
|
// ── Build animated banner for ChatView ─────────────────────────────
|
|
2035
2054
|
const names = this.orchestrator.listTeammates();
|
|
2036
2055
|
const reg = this.orchestrator.getRegistry();
|
|
2037
|
-
|
|
2038
|
-
try {
|
|
2039
|
-
const svcJson = JSON.parse(readFileSync(join(this.teammatesDir, "services.json"), "utf-8"));
|
|
2040
|
-
hasRecall = !!(svcJson && "recall" in svcJson);
|
|
2041
|
-
}
|
|
2042
|
-
catch {
|
|
2043
|
-
/* no services.json */
|
|
2044
|
-
}
|
|
2056
|
+
const hasRecall = this.isServiceInstalled("recall");
|
|
2045
2057
|
const bannerWidget = new AnimatedBanner({
|
|
2046
2058
|
adapterName: this.adapterName,
|
|
2047
2059
|
teammateCount: names.length,
|
|
@@ -2432,15 +2444,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2432
2444
|
printBanner(teammates) {
|
|
2433
2445
|
const registry = this.orchestrator.getRegistry();
|
|
2434
2446
|
const termWidth = process.stdout.columns || 100;
|
|
2435
|
-
// Detect recall from
|
|
2436
|
-
|
|
2437
|
-
try {
|
|
2438
|
-
const svcJson = JSON.parse(readFileSync(join(this.teammatesDir, "services.json"), "utf-8"));
|
|
2439
|
-
recallInstalled = !!(svcJson && "recall" in svcJson);
|
|
2440
|
-
}
|
|
2441
|
-
catch {
|
|
2442
|
-
/* no services.json or invalid */
|
|
2443
|
-
}
|
|
2447
|
+
// Detect recall from settings.json
|
|
2448
|
+
const recallInstalled = this.isServiceInstalled("recall");
|
|
2444
2449
|
this.feedLine();
|
|
2445
2450
|
this.feedLine(concat(tp.bold(" Teammates"), tp.muted(` v${PKG_VERSION}`)));
|
|
2446
2451
|
this.feedLine(concat(tp.text(` ${this.adapterName}`), tp.muted(` · ${teammates.length} teammate${teammates.length === 1 ? "" : "s"}`)));
|
|
@@ -2782,45 +2787,61 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2782
2787
|
}
|
|
2783
2788
|
async cmdDebug(argsStr) {
|
|
2784
2789
|
const arg = argsStr.trim().replace(/^@/, "");
|
|
2785
|
-
// Resolve
|
|
2786
|
-
let
|
|
2790
|
+
// Resolve which teammates to show debug info for
|
|
2791
|
+
let targetNames;
|
|
2787
2792
|
if (arg === "everyone") {
|
|
2788
|
-
|
|
2789
|
-
for (const [name
|
|
2790
|
-
if (name !== this.adapterName
|
|
2791
|
-
|
|
2792
|
-
}
|
|
2793
|
+
targetNames = [];
|
|
2794
|
+
for (const [name] of this.lastResults) {
|
|
2795
|
+
if (name !== this.adapterName)
|
|
2796
|
+
targetNames.push(name);
|
|
2793
2797
|
}
|
|
2794
|
-
if (
|
|
2795
|
-
this.feedLine(tp.muted(" No
|
|
2798
|
+
if (targetNames.length === 0) {
|
|
2799
|
+
this.feedLine(tp.muted(" No debug info available from any teammate."));
|
|
2796
2800
|
this.refreshView();
|
|
2797
2801
|
return;
|
|
2798
2802
|
}
|
|
2799
2803
|
}
|
|
2804
|
+
else if (arg) {
|
|
2805
|
+
targetNames = [arg];
|
|
2806
|
+
}
|
|
2807
|
+
else if (this.lastResult) {
|
|
2808
|
+
targetNames = [this.lastResult.teammate];
|
|
2809
|
+
}
|
|
2800
2810
|
else {
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
(arg ? "" : " Try: /debug <teammate>")));
|
|
2805
|
-
this.refreshView();
|
|
2806
|
-
return;
|
|
2807
|
-
}
|
|
2808
|
-
targets = [{ name: result.teammate, result }];
|
|
2811
|
+
this.feedLine(tp.muted(" No debug info available. Try: /debug [teammate]"));
|
|
2812
|
+
this.refreshView();
|
|
2813
|
+
return;
|
|
2809
2814
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
this.feedLine(tp.muted(` ── raw output from ${name} ──`));
|
|
2815
|
+
let debugText = "";
|
|
2816
|
+
for (const name of targetNames) {
|
|
2813
2817
|
this.feedLine();
|
|
2814
|
-
this.
|
|
2818
|
+
this.feedLine(tp.muted(` ── debug for ${name} ──`));
|
|
2819
|
+
// Read the last debug entry from the session file
|
|
2820
|
+
const sessionEntry = this.readLastDebugEntry(name);
|
|
2821
|
+
if (sessionEntry) {
|
|
2822
|
+
this.feedLine();
|
|
2823
|
+
this.feedMarkdown(sessionEntry);
|
|
2824
|
+
debugText += (debugText ? "\n\n" : "") + sessionEntry;
|
|
2825
|
+
}
|
|
2826
|
+
else {
|
|
2827
|
+
// Fall back to raw output from lastResults
|
|
2828
|
+
const result = this.lastResults.get(name);
|
|
2829
|
+
if (result?.rawOutput) {
|
|
2830
|
+
this.feedLine();
|
|
2831
|
+
this.feedMarkdown(result.rawOutput);
|
|
2832
|
+
debugText += (debugText ? "\n\n" : "") + result.rawOutput;
|
|
2833
|
+
}
|
|
2834
|
+
else {
|
|
2835
|
+
this.feedLine(tp.muted(" No debug info available."));
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2815
2838
|
this.feedLine();
|
|
2816
|
-
this.feedLine(tp.muted(" ── end
|
|
2839
|
+
this.feedLine(tp.muted(" ── end debug ──"));
|
|
2817
2840
|
}
|
|
2818
2841
|
// [copy] action for the debug output
|
|
2819
|
-
if (this.chatView) {
|
|
2842
|
+
if (this.chatView && debugText) {
|
|
2820
2843
|
const t = theme();
|
|
2821
|
-
this.lastCleanedOutput =
|
|
2822
|
-
.map((t) => t.result.rawOutput)
|
|
2823
|
-
.join("\n\n");
|
|
2844
|
+
this.lastCleanedOutput = debugText;
|
|
2824
2845
|
this.chatView.appendActionList([
|
|
2825
2846
|
{
|
|
2826
2847
|
id: "copy",
|
|
@@ -2838,6 +2859,28 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2838
2859
|
this.feedLine();
|
|
2839
2860
|
this.refreshView();
|
|
2840
2861
|
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Read the last debug entry from a teammate's session file.
|
|
2864
|
+
* Debug entries are delimited by "## Debug — " headings.
|
|
2865
|
+
*/
|
|
2866
|
+
readLastDebugEntry(teammate) {
|
|
2867
|
+
try {
|
|
2868
|
+
const sessionFile = this.adapter.getSessionFile?.(teammate);
|
|
2869
|
+
if (!sessionFile)
|
|
2870
|
+
return null;
|
|
2871
|
+
const content = readFileSync(sessionFile, "utf-8");
|
|
2872
|
+
// Split on debug entry headings and return the last one
|
|
2873
|
+
const entries = content.split(/(?=^## Debug — )/m);
|
|
2874
|
+
const last = entries[entries.length - 1];
|
|
2875
|
+
if (last && last.startsWith("## Debug — ")) {
|
|
2876
|
+
return last.trim();
|
|
2877
|
+
}
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
catch {
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2841
2884
|
async cmdCancel(argsStr) {
|
|
2842
2885
|
const n = parseInt(argsStr.trim(), 10);
|
|
2843
2886
|
if (Number.isNaN(n) || n < 1 || n > this.taskQueue.length) {
|
|
@@ -2862,6 +2905,7 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2862
2905
|
break;
|
|
2863
2906
|
const entry = this.taskQueue.splice(idx, 1)[0];
|
|
2864
2907
|
this.agentActive.set(agent, entry);
|
|
2908
|
+
const startTime = Date.now();
|
|
2865
2909
|
try {
|
|
2866
2910
|
if (entry.type === "compact") {
|
|
2867
2911
|
await this.runCompact(entry.teammate);
|
|
@@ -2874,6 +2918,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2874
2918
|
task: entry.task,
|
|
2875
2919
|
extraContext: extraContext || undefined,
|
|
2876
2920
|
});
|
|
2921
|
+
// Write debug entry to session file
|
|
2922
|
+
this.writeDebugEntry(entry.teammate, entry.task, result, startTime);
|
|
2877
2923
|
// btw results are not stored in conversation history
|
|
2878
2924
|
if (entry.type !== "btw") {
|
|
2879
2925
|
this.storeResult(result);
|
|
@@ -2884,6 +2930,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2884
2930
|
}
|
|
2885
2931
|
}
|
|
2886
2932
|
catch (err) {
|
|
2933
|
+
// Write error debug entry to session file
|
|
2934
|
+
this.writeDebugEntry(entry.teammate, entry.task, null, startTime, err);
|
|
2887
2935
|
// Handle spawn failures, network errors, etc. gracefully
|
|
2888
2936
|
this.activeTasks.delete(agent);
|
|
2889
2937
|
if (this.activeTasks.size === 0)
|
|
@@ -2895,6 +2943,52 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2895
2943
|
this.agentActive.delete(agent);
|
|
2896
2944
|
}
|
|
2897
2945
|
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Append a debug entry to the teammate's session file.
|
|
2948
|
+
* Captures task prompt, result summary, raw output, timing, and errors.
|
|
2949
|
+
*/
|
|
2950
|
+
writeDebugEntry(teammate, task, result, startTime, error) {
|
|
2951
|
+
try {
|
|
2952
|
+
const sessionFile = this.adapter.getSessionFile?.(teammate);
|
|
2953
|
+
if (!sessionFile)
|
|
2954
|
+
return;
|
|
2955
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
2956
|
+
const timestamp = new Date().toISOString();
|
|
2957
|
+
const lines = [
|
|
2958
|
+
"",
|
|
2959
|
+
`## Debug — ${timestamp}`,
|
|
2960
|
+
"",
|
|
2961
|
+
`**Duration:** ${elapsed}s`,
|
|
2962
|
+
`**Task:** ${task.length > 200 ? `${task.slice(0, 200)}…` : task}`,
|
|
2963
|
+
"",
|
|
2964
|
+
];
|
|
2965
|
+
if (error) {
|
|
2966
|
+
lines.push(`**Status:** ERROR`);
|
|
2967
|
+
lines.push(`**Error:** ${error?.message ?? String(error)}`);
|
|
2968
|
+
}
|
|
2969
|
+
else if (result) {
|
|
2970
|
+
lines.push(`**Status:** ${result.success ? "OK" : "FAILED"}`);
|
|
2971
|
+
lines.push(`**Summary:** ${result.summary || "(no summary)"}`);
|
|
2972
|
+
if (result.changedFiles.length > 0) {
|
|
2973
|
+
lines.push(`**Changed files:** ${result.changedFiles.join(", ")}`);
|
|
2974
|
+
}
|
|
2975
|
+
if (result.handoffs.length > 0) {
|
|
2976
|
+
lines.push(`**Handoffs:** ${result.handoffs.map((h) => `@${h.to}`).join(", ")}`);
|
|
2977
|
+
}
|
|
2978
|
+
lines.push("");
|
|
2979
|
+
lines.push("<details><summary>Raw output</summary>");
|
|
2980
|
+
lines.push("");
|
|
2981
|
+
lines.push(result.rawOutput ?? "(empty)");
|
|
2982
|
+
lines.push("");
|
|
2983
|
+
lines.push("</details>");
|
|
2984
|
+
}
|
|
2985
|
+
lines.push("");
|
|
2986
|
+
appendFileSync(sessionFile, lines.join("\n"), "utf-8");
|
|
2987
|
+
}
|
|
2988
|
+
catch {
|
|
2989
|
+
// Don't let debug logging break task execution
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2898
2992
|
async cmdInit(argsStr) {
|
|
2899
2993
|
const cwd = process.cwd();
|
|
2900
2994
|
const teammatesDir = join(cwd, ".teammates");
|
|
@@ -2924,16 +3018,16 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
2924
3018
|
return;
|
|
2925
3019
|
}
|
|
2926
3020
|
this.feedLine(tp.success(` Imported ${teammates.length} teammate${teammates.length > 1 ? "s" : ""}: ${teammates.join(", ")} (${files.length} files)`));
|
|
2927
|
-
//
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
}
|
|
3021
|
+
// Copy framework files so the agent has TEMPLATE.md etc. available
|
|
3022
|
+
await copyTemplateFiles(teammatesDir);
|
|
3023
|
+
// Queue a single adaptation task that handles all teammates
|
|
3024
|
+
this.feedLine(tp.muted(` Queuing ${this.adapterName} to scan this project and adapt the team...`));
|
|
3025
|
+
const prompt = await buildImportAdaptationPrompt(teammatesDir, teammates, sourceDir);
|
|
3026
|
+
this.taskQueue.push({
|
|
3027
|
+
type: "agent",
|
|
3028
|
+
teammate: this.adapterName,
|
|
3029
|
+
task: prompt,
|
|
3030
|
+
});
|
|
2937
3031
|
this.kickDrain();
|
|
2938
3032
|
}
|
|
2939
3033
|
catch (err) {
|
|
@@ -3022,19 +3116,12 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3022
3116
|
return;
|
|
3023
3117
|
}
|
|
3024
3118
|
this.feedLine(tp.success(` ✔ ${serviceName} installed successfully`));
|
|
3025
|
-
// Register in
|
|
3026
|
-
const
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
catch {
|
|
3032
|
-
/* new file */
|
|
3033
|
-
}
|
|
3034
|
-
if (!(serviceName in svcJson)) {
|
|
3035
|
-
svcJson[serviceName] = {};
|
|
3036
|
-
writeFileSync(svcPath, `${JSON.stringify(svcJson, null, 2)}\n`);
|
|
3037
|
-
this.feedLine(tp.muted(` Registered in services.json`));
|
|
3119
|
+
// Register in settings.json
|
|
3120
|
+
const settings = this.readSettings();
|
|
3121
|
+
if (!settings.services.some((s) => s.name === serviceName)) {
|
|
3122
|
+
settings.services.push({ name: serviceName });
|
|
3123
|
+
this.writeSettings(settings);
|
|
3124
|
+
this.feedLine(tp.muted(` Registered in settings.json`));
|
|
3038
3125
|
}
|
|
3039
3126
|
// Build initial index if this service supports it
|
|
3040
3127
|
if (service.indexCmd) {
|
|
@@ -3133,15 +3220,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3133
3220
|
.catch(() => { });
|
|
3134
3221
|
}
|
|
3135
3222
|
startRecallWatch() {
|
|
3136
|
-
// Only start if recall is installed (check
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
if (!svcJson || !("recall" in svcJson))
|
|
3140
|
-
return;
|
|
3141
|
-
}
|
|
3142
|
-
catch {
|
|
3143
|
-
return; // No services.json — recall not installed
|
|
3144
|
-
}
|
|
3223
|
+
// Only start if recall is installed (check settings.json)
|
|
3224
|
+
if (!this.isServiceInstalled("recall"))
|
|
3225
|
+
return;
|
|
3145
3226
|
try {
|
|
3146
3227
|
this.recallWatchProcess = cpSpawn("teammates-recall", ["watch", "--dir", this.teammatesDir, "--json"], {
|
|
3147
3228
|
stdio: ["ignore", "ignore", "ignore"],
|
|
@@ -3245,9 +3326,8 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3245
3326
|
if (this.chatView)
|
|
3246
3327
|
this.chatView.setProgress(null);
|
|
3247
3328
|
// Trigger recall sync if installed
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
if (svcJson && "recall" in svcJson) {
|
|
3329
|
+
if (this.isServiceInstalled("recall")) {
|
|
3330
|
+
try {
|
|
3251
3331
|
if (this.chatView) {
|
|
3252
3332
|
this.chatView.setProgress(`Syncing ${name} index...`);
|
|
3253
3333
|
this.refreshView();
|
|
@@ -3267,9 +3347,9 @@ Do NOT modify any other teammate's files. Only edit your own SOUL.md and daily l
|
|
|
3267
3347
|
this.feedLine(tp.success(` ✔ ${name}: index synced`));
|
|
3268
3348
|
}
|
|
3269
3349
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3350
|
+
catch {
|
|
3351
|
+
/* sync failed — non-fatal */
|
|
3352
|
+
}
|
|
3273
3353
|
}
|
|
3274
3354
|
}
|
|
3275
3355
|
catch (err) {
|
|
@@ -3394,14 +3474,7 @@ Issues that can't be resolved unilaterally — they need input from other teamma
|
|
|
3394
3474
|
if (teammates.length === 0)
|
|
3395
3475
|
return;
|
|
3396
3476
|
// Check if recall is installed
|
|
3397
|
-
|
|
3398
|
-
try {
|
|
3399
|
-
const svcJson = JSON.parse(readFileSync(join(this.teammatesDir, "services.json"), "utf-8"));
|
|
3400
|
-
recallInstalled = !!(svcJson && "recall" in svcJson);
|
|
3401
|
-
}
|
|
3402
|
-
catch {
|
|
3403
|
-
/* no services.json */
|
|
3404
|
-
}
|
|
3477
|
+
const recallInstalled = this.isServiceInstalled("recall");
|
|
3405
3478
|
// 1. Check each teammate for stale daily logs (older than 7 days)
|
|
3406
3479
|
const oneWeekAgo = new Date();
|
|
3407
3480
|
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
package/dist/onboard.d.ts
CHANGED
|
@@ -28,14 +28,15 @@ export declare function importTeammates(sourceDir: string, targetDir: string): P
|
|
|
28
28
|
files: string[];
|
|
29
29
|
}>;
|
|
30
30
|
/**
|
|
31
|
-
* Build
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Build an import-adaptation prompt that runs as a single non-interactive agent session.
|
|
32
|
+
* The agent scans the target project and adapts all imported teammates in one pass.
|
|
33
|
+
* No pauses or approval gates — the agent must complete all work autonomously.
|
|
34
34
|
*
|
|
35
35
|
* @param teammatesDir - The .teammates/ directory in the target project
|
|
36
|
-
* @param
|
|
36
|
+
* @param teammateNames - Names of all imported teammates
|
|
37
|
+
* @param sourceProjectPath - Path to the source project (for Previous Projects section)
|
|
37
38
|
*/
|
|
38
|
-
export declare function
|
|
39
|
+
export declare function buildImportAdaptationPrompt(teammatesDir: string, teammateNames: string[], sourceProjectPath: string): Promise<string>;
|
|
39
40
|
/**
|
|
40
41
|
* Load ONBOARDING.md from the project dir, package root, or built-in fallback.
|
|
41
42
|
*/
|
package/dist/onboard.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function copyTemplateFiles(teammatesDir) {
|
|
|
60
60
|
await stat(gitignoreDest);
|
|
61
61
|
}
|
|
62
62
|
catch {
|
|
63
|
-
const gitignoreContent = "USER.md\n
|
|
63
|
+
const gitignoreContent = "USER.md\n.*/\n";
|
|
64
64
|
const { writeFile } = await import("node:fs/promises");
|
|
65
65
|
await writeFile(gitignoreDest, gitignoreContent, "utf-8");
|
|
66
66
|
copied.push(".gitignore");
|
|
@@ -92,7 +92,7 @@ export async function copyTemplateFiles(teammatesDir) {
|
|
|
92
92
|
* Directories starting with "_" are shared non-teammate folders.
|
|
93
93
|
* Files (non-directories) and special names are also excluded.
|
|
94
94
|
*/
|
|
95
|
-
const NON_TEAMMATE_NAMES = new Set(["example", "
|
|
95
|
+
const NON_TEAMMATE_NAMES = new Set(["example", "settings.json"]);
|
|
96
96
|
function isNonTeammateEntry(name) {
|
|
97
97
|
return (name.startsWith(".") || name.startsWith("_") || NON_TEAMMATE_NAMES.has(name));
|
|
98
98
|
}
|
|
@@ -182,66 +182,124 @@ export async function importTeammates(sourceDir, targetDir) {
|
|
|
182
182
|
await stat(gitignoreDest);
|
|
183
183
|
}
|
|
184
184
|
catch {
|
|
185
|
-
await writeFile(gitignoreDest, "USER.md\n
|
|
185
|
+
await writeFile(gitignoreDest, "USER.md\n.*/\n", "utf-8");
|
|
186
186
|
files.push(".gitignore");
|
|
187
187
|
}
|
|
188
188
|
return { teammates, files };
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
|
-
* Build
|
|
192
|
-
*
|
|
193
|
-
*
|
|
191
|
+
* Build an import-adaptation prompt that runs as a single non-interactive agent session.
|
|
192
|
+
* The agent scans the target project and adapts all imported teammates in one pass.
|
|
193
|
+
* No pauses or approval gates — the agent must complete all work autonomously.
|
|
194
194
|
*
|
|
195
195
|
* @param teammatesDir - The .teammates/ directory in the target project
|
|
196
|
-
* @param
|
|
196
|
+
* @param teammateNames - Names of all imported teammates
|
|
197
|
+
* @param sourceProjectPath - Path to the source project (for Previous Projects section)
|
|
197
198
|
*/
|
|
198
|
-
export async function
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
199
|
+
export async function buildImportAdaptationPrompt(teammatesDir, teammateNames, sourceProjectPath) {
|
|
200
|
+
const teammateSections = [];
|
|
201
|
+
for (const name of teammateNames) {
|
|
202
|
+
const dir = join(teammatesDir, name);
|
|
203
|
+
let soulContent = "";
|
|
204
|
+
let wisdomContent = "";
|
|
205
|
+
try {
|
|
206
|
+
soulContent = await readFile(join(dir, "SOUL.md"), "utf-8");
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
/* missing */
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
wisdomContent = await readFile(join(dir, "WISDOM.md"), "utf-8");
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
/* missing */
|
|
216
|
+
}
|
|
217
|
+
const soulBlock = soulContent
|
|
218
|
+
? `**SOUL.md:**\n\`\`\`markdown\n${soulContent}\n\`\`\``
|
|
219
|
+
: "*No SOUL.md found*";
|
|
220
|
+
const wisdomBlock = wisdomContent
|
|
221
|
+
? `\n**WISDOM.md:**\n\`\`\`markdown\n${wisdomContent}\n\`\`\``
|
|
222
|
+
: "";
|
|
223
|
+
teammateSections.push(`### @${name}\n${soulBlock}${wisdomBlock}`);
|
|
214
224
|
}
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const projectDir = dirname(teammatesDir);
|
|
226
|
+
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.
|
|
227
|
+
|
|
228
|
+
**Source project:** \`${sourceProjectPath}\`
|
|
229
|
+
**Target project:** \`${projectDir}\`
|
|
230
|
+
**Target .teammates/ directory:** \`${teammatesDir}\`
|
|
231
|
+
**Imported teammates:** ${teammateNames.map((n) => `@${n}`).join(", ")}
|
|
232
|
+
|
|
233
|
+
> **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.
|
|
222
234
|
|
|
223
|
-
|
|
235
|
+
## Imported Teammates (from source project)
|
|
224
236
|
|
|
225
|
-
|
|
226
|
-
${soulSection}${wisdomSection}
|
|
237
|
+
${teammateSections.join("\n\n---\n\n")}
|
|
227
238
|
|
|
228
|
-
##
|
|
239
|
+
## Instructions
|
|
229
240
|
|
|
230
|
-
|
|
241
|
+
Complete these steps in order. Do NOT pause, ask questions, or wait for approval. Make all changes directly.
|
|
231
242
|
|
|
232
|
-
|
|
233
|
-
- **Preserve**: Identity, Core Principles, Ethics, personality, tone
|
|
234
|
-
- **Update**: Ownership patterns (primary/secondary file globs), Boundaries (reference correct teammate names), Capabilities (commands, file patterns, technologies), Routing keywords, Quality Bar
|
|
235
|
-
- **Adapt**: Any codebase-specific references (paths, package names, tools)
|
|
243
|
+
### Step 1: Scan This Project
|
|
236
244
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
245
|
+
Read the project root to understand its structure:
|
|
246
|
+
- Package manifest, README, config files
|
|
247
|
+
- Major subsystems, languages, frameworks, file patterns
|
|
248
|
+
- Dependency flow and architecture
|
|
241
249
|
|
|
242
|
-
|
|
250
|
+
### Step 2: Adapt EVERY Imported Teammate
|
|
243
251
|
|
|
244
|
-
|
|
252
|
+
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.
|
|
253
|
+
|
|
254
|
+
For each teammate's **SOUL.md**:
|
|
255
|
+
|
|
256
|
+
1. **Add a "Previous Projects" section** (place it after Ethics). Compress what the teammate did in the source project:
|
|
257
|
+
\`\`\`markdown
|
|
258
|
+
## Previous Projects
|
|
259
|
+
|
|
260
|
+
### <source-project-name>
|
|
261
|
+
- **Role**: <one-line summary of what they did>
|
|
262
|
+
- **Stack**: <key technologies they worked with>
|
|
263
|
+
- **Domains**: <what they owned — file patterns or subsystem names>
|
|
264
|
+
- **Key learnings**: <1-3 bullets of notable patterns, decisions, or lessons>
|
|
265
|
+
\`\`\`
|
|
266
|
+
|
|
267
|
+
2. **Rewrite project-specific sections** for THIS project:
|
|
268
|
+
- **Preserve**: Identity (name, personality), Core Principles, Ethics
|
|
269
|
+
- **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
|
|
270
|
+
- **Update**: All codebase-specific references — paths, package names, tools, teammate names must reference THIS project
|
|
271
|
+
|
|
272
|
+
For each teammate's **WISDOM.md**:
|
|
273
|
+
- Add a "Previous Projects" note at the top
|
|
274
|
+
- Keep universal wisdom entries (general principles, patterns)
|
|
275
|
+
- Remove entries that reference source project paths, architecture, or tools not used here
|
|
276
|
+
- Adapt entries with transferable knowledge but old-project-specific details
|
|
277
|
+
|
|
278
|
+
### Step 3: Evaluate Gaps and Create New Teammates
|
|
279
|
+
|
|
280
|
+
After adapting all existing teammates, check if THIS project has major subsystems that no teammate covers. If so, create new teammates:
|
|
281
|
+
- Create \`${teammatesDir}/<name>/\` with SOUL.md, WISDOM.md, and \`memory/\`
|
|
282
|
+
- Use the template at \`${teammatesDir}/TEMPLATE.md\` for structure
|
|
283
|
+
- WISDOM.md starts with one creation entry
|
|
284
|
+
|
|
285
|
+
If a teammate's domain doesn't exist at all in this project and their skills aren't transferable, delete their directory under \`${teammatesDir}\`.
|
|
286
|
+
|
|
287
|
+
### Step 4: Update Framework Files
|
|
288
|
+
|
|
289
|
+
- Update \`${teammatesDir}/README.md\` with the final roster
|
|
290
|
+
- Update \`${teammatesDir}/CROSS-TEAM.md\` ownership table
|
|
291
|
+
|
|
292
|
+
### Step 5: Verify
|
|
293
|
+
|
|
294
|
+
- Every teammate has SOUL.md and WISDOM.md adapted to THIS project
|
|
295
|
+
- Ownership globs reference actual files in THIS project
|
|
296
|
+
- Boundaries reference correct teammate names
|
|
297
|
+
- Previous Projects sections are present for all imported teammates
|
|
298
|
+
- CROSS-TEAM.md has one row per teammate
|
|
299
|
+
|
|
300
|
+
## Critical Reminder
|
|
301
|
+
|
|
302
|
+
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.`;
|
|
245
303
|
}
|
|
246
304
|
/**
|
|
247
305
|
* Load ONBOARDING.md from the project dir, package root, or built-in fallback.
|
|
@@ -288,7 +346,9 @@ function wrapPrompt(onboardingContent, projectDir) {
|
|
|
288
346
|
|
|
289
347
|
You do NOT need to create the framework files listed above — they're already there.
|
|
290
348
|
|
|
291
|
-
|
|
349
|
+
> **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".
|
|
350
|
+
|
|
351
|
+
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.
|
|
292
352
|
|
|
293
353
|
---
|
|
294
354
|
|
|
@@ -312,26 +372,22 @@ Identify:
|
|
|
312
372
|
3. **Key technologies** — languages, frameworks, tools per area
|
|
313
373
|
4. **File patterns** — glob patterns for each domain
|
|
314
374
|
|
|
315
|
-
**Present your analysis to the user and get confirmation before proceeding.**
|
|
316
|
-
|
|
317
375
|
## Step 2: Design the Team
|
|
318
376
|
|
|
319
|
-
|
|
377
|
+
Based on your analysis, design a roster of teammates:
|
|
320
378
|
- **Aim for 3–7 teammates.** Fewer for small projects, more for monorepos.
|
|
321
379
|
- **Each teammate owns a distinct domain** with minimal overlap.
|
|
322
380
|
- **Pick short, memorable names** — one word, evocative of the domain.
|
|
323
381
|
|
|
324
|
-
For each
|
|
382
|
+
For each teammate, define:
|
|
325
383
|
- Name and one-line persona
|
|
326
384
|
- Primary ownership (file patterns)
|
|
327
385
|
- Key technologies
|
|
328
386
|
- Boundaries (what they do NOT own)
|
|
329
387
|
|
|
330
|
-
**Present the proposed roster to the user for approval.**
|
|
331
|
-
|
|
332
388
|
## Step 3: Create the Directory Structure
|
|
333
389
|
|
|
334
|
-
|
|
390
|
+
Create teammate folders under \`.teammates/\`:
|
|
335
391
|
|
|
336
392
|
### Teammate folders
|
|
337
393
|
For each teammate, create \`.teammates/<name>/\` with:
|
package/dist/registry.d.ts
CHANGED
|
@@ -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
|
@@ -5,17 +5,22 @@
|
|
|
5
5
|
* (SOUL.md, WISDOM.md, daily logs, ownership rules).
|
|
6
6
|
*/
|
|
7
7
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
8
|
-
import { basename, join } from "node:path";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
9
9
|
export class Registry {
|
|
10
10
|
teammatesDir;
|
|
11
11
|
teammates = new Map();
|
|
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() &&
|
|
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) {
|
|
@@ -50,6 +55,7 @@ export class Registry {
|
|
|
50
55
|
weeklyLogs,
|
|
51
56
|
ownership,
|
|
52
57
|
routingKeywords,
|
|
58
|
+
cwd: dirname(this.teammatesDir),
|
|
53
59
|
};
|
|
54
60
|
this.teammates.set(name, config);
|
|
55
61
|
return config;
|
package/dist/registry.test.js
CHANGED
|
@@ -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