teamai-cli 0.25.0-beta.0 → 0.25.0-beta.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/CHANGELOG.md +6 -0
- package/README.ja.md +278 -0
- package/README.ko.md +278 -0
- package/README.md +2 -2
- package/README.th.md +278 -0
- package/README.zh-CN.md +2 -2
- package/dist/index.js +919 -625
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1344,7 +1344,8 @@ function resolveRoleResourceNamespaces(input) {
|
|
|
1344
1344
|
skills: [],
|
|
1345
1345
|
// Roles never contribute learnings namespaces; only projects do. Kept empty
|
|
1346
1346
|
// so the shape matches project resolution for a clean union at the call site.
|
|
1347
|
-
learnings: []
|
|
1347
|
+
learnings: [],
|
|
1348
|
+
agents: []
|
|
1348
1349
|
};
|
|
1349
1350
|
for (const type of ROLE_RESOURCE_TYPES) {
|
|
1350
1351
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1388,10 +1389,13 @@ var init_roles = __esm({
|
|
|
1388
1389
|
"use strict";
|
|
1389
1390
|
init_fs();
|
|
1390
1391
|
init_logger();
|
|
1391
|
-
ROLE_RESOURCE_TYPES = ["knowledge", "skills"];
|
|
1392
|
+
ROLE_RESOURCE_TYPES = ["knowledge", "skills", "agents"];
|
|
1392
1393
|
RoleResourceNamespacesSchema = z2.object({
|
|
1393
1394
|
knowledge: z2.array(z2.string().min(1)),
|
|
1394
1395
|
skills: z2.array(z2.string().min(1)),
|
|
1396
|
+
// Optional: a role without `agents` receives root-level agents only, which
|
|
1397
|
+
// is what every manifest written before this key existed already got.
|
|
1398
|
+
agents: z2.array(z2.string().min(1)).default([]),
|
|
1395
1399
|
// learnings is accepted for backward compatibility but ignored at runtime.
|
|
1396
1400
|
// All learnings are shared flat across the entire team (no namespace isolation).
|
|
1397
1401
|
learnings: z2.array(z2.string()).optional()
|
|
@@ -2626,6 +2630,7 @@ function toZcodeEntry(def) {
|
|
|
2626
2630
|
PostToolUse: 3e4,
|
|
2627
2631
|
UserPromptSubmit: 6e4
|
|
2628
2632
|
};
|
|
2633
|
+
const timeoutMs = def.timeout !== void 0 ? def.timeout * 1e3 : ZCODE_TIMEOUT_MS[def.event] ?? 6e4;
|
|
2629
2634
|
const entry = process.platform === "win32" ? {
|
|
2630
2635
|
// Windows must NOT spawn bare `bash`: CreateProcess resolves it to
|
|
2631
2636
|
// System32's WSL launcher before any PATH directory, and the WSL side
|
|
@@ -2635,7 +2640,7 @@ function toZcodeEntry(def) {
|
|
|
2635
2640
|
type: "process",
|
|
2636
2641
|
command: "cmd",
|
|
2637
2642
|
args: ["/c", def.command],
|
|
2638
|
-
timeoutMs
|
|
2643
|
+
timeoutMs
|
|
2639
2644
|
} : {
|
|
2640
2645
|
type: "process",
|
|
2641
2646
|
command: "bash",
|
|
@@ -2643,7 +2648,7 @@ function toZcodeEntry(def) {
|
|
|
2643
2648
|
// so managed-entry detection and the managed-hooks manifest share one
|
|
2644
2649
|
// command representation (the same invariant the Codex format keeps).
|
|
2645
2650
|
args: ["-lc", def.command],
|
|
2646
|
-
timeoutMs
|
|
2651
|
+
timeoutMs
|
|
2647
2652
|
};
|
|
2648
2653
|
const group = { hooks: [entry] };
|
|
2649
2654
|
if (def.matcher && def.matcher !== "*") group.matcher = def.matcher;
|
|
@@ -8058,20 +8063,73 @@ var init_dashboard_collector = __esm({
|
|
|
8058
8063
|
}
|
|
8059
8064
|
});
|
|
8060
8065
|
|
|
8066
|
+
// src/utils/exec.ts
|
|
8067
|
+
import spawn from "cross-spawn";
|
|
8068
|
+
async function probeBinary(binary, args = ["--version"], executor = execCommand) {
|
|
8069
|
+
try {
|
|
8070
|
+
const result = await executor(binary, args, { timeoutMs: 5e3 });
|
|
8071
|
+
if (result.code !== 0) return "";
|
|
8072
|
+
return (result.stdout || result.stderr).trim();
|
|
8073
|
+
} catch {
|
|
8074
|
+
return "";
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
function formatCommand(command, args) {
|
|
8078
|
+
const quote = (value) => /^[a-zA-Z0-9_./:@=+-]+$/.test(value) ? value : JSON.stringify(value);
|
|
8079
|
+
return [command, ...args.map(quote)].join(" ");
|
|
8080
|
+
}
|
|
8081
|
+
var execCommand;
|
|
8082
|
+
var init_exec = __esm({
|
|
8083
|
+
"src/utils/exec.ts"() {
|
|
8084
|
+
"use strict";
|
|
8085
|
+
execCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
8086
|
+
const child = spawn(command, args, {
|
|
8087
|
+
cwd: options.cwd,
|
|
8088
|
+
env: options.env,
|
|
8089
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
8090
|
+
shell: false
|
|
8091
|
+
});
|
|
8092
|
+
let stdout = "";
|
|
8093
|
+
let stderr = "";
|
|
8094
|
+
let settled = false;
|
|
8095
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
8096
|
+
const finish = (callback) => {
|
|
8097
|
+
if (settled) return;
|
|
8098
|
+
settled = true;
|
|
8099
|
+
clearTimeout(timer);
|
|
8100
|
+
callback();
|
|
8101
|
+
};
|
|
8102
|
+
child.stdout?.on("data", (chunk) => {
|
|
8103
|
+
const text = chunk.toString();
|
|
8104
|
+
stdout += text;
|
|
8105
|
+
if (options.stream) process.stdout.write(text);
|
|
8106
|
+
});
|
|
8107
|
+
child.stderr?.on("data", (chunk) => {
|
|
8108
|
+
const text = chunk.toString();
|
|
8109
|
+
stderr += text;
|
|
8110
|
+
if (options.stream) process.stderr.write(text);
|
|
8111
|
+
});
|
|
8112
|
+
const timer = setTimeout(() => {
|
|
8113
|
+
child.kill();
|
|
8114
|
+
finish(() => reject(new Error(
|
|
8115
|
+
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`
|
|
8116
|
+
)));
|
|
8117
|
+
}, timeoutMs);
|
|
8118
|
+
child.on("error", (error) => finish(() => reject(error)));
|
|
8119
|
+
child.on("close", (code) => finish(() => resolve({
|
|
8120
|
+
stdout,
|
|
8121
|
+
stderr,
|
|
8122
|
+
code: code ?? 1
|
|
8123
|
+
})));
|
|
8124
|
+
});
|
|
8125
|
+
}
|
|
8126
|
+
});
|
|
8127
|
+
|
|
8061
8128
|
// src/agent-version.ts
|
|
8062
|
-
import { execFile } from "child_process";
|
|
8063
8129
|
import { readFile } from "fs/promises";
|
|
8064
8130
|
import path22 from "path";
|
|
8065
8131
|
async function execVersion(bin, args = ["--version"]) {
|
|
8066
|
-
return
|
|
8067
|
-
execFile(bin, args, { timeout: 5e3 }, (err, stdout) => {
|
|
8068
|
-
if (err) {
|
|
8069
|
-
resolve("");
|
|
8070
|
-
return;
|
|
8071
|
-
}
|
|
8072
|
-
resolve(stdout.trim());
|
|
8073
|
-
});
|
|
8074
|
-
});
|
|
8132
|
+
return probeBinary(bin, args);
|
|
8075
8133
|
}
|
|
8076
8134
|
async function readPlistVersion(appPath) {
|
|
8077
8135
|
const plistPath = path22.join(appPath, "Contents", "Info.plist");
|
|
@@ -8145,6 +8203,7 @@ var init_agent_version = __esm({
|
|
|
8145
8203
|
"src/agent-version.ts"() {
|
|
8146
8204
|
"use strict";
|
|
8147
8205
|
init_logger();
|
|
8206
|
+
init_exec();
|
|
8148
8207
|
VERSION_CACHE = /* @__PURE__ */ new Map();
|
|
8149
8208
|
CODEBUDDY_IDE_PATHS = [
|
|
8150
8209
|
"/Applications/CodeBuddy.app",
|
|
@@ -9305,7 +9364,7 @@ __export(local_agent_exports, {
|
|
|
9305
9364
|
import fs14 from "fs";
|
|
9306
9365
|
import os7 from "os";
|
|
9307
9366
|
import path28 from "path";
|
|
9308
|
-
import { execFile
|
|
9367
|
+
import { execFile } from "child_process";
|
|
9309
9368
|
import { promisify } from "util";
|
|
9310
9369
|
import fse4 from "fs-extra";
|
|
9311
9370
|
function modelAgentKind(tool) {
|
|
@@ -11544,7 +11603,7 @@ var init_local_agent = __esm({
|
|
|
11544
11603
|
init_types();
|
|
11545
11604
|
init_home();
|
|
11546
11605
|
init_git2();
|
|
11547
|
-
execFileAsync = promisify(
|
|
11606
|
+
execFileAsync = promisify(execFile);
|
|
11548
11607
|
LOCAL_AGENT_DIR = "local-agent";
|
|
11549
11608
|
CONFIG_FILE = "config.json";
|
|
11550
11609
|
MANIFEST_FILE = "manifest.json";
|
|
@@ -12441,6 +12500,7 @@ var init_skills = __esm({
|
|
|
12441
12500
|
await this.addTombstone(name, localConfig);
|
|
12442
12501
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12443
12502
|
if (!toolPath.skills) continue;
|
|
12503
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12444
12504
|
let skillDir;
|
|
12445
12505
|
if (tool === "openclaw") {
|
|
12446
12506
|
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
@@ -12697,6 +12757,7 @@ var init_rules = __esm({
|
|
|
12697
12757
|
await this.addTombstone(name, localConfig);
|
|
12698
12758
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12699
12759
|
if (!toolPath.rules) continue;
|
|
12760
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12700
12761
|
const extensions = /* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"]);
|
|
12701
12762
|
for (const extension of extensions) {
|
|
12702
12763
|
const filePath = path32.join(baseDir, toolPath.rules, `${name}${extension}`);
|
|
@@ -12738,6 +12799,7 @@ var init_rules = __esm({
|
|
|
12738
12799
|
const baseDir = resolveBaseDir(localConfig);
|
|
12739
12800
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12740
12801
|
if (!toolPath.rules) continue;
|
|
12802
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12741
12803
|
if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
|
|
12742
12804
|
const destDir = path32.join(baseDir, toolPath.rules);
|
|
12743
12805
|
if (!await pathExists(destDir)) continue;
|
|
@@ -12941,9 +13003,9 @@ var init_env = __esm({
|
|
|
12941
13003
|
}
|
|
12942
13004
|
const envYamlPath = path34.join(localConfig.repo.localPath, "env", "env.yaml");
|
|
12943
13005
|
if (!await pathExists(envYamlPath)) return [];
|
|
12944
|
-
const { execFile:
|
|
13006
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
12945
13007
|
const { promisify: promisify4 } = await import("util");
|
|
12946
|
-
const execFileAsync4 = promisify4(
|
|
13008
|
+
const execFileAsync4 = promisify4(execFile4);
|
|
12947
13009
|
try {
|
|
12948
13010
|
await execFileAsync4("git", ["diff", "--exit-code", "env/env.yaml"], {
|
|
12949
13011
|
cwd: localConfig.repo.localPath
|
|
@@ -13111,6 +13173,12 @@ import path35 from "path";
|
|
|
13111
13173
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
13112
13174
|
import matter3 from "gray-matter";
|
|
13113
13175
|
import { stringify as stringifyToml, parse as parseToml } from "smol-toml";
|
|
13176
|
+
function agentStemFromFilename(filename) {
|
|
13177
|
+
for (const ext of AGENT_FILE_EXTENSIONS) {
|
|
13178
|
+
if (filename.endsWith(ext)) return filename.slice(0, -ext.length);
|
|
13179
|
+
}
|
|
13180
|
+
return null;
|
|
13181
|
+
}
|
|
13114
13182
|
function agentFileExtensionForTool(tool) {
|
|
13115
13183
|
switch (tool) {
|
|
13116
13184
|
case "codex":
|
|
@@ -13528,7 +13596,7 @@ function renderForTool(spec, tool) {
|
|
|
13528
13596
|
return renderForOpencode(spec);
|
|
13529
13597
|
}
|
|
13530
13598
|
}
|
|
13531
|
-
var ALL_SUPPORTED_TOOLS, KIRO_SESSION_START_COMMAND, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_FIELDS, COMMON_KIRO_FIELDS, COMMON_OPENCODE_FIELDS, MERGE_COMMON_FIELDS;
|
|
13599
|
+
var ALL_SUPPORTED_TOOLS, AGENT_FILE_EXTENSIONS, KIRO_SESSION_START_COMMAND, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_FIELDS, COMMON_KIRO_FIELDS, COMMON_OPENCODE_FIELDS, MERGE_COMMON_FIELDS;
|
|
13532
13600
|
var init_agent_format = __esm({
|
|
13533
13601
|
"src/resources/agent-format.ts"() {
|
|
13534
13602
|
"use strict";
|
|
@@ -13548,6 +13616,7 @@ var init_agent_format = __esm({
|
|
|
13548
13616
|
"zcode",
|
|
13549
13617
|
"opencode"
|
|
13550
13618
|
];
|
|
13619
|
+
AGENT_FILE_EXTENSIONS = [".md", ".toml", ".json"];
|
|
13551
13620
|
KIRO_SESSION_START_COMMAND = getDispatchCommand("session-start", "kiro");
|
|
13552
13621
|
COMMON_CLAUDE_FIELDS = /* @__PURE__ */ new Set(["name", "description", "model", "tools"]);
|
|
13553
13622
|
COMMON_CURSOR_FIELDS = /* @__PURE__ */ new Set(["agent_id", "description", "model", "tools"]);
|
|
@@ -13585,8 +13654,7 @@ async function removeStaleAgentSiblings(targetAgentsDir, stem, targetExt) {
|
|
|
13585
13654
|
return;
|
|
13586
13655
|
}
|
|
13587
13656
|
for (const file of files) {
|
|
13588
|
-
|
|
13589
|
-
if (base !== stem) continue;
|
|
13657
|
+
if (agentStemFromFilename(file) !== stem) continue;
|
|
13590
13658
|
if (file === `${stem}${targetExt}`) continue;
|
|
13591
13659
|
try {
|
|
13592
13660
|
await remove(path36.join(targetAgentsDir, file));
|
|
@@ -13669,10 +13737,267 @@ var init_builtin_agents = __esm({
|
|
|
13669
13737
|
}
|
|
13670
13738
|
});
|
|
13671
13739
|
|
|
13672
|
-
// src/
|
|
13740
|
+
// src/projects.ts
|
|
13741
|
+
var projects_exports = {};
|
|
13742
|
+
__export(projects_exports, {
|
|
13743
|
+
describeProjects: () => describeProjects,
|
|
13744
|
+
findProject: () => findProject,
|
|
13745
|
+
isSafeNamespaceSegment: () => isSafeNamespaceSegment,
|
|
13746
|
+
listProjectIds: () => listProjectIds,
|
|
13747
|
+
loadProjectsManifest: () => loadProjectsManifest,
|
|
13748
|
+
mergeNamespaces: () => mergeNamespaces,
|
|
13749
|
+
resolveActiveLearningsNamespaces: () => resolveActiveLearningsNamespaces,
|
|
13750
|
+
resolveProjectResourceNamespaces: () => resolveProjectResourceNamespaces,
|
|
13751
|
+
saveProjectsManifest: () => saveProjectsManifest
|
|
13752
|
+
});
|
|
13673
13753
|
import path37 from "path";
|
|
13754
|
+
import YAML8 from "yaml";
|
|
13755
|
+
import { z as z6 } from "zod";
|
|
13756
|
+
function isSafeNamespaceSegment(seg) {
|
|
13757
|
+
return SAFE_ID.test(seg) && seg !== "." && seg !== "..";
|
|
13758
|
+
}
|
|
13759
|
+
function validateManifestShape2(raw) {
|
|
13760
|
+
if (!raw || typeof raw !== "object") {
|
|
13761
|
+
throw new Error("Invalid projects manifest: expected an object");
|
|
13762
|
+
}
|
|
13763
|
+
const candidate = raw;
|
|
13764
|
+
const projects = candidate.projects;
|
|
13765
|
+
if (projects !== void 0 && !Array.isArray(projects)) {
|
|
13766
|
+
throw new Error("Invalid projects manifest: projects must be an array");
|
|
13767
|
+
}
|
|
13768
|
+
for (const project of projects ?? []) {
|
|
13769
|
+
if (!project || typeof project !== "object") {
|
|
13770
|
+
throw new Error("Invalid projects manifest: every project must be an object");
|
|
13771
|
+
}
|
|
13772
|
+
const resources = project.resources;
|
|
13773
|
+
if (resources !== void 0 && (typeof resources !== "object" || Array.isArray(resources))) {
|
|
13774
|
+
throw new Error(`Invalid projects manifest: project ${project.id ?? "<unknown>"} has invalid resources`);
|
|
13775
|
+
}
|
|
13776
|
+
if (resources) {
|
|
13777
|
+
const ALLOWED_RESOURCE_KEYS = new Set(PROJECT_RESOURCE_TYPES);
|
|
13778
|
+
for (const key of Object.keys(resources)) {
|
|
13779
|
+
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
13780
|
+
throw new Error(`Invalid projects manifest: unknown resource type "${key}"`);
|
|
13781
|
+
}
|
|
13782
|
+
}
|
|
13783
|
+
}
|
|
13784
|
+
}
|
|
13785
|
+
const manifest = ProjectsManifestSchema.parse(raw);
|
|
13786
|
+
const ids = /* @__PURE__ */ new Set();
|
|
13787
|
+
for (const project of manifest.projects) {
|
|
13788
|
+
if (ids.has(project.id)) {
|
|
13789
|
+
throw new Error(`Invalid projects manifest: duplicate project id "${project.id}"`);
|
|
13790
|
+
}
|
|
13791
|
+
ids.add(project.id);
|
|
13792
|
+
}
|
|
13793
|
+
return manifest;
|
|
13794
|
+
}
|
|
13795
|
+
async function loadProjectsManifest(repoPath) {
|
|
13796
|
+
const manifestPath = path37.join(repoPath, "manifest", "projects.yaml");
|
|
13797
|
+
const content = await readFileSafe(manifestPath);
|
|
13798
|
+
if (!content) {
|
|
13799
|
+
return null;
|
|
13800
|
+
}
|
|
13801
|
+
let raw;
|
|
13802
|
+
try {
|
|
13803
|
+
raw = YAML8.parse(content);
|
|
13804
|
+
} catch (error) {
|
|
13805
|
+
throw new Error(`Invalid projects manifest YAML: ${error.message}`);
|
|
13806
|
+
}
|
|
13807
|
+
return validateManifestShape2(raw);
|
|
13808
|
+
}
|
|
13809
|
+
async function saveProjectsManifest(repoPath, manifest) {
|
|
13810
|
+
validateManifestShape2(manifest);
|
|
13811
|
+
const manifestDir = path37.join(repoPath, "manifest");
|
|
13812
|
+
const manifestPath = path37.join(manifestDir, "projects.yaml");
|
|
13813
|
+
await ensureDir(manifestDir);
|
|
13814
|
+
await writeFile(manifestPath, YAML8.stringify(manifest));
|
|
13815
|
+
}
|
|
13816
|
+
function findProject(manifest, projectId) {
|
|
13817
|
+
return manifest.projects.find((candidate) => candidate.id === projectId);
|
|
13818
|
+
}
|
|
13819
|
+
function listProjectIds(manifest) {
|
|
13820
|
+
return manifest.projects.map((project) => project.id);
|
|
13821
|
+
}
|
|
13822
|
+
function describeProjects(projects) {
|
|
13823
|
+
return projects.map((project) => {
|
|
13824
|
+
const label = project.name || project.id;
|
|
13825
|
+
return project.description ? `${label}: ${project.description}` : label;
|
|
13826
|
+
});
|
|
13827
|
+
}
|
|
13828
|
+
function getProjectOrThrow(manifest, projectId) {
|
|
13829
|
+
const project = findProject(manifest, projectId);
|
|
13830
|
+
if (!project) {
|
|
13831
|
+
throw new Error(`Unknown project "${projectId}". Valid projects: ${listProjectIds(manifest).join(", ")}`);
|
|
13832
|
+
}
|
|
13833
|
+
return project;
|
|
13834
|
+
}
|
|
13835
|
+
function resolveProjectResourceNamespaces(input) {
|
|
13836
|
+
const resolved = input.activeProjects.map((id) => getProjectOrThrow(input.manifest, id));
|
|
13837
|
+
const namespaces = {
|
|
13838
|
+
knowledge: [],
|
|
13839
|
+
skills: [],
|
|
13840
|
+
learnings: [],
|
|
13841
|
+
agents: []
|
|
13842
|
+
};
|
|
13843
|
+
for (const type of PROJECT_RESOURCE_TYPES) {
|
|
13844
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13845
|
+
for (const project of resolved) {
|
|
13846
|
+
for (const namespace of project.resources[type]) {
|
|
13847
|
+
if (seen.has(namespace)) continue;
|
|
13848
|
+
seen.add(namespace);
|
|
13849
|
+
namespaces[type].push(namespace);
|
|
13850
|
+
}
|
|
13851
|
+
}
|
|
13852
|
+
}
|
|
13853
|
+
return namespaces;
|
|
13854
|
+
}
|
|
13855
|
+
async function resolveActiveLearningsNamespaces(repoPath, activeProjects) {
|
|
13856
|
+
if (activeProjects.length === 0) return [];
|
|
13857
|
+
const manifest = await loadProjectsManifest(repoPath);
|
|
13858
|
+
if (!manifest) return [];
|
|
13859
|
+
try {
|
|
13860
|
+
return resolveProjectResourceNamespaces({ manifest, activeProjects }).learnings;
|
|
13861
|
+
} catch {
|
|
13862
|
+
return [];
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
function mergeNamespaces(roleNamespaces, projectNamespaces) {
|
|
13866
|
+
const dedupe = (a, b) => {
|
|
13867
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13868
|
+
const out = [];
|
|
13869
|
+
for (const ns of [...a, ...b]) {
|
|
13870
|
+
if (seen.has(ns)) continue;
|
|
13871
|
+
seen.add(ns);
|
|
13872
|
+
out.push(ns);
|
|
13873
|
+
}
|
|
13874
|
+
return out;
|
|
13875
|
+
};
|
|
13876
|
+
return {
|
|
13877
|
+
knowledge: dedupe(roleNamespaces.knowledge, projectNamespaces.knowledge),
|
|
13878
|
+
skills: dedupe(roleNamespaces.skills, projectNamespaces.skills),
|
|
13879
|
+
// Roles never contribute learnings; this is effectively the project set.
|
|
13880
|
+
learnings: dedupe(roleNamespaces.learnings, projectNamespaces.learnings),
|
|
13881
|
+
agents: dedupe(roleNamespaces.agents, projectNamespaces.agents)
|
|
13882
|
+
};
|
|
13883
|
+
}
|
|
13884
|
+
var PROJECT_RESOURCE_TYPES, ProjectResourceNamespacesSchema, SAFE_ID, ProjectSchema, ProjectsManifestSchema;
|
|
13885
|
+
var init_projects = __esm({
|
|
13886
|
+
"src/projects.ts"() {
|
|
13887
|
+
"use strict";
|
|
13888
|
+
init_fs();
|
|
13889
|
+
PROJECT_RESOURCE_TYPES = ["knowledge", "skills", "learnings", "agents"];
|
|
13890
|
+
ProjectResourceNamespacesSchema = z6.object({
|
|
13891
|
+
knowledge: z6.array(z6.string().min(1)).default([]),
|
|
13892
|
+
skills: z6.array(z6.string().min(1)).default([]),
|
|
13893
|
+
learnings: z6.array(z6.string().min(1)).default([]),
|
|
13894
|
+
agents: z6.array(z6.string().min(1)).default([])
|
|
13895
|
+
});
|
|
13896
|
+
SAFE_ID = /^[A-Za-z0-9._-]+$/;
|
|
13897
|
+
ProjectSchema = z6.object({
|
|
13898
|
+
id: z6.string().min(1).refine((v) => isSafeNamespaceSegment(v), {
|
|
13899
|
+
message: "project id must be a single path segment (letters, digits, '.', '_', '-'; no '/', '\\\\', or '..')"
|
|
13900
|
+
}),
|
|
13901
|
+
name: z6.string().default(""),
|
|
13902
|
+
description: z6.string().default(""),
|
|
13903
|
+
resources: ProjectResourceNamespacesSchema
|
|
13904
|
+
});
|
|
13905
|
+
ProjectsManifestSchema = z6.object({
|
|
13906
|
+
version: z6.number(),
|
|
13907
|
+
// Unlike roles (`.min(1)`), a repo may define zero projects — a team without
|
|
13908
|
+
// project partitioning simply has no projects.yaml, and an empty list is valid.
|
|
13909
|
+
projects: z6.array(ProjectSchema).default([])
|
|
13910
|
+
});
|
|
13911
|
+
}
|
|
13912
|
+
});
|
|
13913
|
+
|
|
13914
|
+
// src/resource-namespaces.ts
|
|
13915
|
+
async function resolveResourceNamespaces(localConfig) {
|
|
13916
|
+
const activeProjects = localConfig.projects ?? [];
|
|
13917
|
+
const primaryRole = localConfig.primaryRole;
|
|
13918
|
+
const hasRole = !!primaryRole;
|
|
13919
|
+
const hasProjects = activeProjects.length > 0;
|
|
13920
|
+
const projectsManifest = await loadProjectsManifest(localConfig.repo.localPath);
|
|
13921
|
+
const teamHasProjects = !!projectsManifest && projectsManifest.projects.length > 0;
|
|
13922
|
+
if (!hasRole && !hasProjects && !teamHasProjects) return null;
|
|
13923
|
+
let roleNamespaces = { knowledge: [], skills: [], learnings: [], agents: [] };
|
|
13924
|
+
let allRoleSkillNamespaces = /* @__PURE__ */ new Set();
|
|
13925
|
+
if (primaryRole) {
|
|
13926
|
+
let rolesManifest;
|
|
13927
|
+
try {
|
|
13928
|
+
rolesManifest = await loadRolesManifest(localConfig.repo.localPath);
|
|
13929
|
+
} catch {
|
|
13930
|
+
log.warn("Could not load roles manifest. Skipping role-based filtering.");
|
|
13931
|
+
rolesManifest = null;
|
|
13932
|
+
}
|
|
13933
|
+
if (rolesManifest) {
|
|
13934
|
+
try {
|
|
13935
|
+
roleNamespaces = resolveRoleResourceNamespaces({
|
|
13936
|
+
manifest: rolesManifest,
|
|
13937
|
+
primaryRole,
|
|
13938
|
+
additionalRoles: localConfig.additionalRoles ?? []
|
|
13939
|
+
});
|
|
13940
|
+
allRoleSkillNamespaces = new Set(rolesManifest.roles.flatMap((role) => role.resources.skills));
|
|
13941
|
+
} catch {
|
|
13942
|
+
log.warn(`Role "${localConfig.primaryRole}" not found in manifest. Falling back to unfiltered sync.`);
|
|
13943
|
+
log.warn("Run `teamai roles set <role>` to pick a valid role.");
|
|
13944
|
+
if (!hasProjects && !teamHasProjects) return null;
|
|
13945
|
+
}
|
|
13946
|
+
} else if (!hasProjects && !teamHasProjects) {
|
|
13947
|
+
return null;
|
|
13948
|
+
}
|
|
13949
|
+
}
|
|
13950
|
+
let projectNamespaces = { knowledge: [], skills: [], learnings: [], agents: [] };
|
|
13951
|
+
let allProjectSkillNamespaces = /* @__PURE__ */ new Set();
|
|
13952
|
+
if (projectsManifest) {
|
|
13953
|
+
allProjectSkillNamespaces = new Set(projectsManifest.projects.flatMap((p) => p.resources.skills));
|
|
13954
|
+
if (hasProjects) {
|
|
13955
|
+
try {
|
|
13956
|
+
projectNamespaces = resolveProjectResourceNamespaces({
|
|
13957
|
+
manifest: projectsManifest,
|
|
13958
|
+
activeProjects
|
|
13959
|
+
});
|
|
13960
|
+
} catch (e) {
|
|
13961
|
+
log.warn(`${e instanceof Error ? e.message : String(e)} Falling back to role-only filtering.`);
|
|
13962
|
+
}
|
|
13963
|
+
}
|
|
13964
|
+
} else if (hasProjects) {
|
|
13965
|
+
log.warn("Active projects configured but no projects manifest found. Skipping project-based filtering.");
|
|
13966
|
+
}
|
|
13967
|
+
const activeNamespaces = mergeNamespaces(roleNamespaces, projectNamespaces);
|
|
13968
|
+
const allSkillNamespaces = /* @__PURE__ */ new Set([...allRoleSkillNamespaces, ...allProjectSkillNamespaces]);
|
|
13969
|
+
return { activeNamespaces, allSkillNamespaces };
|
|
13970
|
+
}
|
|
13971
|
+
var init_resource_namespaces = __esm({
|
|
13972
|
+
"src/resource-namespaces.ts"() {
|
|
13973
|
+
"use strict";
|
|
13974
|
+
init_roles();
|
|
13975
|
+
init_projects();
|
|
13976
|
+
init_logger();
|
|
13977
|
+
}
|
|
13978
|
+
});
|
|
13979
|
+
|
|
13980
|
+
// src/resources/agents.ts
|
|
13981
|
+
import path38 from "path";
|
|
13674
13982
|
import { isDeepStrictEqual } from "util";
|
|
13675
13983
|
import { parse as parseYaml2 } from "yaml";
|
|
13984
|
+
async function listTeamAgentDirs(teamAgentsDir) {
|
|
13985
|
+
const dirs = [{ dir: teamAgentsDir }];
|
|
13986
|
+
for (const namespace of await listDirs(teamAgentsDir)) {
|
|
13987
|
+
if (isSafeNamespaceSegment(namespace)) dirs.push({ dir: path38.join(teamAgentsDir, namespace), namespace });
|
|
13988
|
+
}
|
|
13989
|
+
return dirs;
|
|
13990
|
+
}
|
|
13991
|
+
async function findTeamAgentFiles(teamAgentsDir, stem) {
|
|
13992
|
+
const found = [];
|
|
13993
|
+
for (const { dir, namespace } of await listTeamAgentDirs(teamAgentsDir)) {
|
|
13994
|
+
for (const ext of [".yaml", ".md"]) {
|
|
13995
|
+
const candidate = path38.join(dir, `${stem}${ext}`);
|
|
13996
|
+
if (await pathExists(candidate)) found.push({ path: candidate, ext, ...namespace ? { namespace } : {} });
|
|
13997
|
+
}
|
|
13998
|
+
}
|
|
13999
|
+
return found;
|
|
14000
|
+
}
|
|
13676
14001
|
function mergeCanonicalEdits(canonical, perTool) {
|
|
13677
14002
|
const merged = { ...canonical };
|
|
13678
14003
|
const extras = { ...canonical.tool_extras };
|
|
@@ -13714,16 +14039,10 @@ function mergeCanonicalEdits(canonical, perTool) {
|
|
|
13714
14039
|
else delete merged.tool_extras;
|
|
13715
14040
|
return { ok: true, spec: merged };
|
|
13716
14041
|
}
|
|
13717
|
-
function getAgentStem(filename) {
|
|
13718
|
-
if (filename.endsWith(".md")) return filename.slice(0, -3);
|
|
13719
|
-
if (filename.endsWith(".toml")) return filename.slice(0, -5);
|
|
13720
|
-
if (filename.endsWith(".json")) return filename.slice(0, -5);
|
|
13721
|
-
return null;
|
|
13722
|
-
}
|
|
13723
14042
|
async function removeStaleAgentSiblings2(agentsDir, stem, targetExt) {
|
|
13724
14043
|
for (const file of await listFiles(agentsDir)) {
|
|
13725
|
-
if (
|
|
13726
|
-
await remove(
|
|
14044
|
+
if (agentStemFromFilename(file) !== stem || file === `${stem}${targetExt}`) continue;
|
|
14045
|
+
await remove(path38.join(agentsDir, file));
|
|
13727
14046
|
log.debug(`Removed stale agent sibling ${file} for ${stem}`);
|
|
13728
14047
|
}
|
|
13729
14048
|
}
|
|
@@ -13737,7 +14056,7 @@ async function agentContentEqual(tool, localPath, teamPath) {
|
|
|
13737
14056
|
const canonicalContent = await readFileSafe(teamPath);
|
|
13738
14057
|
const localContent = await readFileSafe(localPath);
|
|
13739
14058
|
if (canonicalContent === null || localContent === null) return false;
|
|
13740
|
-
const parsed = parseAgentYaml(canonicalContent,
|
|
14059
|
+
const parsed = parseAgentYaml(canonicalContent, path38.basename(teamPath));
|
|
13741
14060
|
if (!parsed.ok) return false;
|
|
13742
14061
|
return localContent === renderForTool(parsed.spec, tool).content;
|
|
13743
14062
|
}
|
|
@@ -13767,7 +14086,7 @@ function reverseByTool(tool, filePath, content) {
|
|
|
13767
14086
|
return reverseFromOpencode(filePath, content);
|
|
13768
14087
|
}
|
|
13769
14088
|
}
|
|
13770
|
-
var AgentsHandler;
|
|
14089
|
+
var AgentsHandler, LEGACY_MD_TOOLS;
|
|
13771
14090
|
var init_agents = __esm({
|
|
13772
14091
|
"src/resources/agents.ts"() {
|
|
13773
14092
|
"use strict";
|
|
@@ -13776,6 +14095,9 @@ var init_agents = __esm({
|
|
|
13776
14095
|
init_logger();
|
|
13777
14096
|
init_types();
|
|
13778
14097
|
init_builtin_agents();
|
|
14098
|
+
init_resource_namespaces();
|
|
14099
|
+
init_projects();
|
|
14100
|
+
init_path_safety();
|
|
13779
14101
|
init_agent_format();
|
|
13780
14102
|
AgentsHandler = class extends ResourceHandler {
|
|
13781
14103
|
type = "agents";
|
|
@@ -13787,50 +14109,53 @@ var init_agents = __esm({
|
|
|
13787
14109
|
* Built-in CLI agents are excluded from push.
|
|
13788
14110
|
*/
|
|
13789
14111
|
async scanLocalForPush(teamConfig, localConfig) {
|
|
13790
|
-
const teamAgentsDir =
|
|
14112
|
+
const teamAgentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
13791
14113
|
const tombstones = await this.readTombstones(localConfig);
|
|
13792
14114
|
const baseDir = resolveBaseDir(localConfig);
|
|
13793
14115
|
const directItems = [];
|
|
13794
14116
|
const directStems = /* @__PURE__ */ new Set();
|
|
13795
14117
|
if (isSelfMode(localConfig) && localConfig.projectRoot) {
|
|
13796
|
-
const activeAgentsDir =
|
|
14118
|
+
const activeAgentsDir = path38.join(localConfig.projectRoot, ".teamai", "agents");
|
|
13797
14119
|
if (await pathExists(activeAgentsDir)) {
|
|
13798
|
-
for (const
|
|
13799
|
-
const
|
|
13800
|
-
const
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
14120
|
+
for (const { dir, namespace } of await listTeamAgentDirs(activeAgentsDir)) {
|
|
14121
|
+
const relDir = namespace ? `agents/${namespace}` : "agents";
|
|
14122
|
+
for (const file of await listFiles(dir)) {
|
|
14123
|
+
const isYaml = file.endsWith(".yaml");
|
|
14124
|
+
const isMd = file.endsWith(".md");
|
|
14125
|
+
if (!isYaml && !isMd) continue;
|
|
14126
|
+
const stem = file.replace(/\.(yaml|md)$/, "");
|
|
14127
|
+
if (tombstones.has(stem)) continue;
|
|
14128
|
+
if (BUILTIN_AGENT_NAMES.has(stem)) continue;
|
|
14129
|
+
const activePath = path38.join(dir, file);
|
|
14130
|
+
const basePath = path38.join(localConfig.repo.localPath, relDir, file);
|
|
14131
|
+
const baseExists = await pathExists(basePath);
|
|
14132
|
+
if (baseExists && await fileContentEqual(activePath, basePath)) continue;
|
|
14133
|
+
directItems.push({
|
|
14134
|
+
name: stem,
|
|
14135
|
+
type: "agents",
|
|
14136
|
+
sourcePath: activePath,
|
|
14137
|
+
relativePath: `${relDir}/${file}`,
|
|
14138
|
+
status: baseExists ? "modified" : "new",
|
|
14139
|
+
legacy: isMd
|
|
14140
|
+
});
|
|
14141
|
+
directStems.add(stem);
|
|
14142
|
+
}
|
|
13818
14143
|
}
|
|
13819
14144
|
}
|
|
13820
14145
|
}
|
|
13821
14146
|
const grouped = /* @__PURE__ */ new Map();
|
|
13822
14147
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13823
14148
|
if (!toolPath.agents) continue;
|
|
13824
|
-
const agentsDir =
|
|
14149
|
+
const agentsDir = path38.join(baseDir, toolPath.agents);
|
|
13825
14150
|
if (!await pathExists(agentsDir)) continue;
|
|
13826
14151
|
const files = await listFiles(agentsDir);
|
|
13827
14152
|
for (const file of files) {
|
|
13828
|
-
const stem =
|
|
14153
|
+
const stem = agentStemFromFilename(file);
|
|
13829
14154
|
if (stem === null) continue;
|
|
13830
14155
|
if (tombstones.has(stem)) continue;
|
|
13831
14156
|
if (BUILTIN_AGENT_NAMES.has(stem)) continue;
|
|
13832
14157
|
if (directStems.has(stem)) continue;
|
|
13833
|
-
const filePath =
|
|
14158
|
+
const filePath = path38.join(agentsDir, file);
|
|
13834
14159
|
let toolGroup = grouped.get(stem);
|
|
13835
14160
|
if (!toolGroup) {
|
|
13836
14161
|
toolGroup = /* @__PURE__ */ new Map();
|
|
@@ -13842,11 +14167,41 @@ var init_agents = __esm({
|
|
|
13842
14167
|
}
|
|
13843
14168
|
}
|
|
13844
14169
|
const items = [...directItems];
|
|
14170
|
+
const resolved = await resolveResourceNamespaces(localConfig);
|
|
14171
|
+
const activeNamespaces = resolved?.activeNamespaces.agents ?? null;
|
|
13845
14172
|
for (const [stem, toolFiles] of grouped) {
|
|
13846
|
-
const
|
|
13847
|
-
const
|
|
13848
|
-
|
|
13849
|
-
|
|
14173
|
+
const sources = await findTeamAgentFiles(teamAgentsDir, stem);
|
|
14174
|
+
const candidates = sources.filter(
|
|
14175
|
+
(file) => activeNamespaces === null || !file.namespace || activeNamespaces.includes(file.namespace)
|
|
14176
|
+
);
|
|
14177
|
+
if (candidates.length > 1) {
|
|
14178
|
+
items.push({
|
|
14179
|
+
name: stem,
|
|
14180
|
+
type: "agents",
|
|
14181
|
+
sourcePath: teamAgentsDir,
|
|
14182
|
+
relativePath: `agents/${stem}.yaml`,
|
|
14183
|
+
status: "modified",
|
|
14184
|
+
skipReason: `Ambiguous agent "${stem}": multiple active sources (${candidates.map((file) => file.path).join(", ")}). Give active agents unique names before pushing.`
|
|
14185
|
+
});
|
|
14186
|
+
continue;
|
|
14187
|
+
}
|
|
14188
|
+
if (sources.length > 0 && candidates.length === 0) {
|
|
14189
|
+
items.push({
|
|
14190
|
+
name: stem,
|
|
14191
|
+
type: "agents",
|
|
14192
|
+
sourcePath: teamAgentsDir,
|
|
14193
|
+
relativePath: `agents/${stem}.yaml`,
|
|
14194
|
+
status: "modified",
|
|
14195
|
+
skipReason: `Agent "${stem}" has no active source. Activate its role or project before pushing local edits.`
|
|
14196
|
+
});
|
|
14197
|
+
continue;
|
|
14198
|
+
}
|
|
14199
|
+
const located = candidates[0];
|
|
14200
|
+
const teamYamlPath = located?.ext === ".yaml" ? located.path : path38.join(teamAgentsDir, `${stem}.yaml`);
|
|
14201
|
+
const teamMdPath = located?.ext === ".md" ? located.path : path38.join(teamAgentsDir, `${stem}.md`);
|
|
14202
|
+
const hasTeamYaml = located?.ext === ".yaml";
|
|
14203
|
+
const hasTeamMd = located?.ext === ".md";
|
|
14204
|
+
const teamDir = located?.namespace ? `agents/${located.namespace}` : "agents";
|
|
13850
14205
|
let canonicalSpec;
|
|
13851
14206
|
if (hasTeamYaml) {
|
|
13852
14207
|
const raw = await readFileSafe(teamYamlPath);
|
|
@@ -13856,7 +14211,7 @@ var init_agents = __esm({
|
|
|
13856
14211
|
name: stem,
|
|
13857
14212
|
type: "agents",
|
|
13858
14213
|
sourcePath: teamYamlPath,
|
|
13859
|
-
relativePath:
|
|
14214
|
+
relativePath: `${teamDir}/${stem}.yaml`,
|
|
13860
14215
|
status: "modified",
|
|
13861
14216
|
skipReason: "cannot read or parse canonical agent YAML"
|
|
13862
14217
|
});
|
|
@@ -13934,7 +14289,7 @@ var init_agents = __esm({
|
|
|
13934
14289
|
name: stem,
|
|
13935
14290
|
type: "agents",
|
|
13936
14291
|
sourcePath: bestPath,
|
|
13937
|
-
relativePath:
|
|
14292
|
+
relativePath: `${teamDir}/${stem}.yaml`,
|
|
13938
14293
|
status: status2,
|
|
13939
14294
|
mergedSpec: mergeResult.spec
|
|
13940
14295
|
});
|
|
@@ -13945,7 +14300,7 @@ var init_agents = __esm({
|
|
|
13945
14300
|
name: stem,
|
|
13946
14301
|
type: "agents",
|
|
13947
14302
|
sourcePath: bestPath,
|
|
13948
|
-
relativePath:
|
|
14303
|
+
relativePath: `${teamDir}/${stem}.md`,
|
|
13949
14304
|
status: status2,
|
|
13950
14305
|
skipReason
|
|
13951
14306
|
});
|
|
@@ -13956,32 +14311,33 @@ var init_agents = __esm({
|
|
|
13956
14311
|
* Scan team repo `agents/` for files to pull.
|
|
13957
14312
|
* Recognizes both *.yaml (new) and *.md (legacy).
|
|
13958
14313
|
* Hidden files (tombstones) are filtered out by listFiles.
|
|
14314
|
+
*
|
|
14315
|
+
* Root-level files are shared with everyone. One level of subdirectories
|
|
14316
|
+
* (`agents/<namespace>/`) carries role/project-scoped agents, the same
|
|
14317
|
+
* convention `rules/<namespace>/` uses; pull filters them by the active
|
|
14318
|
+
* `agents` namespaces. Deeper nesting is not scanned.
|
|
13959
14319
|
*/
|
|
13960
14320
|
async scanTeamForPull(_teamConfig, localConfig) {
|
|
13961
|
-
const agentsDir =
|
|
14321
|
+
const agentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
13962
14322
|
if (!await pathExists(agentsDir)) return [];
|
|
13963
|
-
const files = await listFiles(agentsDir);
|
|
13964
14323
|
const items = [];
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14324
|
+
const scanDir = async (dir, namespace) => {
|
|
14325
|
+
const prefix = namespace ? `agents/${namespace}` : "agents";
|
|
14326
|
+
for (const file of await listFiles(dir)) {
|
|
14327
|
+
const legacy = file.endsWith(".md");
|
|
14328
|
+
if (!legacy && !file.endsWith(".yaml")) continue;
|
|
13968
14329
|
items.push({
|
|
13969
|
-
name:
|
|
14330
|
+
name: file.replace(/\.(yaml|md)$/, ""),
|
|
13970
14331
|
type: "agents",
|
|
13971
|
-
sourcePath:
|
|
13972
|
-
relativePath:
|
|
13973
|
-
legacy
|
|
13974
|
-
|
|
13975
|
-
} else if (file.endsWith(".md")) {
|
|
13976
|
-
const stem = file.replace(/\.md$/, "");
|
|
13977
|
-
items.push({
|
|
13978
|
-
name: stem,
|
|
13979
|
-
type: "agents",
|
|
13980
|
-
sourcePath: path37.join(agentsDir, file),
|
|
13981
|
-
relativePath: `agents/${file}`,
|
|
13982
|
-
legacy: true
|
|
14332
|
+
sourcePath: path38.join(dir, file),
|
|
14333
|
+
relativePath: `${prefix}/${file}`,
|
|
14334
|
+
legacy,
|
|
14335
|
+
...namespace ? { namespace } : {}
|
|
13983
14336
|
});
|
|
13984
14337
|
}
|
|
14338
|
+
};
|
|
14339
|
+
for (const { dir, namespace } of await listTeamAgentDirs(agentsDir)) {
|
|
14340
|
+
await scanDir(dir, namespace);
|
|
13985
14341
|
}
|
|
13986
14342
|
return items;
|
|
13987
14343
|
}
|
|
@@ -13994,22 +14350,27 @@ var init_agents = __esm({
|
|
|
13994
14350
|
async pushItem(item, _teamConfig, localConfig) {
|
|
13995
14351
|
const agentItem = item;
|
|
13996
14352
|
if (agentItem.skipReason) {
|
|
13997
|
-
log.warn(`[agents]
|
|
13998
|
-
log.warn(" \u5EFA\u8BAE\u4FEE\u6539\u540E\u91CD\u65B0 push \u8BE5 subagent");
|
|
14353
|
+
log.warn(`[agents] Skipped ${item.name}: ${agentItem.skipReason}`);
|
|
13999
14354
|
return;
|
|
14000
14355
|
}
|
|
14356
|
+
const teamDir = path38.resolve(localConfig.repo.localPath, path38.dirname(item.relativePath));
|
|
14357
|
+
assertWithinRoot(
|
|
14358
|
+
path38.join(localConfig.repo.localPath, "agents"),
|
|
14359
|
+
teamDir,
|
|
14360
|
+
`Invalid agent destination outside team repo agents directory: ${item.relativePath}`
|
|
14361
|
+
);
|
|
14001
14362
|
if (agentItem.mergedSpec) {
|
|
14002
|
-
const dest2 =
|
|
14003
|
-
await ensureDir(
|
|
14363
|
+
const dest2 = path38.join(teamDir, `${item.name}.yaml`);
|
|
14364
|
+
await ensureDir(path38.dirname(dest2));
|
|
14004
14365
|
const yamlContent = serializeAgentYaml(agentItem.mergedSpec);
|
|
14005
14366
|
await writeFile(dest2, yamlContent);
|
|
14006
14367
|
log.debug(`Wrote agent ${item.name} \u2192 team repo (YAML format)`);
|
|
14007
14368
|
return;
|
|
14008
14369
|
}
|
|
14009
14370
|
const ext = item.sourcePath.endsWith(".yaml") ? ".yaml" : ".md";
|
|
14010
|
-
const dest =
|
|
14371
|
+
const dest = path38.join(teamDir, `${item.name}${ext}`);
|
|
14011
14372
|
if (item.sourcePath !== dest) {
|
|
14012
|
-
await ensureDir(
|
|
14373
|
+
await ensureDir(path38.dirname(dest));
|
|
14013
14374
|
await copyFile(item.sourcePath, dest);
|
|
14014
14375
|
}
|
|
14015
14376
|
log.debug(`Copied agent ${item.name} \u2192 team repo (${ext} verbatim)`);
|
|
@@ -14053,12 +14414,12 @@ var init_agents = __esm({
|
|
|
14053
14414
|
continue;
|
|
14054
14415
|
}
|
|
14055
14416
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14056
|
-
const destDir =
|
|
14417
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
14057
14418
|
try {
|
|
14058
14419
|
await ensureDir(destDir);
|
|
14059
14420
|
const { ext, content: rendered } = renderForTool(spec, tool);
|
|
14060
14421
|
await removeStaleAgentSiblings2(destDir, item.name, ext);
|
|
14061
|
-
const dest =
|
|
14422
|
+
const dest = path38.join(destDir, `${item.name}${ext}`);
|
|
14062
14423
|
await writeFile(dest, rendered);
|
|
14063
14424
|
log.debug(`Rendered agent ${item.name} \u2192 ${tool} (${ext})`);
|
|
14064
14425
|
} catch (e) {
|
|
@@ -14074,19 +14435,17 @@ var init_agents = __esm({
|
|
|
14074
14435
|
async removeItem(name, teamConfig, localConfig) {
|
|
14075
14436
|
const removed = [];
|
|
14076
14437
|
const baseDir = resolveBaseDir(localConfig);
|
|
14077
|
-
const teamAgentsDir =
|
|
14078
|
-
for (const
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
await remove(teamFile);
|
|
14082
|
-
removed.push(teamFile);
|
|
14083
|
-
}
|
|
14438
|
+
const teamAgentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
14439
|
+
for (const located of await findTeamAgentFiles(teamAgentsDir, name)) {
|
|
14440
|
+
await remove(located.path);
|
|
14441
|
+
removed.push(located.path);
|
|
14084
14442
|
}
|
|
14085
14443
|
await this.addTombstone(name, localConfig);
|
|
14086
14444
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
14087
14445
|
if (!toolPath.agents) continue;
|
|
14088
|
-
|
|
14089
|
-
|
|
14446
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14447
|
+
for (const ext of AGENT_FILE_EXTENSIONS) {
|
|
14448
|
+
const filePath = path38.join(baseDir, toolPath.agents, `${name}${ext}`);
|
|
14090
14449
|
if (await pathExists(filePath)) {
|
|
14091
14450
|
await remove(filePath);
|
|
14092
14451
|
removed.push(filePath);
|
|
@@ -14096,14 +14455,69 @@ var init_agents = __esm({
|
|
|
14096
14455
|
}
|
|
14097
14456
|
return removed;
|
|
14098
14457
|
}
|
|
14458
|
+
/**
|
|
14459
|
+
* Revocation pass for role/project scoping. Removes the deployed copies of
|
|
14460
|
+
* every agent whose namespace is no longer active, on every installed tool.
|
|
14461
|
+
*
|
|
14462
|
+
* Data-safety gate, same as inactive skills: a file is deleted only when it
|
|
14463
|
+
* is byte-equal to what pull would render from the team source. A local edit
|
|
14464
|
+
* is kept and reported so nothing unpushed is lost. Root-level agents and
|
|
14465
|
+
* agents still deployed to the same tool destination never qualify.
|
|
14466
|
+
*/
|
|
14467
|
+
async cleanupInactiveNamespaces(teamConfig, localConfig, activeNamespaces) {
|
|
14468
|
+
const items = await this.scanTeamForPull(teamConfig, localConfig);
|
|
14469
|
+
const isActive = (item) => !item.namespace || activeNamespaces.includes(item.namespace);
|
|
14470
|
+
const active = items.filter(isActive);
|
|
14471
|
+
const inactive = items.filter((item) => !isActive(item) && !BUILTIN_AGENT_NAMES.has(item.name));
|
|
14472
|
+
if (inactive.length === 0) return;
|
|
14473
|
+
const baseDir = resolveBaseDir(localConfig);
|
|
14474
|
+
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
14475
|
+
if (!toolPath.agents || !isKnownTool(tool) || isAgentExcluded(localConfig, tool)) continue;
|
|
14476
|
+
if (!await ResourceHandler.isToolInstalled(toolPath.agents, baseDir)) continue;
|
|
14477
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
14478
|
+
const activeDestinations = /* @__PURE__ */ new Set();
|
|
14479
|
+
for (const item of active) {
|
|
14480
|
+
const rendered = await this.renderedForTool(item, tool);
|
|
14481
|
+
if (rendered) activeDestinations.add(`${item.name}${rendered.ext}`);
|
|
14482
|
+
}
|
|
14483
|
+
for (const item of inactive) {
|
|
14484
|
+
const expected = await this.renderedForTool(item, tool);
|
|
14485
|
+
if (!expected || activeDestinations.has(`${item.name}${expected.ext}`)) continue;
|
|
14486
|
+
const deployed = path38.join(destDir, `${item.name}${expected.ext}`);
|
|
14487
|
+
const current = await readFileSafe(deployed);
|
|
14488
|
+
if (current === null) continue;
|
|
14489
|
+
if (current !== expected.content) {
|
|
14490
|
+
log.warn(`[${localConfig.scope}] Kept agent "${item.name}" (${tool}): it differs from the team source ${item.relativePath}. Back it up, then delete it manually.`);
|
|
14491
|
+
continue;
|
|
14492
|
+
}
|
|
14493
|
+
await remove(deployed);
|
|
14494
|
+
log.debug(`[${localConfig.scope}] Removed inactive role-scoped agent ${item.name} from ${tool}`);
|
|
14495
|
+
}
|
|
14496
|
+
}
|
|
14497
|
+
}
|
|
14498
|
+
/**
|
|
14499
|
+
* What `pullItem` writes for this agent on this tool, or null when the tool
|
|
14500
|
+
* is not a target (legacy `.md` only reaches LEGACY_MD_TOOLS, a YAML spec
|
|
14501
|
+
* honours `targets`, an unparsable spec is skipped like pull skips it).
|
|
14502
|
+
*/
|
|
14503
|
+
async renderedForTool(item, tool) {
|
|
14504
|
+
const content = await readFileSafe(item.sourcePath);
|
|
14505
|
+
if (content === null) return null;
|
|
14506
|
+
if (item.legacy) {
|
|
14507
|
+
return LEGACY_MD_TOOLS.has(tool) ? { ext: ".md", content } : null;
|
|
14508
|
+
}
|
|
14509
|
+
const parsed = parseAgentYaml(content, `${item.name}.yaml`);
|
|
14510
|
+
if (!parsed.ok) return null;
|
|
14511
|
+
if (parsed.spec.targets && !parsed.spec.targets.includes(tool)) return null;
|
|
14512
|
+
return renderForTool(parsed.spec, tool);
|
|
14513
|
+
}
|
|
14099
14514
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
14100
14515
|
/**
|
|
14101
14516
|
* Legacy pull: copies .md as-is to Claude-compatible tools, including JoyCode.
|
|
14102
14517
|
*/
|
|
14103
14518
|
async pullLegacyMd(item, teamConfig, baseDir, localConfig) {
|
|
14104
|
-
const legacyTools = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
14105
14519
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
14106
|
-
if (!
|
|
14520
|
+
if (!LEGACY_MD_TOOLS.has(tool)) continue;
|
|
14107
14521
|
if (!toolPath.agents) {
|
|
14108
14522
|
log.debug(`Skipping legacy agent sync for ${tool}: no agents path configured`);
|
|
14109
14523
|
continue;
|
|
@@ -14113,10 +14527,10 @@ var init_agents = __esm({
|
|
|
14113
14527
|
continue;
|
|
14114
14528
|
}
|
|
14115
14529
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14116
|
-
const destDir =
|
|
14530
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
14117
14531
|
try {
|
|
14118
14532
|
await ensureDir(destDir);
|
|
14119
|
-
const dest =
|
|
14533
|
+
const dest = path38.join(destDir, `${item.name}.md`);
|
|
14120
14534
|
await copyFile(item.sourcePath, dest);
|
|
14121
14535
|
log.debug(`Synced legacy agent ${item.name} \u2192 ${tool}`);
|
|
14122
14536
|
} catch (e) {
|
|
@@ -14125,6 +14539,7 @@ var init_agents = __esm({
|
|
|
14125
14539
|
}
|
|
14126
14540
|
}
|
|
14127
14541
|
};
|
|
14542
|
+
LEGACY_MD_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
14128
14543
|
}
|
|
14129
14544
|
});
|
|
14130
14545
|
|
|
@@ -14190,11 +14605,11 @@ __export(update_exports, {
|
|
|
14190
14605
|
resolveRegistryForPackage: () => resolveRegistryForPackage,
|
|
14191
14606
|
update: () => update
|
|
14192
14607
|
});
|
|
14193
|
-
import { execFile as
|
|
14608
|
+
import { execFile as execFile2 } from "child_process";
|
|
14194
14609
|
import { promisify as promisify2 } from "util";
|
|
14195
14610
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
14196
14611
|
import fs16 from "fs";
|
|
14197
|
-
import
|
|
14612
|
+
import path39 from "path";
|
|
14198
14613
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14199
14614
|
import fse7 from "fs-extra";
|
|
14200
14615
|
function resolveRegistryForPackage(pkgName) {
|
|
@@ -14204,15 +14619,15 @@ function resolveRegistryForPackage(pkgName) {
|
|
|
14204
14619
|
return PUBLIC_REGISTRY;
|
|
14205
14620
|
}
|
|
14206
14621
|
function resolveNpmCommand() {
|
|
14207
|
-
const nodeDir =
|
|
14622
|
+
const nodeDir = path39.dirname(process.execPath);
|
|
14208
14623
|
const candidates = [
|
|
14209
14624
|
// Bundled runtimes: npm installed flat next to node.exe.
|
|
14210
|
-
|
|
14625
|
+
path39.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
14211
14626
|
// POSIX layout rooted at the node dir itself.
|
|
14212
|
-
|
|
14627
|
+
path39.join(nodeDir, "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
14213
14628
|
// Canonical POSIX install (official tarball, Homebrew, nvm): node lives in
|
|
14214
14629
|
// <prefix>/bin with npm at <prefix>/lib/node_modules — one level up.
|
|
14215
|
-
|
|
14630
|
+
path39.join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
|
|
14216
14631
|
];
|
|
14217
14632
|
for (const c of candidates) {
|
|
14218
14633
|
if (fs16.existsSync(c)) return { cmd: process.execPath, args: [c] };
|
|
@@ -14221,16 +14636,16 @@ function resolveNpmCommand() {
|
|
|
14221
14636
|
return { cmd: "npm", args: [] };
|
|
14222
14637
|
}
|
|
14223
14638
|
function prefixFromEntryPath(entry, posix) {
|
|
14224
|
-
const marker = `${
|
|
14639
|
+
const marker = `${path39.sep}node_modules${path39.sep}`;
|
|
14225
14640
|
const idx = entry.lastIndexOf(marker);
|
|
14226
14641
|
if (idx <= 0) return null;
|
|
14227
14642
|
const root = entry.slice(0, idx);
|
|
14228
14643
|
const pkgDir = getCurrentPackageName();
|
|
14229
|
-
if (posix &&
|
|
14230
|
-
const prefix =
|
|
14231
|
-
return fs16.existsSync(
|
|
14644
|
+
if (posix && path39.basename(root) === "lib") {
|
|
14645
|
+
const prefix = path39.dirname(root);
|
|
14646
|
+
return fs16.existsSync(path39.join(prefix, "lib", "node_modules", pkgDir)) ? { prefix, global: true } : null;
|
|
14232
14647
|
}
|
|
14233
|
-
return fs16.existsSync(
|
|
14648
|
+
return fs16.existsSync(path39.join(root, "node_modules", pkgDir)) ? { prefix: root, global: !posix } : null;
|
|
14234
14649
|
}
|
|
14235
14650
|
function resolveInstallPrefix() {
|
|
14236
14651
|
return prefixFromEntryPath(fileURLToPath4(import.meta.url), process.platform !== "win32");
|
|
@@ -14355,7 +14770,7 @@ async function acquireLock(lockPath) {
|
|
|
14355
14770
|
owner
|
|
14356
14771
|
});
|
|
14357
14772
|
try {
|
|
14358
|
-
await ensureDir(
|
|
14773
|
+
await ensureDir(path39.dirname(resolved));
|
|
14359
14774
|
} catch {
|
|
14360
14775
|
return false;
|
|
14361
14776
|
}
|
|
@@ -14484,12 +14899,12 @@ async function doUpdate() {
|
|
|
14484
14899
|
if (entry) {
|
|
14485
14900
|
try {
|
|
14486
14901
|
const installed = JSON.parse(fs16.readFileSync(
|
|
14487
|
-
|
|
14902
|
+
path39.join(path39.dirname(path39.dirname(entry)), "package.json"),
|
|
14488
14903
|
"utf-8"
|
|
14489
14904
|
));
|
|
14490
14905
|
if (installed.version !== result.latest) {
|
|
14491
14906
|
log.warn(
|
|
14492
|
-
`The running install at ${
|
|
14907
|
+
`The running install at ${path39.dirname(path39.dirname(entry))} is still v${installed.version ?? "unknown"} (expected v${result.latest}) \u2014 it may need a manual update.`
|
|
14493
14908
|
);
|
|
14494
14909
|
}
|
|
14495
14910
|
} catch {
|
|
@@ -14543,7 +14958,7 @@ var init_update = __esm({
|
|
|
14543
14958
|
init_types();
|
|
14544
14959
|
init_prompt();
|
|
14545
14960
|
init_package_info();
|
|
14546
|
-
execFileAsync2 = promisify2(
|
|
14961
|
+
execFileAsync2 = promisify2(execFile2);
|
|
14547
14962
|
PUBLIC_REGISTRY = "https://registry.npmjs.org";
|
|
14548
14963
|
TNPM_REGISTRY = "http://r.tnpm.oa.com";
|
|
14549
14964
|
VERSION_CHECK_TIMEOUT = 5e3;
|
|
@@ -14558,9 +14973,9 @@ var marketplace_exports = {};
|
|
|
14558
14973
|
__export(marketplace_exports, {
|
|
14559
14974
|
refreshMarketplace: () => refreshMarketplace
|
|
14560
14975
|
});
|
|
14561
|
-
import
|
|
14976
|
+
import path40 from "path";
|
|
14562
14977
|
async function extractSkillDescription2(skillDir) {
|
|
14563
|
-
const skillMdPath =
|
|
14978
|
+
const skillMdPath = path40.join(skillDir, SKILL_MD2);
|
|
14564
14979
|
const content = await readFileSafe(skillMdPath);
|
|
14565
14980
|
if (!content) return "";
|
|
14566
14981
|
const { data } = parseFrontmatter(content);
|
|
@@ -14569,7 +14984,7 @@ async function extractSkillDescription2(skillDir) {
|
|
|
14569
14984
|
return String(desc).replace(/\s+/g, " ").trim();
|
|
14570
14985
|
}
|
|
14571
14986
|
async function refreshMarketplace(repoPath) {
|
|
14572
|
-
const marketplacePath =
|
|
14987
|
+
const marketplacePath = path40.join(repoPath, MARKETPLACE_PATH);
|
|
14573
14988
|
if (!await pathExists(marketplacePath)) {
|
|
14574
14989
|
return false;
|
|
14575
14990
|
}
|
|
@@ -14583,11 +14998,11 @@ async function refreshMarketplace(repoPath) {
|
|
|
14583
14998
|
return false;
|
|
14584
14999
|
}
|
|
14585
15000
|
if (!Array.isArray(marketplace.plugins)) return false;
|
|
14586
|
-
const skillsDir =
|
|
15001
|
+
const skillsDir = path40.join(repoPath, "skills");
|
|
14587
15002
|
const currentSkills = /* @__PURE__ */ new Set();
|
|
14588
15003
|
const dirs = await listDirs(skillsDir);
|
|
14589
15004
|
for (const dir of dirs) {
|
|
14590
|
-
const hasSkillMd = await pathExists(
|
|
15005
|
+
const hasSkillMd = await pathExists(path40.join(skillsDir, dir, SKILL_MD2));
|
|
14591
15006
|
if (hasSkillMd) {
|
|
14592
15007
|
currentSkills.add(dir);
|
|
14593
15008
|
}
|
|
@@ -14606,7 +15021,7 @@ async function refreshMarketplace(repoPath) {
|
|
|
14606
15021
|
for (const skillName of currentSkills) {
|
|
14607
15022
|
const existing = existingPlugins.get(skillName);
|
|
14608
15023
|
if (existing) {
|
|
14609
|
-
const desc = await extractSkillDescription2(
|
|
15024
|
+
const desc = await extractSkillDescription2(path40.join(skillsDir, skillName));
|
|
14610
15025
|
if (desc && desc !== existing.description) {
|
|
14611
15026
|
existing.description = desc;
|
|
14612
15027
|
changed = true;
|
|
@@ -14614,7 +15029,7 @@ async function refreshMarketplace(repoPath) {
|
|
|
14614
15029
|
updatedPlugins.push(existing);
|
|
14615
15030
|
existingPlugins.delete(skillName);
|
|
14616
15031
|
} else {
|
|
14617
|
-
const desc = await extractSkillDescription2(
|
|
15032
|
+
const desc = await extractSkillDescription2(path40.join(skillsDir, skillName));
|
|
14618
15033
|
updatedPlugins.push({
|
|
14619
15034
|
name: skillName,
|
|
14620
15035
|
source: `./skills/${skillName}`,
|
|
@@ -14651,177 +15066,6 @@ var init_marketplace = __esm({
|
|
|
14651
15066
|
}
|
|
14652
15067
|
});
|
|
14653
15068
|
|
|
14654
|
-
// src/projects.ts
|
|
14655
|
-
var projects_exports = {};
|
|
14656
|
-
__export(projects_exports, {
|
|
14657
|
-
describeProjects: () => describeProjects,
|
|
14658
|
-
findProject: () => findProject,
|
|
14659
|
-
isSafeNamespaceSegment: () => isSafeNamespaceSegment,
|
|
14660
|
-
listProjectIds: () => listProjectIds,
|
|
14661
|
-
loadProjectsManifest: () => loadProjectsManifest,
|
|
14662
|
-
mergeNamespaces: () => mergeNamespaces,
|
|
14663
|
-
resolveActiveLearningsNamespaces: () => resolveActiveLearningsNamespaces,
|
|
14664
|
-
resolveProjectResourceNamespaces: () => resolveProjectResourceNamespaces,
|
|
14665
|
-
saveProjectsManifest: () => saveProjectsManifest
|
|
14666
|
-
});
|
|
14667
|
-
import path40 from "path";
|
|
14668
|
-
import YAML8 from "yaml";
|
|
14669
|
-
import { z as z6 } from "zod";
|
|
14670
|
-
function isSafeNamespaceSegment(seg) {
|
|
14671
|
-
return SAFE_ID.test(seg) && seg !== "." && seg !== "..";
|
|
14672
|
-
}
|
|
14673
|
-
function validateManifestShape2(raw) {
|
|
14674
|
-
if (!raw || typeof raw !== "object") {
|
|
14675
|
-
throw new Error("Invalid projects manifest: expected an object");
|
|
14676
|
-
}
|
|
14677
|
-
const candidate = raw;
|
|
14678
|
-
const projects = candidate.projects;
|
|
14679
|
-
if (projects !== void 0 && !Array.isArray(projects)) {
|
|
14680
|
-
throw new Error("Invalid projects manifest: projects must be an array");
|
|
14681
|
-
}
|
|
14682
|
-
for (const project of projects ?? []) {
|
|
14683
|
-
if (!project || typeof project !== "object") {
|
|
14684
|
-
throw new Error("Invalid projects manifest: every project must be an object");
|
|
14685
|
-
}
|
|
14686
|
-
const resources = project.resources;
|
|
14687
|
-
if (resources !== void 0 && (typeof resources !== "object" || Array.isArray(resources))) {
|
|
14688
|
-
throw new Error(`Invalid projects manifest: project ${project.id ?? "<unknown>"} has invalid resources`);
|
|
14689
|
-
}
|
|
14690
|
-
if (resources) {
|
|
14691
|
-
const ALLOWED_RESOURCE_KEYS = new Set(PROJECT_RESOURCE_TYPES);
|
|
14692
|
-
for (const key of Object.keys(resources)) {
|
|
14693
|
-
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
14694
|
-
throw new Error(`Invalid projects manifest: unknown resource type "${key}"`);
|
|
14695
|
-
}
|
|
14696
|
-
}
|
|
14697
|
-
}
|
|
14698
|
-
}
|
|
14699
|
-
const manifest = ProjectsManifestSchema.parse(raw);
|
|
14700
|
-
const ids = /* @__PURE__ */ new Set();
|
|
14701
|
-
for (const project of manifest.projects) {
|
|
14702
|
-
if (ids.has(project.id)) {
|
|
14703
|
-
throw new Error(`Invalid projects manifest: duplicate project id "${project.id}"`);
|
|
14704
|
-
}
|
|
14705
|
-
ids.add(project.id);
|
|
14706
|
-
}
|
|
14707
|
-
return manifest;
|
|
14708
|
-
}
|
|
14709
|
-
async function loadProjectsManifest(repoPath) {
|
|
14710
|
-
const manifestPath = path40.join(repoPath, "manifest", "projects.yaml");
|
|
14711
|
-
const content = await readFileSafe(manifestPath);
|
|
14712
|
-
if (!content) {
|
|
14713
|
-
return null;
|
|
14714
|
-
}
|
|
14715
|
-
let raw;
|
|
14716
|
-
try {
|
|
14717
|
-
raw = YAML8.parse(content);
|
|
14718
|
-
} catch (error) {
|
|
14719
|
-
throw new Error(`Invalid projects manifest YAML: ${error.message}`);
|
|
14720
|
-
}
|
|
14721
|
-
return validateManifestShape2(raw);
|
|
14722
|
-
}
|
|
14723
|
-
async function saveProjectsManifest(repoPath, manifest) {
|
|
14724
|
-
validateManifestShape2(manifest);
|
|
14725
|
-
const manifestDir = path40.join(repoPath, "manifest");
|
|
14726
|
-
const manifestPath = path40.join(manifestDir, "projects.yaml");
|
|
14727
|
-
await ensureDir(manifestDir);
|
|
14728
|
-
await writeFile(manifestPath, YAML8.stringify(manifest));
|
|
14729
|
-
}
|
|
14730
|
-
function findProject(manifest, projectId) {
|
|
14731
|
-
return manifest.projects.find((candidate) => candidate.id === projectId);
|
|
14732
|
-
}
|
|
14733
|
-
function listProjectIds(manifest) {
|
|
14734
|
-
return manifest.projects.map((project) => project.id);
|
|
14735
|
-
}
|
|
14736
|
-
function describeProjects(projects) {
|
|
14737
|
-
return projects.map((project) => {
|
|
14738
|
-
const label = project.name || project.id;
|
|
14739
|
-
return project.description ? `${label}: ${project.description}` : label;
|
|
14740
|
-
});
|
|
14741
|
-
}
|
|
14742
|
-
function getProjectOrThrow(manifest, projectId) {
|
|
14743
|
-
const project = findProject(manifest, projectId);
|
|
14744
|
-
if (!project) {
|
|
14745
|
-
throw new Error(`Unknown project "${projectId}". Valid projects: ${listProjectIds(manifest).join(", ")}`);
|
|
14746
|
-
}
|
|
14747
|
-
return project;
|
|
14748
|
-
}
|
|
14749
|
-
function resolveProjectResourceNamespaces(input) {
|
|
14750
|
-
const resolved = input.activeProjects.map((id) => getProjectOrThrow(input.manifest, id));
|
|
14751
|
-
const namespaces = {
|
|
14752
|
-
knowledge: [],
|
|
14753
|
-
skills: [],
|
|
14754
|
-
learnings: []
|
|
14755
|
-
};
|
|
14756
|
-
for (const type of PROJECT_RESOURCE_TYPES) {
|
|
14757
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14758
|
-
for (const project of resolved) {
|
|
14759
|
-
for (const namespace of project.resources[type]) {
|
|
14760
|
-
if (seen.has(namespace)) continue;
|
|
14761
|
-
seen.add(namespace);
|
|
14762
|
-
namespaces[type].push(namespace);
|
|
14763
|
-
}
|
|
14764
|
-
}
|
|
14765
|
-
}
|
|
14766
|
-
return namespaces;
|
|
14767
|
-
}
|
|
14768
|
-
async function resolveActiveLearningsNamespaces(repoPath, activeProjects) {
|
|
14769
|
-
if (activeProjects.length === 0) return [];
|
|
14770
|
-
const manifest = await loadProjectsManifest(repoPath);
|
|
14771
|
-
if (!manifest) return [];
|
|
14772
|
-
try {
|
|
14773
|
-
return resolveProjectResourceNamespaces({ manifest, activeProjects }).learnings;
|
|
14774
|
-
} catch {
|
|
14775
|
-
return [];
|
|
14776
|
-
}
|
|
14777
|
-
}
|
|
14778
|
-
function mergeNamespaces(roleNamespaces, projectNamespaces) {
|
|
14779
|
-
const dedupe = (a, b) => {
|
|
14780
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14781
|
-
const out = [];
|
|
14782
|
-
for (const ns of [...a, ...b]) {
|
|
14783
|
-
if (seen.has(ns)) continue;
|
|
14784
|
-
seen.add(ns);
|
|
14785
|
-
out.push(ns);
|
|
14786
|
-
}
|
|
14787
|
-
return out;
|
|
14788
|
-
};
|
|
14789
|
-
return {
|
|
14790
|
-
knowledge: dedupe(roleNamespaces.knowledge, projectNamespaces.knowledge),
|
|
14791
|
-
skills: dedupe(roleNamespaces.skills, projectNamespaces.skills),
|
|
14792
|
-
// Roles never contribute learnings; this is effectively the project set.
|
|
14793
|
-
learnings: dedupe(roleNamespaces.learnings, projectNamespaces.learnings)
|
|
14794
|
-
};
|
|
14795
|
-
}
|
|
14796
|
-
var PROJECT_RESOURCE_TYPES, ProjectResourceNamespacesSchema, SAFE_ID, ProjectSchema, ProjectsManifestSchema;
|
|
14797
|
-
var init_projects = __esm({
|
|
14798
|
-
"src/projects.ts"() {
|
|
14799
|
-
"use strict";
|
|
14800
|
-
init_fs();
|
|
14801
|
-
PROJECT_RESOURCE_TYPES = ["knowledge", "skills", "learnings"];
|
|
14802
|
-
ProjectResourceNamespacesSchema = z6.object({
|
|
14803
|
-
knowledge: z6.array(z6.string().min(1)).default([]),
|
|
14804
|
-
skills: z6.array(z6.string().min(1)).default([]),
|
|
14805
|
-
learnings: z6.array(z6.string().min(1)).default([])
|
|
14806
|
-
});
|
|
14807
|
-
SAFE_ID = /^[A-Za-z0-9._-]+$/;
|
|
14808
|
-
ProjectSchema = z6.object({
|
|
14809
|
-
id: z6.string().min(1).refine((v) => isSafeNamespaceSegment(v), {
|
|
14810
|
-
message: "project id must be a single path segment (letters, digits, '.', '_', '-'; no '/', '\\\\', or '..')"
|
|
14811
|
-
}),
|
|
14812
|
-
name: z6.string().default(""),
|
|
14813
|
-
description: z6.string().default(""),
|
|
14814
|
-
resources: ProjectResourceNamespacesSchema
|
|
14815
|
-
});
|
|
14816
|
-
ProjectsManifestSchema = z6.object({
|
|
14817
|
-
version: z6.number(),
|
|
14818
|
-
// Unlike roles (`.min(1)`), a repo may define zero projects — a team without
|
|
14819
|
-
// project partitioning simply has no projects.yaml, and an empty list is valid.
|
|
14820
|
-
projects: z6.array(ProjectSchema).default([])
|
|
14821
|
-
});
|
|
14822
|
-
}
|
|
14823
|
-
});
|
|
14824
|
-
|
|
14825
15069
|
// src/pkg/types.ts
|
|
14826
15070
|
import { z as z7 } from "zod";
|
|
14827
15071
|
var PackageScopeSchema, NpmSpecSchema, ClaudeMarketplaceSchema, ClaudePluginSpecSchema, ClaudeEcosystemSchema, PackageSetSchema, PackageManifestSchema, NpmLockEntrySchema, ClaudeMarketplaceLockSchema, ClaudePluginLockSchema, PackageLockSchema;
|
|
@@ -15073,6 +15317,7 @@ __export(reports_branch_exports, {
|
|
|
15073
15317
|
ensureReportsDir: () => ensureReportsDir,
|
|
15074
15318
|
ensureReportsWorktree: () => ensureReportsWorktree,
|
|
15075
15319
|
refreshReportsWorktree: () => refreshReportsWorktree,
|
|
15320
|
+
updateReports: () => updateReports,
|
|
15076
15321
|
withKnowledgeWorktree: () => withKnowledgeWorktree
|
|
15077
15322
|
});
|
|
15078
15323
|
import path42 from "path";
|
|
@@ -15196,6 +15441,38 @@ async function writeWorktreeGitignore(wt) {
|
|
|
15196
15441
|
].join("\n");
|
|
15197
15442
|
await writeFile(path42.join(wt, ".gitignore"), content);
|
|
15198
15443
|
}
|
|
15444
|
+
async function commitAndPushReportsAt(wt, message, files, options = {}) {
|
|
15445
|
+
const git = createGit2(wt);
|
|
15446
|
+
await git.add(files);
|
|
15447
|
+
const status2 = await git.status();
|
|
15448
|
+
if (status2.staged.length === 0 && !options.pushIfUnchanged) {
|
|
15449
|
+
log.debug("[reports] nothing to commit");
|
|
15450
|
+
return false;
|
|
15451
|
+
}
|
|
15452
|
+
if (status2.staged.length > 0) await commitSkippingHooks(git, message);
|
|
15453
|
+
for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {
|
|
15454
|
+
try {
|
|
15455
|
+
await git.push(["origin", REPORTS_BRANCH]);
|
|
15456
|
+
return true;
|
|
15457
|
+
} catch (pushErr) {
|
|
15458
|
+
if (attempt === MAX_PUSH_RETRIES) {
|
|
15459
|
+
log.debug(`[reports] push failed after ${attempt} attempts: ${pushErr.message}`);
|
|
15460
|
+
return false;
|
|
15461
|
+
}
|
|
15462
|
+
try {
|
|
15463
|
+
await git.fetch(["origin", REPORTS_BRANCH]);
|
|
15464
|
+
await git.rebase([`origin/${REPORTS_BRANCH}`]);
|
|
15465
|
+
} catch (rebaseErr) {
|
|
15466
|
+
log.debug(`[reports] rebase failed, retrying: ${rebaseErr.message}`);
|
|
15467
|
+
try {
|
|
15468
|
+
await git.rebase(["--abort"]);
|
|
15469
|
+
} catch {
|
|
15470
|
+
}
|
|
15471
|
+
}
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
return false;
|
|
15475
|
+
}
|
|
15199
15476
|
async function commitAndPushReports(localConfig, message, files, options = {}) {
|
|
15200
15477
|
const lockPath = reportsLockPath(localConfig);
|
|
15201
15478
|
const locked = await acquireLock(lockPath);
|
|
@@ -15205,38 +15482,37 @@ async function commitAndPushReports(localConfig, message, files, options = {}) {
|
|
|
15205
15482
|
}
|
|
15206
15483
|
try {
|
|
15207
15484
|
const wt = await ensureReportsWorktree(localConfig);
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15485
|
+
return await commitAndPushReportsAt(wt, message, files, options);
|
|
15486
|
+
} catch (e) {
|
|
15487
|
+
log.debug(`[reports] commitAndPushReports failed (non-blocking): ${e.message}`);
|
|
15488
|
+
return false;
|
|
15489
|
+
} finally {
|
|
15490
|
+
await releaseLock(lockPath);
|
|
15491
|
+
}
|
|
15492
|
+
}
|
|
15493
|
+
async function updateReports(localConfig, write, options = {}) {
|
|
15494
|
+
if (!usesReportsBranch(localConfig)) {
|
|
15495
|
+
throw new Error("updateReports requires a repo that stores reports on the teamai-reports branch");
|
|
15496
|
+
}
|
|
15497
|
+
const lockPath = reportsLockPath(localConfig);
|
|
15498
|
+
if (!await acquireLock(lockPath)) {
|
|
15499
|
+
log.debug("[reports] another reports write is in progress; skipping");
|
|
15500
|
+
return false;
|
|
15501
|
+
}
|
|
15502
|
+
try {
|
|
15503
|
+
const wt = await ensureReportsWorktree(localConfig);
|
|
15504
|
+
try {
|
|
15505
|
+
await syncReportsWorktree(wt);
|
|
15506
|
+
} catch (e) {
|
|
15507
|
+
log.debug(`[reports] sync before write failed, writing onto the local copy: ${e.message}`);
|
|
15214
15508
|
}
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
await git.push(["origin", REPORTS_BRANCH]);
|
|
15219
|
-
return true;
|
|
15220
|
-
} catch (pushErr) {
|
|
15221
|
-
if (attempt === MAX_PUSH_RETRIES) {
|
|
15222
|
-
log.debug(`[reports] push failed after ${attempt} attempts: ${pushErr.message}`);
|
|
15223
|
-
return false;
|
|
15224
|
-
}
|
|
15225
|
-
try {
|
|
15226
|
-
await git.fetch(["origin", REPORTS_BRANCH]);
|
|
15227
|
-
await git.rebase([`origin/${REPORTS_BRANCH}`]);
|
|
15228
|
-
} catch (rebaseErr) {
|
|
15229
|
-
log.debug(`[reports] rebase failed, retrying: ${rebaseErr.message}`);
|
|
15230
|
-
try {
|
|
15231
|
-
await git.rebase(["--abort"]);
|
|
15232
|
-
} catch {
|
|
15233
|
-
}
|
|
15234
|
-
}
|
|
15235
|
-
}
|
|
15509
|
+
const change = await write(wt);
|
|
15510
|
+
if (!change || change.files.length === 0) {
|
|
15511
|
+
return false;
|
|
15236
15512
|
}
|
|
15237
|
-
return
|
|
15513
|
+
return await commitAndPushReportsAt(wt, change.message, change.files, options);
|
|
15238
15514
|
} catch (e) {
|
|
15239
|
-
log.debug(`[reports]
|
|
15515
|
+
log.debug(`[reports] updateReports failed (non-blocking): ${e.message}`);
|
|
15240
15516
|
return false;
|
|
15241
15517
|
} finally {
|
|
15242
15518
|
await releaseLock(lockPath);
|
|
@@ -15706,6 +15982,7 @@ var init_known_agents = __esm({
|
|
|
15706
15982
|
// src/init.ts
|
|
15707
15983
|
var init_exports = {};
|
|
15708
15984
|
__export(init_exports, {
|
|
15985
|
+
ALL_PROJECTS_SELECTOR: () => ALL_PROJECTS_SELECTOR,
|
|
15709
15986
|
buildSelfModeGitignore: () => buildSelfModeGitignore,
|
|
15710
15987
|
init: () => init,
|
|
15711
15988
|
initHttp: () => initHttp,
|
|
@@ -15713,6 +15990,7 @@ __export(init_exports, {
|
|
|
15713
15990
|
migrateSelfModeGitignore: () => migrateSelfModeGitignore,
|
|
15714
15991
|
migrateSelfModeGitignoreContent: () => migrateSelfModeGitignoreContent,
|
|
15715
15992
|
promptForSelfModeAgents: () => promptForSelfModeAgents,
|
|
15993
|
+
resolveActiveProjects: () => resolveActiveProjects,
|
|
15716
15994
|
resolveInheritUserScope: () => resolveInheritUserScope,
|
|
15717
15995
|
resolveInitRepo: () => resolveInitRepo,
|
|
15718
15996
|
resolveInitScope: () => resolveInitScope,
|
|
@@ -15783,6 +16061,10 @@ async function promptForRoleProfile(repoPath, roleFlag) {
|
|
|
15783
16061
|
resourceProfileVersion: manifest.version
|
|
15784
16062
|
};
|
|
15785
16063
|
}
|
|
16064
|
+
function dedupeIds(ids) {
|
|
16065
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16066
|
+
return ids.filter((id) => seen.has(id) ? false : (seen.add(id), true));
|
|
16067
|
+
}
|
|
15786
16068
|
async function resolveActiveProjects(repoPath, projectFlag) {
|
|
15787
16069
|
const requested = (projectFlag ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
15788
16070
|
if (requested.length === 0) {
|
|
@@ -15794,7 +16076,27 @@ async function resolveActiveProjects(repoPath, projectFlag) {
|
|
|
15794
16076
|
`--project given but no projects manifest (manifest/projects.yaml) exists in the team repo.`
|
|
15795
16077
|
);
|
|
15796
16078
|
}
|
|
15797
|
-
const
|
|
16079
|
+
const declared = listProjectIds(manifest);
|
|
16080
|
+
if (requested.includes(ALL_PROJECTS_SELECTOR)) {
|
|
16081
|
+
if (requested.length > 1) {
|
|
16082
|
+
throw new Error(
|
|
16083
|
+
`--project "${ALL_PROJECTS_SELECTOR}" already covers every declared project; drop the other ids (got: ${requested.join(", ")}).`
|
|
16084
|
+
);
|
|
16085
|
+
}
|
|
16086
|
+
if (declared.length === 0) {
|
|
16087
|
+
log.warn(
|
|
16088
|
+
`--project "${ALL_PROJECTS_SELECTOR}" was given but manifest/projects.yaml declares no projects; nothing was activated.`
|
|
16089
|
+
);
|
|
16090
|
+
return { projects: [] };
|
|
16091
|
+
}
|
|
16092
|
+
if (declared.includes(ALL_PROJECTS_SELECTOR)) {
|
|
16093
|
+
log.warn(
|
|
16094
|
+
`manifest/projects.yaml declares a project with the id "${ALL_PROJECTS_SELECTOR}", which is the reserved --project selector: every project is activated (that one included). To activate only it, run \`teamai projects set ${ALL_PROJECTS_SELECTOR}\`.`
|
|
16095
|
+
);
|
|
16096
|
+
}
|
|
16097
|
+
return { projects: declared };
|
|
16098
|
+
}
|
|
16099
|
+
const validIds = new Set(declared);
|
|
15798
16100
|
for (const id of requested) {
|
|
15799
16101
|
if (!validIds.has(id)) {
|
|
15800
16102
|
throw new Error(
|
|
@@ -15802,9 +16104,7 @@ async function resolveActiveProjects(repoPath, projectFlag) {
|
|
|
15802
16104
|
);
|
|
15803
16105
|
}
|
|
15804
16106
|
}
|
|
15805
|
-
|
|
15806
|
-
const projects = requested.filter((id) => seen.has(id) ? false : (seen.add(id), true));
|
|
15807
|
-
return { projects };
|
|
16107
|
+
return { projects: dedupeIds(requested) };
|
|
15808
16108
|
}
|
|
15809
16109
|
function resolveInitScope(rawScope, cwd, homeDir) {
|
|
15810
16110
|
const cwdResolved = resolveRealPath(cwd);
|
|
@@ -16322,20 +16622,28 @@ async function initSelfRepo(options) {
|
|
|
16322
16622
|
}
|
|
16323
16623
|
if (!options.dryRun) {
|
|
16324
16624
|
try {
|
|
16325
|
-
const {
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
await
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
username
|
|
16334
|
-
|
|
16625
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
16626
|
+
let isNewSelfMember = false;
|
|
16627
|
+
let selfMemberChanged = false;
|
|
16628
|
+
const pushed = await updateReports2(localConfig, async (wt) => {
|
|
16629
|
+
const memberDir = path45.join(wt, "members");
|
|
16630
|
+
await ensureDir(memberDir);
|
|
16631
|
+
const memberPath = path45.join(memberDir, `${username}.yaml`);
|
|
16632
|
+
isNewSelfMember = !await pathExists(memberPath);
|
|
16633
|
+
const existingSelfMember = await getMemberConfig(wt, username);
|
|
16634
|
+
const merged = mergeMemberConfig(existingSelfMember, {
|
|
16635
|
+
username,
|
|
16636
|
+
projects: localConfig.projects
|
|
16637
|
+
});
|
|
16638
|
+
selfMemberChanged = merged.changed;
|
|
16639
|
+
if (!merged.changed) return null;
|
|
16640
|
+
await writeFile(memberPath, YAML11.stringify(merged.config));
|
|
16641
|
+
return {
|
|
16642
|
+
files: ["members/"],
|
|
16643
|
+
message: isNewSelfMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`
|
|
16644
|
+
};
|
|
16335
16645
|
});
|
|
16336
16646
|
if (selfMemberChanged) {
|
|
16337
|
-
await writeFile(memberPath, YAML11.stringify(selfMemberConfig));
|
|
16338
|
-
const pushed = await commitAndPushReports2(localConfig, isNewSelfMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`, ["members/"]);
|
|
16339
16647
|
if (pushed) {
|
|
16340
16648
|
log.success(isNewSelfMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
16341
16649
|
} else {
|
|
@@ -16510,6 +16818,17 @@ async function init(options) {
|
|
|
16510
16818
|
}
|
|
16511
16819
|
} else {
|
|
16512
16820
|
log.info(`Repo already exists at ${localPath}, using existing clone`);
|
|
16821
|
+
try {
|
|
16822
|
+
const pullResult = await pullRepoFastForward(localPath);
|
|
16823
|
+
if (pullResult !== "already up to date") {
|
|
16824
|
+
log.info(`Refreshed existing clone (${pullResult})`);
|
|
16825
|
+
}
|
|
16826
|
+
} catch (e) {
|
|
16827
|
+
log.error(
|
|
16828
|
+
`Failed to refresh existing clone at ${localPath}: ${e.message}. The local clone was left unchanged. Fix network/auth or resolve divergence (commit/stash local edits), or remove ${localPath} manually to start fresh.`
|
|
16829
|
+
);
|
|
16830
|
+
process.exit(1);
|
|
16831
|
+
}
|
|
16513
16832
|
}
|
|
16514
16833
|
} else {
|
|
16515
16834
|
log.warn(`Existing ${localPath} is not a git repository, re-cloning`);
|
|
@@ -16656,29 +16975,37 @@ async function init(options) {
|
|
|
16656
16975
|
let isNewMember = true;
|
|
16657
16976
|
if (!options.dryRun) {
|
|
16658
16977
|
try {
|
|
16659
|
-
const {
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
await
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
username
|
|
16668
|
-
|
|
16978
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
16979
|
+
let memberChanged = false;
|
|
16980
|
+
let memberProjects;
|
|
16981
|
+
const pushed = await updateReports2(reportsConfig, async (wt) => {
|
|
16982
|
+
const memberDir = path45.join(wt, "members");
|
|
16983
|
+
await ensureDir(memberDir);
|
|
16984
|
+
const memberPath = path45.join(memberDir, `${username}.yaml`);
|
|
16985
|
+
isNewMember = !await pathExists(memberPath);
|
|
16986
|
+
const existingMember = await getMemberConfig(wt, username);
|
|
16987
|
+
const merged = mergeMemberConfig(existingMember, {
|
|
16988
|
+
username,
|
|
16989
|
+
projects: resolvedProjects
|
|
16990
|
+
});
|
|
16991
|
+
memberChanged = merged.changed;
|
|
16992
|
+
memberProjects = merged.config.projects;
|
|
16993
|
+
if (!merged.changed) return null;
|
|
16994
|
+
await writeFile(memberPath, YAML11.stringify(merged.config));
|
|
16995
|
+
return {
|
|
16996
|
+
files: ["members/"],
|
|
16997
|
+
message: isNewMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`
|
|
16998
|
+
};
|
|
16669
16999
|
});
|
|
16670
17000
|
if (memberChanged) {
|
|
16671
|
-
|
|
16672
|
-
log.success(isNewMember ? `Registered as team member: ${username}` : `Updated member roster: ${username}${memberConfig.projects ? ` (projects: ${memberConfig.projects.join(", ")})` : ""}`);
|
|
16673
|
-
const pushed = await commitAndPushReports2(reportsConfig, isNewMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`, ["members/"]);
|
|
17001
|
+
log.success(isNewMember ? `Registered as team member: ${username}` : `Updated member roster: ${username}${memberProjects ? ` (projects: ${memberProjects.join(", ")})` : ""}`);
|
|
16674
17002
|
if (pushed) {
|
|
16675
17003
|
log.success(isNewMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
16676
17004
|
} else {
|
|
16677
17005
|
log.warn("Member registration could not be pushed (no write access?). You are still set up locally.");
|
|
16678
17006
|
}
|
|
16679
|
-
} else {
|
|
17007
|
+
} else if (!isNewMember) {
|
|
16680
17008
|
log.info(`Member ${username} already registered`);
|
|
16681
|
-
isNewMember = false;
|
|
16682
17009
|
}
|
|
16683
17010
|
} catch (e) {
|
|
16684
17011
|
log.warn(`Member registration skipped (non-blocking): ${e.message}`);
|
|
@@ -16804,6 +17131,7 @@ async function init(options) {
|
|
|
16804
17131
|
log.info("Run `teamai status` to check current config.");
|
|
16805
17132
|
closePrompt();
|
|
16806
17133
|
}
|
|
17134
|
+
var ALL_PROJECTS_SELECTOR;
|
|
16807
17135
|
var init_init = __esm({
|
|
16808
17136
|
"src/init.ts"() {
|
|
16809
17137
|
"use strict";
|
|
@@ -16822,6 +17150,7 @@ var init_init = __esm({
|
|
|
16822
17150
|
init_members();
|
|
16823
17151
|
init_prompt();
|
|
16824
17152
|
init_known_agents();
|
|
17153
|
+
ALL_PROJECTS_SELECTOR = "all";
|
|
16825
17154
|
}
|
|
16826
17155
|
});
|
|
16827
17156
|
|
|
@@ -17144,13 +17473,19 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
|
|
|
17144
17473
|
}
|
|
17145
17474
|
} : teamConfig;
|
|
17146
17475
|
const pushableTypes = ["skills", "rules", "env", "agents"];
|
|
17147
|
-
const
|
|
17476
|
+
const fullScan = [];
|
|
17148
17477
|
for (const type of pushableTypes) {
|
|
17149
17478
|
const handler = getHandler(type);
|
|
17150
17479
|
const items = await handler.scanLocalForPush(scanTeamConfig, localConfig);
|
|
17151
|
-
|
|
17480
|
+
fullScan.push(...items);
|
|
17152
17481
|
}
|
|
17153
|
-
const
|
|
17482
|
+
const allItems = fullScan.filter((item) => {
|
|
17483
|
+
if (item.type === "agents" && "skipReason" in item && typeof item.skipReason === "string" && item.skipReason) {
|
|
17484
|
+
log.warn(`[agents] Skipped ${item.name}: ${item.skipReason}`);
|
|
17485
|
+
return false;
|
|
17486
|
+
}
|
|
17487
|
+
return true;
|
|
17488
|
+
});
|
|
17154
17489
|
spin.stop();
|
|
17155
17490
|
if (options.skill) {
|
|
17156
17491
|
const skillBasename = path46.basename(
|
|
@@ -17536,6 +17871,7 @@ __export(git_exports, {
|
|
|
17536
17871
|
listWorktrees: () => listWorktrees,
|
|
17537
17872
|
normalizeRepoUrlForCompare: () => normalizeRepoUrlForCompare,
|
|
17538
17873
|
pullRepo: () => pullRepo,
|
|
17874
|
+
pullRepoFastForward: () => pullRepoFastForward,
|
|
17539
17875
|
pushLearningToOrigin: () => pushLearningToOrigin,
|
|
17540
17876
|
pushRepoBranch: () => pushRepoBranch,
|
|
17541
17877
|
pushRepoDirectly: () => pushRepoDirectly,
|
|
@@ -17674,6 +18010,14 @@ async function pullRepo(localPath) {
|
|
|
17674
18010
|
return "reset to origin (diverged)";
|
|
17675
18011
|
}
|
|
17676
18012
|
}
|
|
18013
|
+
async function pullRepoFastForward(localPath) {
|
|
18014
|
+
const git = createGit2(localPath);
|
|
18015
|
+
const result = await git.pull(["--ff-only"]);
|
|
18016
|
+
if (result.summary.changes === 0 && result.summary.insertions === 0 && result.summary.deletions === 0) {
|
|
18017
|
+
return "already up to date";
|
|
18018
|
+
}
|
|
18019
|
+
return `${result.summary.changes} file(s) changed`;
|
|
18020
|
+
}
|
|
17677
18021
|
async function getDefaultBranch(localPath) {
|
|
17678
18022
|
const cached = defaultBranchCache.get(localPath);
|
|
17679
18023
|
if (cached) return cached;
|
|
@@ -18249,19 +18593,19 @@ async function bootstrapSelfRepo(dir, opts) {
|
|
|
18249
18593
|
log.debug(`[bootstrap] hook injection failed (non-blocking): ${e.message}`);
|
|
18250
18594
|
}
|
|
18251
18595
|
try {
|
|
18252
|
-
const {
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18596
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
18597
|
+
await updateReports2(localConfig, async (wt) => {
|
|
18598
|
+
const memberDir = path49.join(wt, "members");
|
|
18599
|
+
await ensureDir(memberDir);
|
|
18600
|
+
const memberPath = path49.join(memberDir, `${username}.yaml`);
|
|
18601
|
+
if (await pathExists(memberPath)) return null;
|
|
18258
18602
|
await writeFile(memberPath, YAML14.stringify({
|
|
18259
18603
|
username,
|
|
18260
18604
|
displayName: username,
|
|
18261
18605
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18262
18606
|
}));
|
|
18263
|
-
|
|
18264
|
-
}
|
|
18607
|
+
return { files: ["members/"], message: `[teamai] Register member: ${username}` };
|
|
18608
|
+
});
|
|
18265
18609
|
} catch (e) {
|
|
18266
18610
|
log.debug(`[bootstrap] member registration skipped (non-blocking): ${e.message}`);
|
|
18267
18611
|
}
|
|
@@ -18529,68 +18873,6 @@ var init_config = __esm({
|
|
|
18529
18873
|
}
|
|
18530
18874
|
});
|
|
18531
18875
|
|
|
18532
|
-
// src/utils/exec.ts
|
|
18533
|
-
import spawn from "cross-spawn";
|
|
18534
|
-
async function probeBinary(binary, args = ["--version"], executor = execCommand) {
|
|
18535
|
-
try {
|
|
18536
|
-
const result = await executor(binary, args, { timeoutMs: 5e3 });
|
|
18537
|
-
if (result.code !== 0) return "";
|
|
18538
|
-
return (result.stdout || result.stderr).trim();
|
|
18539
|
-
} catch {
|
|
18540
|
-
return "";
|
|
18541
|
-
}
|
|
18542
|
-
}
|
|
18543
|
-
function formatCommand(command, args) {
|
|
18544
|
-
const quote = (value) => /^[a-zA-Z0-9_./:@=+-]+$/.test(value) ? value : JSON.stringify(value);
|
|
18545
|
-
return [command, ...args.map(quote)].join(" ");
|
|
18546
|
-
}
|
|
18547
|
-
var execCommand;
|
|
18548
|
-
var init_exec = __esm({
|
|
18549
|
-
"src/utils/exec.ts"() {
|
|
18550
|
-
"use strict";
|
|
18551
|
-
execCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
18552
|
-
const child = spawn(command, args, {
|
|
18553
|
-
cwd: options.cwd,
|
|
18554
|
-
env: options.env,
|
|
18555
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
18556
|
-
shell: false
|
|
18557
|
-
});
|
|
18558
|
-
let stdout = "";
|
|
18559
|
-
let stderr = "";
|
|
18560
|
-
let settled = false;
|
|
18561
|
-
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
18562
|
-
const finish = (callback) => {
|
|
18563
|
-
if (settled) return;
|
|
18564
|
-
settled = true;
|
|
18565
|
-
clearTimeout(timer);
|
|
18566
|
-
callback();
|
|
18567
|
-
};
|
|
18568
|
-
child.stdout?.on("data", (chunk) => {
|
|
18569
|
-
const text = chunk.toString();
|
|
18570
|
-
stdout += text;
|
|
18571
|
-
if (options.stream) process.stdout.write(text);
|
|
18572
|
-
});
|
|
18573
|
-
child.stderr?.on("data", (chunk) => {
|
|
18574
|
-
const text = chunk.toString();
|
|
18575
|
-
stderr += text;
|
|
18576
|
-
if (options.stream) process.stderr.write(text);
|
|
18577
|
-
});
|
|
18578
|
-
const timer = setTimeout(() => {
|
|
18579
|
-
child.kill();
|
|
18580
|
-
finish(() => reject(new Error(
|
|
18581
|
-
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`
|
|
18582
|
-
)));
|
|
18583
|
-
}, timeoutMs);
|
|
18584
|
-
child.on("error", (error) => finish(() => reject(error)));
|
|
18585
|
-
child.on("close", (code) => finish(() => resolve({
|
|
18586
|
-
stdout,
|
|
18587
|
-
stderr,
|
|
18588
|
-
code: code ?? 1
|
|
18589
|
-
})));
|
|
18590
|
-
});
|
|
18591
|
-
}
|
|
18592
|
-
});
|
|
18593
|
-
|
|
18594
18876
|
// src/pkg/adapters/base.ts
|
|
18595
18877
|
var PackageAdapter;
|
|
18596
18878
|
var init_base2 = __esm({
|
|
@@ -19596,9 +19878,9 @@ async function verifyStaging(legacyDir, staging) {
|
|
|
19596
19878
|
throw new Error("migration verify: team-repo/.git missing after copy (clone would be broken)");
|
|
19597
19879
|
}
|
|
19598
19880
|
try {
|
|
19599
|
-
const { execFile:
|
|
19881
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
19600
19882
|
const { promisify: promisify4 } = await import("util");
|
|
19601
|
-
await promisify4(
|
|
19883
|
+
await promisify4(execFile4)("git", ["rev-parse", "HEAD"], { cwd: stagedRepo });
|
|
19602
19884
|
} catch (e) {
|
|
19603
19885
|
throw new Error(
|
|
19604
19886
|
`migration verify: the staged team-repo clone is not a usable git repository (${e.message})`
|
|
@@ -19983,6 +20265,7 @@ var init_tokenizer = __esm({
|
|
|
19983
20265
|
// src/votes.ts
|
|
19984
20266
|
var votes_exports = {};
|
|
19985
20267
|
__export(votes_exports, {
|
|
20268
|
+
hasPendingVoteDeltas: () => hasPendingVoteDeltas,
|
|
19986
20269
|
incrementRecalled: () => incrementRecalled,
|
|
19987
20270
|
incrementUpvoted: () => incrementUpvoted,
|
|
19988
20271
|
loadUserVotes: () => loadUserVotes,
|
|
@@ -20097,6 +20380,10 @@ function mergeDeltas(local, remote) {
|
|
|
20097
20380
|
}
|
|
20098
20381
|
return { version: 2, votes, deltas: {} };
|
|
20099
20382
|
}
|
|
20383
|
+
async function hasPendingVoteDeltas(localVotesDir, username) {
|
|
20384
|
+
const local = await loadUserVotes(path57.join(localVotesDir, `${username}.yaml`));
|
|
20385
|
+
return Object.keys(local.deltas).length > 0;
|
|
20386
|
+
}
|
|
20100
20387
|
async function syncVotesToTeam(repoPath, username, localVotesDir) {
|
|
20101
20388
|
const localVotePath = path57.join(localVotesDir, `${username}.yaml`);
|
|
20102
20389
|
const remoteVotePath = path57.join(repoPath, "votes", `${username}.yaml`);
|
|
@@ -22537,10 +22824,67 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22537
22824
|
const hasInterventions = hasInterventionDelta(interventionDelta);
|
|
22538
22825
|
const hasPromptTokens = hasPromptTokenDelta(promptTokenDelta);
|
|
22539
22826
|
const hasDaily = hasDailyDelta(dailyDelta);
|
|
22540
|
-
|
|
22827
|
+
const hasStats = hasUsage || hasInterventions || hasPromptTokens || hasDaily;
|
|
22828
|
+
const commitMsg = hasUsage ? `[teamai] Update usage stats for ${username}` : hasInterventions || hasPromptTokens || hasDaily ? `[teamai] Update session stats for ${username}` : `[teamai] Update votes for ${username}`;
|
|
22829
|
+
const writeReportFiles = async (writeRoot) => {
|
|
22830
|
+
if (hasStats) {
|
|
22831
|
+
const statsDir = path64.join(writeRoot, "stats");
|
|
22832
|
+
await ensureDir(statsDir);
|
|
22833
|
+
const statsPath = path64.join(statsDir, `${username}.yaml`);
|
|
22834
|
+
const existing = await readExistingStats(statsPath);
|
|
22835
|
+
if (useReportsBranch) {
|
|
22836
|
+
const previousContent = await readFileSafe(statsPath);
|
|
22837
|
+
restoreStats = () => writeFile(statsPath, previousContent ?? "");
|
|
22838
|
+
}
|
|
22839
|
+
const newStats = hasUsage ? aggregateUsage(events) : [];
|
|
22840
|
+
const merged = mergeStats(existing, username, newStats);
|
|
22841
|
+
if (hasInterventions) {
|
|
22842
|
+
merged.interventions = mergeInterventionStats(existing?.interventions, interventionDelta);
|
|
22843
|
+
}
|
|
22844
|
+
if (hasPromptTokens) {
|
|
22845
|
+
const pt = mergePromptTokenStats(existing?.prompts, existing?.tokens, promptTokenDelta);
|
|
22846
|
+
merged.prompts = pt.prompts;
|
|
22847
|
+
merged.tokens = pt.tokens;
|
|
22848
|
+
}
|
|
22849
|
+
if (hasDaily) {
|
|
22850
|
+
merged.daily = mergeDailyStats(existing?.daily, dailyDelta);
|
|
22851
|
+
}
|
|
22852
|
+
await writeFile(statsPath, YAML21.stringify(merged));
|
|
22853
|
+
filesToPush.push(`stats/${username}.yaml`);
|
|
22854
|
+
}
|
|
22855
|
+
try {
|
|
22856
|
+
if (await pathExists(getUserVotesDir())) {
|
|
22857
|
+
const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
22858
|
+
const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
|
|
22859
|
+
if (synced) {
|
|
22860
|
+
filesToPush.push(`votes/${username}.yaml`);
|
|
22861
|
+
}
|
|
22862
|
+
}
|
|
22863
|
+
} catch (e) {
|
|
22864
|
+
log.error(`Vote staging skipped: ${e.message}`);
|
|
22865
|
+
}
|
|
22866
|
+
};
|
|
22541
22867
|
if (useReportsBranch && reportsConfig) {
|
|
22542
|
-
|
|
22543
|
-
|
|
22868
|
+
let hasVotes = false;
|
|
22869
|
+
if (!hasStats && await pathExists(getUserVotesDir())) {
|
|
22870
|
+
const { hasPendingVoteDeltas: hasPendingVoteDeltas2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
22871
|
+
hasVotes = await hasPendingVoteDeltas2(getUserVotesDir(), username);
|
|
22872
|
+
}
|
|
22873
|
+
if (!hasStats && !hasVotes) {
|
|
22874
|
+
log.debug("No usage events or votes to report");
|
|
22875
|
+
return true;
|
|
22876
|
+
}
|
|
22877
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22878
|
+
const pushed = await updateReports2(reportsConfig, async (wt) => {
|
|
22879
|
+
filesToPush.length = 0;
|
|
22880
|
+
await writeReportFiles(wt);
|
|
22881
|
+
return filesToPush.length > 0 ? { files: [...filesToPush], message: commitMsg } : null;
|
|
22882
|
+
}, { pushIfUnchanged: true });
|
|
22883
|
+
if (!pushed) {
|
|
22884
|
+
log.debug("Auto-report push was not confirmed; keeping local report data");
|
|
22885
|
+
await restoreStats?.();
|
|
22886
|
+
return false;
|
|
22887
|
+
}
|
|
22544
22888
|
} else {
|
|
22545
22889
|
const git = createGit2(repoPath);
|
|
22546
22890
|
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
@@ -22564,56 +22908,11 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22564
22908
|
await writeFile(yamlPath, pendingTeamConfig);
|
|
22565
22909
|
}
|
|
22566
22910
|
}
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
const statsPath = path64.join(statsDir, `${username}.yaml`);
|
|
22572
|
-
const existing = await readExistingStats(statsPath);
|
|
22573
|
-
if (useReportsBranch) {
|
|
22574
|
-
const previousContent = await readFileSafe(statsPath);
|
|
22575
|
-
restoreStats = () => writeFile(statsPath, previousContent ?? "");
|
|
22576
|
-
}
|
|
22577
|
-
const newStats = hasUsage ? aggregateUsage(events) : [];
|
|
22578
|
-
const merged = mergeStats(existing, username, newStats);
|
|
22579
|
-
if (hasInterventions) {
|
|
22580
|
-
merged.interventions = mergeInterventionStats(existing?.interventions, interventionDelta);
|
|
22581
|
-
}
|
|
22582
|
-
if (hasPromptTokens) {
|
|
22583
|
-
const pt = mergePromptTokenStats(existing?.prompts, existing?.tokens, promptTokenDelta);
|
|
22584
|
-
merged.prompts = pt.prompts;
|
|
22585
|
-
merged.tokens = pt.tokens;
|
|
22586
|
-
}
|
|
22587
|
-
if (hasDaily) {
|
|
22588
|
-
merged.daily = mergeDailyStats(existing?.daily, dailyDelta);
|
|
22589
|
-
}
|
|
22590
|
-
await writeFile(statsPath, YAML21.stringify(merged));
|
|
22591
|
-
filesToPush.push(`stats/${username}.yaml`);
|
|
22592
|
-
}
|
|
22593
|
-
try {
|
|
22594
|
-
if (await pathExists(getUserVotesDir())) {
|
|
22595
|
-
const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
22596
|
-
const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
|
|
22597
|
-
if (synced) {
|
|
22598
|
-
filesToPush.push(`votes/${username}.yaml`);
|
|
22599
|
-
}
|
|
22600
|
-
}
|
|
22601
|
-
} catch (e) {
|
|
22602
|
-
log.error(`Vote staging skipped: ${e.message}`);
|
|
22603
|
-
}
|
|
22604
|
-
if (filesToPush.length === 0) {
|
|
22605
|
-
log.debug("No usage events or votes to report");
|
|
22606
|
-
return true;
|
|
22607
|
-
}
|
|
22608
|
-
const commitMsg = hasUsage ? `[teamai] Update usage stats for ${username}` : hasInterventions || hasPromptTokens || hasDaily ? `[teamai] Update session stats for ${username}` : `[teamai] Update votes for ${username}`;
|
|
22609
|
-
if (useReportsBranch && reportsConfig) {
|
|
22610
|
-
const { commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22611
|
-
if (!await commitAndPushReports2(reportsConfig, commitMsg, filesToPush, { pushIfUnchanged: true })) {
|
|
22612
|
-
log.debug("Auto-report push was not confirmed; keeping local report data");
|
|
22613
|
-
await restoreStats?.();
|
|
22614
|
-
return false;
|
|
22911
|
+
await writeReportFiles(repoPath);
|
|
22912
|
+
if (filesToPush.length === 0) {
|
|
22913
|
+
log.debug("No usage events or votes to report");
|
|
22914
|
+
return true;
|
|
22615
22915
|
}
|
|
22616
|
-
} else {
|
|
22617
22916
|
await pushRepoDirectly(repoPath, commitMsg, filesToPush);
|
|
22618
22917
|
}
|
|
22619
22918
|
restoreStats = void 0;
|
|
@@ -22836,6 +23135,7 @@ __export(pull_exports, {
|
|
|
22836
23135
|
compileClaudemd: () => compileClaudemd,
|
|
22837
23136
|
compileCulture: () => compileCulture,
|
|
22838
23137
|
compileRecallRulesBlock: () => compileRecallRulesBlock,
|
|
23138
|
+
filterAgentsByNamespaces: () => filterAgentsByNamespaces,
|
|
22839
23139
|
filterRulesByKnowledgeNamespaces: () => filterRulesByKnowledgeNamespaces,
|
|
22840
23140
|
injectRecallBlockIntoTools: () => injectRecallBlockIntoTools,
|
|
22841
23141
|
pull: () => pull,
|
|
@@ -22913,58 +23213,9 @@ async function usageReportDisabled(repoPath) {
|
|
|
22913
23213
|
return (await loadTeamConfig(repoPath))?.usageReport === false;
|
|
22914
23214
|
}
|
|
22915
23215
|
async function buildRolePullContext(localConfig) {
|
|
22916
|
-
const
|
|
22917
|
-
|
|
22918
|
-
const
|
|
22919
|
-
const projectsManifest = await loadProjectsManifest(localConfig.repo.localPath);
|
|
22920
|
-
const teamHasProjects = !!projectsManifest && projectsManifest.projects.length > 0;
|
|
22921
|
-
if (!hasRole && !hasProjects && !teamHasProjects) return null;
|
|
22922
|
-
let roleNamespaces = { knowledge: [], skills: [], learnings: [] };
|
|
22923
|
-
let allRoleSkillNamespaces = /* @__PURE__ */ new Set();
|
|
22924
|
-
if (hasRole) {
|
|
22925
|
-
let rolesManifest;
|
|
22926
|
-
try {
|
|
22927
|
-
rolesManifest = await loadRolesManifest(localConfig.repo.localPath);
|
|
22928
|
-
} catch {
|
|
22929
|
-
log.warn("Could not load roles manifest. Skipping role-based filtering.");
|
|
22930
|
-
rolesManifest = null;
|
|
22931
|
-
}
|
|
22932
|
-
if (rolesManifest) {
|
|
22933
|
-
try {
|
|
22934
|
-
roleNamespaces = resolveRoleResourceNamespaces({
|
|
22935
|
-
manifest: rolesManifest,
|
|
22936
|
-
primaryRole: localConfig.primaryRole,
|
|
22937
|
-
additionalRoles: localConfig.additionalRoles ?? []
|
|
22938
|
-
});
|
|
22939
|
-
allRoleSkillNamespaces = new Set(rolesManifest.roles.flatMap((role) => role.resources.skills));
|
|
22940
|
-
} catch {
|
|
22941
|
-
log.warn(`Role "${localConfig.primaryRole}" not found in manifest. Falling back to unfiltered sync.`);
|
|
22942
|
-
log.warn("Run `teamai roles set <role>` to pick a valid role.");
|
|
22943
|
-
if (!hasProjects && !teamHasProjects) return null;
|
|
22944
|
-
}
|
|
22945
|
-
} else if (!hasProjects && !teamHasProjects) {
|
|
22946
|
-
return null;
|
|
22947
|
-
}
|
|
22948
|
-
}
|
|
22949
|
-
let projectNamespaces = { knowledge: [], skills: [], learnings: [] };
|
|
22950
|
-
let allProjectSkillNamespaces = /* @__PURE__ */ new Set();
|
|
22951
|
-
if (projectsManifest) {
|
|
22952
|
-
allProjectSkillNamespaces = new Set(projectsManifest.projects.flatMap((p) => p.resources.skills));
|
|
22953
|
-
if (hasProjects) {
|
|
22954
|
-
try {
|
|
22955
|
-
projectNamespaces = resolveProjectResourceNamespaces({
|
|
22956
|
-
manifest: projectsManifest,
|
|
22957
|
-
activeProjects
|
|
22958
|
-
});
|
|
22959
|
-
} catch (e) {
|
|
22960
|
-
log.warn(`${e.message} Falling back to role-only filtering.`);
|
|
22961
|
-
}
|
|
22962
|
-
}
|
|
22963
|
-
} else if (hasProjects) {
|
|
22964
|
-
log.warn("Active projects configured but no projects manifest found. Skipping project-based filtering.");
|
|
22965
|
-
}
|
|
22966
|
-
const activeNamespaces = mergeNamespaces(roleNamespaces, projectNamespaces);
|
|
22967
|
-
const allSkillNamespaces = /* @__PURE__ */ new Set([...allRoleSkillNamespaces, ...allProjectSkillNamespaces]);
|
|
23216
|
+
const resolved = await resolveResourceNamespaces(localConfig);
|
|
23217
|
+
if (!resolved) return null;
|
|
23218
|
+
const { activeNamespaces, allSkillNamespaces } = resolved;
|
|
22968
23219
|
const inactiveSkillNamespaces = [...allSkillNamespaces].filter((namespace) => !activeNamespaces.skills.includes(namespace));
|
|
22969
23220
|
const activeSkillNames = /* @__PURE__ */ new Set();
|
|
22970
23221
|
const inactiveSkillNames = /* @__PURE__ */ new Set();
|
|
@@ -22997,6 +23248,20 @@ function filterRulesByKnowledgeNamespaces(rules, knowledgeNamespaces) {
|
|
|
22997
23248
|
return knowledgeNamespaces.includes(namespace);
|
|
22998
23249
|
});
|
|
22999
23250
|
}
|
|
23251
|
+
function filterAgentsByNamespaces(agents, agentNamespaces) {
|
|
23252
|
+
const kept = agentNamespaces ? agents.filter((agent) => !agent.namespace || agentNamespaces.includes(agent.namespace)) : agents;
|
|
23253
|
+
const seen = /* @__PURE__ */ new Map();
|
|
23254
|
+
for (const agent of kept) {
|
|
23255
|
+
const existing = seen.get(agent.name);
|
|
23256
|
+
if (existing) {
|
|
23257
|
+
throw new Error(
|
|
23258
|
+
`Duplicate agent "${agent.name}" found in active namespaces "${existing.namespace ?? "(root)"}" and "${agent.namespace ?? "(root)"}"`
|
|
23259
|
+
);
|
|
23260
|
+
}
|
|
23261
|
+
seen.set(agent.name, agent);
|
|
23262
|
+
}
|
|
23263
|
+
return kept;
|
|
23264
|
+
}
|
|
23000
23265
|
async function scanRoleAwareSkills(localConfig, namespaces) {
|
|
23001
23266
|
const items = /* @__PURE__ */ new Map();
|
|
23002
23267
|
for (const namespace of namespaces.skills) {
|
|
@@ -23098,6 +23363,42 @@ async function getInstalledResourceTargets(teamConfig, localConfig) {
|
|
|
23098
23363
|
}
|
|
23099
23364
|
return targets.sort();
|
|
23100
23365
|
}
|
|
23366
|
+
function tombstoneExtensions(type, tool) {
|
|
23367
|
+
if (type === "rules") return [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])];
|
|
23368
|
+
if (type === "agents") return AGENT_FILE_EXTENSIONS;
|
|
23369
|
+
return [""];
|
|
23370
|
+
}
|
|
23371
|
+
async function cleanupTombstonedResources(freshConfig, localConfig, scopeLabel) {
|
|
23372
|
+
const tombstoneTypes = [
|
|
23373
|
+
{ type: "rules", toolPathField: "rules" },
|
|
23374
|
+
{ type: "skills", toolPathField: "skills" },
|
|
23375
|
+
{ type: "agents", toolPathField: "agents" }
|
|
23376
|
+
];
|
|
23377
|
+
const baseDir = resolveBaseDir(localConfig);
|
|
23378
|
+
for (const { type, toolPathField } of tombstoneTypes) {
|
|
23379
|
+
const handler = getHandler(type);
|
|
23380
|
+
const tombstones = await handler.readTombstones(localConfig);
|
|
23381
|
+
if (tombstones.size === 0) continue;
|
|
23382
|
+
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
23383
|
+
const dir = toolPath[toolPathField];
|
|
23384
|
+
if (!dir) continue;
|
|
23385
|
+
if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
|
|
23386
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
23387
|
+
for (const name of tombstones) {
|
|
23388
|
+
for (const extension of tombstoneExtensions(type, tool)) {
|
|
23389
|
+
const localPath = path66.join(baseDir, dir, `${name}${extension}`);
|
|
23390
|
+
if (!await pathExists(localPath)) continue;
|
|
23391
|
+
if (type === "skills" && await hasVcsMetadataRecursive(localPath)) {
|
|
23392
|
+
log.warn(`[${scopeLabel}] Kept tombstoned skill "${name}" (${tool}): it has local VCS metadata (.git) that may hold unpushed history. Back it up, then delete it manually.`);
|
|
23393
|
+
continue;
|
|
23394
|
+
}
|
|
23395
|
+
await remove(localPath);
|
|
23396
|
+
log.debug(`[${scopeLabel}] Cleaned up tombstoned ${type} ${name} from ${dir}`);
|
|
23397
|
+
}
|
|
23398
|
+
}
|
|
23399
|
+
}
|
|
23400
|
+
}
|
|
23401
|
+
}
|
|
23101
23402
|
async function pullForScope(localConfig, options, policy = {}) {
|
|
23102
23403
|
const scopeLabel = localConfig.scope;
|
|
23103
23404
|
const revisionField = policy.revisionField ?? "lastPullRev";
|
|
@@ -23151,6 +23452,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23151
23452
|
} catch {
|
|
23152
23453
|
}
|
|
23153
23454
|
await injectRecallBlockIntoTools(freshConfig, localConfig, scopeLabel);
|
|
23455
|
+
await cleanupTombstonedResources(freshConfig, localConfig, scopeLabel);
|
|
23154
23456
|
return;
|
|
23155
23457
|
}
|
|
23156
23458
|
log.debug(`[${scopeLabel}] Repo unchanged; resource target set changed, syncing`);
|
|
@@ -23223,6 +23525,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23223
23525
|
desiredSkillNames = new Set(items.map((i) => i.name));
|
|
23224
23526
|
knownRepoSkillNames = new Set(allTeamSkills.map((i) => i.name));
|
|
23225
23527
|
knownRepoSkillSources = new Map(allTeamSkills.map((i) => [i.name, i.sourcePath]));
|
|
23528
|
+
} else if (type === "agents") {
|
|
23529
|
+
items = filterAgentsByNamespaces(
|
|
23530
|
+
await handler.scanTeamForPull(freshConfig, localConfig),
|
|
23531
|
+
roleContext ? roleContext.activeNamespaces.agents : null
|
|
23532
|
+
);
|
|
23226
23533
|
} else {
|
|
23227
23534
|
items = await handler.scanTeamForPull(freshConfig, localConfig);
|
|
23228
23535
|
}
|
|
@@ -23281,37 +23588,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23281
23588
|
totalSynced += items.length;
|
|
23282
23589
|
}
|
|
23283
23590
|
if (!options.dryRun) {
|
|
23284
|
-
|
|
23285
|
-
{ type: "rules", ext: ".md", toolPathField: "rules" },
|
|
23286
|
-
{ type: "skills", toolPathField: "skills" },
|
|
23287
|
-
{ type: "agents", ext: ".md", toolPathField: "agents" }
|
|
23288
|
-
];
|
|
23289
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
23290
|
-
for (const { type, ext, toolPathField } of tombstoneTypes) {
|
|
23291
|
-
const handler = getHandler(type);
|
|
23292
|
-
const tombstones = await handler.readTombstones(localConfig);
|
|
23293
|
-
if (tombstones.size === 0) continue;
|
|
23294
|
-
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
23295
|
-
const dir = toolPath[toolPathField];
|
|
23296
|
-
if (!dir) continue;
|
|
23297
|
-
if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
|
|
23298
|
-
if (isAgentExcluded(localConfig, tool)) continue;
|
|
23299
|
-
const extensions = type === "rules" ? [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])] : [ext];
|
|
23300
|
-
for (const name of tombstones) {
|
|
23301
|
-
for (const extension of extensions) {
|
|
23302
|
-
const localPath = path66.join(baseDir, dir, extension ? `${name}${extension}` : name);
|
|
23303
|
-
if (await pathExists(localPath)) {
|
|
23304
|
-
if (type === "skills" && await hasVcsMetadataRecursive(localPath)) {
|
|
23305
|
-
log.warn(`[${scopeLabel}] Kept tombstoned skill "${name}" (${tool}): it has local VCS metadata (.git) that may hold unpushed history. Back it up, then delete it manually.`);
|
|
23306
|
-
continue;
|
|
23307
|
-
}
|
|
23308
|
-
await remove(localPath);
|
|
23309
|
-
log.debug(`[${scopeLabel}] Cleaned up tombstoned ${type} ${name} from ${dir}`);
|
|
23310
|
-
}
|
|
23311
|
-
}
|
|
23312
|
-
}
|
|
23313
|
-
}
|
|
23314
|
-
}
|
|
23591
|
+
await cleanupTombstonedResources(freshConfig, localConfig, scopeLabel);
|
|
23315
23592
|
if (roleContext) {
|
|
23316
23593
|
await cleanupInactiveNamespaceSkills(
|
|
23317
23594
|
freshConfig,
|
|
@@ -23320,6 +23597,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23320
23597
|
roleContext.inactiveSkillNames,
|
|
23321
23598
|
roleContext.inactiveSkillSources
|
|
23322
23599
|
);
|
|
23600
|
+
await getHandler("agents").cleanupInactiveNamespaces(
|
|
23601
|
+
freshConfig,
|
|
23602
|
+
localConfig,
|
|
23603
|
+
roleContext.activeNamespaces.agents
|
|
23604
|
+
);
|
|
23323
23605
|
}
|
|
23324
23606
|
}
|
|
23325
23607
|
if (!options.dryRun && desiredSkillNames && knownRepoSkillNames) {
|
|
@@ -24034,11 +24316,11 @@ var init_pull = __esm({
|
|
|
24034
24316
|
init_resources();
|
|
24035
24317
|
init_base();
|
|
24036
24318
|
init_rule_format();
|
|
24319
|
+
init_agent_format();
|
|
24037
24320
|
init_tags();
|
|
24038
24321
|
init_builtin_skills();
|
|
24039
24322
|
init_types();
|
|
24040
|
-
|
|
24041
|
-
init_projects();
|
|
24323
|
+
init_resource_namespaces();
|
|
24042
24324
|
init_home();
|
|
24043
24325
|
init_update();
|
|
24044
24326
|
init_learnings_mirror();
|
|
@@ -24898,17 +25180,24 @@ async function doctor(options) {
|
|
|
24898
25180
|
log.info("Running diagnostics...\n");
|
|
24899
25181
|
const projectConfig = await detectProjectConfig();
|
|
24900
25182
|
const localConfig = projectConfig ?? await loadLocalConfig();
|
|
24901
|
-
|
|
24902
|
-
|
|
24903
|
-
|
|
24904
|
-
`);
|
|
24905
|
-
|
|
24906
|
-
|
|
24907
|
-
|
|
25183
|
+
if (!localConfig) {
|
|
25184
|
+
console.log(" Scope: not initialized\n");
|
|
25185
|
+
console.log(" \u2716 TeamAI is not initialized");
|
|
25186
|
+
console.log(" \u2192 Run `teamai init <repo-url>` in a project, or add `--scope user` for all projects");
|
|
25187
|
+
console.log("");
|
|
25188
|
+
log.warn("Initialization is required before diagnostics can run.");
|
|
25189
|
+
return false;
|
|
24908
25190
|
}
|
|
24909
|
-
const
|
|
24910
|
-
const
|
|
24911
|
-
|
|
25191
|
+
const scope = localConfig.scope ?? "user";
|
|
25192
|
+
const scopeLabel = `${scope}${scope === "project" && localConfig.projectRoot ? ` (${localConfig.projectRoot})` : ""}`;
|
|
25193
|
+
console.log(` Scope: ${scopeLabel}
|
|
25194
|
+
`);
|
|
25195
|
+
const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
25196
|
+
const toolPaths = teamConfig ? Object.fromEntries(
|
|
25197
|
+
Object.entries(scopedToolPaths(teamConfig, localConfig)).filter(([tool]) => !isAgentExcluded(localConfig, tool))
|
|
25198
|
+
) : {};
|
|
25199
|
+
const providerName = teamConfig?.provider;
|
|
25200
|
+
const baseDir = resolveHookScope(localConfig).baseDir;
|
|
24912
25201
|
const checks = [];
|
|
24913
25202
|
if (providerName === "tgit") {
|
|
24914
25203
|
const { isGfInstalled: isGfInstalled2, gfIsAuthenticated: gfIsAuthenticated2 } = await Promise.resolve().then(() => (init_tgit(), tgit_exports));
|
|
@@ -24954,23 +25243,14 @@ async function doctor(options) {
|
|
|
24954
25243
|
});
|
|
24955
25244
|
}
|
|
24956
25245
|
checks.push(
|
|
24957
|
-
{
|
|
24958
|
-
name: `Local config exists (${configPathLabel})`,
|
|
24959
|
-
check: async () => localConfig !== null,
|
|
24960
|
-
fix: "Run `teamai init` to initialize"
|
|
24961
|
-
},
|
|
24962
25246
|
{
|
|
24963
25247
|
name: "Team repo exists locally",
|
|
24964
|
-
check: async () =>
|
|
24965
|
-
if (!localConfig) return false;
|
|
24966
|
-
return pathExists(localConfig.repo.localPath);
|
|
24967
|
-
},
|
|
25248
|
+
check: async () => pathExists(localConfig.repo.localPath),
|
|
24968
25249
|
fix: "Run `teamai init` to clone the team repo"
|
|
24969
25250
|
},
|
|
24970
25251
|
{
|
|
24971
25252
|
name: "Team config (teamai.yaml) is valid",
|
|
24972
25253
|
check: async () => {
|
|
24973
|
-
if (!localConfig) return false;
|
|
24974
25254
|
const config = await loadTeamConfig(localConfig.repo.localPath);
|
|
24975
25255
|
return config !== null;
|
|
24976
25256
|
},
|
|
@@ -24981,7 +25261,6 @@ async function doctor(options) {
|
|
|
24981
25261
|
name: "Env variables injected in shell profile",
|
|
24982
25262
|
check: async () => {
|
|
24983
25263
|
if (teamConfig?.sharing?.env?.injectShellProfile === false) return true;
|
|
24984
|
-
if (!localConfig) return true;
|
|
24985
25264
|
const envYamlPath = path70.join(localConfig.repo.localPath, "env", "env.yaml");
|
|
24986
25265
|
if (!await pathExists(envYamlPath)) return true;
|
|
24987
25266
|
const home = getUserHome();
|
|
@@ -25010,13 +25289,11 @@ async function doctor(options) {
|
|
|
25010
25289
|
allPassed = false;
|
|
25011
25290
|
}
|
|
25012
25291
|
}
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
if (!packageReport.allPassed) allPassed = false;
|
|
25019
|
-
}
|
|
25292
|
+
const { pkgDoctorReport: pkgDoctorReport2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
25293
|
+
const packageReport = await pkgDoctorReport2(localConfig, process.cwd());
|
|
25294
|
+
if (packageReport) {
|
|
25295
|
+
for (const line of packageReport.lines) console.log(line);
|
|
25296
|
+
if (!packageReport.allPassed) allPassed = false;
|
|
25020
25297
|
}
|
|
25021
25298
|
if (await hasInstalledCodexHooks(toolPaths, baseDir)) {
|
|
25022
25299
|
console.log("");
|
|
@@ -25028,6 +25305,7 @@ async function doctor(options) {
|
|
|
25028
25305
|
} else {
|
|
25029
25306
|
log.warn("Some checks failed. See suggestions above.");
|
|
25030
25307
|
}
|
|
25308
|
+
return allPassed;
|
|
25031
25309
|
}
|
|
25032
25310
|
var init_doctor = __esm({
|
|
25033
25311
|
"src/doctor.ts"() {
|
|
@@ -25152,7 +25430,8 @@ async function rolesInit(options) {
|
|
|
25152
25430
|
description,
|
|
25153
25431
|
resources: {
|
|
25154
25432
|
knowledge: namespaces,
|
|
25155
|
-
skills: namespaces
|
|
25433
|
+
skills: namespaces,
|
|
25434
|
+
agents: namespaces
|
|
25156
25435
|
}
|
|
25157
25436
|
});
|
|
25158
25437
|
log.success(`Added role: ${id} (namespaces: ${namespaces.join(", ")})`);
|
|
@@ -25296,7 +25575,8 @@ async function rolesAdd(roleId, options) {
|
|
|
25296
25575
|
description: options.description ?? "",
|
|
25297
25576
|
resources: {
|
|
25298
25577
|
knowledge: namespaces,
|
|
25299
|
-
skills: namespaces
|
|
25578
|
+
skills: namespaces,
|
|
25579
|
+
agents: namespaces
|
|
25300
25580
|
}
|
|
25301
25581
|
};
|
|
25302
25582
|
const updatedManifest = {
|
|
@@ -25410,7 +25690,8 @@ async function rolesUpdate(roleId, options) {
|
|
|
25410
25690
|
description: hasDesc ? options.description : existingRole.description,
|
|
25411
25691
|
resources: {
|
|
25412
25692
|
knowledge: updatedNamespaces,
|
|
25413
|
-
skills: updatedNamespaces
|
|
25693
|
+
skills: updatedNamespaces,
|
|
25694
|
+
agents: updatedNamespaces
|
|
25414
25695
|
}
|
|
25415
25696
|
};
|
|
25416
25697
|
const updatedManifest = {
|
|
@@ -25833,10 +26114,13 @@ async function collectTeamRuleNames(repoPath) {
|
|
|
25833
26114
|
async function collectTeamAgentNames(repoPath) {
|
|
25834
26115
|
const teamAgentsDir = path74.join(repoPath, "agents");
|
|
25835
26116
|
if (!await pathExists(teamAgentsDir)) return /* @__PURE__ */ new Set();
|
|
25836
|
-
const
|
|
25837
|
-
|
|
25838
|
-
|
|
25839
|
-
|
|
26117
|
+
const names = /* @__PURE__ */ new Set();
|
|
26118
|
+
for (const { dir } of await listTeamAgentDirs(teamAgentsDir)) {
|
|
26119
|
+
for (const file of await listFiles(dir)) {
|
|
26120
|
+
if (file.endsWith(".yaml") || file.endsWith(".md")) names.add(file.replace(/\.(yaml|md)$/, ""));
|
|
26121
|
+
}
|
|
26122
|
+
}
|
|
26123
|
+
return names;
|
|
25840
26124
|
}
|
|
25841
26125
|
function isEmptyHooksResidue(parsed) {
|
|
25842
26126
|
if (parsed == null || !("hooks" in parsed) || typeof parsed.hooks !== "object" || parsed.hooks == null) return false;
|
|
@@ -25936,8 +26220,8 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
25936
26220
|
const agentsDir = path74.join(baseDir, toolPath.agents);
|
|
25937
26221
|
if (await pathExists(agentsDir)) {
|
|
25938
26222
|
for (const file of await listFiles(agentsDir)) {
|
|
25939
|
-
|
|
25940
|
-
|
|
26223
|
+
const name = agentStemFromFilename(path74.basename(file));
|
|
26224
|
+
if (name === null) continue;
|
|
25941
26225
|
if (!teamAgentNames.has(name) && !BUILTIN_AGENT_NAMES.has(name)) continue;
|
|
25942
26226
|
res.agentFiles.push(path74.join(agentsDir, file));
|
|
25943
26227
|
}
|
|
@@ -26415,6 +26699,8 @@ var init_uninstall = __esm({
|
|
|
26415
26699
|
init_types();
|
|
26416
26700
|
init_builtin_rules();
|
|
26417
26701
|
init_rule_format();
|
|
26702
|
+
init_agent_format();
|
|
26703
|
+
init_agents();
|
|
26418
26704
|
init_builtin_agents();
|
|
26419
26705
|
init_builtin_skills();
|
|
26420
26706
|
init_fs();
|
|
@@ -26967,20 +27253,25 @@ async function saveSession(options) {
|
|
|
26967
27253
|
if (usesReportsBranch(localConfig)) {
|
|
26968
27254
|
const spin2 = spinner("Pushing session summary to team...").start();
|
|
26969
27255
|
try {
|
|
26970
|
-
const {
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
const written = await appendMonthlyLog(teamDir2, summary, { includePrompt: options.includePrompt });
|
|
26974
|
-
if (!written) {
|
|
26975
|
-
spin2.info("Session already present in the team log \u2014 nothing to push.");
|
|
26976
|
-
return;
|
|
26977
|
-
}
|
|
26978
|
-
const rel = path79.relative(wt, written);
|
|
27256
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
27257
|
+
let rel;
|
|
27258
|
+
let ran = false;
|
|
26979
27259
|
const pushed = await withTimeout(
|
|
26980
|
-
|
|
27260
|
+
updateReports2(localConfig, async (wt) => {
|
|
27261
|
+
ran = true;
|
|
27262
|
+
const teamDir2 = path79.join(wt, "sessions", username);
|
|
27263
|
+
const written = await appendMonthlyLog(teamDir2, summary, { includePrompt: options.includePrompt });
|
|
27264
|
+
if (!written) return null;
|
|
27265
|
+
rel = path79.relative(wt, written);
|
|
27266
|
+
return { files: [rel], message: commitMsg };
|
|
27267
|
+
}),
|
|
26981
27268
|
1e4,
|
|
26982
27269
|
"Push timeout (10s)"
|
|
26983
27270
|
);
|
|
27271
|
+
if (ran && !rel) {
|
|
27272
|
+
spin2.info("Session already present in the team log \u2014 nothing to push.");
|
|
27273
|
+
return;
|
|
27274
|
+
}
|
|
26984
27275
|
if (pushed) spin2.succeed(`Pushed: ${rel}`);
|
|
26985
27276
|
else spin2.info("Nothing new to push.");
|
|
26986
27277
|
} catch (e) {
|
|
@@ -31104,11 +31395,13 @@ var init_hook_handlers = __esm({
|
|
|
31104
31395
|
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31105
31396
|
if (usesReportsBranch2(localConfig)) {
|
|
31106
31397
|
try {
|
|
31107
|
-
const {
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
|
|
31398
|
+
const { hasPendingVoteDeltas: hasPendingVoteDeltas2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
31399
|
+
if (await hasPendingVoteDeltas2(votesDir, localConfig.username)) {
|
|
31400
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
31401
|
+
await updateReports2(localConfig, async (wt) => await syncVotesToTeam2(wt, localConfig.username, votesDir) ? {
|
|
31402
|
+
files: [`votes/${localConfig.username}.yaml`],
|
|
31403
|
+
message: `[teamai] Update votes for ${localConfig.username}`
|
|
31404
|
+
} : null);
|
|
31112
31405
|
}
|
|
31113
31406
|
} catch {
|
|
31114
31407
|
}
|
|
@@ -34828,7 +35121,7 @@ var init_codebase = __esm({
|
|
|
34828
35121
|
|
|
34829
35122
|
// src/wiki-engine/code-knowledge/code-collector.ts
|
|
34830
35123
|
import { createHash as createHash5 } from "crypto";
|
|
34831
|
-
import { execFile as
|
|
35124
|
+
import { execFile as execFile3 } from "child_process";
|
|
34832
35125
|
import { readFile as readFile6, readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
34833
35126
|
import path105 from "path";
|
|
34834
35127
|
import { promisify as promisify3 } from "util";
|
|
@@ -34984,7 +35277,7 @@ var init_code_collector = __esm({
|
|
|
34984
35277
|
"src/wiki-engine/code-knowledge/code-collector.ts"() {
|
|
34985
35278
|
"use strict";
|
|
34986
35279
|
init_wiki_protocol();
|
|
34987
|
-
execFileAsync3 = promisify3(
|
|
35280
|
+
execFileAsync3 = promisify3(execFile3);
|
|
34988
35281
|
KEY_FILE_PATTERNS = {
|
|
34989
35282
|
go: [/main\.go$/, /cmd\/.*\.go$/, /handler.*\.go$/, /server\.go$/, /router\.go$/],
|
|
34990
35283
|
python: [/main\.py$/, /app\.py$/, /server\.py$/, /routes?\.py$/, /models?\.py$/],
|
|
@@ -42953,7 +43246,7 @@ program.name("teamai").description("TeamAI \u2014 Make Every Team AI Native").ve
|
|
|
42953
43246
|
process.exit(1);
|
|
42954
43247
|
}
|
|
42955
43248
|
});
|
|
42956
|
-
program.command("init").description("Initialize teamai (configure Git provider, clone repo, register member)").argument("[repo]", 'Team repo (owner/repo or full URL). Pass "." for single-repo mode (the current git repo is the team repo).').option("--repo <repo>", "Team repo (alias of the positional argument)").option("--http <url>", "Git-free HTTP team repo (read-only consumer; only needs an API key)").option("--self", "Single-repo mode: the current git repo is the team repo (equivalent to `teamai init .`). Knowledge lives on main under .teamai/; reports go to the teamai-reports orphan branch.").option("--token <key>", "API key for HTTP team repo / status reporting (stored 0600, never committed). Also reads TEAMAI_API_TOKEN.").option("--scope <scope>", "Install scope: project (default, <cwd>/.teamai + <cwd>/.claude) or user (~/.teamai + ~/.claude)").option("--inherit-user-scope", "In project scope, also sync safe user-scope resources and search its knowledge").option("--no-inherit-user-scope", "Disable user-scope inheritance for this project").option("--role <id>", "Primary role ID (e.g. hai_dev) for non-interactive setup").option("--project <ids>",
|
|
43249
|
+
program.command("init").description("Initialize teamai (configure Git provider, clone repo, register member)").argument("[repo]", 'Team repo (owner/repo or full URL). Pass "." for single-repo mode (the current git repo is the team repo).').option("--repo <repo>", "Team repo (alias of the positional argument)").option("--http <url>", "Git-free HTTP team repo (read-only consumer; only needs an API key)").option("--self", "Single-repo mode: the current git repo is the team repo (equivalent to `teamai init .`). Knowledge lives on main under .teamai/; reports go to the teamai-reports orphan branch.").option("--token <key>", "API key for HTTP team repo / status reporting (stored 0600, never committed). Also reads TEAMAI_API_TOKEN.").option("--scope <scope>", "Install scope: project (default, <cwd>/.teamai + <cwd>/.claude) or user (~/.teamai + ~/.claude)").option("--inherit-user-scope", "In project scope, also sync safe user-scope resources and search its knowledge").option("--no-inherit-user-scope", "Disable user-scope inheritance for this project").option("--role <id>", "Primary role ID (e.g. hai_dev) for non-interactive setup").option("--project <ids>", 'Active logical project(s) from manifest/projects.yaml (comma-separated); scopes which project resources and learnings this directory syncs. Pass "all" to activate every project the manifest declares (a snapshot taken now)').option("--agent <name>", "AI tools to set up (e.g. claude, codex, cursor, codebuddy, workbuddy, dsh). Repeatable or comma-separated. In single-repo mode, selects which tool dirs to create; omit for an interactive picker. Additive on repeated runs.", (val, acc) => acc.concat(val), []).option("--force", "Overwrite existing config without confirmation").action(async (repoArg, cmdOpts) => {
|
|
42957
43250
|
const globalOpts = program.opts();
|
|
42958
43251
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
42959
43252
|
await init2({ ...globalOpts, ...cmdOpts, repoPositional: repoArg });
|
|
@@ -43033,7 +43326,8 @@ registerPackagesCommand(program);
|
|
|
43033
43326
|
program.command("doctor").description("Diagnose configuration issues").action(async () => {
|
|
43034
43327
|
const globalOpts = program.opts();
|
|
43035
43328
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
43036
|
-
await doctor2(globalOpts);
|
|
43329
|
+
const allPassed = await doctor2(globalOpts);
|
|
43330
|
+
if (!allPassed) process.exitCode = 1;
|
|
43037
43331
|
});
|
|
43038
43332
|
var rolesCmd = program.command("roles").description("Manage team roles and resource namespaces").action(async () => {
|
|
43039
43333
|
const globalOpts = program.opts();
|
|
@@ -43334,7 +43628,7 @@ program.command("todowrite-hint", { hidden: true }).description("Remind the agen
|
|
|
43334
43628
|
await todoWriteHint2();
|
|
43335
43629
|
}
|
|
43336
43630
|
});
|
|
43337
|
-
program.command("import").description("Import knowledge from local directories, remote repos, organizations, MRs, or iWiki").option("--dir <path>", "Extract code knowledge from a local directory (same as --from-repo but no clone)").addOption(new Option("--from-claude", "Scan Claude/Cursor rule directories (~/.claude/rules, ~/.cursor/rules)").hideHelp()).option("--from-mr <url>", "Extract learning from merged MR/PR and trigger incremental teamwiki update").option("--from-iwiki <space-id-or-url>", "Import documents from iWiki Space ID or page URL (requires TAI_PAT_TOKEN)").addOption(new Option("--resume", "Resume an interrupted import session").hideHelp()).option("--all", "Accept all suggestions without interactive confirmation").addOption(new Option("--output <path>", "Write drafts to this directory instead of pushing to team repo").hideHelp()).option("--from-repo <url>", "Clone a remote repo and generate per-repo codebase summary").addOption(new Option("--ssh", "Force SSH clone even if HTTPS token is available").hideHelp()).addOption(new Option("--domain <name>", "Skip AI recommendation and assign repo to this domain explicitly").hideHelp()).option("--from-repo-list <path>", "Batch import repos from a YAML whitelist").addOption(new Option("--concurrency <n>", "Concurrent repos for --from-repo-list (default 3)").default("3").hideHelp()).option("--incremental", "Use cached clone with fetch+reset (with --from-repo or --from-repo-list)").option("--skip-enrich", "Skip AI enrichment (only clone + extract + graph, no LLM calls)").option("--from-org <org>", "List repos under an org and generate a repo whitelist").addOption(new Option("--max-repos <n>", "Cap on repos pulled from --from-org (default 200)").default("200").hideHelp()).addOption(new Option("--exclude-archived", "Exclude archived repos from --from-org (default true)").hideHelp()).addOption(new Option("--include-pattern <re>", "Regex to include repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--exclude-pattern <re>", "Regex to exclude repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--skip-import", "Only write drafts; skip the actual --from-repo-list run").hideHelp()).addOption(new Option("--iwiki-dual", "Enable dual-output mode for --from-iwiki (write codebase sections in addition to learning)").hideHelp()).addOption(new Option("--require-review", "Defer codebase section writes to .teamai/pending-review.jsonl for human review").hideHelp()).option("--cache-status", "Show import cache status (repos cached, disk usage)").option("--cache-gc", "Garbage-collect stale import cache entries").addOption(new Option("--max-bytes <n>", "Override capacity cap for --cache-gc").hideHelp()).addOption(new Option("--stale-days <n>", "Threshold for stale-eviction in days (default 30)").default("30").hideHelp()).action(async (cmdOpts) => {
|
|
43631
|
+
program.command("import").description("Import knowledge from local directories, remote repos, organizations, MRs, or iWiki").option("--dir <path>", "Extract code knowledge from a local directory (same as --from-repo but no clone)").addOption(new Option("--from-claude", "Scan Claude/Cursor rule directories (~/.claude/rules, ~/.cursor/rules)").hideHelp()).option("--from-mr <url>", "Extract learning from merged MR/PR and trigger incremental teamwiki update").option("--from-iwiki <space-id-or-url>", "Import documents from iWiki Space ID or page URL (requires TAI_PAT_TOKEN)").addOption(new Option("--resume", "Resume an interrupted import session").hideHelp()).option("--all", "Accept all suggestions without interactive confirmation").addOption(new Option("--output <path>", "Write drafts to this directory instead of pushing to team repo").hideHelp()).option("--from-repo <url>", "Clone a remote repo and generate per-repo codebase summary").addOption(new Option("--ssh", "Force SSH clone even if HTTPS token is available").hideHelp()).addOption(new Option("--domain <name>", "Skip AI recommendation and assign repo to this domain explicitly").hideHelp()).option("--from-repo-list <path>", "Batch import repos from a YAML whitelist").addOption(new Option("--concurrency <n>", "Concurrent repos for --from-repo-list (default 3)").default("3").hideHelp()).option("--incremental", "Use cached clone with fetch+reset (with --from-repo or --from-repo-list)").option("--skip-enrich", "Skip AI enrichment (only clone + extract + graph, no LLM calls)").option("--from-org <org>", "List repos under an org and generate a repo whitelist").addOption(new Option("--max-repos <n>", "Cap on repos pulled from --from-org (default 200)").default("200").hideHelp()).addOption(new Option("--exclude-archived", "Exclude archived repos from --from-org (default true)").hideHelp()).addOption(new Option("--include-pattern <re>", "Regex to include repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--exclude-pattern <re>", "Regex to exclude repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--skip-import", "Only write drafts; skip the actual --from-repo-list run").hideHelp()).addOption(new Option("--iwiki-dual", "Enable dual-output mode for --from-iwiki (write codebase sections in addition to learning)").hideHelp()).addOption(new Option("--require-review", "Defer codebase section writes to .teamai/pending-review.jsonl for human review").hideHelp()).option("--cache-status", "Show import cache status (repos cached, disk usage)").option("--cache-gc", "Garbage-collect stale import cache entries").option("--json", "Output cache status or GC result as JSON").addOption(new Option("--max-bytes <n>", "Override capacity cap for --cache-gc").hideHelp()).addOption(new Option("--stale-days <n>", "Threshold for stale-eviction in days (default 30)").default("30").hideHelp()).action(async (cmdOpts) => {
|
|
43338
43632
|
const globalOpts = program.opts();
|
|
43339
43633
|
if (cmdOpts.cacheStatus || cmdOpts.cacheGc) {
|
|
43340
43634
|
const { cacheCmd: cacheCmd2 } = await Promise.resolve().then(() => (init_cache_cmd(), cache_cmd_exports));
|