teamai-cli 0.24.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 +11 -0
- package/README.ja.md +278 -0
- package/README.ko.md +278 -0
- package/README.md +8 -5
- package/README.th.md +278 -0
- package/README.zh-CN.md +8 -5
- package/dist/index.js +1805 -1067
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -559,6 +559,7 @@ __export(types_exports, {
|
|
|
559
559
|
getDataHome: () => getDataHome,
|
|
560
560
|
getEnvBackupPath: () => getEnvBackupPath,
|
|
561
561
|
getHooksSharing: () => getHooksSharing,
|
|
562
|
+
getInterventionSharing: () => getInterventionSharing,
|
|
562
563
|
getKnowledgeDir: () => getKnowledgeDir,
|
|
563
564
|
getManagedHooksPath: () => getManagedHooksPath,
|
|
564
565
|
getMcpSharing: () => getMcpSharing,
|
|
@@ -590,11 +591,15 @@ __export(types_exports, {
|
|
|
590
591
|
resolveHookScope: () => resolveHookScope,
|
|
591
592
|
resolveLegacyProjectHookScope: () => resolveLegacyProjectHookScope,
|
|
592
593
|
scopedToolPaths: () => scopedToolPaths,
|
|
593
|
-
totalTokens: () => totalTokens
|
|
594
|
+
totalTokens: () => totalTokens,
|
|
595
|
+
usesReportsBranch: () => usesReportsBranch
|
|
594
596
|
});
|
|
595
597
|
import { z } from "zod";
|
|
596
598
|
import path3 from "path";
|
|
597
599
|
import { createHash } from "crypto";
|
|
600
|
+
function getInterventionSharing(config) {
|
|
601
|
+
return { correctionKeywords: config.sharing?.intervention?.correctionKeywords ?? [] };
|
|
602
|
+
}
|
|
598
603
|
function getHooksSharing(config) {
|
|
599
604
|
const h = config.sharing?.hooks;
|
|
600
605
|
return {
|
|
@@ -722,6 +727,9 @@ function scopedToolPaths(teamConfig, localConfig) {
|
|
|
722
727
|
function isSelfMode(localConfig) {
|
|
723
728
|
return localConfig.repo.kind === "self";
|
|
724
729
|
}
|
|
730
|
+
function usesReportsBranch(localConfig) {
|
|
731
|
+
return localConfig.repo.kind !== "http";
|
|
732
|
+
}
|
|
725
733
|
function getKnowledgeDir(localConfig) {
|
|
726
734
|
return localConfig.repo.localPath;
|
|
727
735
|
}
|
|
@@ -730,10 +738,13 @@ function getDataHome(localConfig) {
|
|
|
730
738
|
return getTeamaiHome(localConfig.scope, localConfig.projectRoot);
|
|
731
739
|
}
|
|
732
740
|
function getReportsDir(localConfig) {
|
|
741
|
+
if (!usesReportsBranch(localConfig)) {
|
|
742
|
+
return localConfig.repo.localPath;
|
|
743
|
+
}
|
|
733
744
|
if (isSelfMode(localConfig)) {
|
|
734
745
|
return path3.join(localConfig.repo.localPath, REPORTS_WORKTREE_DIRNAME);
|
|
735
746
|
}
|
|
736
|
-
return localConfig.repo.localPath;
|
|
747
|
+
return path3.join(path3.dirname(localConfig.repo.localPath), REPORTS_WORKTREE_DIRNAME);
|
|
737
748
|
}
|
|
738
749
|
function getTeamaiHome(scope, projectRoot) {
|
|
739
750
|
if (scope === "project") {
|
|
@@ -872,6 +883,15 @@ var init_types = __esm({
|
|
|
872
883
|
allowedCommands: z.array(z.string()).default([]),
|
|
873
884
|
/** Allowed http/sse hosts (supports a leading `*.` wildcard). Empty = no restriction. */
|
|
874
885
|
allowedHosts: z.array(z.string()).default([])
|
|
886
|
+
}).optional(),
|
|
887
|
+
// Optional (not .default) so existing TeamaiConfig literals stay valid; use
|
|
888
|
+
// getInterventionSharing() for the defaulted view.
|
|
889
|
+
intervention: z.object({
|
|
890
|
+
/** Extra course-correction keywords, merged with the built-in list
|
|
891
|
+
* (CORRECTION_KEYWORDS). Teams add the words their members actually type,
|
|
892
|
+
* e.g. Spanish "rehazlo" or "no era eso". Matched case-insensitively; a
|
|
893
|
+
* keyword in a space-separated script must appear as a whole word. */
|
|
894
|
+
correctionKeywords: z.array(z.string()).default([])
|
|
875
895
|
}).optional()
|
|
876
896
|
});
|
|
877
897
|
SourceConfigSchema = z.object({
|
|
@@ -948,6 +968,19 @@ var init_types = __esm({
|
|
|
948
968
|
mcp: ".qoder/settings.json",
|
|
949
969
|
mcpProject: ".qoder/settings.json"
|
|
950
970
|
},
|
|
971
|
+
// Kiro: skills, steering (rules), and custom agents sync to .kiro/. Kiro CLI
|
|
972
|
+
// 2.x stores lifecycle hooks inside each .kiro/agents/*.json config. The
|
|
973
|
+
// Kiro agent renderer therefore embeds TeamAI's session-start dispatch as
|
|
974
|
+
// `hooks.agentSpawn`; there is no standalone `settings` hook surface.
|
|
975
|
+
// MCP uses the dedicated, mcpServers-only .kiro/settings/mcp.json:
|
|
976
|
+
// https://kiro.dev/docs/mcp/configuration/
|
|
977
|
+
kiro: {
|
|
978
|
+
skills: ".kiro/skills",
|
|
979
|
+
rules: ".kiro/steering",
|
|
980
|
+
agents: ".kiro/agents",
|
|
981
|
+
mcp: ".kiro/settings/mcp.json",
|
|
982
|
+
mcpProject: ".kiro/settings/mcp.json"
|
|
983
|
+
},
|
|
951
984
|
// ZCode: user-level config lives at ~/.zcode/cli/config.json (a shared file
|
|
952
985
|
// that also carries plugin state — reconcile must merge, never replace).
|
|
953
986
|
// Hooks are Claude-shaped but nested under `hooks.events` and gated by
|
|
@@ -1009,6 +1042,8 @@ var init_types = __esm({
|
|
|
1009
1042
|
* Knowledge lives on main under <businessRepoRoot>/.teamai/;
|
|
1010
1043
|
* reports (members/sessions/votes/stats) live on the
|
|
1011
1044
|
* `teamai-reports` orphan branch. localPath = <businessRepoRoot>/.teamai.
|
|
1045
|
+
* Independent git clones (`kind: 'git'` or omitted) use the same reports
|
|
1046
|
+
* branch; the worktree sits beside the clone, not inside it.
|
|
1012
1047
|
*/
|
|
1013
1048
|
kind: z.enum(["git", "http", "self"]).optional(),
|
|
1014
1049
|
/** Base URL of the HTTP team repo (only when kind === 'http'). */
|
|
@@ -1212,9 +1247,178 @@ var init_types = __esm({
|
|
|
1212
1247
|
}
|
|
1213
1248
|
});
|
|
1214
1249
|
|
|
1250
|
+
// src/roles.ts
|
|
1251
|
+
var roles_exports = {};
|
|
1252
|
+
__export(roles_exports, {
|
|
1253
|
+
activeRoleIds: () => activeRoleIds,
|
|
1254
|
+
describeRoles: () => describeRoles,
|
|
1255
|
+
findRole: () => findRole,
|
|
1256
|
+
listRoleIds: () => listRoleIds,
|
|
1257
|
+
loadRolesManifest: () => loadRolesManifest,
|
|
1258
|
+
matchesRoles: () => matchesRoles,
|
|
1259
|
+
resolveRoleResourceNamespaces: () => resolveRoleResourceNamespaces,
|
|
1260
|
+
saveRolesManifest: () => saveRolesManifest,
|
|
1261
|
+
warnUnknownRoleIds: () => warnUnknownRoleIds
|
|
1262
|
+
});
|
|
1263
|
+
import path4 from "path";
|
|
1264
|
+
import YAML from "yaml";
|
|
1265
|
+
import { z as z2 } from "zod";
|
|
1266
|
+
function validateManifestShape(raw) {
|
|
1267
|
+
if (!raw || typeof raw !== "object") {
|
|
1268
|
+
throw new Error("Invalid roles manifest: expected an object");
|
|
1269
|
+
}
|
|
1270
|
+
const candidate = raw;
|
|
1271
|
+
const roles = candidate.roles;
|
|
1272
|
+
if (!Array.isArray(roles) || roles.length === 0) {
|
|
1273
|
+
throw new Error("Invalid roles manifest: roles must be a non-empty array");
|
|
1274
|
+
}
|
|
1275
|
+
for (const role of roles) {
|
|
1276
|
+
if (!role || typeof role !== "object") {
|
|
1277
|
+
throw new Error("Invalid roles manifest: every role must be an object");
|
|
1278
|
+
}
|
|
1279
|
+
const resources = role.resources;
|
|
1280
|
+
if (!resources || typeof resources !== "object" || Array.isArray(resources)) {
|
|
1281
|
+
throw new Error(`Invalid roles manifest: role ${role.id ?? "<unknown>"} is missing resources`);
|
|
1282
|
+
}
|
|
1283
|
+
const ALLOWED_RESOURCE_KEYS = /* @__PURE__ */ new Set([...ROLE_RESOURCE_TYPES, "learnings"]);
|
|
1284
|
+
for (const key of Object.keys(resources)) {
|
|
1285
|
+
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
1286
|
+
throw new Error(`Invalid roles manifest: unknown resource type "${key}"`);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const manifest = RolesManifestSchema.parse(raw);
|
|
1291
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1292
|
+
for (const role of manifest.roles) {
|
|
1293
|
+
if (ids.has(role.id)) {
|
|
1294
|
+
throw new Error(`Invalid roles manifest: duplicate role id "${role.id}"`);
|
|
1295
|
+
}
|
|
1296
|
+
ids.add(role.id);
|
|
1297
|
+
}
|
|
1298
|
+
return manifest;
|
|
1299
|
+
}
|
|
1300
|
+
async function loadRolesManifest(repoPath) {
|
|
1301
|
+
const manifestPath = path4.join(repoPath, "manifest", "roles.yaml");
|
|
1302
|
+
const content = await readFileSafe(manifestPath);
|
|
1303
|
+
if (!content) {
|
|
1304
|
+
throw new Error(`Roles manifest not found: ${manifestPath}`);
|
|
1305
|
+
}
|
|
1306
|
+
let raw;
|
|
1307
|
+
try {
|
|
1308
|
+
raw = YAML.parse(content);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
throw new Error(`Invalid roles manifest YAML: ${error.message}`);
|
|
1311
|
+
}
|
|
1312
|
+
return validateManifestShape(raw);
|
|
1313
|
+
}
|
|
1314
|
+
async function saveRolesManifest(repoPath, manifest) {
|
|
1315
|
+
validateManifestShape(manifest);
|
|
1316
|
+
const manifestDir = path4.join(repoPath, "manifest");
|
|
1317
|
+
const manifestPath = path4.join(manifestDir, "roles.yaml");
|
|
1318
|
+
await ensureDir(manifestDir);
|
|
1319
|
+
await writeFile(manifestPath, YAML.stringify(manifest));
|
|
1320
|
+
}
|
|
1321
|
+
function findRole(manifest, roleId) {
|
|
1322
|
+
return manifest.roles.find((candidate) => candidate.id === roleId);
|
|
1323
|
+
}
|
|
1324
|
+
function listRoleIds(manifest) {
|
|
1325
|
+
return manifest.roles.map((role) => role.id);
|
|
1326
|
+
}
|
|
1327
|
+
function describeRoles(roles) {
|
|
1328
|
+
return roles.map((role) => role.description ? `${role.id}: ${role.description}` : `${role.id}`);
|
|
1329
|
+
}
|
|
1330
|
+
function getRoleOrThrow(manifest, roleId) {
|
|
1331
|
+
const role = manifest.roles.find((candidate) => candidate.id === roleId);
|
|
1332
|
+
if (!role) {
|
|
1333
|
+
throw new Error(`Unknown role "${roleId}". Valid roles: ${listRoleIds(manifest).join(", ")}`);
|
|
1334
|
+
}
|
|
1335
|
+
return role;
|
|
1336
|
+
}
|
|
1337
|
+
function resolveRoleResourceNamespaces(input) {
|
|
1338
|
+
const resolvedRoles = [
|
|
1339
|
+
getRoleOrThrow(input.manifest, input.primaryRole),
|
|
1340
|
+
...input.additionalRoles.map((roleId) => getRoleOrThrow(input.manifest, roleId))
|
|
1341
|
+
];
|
|
1342
|
+
const namespaces = {
|
|
1343
|
+
knowledge: [],
|
|
1344
|
+
skills: [],
|
|
1345
|
+
// Roles never contribute learnings namespaces; only projects do. Kept empty
|
|
1346
|
+
// so the shape matches project resolution for a clean union at the call site.
|
|
1347
|
+
learnings: [],
|
|
1348
|
+
agents: []
|
|
1349
|
+
};
|
|
1350
|
+
for (const type of ROLE_RESOURCE_TYPES) {
|
|
1351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1352
|
+
for (const role of resolvedRoles) {
|
|
1353
|
+
for (const namespace of role.resources[type]) {
|
|
1354
|
+
if (seen.has(namespace)) continue;
|
|
1355
|
+
seen.add(namespace);
|
|
1356
|
+
namespaces[type].push(namespace);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
return namespaces;
|
|
1361
|
+
}
|
|
1362
|
+
function activeRoleIds(localConfig) {
|
|
1363
|
+
if (!localConfig.primaryRole) return null;
|
|
1364
|
+
return [.../* @__PURE__ */ new Set([localConfig.primaryRole, ...localConfig.additionalRoles ?? []])];
|
|
1365
|
+
}
|
|
1366
|
+
function matchesRoles(entryRoles, active) {
|
|
1367
|
+
if (!entryRoles || active == null) return true;
|
|
1368
|
+
return entryRoles.some((role) => active.includes(role));
|
|
1369
|
+
}
|
|
1370
|
+
async function warnUnknownRoleIds(repoPath, file, entries) {
|
|
1371
|
+
if (!entries.some((entry) => entry.roles && entry.roles.length > 0)) return;
|
|
1372
|
+
let known;
|
|
1373
|
+
try {
|
|
1374
|
+
known = new Set(listRoleIds(await loadRolesManifest(repoPath)));
|
|
1375
|
+
} catch {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
for (const entry of entries) {
|
|
1379
|
+
for (const role of entry.roles ?? []) {
|
|
1380
|
+
if (known.has(role) || reportedUnknownRoles.has(`${file}:${role}`)) continue;
|
|
1381
|
+
reportedUnknownRoles.add(`${file}:${role}`);
|
|
1382
|
+
log.warn(`roles: unknown role id "${role}" in ${file} ${entry.kind} "${entry.name}". Valid roles: ${[...known].join(", ")}`);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
var ROLE_RESOURCE_TYPES, RoleResourceNamespacesSchema, RoleSchema, RolesManifestSchema, reportedUnknownRoles;
|
|
1387
|
+
var init_roles = __esm({
|
|
1388
|
+
"src/roles.ts"() {
|
|
1389
|
+
"use strict";
|
|
1390
|
+
init_fs();
|
|
1391
|
+
init_logger();
|
|
1392
|
+
ROLE_RESOURCE_TYPES = ["knowledge", "skills", "agents"];
|
|
1393
|
+
RoleResourceNamespacesSchema = z2.object({
|
|
1394
|
+
knowledge: z2.array(z2.string().min(1)),
|
|
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([]),
|
|
1399
|
+
// learnings is accepted for backward compatibility but ignored at runtime.
|
|
1400
|
+
// All learnings are shared flat across the entire team (no namespace isolation).
|
|
1401
|
+
learnings: z2.array(z2.string()).optional()
|
|
1402
|
+
});
|
|
1403
|
+
RoleSchema = z2.object({
|
|
1404
|
+
id: z2.string().min(1),
|
|
1405
|
+
description: z2.string().default(""),
|
|
1406
|
+
resources: RoleResourceNamespacesSchema
|
|
1407
|
+
});
|
|
1408
|
+
RolesManifestSchema = z2.object({
|
|
1409
|
+
version: z2.number(),
|
|
1410
|
+
roles: z2.array(RoleSchema).min(1),
|
|
1411
|
+
// defaults.shareTarget was removed: learnings are flat, no namespace routing needed.
|
|
1412
|
+
// Old manifests with a defaults block are still parseable (z.passthrough on object level).
|
|
1413
|
+
defaults: z2.object({}).passthrough().optional()
|
|
1414
|
+
});
|
|
1415
|
+
reportedUnknownRoles = /* @__PURE__ */ new Set();
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1215
1419
|
// src/bundled-runtime.ts
|
|
1216
1420
|
import fs2 from "fs";
|
|
1217
|
-
import
|
|
1421
|
+
import path5 from "path";
|
|
1218
1422
|
function compareSemver(a, b) {
|
|
1219
1423
|
const aParts = a.split(".").map((s) => parseInt(s, 10) || 0);
|
|
1220
1424
|
const bParts = b.split(".").map((s) => parseInt(s, 10) || 0);
|
|
@@ -1230,18 +1434,18 @@ function pickLatestVersion(versions) {
|
|
|
1230
1434
|
return versions.reduce((best, v) => compareSemver(v, best) > 0 ? v : best, versions[0]);
|
|
1231
1435
|
}
|
|
1232
1436
|
function latestVersionDir(relDir) {
|
|
1233
|
-
const versionsDir =
|
|
1437
|
+
const versionsDir = path5.join(getUserHome(), relDir);
|
|
1234
1438
|
try {
|
|
1235
1439
|
const versions = fs2.readdirSync(versionsDir).filter((d) => !d.startsWith("."));
|
|
1236
1440
|
const latest = pickLatestVersion(versions);
|
|
1237
|
-
return latest ?
|
|
1441
|
+
return latest ? path5.join(versionsDir, latest) : null;
|
|
1238
1442
|
} catch {
|
|
1239
1443
|
return null;
|
|
1240
1444
|
}
|
|
1241
1445
|
}
|
|
1242
1446
|
function resolveWorkbuddyNode() {
|
|
1243
1447
|
const dir = latestVersionDir(WORKBUDDY_BUNDLED_NODE_DIR);
|
|
1244
|
-
const nodeBin = dir &&
|
|
1448
|
+
const nodeBin = dir && path5.join(dir, "bin", "node");
|
|
1245
1449
|
return nodeBin && fs2.existsSync(nodeBin) ? nodeBin : null;
|
|
1246
1450
|
}
|
|
1247
1451
|
function resolveCodebuddyNode() {
|
|
@@ -1251,10 +1455,10 @@ function resolveCodebuddyNode() {
|
|
|
1251
1455
|
for (const entry of entries) {
|
|
1252
1456
|
if (!entry.startsWith(".codebuddy-server")) continue;
|
|
1253
1457
|
try {
|
|
1254
|
-
const binDir =
|
|
1458
|
+
const binDir = path5.join(home, entry, "bin");
|
|
1255
1459
|
const stableDirs = fs2.readdirSync(binDir).filter((d) => d.startsWith("stable-"));
|
|
1256
1460
|
for (const stable of stableDirs) {
|
|
1257
|
-
const nodeBin =
|
|
1461
|
+
const nodeBin = path5.join(binDir, stable, "node");
|
|
1258
1462
|
if (fs2.existsSync(nodeBin)) return nodeBin;
|
|
1259
1463
|
}
|
|
1260
1464
|
} catch {
|
|
@@ -1270,8 +1474,8 @@ function resolveWorkbuddyShell() {
|
|
|
1270
1474
|
if (process.platform === "win32") {
|
|
1271
1475
|
const dir = latestVersionDir(WORKBUDDY_PORTABLE_GIT_DIR);
|
|
1272
1476
|
if (dir) {
|
|
1273
|
-
for (const rel of ["bin",
|
|
1274
|
-
const shBin =
|
|
1477
|
+
for (const rel of ["bin", path5.join("usr", "bin")]) {
|
|
1478
|
+
const shBin = path5.join(dir, rel, "sh.exe");
|
|
1275
1479
|
if (fs2.existsSync(shBin)) {
|
|
1276
1480
|
_wbShellCache = shBin;
|
|
1277
1481
|
break;
|
|
@@ -1301,7 +1505,7 @@ var init_bundled_runtime = __esm({
|
|
|
1301
1505
|
|
|
1302
1506
|
// src/builtin-hooks.ts
|
|
1303
1507
|
import fs3 from "fs";
|
|
1304
|
-
import
|
|
1508
|
+
import path6 from "path";
|
|
1305
1509
|
import { fileURLToPath } from "url";
|
|
1306
1510
|
function hasShell() {
|
|
1307
1511
|
if (_hasShellCache === void 0) {
|
|
@@ -1316,8 +1520,8 @@ function hasShell() {
|
|
|
1316
1520
|
function resolveTeamaiEntryScript() {
|
|
1317
1521
|
try {
|
|
1318
1522
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1319
|
-
const distDir =
|
|
1320
|
-
const candidate =
|
|
1523
|
+
const distDir = path6.dirname(thisFile);
|
|
1524
|
+
const candidate = path6.join(distDir, "index.js");
|
|
1321
1525
|
if (fs3.existsSync(candidate)) return candidate;
|
|
1322
1526
|
} catch {
|
|
1323
1527
|
}
|
|
@@ -1328,8 +1532,8 @@ function ensureTeamaiWrapper() {
|
|
|
1328
1532
|
if (!entryScript) return null;
|
|
1329
1533
|
const nodeBin = resolveWorkbuddyNode() ?? resolveCodebuddyNode() ?? process.argv[0];
|
|
1330
1534
|
const home = getUserHome();
|
|
1331
|
-
const binDir =
|
|
1332
|
-
const wrapperPath =
|
|
1535
|
+
const binDir = path6.join(home, TEAMAI_BIN_DIR);
|
|
1536
|
+
const wrapperPath = path6.join(binDir, WRAPPER_NAME);
|
|
1333
1537
|
const script = [
|
|
1334
1538
|
"#!/bin/sh",
|
|
1335
1539
|
`# Auto-generated by teamai \u2014 do not edit.`,
|
|
@@ -1383,7 +1587,7 @@ function getWrapperDispatchCommand(event, tool, matcher) {
|
|
|
1383
1587
|
return `PATH="$HOME/${TEAMAI_BIN_DIR}:$PATH" teamai hook-dispatch ${event} --tool ${tool}${matcherArg} 2>/dev/null || true`;
|
|
1384
1588
|
}
|
|
1385
1589
|
function builtinHookDefs(tool) {
|
|
1386
|
-
const withTimeout3 = tool === "cursor" || tool === "workbuddy" || tool === "codebuddy"
|
|
1590
|
+
const withTimeout3 = tool === "cursor" || tool === "workbuddy" || tool === "codebuddy";
|
|
1387
1591
|
const buildCommand = tool === "zcode" ? getRawDispatchCommand : WRAPPER_TOOLS.has(tool) ? getWrapperDispatchCommand : getDispatchCommand;
|
|
1388
1592
|
return BUILTIN_HOOK_SPECS.map((spec) => ({
|
|
1389
1593
|
source: "builtin",
|
|
@@ -1427,7 +1631,7 @@ var init_builtin_hooks = __esm({
|
|
|
1427
1631
|
});
|
|
1428
1632
|
|
|
1429
1633
|
// src/resources/base.ts
|
|
1430
|
-
import
|
|
1634
|
+
import path7 from "path";
|
|
1431
1635
|
function toolInstallRoot(toolPath) {
|
|
1432
1636
|
const segments = toolPath.split("/");
|
|
1433
1637
|
if (segments[0] === ".config" && segments.length > 1) {
|
|
@@ -1451,7 +1655,7 @@ var init_base = __esm({
|
|
|
1451
1655
|
*/
|
|
1452
1656
|
static async isToolInstalled(toolPath, baseDir) {
|
|
1453
1657
|
const base = baseDir ?? getUserHome();
|
|
1454
|
-
const toolRoot =
|
|
1658
|
+
const toolRoot = path7.join(base, toolInstallRoot(toolPath));
|
|
1455
1659
|
return pathExists(toolRoot);
|
|
1456
1660
|
}
|
|
1457
1661
|
/**
|
|
@@ -1459,7 +1663,7 @@ var init_base = __esm({
|
|
|
1459
1663
|
* Returns a Set of resource names that have been explicitly deleted.
|
|
1460
1664
|
*/
|
|
1461
1665
|
async readTombstones(localConfig) {
|
|
1462
|
-
const tombstonePath =
|
|
1666
|
+
const tombstonePath = path7.join(localConfig.repo.localPath, this.type, TOMBSTONE_FILE);
|
|
1463
1667
|
const content = await readFileSafe(tombstonePath);
|
|
1464
1668
|
if (!content) return /* @__PURE__ */ new Set();
|
|
1465
1669
|
return new Set(
|
|
@@ -1470,9 +1674,9 @@ var init_base = __esm({
|
|
|
1470
1674
|
* Append a resource name to the tombstone file, deduplicating and sorting.
|
|
1471
1675
|
*/
|
|
1472
1676
|
async addTombstone(name, localConfig) {
|
|
1473
|
-
const dir =
|
|
1677
|
+
const dir = path7.join(localConfig.repo.localPath, this.type);
|
|
1474
1678
|
await ensureDir(dir);
|
|
1475
|
-
const tombstonePath =
|
|
1679
|
+
const tombstonePath = path7.join(dir, TOMBSTONE_FILE);
|
|
1476
1680
|
const existing = await this.readTombstones(localConfig);
|
|
1477
1681
|
existing.add(name);
|
|
1478
1682
|
const sorted = [...existing].sort();
|
|
@@ -1495,17 +1699,17 @@ var init_base = __esm({
|
|
|
1495
1699
|
});
|
|
1496
1700
|
|
|
1497
1701
|
// src/resources/hooks.ts
|
|
1498
|
-
import
|
|
1499
|
-
import { z as
|
|
1500
|
-
import
|
|
1702
|
+
import path8 from "path";
|
|
1703
|
+
import { z as z3 } from "zod";
|
|
1704
|
+
import YAML2 from "yaml";
|
|
1501
1705
|
function teamHooksYamlPath(repoPath) {
|
|
1502
|
-
return
|
|
1706
|
+
return path8.join(repoPath, "hooks", "hooks.yaml");
|
|
1503
1707
|
}
|
|
1504
1708
|
async function parseHooksYaml(repoPath) {
|
|
1505
1709
|
const content = await readFileSafe(teamHooksYamlPath(repoPath));
|
|
1506
1710
|
if (!content) return null;
|
|
1507
1711
|
try {
|
|
1508
|
-
return HooksYamlSchema.parse(
|
|
1712
|
+
return HooksYamlSchema.parse(YAML2.parse(content));
|
|
1509
1713
|
} catch (e) {
|
|
1510
1714
|
log.warn(`Invalid hooks.yaml format: ${e.message} \u2014 skipping team hooks this run`);
|
|
1511
1715
|
return null;
|
|
@@ -1520,7 +1724,8 @@ function teamHookToDef(h) {
|
|
|
1520
1724
|
command: h.command,
|
|
1521
1725
|
timeout: h.timeout,
|
|
1522
1726
|
description: `${TEAMAI_CUSTOM_HOOK_PREFIX}${h.id}] ${h.description}`,
|
|
1523
|
-
tools: h.tools
|
|
1727
|
+
tools: h.tools,
|
|
1728
|
+
roles: h.roles
|
|
1524
1729
|
};
|
|
1525
1730
|
}
|
|
1526
1731
|
async function parseTeamHooks(repoPath) {
|
|
@@ -1544,6 +1749,8 @@ async function resolveTeamHooks(teamConfig, repoPath, opts = {}) {
|
|
|
1544
1749
|
if (defs.length > 0) log.warn(`Team hooks disabled (TEAMAI_HOOKS_DISABLED) \u2014 skipping ${defs.length} team hook(s)`);
|
|
1545
1750
|
return { defs: [], builtin };
|
|
1546
1751
|
}
|
|
1752
|
+
await warnUnknownRoleIds(repoPath, "hooks.yaml", defs.map((d) => ({ kind: "hook", name: d.key, roles: d.roles })));
|
|
1753
|
+
defs = defs.filter((d) => matchesRoles(d.roles, opts.activeRoles));
|
|
1547
1754
|
if (sharing.requireTeamScripts) {
|
|
1548
1755
|
const before = defs.length;
|
|
1549
1756
|
defs = defs.filter((d) => isTeamScriptCommand(d.command));
|
|
@@ -1570,28 +1777,31 @@ var init_hooks = __esm({
|
|
|
1570
1777
|
init_types();
|
|
1571
1778
|
init_fs();
|
|
1572
1779
|
init_logger();
|
|
1573
|
-
|
|
1780
|
+
init_roles();
|
|
1781
|
+
TeamHookSchema = z3.object({
|
|
1574
1782
|
/** Unique id (marker + manifest index). */
|
|
1575
|
-
id:
|
|
1783
|
+
id: z3.string().regex(/^[a-z0-9-]+$/),
|
|
1576
1784
|
/** Written into the hook description. */
|
|
1577
|
-
description:
|
|
1785
|
+
description: z3.string(),
|
|
1578
1786
|
/** Claude PascalCase event name. */
|
|
1579
|
-
event:
|
|
1787
|
+
event: z3.string().min(1),
|
|
1580
1788
|
/** Optional tool matcher (e.g. "Bash"). */
|
|
1581
|
-
matcher:
|
|
1789
|
+
matcher: z3.string().optional(),
|
|
1582
1790
|
/** Shell command to run. */
|
|
1583
|
-
command:
|
|
1791
|
+
command: z3.string().min(1),
|
|
1584
1792
|
/** Optional per-hook timeout in seconds. */
|
|
1585
|
-
timeout:
|
|
1793
|
+
timeout: z3.number().optional(),
|
|
1586
1794
|
/** Optional restriction to specific tools (default = all hook-capable tools). */
|
|
1587
|
-
tools:
|
|
1795
|
+
tools: z3.array(z3.string()).optional(),
|
|
1796
|
+
/** Optional restriction to members holding one of these role ids (default = every member). */
|
|
1797
|
+
roles: z3.array(z3.string()).optional()
|
|
1588
1798
|
});
|
|
1589
|
-
BuiltinOverrideSchema =
|
|
1590
|
-
disabled:
|
|
1591
|
-
overrides:
|
|
1799
|
+
BuiltinOverrideSchema = z3.object({
|
|
1800
|
+
disabled: z3.array(z3.string()).default([]),
|
|
1801
|
+
overrides: z3.record(z3.string(), z3.object({ timeout: z3.number().optional() })).default({})
|
|
1592
1802
|
}).default({ disabled: [], overrides: {} });
|
|
1593
|
-
HooksYamlSchema =
|
|
1594
|
-
hooks:
|
|
1803
|
+
HooksYamlSchema = z3.object({
|
|
1804
|
+
hooks: z3.array(TeamHookSchema).default([]),
|
|
1595
1805
|
builtin: BuiltinOverrideSchema
|
|
1596
1806
|
});
|
|
1597
1807
|
HooksHandler = class extends ResourceHandler {
|
|
@@ -1635,10 +1845,10 @@ __export(opencode_hooks_exports, {
|
|
|
1635
1845
|
removeOpencodeHooks: () => removeOpencodeHooks,
|
|
1636
1846
|
resolveOpencodePluginDir: () => resolveOpencodePluginDir
|
|
1637
1847
|
});
|
|
1638
|
-
import
|
|
1848
|
+
import path9 from "path";
|
|
1639
1849
|
function resolveOpencodePluginDir(baseDir, scope) {
|
|
1640
|
-
const configDir = scope === "project" ? ".opencode" :
|
|
1641
|
-
return
|
|
1850
|
+
const configDir = scope === "project" ? ".opencode" : path9.join(".config", "opencode");
|
|
1851
|
+
return path9.join(baseDir, configDir, OPENCODE_PLUGIN_DIR);
|
|
1642
1852
|
}
|
|
1643
1853
|
function buildPluginSource() {
|
|
1644
1854
|
return `// ${TEAMAI_MARKER} hooks plugin \u2014 generated by teamai, do not edit by hand.
|
|
@@ -1720,20 +1930,20 @@ export const TeamaiHooks = async ({ $, directory, worktree }) => {
|
|
|
1720
1930
|
async function injectOpencodeHooks(baseDir, scope) {
|
|
1721
1931
|
const dir = resolveOpencodePluginDir(baseDir, scope);
|
|
1722
1932
|
await ensureDir(dir);
|
|
1723
|
-
const file =
|
|
1933
|
+
const file = path9.join(dir, OPENCODE_HOOK_FILE);
|
|
1724
1934
|
await writeFile(file, buildPluginSource());
|
|
1725
1935
|
log.success(`Injected teamai OpenCode hook into ${file}`);
|
|
1726
1936
|
}
|
|
1727
1937
|
async function removeOpencodeHooks(baseDir, scope) {
|
|
1728
1938
|
const dir = resolveOpencodePluginDir(baseDir, scope);
|
|
1729
|
-
const file =
|
|
1939
|
+
const file = path9.join(dir, OPENCODE_HOOK_FILE);
|
|
1730
1940
|
if (await pathExists(file)) {
|
|
1731
1941
|
await remove(file);
|
|
1732
1942
|
log.success(`Removed teamai OpenCode hook from ${file}`);
|
|
1733
1943
|
}
|
|
1734
1944
|
}
|
|
1735
1945
|
function assertSafeSlug(slug) {
|
|
1736
|
-
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..") ||
|
|
1946
|
+
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..") || path9.isAbsolute(slug)) {
|
|
1737
1947
|
throw new Error(`Invalid agent-hook slug: ${slug}`);
|
|
1738
1948
|
}
|
|
1739
1949
|
}
|
|
@@ -1785,14 +1995,14 @@ async function applyOpencodeAgentHook(def) {
|
|
|
1785
1995
|
}
|
|
1786
1996
|
const dir = resolveOpencodePluginDir(def.baseDir, def.scope);
|
|
1787
1997
|
await ensureDir(dir);
|
|
1788
|
-
const file =
|
|
1998
|
+
const file = path9.join(dir, `teamai-agent-${def.slug}.ts`);
|
|
1789
1999
|
await writeFile(file, buildAgentHookPluginSource(def.slug, ocEvent, def.command, def.matcher));
|
|
1790
2000
|
log.success(`Installed OpenCode agent hook [${def.slug}] in ${file}`);
|
|
1791
2001
|
}
|
|
1792
2002
|
async function removeOpencodeAgentHook(opts) {
|
|
1793
2003
|
assertSafeSlug(opts.slug);
|
|
1794
2004
|
const dir = resolveOpencodePluginDir(opts.baseDir, opts.scope);
|
|
1795
|
-
const file =
|
|
2005
|
+
const file = path9.join(dir, `teamai-agent-${opts.slug}.ts`);
|
|
1796
2006
|
if (await pathExists(file)) {
|
|
1797
2007
|
await remove(file);
|
|
1798
2008
|
log.success(`Removed OpenCode agent hook [${opts.slug}] from ${file}`);
|
|
@@ -1828,13 +2038,13 @@ __export(openclaw_hooks_exports, {
|
|
|
1828
2038
|
resolveOpenClawHooksDir: () => resolveOpenClawHooksDir,
|
|
1829
2039
|
resolveOpenclawWorkspaceDir: () => resolveOpenclawWorkspaceDir
|
|
1830
2040
|
});
|
|
1831
|
-
import
|
|
2041
|
+
import path10 from "path";
|
|
1832
2042
|
function resolveOpenClawHooksDir(tool) {
|
|
1833
2043
|
if (tool === "openclaw" && process.env.OPENCLAW_STATE_DIR) {
|
|
1834
|
-
return
|
|
2044
|
+
return path10.join(process.env.OPENCLAW_STATE_DIR, "hooks");
|
|
1835
2045
|
}
|
|
1836
2046
|
const home = getUserHome();
|
|
1837
|
-
return
|
|
2047
|
+
return path10.join(home, `.${tool}`, "hooks");
|
|
1838
2048
|
}
|
|
1839
2049
|
function buildHookMd(tool) {
|
|
1840
2050
|
const events = Object.keys(EVENT_MAP);
|
|
@@ -1880,21 +2090,21 @@ async function injectOpenClawHooks(workspacePath, tool = "openclaw") {
|
|
|
1880
2090
|
log.debug(`openclaw: skip hook injection for ${tool} \u2014 workspace dir not found`);
|
|
1881
2091
|
return;
|
|
1882
2092
|
}
|
|
1883
|
-
const dir =
|
|
2093
|
+
const dir = path10.join(wsDir, "hooks", OPENCLAW_HOOK_DIR);
|
|
1884
2094
|
await ensureDir(dir);
|
|
1885
|
-
await writeFile(
|
|
1886
|
-
await writeFile(
|
|
2095
|
+
await writeFile(path10.join(dir, "HOOK.md"), buildHookMd(tool));
|
|
2096
|
+
await writeFile(path10.join(dir, "handler.ts"), buildHandlerTs(tool));
|
|
1887
2097
|
log.success(`Injected teamai OpenClaw hook into ${dir}`);
|
|
1888
2098
|
await enableOpenClawInternalHooks(tool);
|
|
1889
2099
|
}
|
|
1890
2100
|
async function enableOpenClawInternalHooks(tool = "openclaw") {
|
|
1891
2101
|
if (tool !== "openclaw") return;
|
|
1892
2102
|
const stateDir = process.env.OPENCLAW_STATE_DIR;
|
|
1893
|
-
if (!stateDir || !
|
|
2103
|
+
if (!stateDir || !path10.isAbsolute(stateDir)) {
|
|
1894
2104
|
log.debug("openclaw: skip enabling internal hooks \u2014 OPENCLAW_STATE_DIR unset or not absolute");
|
|
1895
2105
|
return;
|
|
1896
2106
|
}
|
|
1897
|
-
const cfgPath =
|
|
2107
|
+
const cfgPath = path10.join(stateDir, "openclaw.json");
|
|
1898
2108
|
try {
|
|
1899
2109
|
const cfg = await readJson(cfgPath) ?? {};
|
|
1900
2110
|
const hooksVal = cfg.hooks;
|
|
@@ -1915,13 +2125,13 @@ async function enableOpenClawInternalHooks(tool = "openclaw") {
|
|
|
1915
2125
|
}
|
|
1916
2126
|
}
|
|
1917
2127
|
async function removeOpenClawHooks(hooksDir) {
|
|
1918
|
-
const dir =
|
|
2128
|
+
const dir = path10.join(hooksDir, OPENCLAW_HOOK_DIR);
|
|
1919
2129
|
if (await pathExists(dir)) {
|
|
1920
2130
|
await remove(dir);
|
|
1921
2131
|
log.success(`Removed teamai OpenClaw hook from ${dir}`);
|
|
1922
2132
|
}
|
|
1923
2133
|
if (process.env.OPENCLAW_STATE_DIR) {
|
|
1924
|
-
const altDir =
|
|
2134
|
+
const altDir = path10.join(process.env.OPENCLAW_STATE_DIR, "hooks", OPENCLAW_HOOK_DIR);
|
|
1925
2135
|
if (altDir !== dir && await pathExists(altDir)) {
|
|
1926
2136
|
await remove(altDir);
|
|
1927
2137
|
log.success(`Removed teamai OpenClaw hook from ${altDir}`);
|
|
@@ -1970,16 +2180,16 @@ async function applyOpenClawAgentHook(def) {
|
|
|
1970
2180
|
}
|
|
1971
2181
|
const tool = def.tool ?? "openclaw";
|
|
1972
2182
|
const hooksDir = resolveOpenClawHooksDir(tool);
|
|
1973
|
-
const dir =
|
|
2183
|
+
const dir = path10.join(hooksDir, def.slug);
|
|
1974
2184
|
await ensureDir(dir);
|
|
1975
|
-
await writeFile(
|
|
1976
|
-
await writeFile(
|
|
2185
|
+
await writeFile(path10.join(dir, "HOOK.md"), buildAgentHookMd(def.slug, openclawEvent));
|
|
2186
|
+
await writeFile(path10.join(dir, "handler.ts"), buildAgentHandlerTs(def.command, def.timeout ?? 10));
|
|
1977
2187
|
log.success(`Installed OpenClaw agent hook [${def.slug}] in ${dir}`);
|
|
1978
2188
|
}
|
|
1979
2189
|
async function removeOpenClawAgentHook(opts) {
|
|
1980
2190
|
const tool = opts.tool ?? "openclaw";
|
|
1981
2191
|
const hooksDir = resolveOpenClawHooksDir(tool);
|
|
1982
|
-
const dir =
|
|
2192
|
+
const dir = path10.join(hooksDir, opts.slug);
|
|
1983
2193
|
if (await pathExists(dir)) {
|
|
1984
2194
|
await remove(dir);
|
|
1985
2195
|
log.success(`Removed OpenClaw agent hook [${opts.slug}] from ${dir}`);
|
|
@@ -1989,8 +2199,8 @@ async function resolveOpenclawWorkspaceDir(workspacePath) {
|
|
|
1989
2199
|
const candidates = [];
|
|
1990
2200
|
if (workspacePath) candidates.push(workspacePath);
|
|
1991
2201
|
const stateDir = process.env.OPENCLAW_STATE_DIR;
|
|
1992
|
-
if (stateDir &&
|
|
1993
|
-
const cfgRaw = await readFileSafe(
|
|
2202
|
+
if (stateDir && path10.isAbsolute(stateDir)) {
|
|
2203
|
+
const cfgRaw = await readFileSafe(path10.join(stateDir, "openclaw.json"));
|
|
1994
2204
|
if (cfgRaw) {
|
|
1995
2205
|
try {
|
|
1996
2206
|
const cfg = JSON.parse(cfgRaw);
|
|
@@ -2001,7 +2211,7 @@ async function resolveOpenclawWorkspaceDir(workspacePath) {
|
|
|
2001
2211
|
}
|
|
2002
2212
|
}
|
|
2003
2213
|
}
|
|
2004
|
-
candidates.push(
|
|
2214
|
+
candidates.push(path10.join(getUserHome(), ".openclaw", "workspace"));
|
|
2005
2215
|
for (const candidate of candidates) {
|
|
2006
2216
|
if (await pathExists(candidate)) {
|
|
2007
2217
|
log.debug(`openclaw: resolved workspace dir to ${candidate}`);
|
|
@@ -2036,14 +2246,14 @@ var hermes_home_exports = {};
|
|
|
2036
2246
|
__export(hermes_home_exports, {
|
|
2037
2247
|
getHermesHome: () => getHermesHome
|
|
2038
2248
|
});
|
|
2039
|
-
import
|
|
2249
|
+
import path11 from "path";
|
|
2040
2250
|
import { homedir } from "os";
|
|
2041
2251
|
function getHermesHome() {
|
|
2042
2252
|
const fromEnv = process.env.HERMES_HOME;
|
|
2043
2253
|
if (fromEnv && fromEnv.trim() !== "") {
|
|
2044
|
-
return
|
|
2254
|
+
return path11.resolve(fromEnv);
|
|
2045
2255
|
}
|
|
2046
|
-
return
|
|
2256
|
+
return path11.join(homedir(), ".hermes");
|
|
2047
2257
|
}
|
|
2048
2258
|
var init_hermes_home = __esm({
|
|
2049
2259
|
"src/hermes-home.ts"() {
|
|
@@ -2064,20 +2274,20 @@ __export(hermes_config_exports, {
|
|
|
2064
2274
|
upsertHermesHook: () => upsertHermesHook,
|
|
2065
2275
|
upsertSoulRules: () => upsertSoulRules
|
|
2066
2276
|
});
|
|
2067
|
-
import
|
|
2068
|
-
import
|
|
2277
|
+
import YAML3 from "yaml";
|
|
2278
|
+
import path12 from "path";
|
|
2069
2279
|
function getHermesConfigPath() {
|
|
2070
|
-
return
|
|
2280
|
+
return path12.join(getHermesHome(), "config.yaml");
|
|
2071
2281
|
}
|
|
2072
2282
|
function getHermesSoulPath() {
|
|
2073
|
-
return
|
|
2283
|
+
return path12.join(getHermesHome(), "SOUL.md");
|
|
2074
2284
|
}
|
|
2075
2285
|
async function readConfigDoc() {
|
|
2076
2286
|
const content = await readFileSafe(getHermesConfigPath());
|
|
2077
|
-
if (!content || content.trim() === "") return new
|
|
2078
|
-
const doc =
|
|
2079
|
-
if (!doc.contents || !
|
|
2080
|
-
return new
|
|
2287
|
+
if (!content || content.trim() === "") return new YAML3.Document({});
|
|
2288
|
+
const doc = YAML3.parseDocument(content);
|
|
2289
|
+
if (!doc.contents || !YAML3.isMap(doc.contents)) {
|
|
2290
|
+
return new YAML3.Document({});
|
|
2081
2291
|
}
|
|
2082
2292
|
return doc;
|
|
2083
2293
|
}
|
|
@@ -2150,12 +2360,12 @@ async function removeSoulRules() {
|
|
|
2150
2360
|
await upsertSoulRules("");
|
|
2151
2361
|
}
|
|
2152
2362
|
function getHermesAllowlistPath() {
|
|
2153
|
-
return
|
|
2363
|
+
return path12.join(getHermesHome(), "shell-hooks-allowlist.json");
|
|
2154
2364
|
}
|
|
2155
2365
|
async function upsertHermesHook(event, entry) {
|
|
2156
2366
|
const doc = await readConfigDoc();
|
|
2157
2367
|
const rawSeq = doc.getIn(["hooks", event]);
|
|
2158
|
-
const currentJs =
|
|
2368
|
+
const currentJs = YAML3.isSeq(rawSeq) ? rawSeq.toJSON() : rawSeq;
|
|
2159
2369
|
const arr = Array.isArray(currentJs) ? currentJs : [];
|
|
2160
2370
|
const untouched = arr.filter((e) => e && typeof e === "object" && e.command !== entry.command);
|
|
2161
2371
|
const cleanEntry = { command: entry.command };
|
|
@@ -2169,7 +2379,7 @@ async function upsertHermesHook(event, entry) {
|
|
|
2169
2379
|
async function removeHermesHookByCommand(command) {
|
|
2170
2380
|
const doc = await readConfigDoc();
|
|
2171
2381
|
const rawHooks = doc.getIn(["hooks"]);
|
|
2172
|
-
const hooks =
|
|
2382
|
+
const hooks = YAML3.isMap(rawHooks) ? rawHooks.toJSON() : null;
|
|
2173
2383
|
if (!hooks || typeof hooks !== "object") return;
|
|
2174
2384
|
let changed = false;
|
|
2175
2385
|
for (const event of Object.keys(hooks)) {
|
|
@@ -2186,7 +2396,7 @@ async function removeHermesHookByCommand(command) {
|
|
|
2186
2396
|
}
|
|
2187
2397
|
if (!changed) return;
|
|
2188
2398
|
const remainingRaw = doc.getIn(["hooks"]);
|
|
2189
|
-
const remaining =
|
|
2399
|
+
const remaining = YAML3.isMap(remainingRaw) ? remainingRaw.toJSON() : null;
|
|
2190
2400
|
if (!remaining || Object.keys(remaining).length === 0) {
|
|
2191
2401
|
doc.deleteIn(["hooks"]);
|
|
2192
2402
|
}
|
|
@@ -2231,10 +2441,10 @@ __export(hermes_hooks_exports, {
|
|
|
2231
2441
|
removeHermesAgentHook: () => removeHermesAgentHook,
|
|
2232
2442
|
removeHermesHooks: () => removeHermesHooks
|
|
2233
2443
|
});
|
|
2234
|
-
import
|
|
2444
|
+
import path13 from "path";
|
|
2235
2445
|
import { chmod } from "fs/promises";
|
|
2236
2446
|
function getReportScriptPath() {
|
|
2237
|
-
return
|
|
2447
|
+
return path13.join(getHermesHome(), "hooks", "teamai-status-report.sh");
|
|
2238
2448
|
}
|
|
2239
2449
|
function buildReportScript() {
|
|
2240
2450
|
return [
|
|
@@ -2246,7 +2456,7 @@ function buildReportScript() {
|
|
|
2246
2456
|
}
|
|
2247
2457
|
async function injectHermesHooks() {
|
|
2248
2458
|
const scriptPath = getReportScriptPath();
|
|
2249
|
-
await ensureDir(
|
|
2459
|
+
await ensureDir(path13.dirname(scriptPath));
|
|
2250
2460
|
await writeFile(scriptPath, buildReportScript());
|
|
2251
2461
|
try {
|
|
2252
2462
|
await chmod(scriptPath, 493);
|
|
@@ -2321,7 +2531,7 @@ __export(hooks_exports, {
|
|
|
2321
2531
|
removeHooks: () => removeHooks,
|
|
2322
2532
|
sweepLegacyProjectHooks: () => sweepLegacyProjectHooks
|
|
2323
2533
|
});
|
|
2324
|
-
import
|
|
2534
|
+
import path14 from "path";
|
|
2325
2535
|
import { realpathSync } from "fs";
|
|
2326
2536
|
function detectFormat(tool) {
|
|
2327
2537
|
if (CODEX_TOOLS.has(tool)) return "codex";
|
|
@@ -2354,7 +2564,7 @@ function canonicalProjectRoot(projectRoot) {
|
|
|
2354
2564
|
try {
|
|
2355
2565
|
return realpathSync.native(projectRoot);
|
|
2356
2566
|
} catch {
|
|
2357
|
-
return
|
|
2567
|
+
return path14.resolve(projectRoot);
|
|
2358
2568
|
}
|
|
2359
2569
|
}
|
|
2360
2570
|
function gateTeamHookCommand(command, projectRoot) {
|
|
@@ -2414,16 +2624,31 @@ function toCodexEntry(def) {
|
|
|
2414
2624
|
return entry;
|
|
2415
2625
|
}
|
|
2416
2626
|
function toZcodeEntry(def) {
|
|
2417
|
-
const
|
|
2627
|
+
const ZCODE_TIMEOUT_MS = {
|
|
2628
|
+
SessionStart: 18e4,
|
|
2629
|
+
Stop: 6e4,
|
|
2630
|
+
PostToolUse: 3e4,
|
|
2631
|
+
UserPromptSubmit: 6e4
|
|
2632
|
+
};
|
|
2633
|
+
const timeoutMs = def.timeout !== void 0 ? def.timeout * 1e3 : ZCODE_TIMEOUT_MS[def.event] ?? 6e4;
|
|
2634
|
+
const entry = process.platform === "win32" ? {
|
|
2635
|
+
// Windows must NOT spawn bare `bash`: CreateProcess resolves it to
|
|
2636
|
+
// System32's WSL launcher before any PATH directory, and the WSL side
|
|
2637
|
+
// has a different $HOME (no ~/.teamai state) and often no Node ≥ 20.
|
|
2638
|
+
// cmd.exe is always present in System32 and resolves teamai from the
|
|
2639
|
+
// Windows PATH (the npm shim is a .cmd, so a shell is required).
|
|
2640
|
+
type: "process",
|
|
2641
|
+
command: "cmd",
|
|
2642
|
+
args: ["/c", def.command],
|
|
2643
|
+
timeoutMs
|
|
2644
|
+
} : {
|
|
2418
2645
|
type: "process",
|
|
2419
2646
|
command: "bash",
|
|
2420
|
-
// Stored verbatim: the shell payload must equal `def.command` exactly
|
|
2421
|
-
// managed-entry detection and the managed-hooks manifest share one
|
|
2422
|
-
// representation (the same invariant the Codex format keeps).
|
|
2423
|
-
// hook-dispatch is silent and failure-tolerant on its success paths, so no
|
|
2424
|
-
// shell redirection is layered on top of the payload.
|
|
2647
|
+
// Stored verbatim: the shell payload must equal `def.command` exactly
|
|
2648
|
+
// so managed-entry detection and the managed-hooks manifest share one
|
|
2649
|
+
// command representation (the same invariant the Codex format keeps).
|
|
2425
2650
|
args: ["-lc", def.command],
|
|
2426
|
-
|
|
2651
|
+
timeoutMs
|
|
2427
2652
|
};
|
|
2428
2653
|
const group = { hooks: [entry] };
|
|
2429
2654
|
if (def.matcher && def.matcher !== "*") group.matcher = def.matcher;
|
|
@@ -2431,7 +2656,7 @@ function toZcodeEntry(def) {
|
|
|
2431
2656
|
}
|
|
2432
2657
|
function zcodeEntryCommand(entry) {
|
|
2433
2658
|
const hook = entry.hooks?.[0];
|
|
2434
|
-
if (hook?.
|
|
2659
|
+
if (Array.isArray(hook?.args) && hook.args.length > 1) return hook.args[1] ?? "";
|
|
2435
2660
|
return hook?.command ?? "";
|
|
2436
2661
|
}
|
|
2437
2662
|
function desiredEventOrder(defs, mapEvent) {
|
|
@@ -2465,7 +2690,7 @@ async function reconcileClaudeFormat(settingsPath, tool, teamDefs, opts, teamAct
|
|
|
2465
2690
|
return true;
|
|
2466
2691
|
};
|
|
2467
2692
|
const expanded = expandHome(settingsPath);
|
|
2468
|
-
await ensureDir(
|
|
2693
|
+
await ensureDir(path14.dirname(expanded));
|
|
2469
2694
|
const settings = await readJson(expanded) ?? {};
|
|
2470
2695
|
if (!settings.hooks) settings.hooks = {};
|
|
2471
2696
|
let changed = false;
|
|
@@ -2497,7 +2722,7 @@ async function reconcileClaudeFormat(settingsPath, tool, teamDefs, opts, teamAct
|
|
|
2497
2722
|
}
|
|
2498
2723
|
async function reconcileCursorFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2499
2724
|
const expanded = expandHome(hooksPath);
|
|
2500
|
-
await ensureDir(
|
|
2725
|
+
await ensureDir(path14.dirname(expanded));
|
|
2501
2726
|
const hooksJson = await readJson(expanded) ?? { version: 1, hooks: {} };
|
|
2502
2727
|
if (!hooksJson.version) hooksJson.version = 1;
|
|
2503
2728
|
if (!hooksJson.hooks) hooksJson.hooks = {};
|
|
@@ -2545,7 +2770,7 @@ async function reconcileCursorFormat(hooksPath, tool, teamDefs, opts, priorTeamC
|
|
|
2545
2770
|
}
|
|
2546
2771
|
async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2547
2772
|
const expanded = expandHome(hooksPath);
|
|
2548
|
-
await ensureDir(
|
|
2773
|
+
await ensureDir(path14.dirname(expanded));
|
|
2549
2774
|
const hooksJson = await readJson(expanded) ?? {};
|
|
2550
2775
|
if (!hooksJson.hooks) hooksJson.hooks = {};
|
|
2551
2776
|
const isManaged = (entry) => {
|
|
@@ -2575,10 +2800,15 @@ async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCo
|
|
|
2575
2800
|
}
|
|
2576
2801
|
async function reconcileZcodeFormat(settingsPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2577
2802
|
const expanded = expandHome(settingsPath);
|
|
2578
|
-
await ensureDir(
|
|
2803
|
+
await ensureDir(path14.dirname(expanded));
|
|
2579
2804
|
const cfg = await readJson(expanded) ?? {};
|
|
2580
2805
|
if (!cfg.hooks) cfg.hooks = {};
|
|
2581
2806
|
let changed = false;
|
|
2807
|
+
const unknownHookKeys = Object.keys(cfg.hooks).filter((k) => k !== "enabled" && k !== "events");
|
|
2808
|
+
if (unknownHookKeys.length > 0) {
|
|
2809
|
+
for (const k of unknownHookKeys) delete cfg.hooks[k];
|
|
2810
|
+
changed = true;
|
|
2811
|
+
}
|
|
2582
2812
|
if (!opts.removeAll && cfg.hooks.enabled !== true) {
|
|
2583
2813
|
cfg.hooks.enabled = true;
|
|
2584
2814
|
changed = true;
|
|
@@ -2628,7 +2858,7 @@ function isAgentClaudeEntry(entry, slug) {
|
|
|
2628
2858
|
async function applyAgentHook(settingsPath, tool, def) {
|
|
2629
2859
|
const format = detectFormat(tool);
|
|
2630
2860
|
const expanded = expandHome(settingsPath);
|
|
2631
|
-
await ensureDir(
|
|
2861
|
+
await ensureDir(path14.dirname(expanded));
|
|
2632
2862
|
const hookDef = {
|
|
2633
2863
|
source: "team",
|
|
2634
2864
|
key: def.slug,
|
|
@@ -2854,15 +3084,15 @@ async function hasTeamaiHooks(settingsPath, tool, manifestPath) {
|
|
|
2854
3084
|
async function reconcileOpencodePlugin(baseDir, removeAll = false, installedBaseDir) {
|
|
2855
3085
|
const home = getUserHome();
|
|
2856
3086
|
const { injectOpencodeHooks: injectOpencodeHooks2, removeOpencodeHooks: removeOpencodeHooks2 } = await Promise.resolve().then(() => (init_opencode_hooks(), opencode_hooks_exports));
|
|
2857
|
-
if (
|
|
3087
|
+
if (path14.resolve(baseDir) !== path14.resolve(home)) {
|
|
2858
3088
|
await removeOpencodeHooks2(baseDir, "project");
|
|
2859
3089
|
}
|
|
2860
3090
|
if (removeAll) {
|
|
2861
3091
|
await removeOpencodeHooks2(home, "user");
|
|
2862
3092
|
return;
|
|
2863
3093
|
}
|
|
2864
|
-
const homeInstalled = await pathExists(
|
|
2865
|
-
const projectInstalled = installedBaseDir ? await pathExists(
|
|
3094
|
+
const homeInstalled = await pathExists(path14.join(home, ".config", "opencode"));
|
|
3095
|
+
const projectInstalled = installedBaseDir ? await pathExists(path14.join(installedBaseDir, ".opencode")) : false;
|
|
2866
3096
|
if (homeInstalled || projectInstalled) {
|
|
2867
3097
|
await injectOpencodeHooks2(home, "user");
|
|
2868
3098
|
}
|
|
@@ -2876,9 +3106,9 @@ async function injectHooksToAllTools(toolPaths, baseDir, filterAgents2) {
|
|
|
2876
3106
|
if (filterAgents2 && !filterAgents2.includes(tool)) continue;
|
|
2877
3107
|
if (skipped.has(tool)) continue;
|
|
2878
3108
|
if (paths.settings) {
|
|
2879
|
-
const toolRoot =
|
|
3109
|
+
const toolRoot = path14.join(resolvedBaseDir, paths.settings.split("/")[0]);
|
|
2880
3110
|
if (!await pathExists(toolRoot)) continue;
|
|
2881
|
-
const settingsPath =
|
|
3111
|
+
const settingsPath = path14.join(resolvedBaseDir, paths.settings);
|
|
2882
3112
|
try {
|
|
2883
3113
|
await injectHooks(settingsPath, tool);
|
|
2884
3114
|
} catch (e) {
|
|
@@ -2941,10 +3171,10 @@ async function reconcileHooksToAllTools(toolPaths, baseDir, teamDefs, manifestPa
|
|
|
2941
3171
|
continue;
|
|
2942
3172
|
}
|
|
2943
3173
|
if (!paths.settings) continue;
|
|
2944
|
-
const toolRoot =
|
|
2945
|
-
const installedRoot = opts.installedBaseDir ?
|
|
3174
|
+
const toolRoot = path14.join(baseDir, paths.settings.split("/")[0]);
|
|
3175
|
+
const installedRoot = opts.installedBaseDir ? path14.join(opts.installedBaseDir, paths.settings.split("/")[0]) : toolRoot;
|
|
2946
3176
|
if (!await pathExists(toolRoot) && !await pathExists(installedRoot)) continue;
|
|
2947
|
-
const settingsPath =
|
|
3177
|
+
const settingsPath = path14.join(baseDir, paths.settings);
|
|
2948
3178
|
try {
|
|
2949
3179
|
await reconcileHooks(settingsPath, tool, teamDefs, {
|
|
2950
3180
|
manifestPath,
|
|
@@ -2960,7 +3190,7 @@ async function reconcileHooksToAllTools(toolPaths, baseDir, teamDefs, manifestPa
|
|
|
2960
3190
|
async function hasInstalledCodexTrustGatedTool(toolPaths, baseDir) {
|
|
2961
3191
|
for (const [tool, paths] of Object.entries(toolPaths)) {
|
|
2962
3192
|
if (!isCodexTrustGatedTool(tool) || !paths.settings) continue;
|
|
2963
|
-
const toolRoot =
|
|
3193
|
+
const toolRoot = path14.join(baseDir, paths.settings.split("/")[0]);
|
|
2964
3194
|
if (await pathExists(toolRoot)) return true;
|
|
2965
3195
|
}
|
|
2966
3196
|
return false;
|
|
@@ -2982,7 +3212,11 @@ async function sweepLegacyProjectHooks(toolPaths, localConfig) {
|
|
|
2982
3212
|
}
|
|
2983
3213
|
}
|
|
2984
3214
|
async function reconcileTeamHooksForConfig(teamConfig, localConfig, opts = {}) {
|
|
2985
|
-
const { defs: teamDefs, builtin } = opts.removeAll ? { defs: [], builtin: void 0 } : await resolveTeamHooks(teamConfig, localConfig.repo.localPath, {
|
|
3215
|
+
const { defs: teamDefs, builtin } = opts.removeAll ? { defs: [], builtin: void 0 } : await resolveTeamHooks(teamConfig, localConfig.repo.localPath, {
|
|
3216
|
+
auto: opts.auto,
|
|
3217
|
+
silent: opts.silent,
|
|
3218
|
+
activeRoles: activeRoleIds(localConfig)
|
|
3219
|
+
});
|
|
2986
3220
|
const { baseDir, manifestPath } = resolveHookScope(localConfig);
|
|
2987
3221
|
let filterAgents2 = opts.filterAgents ?? localConfig.enabledAgents;
|
|
2988
3222
|
const disabled = localConfig.disabledAgents;
|
|
@@ -3008,6 +3242,7 @@ var init_hooks2 = __esm({
|
|
|
3008
3242
|
init_logger();
|
|
3009
3243
|
init_types();
|
|
3010
3244
|
init_types();
|
|
3245
|
+
init_roles();
|
|
3011
3246
|
init_builtin_hooks();
|
|
3012
3247
|
init_hooks();
|
|
3013
3248
|
init_home();
|
|
@@ -3248,7 +3483,7 @@ __export(builtin_rules_exports, {
|
|
|
3248
3483
|
LEGACY_RULE_NAMES: () => LEGACY_RULE_NAMES,
|
|
3249
3484
|
deployBuiltinRules: () => deployBuiltinRules
|
|
3250
3485
|
});
|
|
3251
|
-
import
|
|
3486
|
+
import path15 from "path";
|
|
3252
3487
|
import fs4 from "fs/promises";
|
|
3253
3488
|
async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
3254
3489
|
const baseDir = localConfig ? resolveBaseDir(localConfig) : getUserHome();
|
|
@@ -3263,19 +3498,19 @@ async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
|
3263
3498
|
continue;
|
|
3264
3499
|
}
|
|
3265
3500
|
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
3266
|
-
const rulesDir =
|
|
3501
|
+
const rulesDir = path15.join(baseDir, toolPath.rules);
|
|
3267
3502
|
if (!await pathExists(rulesDir)) continue;
|
|
3268
3503
|
try {
|
|
3269
3504
|
await ensureDir(rulesDir);
|
|
3270
3505
|
const ext = ruleFileExtensionForTool(tool);
|
|
3271
3506
|
for (const rule of builtinRules) {
|
|
3272
|
-
const destFile =
|
|
3507
|
+
const destFile = path15.join(rulesDir, `${rule.name}${ext}`);
|
|
3273
3508
|
const content = usesCursorMdcRules(tool) ? teamRuleToCursorMdc(rule.content) : rule.content;
|
|
3274
3509
|
await writeFile(destFile, content);
|
|
3275
3510
|
log.debug(`Deployed built-in rule ${rule.name} \u2192 ${tool}`);
|
|
3276
3511
|
if (ext !== ".md") {
|
|
3277
3512
|
try {
|
|
3278
|
-
await fs4.unlink(
|
|
3513
|
+
await fs4.unlink(path15.join(rulesDir, `${rule.name}.md`));
|
|
3279
3514
|
log.debug(`Removed legacy .md built-in rule ${rule.name} from ${tool}`);
|
|
3280
3515
|
} catch {
|
|
3281
3516
|
}
|
|
@@ -3283,7 +3518,7 @@ async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
|
3283
3518
|
}
|
|
3284
3519
|
for (const legacyName of LEGACY_RULE_NAMES) {
|
|
3285
3520
|
for (const legacyExt of /* @__PURE__ */ new Set([ext, ".md"])) {
|
|
3286
|
-
const legacyFile =
|
|
3521
|
+
const legacyFile = path15.join(rulesDir, `${legacyName}${legacyExt}`);
|
|
3287
3522
|
try {
|
|
3288
3523
|
await fs4.unlink(legacyFile);
|
|
3289
3524
|
log.debug(`Removed legacy built-in rule ${legacyName} from ${tool}`);
|
|
@@ -3370,7 +3605,7 @@ teamai-recall subagent \u7684\u8FD4\u56DE\u91CC\u5DF2\u5217\u51FA\u672C\u6B21\u6
|
|
|
3370
3605
|
});
|
|
3371
3606
|
|
|
3372
3607
|
// src/utils/pre-push-sync.ts
|
|
3373
|
-
import
|
|
3608
|
+
import path16 from "path";
|
|
3374
3609
|
async function syncTeamUpdatesToLocal(teamConfig, localConfig, lastPullRev) {
|
|
3375
3610
|
if (!lastPullRev) {
|
|
3376
3611
|
log.debug("No lastPullRev \u2014 skipping pre-push sync");
|
|
@@ -3382,12 +3617,12 @@ async function syncTeamUpdatesToLocal(teamConfig, localConfig, lastPullRev) {
|
|
|
3382
3617
|
await syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, lastPullRev);
|
|
3383
3618
|
}
|
|
3384
3619
|
async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, lastPullRev) {
|
|
3385
|
-
const teamRulesDir =
|
|
3620
|
+
const teamRulesDir = path16.join(repoPath, "rules");
|
|
3386
3621
|
if (!await pathExists(teamRulesDir)) return;
|
|
3387
3622
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
3388
3623
|
if (!toolPath.rules) continue;
|
|
3389
3624
|
if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
|
|
3390
|
-
const rulesDir =
|
|
3625
|
+
const rulesDir = path16.join(baseDir, toolPath.rules);
|
|
3391
3626
|
if (!await pathExists(rulesDir)) continue;
|
|
3392
3627
|
const ext = ruleFileExtensionForTool(tool);
|
|
3393
3628
|
const isMdcTool = usesCursorMdcRules(tool);
|
|
@@ -3396,9 +3631,9 @@ async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, last
|
|
|
3396
3631
|
if (!file.endsWith(ext)) continue;
|
|
3397
3632
|
const name = file.slice(0, -ext.length);
|
|
3398
3633
|
if (EXCLUDED_RULE_NAMES.has(name)) continue;
|
|
3399
|
-
const localFilePath =
|
|
3634
|
+
const localFilePath = path16.join(rulesDir, file);
|
|
3400
3635
|
const teamRelPath = `rules/${name}.md`;
|
|
3401
|
-
const teamFilePath =
|
|
3636
|
+
const teamFilePath = path16.join(teamRulesDir, `${name}.md`);
|
|
3402
3637
|
if (!await pathExists(teamFilePath)) continue;
|
|
3403
3638
|
if (isMdcTool) {
|
|
3404
3639
|
const localRaw = await readFileSafe(localFilePath);
|
|
@@ -3424,19 +3659,19 @@ async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, last
|
|
|
3424
3659
|
}
|
|
3425
3660
|
}
|
|
3426
3661
|
async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, lastPullRev) {
|
|
3427
|
-
const teamSkillsDir =
|
|
3662
|
+
const teamSkillsDir = path16.join(repoPath, "skills");
|
|
3428
3663
|
if (!await pathExists(teamSkillsDir)) return;
|
|
3429
3664
|
const teamSkillPaths = /* @__PURE__ */ new Map();
|
|
3430
3665
|
const topDirs = await listDirs(teamSkillsDir);
|
|
3431
3666
|
for (const dir of topDirs) {
|
|
3432
|
-
const dirPath =
|
|
3433
|
-
if (await pathExists(
|
|
3667
|
+
const dirPath = path16.join(teamSkillsDir, dir);
|
|
3668
|
+
if (await pathExists(path16.join(dirPath, "SKILL.md"))) {
|
|
3434
3669
|
teamSkillPaths.set(dir, dirPath);
|
|
3435
3670
|
} else {
|
|
3436
3671
|
const subDirs = await listDirs(dirPath);
|
|
3437
3672
|
for (const subDir of subDirs) {
|
|
3438
3673
|
if (!teamSkillPaths.has(subDir)) {
|
|
3439
|
-
teamSkillPaths.set(subDir,
|
|
3674
|
+
teamSkillPaths.set(subDir, path16.join(dirPath, subDir));
|
|
3440
3675
|
}
|
|
3441
3676
|
}
|
|
3442
3677
|
}
|
|
@@ -3445,12 +3680,12 @@ async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, las
|
|
|
3445
3680
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
3446
3681
|
if (!toolPath.skills) continue;
|
|
3447
3682
|
if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) continue;
|
|
3448
|
-
const skillsDir =
|
|
3683
|
+
const skillsDir = path16.join(baseDir, toolPath.skills);
|
|
3449
3684
|
if (!await pathExists(skillsDir)) continue;
|
|
3450
3685
|
const localSkillNames = await listDirs(skillsDir);
|
|
3451
3686
|
for (const skillName of localSkillNames) {
|
|
3452
3687
|
if (!teamSkillPaths.has(skillName)) continue;
|
|
3453
|
-
const localSkillDir =
|
|
3688
|
+
const localSkillDir = path16.join(skillsDir, skillName);
|
|
3454
3689
|
const teamSkillDir = teamSkillPaths.get(skillName);
|
|
3455
3690
|
if (await dirTeamSubsetEqual(localSkillDir, teamSkillDir, [CONTRIBUTORS_FILE3])) continue;
|
|
3456
3691
|
const teamFiles = await listFilesRecursive(teamSkillDir);
|
|
@@ -3458,10 +3693,10 @@ async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, las
|
|
|
3458
3693
|
let anyDiffers = false;
|
|
3459
3694
|
for (const file of teamFiles) {
|
|
3460
3695
|
if (file === CONTRIBUTORS_FILE3) continue;
|
|
3461
|
-
const localFile =
|
|
3462
|
-
const teamFile =
|
|
3696
|
+
const localFile = path16.join(localSkillDir, file);
|
|
3697
|
+
const teamFile = path16.join(teamSkillDir, file);
|
|
3463
3698
|
if (!await pathExists(localFile)) {
|
|
3464
|
-
const relFromRepo2 =
|
|
3699
|
+
const relFromRepo2 = path16.relative(repoPath, teamFile);
|
|
3465
3700
|
const oldContent2 = await getFileContentAtRev(repoPath, lastPullRev, relFromRepo2);
|
|
3466
3701
|
if (oldContent2 === null) {
|
|
3467
3702
|
anyDiffers = true;
|
|
@@ -3472,7 +3707,7 @@ async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, las
|
|
|
3472
3707
|
}
|
|
3473
3708
|
if (await fileContentEqual(localFile, teamFile)) continue;
|
|
3474
3709
|
anyDiffers = true;
|
|
3475
|
-
const relFromRepo =
|
|
3710
|
+
const relFromRepo = path16.relative(repoPath, teamFile);
|
|
3476
3711
|
const oldContent = await getFileContentAtRev(repoPath, lastPullRev, relFromRepo);
|
|
3477
3712
|
if (oldContent === null) {
|
|
3478
3713
|
allMatchOld = false;
|
|
@@ -3604,12 +3839,12 @@ var init_rest_auth = __esm({
|
|
|
3604
3839
|
import { execSync, spawnSync } from "child_process";
|
|
3605
3840
|
import fs5 from "fs";
|
|
3606
3841
|
import os2 from "os";
|
|
3607
|
-
import
|
|
3842
|
+
import path17 from "path";
|
|
3608
3843
|
function gfInstallDir() {
|
|
3609
|
-
return
|
|
3844
|
+
return path17.join(getTeamaiHomeDir(), "gf");
|
|
3610
3845
|
}
|
|
3611
3846
|
function gfBinPath() {
|
|
3612
|
-
return
|
|
3847
|
+
return path17.join(gfInstallDir(), "gf", "bin", "gf");
|
|
3613
3848
|
}
|
|
3614
3849
|
function shellQuote2(s) {
|
|
3615
3850
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
@@ -3747,7 +3982,7 @@ function ensureAuthenticated() {
|
|
|
3747
3982
|
}
|
|
3748
3983
|
function gfGetOAuthToken() {
|
|
3749
3984
|
try {
|
|
3750
|
-
const netrcPath2 =
|
|
3985
|
+
const netrcPath2 = path17.join(os2.homedir(), ".netrc");
|
|
3751
3986
|
if (!fs5.existsSync(netrcPath2)) return null;
|
|
3752
3987
|
const content = fs5.readFileSync(netrcPath2, "utf-8");
|
|
3753
3988
|
const match = content.match(
|
|
@@ -5599,7 +5834,7 @@ var init_repo_url4 = __esm({
|
|
|
5599
5834
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5600
5835
|
import fs6 from "fs";
|
|
5601
5836
|
import os3 from "os";
|
|
5602
|
-
import
|
|
5837
|
+
import path18 from "path";
|
|
5603
5838
|
function gitcodeApiBase() {
|
|
5604
5839
|
return GITCODE_API_BASE;
|
|
5605
5840
|
}
|
|
@@ -5627,7 +5862,7 @@ function authHeaders3(token) {
|
|
|
5627
5862
|
};
|
|
5628
5863
|
}
|
|
5629
5864
|
function netrcPath() {
|
|
5630
|
-
return
|
|
5865
|
+
return path18.join(os3.homedir(), ".netrc");
|
|
5631
5866
|
}
|
|
5632
5867
|
function readNetrcToken() {
|
|
5633
5868
|
try {
|
|
@@ -6113,12 +6348,13 @@ function buildRepoInfo5(owner, repo, remoteUrl) {
|
|
|
6113
6348
|
projectId: encodeURIComponent(`${owner}/${repo}`)
|
|
6114
6349
|
};
|
|
6115
6350
|
}
|
|
6116
|
-
function
|
|
6351
|
+
function parseGenericGitRepoInputWithOptions(input, options = {}) {
|
|
6117
6352
|
const trimmed = input.trim();
|
|
6118
|
-
|
|
6353
|
+
const allowExistingInsecureOrigin = options.allowExistingInsecureOrigin === true;
|
|
6354
|
+
if (/^http:\/\//i.test(trimmed) && !allowExistingInsecureOrigin) {
|
|
6119
6355
|
throw invalidRepoUrl("plain HTTP is not supported; use HTTPS or SSH");
|
|
6120
6356
|
}
|
|
6121
|
-
if (/^https
|
|
6357
|
+
if (/^https?:\/\//i.test(trimmed) || /^ssh:\/\//i.test(trimmed)) {
|
|
6122
6358
|
let parsed;
|
|
6123
6359
|
try {
|
|
6124
6360
|
parsed = new URL(trimmed);
|
|
@@ -6126,7 +6362,7 @@ function parseGenericGitRepoInput(input) {
|
|
|
6126
6362
|
throw invalidRepoUrl();
|
|
6127
6363
|
}
|
|
6128
6364
|
const httpCredentials = /^https?:$/i.test(parsed.protocol) && (parsed.username || parsed.password);
|
|
6129
|
-
if (!parsed.hostname || parsed.password || httpCredentials) {
|
|
6365
|
+
if (!parsed.hostname || !allowExistingInsecureOrigin && (parsed.password || httpCredentials)) {
|
|
6130
6366
|
throw new Error(
|
|
6131
6367
|
"Invalid Git repo URL. Do not embed credentials in the URL; configure a Git credential helper or SSH key instead."
|
|
6132
6368
|
);
|
|
@@ -6135,7 +6371,7 @@ function parseGenericGitRepoInput(input) {
|
|
|
6135
6371
|
throw invalidRepoUrl("query strings and fragments are not supported");
|
|
6136
6372
|
}
|
|
6137
6373
|
const { owner, repo, fullPath } = parsePath(parsed.pathname);
|
|
6138
|
-
const auth = parsed.username ? `${parsed.username}@` : "";
|
|
6374
|
+
const auth = /^ssh:$/i.test(parsed.protocol) && parsed.username ? `${parsed.username}@` : "";
|
|
6139
6375
|
const remoteUrl = `${parsed.protocol}//${auth}${parsed.host}/${fullPath}.git`;
|
|
6140
6376
|
return buildRepoInfo5(owner, repo, remoteUrl);
|
|
6141
6377
|
}
|
|
@@ -6152,6 +6388,12 @@ function parseGenericGitRepoInput(input) {
|
|
|
6152
6388
|
}
|
|
6153
6389
|
throw invalidRepoUrl();
|
|
6154
6390
|
}
|
|
6391
|
+
function parseGenericGitRepoInput(input) {
|
|
6392
|
+
return parseGenericGitRepoInputWithOptions(input);
|
|
6393
|
+
}
|
|
6394
|
+
function parseGenericGitExistingRemote(input) {
|
|
6395
|
+
return parseGenericGitRepoInputWithOptions(input, { allowExistingInsecureOrigin: true });
|
|
6396
|
+
}
|
|
6155
6397
|
var SUPPORTED_URL_HINT;
|
|
6156
6398
|
var init_repo_url5 = __esm({
|
|
6157
6399
|
"src/providers/git/repo-url.ts"() {
|
|
@@ -6527,17 +6769,17 @@ __export(builtin_skills_exports, {
|
|
|
6527
6769
|
deployBuiltinSkills: () => deployBuiltinSkills
|
|
6528
6770
|
});
|
|
6529
6771
|
import fs7 from "fs";
|
|
6530
|
-
import
|
|
6772
|
+
import path19 from "path";
|
|
6531
6773
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6532
6774
|
import fse2 from "fs-extra";
|
|
6533
6775
|
function getBuiltinSkillsDir() {
|
|
6534
|
-
const distDir =
|
|
6535
|
-
return
|
|
6776
|
+
const distDir = path19.dirname(fileURLToPath2(import.meta.url));
|
|
6777
|
+
return path19.join(distDir, "..", "skills");
|
|
6536
6778
|
}
|
|
6537
6779
|
async function copyBuiltinSkillDir(srcDir, destDir) {
|
|
6538
6780
|
await fse2.copy(srcDir, destDir, {
|
|
6539
6781
|
overwrite: true,
|
|
6540
|
-
filter: (srcPath) => !
|
|
6782
|
+
filter: (srcPath) => !path19.basename(srcPath).startsWith(".")
|
|
6541
6783
|
});
|
|
6542
6784
|
}
|
|
6543
6785
|
async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
@@ -6559,7 +6801,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
|
6559
6801
|
const skillNames = [];
|
|
6560
6802
|
for (const entry of entries) {
|
|
6561
6803
|
if (options?.skipRecall && RECALL_DEPENDENT_SKILLS.has(entry)) continue;
|
|
6562
|
-
const skillMd =
|
|
6804
|
+
const skillMd = path19.join(builtinDir, entry, "SKILL.md");
|
|
6563
6805
|
if (await pathExists(skillMd)) {
|
|
6564
6806
|
skillNames.push(entry);
|
|
6565
6807
|
}
|
|
@@ -6575,7 +6817,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
|
6575
6817
|
}
|
|
6576
6818
|
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
6577
6819
|
for (const skillName of skillNames) {
|
|
6578
|
-
const srcDir =
|
|
6820
|
+
const srcDir = path19.join(builtinDir, skillName);
|
|
6579
6821
|
const destDir = await resolveSkillDestination(tool, toolPath.skills, baseDir, skillName, srcDir);
|
|
6580
6822
|
try {
|
|
6581
6823
|
await copyBuiltinSkillDir(srcDir, destDir);
|
|
@@ -6603,142 +6845,6 @@ var init_builtin_skills = __esm({
|
|
|
6603
6845
|
}
|
|
6604
6846
|
});
|
|
6605
6847
|
|
|
6606
|
-
// src/roles.ts
|
|
6607
|
-
var roles_exports = {};
|
|
6608
|
-
__export(roles_exports, {
|
|
6609
|
-
describeRoles: () => describeRoles,
|
|
6610
|
-
findRole: () => findRole,
|
|
6611
|
-
listRoleIds: () => listRoleIds,
|
|
6612
|
-
loadRolesManifest: () => loadRolesManifest,
|
|
6613
|
-
resolveRoleResourceNamespaces: () => resolveRoleResourceNamespaces,
|
|
6614
|
-
saveRolesManifest: () => saveRolesManifest
|
|
6615
|
-
});
|
|
6616
|
-
import path19 from "path";
|
|
6617
|
-
import YAML3 from "yaml";
|
|
6618
|
-
import { z as z3 } from "zod";
|
|
6619
|
-
function validateManifestShape(raw) {
|
|
6620
|
-
if (!raw || typeof raw !== "object") {
|
|
6621
|
-
throw new Error("Invalid roles manifest: expected an object");
|
|
6622
|
-
}
|
|
6623
|
-
const candidate = raw;
|
|
6624
|
-
const roles = candidate.roles;
|
|
6625
|
-
if (!Array.isArray(roles) || roles.length === 0) {
|
|
6626
|
-
throw new Error("Invalid roles manifest: roles must be a non-empty array");
|
|
6627
|
-
}
|
|
6628
|
-
for (const role of roles) {
|
|
6629
|
-
if (!role || typeof role !== "object") {
|
|
6630
|
-
throw new Error("Invalid roles manifest: every role must be an object");
|
|
6631
|
-
}
|
|
6632
|
-
const resources = role.resources;
|
|
6633
|
-
if (!resources || typeof resources !== "object" || Array.isArray(resources)) {
|
|
6634
|
-
throw new Error(`Invalid roles manifest: role ${role.id ?? "<unknown>"} is missing resources`);
|
|
6635
|
-
}
|
|
6636
|
-
const ALLOWED_RESOURCE_KEYS = /* @__PURE__ */ new Set([...ROLE_RESOURCE_TYPES, "learnings"]);
|
|
6637
|
-
for (const key of Object.keys(resources)) {
|
|
6638
|
-
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
6639
|
-
throw new Error(`Invalid roles manifest: unknown resource type "${key}"`);
|
|
6640
|
-
}
|
|
6641
|
-
}
|
|
6642
|
-
}
|
|
6643
|
-
const manifest = RolesManifestSchema.parse(raw);
|
|
6644
|
-
const ids = /* @__PURE__ */ new Set();
|
|
6645
|
-
for (const role of manifest.roles) {
|
|
6646
|
-
if (ids.has(role.id)) {
|
|
6647
|
-
throw new Error(`Invalid roles manifest: duplicate role id "${role.id}"`);
|
|
6648
|
-
}
|
|
6649
|
-
ids.add(role.id);
|
|
6650
|
-
}
|
|
6651
|
-
return manifest;
|
|
6652
|
-
}
|
|
6653
|
-
async function loadRolesManifest(repoPath) {
|
|
6654
|
-
const manifestPath = path19.join(repoPath, "manifest", "roles.yaml");
|
|
6655
|
-
const content = await readFileSafe(manifestPath);
|
|
6656
|
-
if (!content) {
|
|
6657
|
-
throw new Error(`Roles manifest not found: ${manifestPath}`);
|
|
6658
|
-
}
|
|
6659
|
-
let raw;
|
|
6660
|
-
try {
|
|
6661
|
-
raw = YAML3.parse(content);
|
|
6662
|
-
} catch (error) {
|
|
6663
|
-
throw new Error(`Invalid roles manifest YAML: ${error.message}`);
|
|
6664
|
-
}
|
|
6665
|
-
return validateManifestShape(raw);
|
|
6666
|
-
}
|
|
6667
|
-
async function saveRolesManifest(repoPath, manifest) {
|
|
6668
|
-
validateManifestShape(manifest);
|
|
6669
|
-
const manifestDir = path19.join(repoPath, "manifest");
|
|
6670
|
-
const manifestPath = path19.join(manifestDir, "roles.yaml");
|
|
6671
|
-
await ensureDir(manifestDir);
|
|
6672
|
-
await writeFile(manifestPath, YAML3.stringify(manifest));
|
|
6673
|
-
}
|
|
6674
|
-
function findRole(manifest, roleId) {
|
|
6675
|
-
return manifest.roles.find((candidate) => candidate.id === roleId);
|
|
6676
|
-
}
|
|
6677
|
-
function listRoleIds(manifest) {
|
|
6678
|
-
return manifest.roles.map((role) => role.id);
|
|
6679
|
-
}
|
|
6680
|
-
function describeRoles(roles) {
|
|
6681
|
-
return roles.map((role) => role.description ? `${role.id}: ${role.description}` : `${role.id}`);
|
|
6682
|
-
}
|
|
6683
|
-
function getRoleOrThrow(manifest, roleId) {
|
|
6684
|
-
const role = manifest.roles.find((candidate) => candidate.id === roleId);
|
|
6685
|
-
if (!role) {
|
|
6686
|
-
throw new Error(`Unknown role "${roleId}". Valid roles: ${listRoleIds(manifest).join(", ")}`);
|
|
6687
|
-
}
|
|
6688
|
-
return role;
|
|
6689
|
-
}
|
|
6690
|
-
function resolveRoleResourceNamespaces(input) {
|
|
6691
|
-
const resolvedRoles = [
|
|
6692
|
-
getRoleOrThrow(input.manifest, input.primaryRole),
|
|
6693
|
-
...input.additionalRoles.map((roleId) => getRoleOrThrow(input.manifest, roleId))
|
|
6694
|
-
];
|
|
6695
|
-
const namespaces = {
|
|
6696
|
-
knowledge: [],
|
|
6697
|
-
skills: [],
|
|
6698
|
-
// Roles never contribute learnings namespaces; only projects do. Kept empty
|
|
6699
|
-
// so the shape matches project resolution for a clean union at the call site.
|
|
6700
|
-
learnings: []
|
|
6701
|
-
};
|
|
6702
|
-
for (const type of ROLE_RESOURCE_TYPES) {
|
|
6703
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6704
|
-
for (const role of resolvedRoles) {
|
|
6705
|
-
for (const namespace of role.resources[type]) {
|
|
6706
|
-
if (seen.has(namespace)) continue;
|
|
6707
|
-
seen.add(namespace);
|
|
6708
|
-
namespaces[type].push(namespace);
|
|
6709
|
-
}
|
|
6710
|
-
}
|
|
6711
|
-
}
|
|
6712
|
-
return namespaces;
|
|
6713
|
-
}
|
|
6714
|
-
var ROLE_RESOURCE_TYPES, RoleResourceNamespacesSchema, RoleSchema, RolesManifestSchema;
|
|
6715
|
-
var init_roles = __esm({
|
|
6716
|
-
"src/roles.ts"() {
|
|
6717
|
-
"use strict";
|
|
6718
|
-
init_fs();
|
|
6719
|
-
ROLE_RESOURCE_TYPES = ["knowledge", "skills"];
|
|
6720
|
-
RoleResourceNamespacesSchema = z3.object({
|
|
6721
|
-
knowledge: z3.array(z3.string().min(1)),
|
|
6722
|
-
skills: z3.array(z3.string().min(1)),
|
|
6723
|
-
// learnings is accepted for backward compatibility but ignored at runtime.
|
|
6724
|
-
// All learnings are shared flat across the entire team (no namespace isolation).
|
|
6725
|
-
learnings: z3.array(z3.string()).optional()
|
|
6726
|
-
});
|
|
6727
|
-
RoleSchema = z3.object({
|
|
6728
|
-
id: z3.string().min(1),
|
|
6729
|
-
description: z3.string().default(""),
|
|
6730
|
-
resources: RoleResourceNamespacesSchema
|
|
6731
|
-
});
|
|
6732
|
-
RolesManifestSchema = z3.object({
|
|
6733
|
-
version: z3.number(),
|
|
6734
|
-
roles: z3.array(RoleSchema).min(1),
|
|
6735
|
-
// defaults.shareTarget was removed: learnings are flat, no namespace routing needed.
|
|
6736
|
-
// Old manifests with a defaults block are still parseable (z.passthrough on object level).
|
|
6737
|
-
defaults: z3.object({}).passthrough().optional()
|
|
6738
|
-
});
|
|
6739
|
-
}
|
|
6740
|
-
});
|
|
6741
|
-
|
|
6742
6848
|
// src/utils/path-safety.ts
|
|
6743
6849
|
var path_safety_exports = {};
|
|
6744
6850
|
__export(path_safety_exports, {
|
|
@@ -7487,10 +7593,20 @@ async function countInterventions(transcriptPath) {
|
|
|
7487
7593
|
const { interrupt, toolReject, toolError } = await scanTranscriptStop(transcriptPath);
|
|
7488
7594
|
return { interrupt, toolReject, toolError };
|
|
7489
7595
|
}
|
|
7490
|
-
function
|
|
7596
|
+
function wordBoundaryPattern(keyword) {
|
|
7597
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7598
|
+
return new RegExp(`(?<![\\p{L}\\p{N}_])${escaped}(?![\\p{L}\\p{N}_])`, "u");
|
|
7599
|
+
}
|
|
7600
|
+
function containsKeyword(lower, keyword) {
|
|
7601
|
+
const k = keyword.trim().normalize("NFC").toLowerCase();
|
|
7602
|
+
if (!k) return false;
|
|
7603
|
+
if (UNSPACED_SCRIPT_RE.test(k)) return lower.includes(k);
|
|
7604
|
+
return wordBoundaryPattern(k).test(lower);
|
|
7605
|
+
}
|
|
7606
|
+
function isCorrectionPrompt(text, extraKeywords = []) {
|
|
7491
7607
|
if (!text) return false;
|
|
7492
|
-
const lower = text.toLowerCase();
|
|
7493
|
-
return CORRECTION_KEYWORDS.some((k) => lower
|
|
7608
|
+
const lower = text.normalize("NFC").toLowerCase();
|
|
7609
|
+
return [...CORRECTION_KEYWORDS, ...extraKeywords].some((k) => containsKeyword(lower, k));
|
|
7494
7610
|
}
|
|
7495
7611
|
function mapEventType(hookEventName) {
|
|
7496
7612
|
switch (hookEventName) {
|
|
@@ -7511,7 +7627,7 @@ function mapEventType(hookEventName) {
|
|
|
7511
7627
|
return null;
|
|
7512
7628
|
}
|
|
7513
7629
|
}
|
|
7514
|
-
async function parseHookEvent(raw, tool) {
|
|
7630
|
+
async function parseHookEvent(raw, tool, options) {
|
|
7515
7631
|
if (!raw.trim()) return null;
|
|
7516
7632
|
let hookData;
|
|
7517
7633
|
try {
|
|
@@ -7550,6 +7666,7 @@ async function parseHookEvent(raw, tool) {
|
|
|
7550
7666
|
}
|
|
7551
7667
|
if (eventType === "prompt_submit" && typeof hookData.prompt === "string") {
|
|
7552
7668
|
event.promptSummary = hookData.prompt.slice(0, 200);
|
|
7669
|
+
event.correction = isCorrectionPrompt(hookData.prompt, options?.correctionKeywords);
|
|
7553
7670
|
}
|
|
7554
7671
|
if (eventType === "stop" && typeof hookData.transcript_path === "string") {
|
|
7555
7672
|
event.transcriptPath = hookData.transcript_path;
|
|
@@ -7856,7 +7973,8 @@ function aggregateSessionMetrics(events) {
|
|
|
7856
7973
|
const stopAt = lastStopAt.get(event.sessionId);
|
|
7857
7974
|
if (stopAt !== void 0) {
|
|
7858
7975
|
const gap = new Date(event.timestamp).getTime() - stopAt;
|
|
7859
|
-
|
|
7976
|
+
const isCorrection = event.correction ?? isCorrectionPrompt(event.promptSummary);
|
|
7977
|
+
if (gap >= 0 && gap <= CORRECTION_WINDOW_MS && isCorrection) {
|
|
7860
7978
|
m.correction++;
|
|
7861
7979
|
}
|
|
7862
7980
|
lastStopAt.delete(event.sessionId);
|
|
@@ -7918,7 +8036,7 @@ async function dashboardReport(toolArg) {
|
|
|
7918
8036
|
compactEvents().catch(() => {
|
|
7919
8037
|
});
|
|
7920
8038
|
}
|
|
7921
|
-
var TRANSCRIPT_TAIL_BYTES, STOPPED_OUTPUT_MAX_CHARS, CODEX_USAGE_TAIL_BYTES, CODEX_USAGE_MAX_ATTEMPTS, CODEX_USAGE_RETRY_MS, CODEBUDDY_USAGE_MAX_ATTEMPTS, CODEBUDDY_USAGE_RETRY_MS, CODEBUDDY_BLOB_MAX_COUNT, CODEBUDDY_REJECT_MARKER;
|
|
8039
|
+
var TRANSCRIPT_TAIL_BYTES, STOPPED_OUTPUT_MAX_CHARS, CODEX_USAGE_TAIL_BYTES, CODEX_USAGE_MAX_ATTEMPTS, CODEX_USAGE_RETRY_MS, CODEBUDDY_USAGE_MAX_ATTEMPTS, CODEBUDDY_USAGE_RETRY_MS, CODEBUDDY_BLOB_MAX_COUNT, CODEBUDDY_REJECT_MARKER, UNSPACED_SCRIPT_RE;
|
|
7922
8040
|
var init_dashboard_collector = __esm({
|
|
7923
8041
|
"src/dashboard-collector.ts"() {
|
|
7924
8042
|
"use strict";
|
|
@@ -7941,23 +8059,77 @@ var init_dashboard_collector = __esm({
|
|
|
7941
8059
|
CODEBUDDY_USAGE_RETRY_MS = 250;
|
|
7942
8060
|
CODEBUDDY_BLOB_MAX_COUNT = 2e3;
|
|
7943
8061
|
CODEBUDDY_REJECT_MARKER = "User rejected this command";
|
|
8062
|
+
UNSPACED_SCRIPT_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
8063
|
+
}
|
|
8064
|
+
});
|
|
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
|
+
});
|
|
7944
8125
|
}
|
|
7945
8126
|
});
|
|
7946
8127
|
|
|
7947
8128
|
// src/agent-version.ts
|
|
7948
|
-
import { execFile } from "child_process";
|
|
7949
8129
|
import { readFile } from "fs/promises";
|
|
7950
8130
|
import path22 from "path";
|
|
7951
8131
|
async function execVersion(bin, args = ["--version"]) {
|
|
7952
|
-
return
|
|
7953
|
-
execFile(bin, args, { timeout: 5e3 }, (err, stdout) => {
|
|
7954
|
-
if (err) {
|
|
7955
|
-
resolve("");
|
|
7956
|
-
return;
|
|
7957
|
-
}
|
|
7958
|
-
resolve(stdout.trim());
|
|
7959
|
-
});
|
|
7960
|
-
});
|
|
8132
|
+
return probeBinary(bin, args);
|
|
7961
8133
|
}
|
|
7962
8134
|
async function readPlistVersion(appPath) {
|
|
7963
8135
|
const plistPath = path22.join(appPath, "Contents", "Info.plist");
|
|
@@ -8031,6 +8203,7 @@ var init_agent_version = __esm({
|
|
|
8031
8203
|
"src/agent-version.ts"() {
|
|
8032
8204
|
"use strict";
|
|
8033
8205
|
init_logger();
|
|
8206
|
+
init_exec();
|
|
8034
8207
|
VERSION_CACHE = /* @__PURE__ */ new Map();
|
|
8035
8208
|
CODEBUDDY_IDE_PATHS = [
|
|
8036
8209
|
"/Applications/CodeBuddy.app",
|
|
@@ -8297,7 +8470,7 @@ var CLAUDE_TOOLS, CURSOR_TOOLS2, CODEX_TOOLS2, BUDDY_TOOLS, OPENCODE_TOOLS, MCP_
|
|
|
8297
8470
|
var init_mcp_format = __esm({
|
|
8298
8471
|
"src/resources/mcp-format.ts"() {
|
|
8299
8472
|
"use strict";
|
|
8300
|
-
CLAUDE_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "qoder", "zcode"]);
|
|
8473
|
+
CLAUDE_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "qoder", "kiro", "zcode"]);
|
|
8301
8474
|
CURSOR_TOOLS2 = /* @__PURE__ */ new Set(["cursor"]);
|
|
8302
8475
|
CODEX_TOOLS2 = /* @__PURE__ */ new Set(["codex", "codex-internal", "tcodex"]);
|
|
8303
8476
|
BUDDY_TOOLS = /* @__PURE__ */ new Set(["codebuddy", "workbuddy"]);
|
|
@@ -8354,7 +8527,8 @@ function teamMcpToDef(s) {
|
|
|
8354
8527
|
env: s.env,
|
|
8355
8528
|
timeout: s.timeout,
|
|
8356
8529
|
requires: s.requires,
|
|
8357
|
-
tools: s.tools
|
|
8530
|
+
tools: s.tools,
|
|
8531
|
+
roles: s.roles
|
|
8358
8532
|
};
|
|
8359
8533
|
}
|
|
8360
8534
|
async function parseTeamMcpServers(repoPath) {
|
|
@@ -8380,7 +8554,8 @@ var init_mcp = __esm({
|
|
|
8380
8554
|
env: z4.record(z4.string(), z4.string()).optional(),
|
|
8381
8555
|
timeout: z4.number().int().positive().optional(),
|
|
8382
8556
|
requires: z4.array(z4.string()).optional(),
|
|
8383
|
-
tools: z4.array(z4.string()).optional()
|
|
8557
|
+
tools: z4.array(z4.string()).optional(),
|
|
8558
|
+
roles: z4.array(z4.string()).optional()
|
|
8384
8559
|
}).refine((s) => s.transport === "stdio" ? !!s.command : true, {
|
|
8385
8560
|
message: "stdio transport requires `command`"
|
|
8386
8561
|
}).refine((s) => s.transport === "stdio" ? true : !!s.url, {
|
|
@@ -8703,6 +8878,14 @@ async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
|
|
|
8703
8878
|
return { changes, wrote };
|
|
8704
8879
|
}
|
|
8705
8880
|
const excluded = new Set(localConfig.excludedSkills ?? []);
|
|
8881
|
+
const activeRoles = activeRoleIds(localConfig);
|
|
8882
|
+
if (!removeAll) {
|
|
8883
|
+
await warnUnknownRoleIds(
|
|
8884
|
+
localConfig.repo.localPath,
|
|
8885
|
+
"mcp.yaml",
|
|
8886
|
+
teamDefs.map((def) => ({ kind: "server", name: def.name, roles: def.roles }))
|
|
8887
|
+
);
|
|
8888
|
+
}
|
|
8706
8889
|
const targets = await resolveMcpTargets(teamConfig, localConfig);
|
|
8707
8890
|
if (targets.length === 0) return { changes, wrote };
|
|
8708
8891
|
const dataHome = getDataHome(localConfig);
|
|
@@ -8726,6 +8909,7 @@ async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
|
|
|
8726
8909
|
const desired = /* @__PURE__ */ new Map();
|
|
8727
8910
|
for (const raw of teamDefs) {
|
|
8728
8911
|
if (raw.tools && !raw.tools.includes(target.tool)) continue;
|
|
8912
|
+
if (!matchesRoles(raw.roles, activeRoles)) continue;
|
|
8729
8913
|
if (excluded.has(raw.name)) {
|
|
8730
8914
|
changes.push({ tool: target.tool, server: raw.name, action: "skipped", reason: "excluded by user" });
|
|
8731
8915
|
continue;
|
|
@@ -8878,6 +9062,7 @@ var init_mcp_reconcile = __esm({
|
|
|
8878
9062
|
init_types();
|
|
8879
9063
|
init_mcp_format();
|
|
8880
9064
|
init_mcp();
|
|
9065
|
+
init_roles();
|
|
8881
9066
|
init_fs();
|
|
8882
9067
|
init_logger();
|
|
8883
9068
|
init_mcp_manifest();
|
|
@@ -9179,7 +9364,7 @@ __export(local_agent_exports, {
|
|
|
9179
9364
|
import fs14 from "fs";
|
|
9180
9365
|
import os7 from "os";
|
|
9181
9366
|
import path28 from "path";
|
|
9182
|
-
import { execFile
|
|
9367
|
+
import { execFile } from "child_process";
|
|
9183
9368
|
import { promisify } from "util";
|
|
9184
9369
|
import fse4 from "fs-extra";
|
|
9185
9370
|
function modelAgentKind(tool) {
|
|
@@ -11418,7 +11603,7 @@ var init_local_agent = __esm({
|
|
|
11418
11603
|
init_types();
|
|
11419
11604
|
init_home();
|
|
11420
11605
|
init_git2();
|
|
11421
|
-
execFileAsync = promisify(
|
|
11606
|
+
execFileAsync = promisify(execFile);
|
|
11422
11607
|
LOCAL_AGENT_DIR = "local-agent";
|
|
11423
11608
|
CONFIG_FILE = "config.json";
|
|
11424
11609
|
MANIFEST_FILE = "manifest.json";
|
|
@@ -12315,6 +12500,7 @@ var init_skills = __esm({
|
|
|
12315
12500
|
await this.addTombstone(name, localConfig);
|
|
12316
12501
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12317
12502
|
if (!toolPath.skills) continue;
|
|
12503
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12318
12504
|
let skillDir;
|
|
12319
12505
|
if (tool === "openclaw") {
|
|
12320
12506
|
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
@@ -12571,6 +12757,7 @@ var init_rules = __esm({
|
|
|
12571
12757
|
await this.addTombstone(name, localConfig);
|
|
12572
12758
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12573
12759
|
if (!toolPath.rules) continue;
|
|
12760
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12574
12761
|
const extensions = /* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"]);
|
|
12575
12762
|
for (const extension of extensions) {
|
|
12576
12763
|
const filePath = path32.join(baseDir, toolPath.rules, `${name}${extension}`);
|
|
@@ -12612,6 +12799,7 @@ var init_rules = __esm({
|
|
|
12612
12799
|
const baseDir = resolveBaseDir(localConfig);
|
|
12613
12800
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12614
12801
|
if (!toolPath.rules) continue;
|
|
12802
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12615
12803
|
if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
|
|
12616
12804
|
const destDir = path32.join(baseDir, toolPath.rules);
|
|
12617
12805
|
if (!await pathExists(destDir)) continue;
|
|
@@ -12815,9 +13003,9 @@ var init_env = __esm({
|
|
|
12815
13003
|
}
|
|
12816
13004
|
const envYamlPath = path34.join(localConfig.repo.localPath, "env", "env.yaml");
|
|
12817
13005
|
if (!await pathExists(envYamlPath)) return [];
|
|
12818
|
-
const { execFile:
|
|
13006
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
12819
13007
|
const { promisify: promisify4 } = await import("util");
|
|
12820
|
-
const execFileAsync4 = promisify4(
|
|
13008
|
+
const execFileAsync4 = promisify4(execFile4);
|
|
12821
13009
|
try {
|
|
12822
13010
|
await execFileAsync4("git", ["diff", "--exit-code", "env/env.yaml"], {
|
|
12823
13011
|
cwd: localConfig.repo.localPath
|
|
@@ -12985,12 +13173,20 @@ import path35 from "path";
|
|
|
12985
13173
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
12986
13174
|
import matter3 from "gray-matter";
|
|
12987
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
|
+
}
|
|
12988
13182
|
function agentFileExtensionForTool(tool) {
|
|
12989
13183
|
switch (tool) {
|
|
12990
13184
|
case "codex":
|
|
12991
13185
|
case "codex-internal":
|
|
12992
13186
|
case "tcodex":
|
|
12993
13187
|
return ".toml";
|
|
13188
|
+
case "kiro":
|
|
13189
|
+
return ".json";
|
|
12994
13190
|
default:
|
|
12995
13191
|
return ".md";
|
|
12996
13192
|
}
|
|
@@ -13051,6 +13247,35 @@ function renderForJoycode(spec) {
|
|
|
13051
13247
|
content: renderMarkdownAgent(spec, spec.tool_extras?.["joycode"])
|
|
13052
13248
|
};
|
|
13053
13249
|
}
|
|
13250
|
+
function isRecord(value) {
|
|
13251
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13252
|
+
}
|
|
13253
|
+
function isManagedKiroSessionHook(value) {
|
|
13254
|
+
return isRecord(value) && typeof value["command"] === "string" && value["command"].includes("teamai hook-dispatch session-start --tool kiro");
|
|
13255
|
+
}
|
|
13256
|
+
function renderForKiro(spec) {
|
|
13257
|
+
const extras = { ...spec.tool_extras?.["kiro"] ?? {} };
|
|
13258
|
+
const existingHooks = isRecord(extras["hooks"]) ? { ...extras["hooks"] } : {};
|
|
13259
|
+
const existingAgentSpawn = Array.isArray(existingHooks["agentSpawn"]) ? existingHooks["agentSpawn"].filter((entry) => !isManagedKiroSessionHook(entry)) : [];
|
|
13260
|
+
existingHooks["agentSpawn"] = [
|
|
13261
|
+
...existingAgentSpawn,
|
|
13262
|
+
{ command: KIRO_SESSION_START_COMMAND }
|
|
13263
|
+
];
|
|
13264
|
+
extras["hooks"] = existingHooks;
|
|
13265
|
+
const json = {
|
|
13266
|
+
name: spec.name,
|
|
13267
|
+
description: spec.description,
|
|
13268
|
+
prompt: spec.instructions
|
|
13269
|
+
};
|
|
13270
|
+
if (spec.model !== void 0) json["model"] = spec.model;
|
|
13271
|
+
if (spec.tools !== void 0 && spec.tools.length > 0) json["tools"] = spec.tools;
|
|
13272
|
+
Object.assign(json, extras);
|
|
13273
|
+
return {
|
|
13274
|
+
ext: agentFileExtensionForTool("kiro"),
|
|
13275
|
+
content: `${JSON.stringify(json, null, 2)}
|
|
13276
|
+
`
|
|
13277
|
+
};
|
|
13278
|
+
}
|
|
13054
13279
|
function renderForCodex(spec) {
|
|
13055
13280
|
return {
|
|
13056
13281
|
ext: agentFileExtensionForTool("codex"),
|
|
@@ -13178,6 +13403,43 @@ function reverseFromJoycode(filePath, content) {
|
|
|
13178
13403
|
}
|
|
13179
13404
|
return { ok: true, spec };
|
|
13180
13405
|
}
|
|
13406
|
+
function reverseFromKiro(filePath, content) {
|
|
13407
|
+
let parsed;
|
|
13408
|
+
try {
|
|
13409
|
+
const raw = JSON.parse(content);
|
|
13410
|
+
if (!isRecord(raw)) return { ok: false, reason: "agent config must be a JSON object" };
|
|
13411
|
+
parsed = raw;
|
|
13412
|
+
} catch (err) {
|
|
13413
|
+
return { ok: false, reason: `parse error: ${err.message}` };
|
|
13414
|
+
}
|
|
13415
|
+
const name = parsed["name"] ?? path35.basename(filePath, ".json");
|
|
13416
|
+
if (!name) return { ok: false, reason: "missing field name" };
|
|
13417
|
+
if (!parsed["description"]) return { ok: false, reason: "missing field description" };
|
|
13418
|
+
if (!parsed["prompt"]) return { ok: false, reason: "missing field prompt" };
|
|
13419
|
+
const extras = {};
|
|
13420
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
13421
|
+
if (!COMMON_KIRO_FIELDS.has(key)) extras[key] = value;
|
|
13422
|
+
}
|
|
13423
|
+
if (isRecord(extras["hooks"])) {
|
|
13424
|
+
const hooks = { ...extras["hooks"] };
|
|
13425
|
+
if (Array.isArray(hooks["agentSpawn"])) {
|
|
13426
|
+
const remaining = hooks["agentSpawn"].filter((entry) => !isManagedKiroSessionHook(entry));
|
|
13427
|
+
if (remaining.length > 0) hooks["agentSpawn"] = remaining;
|
|
13428
|
+
else delete hooks["agentSpawn"];
|
|
13429
|
+
}
|
|
13430
|
+
if (Object.keys(hooks).length > 0) extras["hooks"] = hooks;
|
|
13431
|
+
else delete extras["hooks"];
|
|
13432
|
+
}
|
|
13433
|
+
const spec = {
|
|
13434
|
+
name,
|
|
13435
|
+
description: parsed["description"],
|
|
13436
|
+
instructions: parsed["prompt"]
|
|
13437
|
+
};
|
|
13438
|
+
if (parsed["model"] !== void 0) spec.model = parsed["model"];
|
|
13439
|
+
if (parsed["tools"] !== void 0) spec.tools = parsed["tools"];
|
|
13440
|
+
if (Object.keys(extras).length > 0) spec.tool_extras = { kiro: extras };
|
|
13441
|
+
return { ok: true, spec };
|
|
13442
|
+
}
|
|
13181
13443
|
function reverseFromCodex(filePath, content) {
|
|
13182
13444
|
let parsed;
|
|
13183
13445
|
try {
|
|
@@ -13326,16 +13588,19 @@ function renderForTool(spec, tool) {
|
|
|
13326
13588
|
return renderForJoycode(spec);
|
|
13327
13589
|
case "qoder":
|
|
13328
13590
|
return renderForClaude(spec);
|
|
13591
|
+
case "kiro":
|
|
13592
|
+
return renderForKiro(spec);
|
|
13329
13593
|
case "zcode":
|
|
13330
13594
|
return renderForClaude(spec);
|
|
13331
13595
|
case "opencode":
|
|
13332
13596
|
return renderForOpencode(spec);
|
|
13333
13597
|
}
|
|
13334
13598
|
}
|
|
13335
|
-
var ALL_SUPPORTED_TOOLS, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_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;
|
|
13336
13600
|
var init_agent_format = __esm({
|
|
13337
13601
|
"src/resources/agent-format.ts"() {
|
|
13338
13602
|
"use strict";
|
|
13603
|
+
init_builtin_hooks();
|
|
13339
13604
|
ALL_SUPPORTED_TOOLS = [
|
|
13340
13605
|
"claude",
|
|
13341
13606
|
"claude-internal",
|
|
@@ -13347,12 +13612,16 @@ var init_agent_format = __esm({
|
|
|
13347
13612
|
"cursor",
|
|
13348
13613
|
"joycode",
|
|
13349
13614
|
"qoder",
|
|
13615
|
+
"kiro",
|
|
13350
13616
|
"zcode",
|
|
13351
13617
|
"opencode"
|
|
13352
13618
|
];
|
|
13619
|
+
AGENT_FILE_EXTENSIONS = [".md", ".toml", ".json"];
|
|
13620
|
+
KIRO_SESSION_START_COMMAND = getDispatchCommand("session-start", "kiro");
|
|
13353
13621
|
COMMON_CLAUDE_FIELDS = /* @__PURE__ */ new Set(["name", "description", "model", "tools"]);
|
|
13354
13622
|
COMMON_CURSOR_FIELDS = /* @__PURE__ */ new Set(["agent_id", "description", "model", "tools"]);
|
|
13355
13623
|
COMMON_CODEX_FIELDS = /* @__PURE__ */ new Set(["name", "description", "developer_instructions", "model"]);
|
|
13624
|
+
COMMON_KIRO_FIELDS = /* @__PURE__ */ new Set(["name", "description", "prompt", "model", "tools"]);
|
|
13356
13625
|
COMMON_OPENCODE_FIELDS = /* @__PURE__ */ new Set(["description", "model"]);
|
|
13357
13626
|
MERGE_COMMON_FIELDS = [
|
|
13358
13627
|
"name",
|
|
@@ -13385,8 +13654,7 @@ async function removeStaleAgentSiblings(targetAgentsDir, stem, targetExt) {
|
|
|
13385
13654
|
return;
|
|
13386
13655
|
}
|
|
13387
13656
|
for (const file of files) {
|
|
13388
|
-
|
|
13389
|
-
if (base !== stem) continue;
|
|
13657
|
+
if (agentStemFromFilename(file) !== stem) continue;
|
|
13390
13658
|
if (file === `${stem}${targetExt}`) continue;
|
|
13391
13659
|
try {
|
|
13392
13660
|
await remove(path36.join(targetAgentsDir, file));
|
|
@@ -13469,10 +13737,267 @@ var init_builtin_agents = __esm({
|
|
|
13469
13737
|
}
|
|
13470
13738
|
});
|
|
13471
13739
|
|
|
13472
|
-
// 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
|
+
});
|
|
13473
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";
|
|
13474
13982
|
import { isDeepStrictEqual } from "util";
|
|
13475
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
|
+
}
|
|
13476
14001
|
function mergeCanonicalEdits(canonical, perTool) {
|
|
13477
14002
|
const merged = { ...canonical };
|
|
13478
14003
|
const extras = { ...canonical.tool_extras };
|
|
@@ -13514,10 +14039,12 @@ function mergeCanonicalEdits(canonical, perTool) {
|
|
|
13514
14039
|
else delete merged.tool_extras;
|
|
13515
14040
|
return { ok: true, spec: merged };
|
|
13516
14041
|
}
|
|
13517
|
-
function
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
14042
|
+
async function removeStaleAgentSiblings2(agentsDir, stem, targetExt) {
|
|
14043
|
+
for (const file of await listFiles(agentsDir)) {
|
|
14044
|
+
if (agentStemFromFilename(file) !== stem || file === `${stem}${targetExt}`) continue;
|
|
14045
|
+
await remove(path38.join(agentsDir, file));
|
|
14046
|
+
log.debug(`Removed stale agent sibling ${file} for ${stem}`);
|
|
14047
|
+
}
|
|
13521
14048
|
}
|
|
13522
14049
|
function isKnownTool(tool) {
|
|
13523
14050
|
return ALL_SUPPORTED_TOOLS.includes(tool);
|
|
@@ -13529,7 +14056,7 @@ async function agentContentEqual(tool, localPath, teamPath) {
|
|
|
13529
14056
|
const canonicalContent = await readFileSafe(teamPath);
|
|
13530
14057
|
const localContent = await readFileSafe(localPath);
|
|
13531
14058
|
if (canonicalContent === null || localContent === null) return false;
|
|
13532
|
-
const parsed = parseAgentYaml(canonicalContent,
|
|
14059
|
+
const parsed = parseAgentYaml(canonicalContent, path38.basename(teamPath));
|
|
13533
14060
|
if (!parsed.ok) return false;
|
|
13534
14061
|
return localContent === renderForTool(parsed.spec, tool).content;
|
|
13535
14062
|
}
|
|
@@ -13551,13 +14078,15 @@ function reverseByTool(tool, filePath, content) {
|
|
|
13551
14078
|
return reverseFromJoycode(filePath, content);
|
|
13552
14079
|
case "qoder":
|
|
13553
14080
|
return reverseFromClaude(filePath, content);
|
|
14081
|
+
case "kiro":
|
|
14082
|
+
return reverseFromKiro(filePath, content);
|
|
13554
14083
|
case "zcode":
|
|
13555
14084
|
return reverseFromClaude(filePath, content);
|
|
13556
14085
|
case "opencode":
|
|
13557
14086
|
return reverseFromOpencode(filePath, content);
|
|
13558
14087
|
}
|
|
13559
14088
|
}
|
|
13560
|
-
var AgentsHandler;
|
|
14089
|
+
var AgentsHandler, LEGACY_MD_TOOLS;
|
|
13561
14090
|
var init_agents = __esm({
|
|
13562
14091
|
"src/resources/agents.ts"() {
|
|
13563
14092
|
"use strict";
|
|
@@ -13566,6 +14095,9 @@ var init_agents = __esm({
|
|
|
13566
14095
|
init_logger();
|
|
13567
14096
|
init_types();
|
|
13568
14097
|
init_builtin_agents();
|
|
14098
|
+
init_resource_namespaces();
|
|
14099
|
+
init_projects();
|
|
14100
|
+
init_path_safety();
|
|
13569
14101
|
init_agent_format();
|
|
13570
14102
|
AgentsHandler = class extends ResourceHandler {
|
|
13571
14103
|
type = "agents";
|
|
@@ -13577,50 +14109,53 @@ var init_agents = __esm({
|
|
|
13577
14109
|
* Built-in CLI agents are excluded from push.
|
|
13578
14110
|
*/
|
|
13579
14111
|
async scanLocalForPush(teamConfig, localConfig) {
|
|
13580
|
-
const teamAgentsDir =
|
|
14112
|
+
const teamAgentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
13581
14113
|
const tombstones = await this.readTombstones(localConfig);
|
|
13582
14114
|
const baseDir = resolveBaseDir(localConfig);
|
|
13583
14115
|
const directItems = [];
|
|
13584
14116
|
const directStems = /* @__PURE__ */ new Set();
|
|
13585
14117
|
if (isSelfMode(localConfig) && localConfig.projectRoot) {
|
|
13586
|
-
const activeAgentsDir =
|
|
14118
|
+
const activeAgentsDir = path38.join(localConfig.projectRoot, ".teamai", "agents");
|
|
13587
14119
|
if (await pathExists(activeAgentsDir)) {
|
|
13588
|
-
for (const
|
|
13589
|
-
const
|
|
13590
|
-
const
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
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
|
+
}
|
|
13608
14143
|
}
|
|
13609
14144
|
}
|
|
13610
14145
|
}
|
|
13611
14146
|
const grouped = /* @__PURE__ */ new Map();
|
|
13612
14147
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13613
14148
|
if (!toolPath.agents) continue;
|
|
13614
|
-
const agentsDir =
|
|
14149
|
+
const agentsDir = path38.join(baseDir, toolPath.agents);
|
|
13615
14150
|
if (!await pathExists(agentsDir)) continue;
|
|
13616
14151
|
const files = await listFiles(agentsDir);
|
|
13617
14152
|
for (const file of files) {
|
|
13618
|
-
const stem =
|
|
14153
|
+
const stem = agentStemFromFilename(file);
|
|
13619
14154
|
if (stem === null) continue;
|
|
13620
14155
|
if (tombstones.has(stem)) continue;
|
|
13621
14156
|
if (BUILTIN_AGENT_NAMES.has(stem)) continue;
|
|
13622
14157
|
if (directStems.has(stem)) continue;
|
|
13623
|
-
const filePath =
|
|
14158
|
+
const filePath = path38.join(agentsDir, file);
|
|
13624
14159
|
let toolGroup = grouped.get(stem);
|
|
13625
14160
|
if (!toolGroup) {
|
|
13626
14161
|
toolGroup = /* @__PURE__ */ new Map();
|
|
@@ -13632,11 +14167,41 @@ var init_agents = __esm({
|
|
|
13632
14167
|
}
|
|
13633
14168
|
}
|
|
13634
14169
|
const items = [...directItems];
|
|
14170
|
+
const resolved = await resolveResourceNamespaces(localConfig);
|
|
14171
|
+
const activeNamespaces = resolved?.activeNamespaces.agents ?? null;
|
|
13635
14172
|
for (const [stem, toolFiles] of grouped) {
|
|
13636
|
-
const
|
|
13637
|
-
const
|
|
13638
|
-
|
|
13639
|
-
|
|
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";
|
|
13640
14205
|
let canonicalSpec;
|
|
13641
14206
|
if (hasTeamYaml) {
|
|
13642
14207
|
const raw = await readFileSafe(teamYamlPath);
|
|
@@ -13646,7 +14211,7 @@ var init_agents = __esm({
|
|
|
13646
14211
|
name: stem,
|
|
13647
14212
|
type: "agents",
|
|
13648
14213
|
sourcePath: teamYamlPath,
|
|
13649
|
-
relativePath:
|
|
14214
|
+
relativePath: `${teamDir}/${stem}.yaml`,
|
|
13650
14215
|
status: "modified",
|
|
13651
14216
|
skipReason: "cannot read or parse canonical agent YAML"
|
|
13652
14217
|
});
|
|
@@ -13724,7 +14289,7 @@ var init_agents = __esm({
|
|
|
13724
14289
|
name: stem,
|
|
13725
14290
|
type: "agents",
|
|
13726
14291
|
sourcePath: bestPath,
|
|
13727
|
-
relativePath:
|
|
14292
|
+
relativePath: `${teamDir}/${stem}.yaml`,
|
|
13728
14293
|
status: status2,
|
|
13729
14294
|
mergedSpec: mergeResult.spec
|
|
13730
14295
|
});
|
|
@@ -13735,7 +14300,7 @@ var init_agents = __esm({
|
|
|
13735
14300
|
name: stem,
|
|
13736
14301
|
type: "agents",
|
|
13737
14302
|
sourcePath: bestPath,
|
|
13738
|
-
relativePath:
|
|
14303
|
+
relativePath: `${teamDir}/${stem}.md`,
|
|
13739
14304
|
status: status2,
|
|
13740
14305
|
skipReason
|
|
13741
14306
|
});
|
|
@@ -13746,32 +14311,33 @@ var init_agents = __esm({
|
|
|
13746
14311
|
* Scan team repo `agents/` for files to pull.
|
|
13747
14312
|
* Recognizes both *.yaml (new) and *.md (legacy).
|
|
13748
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.
|
|
13749
14319
|
*/
|
|
13750
14320
|
async scanTeamForPull(_teamConfig, localConfig) {
|
|
13751
|
-
const agentsDir =
|
|
14321
|
+
const agentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
13752
14322
|
if (!await pathExists(agentsDir)) return [];
|
|
13753
|
-
const files = await listFiles(agentsDir);
|
|
13754
14323
|
const items = [];
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
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;
|
|
13758
14329
|
items.push({
|
|
13759
|
-
name:
|
|
14330
|
+
name: file.replace(/\.(yaml|md)$/, ""),
|
|
13760
14331
|
type: "agents",
|
|
13761
|
-
sourcePath:
|
|
13762
|
-
relativePath:
|
|
13763
|
-
legacy
|
|
13764
|
-
|
|
13765
|
-
} else if (file.endsWith(".md")) {
|
|
13766
|
-
const stem = file.replace(/\.md$/, "");
|
|
13767
|
-
items.push({
|
|
13768
|
-
name: stem,
|
|
13769
|
-
type: "agents",
|
|
13770
|
-
sourcePath: path37.join(agentsDir, file),
|
|
13771
|
-
relativePath: `agents/${file}`,
|
|
13772
|
-
legacy: true
|
|
14332
|
+
sourcePath: path38.join(dir, file),
|
|
14333
|
+
relativePath: `${prefix}/${file}`,
|
|
14334
|
+
legacy,
|
|
14335
|
+
...namespace ? { namespace } : {}
|
|
13773
14336
|
});
|
|
13774
14337
|
}
|
|
14338
|
+
};
|
|
14339
|
+
for (const { dir, namespace } of await listTeamAgentDirs(agentsDir)) {
|
|
14340
|
+
await scanDir(dir, namespace);
|
|
13775
14341
|
}
|
|
13776
14342
|
return items;
|
|
13777
14343
|
}
|
|
@@ -13784,22 +14350,27 @@ var init_agents = __esm({
|
|
|
13784
14350
|
async pushItem(item, _teamConfig, localConfig) {
|
|
13785
14351
|
const agentItem = item;
|
|
13786
14352
|
if (agentItem.skipReason) {
|
|
13787
|
-
log.warn(`[agents]
|
|
13788
|
-
log.warn(" \u5EFA\u8BAE\u4FEE\u6539\u540E\u91CD\u65B0 push \u8BE5 subagent");
|
|
14353
|
+
log.warn(`[agents] Skipped ${item.name}: ${agentItem.skipReason}`);
|
|
13789
14354
|
return;
|
|
13790
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
|
+
);
|
|
13791
14362
|
if (agentItem.mergedSpec) {
|
|
13792
|
-
const dest2 =
|
|
13793
|
-
await ensureDir(
|
|
14363
|
+
const dest2 = path38.join(teamDir, `${item.name}.yaml`);
|
|
14364
|
+
await ensureDir(path38.dirname(dest2));
|
|
13794
14365
|
const yamlContent = serializeAgentYaml(agentItem.mergedSpec);
|
|
13795
14366
|
await writeFile(dest2, yamlContent);
|
|
13796
14367
|
log.debug(`Wrote agent ${item.name} \u2192 team repo (YAML format)`);
|
|
13797
14368
|
return;
|
|
13798
14369
|
}
|
|
13799
14370
|
const ext = item.sourcePath.endsWith(".yaml") ? ".yaml" : ".md";
|
|
13800
|
-
const dest =
|
|
14371
|
+
const dest = path38.join(teamDir, `${item.name}${ext}`);
|
|
13801
14372
|
if (item.sourcePath !== dest) {
|
|
13802
|
-
await ensureDir(
|
|
14373
|
+
await ensureDir(path38.dirname(dest));
|
|
13803
14374
|
await copyFile(item.sourcePath, dest);
|
|
13804
14375
|
}
|
|
13805
14376
|
log.debug(`Copied agent ${item.name} \u2192 team repo (${ext} verbatim)`);
|
|
@@ -13843,11 +14414,12 @@ var init_agents = __esm({
|
|
|
13843
14414
|
continue;
|
|
13844
14415
|
}
|
|
13845
14416
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
13846
|
-
const destDir =
|
|
14417
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
13847
14418
|
try {
|
|
13848
14419
|
await ensureDir(destDir);
|
|
13849
14420
|
const { ext, content: rendered } = renderForTool(spec, tool);
|
|
13850
|
-
|
|
14421
|
+
await removeStaleAgentSiblings2(destDir, item.name, ext);
|
|
14422
|
+
const dest = path38.join(destDir, `${item.name}${ext}`);
|
|
13851
14423
|
await writeFile(dest, rendered);
|
|
13852
14424
|
log.debug(`Rendered agent ${item.name} \u2192 ${tool} (${ext})`);
|
|
13853
14425
|
} catch (e) {
|
|
@@ -13863,19 +14435,17 @@ var init_agents = __esm({
|
|
|
13863
14435
|
async removeItem(name, teamConfig, localConfig) {
|
|
13864
14436
|
const removed = [];
|
|
13865
14437
|
const baseDir = resolveBaseDir(localConfig);
|
|
13866
|
-
const teamAgentsDir =
|
|
13867
|
-
for (const
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
await remove(teamFile);
|
|
13871
|
-
removed.push(teamFile);
|
|
13872
|
-
}
|
|
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);
|
|
13873
14442
|
}
|
|
13874
14443
|
await this.addTombstone(name, localConfig);
|
|
13875
14444
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13876
14445
|
if (!toolPath.agents) continue;
|
|
13877
|
-
|
|
13878
|
-
|
|
14446
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14447
|
+
for (const ext of AGENT_FILE_EXTENSIONS) {
|
|
14448
|
+
const filePath = path38.join(baseDir, toolPath.agents, `${name}${ext}`);
|
|
13879
14449
|
if (await pathExists(filePath)) {
|
|
13880
14450
|
await remove(filePath);
|
|
13881
14451
|
removed.push(filePath);
|
|
@@ -13885,14 +14455,69 @@ var init_agents = __esm({
|
|
|
13885
14455
|
}
|
|
13886
14456
|
return removed;
|
|
13887
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
|
+
}
|
|
13888
14514
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
13889
14515
|
/**
|
|
13890
14516
|
* Legacy pull: copies .md as-is to Claude-compatible tools, including JoyCode.
|
|
13891
14517
|
*/
|
|
13892
14518
|
async pullLegacyMd(item, teamConfig, baseDir, localConfig) {
|
|
13893
|
-
const legacyTools = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
13894
14519
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13895
|
-
if (!
|
|
14520
|
+
if (!LEGACY_MD_TOOLS.has(tool)) continue;
|
|
13896
14521
|
if (!toolPath.agents) {
|
|
13897
14522
|
log.debug(`Skipping legacy agent sync for ${tool}: no agents path configured`);
|
|
13898
14523
|
continue;
|
|
@@ -13902,10 +14527,10 @@ var init_agents = __esm({
|
|
|
13902
14527
|
continue;
|
|
13903
14528
|
}
|
|
13904
14529
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
13905
|
-
const destDir =
|
|
14530
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
13906
14531
|
try {
|
|
13907
14532
|
await ensureDir(destDir);
|
|
13908
|
-
const dest =
|
|
14533
|
+
const dest = path38.join(destDir, `${item.name}.md`);
|
|
13909
14534
|
await copyFile(item.sourcePath, dest);
|
|
13910
14535
|
log.debug(`Synced legacy agent ${item.name} \u2192 ${tool}`);
|
|
13911
14536
|
} catch (e) {
|
|
@@ -13914,6 +14539,7 @@ var init_agents = __esm({
|
|
|
13914
14539
|
}
|
|
13915
14540
|
}
|
|
13916
14541
|
};
|
|
14542
|
+
LEGACY_MD_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
13917
14543
|
}
|
|
13918
14544
|
});
|
|
13919
14545
|
|
|
@@ -13979,11 +14605,11 @@ __export(update_exports, {
|
|
|
13979
14605
|
resolveRegistryForPackage: () => resolveRegistryForPackage,
|
|
13980
14606
|
update: () => update
|
|
13981
14607
|
});
|
|
13982
|
-
import { execFile as
|
|
14608
|
+
import { execFile as execFile2 } from "child_process";
|
|
13983
14609
|
import { promisify as promisify2 } from "util";
|
|
13984
14610
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
13985
14611
|
import fs16 from "fs";
|
|
13986
|
-
import
|
|
14612
|
+
import path39 from "path";
|
|
13987
14613
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13988
14614
|
import fse7 from "fs-extra";
|
|
13989
14615
|
function resolveRegistryForPackage(pkgName) {
|
|
@@ -13993,15 +14619,15 @@ function resolveRegistryForPackage(pkgName) {
|
|
|
13993
14619
|
return PUBLIC_REGISTRY;
|
|
13994
14620
|
}
|
|
13995
14621
|
function resolveNpmCommand() {
|
|
13996
|
-
const nodeDir =
|
|
14622
|
+
const nodeDir = path39.dirname(process.execPath);
|
|
13997
14623
|
const candidates = [
|
|
13998
14624
|
// Bundled runtimes: npm installed flat next to node.exe.
|
|
13999
|
-
|
|
14625
|
+
path39.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
14000
14626
|
// POSIX layout rooted at the node dir itself.
|
|
14001
|
-
|
|
14627
|
+
path39.join(nodeDir, "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
14002
14628
|
// Canonical POSIX install (official tarball, Homebrew, nvm): node lives in
|
|
14003
14629
|
// <prefix>/bin with npm at <prefix>/lib/node_modules — one level up.
|
|
14004
|
-
|
|
14630
|
+
path39.join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
|
|
14005
14631
|
];
|
|
14006
14632
|
for (const c of candidates) {
|
|
14007
14633
|
if (fs16.existsSync(c)) return { cmd: process.execPath, args: [c] };
|
|
@@ -14010,16 +14636,16 @@ function resolveNpmCommand() {
|
|
|
14010
14636
|
return { cmd: "npm", args: [] };
|
|
14011
14637
|
}
|
|
14012
14638
|
function prefixFromEntryPath(entry, posix) {
|
|
14013
|
-
const marker = `${
|
|
14639
|
+
const marker = `${path39.sep}node_modules${path39.sep}`;
|
|
14014
14640
|
const idx = entry.lastIndexOf(marker);
|
|
14015
14641
|
if (idx <= 0) return null;
|
|
14016
14642
|
const root = entry.slice(0, idx);
|
|
14017
14643
|
const pkgDir = getCurrentPackageName();
|
|
14018
|
-
if (posix &&
|
|
14019
|
-
const prefix =
|
|
14020
|
-
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;
|
|
14021
14647
|
}
|
|
14022
|
-
return fs16.existsSync(
|
|
14648
|
+
return fs16.existsSync(path39.join(root, "node_modules", pkgDir)) ? { prefix: root, global: !posix } : null;
|
|
14023
14649
|
}
|
|
14024
14650
|
function resolveInstallPrefix() {
|
|
14025
14651
|
return prefixFromEntryPath(fileURLToPath4(import.meta.url), process.platform !== "win32");
|
|
@@ -14032,7 +14658,7 @@ async function fetchLatestVersion(registry, timeout = VERSION_CHECK_TIMEOUT) {
|
|
|
14032
14658
|
const { stdout } = await execFileAsync2(
|
|
14033
14659
|
npm.cmd,
|
|
14034
14660
|
[...npm.args, "view", pkgName, "version", `--registry=${resolvedRegistry}`],
|
|
14035
|
-
{ timeout, encoding: "utf-8" }
|
|
14661
|
+
{ timeout, encoding: "utf-8", windowsHide: true }
|
|
14036
14662
|
);
|
|
14037
14663
|
const version2 = stdout.trim();
|
|
14038
14664
|
if (!version2) return null;
|
|
@@ -14144,7 +14770,7 @@ async function acquireLock(lockPath) {
|
|
|
14144
14770
|
owner
|
|
14145
14771
|
});
|
|
14146
14772
|
try {
|
|
14147
|
-
await ensureDir(
|
|
14773
|
+
await ensureDir(path39.dirname(resolved));
|
|
14148
14774
|
} catch {
|
|
14149
14775
|
return false;
|
|
14150
14776
|
}
|
|
@@ -14266,19 +14892,19 @@ async function doUpdate() {
|
|
|
14266
14892
|
...target ? [`--prefix=${target.prefix}`] : [],
|
|
14267
14893
|
`--registry=${registry}`
|
|
14268
14894
|
],
|
|
14269
|
-
{ timeout: INSTALL_TIMEOUT }
|
|
14895
|
+
{ timeout: INSTALL_TIMEOUT, windowsHide: true }
|
|
14270
14896
|
);
|
|
14271
14897
|
log.success(`Updated teamai to v${result.latest}`);
|
|
14272
14898
|
const entry = resolveTeamaiEntryScript();
|
|
14273
14899
|
if (entry) {
|
|
14274
14900
|
try {
|
|
14275
14901
|
const installed = JSON.parse(fs16.readFileSync(
|
|
14276
|
-
|
|
14902
|
+
path39.join(path39.dirname(path39.dirname(entry)), "package.json"),
|
|
14277
14903
|
"utf-8"
|
|
14278
14904
|
));
|
|
14279
14905
|
if (installed.version !== result.latest) {
|
|
14280
14906
|
log.warn(
|
|
14281
|
-
`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.`
|
|
14282
14908
|
);
|
|
14283
14909
|
}
|
|
14284
14910
|
} catch {
|
|
@@ -14287,7 +14913,8 @@ async function doUpdate() {
|
|
|
14287
14913
|
try {
|
|
14288
14914
|
const refresh = entry ? { cmd: process.execPath, args: [entry, "hooks", "inject", "--silent"] } : { cmd: "teamai", args: ["hooks", "inject", "--silent"] };
|
|
14289
14915
|
await execFileAsync2(refresh.cmd, refresh.args, {
|
|
14290
|
-
timeout: 15e3
|
|
14916
|
+
timeout: 15e3,
|
|
14917
|
+
windowsHide: true
|
|
14291
14918
|
});
|
|
14292
14919
|
log.success("Refreshed hooks with new version");
|
|
14293
14920
|
} catch (e) {
|
|
@@ -14331,7 +14958,7 @@ var init_update = __esm({
|
|
|
14331
14958
|
init_types();
|
|
14332
14959
|
init_prompt();
|
|
14333
14960
|
init_package_info();
|
|
14334
|
-
execFileAsync2 = promisify2(
|
|
14961
|
+
execFileAsync2 = promisify2(execFile2);
|
|
14335
14962
|
PUBLIC_REGISTRY = "https://registry.npmjs.org";
|
|
14336
14963
|
TNPM_REGISTRY = "http://r.tnpm.oa.com";
|
|
14337
14964
|
VERSION_CHECK_TIMEOUT = 5e3;
|
|
@@ -14346,9 +14973,9 @@ var marketplace_exports = {};
|
|
|
14346
14973
|
__export(marketplace_exports, {
|
|
14347
14974
|
refreshMarketplace: () => refreshMarketplace
|
|
14348
14975
|
});
|
|
14349
|
-
import
|
|
14976
|
+
import path40 from "path";
|
|
14350
14977
|
async function extractSkillDescription2(skillDir) {
|
|
14351
|
-
const skillMdPath =
|
|
14978
|
+
const skillMdPath = path40.join(skillDir, SKILL_MD2);
|
|
14352
14979
|
const content = await readFileSafe(skillMdPath);
|
|
14353
14980
|
if (!content) return "";
|
|
14354
14981
|
const { data } = parseFrontmatter(content);
|
|
@@ -14357,7 +14984,7 @@ async function extractSkillDescription2(skillDir) {
|
|
|
14357
14984
|
return String(desc).replace(/\s+/g, " ").trim();
|
|
14358
14985
|
}
|
|
14359
14986
|
async function refreshMarketplace(repoPath) {
|
|
14360
|
-
const marketplacePath =
|
|
14987
|
+
const marketplacePath = path40.join(repoPath, MARKETPLACE_PATH);
|
|
14361
14988
|
if (!await pathExists(marketplacePath)) {
|
|
14362
14989
|
return false;
|
|
14363
14990
|
}
|
|
@@ -14371,11 +14998,11 @@ async function refreshMarketplace(repoPath) {
|
|
|
14371
14998
|
return false;
|
|
14372
14999
|
}
|
|
14373
15000
|
if (!Array.isArray(marketplace.plugins)) return false;
|
|
14374
|
-
const skillsDir =
|
|
15001
|
+
const skillsDir = path40.join(repoPath, "skills");
|
|
14375
15002
|
const currentSkills = /* @__PURE__ */ new Set();
|
|
14376
15003
|
const dirs = await listDirs(skillsDir);
|
|
14377
15004
|
for (const dir of dirs) {
|
|
14378
|
-
const hasSkillMd = await pathExists(
|
|
15005
|
+
const hasSkillMd = await pathExists(path40.join(skillsDir, dir, SKILL_MD2));
|
|
14379
15006
|
if (hasSkillMd) {
|
|
14380
15007
|
currentSkills.add(dir);
|
|
14381
15008
|
}
|
|
@@ -14394,7 +15021,7 @@ async function refreshMarketplace(repoPath) {
|
|
|
14394
15021
|
for (const skillName of currentSkills) {
|
|
14395
15022
|
const existing = existingPlugins.get(skillName);
|
|
14396
15023
|
if (existing) {
|
|
14397
|
-
const desc = await extractSkillDescription2(
|
|
15024
|
+
const desc = await extractSkillDescription2(path40.join(skillsDir, skillName));
|
|
14398
15025
|
if (desc && desc !== existing.description) {
|
|
14399
15026
|
existing.description = desc;
|
|
14400
15027
|
changed = true;
|
|
@@ -14402,7 +15029,7 @@ async function refreshMarketplace(repoPath) {
|
|
|
14402
15029
|
updatedPlugins.push(existing);
|
|
14403
15030
|
existingPlugins.delete(skillName);
|
|
14404
15031
|
} else {
|
|
14405
|
-
const desc = await extractSkillDescription2(
|
|
15032
|
+
const desc = await extractSkillDescription2(path40.join(skillsDir, skillName));
|
|
14406
15033
|
updatedPlugins.push({
|
|
14407
15034
|
name: skillName,
|
|
14408
15035
|
source: `./skills/${skillName}`,
|
|
@@ -14439,177 +15066,6 @@ var init_marketplace = __esm({
|
|
|
14439
15066
|
}
|
|
14440
15067
|
});
|
|
14441
15068
|
|
|
14442
|
-
// src/projects.ts
|
|
14443
|
-
var projects_exports = {};
|
|
14444
|
-
__export(projects_exports, {
|
|
14445
|
-
describeProjects: () => describeProjects,
|
|
14446
|
-
findProject: () => findProject,
|
|
14447
|
-
isSafeNamespaceSegment: () => isSafeNamespaceSegment,
|
|
14448
|
-
listProjectIds: () => listProjectIds,
|
|
14449
|
-
loadProjectsManifest: () => loadProjectsManifest,
|
|
14450
|
-
mergeNamespaces: () => mergeNamespaces,
|
|
14451
|
-
resolveActiveLearningsNamespaces: () => resolveActiveLearningsNamespaces,
|
|
14452
|
-
resolveProjectResourceNamespaces: () => resolveProjectResourceNamespaces,
|
|
14453
|
-
saveProjectsManifest: () => saveProjectsManifest
|
|
14454
|
-
});
|
|
14455
|
-
import path40 from "path";
|
|
14456
|
-
import YAML8 from "yaml";
|
|
14457
|
-
import { z as z6 } from "zod";
|
|
14458
|
-
function isSafeNamespaceSegment(seg) {
|
|
14459
|
-
return SAFE_ID.test(seg) && seg !== "." && seg !== "..";
|
|
14460
|
-
}
|
|
14461
|
-
function validateManifestShape2(raw) {
|
|
14462
|
-
if (!raw || typeof raw !== "object") {
|
|
14463
|
-
throw new Error("Invalid projects manifest: expected an object");
|
|
14464
|
-
}
|
|
14465
|
-
const candidate = raw;
|
|
14466
|
-
const projects = candidate.projects;
|
|
14467
|
-
if (projects !== void 0 && !Array.isArray(projects)) {
|
|
14468
|
-
throw new Error("Invalid projects manifest: projects must be an array");
|
|
14469
|
-
}
|
|
14470
|
-
for (const project of projects ?? []) {
|
|
14471
|
-
if (!project || typeof project !== "object") {
|
|
14472
|
-
throw new Error("Invalid projects manifest: every project must be an object");
|
|
14473
|
-
}
|
|
14474
|
-
const resources = project.resources;
|
|
14475
|
-
if (resources !== void 0 && (typeof resources !== "object" || Array.isArray(resources))) {
|
|
14476
|
-
throw new Error(`Invalid projects manifest: project ${project.id ?? "<unknown>"} has invalid resources`);
|
|
14477
|
-
}
|
|
14478
|
-
if (resources) {
|
|
14479
|
-
const ALLOWED_RESOURCE_KEYS = new Set(PROJECT_RESOURCE_TYPES);
|
|
14480
|
-
for (const key of Object.keys(resources)) {
|
|
14481
|
-
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
14482
|
-
throw new Error(`Invalid projects manifest: unknown resource type "${key}"`);
|
|
14483
|
-
}
|
|
14484
|
-
}
|
|
14485
|
-
}
|
|
14486
|
-
}
|
|
14487
|
-
const manifest = ProjectsManifestSchema.parse(raw);
|
|
14488
|
-
const ids = /* @__PURE__ */ new Set();
|
|
14489
|
-
for (const project of manifest.projects) {
|
|
14490
|
-
if (ids.has(project.id)) {
|
|
14491
|
-
throw new Error(`Invalid projects manifest: duplicate project id "${project.id}"`);
|
|
14492
|
-
}
|
|
14493
|
-
ids.add(project.id);
|
|
14494
|
-
}
|
|
14495
|
-
return manifest;
|
|
14496
|
-
}
|
|
14497
|
-
async function loadProjectsManifest(repoPath) {
|
|
14498
|
-
const manifestPath = path40.join(repoPath, "manifest", "projects.yaml");
|
|
14499
|
-
const content = await readFileSafe(manifestPath);
|
|
14500
|
-
if (!content) {
|
|
14501
|
-
return null;
|
|
14502
|
-
}
|
|
14503
|
-
let raw;
|
|
14504
|
-
try {
|
|
14505
|
-
raw = YAML8.parse(content);
|
|
14506
|
-
} catch (error) {
|
|
14507
|
-
throw new Error(`Invalid projects manifest YAML: ${error.message}`);
|
|
14508
|
-
}
|
|
14509
|
-
return validateManifestShape2(raw);
|
|
14510
|
-
}
|
|
14511
|
-
async function saveProjectsManifest(repoPath, manifest) {
|
|
14512
|
-
validateManifestShape2(manifest);
|
|
14513
|
-
const manifestDir = path40.join(repoPath, "manifest");
|
|
14514
|
-
const manifestPath = path40.join(manifestDir, "projects.yaml");
|
|
14515
|
-
await ensureDir(manifestDir);
|
|
14516
|
-
await writeFile(manifestPath, YAML8.stringify(manifest));
|
|
14517
|
-
}
|
|
14518
|
-
function findProject(manifest, projectId) {
|
|
14519
|
-
return manifest.projects.find((candidate) => candidate.id === projectId);
|
|
14520
|
-
}
|
|
14521
|
-
function listProjectIds(manifest) {
|
|
14522
|
-
return manifest.projects.map((project) => project.id);
|
|
14523
|
-
}
|
|
14524
|
-
function describeProjects(projects) {
|
|
14525
|
-
return projects.map((project) => {
|
|
14526
|
-
const label = project.name || project.id;
|
|
14527
|
-
return project.description ? `${label}: ${project.description}` : label;
|
|
14528
|
-
});
|
|
14529
|
-
}
|
|
14530
|
-
function getProjectOrThrow(manifest, projectId) {
|
|
14531
|
-
const project = findProject(manifest, projectId);
|
|
14532
|
-
if (!project) {
|
|
14533
|
-
throw new Error(`Unknown project "${projectId}". Valid projects: ${listProjectIds(manifest).join(", ")}`);
|
|
14534
|
-
}
|
|
14535
|
-
return project;
|
|
14536
|
-
}
|
|
14537
|
-
function resolveProjectResourceNamespaces(input) {
|
|
14538
|
-
const resolved = input.activeProjects.map((id) => getProjectOrThrow(input.manifest, id));
|
|
14539
|
-
const namespaces = {
|
|
14540
|
-
knowledge: [],
|
|
14541
|
-
skills: [],
|
|
14542
|
-
learnings: []
|
|
14543
|
-
};
|
|
14544
|
-
for (const type of PROJECT_RESOURCE_TYPES) {
|
|
14545
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14546
|
-
for (const project of resolved) {
|
|
14547
|
-
for (const namespace of project.resources[type]) {
|
|
14548
|
-
if (seen.has(namespace)) continue;
|
|
14549
|
-
seen.add(namespace);
|
|
14550
|
-
namespaces[type].push(namespace);
|
|
14551
|
-
}
|
|
14552
|
-
}
|
|
14553
|
-
}
|
|
14554
|
-
return namespaces;
|
|
14555
|
-
}
|
|
14556
|
-
async function resolveActiveLearningsNamespaces(repoPath, activeProjects) {
|
|
14557
|
-
if (activeProjects.length === 0) return [];
|
|
14558
|
-
const manifest = await loadProjectsManifest(repoPath);
|
|
14559
|
-
if (!manifest) return [];
|
|
14560
|
-
try {
|
|
14561
|
-
return resolveProjectResourceNamespaces({ manifest, activeProjects }).learnings;
|
|
14562
|
-
} catch {
|
|
14563
|
-
return [];
|
|
14564
|
-
}
|
|
14565
|
-
}
|
|
14566
|
-
function mergeNamespaces(roleNamespaces, projectNamespaces) {
|
|
14567
|
-
const dedupe = (a, b) => {
|
|
14568
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14569
|
-
const out = [];
|
|
14570
|
-
for (const ns of [...a, ...b]) {
|
|
14571
|
-
if (seen.has(ns)) continue;
|
|
14572
|
-
seen.add(ns);
|
|
14573
|
-
out.push(ns);
|
|
14574
|
-
}
|
|
14575
|
-
return out;
|
|
14576
|
-
};
|
|
14577
|
-
return {
|
|
14578
|
-
knowledge: dedupe(roleNamespaces.knowledge, projectNamespaces.knowledge),
|
|
14579
|
-
skills: dedupe(roleNamespaces.skills, projectNamespaces.skills),
|
|
14580
|
-
// Roles never contribute learnings; this is effectively the project set.
|
|
14581
|
-
learnings: dedupe(roleNamespaces.learnings, projectNamespaces.learnings)
|
|
14582
|
-
};
|
|
14583
|
-
}
|
|
14584
|
-
var PROJECT_RESOURCE_TYPES, ProjectResourceNamespacesSchema, SAFE_ID, ProjectSchema, ProjectsManifestSchema;
|
|
14585
|
-
var init_projects = __esm({
|
|
14586
|
-
"src/projects.ts"() {
|
|
14587
|
-
"use strict";
|
|
14588
|
-
init_fs();
|
|
14589
|
-
PROJECT_RESOURCE_TYPES = ["knowledge", "skills", "learnings"];
|
|
14590
|
-
ProjectResourceNamespacesSchema = z6.object({
|
|
14591
|
-
knowledge: z6.array(z6.string().min(1)).default([]),
|
|
14592
|
-
skills: z6.array(z6.string().min(1)).default([]),
|
|
14593
|
-
learnings: z6.array(z6.string().min(1)).default([])
|
|
14594
|
-
});
|
|
14595
|
-
SAFE_ID = /^[A-Za-z0-9._-]+$/;
|
|
14596
|
-
ProjectSchema = z6.object({
|
|
14597
|
-
id: z6.string().min(1).refine((v) => isSafeNamespaceSegment(v), {
|
|
14598
|
-
message: "project id must be a single path segment (letters, digits, '.', '_', '-'; no '/', '\\\\', or '..')"
|
|
14599
|
-
}),
|
|
14600
|
-
name: z6.string().default(""),
|
|
14601
|
-
description: z6.string().default(""),
|
|
14602
|
-
resources: ProjectResourceNamespacesSchema
|
|
14603
|
-
});
|
|
14604
|
-
ProjectsManifestSchema = z6.object({
|
|
14605
|
-
version: z6.number(),
|
|
14606
|
-
// Unlike roles (`.min(1)`), a repo may define zero projects — a team without
|
|
14607
|
-
// project partitioning simply has no projects.yaml, and an empty list is valid.
|
|
14608
|
-
projects: z6.array(ProjectSchema).default([])
|
|
14609
|
-
});
|
|
14610
|
-
}
|
|
14611
|
-
});
|
|
14612
|
-
|
|
14613
15069
|
// src/pkg/types.ts
|
|
14614
15070
|
import { z as z7 } from "zod";
|
|
14615
15071
|
var PackageScopeSchema, NpmSpecSchema, ClaudeMarketplaceSchema, ClaudePluginSpecSchema, ClaudeEcosystemSchema, PackageSetSchema, PackageManifestSchema, NpmLockEntrySchema, ClaudeMarketplaceLockSchema, ClaudePluginLockSchema, PackageLockSchema;
|
|
@@ -14861,6 +15317,7 @@ __export(reports_branch_exports, {
|
|
|
14861
15317
|
ensureReportsDir: () => ensureReportsDir,
|
|
14862
15318
|
ensureReportsWorktree: () => ensureReportsWorktree,
|
|
14863
15319
|
refreshReportsWorktree: () => refreshReportsWorktree,
|
|
15320
|
+
updateReports: () => updateReports,
|
|
14864
15321
|
withKnowledgeWorktree: () => withKnowledgeWorktree
|
|
14865
15322
|
});
|
|
14866
15323
|
import path42 from "path";
|
|
@@ -14868,8 +15325,17 @@ import fse8 from "fs-extra";
|
|
|
14868
15325
|
function businessRoot(localConfig) {
|
|
14869
15326
|
return localConfig.repo.businessRepoRoot ?? path42.dirname(localConfig.repo.localPath);
|
|
14870
15327
|
}
|
|
15328
|
+
function reportsGitRoot(localConfig) {
|
|
15329
|
+
if (isSelfMode(localConfig)) {
|
|
15330
|
+
return businessRoot(localConfig);
|
|
15331
|
+
}
|
|
15332
|
+
return localConfig.repo.localPath;
|
|
15333
|
+
}
|
|
14871
15334
|
function reportsWorktreePath(localConfig) {
|
|
14872
|
-
return
|
|
15335
|
+
return getReportsDir(localConfig);
|
|
15336
|
+
}
|
|
15337
|
+
function reportsLockPath(localConfig) {
|
|
15338
|
+
return path42.join(path42.dirname(getReportsDir(localConfig)), REPORTS_LOCK_FILENAME);
|
|
14873
15339
|
}
|
|
14874
15340
|
async function remoteBranchExists2(repoRoot) {
|
|
14875
15341
|
const git = createGit2(repoRoot);
|
|
@@ -14881,10 +15347,22 @@ async function remoteBranchExists2(repoRoot) {
|
|
|
14881
15347
|
}
|
|
14882
15348
|
}
|
|
14883
15349
|
async function ensureReportsWorktree(localConfig, options = {}) {
|
|
15350
|
+
if (!usesReportsBranch(localConfig)) {
|
|
15351
|
+
return getReportsDir(localConfig);
|
|
15352
|
+
}
|
|
14884
15353
|
const wt = reportsWorktreePath(localConfig);
|
|
14885
|
-
const repoRoot =
|
|
15354
|
+
const repoRoot = reportsGitRoot(localConfig);
|
|
15355
|
+
if (!isSelfMode(localConfig) && !await isDedicatedRepoRoot(repoRoot)) {
|
|
15356
|
+
throw new Error(
|
|
15357
|
+
`Refusing to create a reports worktree: ${repoRoot} is not a dedicated team-repo clone root`
|
|
15358
|
+
);
|
|
15359
|
+
}
|
|
14886
15360
|
if (await isGitRepo(wt)) {
|
|
14887
|
-
|
|
15361
|
+
try {
|
|
15362
|
+
await createGit2(wt).revparse(["--is-inside-work-tree"]);
|
|
15363
|
+
return wt;
|
|
15364
|
+
} catch {
|
|
15365
|
+
}
|
|
14888
15366
|
}
|
|
14889
15367
|
if (await pathExists(wt)) {
|
|
14890
15368
|
await fse8.remove(wt);
|
|
@@ -14907,14 +15385,19 @@ async function ensureReportsWorktree(localConfig, options = {}) {
|
|
|
14907
15385
|
await git.raw(["worktree", "add", wt, "--track", "-b", REPORTS_BRANCH, `origin/${REPORTS_BRANCH}`]);
|
|
14908
15386
|
}
|
|
14909
15387
|
} else {
|
|
14910
|
-
await
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
15388
|
+
const branches = await git.branchLocal();
|
|
15389
|
+
if (branches.all.includes(REPORTS_BRANCH)) {
|
|
15390
|
+
await git.raw(["worktree", "add", wt, REPORTS_BRANCH]);
|
|
15391
|
+
} else {
|
|
15392
|
+
await createOrphanWorktree(repoRoot, wt);
|
|
15393
|
+
await writeWorktreeGitignore(wt);
|
|
15394
|
+
const wtGit = createGit2(wt);
|
|
15395
|
+
await wtGit.add([".gitignore"]);
|
|
15396
|
+
await commitSkippingHooks(wtGit, "[teamai] Initialize reports branch");
|
|
15397
|
+
}
|
|
14915
15398
|
if (options.pushIfCreated !== false) {
|
|
14916
15399
|
try {
|
|
14917
|
-
await
|
|
15400
|
+
await createGit2(wt).push(["-u", "origin", REPORTS_BRANCH]);
|
|
14918
15401
|
} catch (e) {
|
|
14919
15402
|
log.debug(`[reports] initial push skipped: ${e.message}`);
|
|
14920
15403
|
}
|
|
@@ -14958,45 +15441,48 @@ async function writeWorktreeGitignore(wt) {
|
|
|
14958
15441
|
].join("\n");
|
|
14959
15442
|
await writeFile(path42.join(wt, ".gitignore"), content);
|
|
14960
15443
|
}
|
|
14961
|
-
async function
|
|
14962
|
-
const
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
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");
|
|
14966
15450
|
return false;
|
|
14967
15451
|
}
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {
|
|
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
|
+
}
|
|
14979
15462
|
try {
|
|
14980
|
-
await git.
|
|
14981
|
-
|
|
14982
|
-
} catch (
|
|
14983
|
-
|
|
14984
|
-
log.debug(`[reports] push failed after ${attempt} attempts: ${pushErr.message}`);
|
|
14985
|
-
return false;
|
|
14986
|
-
}
|
|
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}`);
|
|
14987
15467
|
try {
|
|
14988
|
-
await git.
|
|
14989
|
-
|
|
14990
|
-
} catch (rebaseErr) {
|
|
14991
|
-
log.debug(`[reports] rebase failed, retrying: ${rebaseErr.message}`);
|
|
14992
|
-
try {
|
|
14993
|
-
await git.rebase(["--abort"]);
|
|
14994
|
-
} catch {
|
|
14995
|
-
}
|
|
15468
|
+
await git.rebase(["--abort"]);
|
|
15469
|
+
} catch {
|
|
14996
15470
|
}
|
|
14997
15471
|
}
|
|
14998
15472
|
}
|
|
15473
|
+
}
|
|
15474
|
+
return false;
|
|
15475
|
+
}
|
|
15476
|
+
async function commitAndPushReports(localConfig, message, files, options = {}) {
|
|
15477
|
+
const lockPath = reportsLockPath(localConfig);
|
|
15478
|
+
const locked = await acquireLock(lockPath);
|
|
15479
|
+
if (!locked) {
|
|
15480
|
+
log.debug("[reports] another reports write is in progress; skipping");
|
|
14999
15481
|
return false;
|
|
15482
|
+
}
|
|
15483
|
+
try {
|
|
15484
|
+
const wt = await ensureReportsWorktree(localConfig);
|
|
15485
|
+
return await commitAndPushReportsAt(wt, message, files, options);
|
|
15000
15486
|
} catch (e) {
|
|
15001
15487
|
log.debug(`[reports] commitAndPushReports failed (non-blocking): ${e.message}`);
|
|
15002
15488
|
return false;
|
|
@@ -15004,18 +15490,167 @@ async function commitAndPushReports(localConfig, message, files) {
|
|
|
15004
15490
|
await releaseLock(lockPath);
|
|
15005
15491
|
}
|
|
15006
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}`);
|
|
15508
|
+
}
|
|
15509
|
+
const change = await write(wt);
|
|
15510
|
+
if (!change || change.files.length === 0) {
|
|
15511
|
+
return false;
|
|
15512
|
+
}
|
|
15513
|
+
return await commitAndPushReportsAt(wt, change.message, change.files, options);
|
|
15514
|
+
} catch (e) {
|
|
15515
|
+
log.debug(`[reports] updateReports failed (non-blocking): ${e.message}`);
|
|
15516
|
+
return false;
|
|
15517
|
+
} finally {
|
|
15518
|
+
await releaseLock(lockPath);
|
|
15519
|
+
}
|
|
15520
|
+
}
|
|
15521
|
+
async function gitPathExists(git, gitPath) {
|
|
15522
|
+
try {
|
|
15523
|
+
const resolved = (await git.raw(["rev-parse", "--git-path", gitPath])).trim();
|
|
15524
|
+
return resolved.length > 0 && await pathExists(resolved);
|
|
15525
|
+
} catch {
|
|
15526
|
+
return false;
|
|
15527
|
+
}
|
|
15528
|
+
}
|
|
15529
|
+
async function rebaseInProgress(git) {
|
|
15530
|
+
return await gitPathExists(git, "rebase-merge") || await gitPathExists(git, "rebase-apply");
|
|
15531
|
+
}
|
|
15532
|
+
async function restoreConflictedFiles(git) {
|
|
15533
|
+
let conflicted = [];
|
|
15534
|
+
try {
|
|
15535
|
+
conflicted = (await git.status()).conflicted ?? [];
|
|
15536
|
+
} catch {
|
|
15537
|
+
return;
|
|
15538
|
+
}
|
|
15539
|
+
if (conflicted.length === 0) {
|
|
15540
|
+
return;
|
|
15541
|
+
}
|
|
15542
|
+
if (await rebaseInProgress(git)) {
|
|
15543
|
+
return;
|
|
15544
|
+
}
|
|
15545
|
+
try {
|
|
15546
|
+
await git.raw(["checkout", "--theirs", "--", ...conflicted]);
|
|
15547
|
+
await git.raw(["add", "--", ...conflicted]);
|
|
15548
|
+
await git.raw(["reset", "HEAD", "--", ...conflicted]);
|
|
15549
|
+
log.debug("[reports] restored uncommitted report files after a stash-apply conflict; using the local copy");
|
|
15550
|
+
} catch (e) {
|
|
15551
|
+
log.debug(`[reports] could not restore stash-apply conflicts: ${e.message}`);
|
|
15552
|
+
try {
|
|
15553
|
+
await git.raw(["reset", "--hard", "HEAD"]);
|
|
15554
|
+
} catch {
|
|
15555
|
+
}
|
|
15556
|
+
}
|
|
15557
|
+
}
|
|
15558
|
+
async function snapshotDirtyTree(git) {
|
|
15559
|
+
try {
|
|
15560
|
+
const sha = (await git.raw(["stash", "create"])).trim();
|
|
15561
|
+
return sha.length > 0 ? sha : null;
|
|
15562
|
+
} catch {
|
|
15563
|
+
return null;
|
|
15564
|
+
}
|
|
15565
|
+
}
|
|
15566
|
+
async function applyDirtySnapshot(git, sha) {
|
|
15567
|
+
try {
|
|
15568
|
+
await git.raw(["stash", "apply", sha]);
|
|
15569
|
+
} catch {
|
|
15570
|
+
}
|
|
15571
|
+
await restoreConflictedFiles(git);
|
|
15572
|
+
}
|
|
15573
|
+
async function syncReportsWorktree(wt) {
|
|
15574
|
+
const git = createGit2(wt);
|
|
15575
|
+
const upstream = `origin/${REPORTS_BRANCH}`;
|
|
15576
|
+
try {
|
|
15577
|
+
await git.fetch(["origin", REPORTS_BRANCH]);
|
|
15578
|
+
} catch (e) {
|
|
15579
|
+
log.debug(`[reports] fetch failed, using the local copy: ${e.message}`);
|
|
15580
|
+
return;
|
|
15581
|
+
}
|
|
15582
|
+
await restoreConflictedFiles(git);
|
|
15583
|
+
const dirty = !(await git.status()).isClean();
|
|
15584
|
+
const ahead = Number.parseInt((await git.raw(["rev-list", "--count", `${upstream}..HEAD`])).trim(), 10);
|
|
15585
|
+
let carried = null;
|
|
15586
|
+
if (dirty && ahead > 0) {
|
|
15587
|
+
carried = await snapshotDirtyTree(git);
|
|
15588
|
+
if (!carried) {
|
|
15589
|
+
log.debug("[reports] uncommitted report files block the refresh; using the local copy");
|
|
15590
|
+
return;
|
|
15591
|
+
}
|
|
15592
|
+
try {
|
|
15593
|
+
await git.raw(["reset", "--hard", "HEAD"]);
|
|
15594
|
+
} catch (e) {
|
|
15595
|
+
log.debug(`[reports] could not clear the worktree for rebase; using the local copy: ${e.message}`);
|
|
15596
|
+
await applyDirtySnapshot(git, carried);
|
|
15597
|
+
return;
|
|
15598
|
+
}
|
|
15599
|
+
}
|
|
15600
|
+
try {
|
|
15601
|
+
if (ahead > 0) {
|
|
15602
|
+
await git.rebase([upstream]);
|
|
15603
|
+
} else {
|
|
15604
|
+
await git.raw(["merge", "--ff-only", upstream]);
|
|
15605
|
+
}
|
|
15606
|
+
} catch (e) {
|
|
15607
|
+
if (ahead > 0) {
|
|
15608
|
+
try {
|
|
15609
|
+
await git.rebase(["--abort"]);
|
|
15610
|
+
} catch {
|
|
15611
|
+
}
|
|
15612
|
+
}
|
|
15613
|
+
if (carried) {
|
|
15614
|
+
await applyDirtySnapshot(git, carried);
|
|
15615
|
+
log.debug(`[reports] uncommitted report files block the refresh; using the local copy: ${e.message}`);
|
|
15616
|
+
return;
|
|
15617
|
+
}
|
|
15618
|
+
if (dirty) {
|
|
15619
|
+
log.debug(`[reports] uncommitted report files block the refresh; using the local copy: ${e.message}`);
|
|
15620
|
+
return;
|
|
15621
|
+
}
|
|
15622
|
+
log.debug(`[reports] dropping ${ahead} unpushed report commit(s) that conflict with ${upstream}: ${e.message}`);
|
|
15623
|
+
await git.raw(["reset", "--hard", upstream]);
|
|
15624
|
+
return;
|
|
15625
|
+
}
|
|
15626
|
+
if (carried) {
|
|
15627
|
+
await applyDirtySnapshot(git, carried);
|
|
15628
|
+
}
|
|
15629
|
+
}
|
|
15007
15630
|
async function refreshReportsWorktree(localConfig, options = {}) {
|
|
15631
|
+
if (!usesReportsBranch(localConfig)) {
|
|
15632
|
+
return;
|
|
15633
|
+
}
|
|
15634
|
+
const lockPath = reportsLockPath(localConfig);
|
|
15635
|
+
let locked = false;
|
|
15008
15636
|
try {
|
|
15637
|
+
locked = await acquireLock(lockPath);
|
|
15009
15638
|
const wt = await ensureReportsWorktree(localConfig, options);
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15639
|
+
if (!locked) {
|
|
15640
|
+
log.debug("[reports] a reports write is in progress; reading the local copy");
|
|
15641
|
+
return;
|
|
15642
|
+
}
|
|
15643
|
+
await syncReportsWorktree(wt);
|
|
15013
15644
|
} catch (e) {
|
|
15014
15645
|
log.debug(`[reports] refresh skipped: ${e.message}`);
|
|
15646
|
+
} finally {
|
|
15647
|
+
if (locked) {
|
|
15648
|
+
await releaseLock(lockPath);
|
|
15649
|
+
}
|
|
15015
15650
|
}
|
|
15016
15651
|
}
|
|
15017
15652
|
async function ensureReportsDir(localConfig) {
|
|
15018
|
-
if (localConfig
|
|
15653
|
+
if (usesReportsBranch(localConfig)) {
|
|
15019
15654
|
return ensureReportsWorktree(localConfig);
|
|
15020
15655
|
}
|
|
15021
15656
|
return getReportsDir(localConfig);
|
|
@@ -15139,10 +15774,11 @@ async function listMembers(options) {
|
|
|
15139
15774
|
const projectConfig = await detectProjectConfig();
|
|
15140
15775
|
const localConfig = projectConfig ?? (await requireInit()).localConfig;
|
|
15141
15776
|
let repoPath;
|
|
15142
|
-
|
|
15777
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
15778
|
+
if (usesReportsBranch2(localConfig)) {
|
|
15143
15779
|
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
15144
|
-
await refreshReportsWorktree2(localConfig);
|
|
15145
|
-
repoPath = await ensureReportsWorktree2(localConfig);
|
|
15780
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
15781
|
+
repoPath = await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
15146
15782
|
} else {
|
|
15147
15783
|
repoPath = localConfig.repo.localPath;
|
|
15148
15784
|
await pullRepo(repoPath);
|
|
@@ -15346,6 +15982,7 @@ var init_known_agents = __esm({
|
|
|
15346
15982
|
// src/init.ts
|
|
15347
15983
|
var init_exports = {};
|
|
15348
15984
|
__export(init_exports, {
|
|
15985
|
+
ALL_PROJECTS_SELECTOR: () => ALL_PROJECTS_SELECTOR,
|
|
15349
15986
|
buildSelfModeGitignore: () => buildSelfModeGitignore,
|
|
15350
15987
|
init: () => init,
|
|
15351
15988
|
initHttp: () => initHttp,
|
|
@@ -15353,6 +15990,7 @@ __export(init_exports, {
|
|
|
15353
15990
|
migrateSelfModeGitignore: () => migrateSelfModeGitignore,
|
|
15354
15991
|
migrateSelfModeGitignoreContent: () => migrateSelfModeGitignoreContent,
|
|
15355
15992
|
promptForSelfModeAgents: () => promptForSelfModeAgents,
|
|
15993
|
+
resolveActiveProjects: () => resolveActiveProjects,
|
|
15356
15994
|
resolveInheritUserScope: () => resolveInheritUserScope,
|
|
15357
15995
|
resolveInitRepo: () => resolveInitRepo,
|
|
15358
15996
|
resolveInitScope: () => resolveInitScope,
|
|
@@ -15423,6 +16061,10 @@ async function promptForRoleProfile(repoPath, roleFlag) {
|
|
|
15423
16061
|
resourceProfileVersion: manifest.version
|
|
15424
16062
|
};
|
|
15425
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
|
+
}
|
|
15426
16068
|
async function resolveActiveProjects(repoPath, projectFlag) {
|
|
15427
16069
|
const requested = (projectFlag ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
15428
16070
|
if (requested.length === 0) {
|
|
@@ -15434,7 +16076,27 @@ async function resolveActiveProjects(repoPath, projectFlag) {
|
|
|
15434
16076
|
`--project given but no projects manifest (manifest/projects.yaml) exists in the team repo.`
|
|
15435
16077
|
);
|
|
15436
16078
|
}
|
|
15437
|
-
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);
|
|
15438
16100
|
for (const id of requested) {
|
|
15439
16101
|
if (!validIds.has(id)) {
|
|
15440
16102
|
throw new Error(
|
|
@@ -15442,9 +16104,7 @@ async function resolveActiveProjects(repoPath, projectFlag) {
|
|
|
15442
16104
|
);
|
|
15443
16105
|
}
|
|
15444
16106
|
}
|
|
15445
|
-
|
|
15446
|
-
const projects = requested.filter((id) => seen.has(id) ? false : (seen.add(id), true));
|
|
15447
|
-
return { projects };
|
|
16107
|
+
return { projects: dedupeIds(requested) };
|
|
15448
16108
|
}
|
|
15449
16109
|
function resolveInitScope(rawScope, cwd, homeDir) {
|
|
15450
16110
|
const cwdResolved = resolveRealPath(cwd);
|
|
@@ -15834,12 +16494,12 @@ async function initSelfRepo(options) {
|
|
|
15834
16494
|
return;
|
|
15835
16495
|
}
|
|
15836
16496
|
const provider = getProvider(providerName);
|
|
15837
|
-
log.debug(`Detected provider: ${providerName} (from ${remoteUrl})`);
|
|
16497
|
+
log.debug(`Detected provider: ${providerName} (from ${redactGitCredentials(remoteUrl)})`);
|
|
15838
16498
|
let repoInfo;
|
|
15839
16499
|
try {
|
|
15840
|
-
repoInfo = provider.parseRepoInput(remoteUrl);
|
|
16500
|
+
repoInfo = providerName === "git" ? parseGenericGitExistingRemote(remoteUrl) : provider.parseRepoInput(remoteUrl);
|
|
15841
16501
|
} catch (e) {
|
|
15842
|
-
log.error(`Could not parse the business repo remote "${remoteUrl}": ${e.message}`);
|
|
16502
|
+
log.error(`Could not parse the business repo remote "${redactGitCredentials(remoteUrl)}": ${e.message}`);
|
|
15843
16503
|
process.exit(1);
|
|
15844
16504
|
return;
|
|
15845
16505
|
}
|
|
@@ -15962,20 +16622,28 @@ async function initSelfRepo(options) {
|
|
|
15962
16622
|
}
|
|
15963
16623
|
if (!options.dryRun) {
|
|
15964
16624
|
try {
|
|
15965
|
-
const {
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
await
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
username
|
|
15974
|
-
|
|
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
|
+
};
|
|
15975
16645
|
});
|
|
15976
16646
|
if (selfMemberChanged) {
|
|
15977
|
-
await writeFile(memberPath, YAML11.stringify(selfMemberConfig));
|
|
15978
|
-
const pushed = await commitAndPushReports2(localConfig, isNewSelfMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`, ["members/"]);
|
|
15979
16647
|
if (pushed) {
|
|
15980
16648
|
log.success(isNewSelfMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
15981
16649
|
} else {
|
|
@@ -16150,6 +16818,17 @@ async function init(options) {
|
|
|
16150
16818
|
}
|
|
16151
16819
|
} else {
|
|
16152
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
|
+
}
|
|
16153
16832
|
}
|
|
16154
16833
|
} else {
|
|
16155
16834
|
log.warn(`Existing ${localPath} is not a git repository, re-cloning`);
|
|
@@ -16242,6 +16921,7 @@ async function init(options) {
|
|
|
16242
16921
|
const emailDomain = provider.getDefaultEmailDomain() ?? void 0;
|
|
16243
16922
|
await configureGitUser(localPath, username, username, void 0, emailDomain);
|
|
16244
16923
|
const teamConfig = await loadTeamConfig(localPath);
|
|
16924
|
+
const createdSkeleton = !teamConfig;
|
|
16245
16925
|
if (!teamConfig) {
|
|
16246
16926
|
log.warn("teamai.yaml not found in repo. Creating default config...");
|
|
16247
16927
|
const defaultConfig = YAML11.stringify({
|
|
@@ -16271,33 +16951,67 @@ async function init(options) {
|
|
|
16271
16951
|
log.error(error.message);
|
|
16272
16952
|
process.exit(1);
|
|
16273
16953
|
}
|
|
16274
|
-
const
|
|
16275
|
-
|
|
16276
|
-
const existingMember = await getMemberConfig(localPath, username);
|
|
16277
|
-
const { config: memberConfig, changed: memberChanged } = mergeMemberConfig(existingMember, {
|
|
16954
|
+
const reportsConfig = {
|
|
16955
|
+
repo: { localPath, remote: repoInfo.httpsUrl },
|
|
16278
16956
|
username,
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16957
|
+
scope,
|
|
16958
|
+
projectRoot,
|
|
16959
|
+
additionalRoles: []
|
|
16960
|
+
};
|
|
16961
|
+
if (createdSkeleton && !options.dryRun) {
|
|
16962
|
+
try {
|
|
16963
|
+
await pushRepoDirectly(localPath, "[teamai] Initialize team repo skeleton", [
|
|
16964
|
+
"teamai.yaml",
|
|
16965
|
+
"skills/.gitkeep",
|
|
16966
|
+
"rules/.gitkeep",
|
|
16967
|
+
"docs/.gitkeep",
|
|
16968
|
+
"env/.gitkeep",
|
|
16969
|
+
"members/.gitkeep"
|
|
16970
|
+
]);
|
|
16971
|
+
} catch (e) {
|
|
16972
|
+
log.warn(`Push failed (you can push manually later): ${e.message}`);
|
|
16973
|
+
}
|
|
16974
|
+
}
|
|
16975
|
+
let isNewMember = true;
|
|
16976
|
+
if (!options.dryRun) {
|
|
16977
|
+
try {
|
|
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
|
+
};
|
|
16999
|
+
});
|
|
17000
|
+
if (memberChanged) {
|
|
17001
|
+
log.success(isNewMember ? `Registered as team member: ${username}` : `Updated member roster: ${username}${memberProjects ? ` (projects: ${memberProjects.join(", ")})` : ""}`);
|
|
17002
|
+
if (pushed) {
|
|
17003
|
+
log.success(isNewMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
17004
|
+
} else {
|
|
17005
|
+
log.warn("Member registration could not be pushed (no write access?). You are still set up locally.");
|
|
17006
|
+
}
|
|
17007
|
+
} else if (!isNewMember) {
|
|
17008
|
+
log.info(`Member ${username} already registered`);
|
|
16297
17009
|
}
|
|
17010
|
+
} catch (e) {
|
|
17011
|
+
log.warn(`Member registration skipped (non-blocking): ${e.message}`);
|
|
16298
17012
|
}
|
|
16299
17013
|
} else {
|
|
16300
|
-
log.info(`
|
|
17014
|
+
log.info(`[dry-run] Would register member ${username} on the teamai-reports branch`);
|
|
16301
17015
|
}
|
|
16302
17016
|
const currentConfig = await loadTeamConfig(localPath);
|
|
16303
17017
|
const hasReviewers = currentConfig?.reviewers && currentConfig.reviewers.length > 0;
|
|
@@ -16417,6 +17131,7 @@ async function init(options) {
|
|
|
16417
17131
|
log.info("Run `teamai status` to check current config.");
|
|
16418
17132
|
closePrompt();
|
|
16419
17133
|
}
|
|
17134
|
+
var ALL_PROJECTS_SELECTOR;
|
|
16420
17135
|
var init_init = __esm({
|
|
16421
17136
|
"src/init.ts"() {
|
|
16422
17137
|
"use strict";
|
|
@@ -16425,6 +17140,7 @@ var init_init = __esm({
|
|
|
16425
17140
|
init_git2();
|
|
16426
17141
|
init_git2();
|
|
16427
17142
|
init_providers();
|
|
17143
|
+
init_repo_url5();
|
|
16428
17144
|
init_fs();
|
|
16429
17145
|
init_logger();
|
|
16430
17146
|
init_types();
|
|
@@ -16434,6 +17150,7 @@ var init_init = __esm({
|
|
|
16434
17150
|
init_members();
|
|
16435
17151
|
init_prompt();
|
|
16436
17152
|
init_known_agents();
|
|
17153
|
+
ALL_PROJECTS_SELECTOR = "all";
|
|
16437
17154
|
}
|
|
16438
17155
|
});
|
|
16439
17156
|
|
|
@@ -16756,13 +17473,19 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
|
|
|
16756
17473
|
}
|
|
16757
17474
|
} : teamConfig;
|
|
16758
17475
|
const pushableTypes = ["skills", "rules", "env", "agents"];
|
|
16759
|
-
const
|
|
17476
|
+
const fullScan = [];
|
|
16760
17477
|
for (const type of pushableTypes) {
|
|
16761
17478
|
const handler = getHandler(type);
|
|
16762
17479
|
const items = await handler.scanLocalForPush(scanTeamConfig, localConfig);
|
|
16763
|
-
|
|
17480
|
+
fullScan.push(...items);
|
|
16764
17481
|
}
|
|
16765
|
-
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
|
+
});
|
|
16766
17489
|
spin.stop();
|
|
16767
17490
|
if (options.skill) {
|
|
16768
17491
|
const skillBasename = path46.basename(
|
|
@@ -17148,6 +17871,7 @@ __export(git_exports, {
|
|
|
17148
17871
|
listWorktrees: () => listWorktrees,
|
|
17149
17872
|
normalizeRepoUrlForCompare: () => normalizeRepoUrlForCompare,
|
|
17150
17873
|
pullRepo: () => pullRepo,
|
|
17874
|
+
pullRepoFastForward: () => pullRepoFastForward,
|
|
17151
17875
|
pushLearningToOrigin: () => pushLearningToOrigin,
|
|
17152
17876
|
pushRepoBranch: () => pushRepoBranch,
|
|
17153
17877
|
pushRepoDirectly: () => pushRepoDirectly,
|
|
@@ -17286,6 +18010,14 @@ async function pullRepo(localPath) {
|
|
|
17286
18010
|
return "reset to origin (diverged)";
|
|
17287
18011
|
}
|
|
17288
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
|
+
}
|
|
17289
18021
|
async function getDefaultBranch(localPath) {
|
|
17290
18022
|
const cached = defaultBranchCache.get(localPath);
|
|
17291
18023
|
if (cached) return cached;
|
|
@@ -17861,19 +18593,19 @@ async function bootstrapSelfRepo(dir, opts) {
|
|
|
17861
18593
|
log.debug(`[bootstrap] hook injection failed (non-blocking): ${e.message}`);
|
|
17862
18594
|
}
|
|
17863
18595
|
try {
|
|
17864
|
-
const {
|
|
17865
|
-
|
|
17866
|
-
|
|
17867
|
-
|
|
17868
|
-
|
|
17869
|
-
|
|
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;
|
|
17870
18602
|
await writeFile(memberPath, YAML14.stringify({
|
|
17871
18603
|
username,
|
|
17872
18604
|
displayName: username,
|
|
17873
18605
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17874
18606
|
}));
|
|
17875
|
-
|
|
17876
|
-
}
|
|
18607
|
+
return { files: ["members/"], message: `[teamai] Register member: ${username}` };
|
|
18608
|
+
});
|
|
17877
18609
|
} catch (e) {
|
|
17878
18610
|
log.debug(`[bootstrap] member registration skipped (non-blocking): ${e.message}`);
|
|
17879
18611
|
}
|
|
@@ -18141,68 +18873,6 @@ var init_config = __esm({
|
|
|
18141
18873
|
}
|
|
18142
18874
|
});
|
|
18143
18875
|
|
|
18144
|
-
// src/utils/exec.ts
|
|
18145
|
-
import spawn from "cross-spawn";
|
|
18146
|
-
async function probeBinary(binary, args = ["--version"], executor = execCommand) {
|
|
18147
|
-
try {
|
|
18148
|
-
const result = await executor(binary, args, { timeoutMs: 5e3 });
|
|
18149
|
-
if (result.code !== 0) return "";
|
|
18150
|
-
return (result.stdout || result.stderr).trim();
|
|
18151
|
-
} catch {
|
|
18152
|
-
return "";
|
|
18153
|
-
}
|
|
18154
|
-
}
|
|
18155
|
-
function formatCommand(command, args) {
|
|
18156
|
-
const quote = (value) => /^[a-zA-Z0-9_./:@=+-]+$/.test(value) ? value : JSON.stringify(value);
|
|
18157
|
-
return [command, ...args.map(quote)].join(" ");
|
|
18158
|
-
}
|
|
18159
|
-
var execCommand;
|
|
18160
|
-
var init_exec = __esm({
|
|
18161
|
-
"src/utils/exec.ts"() {
|
|
18162
|
-
"use strict";
|
|
18163
|
-
execCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
18164
|
-
const child = spawn(command, args, {
|
|
18165
|
-
cwd: options.cwd,
|
|
18166
|
-
env: options.env,
|
|
18167
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
18168
|
-
shell: false
|
|
18169
|
-
});
|
|
18170
|
-
let stdout = "";
|
|
18171
|
-
let stderr = "";
|
|
18172
|
-
let settled = false;
|
|
18173
|
-
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
18174
|
-
const finish = (callback) => {
|
|
18175
|
-
if (settled) return;
|
|
18176
|
-
settled = true;
|
|
18177
|
-
clearTimeout(timer);
|
|
18178
|
-
callback();
|
|
18179
|
-
};
|
|
18180
|
-
child.stdout?.on("data", (chunk) => {
|
|
18181
|
-
const text = chunk.toString();
|
|
18182
|
-
stdout += text;
|
|
18183
|
-
if (options.stream) process.stdout.write(text);
|
|
18184
|
-
});
|
|
18185
|
-
child.stderr?.on("data", (chunk) => {
|
|
18186
|
-
const text = chunk.toString();
|
|
18187
|
-
stderr += text;
|
|
18188
|
-
if (options.stream) process.stderr.write(text);
|
|
18189
|
-
});
|
|
18190
|
-
const timer = setTimeout(() => {
|
|
18191
|
-
child.kill();
|
|
18192
|
-
finish(() => reject(new Error(
|
|
18193
|
-
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`
|
|
18194
|
-
)));
|
|
18195
|
-
}, timeoutMs);
|
|
18196
|
-
child.on("error", (error) => finish(() => reject(error)));
|
|
18197
|
-
child.on("close", (code) => finish(() => resolve({
|
|
18198
|
-
stdout,
|
|
18199
|
-
stderr,
|
|
18200
|
-
code: code ?? 1
|
|
18201
|
-
})));
|
|
18202
|
-
});
|
|
18203
|
-
}
|
|
18204
|
-
});
|
|
18205
|
-
|
|
18206
18876
|
// src/pkg/adapters/base.ts
|
|
18207
18877
|
var PackageAdapter;
|
|
18208
18878
|
var init_base2 = __esm({
|
|
@@ -19208,9 +19878,9 @@ async function verifyStaging(legacyDir, staging) {
|
|
|
19208
19878
|
throw new Error("migration verify: team-repo/.git missing after copy (clone would be broken)");
|
|
19209
19879
|
}
|
|
19210
19880
|
try {
|
|
19211
|
-
const { execFile:
|
|
19881
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
19212
19882
|
const { promisify: promisify4 } = await import("util");
|
|
19213
|
-
await promisify4(
|
|
19883
|
+
await promisify4(execFile4)("git", ["rev-parse", "HEAD"], { cwd: stagedRepo });
|
|
19214
19884
|
} catch (e) {
|
|
19215
19885
|
throw new Error(
|
|
19216
19886
|
`migration verify: the staged team-repo clone is not a usable git repository (${e.message})`
|
|
@@ -19595,6 +20265,7 @@ var init_tokenizer = __esm({
|
|
|
19595
20265
|
// src/votes.ts
|
|
19596
20266
|
var votes_exports = {};
|
|
19597
20267
|
__export(votes_exports, {
|
|
20268
|
+
hasPendingVoteDeltas: () => hasPendingVoteDeltas,
|
|
19598
20269
|
incrementRecalled: () => incrementRecalled,
|
|
19599
20270
|
incrementUpvoted: () => incrementUpvoted,
|
|
19600
20271
|
loadUserVotes: () => loadUserVotes,
|
|
@@ -19700,9 +20371,19 @@ function mergeDeltas(local, remote) {
|
|
|
19700
20371
|
votes[docId].last_upvoted_at = localEntry.last_upvoted_at;
|
|
19701
20372
|
}
|
|
19702
20373
|
}
|
|
20374
|
+
if (votes[docId].recalled_count === 0) {
|
|
20375
|
+
votes[docId].last_recalled_at = "";
|
|
20376
|
+
}
|
|
20377
|
+
if (votes[docId].upvoted_count === 0) {
|
|
20378
|
+
delete votes[docId].last_upvoted_at;
|
|
20379
|
+
}
|
|
19703
20380
|
}
|
|
19704
20381
|
return { version: 2, votes, deltas: {} };
|
|
19705
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
|
+
}
|
|
19706
20387
|
async function syncVotesToTeam(repoPath, username, localVotesDir) {
|
|
19707
20388
|
const localVotePath = path57.join(localVotesDir, `${username}.yaml`);
|
|
19708
20389
|
const remoteVotePath = path57.join(repoPath, "votes", `${username}.yaml`);
|
|
@@ -19739,11 +20420,16 @@ async function recallFeedback(opts) {
|
|
|
19739
20420
|
return;
|
|
19740
20421
|
}
|
|
19741
20422
|
const existingDelta = data.deltas[opts.negative] ?? { recalled_delta: 0, upvoted_delta: 0 };
|
|
20423
|
+
const decrementedCount = entry.upvoted_count - 1;
|
|
20424
|
+
const updatedEntry = { ...entry, upvoted_count: decrementedCount };
|
|
20425
|
+
if (decrementedCount === 0) {
|
|
20426
|
+
delete updatedEntry.last_upvoted_at;
|
|
20427
|
+
}
|
|
19742
20428
|
const updated = {
|
|
19743
20429
|
...data,
|
|
19744
20430
|
votes: {
|
|
19745
20431
|
...data.votes,
|
|
19746
|
-
[opts.negative]:
|
|
20432
|
+
[opts.negative]: updatedEntry
|
|
19747
20433
|
},
|
|
19748
20434
|
deltas: {
|
|
19749
20435
|
...data.deltas,
|
|
@@ -21473,10 +22159,11 @@ async function generateDigest(options) {
|
|
|
21473
22159
|
const localConfig = projectConfig ?? (await requireInit()).localConfig;
|
|
21474
22160
|
const repoPath = localConfig.repo.localPath;
|
|
21475
22161
|
let reportsRoot = repoPath;
|
|
21476
|
-
|
|
22162
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
22163
|
+
if (usesReportsBranch2(localConfig)) {
|
|
21477
22164
|
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
21478
|
-
await refreshReportsWorktree2(localConfig);
|
|
21479
|
-
reportsRoot = await ensureReportsWorktree2(localConfig);
|
|
22165
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
22166
|
+
reportsRoot = await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
21480
22167
|
}
|
|
21481
22168
|
const teamStats = await loadTeamStats(reportsRoot);
|
|
21482
22169
|
if (teamStats.length === 0) {
|
|
@@ -21629,9 +22316,10 @@ async function loadReportedStats() {
|
|
|
21629
22316
|
const config = await detectProjectConfig() ?? await loadLocalConfig();
|
|
21630
22317
|
if (!config) return null;
|
|
21631
22318
|
let statsRoot = config.repo.localPath;
|
|
21632
|
-
|
|
22319
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
22320
|
+
if (usesReportsBranch2(config)) {
|
|
21633
22321
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
21634
|
-
statsRoot = await ensureReportsWorktree2(config);
|
|
22322
|
+
statsRoot = await ensureReportsWorktree2(config, { pushIfCreated: false });
|
|
21635
22323
|
}
|
|
21636
22324
|
const statsPath = path62.join(statsRoot, "stats", `${config.username}.yaml`);
|
|
21637
22325
|
const content = await readFileSafe(statsPath);
|
|
@@ -22105,8 +22793,9 @@ function filterEventsByScope(events, opts) {
|
|
|
22105
22793
|
return events;
|
|
22106
22794
|
}
|
|
22107
22795
|
async function reportUsageToTeam(repoPath, username, options) {
|
|
22108
|
-
const
|
|
22109
|
-
const
|
|
22796
|
+
const reportsConfig = options?.selfConfig;
|
|
22797
|
+
const useReportsBranch = !!reportsConfig && usesReportsBranch(reportsConfig);
|
|
22798
|
+
let restoreStats;
|
|
22110
22799
|
try {
|
|
22111
22800
|
const events = await readUsageEvents();
|
|
22112
22801
|
const filesToPush = [];
|
|
@@ -22135,20 +22824,77 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22135
22824
|
const hasInterventions = hasInterventionDelta(interventionDelta);
|
|
22136
22825
|
const hasPromptTokens = hasPromptTokenDelta(promptTokenDelta);
|
|
22137
22826
|
const hasDaily = hasDailyDelta(dailyDelta);
|
|
22138
|
-
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
|
|
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
|
+
};
|
|
22867
|
+
if (useReportsBranch && reportsConfig) {
|
|
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
|
+
}
|
|
22142
22888
|
} else {
|
|
22143
22889
|
const git = createGit2(repoPath);
|
|
22144
22890
|
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
22145
22891
|
log.debug(`Skipping report: ${repoPath} is not a dedicated team-repo root (safety guard)`);
|
|
22146
|
-
return;
|
|
22892
|
+
return false;
|
|
22147
22893
|
}
|
|
22148
22894
|
const { isImportInProgress: isImportInProgress2 } = await Promise.resolve().then(() => (init_import_lock(), import_lock_exports));
|
|
22149
22895
|
if (await isImportInProgress2(repoPath)) {
|
|
22150
22896
|
log.debug(`Skipping report: import in progress for ${repoPath} (would reset uncommitted artifacts)`);
|
|
22151
|
-
return;
|
|
22897
|
+
return false;
|
|
22152
22898
|
}
|
|
22153
22899
|
const yamlPath = path64.join(repoPath, "teamai.yaml");
|
|
22154
22900
|
const workingContent = await readFileSafe(yamlPath);
|
|
@@ -22162,58 +22908,14 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22162
22908
|
await writeFile(yamlPath, pendingTeamConfig);
|
|
22163
22909
|
}
|
|
22164
22910
|
}
|
|
22165
|
-
|
|
22166
|
-
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
const statsPath = path64.join(statsDir, `${username}.yaml`);
|
|
22170
|
-
const existing = await readExistingStats(statsPath);
|
|
22171
|
-
const newStats = hasUsage ? aggregateUsage(events) : [];
|
|
22172
|
-
const merged = mergeStats(existing, username, newStats);
|
|
22173
|
-
if (hasInterventions) {
|
|
22174
|
-
merged.interventions = mergeInterventionStats(existing?.interventions, interventionDelta);
|
|
22175
|
-
}
|
|
22176
|
-
if (hasPromptTokens) {
|
|
22177
|
-
const pt = mergePromptTokenStats(existing?.prompts, existing?.tokens, promptTokenDelta);
|
|
22178
|
-
merged.prompts = pt.prompts;
|
|
22179
|
-
merged.tokens = pt.tokens;
|
|
22180
|
-
}
|
|
22181
|
-
if (hasDaily) {
|
|
22182
|
-
merged.daily = mergeDailyStats(existing?.daily, dailyDelta);
|
|
22183
|
-
}
|
|
22184
|
-
await writeFile(statsPath, YAML21.stringify(merged));
|
|
22185
|
-
filesToPush.push(`stats/${username}.yaml`);
|
|
22186
|
-
}
|
|
22187
|
-
try {
|
|
22188
|
-
if (await pathExists(getUserVotesDir())) {
|
|
22189
|
-
const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
22190
|
-
const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
|
|
22191
|
-
if (synced) {
|
|
22192
|
-
filesToPush.push(`votes/${username}.yaml`);
|
|
22193
|
-
}
|
|
22911
|
+
await writeReportFiles(repoPath);
|
|
22912
|
+
if (filesToPush.length === 0) {
|
|
22913
|
+
log.debug("No usage events or votes to report");
|
|
22914
|
+
return true;
|
|
22194
22915
|
}
|
|
22195
|
-
|
|
22196
|
-
log.error(`Vote staging skipped: ${e.message}`);
|
|
22197
|
-
}
|
|
22198
|
-
if (filesToPush.length === 0) {
|
|
22199
|
-
log.debug("No usage events or votes to report");
|
|
22200
|
-
return;
|
|
22201
|
-
}
|
|
22202
|
-
const commitMsg = hasUsage ? `[teamai] Update usage stats for ${username}` : hasInterventions || hasPromptTokens || hasDaily ? `[teamai] Update session stats for ${username}` : `[teamai] Update votes for ${username}`;
|
|
22203
|
-
if (selfMode && selfConfig) {
|
|
22204
|
-
const { commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22205
|
-
await withTimeout(
|
|
22206
|
-
commitAndPushReports2(selfConfig, commitMsg, filesToPush),
|
|
22207
|
-
5e3,
|
|
22208
|
-
"Auto-report timeout (5s)"
|
|
22209
|
-
);
|
|
22210
|
-
} else {
|
|
22211
|
-
await withTimeout(
|
|
22212
|
-
pushRepoDirectly(repoPath, commitMsg, filesToPush),
|
|
22213
|
-
5e3,
|
|
22214
|
-
"Auto-report timeout (5s)"
|
|
22215
|
-
);
|
|
22916
|
+
await pushRepoDirectly(repoPath, commitMsg, filesToPush);
|
|
22216
22917
|
}
|
|
22918
|
+
restoreStats = void 0;
|
|
22217
22919
|
if (hasUsage && !options?.skipTruncate) {
|
|
22218
22920
|
await truncateUsageAfterReport(events.length);
|
|
22219
22921
|
log.debug(`Reported ${events.length} usage events to team repo`);
|
|
@@ -22238,8 +22940,15 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22238
22940
|
if (!hasUsage && !hasInterventions && !hasPromptTokens && !hasDaily) {
|
|
22239
22941
|
log.debug("Pushed pending votes to team repo");
|
|
22240
22942
|
}
|
|
22943
|
+
return true;
|
|
22241
22944
|
} catch (e) {
|
|
22945
|
+
try {
|
|
22946
|
+
await restoreStats?.();
|
|
22947
|
+
} catch (restoreError) {
|
|
22948
|
+
log.error(`Could not restore report stats after failure: ${restoreError.message}`);
|
|
22949
|
+
}
|
|
22242
22950
|
log.error(`Auto-report skipped: ${e.message}`);
|
|
22951
|
+
return false;
|
|
22243
22952
|
}
|
|
22244
22953
|
}
|
|
22245
22954
|
var init_team_push = __esm({
|
|
@@ -22249,7 +22958,6 @@ var init_team_push = __esm({
|
|
|
22249
22958
|
init_stats();
|
|
22250
22959
|
init_dashboard_collector();
|
|
22251
22960
|
init_git2();
|
|
22252
|
-
init_async();
|
|
22253
22961
|
init_fs();
|
|
22254
22962
|
init_logger();
|
|
22255
22963
|
init_types();
|
|
@@ -22427,6 +23135,7 @@ __export(pull_exports, {
|
|
|
22427
23135
|
compileClaudemd: () => compileClaudemd,
|
|
22428
23136
|
compileCulture: () => compileCulture,
|
|
22429
23137
|
compileRecallRulesBlock: () => compileRecallRulesBlock,
|
|
23138
|
+
filterAgentsByNamespaces: () => filterAgentsByNamespaces,
|
|
22430
23139
|
filterRulesByKnowledgeNamespaces: () => filterRulesByKnowledgeNamespaces,
|
|
22431
23140
|
injectRecallBlockIntoTools: () => injectRecallBlockIntoTools,
|
|
22432
23141
|
pull: () => pull,
|
|
@@ -22504,58 +23213,9 @@ async function usageReportDisabled(repoPath) {
|
|
|
22504
23213
|
return (await loadTeamConfig(repoPath))?.usageReport === false;
|
|
22505
23214
|
}
|
|
22506
23215
|
async function buildRolePullContext(localConfig) {
|
|
22507
|
-
const
|
|
22508
|
-
|
|
22509
|
-
const
|
|
22510
|
-
const projectsManifest = await loadProjectsManifest(localConfig.repo.localPath);
|
|
22511
|
-
const teamHasProjects = !!projectsManifest && projectsManifest.projects.length > 0;
|
|
22512
|
-
if (!hasRole && !hasProjects && !teamHasProjects) return null;
|
|
22513
|
-
let roleNamespaces = { knowledge: [], skills: [], learnings: [] };
|
|
22514
|
-
let allRoleSkillNamespaces = /* @__PURE__ */ new Set();
|
|
22515
|
-
if (hasRole) {
|
|
22516
|
-
let rolesManifest;
|
|
22517
|
-
try {
|
|
22518
|
-
rolesManifest = await loadRolesManifest(localConfig.repo.localPath);
|
|
22519
|
-
} catch {
|
|
22520
|
-
log.warn("Could not load roles manifest. Skipping role-based filtering.");
|
|
22521
|
-
rolesManifest = null;
|
|
22522
|
-
}
|
|
22523
|
-
if (rolesManifest) {
|
|
22524
|
-
try {
|
|
22525
|
-
roleNamespaces = resolveRoleResourceNamespaces({
|
|
22526
|
-
manifest: rolesManifest,
|
|
22527
|
-
primaryRole: localConfig.primaryRole,
|
|
22528
|
-
additionalRoles: localConfig.additionalRoles ?? []
|
|
22529
|
-
});
|
|
22530
|
-
allRoleSkillNamespaces = new Set(rolesManifest.roles.flatMap((role) => role.resources.skills));
|
|
22531
|
-
} catch {
|
|
22532
|
-
log.warn(`Role "${localConfig.primaryRole}" not found in manifest. Falling back to unfiltered sync.`);
|
|
22533
|
-
log.warn("Run `teamai roles set <role>` to pick a valid role.");
|
|
22534
|
-
if (!hasProjects && !teamHasProjects) return null;
|
|
22535
|
-
}
|
|
22536
|
-
} else if (!hasProjects && !teamHasProjects) {
|
|
22537
|
-
return null;
|
|
22538
|
-
}
|
|
22539
|
-
}
|
|
22540
|
-
let projectNamespaces = { knowledge: [], skills: [], learnings: [] };
|
|
22541
|
-
let allProjectSkillNamespaces = /* @__PURE__ */ new Set();
|
|
22542
|
-
if (projectsManifest) {
|
|
22543
|
-
allProjectSkillNamespaces = new Set(projectsManifest.projects.flatMap((p) => p.resources.skills));
|
|
22544
|
-
if (hasProjects) {
|
|
22545
|
-
try {
|
|
22546
|
-
projectNamespaces = resolveProjectResourceNamespaces({
|
|
22547
|
-
manifest: projectsManifest,
|
|
22548
|
-
activeProjects
|
|
22549
|
-
});
|
|
22550
|
-
} catch (e) {
|
|
22551
|
-
log.warn(`${e.message} Falling back to role-only filtering.`);
|
|
22552
|
-
}
|
|
22553
|
-
}
|
|
22554
|
-
} else if (hasProjects) {
|
|
22555
|
-
log.warn("Active projects configured but no projects manifest found. Skipping project-based filtering.");
|
|
22556
|
-
}
|
|
22557
|
-
const activeNamespaces = mergeNamespaces(roleNamespaces, projectNamespaces);
|
|
22558
|
-
const allSkillNamespaces = /* @__PURE__ */ new Set([...allRoleSkillNamespaces, ...allProjectSkillNamespaces]);
|
|
23216
|
+
const resolved = await resolveResourceNamespaces(localConfig);
|
|
23217
|
+
if (!resolved) return null;
|
|
23218
|
+
const { activeNamespaces, allSkillNamespaces } = resolved;
|
|
22559
23219
|
const inactiveSkillNamespaces = [...allSkillNamespaces].filter((namespace) => !activeNamespaces.skills.includes(namespace));
|
|
22560
23220
|
const activeSkillNames = /* @__PURE__ */ new Set();
|
|
22561
23221
|
const inactiveSkillNames = /* @__PURE__ */ new Set();
|
|
@@ -22588,6 +23248,20 @@ function filterRulesByKnowledgeNamespaces(rules, knowledgeNamespaces) {
|
|
|
22588
23248
|
return knowledgeNamespaces.includes(namespace);
|
|
22589
23249
|
});
|
|
22590
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
|
+
}
|
|
22591
23265
|
async function scanRoleAwareSkills(localConfig, namespaces) {
|
|
22592
23266
|
const items = /* @__PURE__ */ new Map();
|
|
22593
23267
|
for (const namespace of namespaces.skills) {
|
|
@@ -22689,15 +23363,46 @@ async function getInstalledResourceTargets(teamConfig, localConfig) {
|
|
|
22689
23363
|
}
|
|
22690
23364
|
return targets.sort();
|
|
22691
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
|
+
}
|
|
22692
23402
|
async function pullForScope(localConfig, options, policy = {}) {
|
|
22693
23403
|
const scopeLabel = localConfig.scope;
|
|
22694
23404
|
const revisionField = policy.revisionField ?? "lastPullRev";
|
|
22695
23405
|
const targetsField = revisionField === "lastPullRev" ? "lastPullTargets" : "lastInheritedPullTargets";
|
|
22696
|
-
const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
22697
|
-
if (!teamConfig) {
|
|
22698
|
-
log.warn(`[${scopeLabel}] Team config (teamai.yaml) not found. Skipping.`);
|
|
22699
|
-
return;
|
|
22700
|
-
}
|
|
22701
23406
|
const pullSpin = spinner(`[${scopeLabel}] Pulling team repo...`).start();
|
|
22702
23407
|
let currentRev = null;
|
|
22703
23408
|
let reportingOnly = false;
|
|
@@ -22714,39 +23419,40 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22714
23419
|
pullSpin.fail(`[${scopeLabel}] Pull failed: ${e.message}`);
|
|
22715
23420
|
return;
|
|
22716
23421
|
}
|
|
23422
|
+
const freshConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
23423
|
+
if (!freshConfig) {
|
|
23424
|
+
log.warn(`[${scopeLabel}] Team config (teamai.yaml) not found. Skipping.`);
|
|
23425
|
+
return;
|
|
23426
|
+
}
|
|
22717
23427
|
let currentTargets = null;
|
|
22718
23428
|
if (!options.force && !options.dryRun && !submodulesChanged) {
|
|
22719
23429
|
try {
|
|
22720
23430
|
const state = await loadStateForScope(localConfig);
|
|
22721
23431
|
if (currentRev && state[revisionField] && state[revisionField] === currentRev) {
|
|
22722
|
-
currentTargets = await getInstalledResourceTargets(
|
|
23432
|
+
currentTargets = await getInstalledResourceTargets(freshConfig, localConfig);
|
|
22723
23433
|
const previousTargets = state[targetsField];
|
|
22724
23434
|
const syncedTargets = new Set(previousTargets ?? []);
|
|
22725
23435
|
const targetSetMatches = previousTargets !== void 0 && previousTargets.length === currentTargets.length && currentTargets.every((target) => syncedTargets.has(target));
|
|
22726
23436
|
if (targetSetMatches) {
|
|
22727
23437
|
log.success(`[${scopeLabel}] Already synced at ${currentRev}, skipping`);
|
|
22728
|
-
|
|
22729
|
-
|
|
22730
|
-
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
const { deployBuiltinSkills: deployBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
22744
|
-
await deployBuiltinSkills2(cfg, localConfig, { reportingOnly, skipRecall });
|
|
22745
|
-
} catch {
|
|
22746
|
-
}
|
|
22747
|
-
await injectRecallBlockIntoTools(cfg, localConfig, scopeLabel);
|
|
22748
|
-
}
|
|
23438
|
+
const skipRecall = !isRecallEnabled(localConfig, freshConfig);
|
|
23439
|
+
try {
|
|
23440
|
+
const { deployBuiltinAgents: deployBuiltinAgents2 } = await Promise.resolve().then(() => (init_builtin_agents(), builtin_agents_exports));
|
|
23441
|
+
await deployBuiltinAgents2(freshConfig, localConfig, { skipRecall });
|
|
23442
|
+
} catch {
|
|
23443
|
+
}
|
|
23444
|
+
try {
|
|
23445
|
+
const { deployBuiltinRules: deployBuiltinRules2 } = await Promise.resolve().then(() => (init_builtin_rules(), builtin_rules_exports));
|
|
23446
|
+
await deployBuiltinRules2(freshConfig, localConfig, { skipRecall });
|
|
23447
|
+
} catch {
|
|
23448
|
+
}
|
|
23449
|
+
try {
|
|
23450
|
+
const { deployBuiltinSkills: deployBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
23451
|
+
await deployBuiltinSkills2(freshConfig, localConfig, { reportingOnly, skipRecall });
|
|
23452
|
+
} catch {
|
|
22749
23453
|
}
|
|
23454
|
+
await injectRecallBlockIntoTools(freshConfig, localConfig, scopeLabel);
|
|
23455
|
+
await cleanupTombstonedResources(freshConfig, localConfig, scopeLabel);
|
|
22750
23456
|
return;
|
|
22751
23457
|
}
|
|
22752
23458
|
log.debug(`[${scopeLabel}] Repo unchanged; resource target set changed, syncing`);
|
|
@@ -22755,11 +23461,6 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22755
23461
|
log.debug(`[${scopeLabel}] Rev check failed, proceeding with full sync`);
|
|
22756
23462
|
}
|
|
22757
23463
|
}
|
|
22758
|
-
const freshConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
22759
|
-
if (!freshConfig) {
|
|
22760
|
-
log.warn(`[${scopeLabel}] Team config disappeared after pull. Skipping.`);
|
|
22761
|
-
return;
|
|
22762
|
-
}
|
|
22763
23464
|
let roleContext = null;
|
|
22764
23465
|
try {
|
|
22765
23466
|
roleContext = await buildRolePullContext(localConfig);
|
|
@@ -22824,6 +23525,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22824
23525
|
desiredSkillNames = new Set(items.map((i) => i.name));
|
|
22825
23526
|
knownRepoSkillNames = new Set(allTeamSkills.map((i) => i.name));
|
|
22826
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
|
+
);
|
|
22827
23533
|
} else {
|
|
22828
23534
|
items = await handler.scanTeamForPull(freshConfig, localConfig);
|
|
22829
23535
|
}
|
|
@@ -22882,37 +23588,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22882
23588
|
totalSynced += items.length;
|
|
22883
23589
|
}
|
|
22884
23590
|
if (!options.dryRun) {
|
|
22885
|
-
|
|
22886
|
-
{ type: "rules", ext: ".md", toolPathField: "rules" },
|
|
22887
|
-
{ type: "skills", toolPathField: "skills" },
|
|
22888
|
-
{ type: "agents", ext: ".md", toolPathField: "agents" }
|
|
22889
|
-
];
|
|
22890
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
22891
|
-
for (const { type, ext, toolPathField } of tombstoneTypes) {
|
|
22892
|
-
const handler = getHandler(type);
|
|
22893
|
-
const tombstones = await handler.readTombstones(localConfig);
|
|
22894
|
-
if (tombstones.size === 0) continue;
|
|
22895
|
-
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
22896
|
-
const dir = toolPath[toolPathField];
|
|
22897
|
-
if (!dir) continue;
|
|
22898
|
-
if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
|
|
22899
|
-
if (isAgentExcluded(localConfig, tool)) continue;
|
|
22900
|
-
const extensions = type === "rules" ? [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])] : [ext];
|
|
22901
|
-
for (const name of tombstones) {
|
|
22902
|
-
for (const extension of extensions) {
|
|
22903
|
-
const localPath = path66.join(baseDir, dir, extension ? `${name}${extension}` : name);
|
|
22904
|
-
if (await pathExists(localPath)) {
|
|
22905
|
-
if (type === "skills" && await hasVcsMetadataRecursive(localPath)) {
|
|
22906
|
-
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.`);
|
|
22907
|
-
continue;
|
|
22908
|
-
}
|
|
22909
|
-
await remove(localPath);
|
|
22910
|
-
log.debug(`[${scopeLabel}] Cleaned up tombstoned ${type} ${name} from ${dir}`);
|
|
22911
|
-
}
|
|
22912
|
-
}
|
|
22913
|
-
}
|
|
22914
|
-
}
|
|
22915
|
-
}
|
|
23591
|
+
await cleanupTombstonedResources(freshConfig, localConfig, scopeLabel);
|
|
22916
23592
|
if (roleContext) {
|
|
22917
23593
|
await cleanupInactiveNamespaceSkills(
|
|
22918
23594
|
freshConfig,
|
|
@@ -22921,6 +23597,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22921
23597
|
roleContext.inactiveSkillNames,
|
|
22922
23598
|
roleContext.inactiveSkillSources
|
|
22923
23599
|
);
|
|
23600
|
+
await getHandler("agents").cleanupInactiveNamespaces(
|
|
23601
|
+
freshConfig,
|
|
23602
|
+
localConfig,
|
|
23603
|
+
roleContext.activeNamespaces.agents
|
|
23604
|
+
);
|
|
22924
23605
|
}
|
|
22925
23606
|
}
|
|
22926
23607
|
if (!options.dryRun && desiredSkillNames && knownRepoSkillNames) {
|
|
@@ -22962,21 +23643,29 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22962
23643
|
if (totalSynced === 0) {
|
|
22963
23644
|
log.info(`[${scopeLabel}] No resources to sync`);
|
|
22964
23645
|
}
|
|
23646
|
+
let reportsReadRoot;
|
|
23647
|
+
const resolveReportsReadRoot = () => {
|
|
23648
|
+
reportsReadRoot ??= (async () => {
|
|
23649
|
+
if (!usesReportsBranch(localConfig)) return localConfig.repo.localPath;
|
|
23650
|
+
try {
|
|
23651
|
+
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
23652
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
23653
|
+
return await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
23654
|
+
} catch (e) {
|
|
23655
|
+
log.debug(`reports worktree unavailable: ${e.message}`);
|
|
23656
|
+
return void 0;
|
|
23657
|
+
}
|
|
23658
|
+
})();
|
|
23659
|
+
return reportsReadRoot;
|
|
23660
|
+
};
|
|
22965
23661
|
if (!options.dryRun) {
|
|
22966
23662
|
try {
|
|
22967
23663
|
const learningsRepoDir = path66.join(localConfig.repo.localPath, "learnings");
|
|
22968
23664
|
const docsRepoDir = path66.join(localConfig.repo.localPath, "docs");
|
|
22969
23665
|
const rulesRepoDir = path66.join(localConfig.repo.localPath, "rules");
|
|
22970
23666
|
const skillsRepoDir = path66.join(localConfig.repo.localPath, "skills");
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
try {
|
|
22974
|
-
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22975
|
-
votesDir = path66.join(await ensureReportsWorktree2(localConfig), "votes");
|
|
22976
|
-
} catch (e) {
|
|
22977
|
-
log.debug(`[self] reports worktree for votes unavailable: ${e.message}`);
|
|
22978
|
-
}
|
|
22979
|
-
}
|
|
23667
|
+
const reportsRoot = await resolveReportsReadRoot();
|
|
23668
|
+
const votesDir = reportsRoot ? path66.join(reportsRoot, "votes") : void 0;
|
|
22980
23669
|
const activeLearningsNamespaces = roleContext?.activeNamespaces.learnings ?? [];
|
|
22981
23670
|
const countLearnings = async (baseDir) => {
|
|
22982
23671
|
let n = (await listFiles(baseDir)).filter((f) => f.endsWith(".md")).length;
|
|
@@ -23010,7 +23699,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23010
23699
|
const repoCodebaseDir = path66.join(localConfig.repo.localPath, "docs", "team-codebase");
|
|
23011
23700
|
const effectiveCodebaseDir = await pathExists(repoCodebaseDir) ? repoCodebaseDir : void 0;
|
|
23012
23701
|
if (hasAnySource || effectiveCodebaseDir) {
|
|
23013
|
-
const votesExist = await pathExists(votesDir);
|
|
23702
|
+
const votesExist = votesDir ? await pathExists(votesDir) : false;
|
|
23014
23703
|
const teamaiHome = getDataHome(localConfig);
|
|
23015
23704
|
const indexPath = path66.join(teamaiHome, "search-index.json");
|
|
23016
23705
|
const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
|
|
@@ -23156,16 +23845,9 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23156
23845
|
const YAML25 = (await import("yaml")).default;
|
|
23157
23846
|
const { listFiles: listFiles2, readFileSafe: readFileSafe5 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
23158
23847
|
const { getRecommendations: getRecommendations2, displayRecommendations: displayRecommendations2 } = await Promise.resolve().then(() => (init_skill_recommend(), skill_recommend_exports));
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
|
|
23162
|
-
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
23163
|
-
statsDir = path66.join(await ensureReportsWorktree2(localConfig), "stats");
|
|
23164
|
-
} catch (e) {
|
|
23165
|
-
log.debug(`[self] reports worktree for stats unavailable: ${e.message}`);
|
|
23166
|
-
}
|
|
23167
|
-
}
|
|
23168
|
-
const files = await listFiles2(statsDir);
|
|
23848
|
+
const reportsRoot = await resolveReportsReadRoot();
|
|
23849
|
+
const statsDir = reportsRoot ? path66.join(reportsRoot, "stats") : void 0;
|
|
23850
|
+
const files = statsDir ? await listFiles2(statsDir) : [];
|
|
23169
23851
|
const teamStats = [];
|
|
23170
23852
|
for (const file of files) {
|
|
23171
23853
|
if (!file.endsWith(".yaml")) continue;
|
|
@@ -23394,6 +24076,7 @@ async function pull(options) {
|
|
|
23394
24076
|
const needsHookMigration = await legacyHooksNeedReinject().catch(() => false);
|
|
23395
24077
|
const contended = /* @__PURE__ */ new Set();
|
|
23396
24078
|
const heldLocks = /* @__PURE__ */ new Map();
|
|
24079
|
+
let usageReport;
|
|
23397
24080
|
const lockScope = async (config) => {
|
|
23398
24081
|
if (config.repo.kind === "http") return true;
|
|
23399
24082
|
const lock = path66.join(getDataHome(config), SYNC_LOCK_FILENAME);
|
|
@@ -23469,49 +24152,62 @@ async function pull(options) {
|
|
|
23469
24152
|
await reconcileHooksAllScopes(reconcileUser, reconcileProject, options);
|
|
23470
24153
|
await reconcileMcpAllScopes(reconcileUser, reconcileProject, options);
|
|
23471
24154
|
await reconcileCoAuthorAllScopes(reconcileUser, reconcileProject, options);
|
|
23472
|
-
if (!options.dryRun) {
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
|
|
23478
|
-
|
|
23479
|
-
|
|
23480
|
-
|
|
23481
|
-
|
|
23482
|
-
|
|
23483
|
-
|
|
23484
|
-
|
|
23485
|
-
|
|
23486
|
-
|
|
23487
|
-
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23492
|
-
|
|
23493
|
-
|
|
23494
|
-
|
|
23495
|
-
|
|
23496
|
-
|
|
23497
|
-
|
|
23498
|
-
|
|
24155
|
+
if (!options.dryRun && !pendingUsageReport) {
|
|
24156
|
+
pendingUsageReport = (async () => {
|
|
24157
|
+
try {
|
|
24158
|
+
const { reportUsageToTeam: reportUsageToTeam2 } = await Promise.resolve().then(() => (init_team_push(), team_push_exports));
|
|
24159
|
+
const { truncateUsageAfterReport: truncateUsageAfterReport2, readUsageEvents: readUsageEvents2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
24160
|
+
const targets = [];
|
|
24161
|
+
if (reconcileProject && reconcileProject.repo.kind !== "http" && !await usageReportDisabled(reconcileProject.repo.localPath)) {
|
|
24162
|
+
targets.push({
|
|
24163
|
+
repoPath: reconcileProject.repo.localPath,
|
|
24164
|
+
username: reconcileProject.username,
|
|
24165
|
+
opts: {
|
|
24166
|
+
skipTruncate: true,
|
|
24167
|
+
projectRoot: reconcileProject.projectRoot,
|
|
24168
|
+
// Non-HTTP repos route stats/votes to the teamai-reports orphan branch.
|
|
24169
|
+
selfConfig: reconcileProject
|
|
24170
|
+
}
|
|
24171
|
+
});
|
|
24172
|
+
}
|
|
24173
|
+
if (reconcileUser && reconcileUser.repo.kind !== "http" && !await usageReportDisabled(reconcileUser.repo.localPath)) {
|
|
24174
|
+
targets.push({
|
|
24175
|
+
repoPath: reconcileUser.repo.localPath,
|
|
24176
|
+
username: reconcileUser.username,
|
|
24177
|
+
opts: {
|
|
24178
|
+
skipTruncate: true,
|
|
24179
|
+
excludeProjectRoots: projectConfig?.projectRoot ? [projectConfig.projectRoot] : [],
|
|
24180
|
+
// Non-HTTP repos route stats/votes to the teamai-reports orphan branch —
|
|
24181
|
+
// never reset/pull the default branch (or, in self mode, the business tree).
|
|
24182
|
+
selfConfig: reconcileUser
|
|
24183
|
+
}
|
|
24184
|
+
});
|
|
24185
|
+
}
|
|
24186
|
+
const eventCount = (await readUsageEvents2()).length;
|
|
24187
|
+
let allReported = true;
|
|
24188
|
+
for (const t of targets) {
|
|
24189
|
+
try {
|
|
24190
|
+
const reported = await reportUsageToTeam2(t.repoPath, t.username, t.opts);
|
|
24191
|
+
if (!reported) allReported = false;
|
|
24192
|
+
} catch (e) {
|
|
24193
|
+
allReported = false;
|
|
24194
|
+
log.error(`Auto-report to ${t.repoPath} skipped: ${e.message}`);
|
|
23499
24195
|
}
|
|
23500
|
-
});
|
|
23501
|
-
}
|
|
23502
|
-
const eventCount = (await readUsageEvents2()).length;
|
|
23503
|
-
for (const t of targets) {
|
|
23504
|
-
try {
|
|
23505
|
-
await reportUsageToTeam2(t.repoPath, t.username, t.opts);
|
|
23506
|
-
} catch (e) {
|
|
23507
|
-
log.error(`Auto-report to ${t.repoPath} skipped: ${e.message}`);
|
|
23508
24196
|
}
|
|
24197
|
+
if (allReported && eventCount > 0 && targets.length > 0) {
|
|
24198
|
+
await truncateUsageAfterReport2(eventCount);
|
|
24199
|
+
}
|
|
24200
|
+
} catch (e) {
|
|
24201
|
+
log.debug(`Auto-report skipped: ${e.message}`);
|
|
23509
24202
|
}
|
|
23510
|
-
|
|
23511
|
-
|
|
23512
|
-
|
|
24203
|
+
})().finally(() => {
|
|
24204
|
+
pendingUsageReport = void 0;
|
|
24205
|
+
});
|
|
24206
|
+
usageReport = pendingUsageReport;
|
|
24207
|
+
try {
|
|
24208
|
+
await withTimeout(pendingUsageReport, 5e3, "Auto-report is still running after 5s");
|
|
23513
24209
|
} catch (e) {
|
|
23514
|
-
log.debug(
|
|
24210
|
+
log.debug(e.message);
|
|
23515
24211
|
}
|
|
23516
24212
|
}
|
|
23517
24213
|
const sourceConfig = reconcileProject ?? reconcileUser;
|
|
@@ -23524,8 +24220,15 @@ async function pull(options) {
|
|
|
23524
24220
|
}
|
|
23525
24221
|
}
|
|
23526
24222
|
} finally {
|
|
23527
|
-
|
|
23528
|
-
await releaseLock(lock);
|
|
24223
|
+
const releaseSyncLocks = async () => {
|
|
24224
|
+
for (const lock of heldLocks.values()) await releaseLock(lock);
|
|
24225
|
+
};
|
|
24226
|
+
if (usageReport && usageReport === pendingUsageReport) {
|
|
24227
|
+
void usageReport.then(releaseSyncLocks, releaseSyncLocks).catch((e) => {
|
|
24228
|
+
log.error(`Could not release report sync locks: ${e.message}`);
|
|
24229
|
+
});
|
|
24230
|
+
} else {
|
|
24231
|
+
await releaseSyncLocks();
|
|
23529
24232
|
}
|
|
23530
24233
|
}
|
|
23531
24234
|
}
|
|
@@ -23600,7 +24303,7 @@ async function reconcileCoAuthorAllScopes(userConfig, projectConfig, options) {
|
|
|
23600
24303
|
}
|
|
23601
24304
|
}
|
|
23602
24305
|
}
|
|
23603
|
-
var CONTRIBUTORS_FILE2;
|
|
24306
|
+
var pendingUsageReport, CONTRIBUTORS_FILE2;
|
|
23604
24307
|
var init_pull = __esm({
|
|
23605
24308
|
"src/pull.ts"() {
|
|
23606
24309
|
"use strict";
|
|
@@ -23613,14 +24316,15 @@ var init_pull = __esm({
|
|
|
23613
24316
|
init_resources();
|
|
23614
24317
|
init_base();
|
|
23615
24318
|
init_rule_format();
|
|
24319
|
+
init_agent_format();
|
|
23616
24320
|
init_tags();
|
|
23617
24321
|
init_builtin_skills();
|
|
23618
24322
|
init_types();
|
|
23619
|
-
|
|
23620
|
-
init_projects();
|
|
24323
|
+
init_resource_namespaces();
|
|
23621
24324
|
init_home();
|
|
23622
24325
|
init_update();
|
|
23623
24326
|
init_learnings_mirror();
|
|
24327
|
+
init_async();
|
|
23624
24328
|
CONTRIBUTORS_FILE2 = "CONTRIBUTORS";
|
|
23625
24329
|
}
|
|
23626
24330
|
});
|
|
@@ -24476,17 +25180,24 @@ async function doctor(options) {
|
|
|
24476
25180
|
log.info("Running diagnostics...\n");
|
|
24477
25181
|
const projectConfig = await detectProjectConfig();
|
|
24478
25182
|
const localConfig = projectConfig ?? await loadLocalConfig();
|
|
24479
|
-
|
|
24480
|
-
|
|
24481
|
-
|
|
24482
|
-
`);
|
|
24483
|
-
|
|
24484
|
-
|
|
24485
|
-
|
|
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;
|
|
24486
25190
|
}
|
|
24487
|
-
const
|
|
24488
|
-
const
|
|
24489
|
-
|
|
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;
|
|
24490
25201
|
const checks = [];
|
|
24491
25202
|
if (providerName === "tgit") {
|
|
24492
25203
|
const { isGfInstalled: isGfInstalled2, gfIsAuthenticated: gfIsAuthenticated2 } = await Promise.resolve().then(() => (init_tgit(), tgit_exports));
|
|
@@ -24532,23 +25243,14 @@ async function doctor(options) {
|
|
|
24532
25243
|
});
|
|
24533
25244
|
}
|
|
24534
25245
|
checks.push(
|
|
24535
|
-
{
|
|
24536
|
-
name: `Local config exists (${configPathLabel})`,
|
|
24537
|
-
check: async () => localConfig !== null,
|
|
24538
|
-
fix: "Run `teamai init` to initialize"
|
|
24539
|
-
},
|
|
24540
25246
|
{
|
|
24541
25247
|
name: "Team repo exists locally",
|
|
24542
|
-
check: async () =>
|
|
24543
|
-
if (!localConfig) return false;
|
|
24544
|
-
return pathExists(localConfig.repo.localPath);
|
|
24545
|
-
},
|
|
25248
|
+
check: async () => pathExists(localConfig.repo.localPath),
|
|
24546
25249
|
fix: "Run `teamai init` to clone the team repo"
|
|
24547
25250
|
},
|
|
24548
25251
|
{
|
|
24549
25252
|
name: "Team config (teamai.yaml) is valid",
|
|
24550
25253
|
check: async () => {
|
|
24551
|
-
if (!localConfig) return false;
|
|
24552
25254
|
const config = await loadTeamConfig(localConfig.repo.localPath);
|
|
24553
25255
|
return config !== null;
|
|
24554
25256
|
},
|
|
@@ -24559,7 +25261,6 @@ async function doctor(options) {
|
|
|
24559
25261
|
name: "Env variables injected in shell profile",
|
|
24560
25262
|
check: async () => {
|
|
24561
25263
|
if (teamConfig?.sharing?.env?.injectShellProfile === false) return true;
|
|
24562
|
-
if (!localConfig) return true;
|
|
24563
25264
|
const envYamlPath = path70.join(localConfig.repo.localPath, "env", "env.yaml");
|
|
24564
25265
|
if (!await pathExists(envYamlPath)) return true;
|
|
24565
25266
|
const home = getUserHome();
|
|
@@ -24588,13 +25289,11 @@ async function doctor(options) {
|
|
|
24588
25289
|
allPassed = false;
|
|
24589
25290
|
}
|
|
24590
25291
|
}
|
|
24591
|
-
|
|
24592
|
-
|
|
24593
|
-
|
|
24594
|
-
|
|
24595
|
-
|
|
24596
|
-
if (!packageReport.allPassed) allPassed = false;
|
|
24597
|
-
}
|
|
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;
|
|
24598
25297
|
}
|
|
24599
25298
|
if (await hasInstalledCodexHooks(toolPaths, baseDir)) {
|
|
24600
25299
|
console.log("");
|
|
@@ -24606,6 +25305,7 @@ async function doctor(options) {
|
|
|
24606
25305
|
} else {
|
|
24607
25306
|
log.warn("Some checks failed. See suggestions above.");
|
|
24608
25307
|
}
|
|
25308
|
+
return allPassed;
|
|
24609
25309
|
}
|
|
24610
25310
|
var init_doctor = __esm({
|
|
24611
25311
|
"src/doctor.ts"() {
|
|
@@ -24730,7 +25430,8 @@ async function rolesInit(options) {
|
|
|
24730
25430
|
description,
|
|
24731
25431
|
resources: {
|
|
24732
25432
|
knowledge: namespaces,
|
|
24733
|
-
skills: namespaces
|
|
25433
|
+
skills: namespaces,
|
|
25434
|
+
agents: namespaces
|
|
24734
25435
|
}
|
|
24735
25436
|
});
|
|
24736
25437
|
log.success(`Added role: ${id} (namespaces: ${namespaces.join(", ")})`);
|
|
@@ -24874,7 +25575,8 @@ async function rolesAdd(roleId, options) {
|
|
|
24874
25575
|
description: options.description ?? "",
|
|
24875
25576
|
resources: {
|
|
24876
25577
|
knowledge: namespaces,
|
|
24877
|
-
skills: namespaces
|
|
25578
|
+
skills: namespaces,
|
|
25579
|
+
agents: namespaces
|
|
24878
25580
|
}
|
|
24879
25581
|
};
|
|
24880
25582
|
const updatedManifest = {
|
|
@@ -24988,7 +25690,8 @@ async function rolesUpdate(roleId, options) {
|
|
|
24988
25690
|
description: hasDesc ? options.description : existingRole.description,
|
|
24989
25691
|
resources: {
|
|
24990
25692
|
knowledge: updatedNamespaces,
|
|
24991
|
-
skills: updatedNamespaces
|
|
25693
|
+
skills: updatedNamespaces,
|
|
25694
|
+
agents: updatedNamespaces
|
|
24992
25695
|
}
|
|
24993
25696
|
};
|
|
24994
25697
|
const updatedManifest = {
|
|
@@ -25120,21 +25823,22 @@ async function projectsSet(ids, _options) {
|
|
|
25120
25823
|
}
|
|
25121
25824
|
async function projectsMembers(projectId, _options) {
|
|
25122
25825
|
const { localConfig } = await autoDetectInit();
|
|
25123
|
-
|
|
25124
|
-
|
|
25826
|
+
const knowledgePath = localConfig.repo.localPath;
|
|
25827
|
+
let membersRoot = knowledgePath;
|
|
25828
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
25829
|
+
if (usesReportsBranch2(localConfig)) {
|
|
25125
25830
|
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
25126
|
-
await refreshReportsWorktree2(localConfig);
|
|
25127
|
-
|
|
25831
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
25832
|
+
membersRoot = await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
25128
25833
|
} else {
|
|
25129
|
-
|
|
25130
|
-
await pullRepo(repoPath).catch(() => {
|
|
25834
|
+
await pullRepo(knowledgePath).catch(() => {
|
|
25131
25835
|
});
|
|
25132
25836
|
}
|
|
25133
|
-
const manifest = await loadProjectsManifest(
|
|
25837
|
+
const manifest = await loadProjectsManifest(knowledgePath);
|
|
25134
25838
|
if (manifest && !listProjectIds(manifest).includes(projectId)) {
|
|
25135
25839
|
log.warn(`Project "${projectId}" is not defined in manifest/projects.yaml.`);
|
|
25136
25840
|
}
|
|
25137
|
-
const membersDir = path72.join(
|
|
25841
|
+
const membersDir = path72.join(membersRoot, "members");
|
|
25138
25842
|
const files = (await listFiles(membersDir)).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
25139
25843
|
const members = [];
|
|
25140
25844
|
for (const file of files) {
|
|
@@ -25410,10 +26114,13 @@ async function collectTeamRuleNames(repoPath) {
|
|
|
25410
26114
|
async function collectTeamAgentNames(repoPath) {
|
|
25411
26115
|
const teamAgentsDir = path74.join(repoPath, "agents");
|
|
25412
26116
|
if (!await pathExists(teamAgentsDir)) return /* @__PURE__ */ new Set();
|
|
25413
|
-
const
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
|
|
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;
|
|
25417
26124
|
}
|
|
25418
26125
|
function isEmptyHooksResidue(parsed) {
|
|
25419
26126
|
if (parsed == null || !("hooks" in parsed) || typeof parsed.hooks !== "object" || parsed.hooks == null) return false;
|
|
@@ -25513,8 +26220,8 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
25513
26220
|
const agentsDir = path74.join(baseDir, toolPath.agents);
|
|
25514
26221
|
if (await pathExists(agentsDir)) {
|
|
25515
26222
|
for (const file of await listFiles(agentsDir)) {
|
|
25516
|
-
|
|
25517
|
-
|
|
26223
|
+
const name = agentStemFromFilename(path74.basename(file));
|
|
26224
|
+
if (name === null) continue;
|
|
25518
26225
|
if (!teamAgentNames.has(name) && !BUILTIN_AGENT_NAMES.has(name)) continue;
|
|
25519
26226
|
res.agentFiles.push(path74.join(agentsDir, file));
|
|
25520
26227
|
}
|
|
@@ -25992,6 +26699,8 @@ var init_uninstall = __esm({
|
|
|
25992
26699
|
init_types();
|
|
25993
26700
|
init_builtin_rules();
|
|
25994
26701
|
init_rule_format();
|
|
26702
|
+
init_agent_format();
|
|
26703
|
+
init_agents();
|
|
25995
26704
|
init_builtin_agents();
|
|
25996
26705
|
init_builtin_skills();
|
|
25997
26706
|
init_fs();
|
|
@@ -26209,7 +26918,8 @@ async function hooksList(_options) {
|
|
|
26209
26918
|
for (const d of teamDefs) {
|
|
26210
26919
|
const matcher = d.matcher ? ` [${d.matcher}]` : "";
|
|
26211
26920
|
const tools = d.tools && d.tools.length > 0 ? d.tools.join(",") : "all";
|
|
26212
|
-
|
|
26921
|
+
const roles = d.roles ? `, roles: ${d.roles.length > 0 ? d.roles.join(",") : "nobody"}` : "";
|
|
26922
|
+
console.log(` [${d.key}] ${d.event}${matcher} \u2192 ${d.command} (tools: ${tools}${roles})`);
|
|
26213
26923
|
}
|
|
26214
26924
|
}
|
|
26215
26925
|
console.log("");
|
|
@@ -26269,6 +26979,7 @@ async function mcpList(_options) {
|
|
|
26269
26979
|
console.log(` ${s.name} [${s.transport}]`);
|
|
26270
26980
|
if (s.description) console.log(` ${s.description}`);
|
|
26271
26981
|
console.log(` endpoint: ${endpoint}`);
|
|
26982
|
+
if (s.roles) console.log(` roles: ${s.roles.length > 0 ? s.roles.join(", ") : "nobody"}`);
|
|
26272
26983
|
const needed = referencedVars(s);
|
|
26273
26984
|
if (needed.length > 0) {
|
|
26274
26985
|
const missing = needed.filter((v) => !vars[v]);
|
|
@@ -26539,23 +27250,28 @@ async function saveSession(options) {
|
|
|
26539
27250
|
log.info(`[dry-run] Would push session summary to sessions/${username}/${monthKey(summary)}.md`);
|
|
26540
27251
|
return;
|
|
26541
27252
|
}
|
|
26542
|
-
if (localConfig
|
|
27253
|
+
if (usesReportsBranch(localConfig)) {
|
|
26543
27254
|
const spin2 = spinner("Pushing session summary to team...").start();
|
|
26544
27255
|
try {
|
|
26545
|
-
const {
|
|
26546
|
-
|
|
26547
|
-
|
|
26548
|
-
const written = await appendMonthlyLog(teamDir2, summary, { includePrompt: options.includePrompt });
|
|
26549
|
-
if (!written) {
|
|
26550
|
-
spin2.info("Session already present in the team log \u2014 nothing to push.");
|
|
26551
|
-
return;
|
|
26552
|
-
}
|
|
26553
|
-
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;
|
|
26554
27259
|
const pushed = await withTimeout(
|
|
26555
|
-
|
|
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
|
+
}),
|
|
26556
27268
|
1e4,
|
|
26557
27269
|
"Push timeout (10s)"
|
|
26558
27270
|
);
|
|
27271
|
+
if (ran && !rel) {
|
|
27272
|
+
spin2.info("Session already present in the team log \u2014 nothing to push.");
|
|
27273
|
+
return;
|
|
27274
|
+
}
|
|
26559
27275
|
if (pushed) spin2.succeed(`Pushed: ${rel}`);
|
|
26560
27276
|
else spin2.info("Nothing new to push.");
|
|
26561
27277
|
} catch (e) {
|
|
@@ -28670,9 +29386,10 @@ async function resolveVizRoot(opts) {
|
|
|
28670
29386
|
}
|
|
28671
29387
|
const config = await detectProjectConfig() ?? await loadLocalConfig();
|
|
28672
29388
|
if (config?.repo?.localPath) {
|
|
28673
|
-
|
|
29389
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
29390
|
+
if (usesReportsBranch2(config)) {
|
|
28674
29391
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
28675
|
-
await ensureReportsWorktree2(config);
|
|
29392
|
+
await ensureReportsWorktree2(config, { pushIfCreated: false });
|
|
28676
29393
|
}
|
|
28677
29394
|
const knowledgeRoot = getKnowledgeDir(config);
|
|
28678
29395
|
const reportsRoot = getReportsDir(config);
|
|
@@ -30439,6 +31156,17 @@ var init_mr_hint = __esm({
|
|
|
30439
31156
|
|
|
30440
31157
|
// src/hook-handlers.ts
|
|
30441
31158
|
import path92 from "path";
|
|
31159
|
+
async function teamCorrectionKeywords(stdin) {
|
|
31160
|
+
if (typeof stdin.prompt !== "string") return [];
|
|
31161
|
+
try {
|
|
31162
|
+
const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
31163
|
+
const { getInterventionSharing: getInterventionSharing2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31164
|
+
const { teamConfig } = await autoDetectInit2();
|
|
31165
|
+
return getInterventionSharing2(teamConfig).correctionKeywords;
|
|
31166
|
+
} catch {
|
|
31167
|
+
return [];
|
|
31168
|
+
}
|
|
31169
|
+
}
|
|
30442
31170
|
async function contributeHintAllowed() {
|
|
30443
31171
|
const { isContributeHintEnabled: isContributeHintEnabled2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
30444
31172
|
try {
|
|
@@ -30539,7 +31267,7 @@ var init_hook_handlers = __esm({
|
|
|
30539
31267
|
async execute(stdin, tool) {
|
|
30540
31268
|
const { parseHookEvent: parseHookEvent2, appendEvent: appendEvent2, compactEvents: compactEvents2 } = await Promise.resolve().then(() => (init_dashboard_collector(), dashboard_collector_exports));
|
|
30541
31269
|
const raw = JSON.stringify(stdin);
|
|
30542
|
-
const event = await parseHookEvent2(raw, tool);
|
|
31270
|
+
const event = await parseHookEvent2(raw, tool, { correctionKeywords: await teamCorrectionKeywords(stdin) });
|
|
30543
31271
|
if (event) {
|
|
30544
31272
|
await appendEvent2(event);
|
|
30545
31273
|
compactEvents2().catch(() => {
|
|
@@ -30664,13 +31392,16 @@ var init_hook_handlers = __esm({
|
|
|
30664
31392
|
if (verifiedDocIds.length > 0) {
|
|
30665
31393
|
await incrementUpvoted2(votePath, verifiedDocIds);
|
|
30666
31394
|
}
|
|
30667
|
-
|
|
31395
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31396
|
+
if (usesReportsBranch2(localConfig)) {
|
|
30668
31397
|
try {
|
|
30669
|
-
const {
|
|
30670
|
-
|
|
30671
|
-
|
|
30672
|
-
|
|
30673
|
-
|
|
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);
|
|
30674
31405
|
}
|
|
30675
31406
|
} catch {
|
|
30676
31407
|
}
|
|
@@ -30767,7 +31498,8 @@ var init_hook_handlers = __esm({
|
|
|
30767
31498
|
// src/hook-dispatch-cli.ts
|
|
30768
31499
|
var hook_dispatch_cli_exports = {};
|
|
30769
31500
|
__export(hook_dispatch_cli_exports, {
|
|
30770
|
-
hookDispatchCli: () => hookDispatchCli
|
|
31501
|
+
hookDispatchCli: () => hookDispatchCli,
|
|
31502
|
+
parseStdin: () => parseStdin
|
|
30771
31503
|
});
|
|
30772
31504
|
import { spawn as spawn3 } from "child_process";
|
|
30773
31505
|
async function readStdin4() {
|
|
@@ -30828,10 +31560,15 @@ function parseStdin(raw, event) {
|
|
|
30828
31560
|
try {
|
|
30829
31561
|
stdin = JSON.parse(raw);
|
|
30830
31562
|
} catch {
|
|
30831
|
-
|
|
30832
|
-
|
|
31563
|
+
const preview = raw.length > 160 ? `${raw.slice(0, 80)}...${raw.slice(-80)}` : raw;
|
|
31564
|
+
log.debug(
|
|
31565
|
+
`hook-dispatch: failed to parse STDIN JSON for event=${event} (len=${raw.length}, body=${JSON.stringify(preview)})`
|
|
31566
|
+
);
|
|
30833
31567
|
}
|
|
30834
31568
|
}
|
|
31569
|
+
if (!stdin || typeof stdin !== "object" || Array.isArray(stdin)) {
|
|
31570
|
+
stdin = {};
|
|
31571
|
+
}
|
|
30835
31572
|
if (!stdin.hook_event_name) {
|
|
30836
31573
|
const EVENT_MAP2 = {
|
|
30837
31574
|
"session-start": "SessionStart",
|
|
@@ -30857,7 +31594,6 @@ async function hookDispatchCli(event, tool, matcher, bgOnly = false) {
|
|
|
30857
31594
|
try {
|
|
30858
31595
|
const raw = await readStdin4();
|
|
30859
31596
|
const stdin = parseStdin(raw, event);
|
|
30860
|
-
if (stdin === null) return;
|
|
30861
31597
|
const { loadLocalConfig: loadLocalConfig4, detectProjectConfig: detectProjectConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
30862
31598
|
const cwd = resolveHookCwd(stdin);
|
|
30863
31599
|
if (cwd) {
|
|
@@ -30925,7 +31661,8 @@ async function rebuildIndexAfterContribute(localConfig) {
|
|
|
30925
31661
|
const docsRepoDir = path93.join(repoPath, "docs");
|
|
30926
31662
|
const rulesRepoDir = path93.join(repoPath, "rules");
|
|
30927
31663
|
const skillsRepoDir = path93.join(repoPath, "skills");
|
|
30928
|
-
const
|
|
31664
|
+
const { getReportsDir: getReportsDir2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31665
|
+
const votesDir = path93.join(getReportsDir2(localConfig), "votes");
|
|
30929
31666
|
let effectiveLearningsDir;
|
|
30930
31667
|
const activeLearningsNamespaces = await resolveActiveLearningsNamespaces(
|
|
30931
31668
|
repoPath,
|
|
@@ -31075,7 +31812,7 @@ async function contributeSelf(localConfig, content, options) {
|
|
|
31075
31812
|
let votesDir;
|
|
31076
31813
|
try {
|
|
31077
31814
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
31078
|
-
const candidate = path93.join(await ensureReportsWorktree2(localConfig), "votes");
|
|
31815
|
+
const candidate = path93.join(await ensureReportsWorktree2(localConfig, { pushIfCreated: false }), "votes");
|
|
31079
31816
|
if (await pathExists3(candidate)) votesDir = candidate;
|
|
31080
31817
|
} catch {
|
|
31081
31818
|
}
|
|
@@ -34384,7 +35121,7 @@ var init_codebase = __esm({
|
|
|
34384
35121
|
|
|
34385
35122
|
// src/wiki-engine/code-knowledge/code-collector.ts
|
|
34386
35123
|
import { createHash as createHash5 } from "crypto";
|
|
34387
|
-
import { execFile as
|
|
35124
|
+
import { execFile as execFile3 } from "child_process";
|
|
34388
35125
|
import { readFile as readFile6, readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
34389
35126
|
import path105 from "path";
|
|
34390
35127
|
import { promisify as promisify3 } from "util";
|
|
@@ -34540,7 +35277,7 @@ var init_code_collector = __esm({
|
|
|
34540
35277
|
"src/wiki-engine/code-knowledge/code-collector.ts"() {
|
|
34541
35278
|
"use strict";
|
|
34542
35279
|
init_wiki_protocol();
|
|
34543
|
-
execFileAsync3 = promisify3(
|
|
35280
|
+
execFileAsync3 = promisify3(execFile3);
|
|
34544
35281
|
KEY_FILE_PATTERNS = {
|
|
34545
35282
|
go: [/main\.go$/, /cmd\/.*\.go$/, /handler.*\.go$/, /server\.go$/, /router\.go$/],
|
|
34546
35283
|
python: [/main\.py$/, /app\.py$/, /server\.py$/, /routes?\.py$/, /models?\.py$/],
|
|
@@ -42412,7 +43149,7 @@ var init_extract_mr = __esm({
|
|
|
42412
43149
|
// src/maintenance/paths.ts
|
|
42413
43150
|
import path130 from "path";
|
|
42414
43151
|
async function resolveMaintenancePaths(localConfig) {
|
|
42415
|
-
if (
|
|
43152
|
+
if (usesReportsBranch(localConfig)) {
|
|
42416
43153
|
const { refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
42417
43154
|
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
42418
43155
|
}
|
|
@@ -42509,7 +43246,7 @@ program.name("teamai").description("TeamAI \u2014 Make Every Team AI Native").ve
|
|
|
42509
43246
|
process.exit(1);
|
|
42510
43247
|
}
|
|
42511
43248
|
});
|
|
42512
|
-
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) => {
|
|
42513
43250
|
const globalOpts = program.opts();
|
|
42514
43251
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
42515
43252
|
await init2({ ...globalOpts, ...cmdOpts, repoPositional: repoArg });
|
|
@@ -42589,7 +43326,8 @@ registerPackagesCommand(program);
|
|
|
42589
43326
|
program.command("doctor").description("Diagnose configuration issues").action(async () => {
|
|
42590
43327
|
const globalOpts = program.opts();
|
|
42591
43328
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
42592
|
-
await doctor2(globalOpts);
|
|
43329
|
+
const allPassed = await doctor2(globalOpts);
|
|
43330
|
+
if (!allPassed) process.exitCode = 1;
|
|
42593
43331
|
});
|
|
42594
43332
|
var rolesCmd = program.command("roles").description("Manage team roles and resource namespaces").action(async () => {
|
|
42595
43333
|
const globalOpts = program.opts();
|
|
@@ -42890,7 +43628,7 @@ program.command("todowrite-hint", { hidden: true }).description("Remind the agen
|
|
|
42890
43628
|
await todoWriteHint2();
|
|
42891
43629
|
}
|
|
42892
43630
|
});
|
|
42893
|
-
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) => {
|
|
42894
43632
|
const globalOpts = program.opts();
|
|
42895
43633
|
if (cmdOpts.cacheStatus || cmdOpts.cacheGc) {
|
|
42896
43634
|
const { cacheCmd: cacheCmd2 } = await Promise.resolve().then(() => (init_cache_cmd(), cache_cmd_exports));
|