teamai-cli 0.24.0 → 0.25.0-beta.0
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 +5 -0
- package/README.md +7 -4
- package/README.zh-CN.md +7 -4
- package/dist/index.js +928 -484
- 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,174 @@ 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
|
+
};
|
|
1349
|
+
for (const type of ROLE_RESOURCE_TYPES) {
|
|
1350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1351
|
+
for (const role of resolvedRoles) {
|
|
1352
|
+
for (const namespace of role.resources[type]) {
|
|
1353
|
+
if (seen.has(namespace)) continue;
|
|
1354
|
+
seen.add(namespace);
|
|
1355
|
+
namespaces[type].push(namespace);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return namespaces;
|
|
1360
|
+
}
|
|
1361
|
+
function activeRoleIds(localConfig) {
|
|
1362
|
+
if (!localConfig.primaryRole) return null;
|
|
1363
|
+
return [.../* @__PURE__ */ new Set([localConfig.primaryRole, ...localConfig.additionalRoles ?? []])];
|
|
1364
|
+
}
|
|
1365
|
+
function matchesRoles(entryRoles, active) {
|
|
1366
|
+
if (!entryRoles || active == null) return true;
|
|
1367
|
+
return entryRoles.some((role) => active.includes(role));
|
|
1368
|
+
}
|
|
1369
|
+
async function warnUnknownRoleIds(repoPath, file, entries) {
|
|
1370
|
+
if (!entries.some((entry) => entry.roles && entry.roles.length > 0)) return;
|
|
1371
|
+
let known;
|
|
1372
|
+
try {
|
|
1373
|
+
known = new Set(listRoleIds(await loadRolesManifest(repoPath)));
|
|
1374
|
+
} catch {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
for (const entry of entries) {
|
|
1378
|
+
for (const role of entry.roles ?? []) {
|
|
1379
|
+
if (known.has(role) || reportedUnknownRoles.has(`${file}:${role}`)) continue;
|
|
1380
|
+
reportedUnknownRoles.add(`${file}:${role}`);
|
|
1381
|
+
log.warn(`roles: unknown role id "${role}" in ${file} ${entry.kind} "${entry.name}". Valid roles: ${[...known].join(", ")}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
var ROLE_RESOURCE_TYPES, RoleResourceNamespacesSchema, RoleSchema, RolesManifestSchema, reportedUnknownRoles;
|
|
1386
|
+
var init_roles = __esm({
|
|
1387
|
+
"src/roles.ts"() {
|
|
1388
|
+
"use strict";
|
|
1389
|
+
init_fs();
|
|
1390
|
+
init_logger();
|
|
1391
|
+
ROLE_RESOURCE_TYPES = ["knowledge", "skills"];
|
|
1392
|
+
RoleResourceNamespacesSchema = z2.object({
|
|
1393
|
+
knowledge: z2.array(z2.string().min(1)),
|
|
1394
|
+
skills: z2.array(z2.string().min(1)),
|
|
1395
|
+
// learnings is accepted for backward compatibility but ignored at runtime.
|
|
1396
|
+
// All learnings are shared flat across the entire team (no namespace isolation).
|
|
1397
|
+
learnings: z2.array(z2.string()).optional()
|
|
1398
|
+
});
|
|
1399
|
+
RoleSchema = z2.object({
|
|
1400
|
+
id: z2.string().min(1),
|
|
1401
|
+
description: z2.string().default(""),
|
|
1402
|
+
resources: RoleResourceNamespacesSchema
|
|
1403
|
+
});
|
|
1404
|
+
RolesManifestSchema = z2.object({
|
|
1405
|
+
version: z2.number(),
|
|
1406
|
+
roles: z2.array(RoleSchema).min(1),
|
|
1407
|
+
// defaults.shareTarget was removed: learnings are flat, no namespace routing needed.
|
|
1408
|
+
// Old manifests with a defaults block are still parseable (z.passthrough on object level).
|
|
1409
|
+
defaults: z2.object({}).passthrough().optional()
|
|
1410
|
+
});
|
|
1411
|
+
reportedUnknownRoles = /* @__PURE__ */ new Set();
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1215
1415
|
// src/bundled-runtime.ts
|
|
1216
1416
|
import fs2 from "fs";
|
|
1217
|
-
import
|
|
1417
|
+
import path5 from "path";
|
|
1218
1418
|
function compareSemver(a, b) {
|
|
1219
1419
|
const aParts = a.split(".").map((s) => parseInt(s, 10) || 0);
|
|
1220
1420
|
const bParts = b.split(".").map((s) => parseInt(s, 10) || 0);
|
|
@@ -1230,18 +1430,18 @@ function pickLatestVersion(versions) {
|
|
|
1230
1430
|
return versions.reduce((best, v) => compareSemver(v, best) > 0 ? v : best, versions[0]);
|
|
1231
1431
|
}
|
|
1232
1432
|
function latestVersionDir(relDir) {
|
|
1233
|
-
const versionsDir =
|
|
1433
|
+
const versionsDir = path5.join(getUserHome(), relDir);
|
|
1234
1434
|
try {
|
|
1235
1435
|
const versions = fs2.readdirSync(versionsDir).filter((d) => !d.startsWith("."));
|
|
1236
1436
|
const latest = pickLatestVersion(versions);
|
|
1237
|
-
return latest ?
|
|
1437
|
+
return latest ? path5.join(versionsDir, latest) : null;
|
|
1238
1438
|
} catch {
|
|
1239
1439
|
return null;
|
|
1240
1440
|
}
|
|
1241
1441
|
}
|
|
1242
1442
|
function resolveWorkbuddyNode() {
|
|
1243
1443
|
const dir = latestVersionDir(WORKBUDDY_BUNDLED_NODE_DIR);
|
|
1244
|
-
const nodeBin = dir &&
|
|
1444
|
+
const nodeBin = dir && path5.join(dir, "bin", "node");
|
|
1245
1445
|
return nodeBin && fs2.existsSync(nodeBin) ? nodeBin : null;
|
|
1246
1446
|
}
|
|
1247
1447
|
function resolveCodebuddyNode() {
|
|
@@ -1251,10 +1451,10 @@ function resolveCodebuddyNode() {
|
|
|
1251
1451
|
for (const entry of entries) {
|
|
1252
1452
|
if (!entry.startsWith(".codebuddy-server")) continue;
|
|
1253
1453
|
try {
|
|
1254
|
-
const binDir =
|
|
1454
|
+
const binDir = path5.join(home, entry, "bin");
|
|
1255
1455
|
const stableDirs = fs2.readdirSync(binDir).filter((d) => d.startsWith("stable-"));
|
|
1256
1456
|
for (const stable of stableDirs) {
|
|
1257
|
-
const nodeBin =
|
|
1457
|
+
const nodeBin = path5.join(binDir, stable, "node");
|
|
1258
1458
|
if (fs2.existsSync(nodeBin)) return nodeBin;
|
|
1259
1459
|
}
|
|
1260
1460
|
} catch {
|
|
@@ -1270,8 +1470,8 @@ function resolveWorkbuddyShell() {
|
|
|
1270
1470
|
if (process.platform === "win32") {
|
|
1271
1471
|
const dir = latestVersionDir(WORKBUDDY_PORTABLE_GIT_DIR);
|
|
1272
1472
|
if (dir) {
|
|
1273
|
-
for (const rel of ["bin",
|
|
1274
|
-
const shBin =
|
|
1473
|
+
for (const rel of ["bin", path5.join("usr", "bin")]) {
|
|
1474
|
+
const shBin = path5.join(dir, rel, "sh.exe");
|
|
1275
1475
|
if (fs2.existsSync(shBin)) {
|
|
1276
1476
|
_wbShellCache = shBin;
|
|
1277
1477
|
break;
|
|
@@ -1301,7 +1501,7 @@ var init_bundled_runtime = __esm({
|
|
|
1301
1501
|
|
|
1302
1502
|
// src/builtin-hooks.ts
|
|
1303
1503
|
import fs3 from "fs";
|
|
1304
|
-
import
|
|
1504
|
+
import path6 from "path";
|
|
1305
1505
|
import { fileURLToPath } from "url";
|
|
1306
1506
|
function hasShell() {
|
|
1307
1507
|
if (_hasShellCache === void 0) {
|
|
@@ -1316,8 +1516,8 @@ function hasShell() {
|
|
|
1316
1516
|
function resolveTeamaiEntryScript() {
|
|
1317
1517
|
try {
|
|
1318
1518
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1319
|
-
const distDir =
|
|
1320
|
-
const candidate =
|
|
1519
|
+
const distDir = path6.dirname(thisFile);
|
|
1520
|
+
const candidate = path6.join(distDir, "index.js");
|
|
1321
1521
|
if (fs3.existsSync(candidate)) return candidate;
|
|
1322
1522
|
} catch {
|
|
1323
1523
|
}
|
|
@@ -1328,8 +1528,8 @@ function ensureTeamaiWrapper() {
|
|
|
1328
1528
|
if (!entryScript) return null;
|
|
1329
1529
|
const nodeBin = resolveWorkbuddyNode() ?? resolveCodebuddyNode() ?? process.argv[0];
|
|
1330
1530
|
const home = getUserHome();
|
|
1331
|
-
const binDir =
|
|
1332
|
-
const wrapperPath =
|
|
1531
|
+
const binDir = path6.join(home, TEAMAI_BIN_DIR);
|
|
1532
|
+
const wrapperPath = path6.join(binDir, WRAPPER_NAME);
|
|
1333
1533
|
const script = [
|
|
1334
1534
|
"#!/bin/sh",
|
|
1335
1535
|
`# Auto-generated by teamai \u2014 do not edit.`,
|
|
@@ -1383,7 +1583,7 @@ function getWrapperDispatchCommand(event, tool, matcher) {
|
|
|
1383
1583
|
return `PATH="$HOME/${TEAMAI_BIN_DIR}:$PATH" teamai hook-dispatch ${event} --tool ${tool}${matcherArg} 2>/dev/null || true`;
|
|
1384
1584
|
}
|
|
1385
1585
|
function builtinHookDefs(tool) {
|
|
1386
|
-
const withTimeout3 = tool === "cursor" || tool === "workbuddy" || tool === "codebuddy"
|
|
1586
|
+
const withTimeout3 = tool === "cursor" || tool === "workbuddy" || tool === "codebuddy";
|
|
1387
1587
|
const buildCommand = tool === "zcode" ? getRawDispatchCommand : WRAPPER_TOOLS.has(tool) ? getWrapperDispatchCommand : getDispatchCommand;
|
|
1388
1588
|
return BUILTIN_HOOK_SPECS.map((spec) => ({
|
|
1389
1589
|
source: "builtin",
|
|
@@ -1427,7 +1627,7 @@ var init_builtin_hooks = __esm({
|
|
|
1427
1627
|
});
|
|
1428
1628
|
|
|
1429
1629
|
// src/resources/base.ts
|
|
1430
|
-
import
|
|
1630
|
+
import path7 from "path";
|
|
1431
1631
|
function toolInstallRoot(toolPath) {
|
|
1432
1632
|
const segments = toolPath.split("/");
|
|
1433
1633
|
if (segments[0] === ".config" && segments.length > 1) {
|
|
@@ -1451,7 +1651,7 @@ var init_base = __esm({
|
|
|
1451
1651
|
*/
|
|
1452
1652
|
static async isToolInstalled(toolPath, baseDir) {
|
|
1453
1653
|
const base = baseDir ?? getUserHome();
|
|
1454
|
-
const toolRoot =
|
|
1654
|
+
const toolRoot = path7.join(base, toolInstallRoot(toolPath));
|
|
1455
1655
|
return pathExists(toolRoot);
|
|
1456
1656
|
}
|
|
1457
1657
|
/**
|
|
@@ -1459,7 +1659,7 @@ var init_base = __esm({
|
|
|
1459
1659
|
* Returns a Set of resource names that have been explicitly deleted.
|
|
1460
1660
|
*/
|
|
1461
1661
|
async readTombstones(localConfig) {
|
|
1462
|
-
const tombstonePath =
|
|
1662
|
+
const tombstonePath = path7.join(localConfig.repo.localPath, this.type, TOMBSTONE_FILE);
|
|
1463
1663
|
const content = await readFileSafe(tombstonePath);
|
|
1464
1664
|
if (!content) return /* @__PURE__ */ new Set();
|
|
1465
1665
|
return new Set(
|
|
@@ -1470,9 +1670,9 @@ var init_base = __esm({
|
|
|
1470
1670
|
* Append a resource name to the tombstone file, deduplicating and sorting.
|
|
1471
1671
|
*/
|
|
1472
1672
|
async addTombstone(name, localConfig) {
|
|
1473
|
-
const dir =
|
|
1673
|
+
const dir = path7.join(localConfig.repo.localPath, this.type);
|
|
1474
1674
|
await ensureDir(dir);
|
|
1475
|
-
const tombstonePath =
|
|
1675
|
+
const tombstonePath = path7.join(dir, TOMBSTONE_FILE);
|
|
1476
1676
|
const existing = await this.readTombstones(localConfig);
|
|
1477
1677
|
existing.add(name);
|
|
1478
1678
|
const sorted = [...existing].sort();
|
|
@@ -1495,17 +1695,17 @@ var init_base = __esm({
|
|
|
1495
1695
|
});
|
|
1496
1696
|
|
|
1497
1697
|
// src/resources/hooks.ts
|
|
1498
|
-
import
|
|
1499
|
-
import { z as
|
|
1500
|
-
import
|
|
1698
|
+
import path8 from "path";
|
|
1699
|
+
import { z as z3 } from "zod";
|
|
1700
|
+
import YAML2 from "yaml";
|
|
1501
1701
|
function teamHooksYamlPath(repoPath) {
|
|
1502
|
-
return
|
|
1702
|
+
return path8.join(repoPath, "hooks", "hooks.yaml");
|
|
1503
1703
|
}
|
|
1504
1704
|
async function parseHooksYaml(repoPath) {
|
|
1505
1705
|
const content = await readFileSafe(teamHooksYamlPath(repoPath));
|
|
1506
1706
|
if (!content) return null;
|
|
1507
1707
|
try {
|
|
1508
|
-
return HooksYamlSchema.parse(
|
|
1708
|
+
return HooksYamlSchema.parse(YAML2.parse(content));
|
|
1509
1709
|
} catch (e) {
|
|
1510
1710
|
log.warn(`Invalid hooks.yaml format: ${e.message} \u2014 skipping team hooks this run`);
|
|
1511
1711
|
return null;
|
|
@@ -1520,7 +1720,8 @@ function teamHookToDef(h) {
|
|
|
1520
1720
|
command: h.command,
|
|
1521
1721
|
timeout: h.timeout,
|
|
1522
1722
|
description: `${TEAMAI_CUSTOM_HOOK_PREFIX}${h.id}] ${h.description}`,
|
|
1523
|
-
tools: h.tools
|
|
1723
|
+
tools: h.tools,
|
|
1724
|
+
roles: h.roles
|
|
1524
1725
|
};
|
|
1525
1726
|
}
|
|
1526
1727
|
async function parseTeamHooks(repoPath) {
|
|
@@ -1544,6 +1745,8 @@ async function resolveTeamHooks(teamConfig, repoPath, opts = {}) {
|
|
|
1544
1745
|
if (defs.length > 0) log.warn(`Team hooks disabled (TEAMAI_HOOKS_DISABLED) \u2014 skipping ${defs.length} team hook(s)`);
|
|
1545
1746
|
return { defs: [], builtin };
|
|
1546
1747
|
}
|
|
1748
|
+
await warnUnknownRoleIds(repoPath, "hooks.yaml", defs.map((d) => ({ kind: "hook", name: d.key, roles: d.roles })));
|
|
1749
|
+
defs = defs.filter((d) => matchesRoles(d.roles, opts.activeRoles));
|
|
1547
1750
|
if (sharing.requireTeamScripts) {
|
|
1548
1751
|
const before = defs.length;
|
|
1549
1752
|
defs = defs.filter((d) => isTeamScriptCommand(d.command));
|
|
@@ -1570,28 +1773,31 @@ var init_hooks = __esm({
|
|
|
1570
1773
|
init_types();
|
|
1571
1774
|
init_fs();
|
|
1572
1775
|
init_logger();
|
|
1573
|
-
|
|
1776
|
+
init_roles();
|
|
1777
|
+
TeamHookSchema = z3.object({
|
|
1574
1778
|
/** Unique id (marker + manifest index). */
|
|
1575
|
-
id:
|
|
1779
|
+
id: z3.string().regex(/^[a-z0-9-]+$/),
|
|
1576
1780
|
/** Written into the hook description. */
|
|
1577
|
-
description:
|
|
1781
|
+
description: z3.string(),
|
|
1578
1782
|
/** Claude PascalCase event name. */
|
|
1579
|
-
event:
|
|
1783
|
+
event: z3.string().min(1),
|
|
1580
1784
|
/** Optional tool matcher (e.g. "Bash"). */
|
|
1581
|
-
matcher:
|
|
1785
|
+
matcher: z3.string().optional(),
|
|
1582
1786
|
/** Shell command to run. */
|
|
1583
|
-
command:
|
|
1787
|
+
command: z3.string().min(1),
|
|
1584
1788
|
/** Optional per-hook timeout in seconds. */
|
|
1585
|
-
timeout:
|
|
1789
|
+
timeout: z3.number().optional(),
|
|
1586
1790
|
/** Optional restriction to specific tools (default = all hook-capable tools). */
|
|
1587
|
-
tools:
|
|
1791
|
+
tools: z3.array(z3.string()).optional(),
|
|
1792
|
+
/** Optional restriction to members holding one of these role ids (default = every member). */
|
|
1793
|
+
roles: z3.array(z3.string()).optional()
|
|
1588
1794
|
});
|
|
1589
|
-
BuiltinOverrideSchema =
|
|
1590
|
-
disabled:
|
|
1591
|
-
overrides:
|
|
1795
|
+
BuiltinOverrideSchema = z3.object({
|
|
1796
|
+
disabled: z3.array(z3.string()).default([]),
|
|
1797
|
+
overrides: z3.record(z3.string(), z3.object({ timeout: z3.number().optional() })).default({})
|
|
1592
1798
|
}).default({ disabled: [], overrides: {} });
|
|
1593
|
-
HooksYamlSchema =
|
|
1594
|
-
hooks:
|
|
1799
|
+
HooksYamlSchema = z3.object({
|
|
1800
|
+
hooks: z3.array(TeamHookSchema).default([]),
|
|
1595
1801
|
builtin: BuiltinOverrideSchema
|
|
1596
1802
|
});
|
|
1597
1803
|
HooksHandler = class extends ResourceHandler {
|
|
@@ -1635,10 +1841,10 @@ __export(opencode_hooks_exports, {
|
|
|
1635
1841
|
removeOpencodeHooks: () => removeOpencodeHooks,
|
|
1636
1842
|
resolveOpencodePluginDir: () => resolveOpencodePluginDir
|
|
1637
1843
|
});
|
|
1638
|
-
import
|
|
1844
|
+
import path9 from "path";
|
|
1639
1845
|
function resolveOpencodePluginDir(baseDir, scope) {
|
|
1640
|
-
const configDir = scope === "project" ? ".opencode" :
|
|
1641
|
-
return
|
|
1846
|
+
const configDir = scope === "project" ? ".opencode" : path9.join(".config", "opencode");
|
|
1847
|
+
return path9.join(baseDir, configDir, OPENCODE_PLUGIN_DIR);
|
|
1642
1848
|
}
|
|
1643
1849
|
function buildPluginSource() {
|
|
1644
1850
|
return `// ${TEAMAI_MARKER} hooks plugin \u2014 generated by teamai, do not edit by hand.
|
|
@@ -1720,20 +1926,20 @@ export const TeamaiHooks = async ({ $, directory, worktree }) => {
|
|
|
1720
1926
|
async function injectOpencodeHooks(baseDir, scope) {
|
|
1721
1927
|
const dir = resolveOpencodePluginDir(baseDir, scope);
|
|
1722
1928
|
await ensureDir(dir);
|
|
1723
|
-
const file =
|
|
1929
|
+
const file = path9.join(dir, OPENCODE_HOOK_FILE);
|
|
1724
1930
|
await writeFile(file, buildPluginSource());
|
|
1725
1931
|
log.success(`Injected teamai OpenCode hook into ${file}`);
|
|
1726
1932
|
}
|
|
1727
1933
|
async function removeOpencodeHooks(baseDir, scope) {
|
|
1728
1934
|
const dir = resolveOpencodePluginDir(baseDir, scope);
|
|
1729
|
-
const file =
|
|
1935
|
+
const file = path9.join(dir, OPENCODE_HOOK_FILE);
|
|
1730
1936
|
if (await pathExists(file)) {
|
|
1731
1937
|
await remove(file);
|
|
1732
1938
|
log.success(`Removed teamai OpenCode hook from ${file}`);
|
|
1733
1939
|
}
|
|
1734
1940
|
}
|
|
1735
1941
|
function assertSafeSlug(slug) {
|
|
1736
|
-
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..") ||
|
|
1942
|
+
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..") || path9.isAbsolute(slug)) {
|
|
1737
1943
|
throw new Error(`Invalid agent-hook slug: ${slug}`);
|
|
1738
1944
|
}
|
|
1739
1945
|
}
|
|
@@ -1785,14 +1991,14 @@ async function applyOpencodeAgentHook(def) {
|
|
|
1785
1991
|
}
|
|
1786
1992
|
const dir = resolveOpencodePluginDir(def.baseDir, def.scope);
|
|
1787
1993
|
await ensureDir(dir);
|
|
1788
|
-
const file =
|
|
1994
|
+
const file = path9.join(dir, `teamai-agent-${def.slug}.ts`);
|
|
1789
1995
|
await writeFile(file, buildAgentHookPluginSource(def.slug, ocEvent, def.command, def.matcher));
|
|
1790
1996
|
log.success(`Installed OpenCode agent hook [${def.slug}] in ${file}`);
|
|
1791
1997
|
}
|
|
1792
1998
|
async function removeOpencodeAgentHook(opts) {
|
|
1793
1999
|
assertSafeSlug(opts.slug);
|
|
1794
2000
|
const dir = resolveOpencodePluginDir(opts.baseDir, opts.scope);
|
|
1795
|
-
const file =
|
|
2001
|
+
const file = path9.join(dir, `teamai-agent-${opts.slug}.ts`);
|
|
1796
2002
|
if (await pathExists(file)) {
|
|
1797
2003
|
await remove(file);
|
|
1798
2004
|
log.success(`Removed OpenCode agent hook [${opts.slug}] from ${file}`);
|
|
@@ -1828,13 +2034,13 @@ __export(openclaw_hooks_exports, {
|
|
|
1828
2034
|
resolveOpenClawHooksDir: () => resolveOpenClawHooksDir,
|
|
1829
2035
|
resolveOpenclawWorkspaceDir: () => resolveOpenclawWorkspaceDir
|
|
1830
2036
|
});
|
|
1831
|
-
import
|
|
2037
|
+
import path10 from "path";
|
|
1832
2038
|
function resolveOpenClawHooksDir(tool) {
|
|
1833
2039
|
if (tool === "openclaw" && process.env.OPENCLAW_STATE_DIR) {
|
|
1834
|
-
return
|
|
2040
|
+
return path10.join(process.env.OPENCLAW_STATE_DIR, "hooks");
|
|
1835
2041
|
}
|
|
1836
2042
|
const home = getUserHome();
|
|
1837
|
-
return
|
|
2043
|
+
return path10.join(home, `.${tool}`, "hooks");
|
|
1838
2044
|
}
|
|
1839
2045
|
function buildHookMd(tool) {
|
|
1840
2046
|
const events = Object.keys(EVENT_MAP);
|
|
@@ -1880,21 +2086,21 @@ async function injectOpenClawHooks(workspacePath, tool = "openclaw") {
|
|
|
1880
2086
|
log.debug(`openclaw: skip hook injection for ${tool} \u2014 workspace dir not found`);
|
|
1881
2087
|
return;
|
|
1882
2088
|
}
|
|
1883
|
-
const dir =
|
|
2089
|
+
const dir = path10.join(wsDir, "hooks", OPENCLAW_HOOK_DIR);
|
|
1884
2090
|
await ensureDir(dir);
|
|
1885
|
-
await writeFile(
|
|
1886
|
-
await writeFile(
|
|
2091
|
+
await writeFile(path10.join(dir, "HOOK.md"), buildHookMd(tool));
|
|
2092
|
+
await writeFile(path10.join(dir, "handler.ts"), buildHandlerTs(tool));
|
|
1887
2093
|
log.success(`Injected teamai OpenClaw hook into ${dir}`);
|
|
1888
2094
|
await enableOpenClawInternalHooks(tool);
|
|
1889
2095
|
}
|
|
1890
2096
|
async function enableOpenClawInternalHooks(tool = "openclaw") {
|
|
1891
2097
|
if (tool !== "openclaw") return;
|
|
1892
2098
|
const stateDir = process.env.OPENCLAW_STATE_DIR;
|
|
1893
|
-
if (!stateDir || !
|
|
2099
|
+
if (!stateDir || !path10.isAbsolute(stateDir)) {
|
|
1894
2100
|
log.debug("openclaw: skip enabling internal hooks \u2014 OPENCLAW_STATE_DIR unset or not absolute");
|
|
1895
2101
|
return;
|
|
1896
2102
|
}
|
|
1897
|
-
const cfgPath =
|
|
2103
|
+
const cfgPath = path10.join(stateDir, "openclaw.json");
|
|
1898
2104
|
try {
|
|
1899
2105
|
const cfg = await readJson(cfgPath) ?? {};
|
|
1900
2106
|
const hooksVal = cfg.hooks;
|
|
@@ -1915,13 +2121,13 @@ async function enableOpenClawInternalHooks(tool = "openclaw") {
|
|
|
1915
2121
|
}
|
|
1916
2122
|
}
|
|
1917
2123
|
async function removeOpenClawHooks(hooksDir) {
|
|
1918
|
-
const dir =
|
|
2124
|
+
const dir = path10.join(hooksDir, OPENCLAW_HOOK_DIR);
|
|
1919
2125
|
if (await pathExists(dir)) {
|
|
1920
2126
|
await remove(dir);
|
|
1921
2127
|
log.success(`Removed teamai OpenClaw hook from ${dir}`);
|
|
1922
2128
|
}
|
|
1923
2129
|
if (process.env.OPENCLAW_STATE_DIR) {
|
|
1924
|
-
const altDir =
|
|
2130
|
+
const altDir = path10.join(process.env.OPENCLAW_STATE_DIR, "hooks", OPENCLAW_HOOK_DIR);
|
|
1925
2131
|
if (altDir !== dir && await pathExists(altDir)) {
|
|
1926
2132
|
await remove(altDir);
|
|
1927
2133
|
log.success(`Removed teamai OpenClaw hook from ${altDir}`);
|
|
@@ -1970,16 +2176,16 @@ async function applyOpenClawAgentHook(def) {
|
|
|
1970
2176
|
}
|
|
1971
2177
|
const tool = def.tool ?? "openclaw";
|
|
1972
2178
|
const hooksDir = resolveOpenClawHooksDir(tool);
|
|
1973
|
-
const dir =
|
|
2179
|
+
const dir = path10.join(hooksDir, def.slug);
|
|
1974
2180
|
await ensureDir(dir);
|
|
1975
|
-
await writeFile(
|
|
1976
|
-
await writeFile(
|
|
2181
|
+
await writeFile(path10.join(dir, "HOOK.md"), buildAgentHookMd(def.slug, openclawEvent));
|
|
2182
|
+
await writeFile(path10.join(dir, "handler.ts"), buildAgentHandlerTs(def.command, def.timeout ?? 10));
|
|
1977
2183
|
log.success(`Installed OpenClaw agent hook [${def.slug}] in ${dir}`);
|
|
1978
2184
|
}
|
|
1979
2185
|
async function removeOpenClawAgentHook(opts) {
|
|
1980
2186
|
const tool = opts.tool ?? "openclaw";
|
|
1981
2187
|
const hooksDir = resolveOpenClawHooksDir(tool);
|
|
1982
|
-
const dir =
|
|
2188
|
+
const dir = path10.join(hooksDir, opts.slug);
|
|
1983
2189
|
if (await pathExists(dir)) {
|
|
1984
2190
|
await remove(dir);
|
|
1985
2191
|
log.success(`Removed OpenClaw agent hook [${opts.slug}] from ${dir}`);
|
|
@@ -1989,8 +2195,8 @@ async function resolveOpenclawWorkspaceDir(workspacePath) {
|
|
|
1989
2195
|
const candidates = [];
|
|
1990
2196
|
if (workspacePath) candidates.push(workspacePath);
|
|
1991
2197
|
const stateDir = process.env.OPENCLAW_STATE_DIR;
|
|
1992
|
-
if (stateDir &&
|
|
1993
|
-
const cfgRaw = await readFileSafe(
|
|
2198
|
+
if (stateDir && path10.isAbsolute(stateDir)) {
|
|
2199
|
+
const cfgRaw = await readFileSafe(path10.join(stateDir, "openclaw.json"));
|
|
1994
2200
|
if (cfgRaw) {
|
|
1995
2201
|
try {
|
|
1996
2202
|
const cfg = JSON.parse(cfgRaw);
|
|
@@ -2001,7 +2207,7 @@ async function resolveOpenclawWorkspaceDir(workspacePath) {
|
|
|
2001
2207
|
}
|
|
2002
2208
|
}
|
|
2003
2209
|
}
|
|
2004
|
-
candidates.push(
|
|
2210
|
+
candidates.push(path10.join(getUserHome(), ".openclaw", "workspace"));
|
|
2005
2211
|
for (const candidate of candidates) {
|
|
2006
2212
|
if (await pathExists(candidate)) {
|
|
2007
2213
|
log.debug(`openclaw: resolved workspace dir to ${candidate}`);
|
|
@@ -2036,14 +2242,14 @@ var hermes_home_exports = {};
|
|
|
2036
2242
|
__export(hermes_home_exports, {
|
|
2037
2243
|
getHermesHome: () => getHermesHome
|
|
2038
2244
|
});
|
|
2039
|
-
import
|
|
2245
|
+
import path11 from "path";
|
|
2040
2246
|
import { homedir } from "os";
|
|
2041
2247
|
function getHermesHome() {
|
|
2042
2248
|
const fromEnv = process.env.HERMES_HOME;
|
|
2043
2249
|
if (fromEnv && fromEnv.trim() !== "") {
|
|
2044
|
-
return
|
|
2250
|
+
return path11.resolve(fromEnv);
|
|
2045
2251
|
}
|
|
2046
|
-
return
|
|
2252
|
+
return path11.join(homedir(), ".hermes");
|
|
2047
2253
|
}
|
|
2048
2254
|
var init_hermes_home = __esm({
|
|
2049
2255
|
"src/hermes-home.ts"() {
|
|
@@ -2064,20 +2270,20 @@ __export(hermes_config_exports, {
|
|
|
2064
2270
|
upsertHermesHook: () => upsertHermesHook,
|
|
2065
2271
|
upsertSoulRules: () => upsertSoulRules
|
|
2066
2272
|
});
|
|
2067
|
-
import
|
|
2068
|
-
import
|
|
2273
|
+
import YAML3 from "yaml";
|
|
2274
|
+
import path12 from "path";
|
|
2069
2275
|
function getHermesConfigPath() {
|
|
2070
|
-
return
|
|
2276
|
+
return path12.join(getHermesHome(), "config.yaml");
|
|
2071
2277
|
}
|
|
2072
2278
|
function getHermesSoulPath() {
|
|
2073
|
-
return
|
|
2279
|
+
return path12.join(getHermesHome(), "SOUL.md");
|
|
2074
2280
|
}
|
|
2075
2281
|
async function readConfigDoc() {
|
|
2076
2282
|
const content = await readFileSafe(getHermesConfigPath());
|
|
2077
|
-
if (!content || content.trim() === "") return new
|
|
2078
|
-
const doc =
|
|
2079
|
-
if (!doc.contents || !
|
|
2080
|
-
return new
|
|
2283
|
+
if (!content || content.trim() === "") return new YAML3.Document({});
|
|
2284
|
+
const doc = YAML3.parseDocument(content);
|
|
2285
|
+
if (!doc.contents || !YAML3.isMap(doc.contents)) {
|
|
2286
|
+
return new YAML3.Document({});
|
|
2081
2287
|
}
|
|
2082
2288
|
return doc;
|
|
2083
2289
|
}
|
|
@@ -2150,12 +2356,12 @@ async function removeSoulRules() {
|
|
|
2150
2356
|
await upsertSoulRules("");
|
|
2151
2357
|
}
|
|
2152
2358
|
function getHermesAllowlistPath() {
|
|
2153
|
-
return
|
|
2359
|
+
return path12.join(getHermesHome(), "shell-hooks-allowlist.json");
|
|
2154
2360
|
}
|
|
2155
2361
|
async function upsertHermesHook(event, entry) {
|
|
2156
2362
|
const doc = await readConfigDoc();
|
|
2157
2363
|
const rawSeq = doc.getIn(["hooks", event]);
|
|
2158
|
-
const currentJs =
|
|
2364
|
+
const currentJs = YAML3.isSeq(rawSeq) ? rawSeq.toJSON() : rawSeq;
|
|
2159
2365
|
const arr = Array.isArray(currentJs) ? currentJs : [];
|
|
2160
2366
|
const untouched = arr.filter((e) => e && typeof e === "object" && e.command !== entry.command);
|
|
2161
2367
|
const cleanEntry = { command: entry.command };
|
|
@@ -2169,7 +2375,7 @@ async function upsertHermesHook(event, entry) {
|
|
|
2169
2375
|
async function removeHermesHookByCommand(command) {
|
|
2170
2376
|
const doc = await readConfigDoc();
|
|
2171
2377
|
const rawHooks = doc.getIn(["hooks"]);
|
|
2172
|
-
const hooks =
|
|
2378
|
+
const hooks = YAML3.isMap(rawHooks) ? rawHooks.toJSON() : null;
|
|
2173
2379
|
if (!hooks || typeof hooks !== "object") return;
|
|
2174
2380
|
let changed = false;
|
|
2175
2381
|
for (const event of Object.keys(hooks)) {
|
|
@@ -2186,7 +2392,7 @@ async function removeHermesHookByCommand(command) {
|
|
|
2186
2392
|
}
|
|
2187
2393
|
if (!changed) return;
|
|
2188
2394
|
const remainingRaw = doc.getIn(["hooks"]);
|
|
2189
|
-
const remaining =
|
|
2395
|
+
const remaining = YAML3.isMap(remainingRaw) ? remainingRaw.toJSON() : null;
|
|
2190
2396
|
if (!remaining || Object.keys(remaining).length === 0) {
|
|
2191
2397
|
doc.deleteIn(["hooks"]);
|
|
2192
2398
|
}
|
|
@@ -2231,10 +2437,10 @@ __export(hermes_hooks_exports, {
|
|
|
2231
2437
|
removeHermesAgentHook: () => removeHermesAgentHook,
|
|
2232
2438
|
removeHermesHooks: () => removeHermesHooks
|
|
2233
2439
|
});
|
|
2234
|
-
import
|
|
2440
|
+
import path13 from "path";
|
|
2235
2441
|
import { chmod } from "fs/promises";
|
|
2236
2442
|
function getReportScriptPath() {
|
|
2237
|
-
return
|
|
2443
|
+
return path13.join(getHermesHome(), "hooks", "teamai-status-report.sh");
|
|
2238
2444
|
}
|
|
2239
2445
|
function buildReportScript() {
|
|
2240
2446
|
return [
|
|
@@ -2246,7 +2452,7 @@ function buildReportScript() {
|
|
|
2246
2452
|
}
|
|
2247
2453
|
async function injectHermesHooks() {
|
|
2248
2454
|
const scriptPath = getReportScriptPath();
|
|
2249
|
-
await ensureDir(
|
|
2455
|
+
await ensureDir(path13.dirname(scriptPath));
|
|
2250
2456
|
await writeFile(scriptPath, buildReportScript());
|
|
2251
2457
|
try {
|
|
2252
2458
|
await chmod(scriptPath, 493);
|
|
@@ -2321,7 +2527,7 @@ __export(hooks_exports, {
|
|
|
2321
2527
|
removeHooks: () => removeHooks,
|
|
2322
2528
|
sweepLegacyProjectHooks: () => sweepLegacyProjectHooks
|
|
2323
2529
|
});
|
|
2324
|
-
import
|
|
2530
|
+
import path14 from "path";
|
|
2325
2531
|
import { realpathSync } from "fs";
|
|
2326
2532
|
function detectFormat(tool) {
|
|
2327
2533
|
if (CODEX_TOOLS.has(tool)) return "codex";
|
|
@@ -2354,7 +2560,7 @@ function canonicalProjectRoot(projectRoot) {
|
|
|
2354
2560
|
try {
|
|
2355
2561
|
return realpathSync.native(projectRoot);
|
|
2356
2562
|
} catch {
|
|
2357
|
-
return
|
|
2563
|
+
return path14.resolve(projectRoot);
|
|
2358
2564
|
}
|
|
2359
2565
|
}
|
|
2360
2566
|
function gateTeamHookCommand(command, projectRoot) {
|
|
@@ -2414,16 +2620,30 @@ function toCodexEntry(def) {
|
|
|
2414
2620
|
return entry;
|
|
2415
2621
|
}
|
|
2416
2622
|
function toZcodeEntry(def) {
|
|
2417
|
-
const
|
|
2623
|
+
const ZCODE_TIMEOUT_MS = {
|
|
2624
|
+
SessionStart: 18e4,
|
|
2625
|
+
Stop: 6e4,
|
|
2626
|
+
PostToolUse: 3e4,
|
|
2627
|
+
UserPromptSubmit: 6e4
|
|
2628
|
+
};
|
|
2629
|
+
const entry = process.platform === "win32" ? {
|
|
2630
|
+
// Windows must NOT spawn bare `bash`: CreateProcess resolves it to
|
|
2631
|
+
// System32's WSL launcher before any PATH directory, and the WSL side
|
|
2632
|
+
// has a different $HOME (no ~/.teamai state) and often no Node ≥ 20.
|
|
2633
|
+
// cmd.exe is always present in System32 and resolves teamai from the
|
|
2634
|
+
// Windows PATH (the npm shim is a .cmd, so a shell is required).
|
|
2635
|
+
type: "process",
|
|
2636
|
+
command: "cmd",
|
|
2637
|
+
args: ["/c", def.command],
|
|
2638
|
+
timeoutMs: ZCODE_TIMEOUT_MS[def.event] ?? 6e4
|
|
2639
|
+
} : {
|
|
2418
2640
|
type: "process",
|
|
2419
2641
|
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.
|
|
2642
|
+
// Stored verbatim: the shell payload must equal `def.command` exactly
|
|
2643
|
+
// so managed-entry detection and the managed-hooks manifest share one
|
|
2644
|
+
// command representation (the same invariant the Codex format keeps).
|
|
2425
2645
|
args: ["-lc", def.command],
|
|
2426
|
-
|
|
2646
|
+
timeoutMs: ZCODE_TIMEOUT_MS[def.event] ?? 6e4
|
|
2427
2647
|
};
|
|
2428
2648
|
const group = { hooks: [entry] };
|
|
2429
2649
|
if (def.matcher && def.matcher !== "*") group.matcher = def.matcher;
|
|
@@ -2431,7 +2651,7 @@ function toZcodeEntry(def) {
|
|
|
2431
2651
|
}
|
|
2432
2652
|
function zcodeEntryCommand(entry) {
|
|
2433
2653
|
const hook = entry.hooks?.[0];
|
|
2434
|
-
if (hook?.
|
|
2654
|
+
if (Array.isArray(hook?.args) && hook.args.length > 1) return hook.args[1] ?? "";
|
|
2435
2655
|
return hook?.command ?? "";
|
|
2436
2656
|
}
|
|
2437
2657
|
function desiredEventOrder(defs, mapEvent) {
|
|
@@ -2465,7 +2685,7 @@ async function reconcileClaudeFormat(settingsPath, tool, teamDefs, opts, teamAct
|
|
|
2465
2685
|
return true;
|
|
2466
2686
|
};
|
|
2467
2687
|
const expanded = expandHome(settingsPath);
|
|
2468
|
-
await ensureDir(
|
|
2688
|
+
await ensureDir(path14.dirname(expanded));
|
|
2469
2689
|
const settings = await readJson(expanded) ?? {};
|
|
2470
2690
|
if (!settings.hooks) settings.hooks = {};
|
|
2471
2691
|
let changed = false;
|
|
@@ -2497,7 +2717,7 @@ async function reconcileClaudeFormat(settingsPath, tool, teamDefs, opts, teamAct
|
|
|
2497
2717
|
}
|
|
2498
2718
|
async function reconcileCursorFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2499
2719
|
const expanded = expandHome(hooksPath);
|
|
2500
|
-
await ensureDir(
|
|
2720
|
+
await ensureDir(path14.dirname(expanded));
|
|
2501
2721
|
const hooksJson = await readJson(expanded) ?? { version: 1, hooks: {} };
|
|
2502
2722
|
if (!hooksJson.version) hooksJson.version = 1;
|
|
2503
2723
|
if (!hooksJson.hooks) hooksJson.hooks = {};
|
|
@@ -2545,7 +2765,7 @@ async function reconcileCursorFormat(hooksPath, tool, teamDefs, opts, priorTeamC
|
|
|
2545
2765
|
}
|
|
2546
2766
|
async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2547
2767
|
const expanded = expandHome(hooksPath);
|
|
2548
|
-
await ensureDir(
|
|
2768
|
+
await ensureDir(path14.dirname(expanded));
|
|
2549
2769
|
const hooksJson = await readJson(expanded) ?? {};
|
|
2550
2770
|
if (!hooksJson.hooks) hooksJson.hooks = {};
|
|
2551
2771
|
const isManaged = (entry) => {
|
|
@@ -2575,10 +2795,15 @@ async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCo
|
|
|
2575
2795
|
}
|
|
2576
2796
|
async function reconcileZcodeFormat(settingsPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2577
2797
|
const expanded = expandHome(settingsPath);
|
|
2578
|
-
await ensureDir(
|
|
2798
|
+
await ensureDir(path14.dirname(expanded));
|
|
2579
2799
|
const cfg = await readJson(expanded) ?? {};
|
|
2580
2800
|
if (!cfg.hooks) cfg.hooks = {};
|
|
2581
2801
|
let changed = false;
|
|
2802
|
+
const unknownHookKeys = Object.keys(cfg.hooks).filter((k) => k !== "enabled" && k !== "events");
|
|
2803
|
+
if (unknownHookKeys.length > 0) {
|
|
2804
|
+
for (const k of unknownHookKeys) delete cfg.hooks[k];
|
|
2805
|
+
changed = true;
|
|
2806
|
+
}
|
|
2582
2807
|
if (!opts.removeAll && cfg.hooks.enabled !== true) {
|
|
2583
2808
|
cfg.hooks.enabled = true;
|
|
2584
2809
|
changed = true;
|
|
@@ -2628,7 +2853,7 @@ function isAgentClaudeEntry(entry, slug) {
|
|
|
2628
2853
|
async function applyAgentHook(settingsPath, tool, def) {
|
|
2629
2854
|
const format = detectFormat(tool);
|
|
2630
2855
|
const expanded = expandHome(settingsPath);
|
|
2631
|
-
await ensureDir(
|
|
2856
|
+
await ensureDir(path14.dirname(expanded));
|
|
2632
2857
|
const hookDef = {
|
|
2633
2858
|
source: "team",
|
|
2634
2859
|
key: def.slug,
|
|
@@ -2854,15 +3079,15 @@ async function hasTeamaiHooks(settingsPath, tool, manifestPath) {
|
|
|
2854
3079
|
async function reconcileOpencodePlugin(baseDir, removeAll = false, installedBaseDir) {
|
|
2855
3080
|
const home = getUserHome();
|
|
2856
3081
|
const { injectOpencodeHooks: injectOpencodeHooks2, removeOpencodeHooks: removeOpencodeHooks2 } = await Promise.resolve().then(() => (init_opencode_hooks(), opencode_hooks_exports));
|
|
2857
|
-
if (
|
|
3082
|
+
if (path14.resolve(baseDir) !== path14.resolve(home)) {
|
|
2858
3083
|
await removeOpencodeHooks2(baseDir, "project");
|
|
2859
3084
|
}
|
|
2860
3085
|
if (removeAll) {
|
|
2861
3086
|
await removeOpencodeHooks2(home, "user");
|
|
2862
3087
|
return;
|
|
2863
3088
|
}
|
|
2864
|
-
const homeInstalled = await pathExists(
|
|
2865
|
-
const projectInstalled = installedBaseDir ? await pathExists(
|
|
3089
|
+
const homeInstalled = await pathExists(path14.join(home, ".config", "opencode"));
|
|
3090
|
+
const projectInstalled = installedBaseDir ? await pathExists(path14.join(installedBaseDir, ".opencode")) : false;
|
|
2866
3091
|
if (homeInstalled || projectInstalled) {
|
|
2867
3092
|
await injectOpencodeHooks2(home, "user");
|
|
2868
3093
|
}
|
|
@@ -2876,9 +3101,9 @@ async function injectHooksToAllTools(toolPaths, baseDir, filterAgents2) {
|
|
|
2876
3101
|
if (filterAgents2 && !filterAgents2.includes(tool)) continue;
|
|
2877
3102
|
if (skipped.has(tool)) continue;
|
|
2878
3103
|
if (paths.settings) {
|
|
2879
|
-
const toolRoot =
|
|
3104
|
+
const toolRoot = path14.join(resolvedBaseDir, paths.settings.split("/")[0]);
|
|
2880
3105
|
if (!await pathExists(toolRoot)) continue;
|
|
2881
|
-
const settingsPath =
|
|
3106
|
+
const settingsPath = path14.join(resolvedBaseDir, paths.settings);
|
|
2882
3107
|
try {
|
|
2883
3108
|
await injectHooks(settingsPath, tool);
|
|
2884
3109
|
} catch (e) {
|
|
@@ -2941,10 +3166,10 @@ async function reconcileHooksToAllTools(toolPaths, baseDir, teamDefs, manifestPa
|
|
|
2941
3166
|
continue;
|
|
2942
3167
|
}
|
|
2943
3168
|
if (!paths.settings) continue;
|
|
2944
|
-
const toolRoot =
|
|
2945
|
-
const installedRoot = opts.installedBaseDir ?
|
|
3169
|
+
const toolRoot = path14.join(baseDir, paths.settings.split("/")[0]);
|
|
3170
|
+
const installedRoot = opts.installedBaseDir ? path14.join(opts.installedBaseDir, paths.settings.split("/")[0]) : toolRoot;
|
|
2946
3171
|
if (!await pathExists(toolRoot) && !await pathExists(installedRoot)) continue;
|
|
2947
|
-
const settingsPath =
|
|
3172
|
+
const settingsPath = path14.join(baseDir, paths.settings);
|
|
2948
3173
|
try {
|
|
2949
3174
|
await reconcileHooks(settingsPath, tool, teamDefs, {
|
|
2950
3175
|
manifestPath,
|
|
@@ -2960,7 +3185,7 @@ async function reconcileHooksToAllTools(toolPaths, baseDir, teamDefs, manifestPa
|
|
|
2960
3185
|
async function hasInstalledCodexTrustGatedTool(toolPaths, baseDir) {
|
|
2961
3186
|
for (const [tool, paths] of Object.entries(toolPaths)) {
|
|
2962
3187
|
if (!isCodexTrustGatedTool(tool) || !paths.settings) continue;
|
|
2963
|
-
const toolRoot =
|
|
3188
|
+
const toolRoot = path14.join(baseDir, paths.settings.split("/")[0]);
|
|
2964
3189
|
if (await pathExists(toolRoot)) return true;
|
|
2965
3190
|
}
|
|
2966
3191
|
return false;
|
|
@@ -2982,7 +3207,11 @@ async function sweepLegacyProjectHooks(toolPaths, localConfig) {
|
|
|
2982
3207
|
}
|
|
2983
3208
|
}
|
|
2984
3209
|
async function reconcileTeamHooksForConfig(teamConfig, localConfig, opts = {}) {
|
|
2985
|
-
const { defs: teamDefs, builtin } = opts.removeAll ? { defs: [], builtin: void 0 } : await resolveTeamHooks(teamConfig, localConfig.repo.localPath, {
|
|
3210
|
+
const { defs: teamDefs, builtin } = opts.removeAll ? { defs: [], builtin: void 0 } : await resolveTeamHooks(teamConfig, localConfig.repo.localPath, {
|
|
3211
|
+
auto: opts.auto,
|
|
3212
|
+
silent: opts.silent,
|
|
3213
|
+
activeRoles: activeRoleIds(localConfig)
|
|
3214
|
+
});
|
|
2986
3215
|
const { baseDir, manifestPath } = resolveHookScope(localConfig);
|
|
2987
3216
|
let filterAgents2 = opts.filterAgents ?? localConfig.enabledAgents;
|
|
2988
3217
|
const disabled = localConfig.disabledAgents;
|
|
@@ -3008,6 +3237,7 @@ var init_hooks2 = __esm({
|
|
|
3008
3237
|
init_logger();
|
|
3009
3238
|
init_types();
|
|
3010
3239
|
init_types();
|
|
3240
|
+
init_roles();
|
|
3011
3241
|
init_builtin_hooks();
|
|
3012
3242
|
init_hooks();
|
|
3013
3243
|
init_home();
|
|
@@ -3248,7 +3478,7 @@ __export(builtin_rules_exports, {
|
|
|
3248
3478
|
LEGACY_RULE_NAMES: () => LEGACY_RULE_NAMES,
|
|
3249
3479
|
deployBuiltinRules: () => deployBuiltinRules
|
|
3250
3480
|
});
|
|
3251
|
-
import
|
|
3481
|
+
import path15 from "path";
|
|
3252
3482
|
import fs4 from "fs/promises";
|
|
3253
3483
|
async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
3254
3484
|
const baseDir = localConfig ? resolveBaseDir(localConfig) : getUserHome();
|
|
@@ -3263,19 +3493,19 @@ async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
|
3263
3493
|
continue;
|
|
3264
3494
|
}
|
|
3265
3495
|
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
3266
|
-
const rulesDir =
|
|
3496
|
+
const rulesDir = path15.join(baseDir, toolPath.rules);
|
|
3267
3497
|
if (!await pathExists(rulesDir)) continue;
|
|
3268
3498
|
try {
|
|
3269
3499
|
await ensureDir(rulesDir);
|
|
3270
3500
|
const ext = ruleFileExtensionForTool(tool);
|
|
3271
3501
|
for (const rule of builtinRules) {
|
|
3272
|
-
const destFile =
|
|
3502
|
+
const destFile = path15.join(rulesDir, `${rule.name}${ext}`);
|
|
3273
3503
|
const content = usesCursorMdcRules(tool) ? teamRuleToCursorMdc(rule.content) : rule.content;
|
|
3274
3504
|
await writeFile(destFile, content);
|
|
3275
3505
|
log.debug(`Deployed built-in rule ${rule.name} \u2192 ${tool}`);
|
|
3276
3506
|
if (ext !== ".md") {
|
|
3277
3507
|
try {
|
|
3278
|
-
await fs4.unlink(
|
|
3508
|
+
await fs4.unlink(path15.join(rulesDir, `${rule.name}.md`));
|
|
3279
3509
|
log.debug(`Removed legacy .md built-in rule ${rule.name} from ${tool}`);
|
|
3280
3510
|
} catch {
|
|
3281
3511
|
}
|
|
@@ -3283,7 +3513,7 @@ async function deployBuiltinRules(teamConfig, localConfig, options) {
|
|
|
3283
3513
|
}
|
|
3284
3514
|
for (const legacyName of LEGACY_RULE_NAMES) {
|
|
3285
3515
|
for (const legacyExt of /* @__PURE__ */ new Set([ext, ".md"])) {
|
|
3286
|
-
const legacyFile =
|
|
3516
|
+
const legacyFile = path15.join(rulesDir, `${legacyName}${legacyExt}`);
|
|
3287
3517
|
try {
|
|
3288
3518
|
await fs4.unlink(legacyFile);
|
|
3289
3519
|
log.debug(`Removed legacy built-in rule ${legacyName} from ${tool}`);
|
|
@@ -3370,7 +3600,7 @@ teamai-recall subagent \u7684\u8FD4\u56DE\u91CC\u5DF2\u5217\u51FA\u672C\u6B21\u6
|
|
|
3370
3600
|
});
|
|
3371
3601
|
|
|
3372
3602
|
// src/utils/pre-push-sync.ts
|
|
3373
|
-
import
|
|
3603
|
+
import path16 from "path";
|
|
3374
3604
|
async function syncTeamUpdatesToLocal(teamConfig, localConfig, lastPullRev) {
|
|
3375
3605
|
if (!lastPullRev) {
|
|
3376
3606
|
log.debug("No lastPullRev \u2014 skipping pre-push sync");
|
|
@@ -3382,12 +3612,12 @@ async function syncTeamUpdatesToLocal(teamConfig, localConfig, lastPullRev) {
|
|
|
3382
3612
|
await syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, lastPullRev);
|
|
3383
3613
|
}
|
|
3384
3614
|
async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, lastPullRev) {
|
|
3385
|
-
const teamRulesDir =
|
|
3615
|
+
const teamRulesDir = path16.join(repoPath, "rules");
|
|
3386
3616
|
if (!await pathExists(teamRulesDir)) return;
|
|
3387
3617
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
3388
3618
|
if (!toolPath.rules) continue;
|
|
3389
3619
|
if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
|
|
3390
|
-
const rulesDir =
|
|
3620
|
+
const rulesDir = path16.join(baseDir, toolPath.rules);
|
|
3391
3621
|
if (!await pathExists(rulesDir)) continue;
|
|
3392
3622
|
const ext = ruleFileExtensionForTool(tool);
|
|
3393
3623
|
const isMdcTool = usesCursorMdcRules(tool);
|
|
@@ -3396,9 +3626,9 @@ async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, last
|
|
|
3396
3626
|
if (!file.endsWith(ext)) continue;
|
|
3397
3627
|
const name = file.slice(0, -ext.length);
|
|
3398
3628
|
if (EXCLUDED_RULE_NAMES.has(name)) continue;
|
|
3399
|
-
const localFilePath =
|
|
3629
|
+
const localFilePath = path16.join(rulesDir, file);
|
|
3400
3630
|
const teamRelPath = `rules/${name}.md`;
|
|
3401
|
-
const teamFilePath =
|
|
3631
|
+
const teamFilePath = path16.join(teamRulesDir, `${name}.md`);
|
|
3402
3632
|
if (!await pathExists(teamFilePath)) continue;
|
|
3403
3633
|
if (isMdcTool) {
|
|
3404
3634
|
const localRaw = await readFileSafe(localFilePath);
|
|
@@ -3424,19 +3654,19 @@ async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, last
|
|
|
3424
3654
|
}
|
|
3425
3655
|
}
|
|
3426
3656
|
async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, lastPullRev) {
|
|
3427
|
-
const teamSkillsDir =
|
|
3657
|
+
const teamSkillsDir = path16.join(repoPath, "skills");
|
|
3428
3658
|
if (!await pathExists(teamSkillsDir)) return;
|
|
3429
3659
|
const teamSkillPaths = /* @__PURE__ */ new Map();
|
|
3430
3660
|
const topDirs = await listDirs(teamSkillsDir);
|
|
3431
3661
|
for (const dir of topDirs) {
|
|
3432
|
-
const dirPath =
|
|
3433
|
-
if (await pathExists(
|
|
3662
|
+
const dirPath = path16.join(teamSkillsDir, dir);
|
|
3663
|
+
if (await pathExists(path16.join(dirPath, "SKILL.md"))) {
|
|
3434
3664
|
teamSkillPaths.set(dir, dirPath);
|
|
3435
3665
|
} else {
|
|
3436
3666
|
const subDirs = await listDirs(dirPath);
|
|
3437
3667
|
for (const subDir of subDirs) {
|
|
3438
3668
|
if (!teamSkillPaths.has(subDir)) {
|
|
3439
|
-
teamSkillPaths.set(subDir,
|
|
3669
|
+
teamSkillPaths.set(subDir, path16.join(dirPath, subDir));
|
|
3440
3670
|
}
|
|
3441
3671
|
}
|
|
3442
3672
|
}
|
|
@@ -3445,12 +3675,12 @@ async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, las
|
|
|
3445
3675
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
3446
3676
|
if (!toolPath.skills) continue;
|
|
3447
3677
|
if (!await ResourceHandler.isToolInstalled(toolPath.skills, baseDir)) continue;
|
|
3448
|
-
const skillsDir =
|
|
3678
|
+
const skillsDir = path16.join(baseDir, toolPath.skills);
|
|
3449
3679
|
if (!await pathExists(skillsDir)) continue;
|
|
3450
3680
|
const localSkillNames = await listDirs(skillsDir);
|
|
3451
3681
|
for (const skillName of localSkillNames) {
|
|
3452
3682
|
if (!teamSkillPaths.has(skillName)) continue;
|
|
3453
|
-
const localSkillDir =
|
|
3683
|
+
const localSkillDir = path16.join(skillsDir, skillName);
|
|
3454
3684
|
const teamSkillDir = teamSkillPaths.get(skillName);
|
|
3455
3685
|
if (await dirTeamSubsetEqual(localSkillDir, teamSkillDir, [CONTRIBUTORS_FILE3])) continue;
|
|
3456
3686
|
const teamFiles = await listFilesRecursive(teamSkillDir);
|
|
@@ -3458,10 +3688,10 @@ async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, las
|
|
|
3458
3688
|
let anyDiffers = false;
|
|
3459
3689
|
for (const file of teamFiles) {
|
|
3460
3690
|
if (file === CONTRIBUTORS_FILE3) continue;
|
|
3461
|
-
const localFile =
|
|
3462
|
-
const teamFile =
|
|
3691
|
+
const localFile = path16.join(localSkillDir, file);
|
|
3692
|
+
const teamFile = path16.join(teamSkillDir, file);
|
|
3463
3693
|
if (!await pathExists(localFile)) {
|
|
3464
|
-
const relFromRepo2 =
|
|
3694
|
+
const relFromRepo2 = path16.relative(repoPath, teamFile);
|
|
3465
3695
|
const oldContent2 = await getFileContentAtRev(repoPath, lastPullRev, relFromRepo2);
|
|
3466
3696
|
if (oldContent2 === null) {
|
|
3467
3697
|
anyDiffers = true;
|
|
@@ -3472,7 +3702,7 @@ async function syncSkillsToLocal(teamConfig, localConfig, repoPath, baseDir, las
|
|
|
3472
3702
|
}
|
|
3473
3703
|
if (await fileContentEqual(localFile, teamFile)) continue;
|
|
3474
3704
|
anyDiffers = true;
|
|
3475
|
-
const relFromRepo =
|
|
3705
|
+
const relFromRepo = path16.relative(repoPath, teamFile);
|
|
3476
3706
|
const oldContent = await getFileContentAtRev(repoPath, lastPullRev, relFromRepo);
|
|
3477
3707
|
if (oldContent === null) {
|
|
3478
3708
|
allMatchOld = false;
|
|
@@ -3604,12 +3834,12 @@ var init_rest_auth = __esm({
|
|
|
3604
3834
|
import { execSync, spawnSync } from "child_process";
|
|
3605
3835
|
import fs5 from "fs";
|
|
3606
3836
|
import os2 from "os";
|
|
3607
|
-
import
|
|
3837
|
+
import path17 from "path";
|
|
3608
3838
|
function gfInstallDir() {
|
|
3609
|
-
return
|
|
3839
|
+
return path17.join(getTeamaiHomeDir(), "gf");
|
|
3610
3840
|
}
|
|
3611
3841
|
function gfBinPath() {
|
|
3612
|
-
return
|
|
3842
|
+
return path17.join(gfInstallDir(), "gf", "bin", "gf");
|
|
3613
3843
|
}
|
|
3614
3844
|
function shellQuote2(s) {
|
|
3615
3845
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
@@ -3747,7 +3977,7 @@ function ensureAuthenticated() {
|
|
|
3747
3977
|
}
|
|
3748
3978
|
function gfGetOAuthToken() {
|
|
3749
3979
|
try {
|
|
3750
|
-
const netrcPath2 =
|
|
3980
|
+
const netrcPath2 = path17.join(os2.homedir(), ".netrc");
|
|
3751
3981
|
if (!fs5.existsSync(netrcPath2)) return null;
|
|
3752
3982
|
const content = fs5.readFileSync(netrcPath2, "utf-8");
|
|
3753
3983
|
const match = content.match(
|
|
@@ -5599,7 +5829,7 @@ var init_repo_url4 = __esm({
|
|
|
5599
5829
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5600
5830
|
import fs6 from "fs";
|
|
5601
5831
|
import os3 from "os";
|
|
5602
|
-
import
|
|
5832
|
+
import path18 from "path";
|
|
5603
5833
|
function gitcodeApiBase() {
|
|
5604
5834
|
return GITCODE_API_BASE;
|
|
5605
5835
|
}
|
|
@@ -5627,7 +5857,7 @@ function authHeaders3(token) {
|
|
|
5627
5857
|
};
|
|
5628
5858
|
}
|
|
5629
5859
|
function netrcPath() {
|
|
5630
|
-
return
|
|
5860
|
+
return path18.join(os3.homedir(), ".netrc");
|
|
5631
5861
|
}
|
|
5632
5862
|
function readNetrcToken() {
|
|
5633
5863
|
try {
|
|
@@ -6113,12 +6343,13 @@ function buildRepoInfo5(owner, repo, remoteUrl) {
|
|
|
6113
6343
|
projectId: encodeURIComponent(`${owner}/${repo}`)
|
|
6114
6344
|
};
|
|
6115
6345
|
}
|
|
6116
|
-
function
|
|
6346
|
+
function parseGenericGitRepoInputWithOptions(input, options = {}) {
|
|
6117
6347
|
const trimmed = input.trim();
|
|
6118
|
-
|
|
6348
|
+
const allowExistingInsecureOrigin = options.allowExistingInsecureOrigin === true;
|
|
6349
|
+
if (/^http:\/\//i.test(trimmed) && !allowExistingInsecureOrigin) {
|
|
6119
6350
|
throw invalidRepoUrl("plain HTTP is not supported; use HTTPS or SSH");
|
|
6120
6351
|
}
|
|
6121
|
-
if (/^https
|
|
6352
|
+
if (/^https?:\/\//i.test(trimmed) || /^ssh:\/\//i.test(trimmed)) {
|
|
6122
6353
|
let parsed;
|
|
6123
6354
|
try {
|
|
6124
6355
|
parsed = new URL(trimmed);
|
|
@@ -6126,7 +6357,7 @@ function parseGenericGitRepoInput(input) {
|
|
|
6126
6357
|
throw invalidRepoUrl();
|
|
6127
6358
|
}
|
|
6128
6359
|
const httpCredentials = /^https?:$/i.test(parsed.protocol) && (parsed.username || parsed.password);
|
|
6129
|
-
if (!parsed.hostname || parsed.password || httpCredentials) {
|
|
6360
|
+
if (!parsed.hostname || !allowExistingInsecureOrigin && (parsed.password || httpCredentials)) {
|
|
6130
6361
|
throw new Error(
|
|
6131
6362
|
"Invalid Git repo URL. Do not embed credentials in the URL; configure a Git credential helper or SSH key instead."
|
|
6132
6363
|
);
|
|
@@ -6135,7 +6366,7 @@ function parseGenericGitRepoInput(input) {
|
|
|
6135
6366
|
throw invalidRepoUrl("query strings and fragments are not supported");
|
|
6136
6367
|
}
|
|
6137
6368
|
const { owner, repo, fullPath } = parsePath(parsed.pathname);
|
|
6138
|
-
const auth = parsed.username ? `${parsed.username}@` : "";
|
|
6369
|
+
const auth = /^ssh:$/i.test(parsed.protocol) && parsed.username ? `${parsed.username}@` : "";
|
|
6139
6370
|
const remoteUrl = `${parsed.protocol}//${auth}${parsed.host}/${fullPath}.git`;
|
|
6140
6371
|
return buildRepoInfo5(owner, repo, remoteUrl);
|
|
6141
6372
|
}
|
|
@@ -6152,6 +6383,12 @@ function parseGenericGitRepoInput(input) {
|
|
|
6152
6383
|
}
|
|
6153
6384
|
throw invalidRepoUrl();
|
|
6154
6385
|
}
|
|
6386
|
+
function parseGenericGitRepoInput(input) {
|
|
6387
|
+
return parseGenericGitRepoInputWithOptions(input);
|
|
6388
|
+
}
|
|
6389
|
+
function parseGenericGitExistingRemote(input) {
|
|
6390
|
+
return parseGenericGitRepoInputWithOptions(input, { allowExistingInsecureOrigin: true });
|
|
6391
|
+
}
|
|
6155
6392
|
var SUPPORTED_URL_HINT;
|
|
6156
6393
|
var init_repo_url5 = __esm({
|
|
6157
6394
|
"src/providers/git/repo-url.ts"() {
|
|
@@ -6527,17 +6764,17 @@ __export(builtin_skills_exports, {
|
|
|
6527
6764
|
deployBuiltinSkills: () => deployBuiltinSkills
|
|
6528
6765
|
});
|
|
6529
6766
|
import fs7 from "fs";
|
|
6530
|
-
import
|
|
6767
|
+
import path19 from "path";
|
|
6531
6768
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6532
6769
|
import fse2 from "fs-extra";
|
|
6533
6770
|
function getBuiltinSkillsDir() {
|
|
6534
|
-
const distDir =
|
|
6535
|
-
return
|
|
6771
|
+
const distDir = path19.dirname(fileURLToPath2(import.meta.url));
|
|
6772
|
+
return path19.join(distDir, "..", "skills");
|
|
6536
6773
|
}
|
|
6537
6774
|
async function copyBuiltinSkillDir(srcDir, destDir) {
|
|
6538
6775
|
await fse2.copy(srcDir, destDir, {
|
|
6539
6776
|
overwrite: true,
|
|
6540
|
-
filter: (srcPath) => !
|
|
6777
|
+
filter: (srcPath) => !path19.basename(srcPath).startsWith(".")
|
|
6541
6778
|
});
|
|
6542
6779
|
}
|
|
6543
6780
|
async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
@@ -6559,7 +6796,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
|
6559
6796
|
const skillNames = [];
|
|
6560
6797
|
for (const entry of entries) {
|
|
6561
6798
|
if (options?.skipRecall && RECALL_DEPENDENT_SKILLS.has(entry)) continue;
|
|
6562
|
-
const skillMd =
|
|
6799
|
+
const skillMd = path19.join(builtinDir, entry, "SKILL.md");
|
|
6563
6800
|
if (await pathExists(skillMd)) {
|
|
6564
6801
|
skillNames.push(entry);
|
|
6565
6802
|
}
|
|
@@ -6575,7 +6812,7 @@ async function deployBuiltinSkills(teamConfig, localConfig, options) {
|
|
|
6575
6812
|
}
|
|
6576
6813
|
if (localConfig && isAgentExcluded(localConfig, tool)) continue;
|
|
6577
6814
|
for (const skillName of skillNames) {
|
|
6578
|
-
const srcDir =
|
|
6815
|
+
const srcDir = path19.join(builtinDir, skillName);
|
|
6579
6816
|
const destDir = await resolveSkillDestination(tool, toolPath.skills, baseDir, skillName, srcDir);
|
|
6580
6817
|
try {
|
|
6581
6818
|
await copyBuiltinSkillDir(srcDir, destDir);
|
|
@@ -6603,142 +6840,6 @@ var init_builtin_skills = __esm({
|
|
|
6603
6840
|
}
|
|
6604
6841
|
});
|
|
6605
6842
|
|
|
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
6843
|
// src/utils/path-safety.ts
|
|
6743
6844
|
var path_safety_exports = {};
|
|
6744
6845
|
__export(path_safety_exports, {
|
|
@@ -7487,10 +7588,20 @@ async function countInterventions(transcriptPath) {
|
|
|
7487
7588
|
const { interrupt, toolReject, toolError } = await scanTranscriptStop(transcriptPath);
|
|
7488
7589
|
return { interrupt, toolReject, toolError };
|
|
7489
7590
|
}
|
|
7490
|
-
function
|
|
7591
|
+
function wordBoundaryPattern(keyword) {
|
|
7592
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7593
|
+
return new RegExp(`(?<![\\p{L}\\p{N}_])${escaped}(?![\\p{L}\\p{N}_])`, "u");
|
|
7594
|
+
}
|
|
7595
|
+
function containsKeyword(lower, keyword) {
|
|
7596
|
+
const k = keyword.trim().normalize("NFC").toLowerCase();
|
|
7597
|
+
if (!k) return false;
|
|
7598
|
+
if (UNSPACED_SCRIPT_RE.test(k)) return lower.includes(k);
|
|
7599
|
+
return wordBoundaryPattern(k).test(lower);
|
|
7600
|
+
}
|
|
7601
|
+
function isCorrectionPrompt(text, extraKeywords = []) {
|
|
7491
7602
|
if (!text) return false;
|
|
7492
|
-
const lower = text.toLowerCase();
|
|
7493
|
-
return CORRECTION_KEYWORDS.some((k) => lower
|
|
7603
|
+
const lower = text.normalize("NFC").toLowerCase();
|
|
7604
|
+
return [...CORRECTION_KEYWORDS, ...extraKeywords].some((k) => containsKeyword(lower, k));
|
|
7494
7605
|
}
|
|
7495
7606
|
function mapEventType(hookEventName) {
|
|
7496
7607
|
switch (hookEventName) {
|
|
@@ -7511,7 +7622,7 @@ function mapEventType(hookEventName) {
|
|
|
7511
7622
|
return null;
|
|
7512
7623
|
}
|
|
7513
7624
|
}
|
|
7514
|
-
async function parseHookEvent(raw, tool) {
|
|
7625
|
+
async function parseHookEvent(raw, tool, options) {
|
|
7515
7626
|
if (!raw.trim()) return null;
|
|
7516
7627
|
let hookData;
|
|
7517
7628
|
try {
|
|
@@ -7550,6 +7661,7 @@ async function parseHookEvent(raw, tool) {
|
|
|
7550
7661
|
}
|
|
7551
7662
|
if (eventType === "prompt_submit" && typeof hookData.prompt === "string") {
|
|
7552
7663
|
event.promptSummary = hookData.prompt.slice(0, 200);
|
|
7664
|
+
event.correction = isCorrectionPrompt(hookData.prompt, options?.correctionKeywords);
|
|
7553
7665
|
}
|
|
7554
7666
|
if (eventType === "stop" && typeof hookData.transcript_path === "string") {
|
|
7555
7667
|
event.transcriptPath = hookData.transcript_path;
|
|
@@ -7856,7 +7968,8 @@ function aggregateSessionMetrics(events) {
|
|
|
7856
7968
|
const stopAt = lastStopAt.get(event.sessionId);
|
|
7857
7969
|
if (stopAt !== void 0) {
|
|
7858
7970
|
const gap = new Date(event.timestamp).getTime() - stopAt;
|
|
7859
|
-
|
|
7971
|
+
const isCorrection = event.correction ?? isCorrectionPrompt(event.promptSummary);
|
|
7972
|
+
if (gap >= 0 && gap <= CORRECTION_WINDOW_MS && isCorrection) {
|
|
7860
7973
|
m.correction++;
|
|
7861
7974
|
}
|
|
7862
7975
|
lastStopAt.delete(event.sessionId);
|
|
@@ -7918,7 +8031,7 @@ async function dashboardReport(toolArg) {
|
|
|
7918
8031
|
compactEvents().catch(() => {
|
|
7919
8032
|
});
|
|
7920
8033
|
}
|
|
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;
|
|
8034
|
+
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
8035
|
var init_dashboard_collector = __esm({
|
|
7923
8036
|
"src/dashboard-collector.ts"() {
|
|
7924
8037
|
"use strict";
|
|
@@ -7941,6 +8054,7 @@ var init_dashboard_collector = __esm({
|
|
|
7941
8054
|
CODEBUDDY_USAGE_RETRY_MS = 250;
|
|
7942
8055
|
CODEBUDDY_BLOB_MAX_COUNT = 2e3;
|
|
7943
8056
|
CODEBUDDY_REJECT_MARKER = "User rejected this command";
|
|
8057
|
+
UNSPACED_SCRIPT_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
7944
8058
|
}
|
|
7945
8059
|
});
|
|
7946
8060
|
|
|
@@ -8297,7 +8411,7 @@ var CLAUDE_TOOLS, CURSOR_TOOLS2, CODEX_TOOLS2, BUDDY_TOOLS, OPENCODE_TOOLS, MCP_
|
|
|
8297
8411
|
var init_mcp_format = __esm({
|
|
8298
8412
|
"src/resources/mcp-format.ts"() {
|
|
8299
8413
|
"use strict";
|
|
8300
|
-
CLAUDE_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "qoder", "zcode"]);
|
|
8414
|
+
CLAUDE_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "qoder", "kiro", "zcode"]);
|
|
8301
8415
|
CURSOR_TOOLS2 = /* @__PURE__ */ new Set(["cursor"]);
|
|
8302
8416
|
CODEX_TOOLS2 = /* @__PURE__ */ new Set(["codex", "codex-internal", "tcodex"]);
|
|
8303
8417
|
BUDDY_TOOLS = /* @__PURE__ */ new Set(["codebuddy", "workbuddy"]);
|
|
@@ -8354,7 +8468,8 @@ function teamMcpToDef(s) {
|
|
|
8354
8468
|
env: s.env,
|
|
8355
8469
|
timeout: s.timeout,
|
|
8356
8470
|
requires: s.requires,
|
|
8357
|
-
tools: s.tools
|
|
8471
|
+
tools: s.tools,
|
|
8472
|
+
roles: s.roles
|
|
8358
8473
|
};
|
|
8359
8474
|
}
|
|
8360
8475
|
async function parseTeamMcpServers(repoPath) {
|
|
@@ -8380,7 +8495,8 @@ var init_mcp = __esm({
|
|
|
8380
8495
|
env: z4.record(z4.string(), z4.string()).optional(),
|
|
8381
8496
|
timeout: z4.number().int().positive().optional(),
|
|
8382
8497
|
requires: z4.array(z4.string()).optional(),
|
|
8383
|
-
tools: z4.array(z4.string()).optional()
|
|
8498
|
+
tools: z4.array(z4.string()).optional(),
|
|
8499
|
+
roles: z4.array(z4.string()).optional()
|
|
8384
8500
|
}).refine((s) => s.transport === "stdio" ? !!s.command : true, {
|
|
8385
8501
|
message: "stdio transport requires `command`"
|
|
8386
8502
|
}).refine((s) => s.transport === "stdio" ? true : !!s.url, {
|
|
@@ -8703,6 +8819,14 @@ async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
|
|
|
8703
8819
|
return { changes, wrote };
|
|
8704
8820
|
}
|
|
8705
8821
|
const excluded = new Set(localConfig.excludedSkills ?? []);
|
|
8822
|
+
const activeRoles = activeRoleIds(localConfig);
|
|
8823
|
+
if (!removeAll) {
|
|
8824
|
+
await warnUnknownRoleIds(
|
|
8825
|
+
localConfig.repo.localPath,
|
|
8826
|
+
"mcp.yaml",
|
|
8827
|
+
teamDefs.map((def) => ({ kind: "server", name: def.name, roles: def.roles }))
|
|
8828
|
+
);
|
|
8829
|
+
}
|
|
8706
8830
|
const targets = await resolveMcpTargets(teamConfig, localConfig);
|
|
8707
8831
|
if (targets.length === 0) return { changes, wrote };
|
|
8708
8832
|
const dataHome = getDataHome(localConfig);
|
|
@@ -8726,6 +8850,7 @@ async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
|
|
|
8726
8850
|
const desired = /* @__PURE__ */ new Map();
|
|
8727
8851
|
for (const raw of teamDefs) {
|
|
8728
8852
|
if (raw.tools && !raw.tools.includes(target.tool)) continue;
|
|
8853
|
+
if (!matchesRoles(raw.roles, activeRoles)) continue;
|
|
8729
8854
|
if (excluded.has(raw.name)) {
|
|
8730
8855
|
changes.push({ tool: target.tool, server: raw.name, action: "skipped", reason: "excluded by user" });
|
|
8731
8856
|
continue;
|
|
@@ -8878,6 +9003,7 @@ var init_mcp_reconcile = __esm({
|
|
|
8878
9003
|
init_types();
|
|
8879
9004
|
init_mcp_format();
|
|
8880
9005
|
init_mcp();
|
|
9006
|
+
init_roles();
|
|
8881
9007
|
init_fs();
|
|
8882
9008
|
init_logger();
|
|
8883
9009
|
init_mcp_manifest();
|
|
@@ -12991,6 +13117,8 @@ function agentFileExtensionForTool(tool) {
|
|
|
12991
13117
|
case "codex-internal":
|
|
12992
13118
|
case "tcodex":
|
|
12993
13119
|
return ".toml";
|
|
13120
|
+
case "kiro":
|
|
13121
|
+
return ".json";
|
|
12994
13122
|
default:
|
|
12995
13123
|
return ".md";
|
|
12996
13124
|
}
|
|
@@ -13051,6 +13179,35 @@ function renderForJoycode(spec) {
|
|
|
13051
13179
|
content: renderMarkdownAgent(spec, spec.tool_extras?.["joycode"])
|
|
13052
13180
|
};
|
|
13053
13181
|
}
|
|
13182
|
+
function isRecord(value) {
|
|
13183
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13184
|
+
}
|
|
13185
|
+
function isManagedKiroSessionHook(value) {
|
|
13186
|
+
return isRecord(value) && typeof value["command"] === "string" && value["command"].includes("teamai hook-dispatch session-start --tool kiro");
|
|
13187
|
+
}
|
|
13188
|
+
function renderForKiro(spec) {
|
|
13189
|
+
const extras = { ...spec.tool_extras?.["kiro"] ?? {} };
|
|
13190
|
+
const existingHooks = isRecord(extras["hooks"]) ? { ...extras["hooks"] } : {};
|
|
13191
|
+
const existingAgentSpawn = Array.isArray(existingHooks["agentSpawn"]) ? existingHooks["agentSpawn"].filter((entry) => !isManagedKiroSessionHook(entry)) : [];
|
|
13192
|
+
existingHooks["agentSpawn"] = [
|
|
13193
|
+
...existingAgentSpawn,
|
|
13194
|
+
{ command: KIRO_SESSION_START_COMMAND }
|
|
13195
|
+
];
|
|
13196
|
+
extras["hooks"] = existingHooks;
|
|
13197
|
+
const json = {
|
|
13198
|
+
name: spec.name,
|
|
13199
|
+
description: spec.description,
|
|
13200
|
+
prompt: spec.instructions
|
|
13201
|
+
};
|
|
13202
|
+
if (spec.model !== void 0) json["model"] = spec.model;
|
|
13203
|
+
if (spec.tools !== void 0 && spec.tools.length > 0) json["tools"] = spec.tools;
|
|
13204
|
+
Object.assign(json, extras);
|
|
13205
|
+
return {
|
|
13206
|
+
ext: agentFileExtensionForTool("kiro"),
|
|
13207
|
+
content: `${JSON.stringify(json, null, 2)}
|
|
13208
|
+
`
|
|
13209
|
+
};
|
|
13210
|
+
}
|
|
13054
13211
|
function renderForCodex(spec) {
|
|
13055
13212
|
return {
|
|
13056
13213
|
ext: agentFileExtensionForTool("codex"),
|
|
@@ -13178,6 +13335,43 @@ function reverseFromJoycode(filePath, content) {
|
|
|
13178
13335
|
}
|
|
13179
13336
|
return { ok: true, spec };
|
|
13180
13337
|
}
|
|
13338
|
+
function reverseFromKiro(filePath, content) {
|
|
13339
|
+
let parsed;
|
|
13340
|
+
try {
|
|
13341
|
+
const raw = JSON.parse(content);
|
|
13342
|
+
if (!isRecord(raw)) return { ok: false, reason: "agent config must be a JSON object" };
|
|
13343
|
+
parsed = raw;
|
|
13344
|
+
} catch (err) {
|
|
13345
|
+
return { ok: false, reason: `parse error: ${err.message}` };
|
|
13346
|
+
}
|
|
13347
|
+
const name = parsed["name"] ?? path35.basename(filePath, ".json");
|
|
13348
|
+
if (!name) return { ok: false, reason: "missing field name" };
|
|
13349
|
+
if (!parsed["description"]) return { ok: false, reason: "missing field description" };
|
|
13350
|
+
if (!parsed["prompt"]) return { ok: false, reason: "missing field prompt" };
|
|
13351
|
+
const extras = {};
|
|
13352
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
13353
|
+
if (!COMMON_KIRO_FIELDS.has(key)) extras[key] = value;
|
|
13354
|
+
}
|
|
13355
|
+
if (isRecord(extras["hooks"])) {
|
|
13356
|
+
const hooks = { ...extras["hooks"] };
|
|
13357
|
+
if (Array.isArray(hooks["agentSpawn"])) {
|
|
13358
|
+
const remaining = hooks["agentSpawn"].filter((entry) => !isManagedKiroSessionHook(entry));
|
|
13359
|
+
if (remaining.length > 0) hooks["agentSpawn"] = remaining;
|
|
13360
|
+
else delete hooks["agentSpawn"];
|
|
13361
|
+
}
|
|
13362
|
+
if (Object.keys(hooks).length > 0) extras["hooks"] = hooks;
|
|
13363
|
+
else delete extras["hooks"];
|
|
13364
|
+
}
|
|
13365
|
+
const spec = {
|
|
13366
|
+
name,
|
|
13367
|
+
description: parsed["description"],
|
|
13368
|
+
instructions: parsed["prompt"]
|
|
13369
|
+
};
|
|
13370
|
+
if (parsed["model"] !== void 0) spec.model = parsed["model"];
|
|
13371
|
+
if (parsed["tools"] !== void 0) spec.tools = parsed["tools"];
|
|
13372
|
+
if (Object.keys(extras).length > 0) spec.tool_extras = { kiro: extras };
|
|
13373
|
+
return { ok: true, spec };
|
|
13374
|
+
}
|
|
13181
13375
|
function reverseFromCodex(filePath, content) {
|
|
13182
13376
|
let parsed;
|
|
13183
13377
|
try {
|
|
@@ -13326,16 +13520,19 @@ function renderForTool(spec, tool) {
|
|
|
13326
13520
|
return renderForJoycode(spec);
|
|
13327
13521
|
case "qoder":
|
|
13328
13522
|
return renderForClaude(spec);
|
|
13523
|
+
case "kiro":
|
|
13524
|
+
return renderForKiro(spec);
|
|
13329
13525
|
case "zcode":
|
|
13330
13526
|
return renderForClaude(spec);
|
|
13331
13527
|
case "opencode":
|
|
13332
13528
|
return renderForOpencode(spec);
|
|
13333
13529
|
}
|
|
13334
13530
|
}
|
|
13335
|
-
var ALL_SUPPORTED_TOOLS, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_FIELDS, COMMON_OPENCODE_FIELDS, MERGE_COMMON_FIELDS;
|
|
13531
|
+
var ALL_SUPPORTED_TOOLS, KIRO_SESSION_START_COMMAND, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_FIELDS, COMMON_KIRO_FIELDS, COMMON_OPENCODE_FIELDS, MERGE_COMMON_FIELDS;
|
|
13336
13532
|
var init_agent_format = __esm({
|
|
13337
13533
|
"src/resources/agent-format.ts"() {
|
|
13338
13534
|
"use strict";
|
|
13535
|
+
init_builtin_hooks();
|
|
13339
13536
|
ALL_SUPPORTED_TOOLS = [
|
|
13340
13537
|
"claude",
|
|
13341
13538
|
"claude-internal",
|
|
@@ -13347,12 +13544,15 @@ var init_agent_format = __esm({
|
|
|
13347
13544
|
"cursor",
|
|
13348
13545
|
"joycode",
|
|
13349
13546
|
"qoder",
|
|
13547
|
+
"kiro",
|
|
13350
13548
|
"zcode",
|
|
13351
13549
|
"opencode"
|
|
13352
13550
|
];
|
|
13551
|
+
KIRO_SESSION_START_COMMAND = getDispatchCommand("session-start", "kiro");
|
|
13353
13552
|
COMMON_CLAUDE_FIELDS = /* @__PURE__ */ new Set(["name", "description", "model", "tools"]);
|
|
13354
13553
|
COMMON_CURSOR_FIELDS = /* @__PURE__ */ new Set(["agent_id", "description", "model", "tools"]);
|
|
13355
13554
|
COMMON_CODEX_FIELDS = /* @__PURE__ */ new Set(["name", "description", "developer_instructions", "model"]);
|
|
13555
|
+
COMMON_KIRO_FIELDS = /* @__PURE__ */ new Set(["name", "description", "prompt", "model", "tools"]);
|
|
13356
13556
|
COMMON_OPENCODE_FIELDS = /* @__PURE__ */ new Set(["description", "model"]);
|
|
13357
13557
|
MERGE_COMMON_FIELDS = [
|
|
13358
13558
|
"name",
|
|
@@ -13385,7 +13585,7 @@ async function removeStaleAgentSiblings(targetAgentsDir, stem, targetExt) {
|
|
|
13385
13585
|
return;
|
|
13386
13586
|
}
|
|
13387
13587
|
for (const file of files) {
|
|
13388
|
-
const base = file.replace(/\.(md|toml)$/, "");
|
|
13588
|
+
const base = file.replace(/\.(md|toml|json)$/, "");
|
|
13389
13589
|
if (base !== stem) continue;
|
|
13390
13590
|
if (file === `${stem}${targetExt}`) continue;
|
|
13391
13591
|
try {
|
|
@@ -13517,8 +13717,16 @@ function mergeCanonicalEdits(canonical, perTool) {
|
|
|
13517
13717
|
function getAgentStem(filename) {
|
|
13518
13718
|
if (filename.endsWith(".md")) return filename.slice(0, -3);
|
|
13519
13719
|
if (filename.endsWith(".toml")) return filename.slice(0, -5);
|
|
13720
|
+
if (filename.endsWith(".json")) return filename.slice(0, -5);
|
|
13520
13721
|
return null;
|
|
13521
13722
|
}
|
|
13723
|
+
async function removeStaleAgentSiblings2(agentsDir, stem, targetExt) {
|
|
13724
|
+
for (const file of await listFiles(agentsDir)) {
|
|
13725
|
+
if (getAgentStem(file) !== stem || file === `${stem}${targetExt}`) continue;
|
|
13726
|
+
await remove(path37.join(agentsDir, file));
|
|
13727
|
+
log.debug(`Removed stale agent sibling ${file} for ${stem}`);
|
|
13728
|
+
}
|
|
13729
|
+
}
|
|
13522
13730
|
function isKnownTool(tool) {
|
|
13523
13731
|
return ALL_SUPPORTED_TOOLS.includes(tool);
|
|
13524
13732
|
}
|
|
@@ -13551,6 +13759,8 @@ function reverseByTool(tool, filePath, content) {
|
|
|
13551
13759
|
return reverseFromJoycode(filePath, content);
|
|
13552
13760
|
case "qoder":
|
|
13553
13761
|
return reverseFromClaude(filePath, content);
|
|
13762
|
+
case "kiro":
|
|
13763
|
+
return reverseFromKiro(filePath, content);
|
|
13554
13764
|
case "zcode":
|
|
13555
13765
|
return reverseFromClaude(filePath, content);
|
|
13556
13766
|
case "opencode":
|
|
@@ -13847,6 +14057,7 @@ var init_agents = __esm({
|
|
|
13847
14057
|
try {
|
|
13848
14058
|
await ensureDir(destDir);
|
|
13849
14059
|
const { ext, content: rendered } = renderForTool(spec, tool);
|
|
14060
|
+
await removeStaleAgentSiblings2(destDir, item.name, ext);
|
|
13850
14061
|
const dest = path37.join(destDir, `${item.name}${ext}`);
|
|
13851
14062
|
await writeFile(dest, rendered);
|
|
13852
14063
|
log.debug(`Rendered agent ${item.name} \u2192 ${tool} (${ext})`);
|
|
@@ -13874,7 +14085,7 @@ var init_agents = __esm({
|
|
|
13874
14085
|
await this.addTombstone(name, localConfig);
|
|
13875
14086
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13876
14087
|
if (!toolPath.agents) continue;
|
|
13877
|
-
for (const ext of [".md", ".toml"]) {
|
|
14088
|
+
for (const ext of [".md", ".toml", ".json"]) {
|
|
13878
14089
|
const filePath = path37.join(baseDir, toolPath.agents, `${name}${ext}`);
|
|
13879
14090
|
if (await pathExists(filePath)) {
|
|
13880
14091
|
await remove(filePath);
|
|
@@ -14032,7 +14243,7 @@ async function fetchLatestVersion(registry, timeout = VERSION_CHECK_TIMEOUT) {
|
|
|
14032
14243
|
const { stdout } = await execFileAsync2(
|
|
14033
14244
|
npm.cmd,
|
|
14034
14245
|
[...npm.args, "view", pkgName, "version", `--registry=${resolvedRegistry}`],
|
|
14035
|
-
{ timeout, encoding: "utf-8" }
|
|
14246
|
+
{ timeout, encoding: "utf-8", windowsHide: true }
|
|
14036
14247
|
);
|
|
14037
14248
|
const version2 = stdout.trim();
|
|
14038
14249
|
if (!version2) return null;
|
|
@@ -14266,7 +14477,7 @@ async function doUpdate() {
|
|
|
14266
14477
|
...target ? [`--prefix=${target.prefix}`] : [],
|
|
14267
14478
|
`--registry=${registry}`
|
|
14268
14479
|
],
|
|
14269
|
-
{ timeout: INSTALL_TIMEOUT }
|
|
14480
|
+
{ timeout: INSTALL_TIMEOUT, windowsHide: true }
|
|
14270
14481
|
);
|
|
14271
14482
|
log.success(`Updated teamai to v${result.latest}`);
|
|
14272
14483
|
const entry = resolveTeamaiEntryScript();
|
|
@@ -14287,7 +14498,8 @@ async function doUpdate() {
|
|
|
14287
14498
|
try {
|
|
14288
14499
|
const refresh = entry ? { cmd: process.execPath, args: [entry, "hooks", "inject", "--silent"] } : { cmd: "teamai", args: ["hooks", "inject", "--silent"] };
|
|
14289
14500
|
await execFileAsync2(refresh.cmd, refresh.args, {
|
|
14290
|
-
timeout: 15e3
|
|
14501
|
+
timeout: 15e3,
|
|
14502
|
+
windowsHide: true
|
|
14291
14503
|
});
|
|
14292
14504
|
log.success("Refreshed hooks with new version");
|
|
14293
14505
|
} catch (e) {
|
|
@@ -14868,8 +15080,17 @@ import fse8 from "fs-extra";
|
|
|
14868
15080
|
function businessRoot(localConfig) {
|
|
14869
15081
|
return localConfig.repo.businessRepoRoot ?? path42.dirname(localConfig.repo.localPath);
|
|
14870
15082
|
}
|
|
15083
|
+
function reportsGitRoot(localConfig) {
|
|
15084
|
+
if (isSelfMode(localConfig)) {
|
|
15085
|
+
return businessRoot(localConfig);
|
|
15086
|
+
}
|
|
15087
|
+
return localConfig.repo.localPath;
|
|
15088
|
+
}
|
|
14871
15089
|
function reportsWorktreePath(localConfig) {
|
|
14872
|
-
return
|
|
15090
|
+
return getReportsDir(localConfig);
|
|
15091
|
+
}
|
|
15092
|
+
function reportsLockPath(localConfig) {
|
|
15093
|
+
return path42.join(path42.dirname(getReportsDir(localConfig)), REPORTS_LOCK_FILENAME);
|
|
14873
15094
|
}
|
|
14874
15095
|
async function remoteBranchExists2(repoRoot) {
|
|
14875
15096
|
const git = createGit2(repoRoot);
|
|
@@ -14881,10 +15102,22 @@ async function remoteBranchExists2(repoRoot) {
|
|
|
14881
15102
|
}
|
|
14882
15103
|
}
|
|
14883
15104
|
async function ensureReportsWorktree(localConfig, options = {}) {
|
|
15105
|
+
if (!usesReportsBranch(localConfig)) {
|
|
15106
|
+
return getReportsDir(localConfig);
|
|
15107
|
+
}
|
|
14884
15108
|
const wt = reportsWorktreePath(localConfig);
|
|
14885
|
-
const repoRoot =
|
|
15109
|
+
const repoRoot = reportsGitRoot(localConfig);
|
|
15110
|
+
if (!isSelfMode(localConfig) && !await isDedicatedRepoRoot(repoRoot)) {
|
|
15111
|
+
throw new Error(
|
|
15112
|
+
`Refusing to create a reports worktree: ${repoRoot} is not a dedicated team-repo clone root`
|
|
15113
|
+
);
|
|
15114
|
+
}
|
|
14886
15115
|
if (await isGitRepo(wt)) {
|
|
14887
|
-
|
|
15116
|
+
try {
|
|
15117
|
+
await createGit2(wt).revparse(["--is-inside-work-tree"]);
|
|
15118
|
+
return wt;
|
|
15119
|
+
} catch {
|
|
15120
|
+
}
|
|
14888
15121
|
}
|
|
14889
15122
|
if (await pathExists(wt)) {
|
|
14890
15123
|
await fse8.remove(wt);
|
|
@@ -14907,14 +15140,19 @@ async function ensureReportsWorktree(localConfig, options = {}) {
|
|
|
14907
15140
|
await git.raw(["worktree", "add", wt, "--track", "-b", REPORTS_BRANCH, `origin/${REPORTS_BRANCH}`]);
|
|
14908
15141
|
}
|
|
14909
15142
|
} else {
|
|
14910
|
-
await
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
15143
|
+
const branches = await git.branchLocal();
|
|
15144
|
+
if (branches.all.includes(REPORTS_BRANCH)) {
|
|
15145
|
+
await git.raw(["worktree", "add", wt, REPORTS_BRANCH]);
|
|
15146
|
+
} else {
|
|
15147
|
+
await createOrphanWorktree(repoRoot, wt);
|
|
15148
|
+
await writeWorktreeGitignore(wt);
|
|
15149
|
+
const wtGit = createGit2(wt);
|
|
15150
|
+
await wtGit.add([".gitignore"]);
|
|
15151
|
+
await commitSkippingHooks(wtGit, "[teamai] Initialize reports branch");
|
|
15152
|
+
}
|
|
14915
15153
|
if (options.pushIfCreated !== false) {
|
|
14916
15154
|
try {
|
|
14917
|
-
await
|
|
15155
|
+
await createGit2(wt).push(["-u", "origin", REPORTS_BRANCH]);
|
|
14918
15156
|
} catch (e) {
|
|
14919
15157
|
log.debug(`[reports] initial push skipped: ${e.message}`);
|
|
14920
15158
|
}
|
|
@@ -14958,8 +15196,8 @@ async function writeWorktreeGitignore(wt) {
|
|
|
14958
15196
|
].join("\n");
|
|
14959
15197
|
await writeFile(path42.join(wt, ".gitignore"), content);
|
|
14960
15198
|
}
|
|
14961
|
-
async function commitAndPushReports(localConfig, message, files) {
|
|
14962
|
-
const lockPath =
|
|
15199
|
+
async function commitAndPushReports(localConfig, message, files, options = {}) {
|
|
15200
|
+
const lockPath = reportsLockPath(localConfig);
|
|
14963
15201
|
const locked = await acquireLock(lockPath);
|
|
14964
15202
|
if (!locked) {
|
|
14965
15203
|
log.debug("[reports] another reports write is in progress; skipping");
|
|
@@ -14970,11 +15208,11 @@ async function commitAndPushReports(localConfig, message, files) {
|
|
|
14970
15208
|
const git = createGit2(wt);
|
|
14971
15209
|
await git.add(files);
|
|
14972
15210
|
const status2 = await git.status();
|
|
14973
|
-
if (status2.staged.length === 0) {
|
|
15211
|
+
if (status2.staged.length === 0 && !options.pushIfUnchanged) {
|
|
14974
15212
|
log.debug("[reports] nothing to commit");
|
|
14975
15213
|
return false;
|
|
14976
15214
|
}
|
|
14977
|
-
await commitSkippingHooks(git, message);
|
|
15215
|
+
if (status2.staged.length > 0) await commitSkippingHooks(git, message);
|
|
14978
15216
|
for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {
|
|
14979
15217
|
try {
|
|
14980
15218
|
await git.push(["origin", REPORTS_BRANCH]);
|
|
@@ -15004,18 +15242,139 @@ async function commitAndPushReports(localConfig, message, files) {
|
|
|
15004
15242
|
await releaseLock(lockPath);
|
|
15005
15243
|
}
|
|
15006
15244
|
}
|
|
15245
|
+
async function gitPathExists(git, gitPath) {
|
|
15246
|
+
try {
|
|
15247
|
+
const resolved = (await git.raw(["rev-parse", "--git-path", gitPath])).trim();
|
|
15248
|
+
return resolved.length > 0 && await pathExists(resolved);
|
|
15249
|
+
} catch {
|
|
15250
|
+
return false;
|
|
15251
|
+
}
|
|
15252
|
+
}
|
|
15253
|
+
async function rebaseInProgress(git) {
|
|
15254
|
+
return await gitPathExists(git, "rebase-merge") || await gitPathExists(git, "rebase-apply");
|
|
15255
|
+
}
|
|
15256
|
+
async function restoreConflictedFiles(git) {
|
|
15257
|
+
let conflicted = [];
|
|
15258
|
+
try {
|
|
15259
|
+
conflicted = (await git.status()).conflicted ?? [];
|
|
15260
|
+
} catch {
|
|
15261
|
+
return;
|
|
15262
|
+
}
|
|
15263
|
+
if (conflicted.length === 0) {
|
|
15264
|
+
return;
|
|
15265
|
+
}
|
|
15266
|
+
if (await rebaseInProgress(git)) {
|
|
15267
|
+
return;
|
|
15268
|
+
}
|
|
15269
|
+
try {
|
|
15270
|
+
await git.raw(["checkout", "--theirs", "--", ...conflicted]);
|
|
15271
|
+
await git.raw(["add", "--", ...conflicted]);
|
|
15272
|
+
await git.raw(["reset", "HEAD", "--", ...conflicted]);
|
|
15273
|
+
log.debug("[reports] restored uncommitted report files after a stash-apply conflict; using the local copy");
|
|
15274
|
+
} catch (e) {
|
|
15275
|
+
log.debug(`[reports] could not restore stash-apply conflicts: ${e.message}`);
|
|
15276
|
+
try {
|
|
15277
|
+
await git.raw(["reset", "--hard", "HEAD"]);
|
|
15278
|
+
} catch {
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
}
|
|
15282
|
+
async function snapshotDirtyTree(git) {
|
|
15283
|
+
try {
|
|
15284
|
+
const sha = (await git.raw(["stash", "create"])).trim();
|
|
15285
|
+
return sha.length > 0 ? sha : null;
|
|
15286
|
+
} catch {
|
|
15287
|
+
return null;
|
|
15288
|
+
}
|
|
15289
|
+
}
|
|
15290
|
+
async function applyDirtySnapshot(git, sha) {
|
|
15291
|
+
try {
|
|
15292
|
+
await git.raw(["stash", "apply", sha]);
|
|
15293
|
+
} catch {
|
|
15294
|
+
}
|
|
15295
|
+
await restoreConflictedFiles(git);
|
|
15296
|
+
}
|
|
15297
|
+
async function syncReportsWorktree(wt) {
|
|
15298
|
+
const git = createGit2(wt);
|
|
15299
|
+
const upstream = `origin/${REPORTS_BRANCH}`;
|
|
15300
|
+
try {
|
|
15301
|
+
await git.fetch(["origin", REPORTS_BRANCH]);
|
|
15302
|
+
} catch (e) {
|
|
15303
|
+
log.debug(`[reports] fetch failed, using the local copy: ${e.message}`);
|
|
15304
|
+
return;
|
|
15305
|
+
}
|
|
15306
|
+
await restoreConflictedFiles(git);
|
|
15307
|
+
const dirty = !(await git.status()).isClean();
|
|
15308
|
+
const ahead = Number.parseInt((await git.raw(["rev-list", "--count", `${upstream}..HEAD`])).trim(), 10);
|
|
15309
|
+
let carried = null;
|
|
15310
|
+
if (dirty && ahead > 0) {
|
|
15311
|
+
carried = await snapshotDirtyTree(git);
|
|
15312
|
+
if (!carried) {
|
|
15313
|
+
log.debug("[reports] uncommitted report files block the refresh; using the local copy");
|
|
15314
|
+
return;
|
|
15315
|
+
}
|
|
15316
|
+
try {
|
|
15317
|
+
await git.raw(["reset", "--hard", "HEAD"]);
|
|
15318
|
+
} catch (e) {
|
|
15319
|
+
log.debug(`[reports] could not clear the worktree for rebase; using the local copy: ${e.message}`);
|
|
15320
|
+
await applyDirtySnapshot(git, carried);
|
|
15321
|
+
return;
|
|
15322
|
+
}
|
|
15323
|
+
}
|
|
15324
|
+
try {
|
|
15325
|
+
if (ahead > 0) {
|
|
15326
|
+
await git.rebase([upstream]);
|
|
15327
|
+
} else {
|
|
15328
|
+
await git.raw(["merge", "--ff-only", upstream]);
|
|
15329
|
+
}
|
|
15330
|
+
} catch (e) {
|
|
15331
|
+
if (ahead > 0) {
|
|
15332
|
+
try {
|
|
15333
|
+
await git.rebase(["--abort"]);
|
|
15334
|
+
} catch {
|
|
15335
|
+
}
|
|
15336
|
+
}
|
|
15337
|
+
if (carried) {
|
|
15338
|
+
await applyDirtySnapshot(git, carried);
|
|
15339
|
+
log.debug(`[reports] uncommitted report files block the refresh; using the local copy: ${e.message}`);
|
|
15340
|
+
return;
|
|
15341
|
+
}
|
|
15342
|
+
if (dirty) {
|
|
15343
|
+
log.debug(`[reports] uncommitted report files block the refresh; using the local copy: ${e.message}`);
|
|
15344
|
+
return;
|
|
15345
|
+
}
|
|
15346
|
+
log.debug(`[reports] dropping ${ahead} unpushed report commit(s) that conflict with ${upstream}: ${e.message}`);
|
|
15347
|
+
await git.raw(["reset", "--hard", upstream]);
|
|
15348
|
+
return;
|
|
15349
|
+
}
|
|
15350
|
+
if (carried) {
|
|
15351
|
+
await applyDirtySnapshot(git, carried);
|
|
15352
|
+
}
|
|
15353
|
+
}
|
|
15007
15354
|
async function refreshReportsWorktree(localConfig, options = {}) {
|
|
15355
|
+
if (!usesReportsBranch(localConfig)) {
|
|
15356
|
+
return;
|
|
15357
|
+
}
|
|
15358
|
+
const lockPath = reportsLockPath(localConfig);
|
|
15359
|
+
let locked = false;
|
|
15008
15360
|
try {
|
|
15361
|
+
locked = await acquireLock(lockPath);
|
|
15009
15362
|
const wt = await ensureReportsWorktree(localConfig, options);
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15363
|
+
if (!locked) {
|
|
15364
|
+
log.debug("[reports] a reports write is in progress; reading the local copy");
|
|
15365
|
+
return;
|
|
15366
|
+
}
|
|
15367
|
+
await syncReportsWorktree(wt);
|
|
15013
15368
|
} catch (e) {
|
|
15014
15369
|
log.debug(`[reports] refresh skipped: ${e.message}`);
|
|
15370
|
+
} finally {
|
|
15371
|
+
if (locked) {
|
|
15372
|
+
await releaseLock(lockPath);
|
|
15373
|
+
}
|
|
15015
15374
|
}
|
|
15016
15375
|
}
|
|
15017
15376
|
async function ensureReportsDir(localConfig) {
|
|
15018
|
-
if (localConfig
|
|
15377
|
+
if (usesReportsBranch(localConfig)) {
|
|
15019
15378
|
return ensureReportsWorktree(localConfig);
|
|
15020
15379
|
}
|
|
15021
15380
|
return getReportsDir(localConfig);
|
|
@@ -15139,10 +15498,11 @@ async function listMembers(options) {
|
|
|
15139
15498
|
const projectConfig = await detectProjectConfig();
|
|
15140
15499
|
const localConfig = projectConfig ?? (await requireInit()).localConfig;
|
|
15141
15500
|
let repoPath;
|
|
15142
|
-
|
|
15501
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
15502
|
+
if (usesReportsBranch2(localConfig)) {
|
|
15143
15503
|
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
15144
|
-
await refreshReportsWorktree2(localConfig);
|
|
15145
|
-
repoPath = await ensureReportsWorktree2(localConfig);
|
|
15504
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
15505
|
+
repoPath = await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
15146
15506
|
} else {
|
|
15147
15507
|
repoPath = localConfig.repo.localPath;
|
|
15148
15508
|
await pullRepo(repoPath);
|
|
@@ -15834,12 +16194,12 @@ async function initSelfRepo(options) {
|
|
|
15834
16194
|
return;
|
|
15835
16195
|
}
|
|
15836
16196
|
const provider = getProvider(providerName);
|
|
15837
|
-
log.debug(`Detected provider: ${providerName} (from ${remoteUrl})`);
|
|
16197
|
+
log.debug(`Detected provider: ${providerName} (from ${redactGitCredentials(remoteUrl)})`);
|
|
15838
16198
|
let repoInfo;
|
|
15839
16199
|
try {
|
|
15840
|
-
repoInfo = provider.parseRepoInput(remoteUrl);
|
|
16200
|
+
repoInfo = providerName === "git" ? parseGenericGitExistingRemote(remoteUrl) : provider.parseRepoInput(remoteUrl);
|
|
15841
16201
|
} catch (e) {
|
|
15842
|
-
log.error(`Could not parse the business repo remote "${remoteUrl}": ${e.message}`);
|
|
16202
|
+
log.error(`Could not parse the business repo remote "${redactGitCredentials(remoteUrl)}": ${e.message}`);
|
|
15843
16203
|
process.exit(1);
|
|
15844
16204
|
return;
|
|
15845
16205
|
}
|
|
@@ -16242,6 +16602,7 @@ async function init(options) {
|
|
|
16242
16602
|
const emailDomain = provider.getDefaultEmailDomain() ?? void 0;
|
|
16243
16603
|
await configureGitUser(localPath, username, username, void 0, emailDomain);
|
|
16244
16604
|
const teamConfig = await loadTeamConfig(localPath);
|
|
16605
|
+
const createdSkeleton = !teamConfig;
|
|
16245
16606
|
if (!teamConfig) {
|
|
16246
16607
|
log.warn("teamai.yaml not found in repo. Creating default config...");
|
|
16247
16608
|
const defaultConfig = YAML11.stringify({
|
|
@@ -16271,33 +16632,59 @@ async function init(options) {
|
|
|
16271
16632
|
log.error(error.message);
|
|
16272
16633
|
process.exit(1);
|
|
16273
16634
|
}
|
|
16274
|
-
const
|
|
16275
|
-
|
|
16276
|
-
const existingMember = await getMemberConfig(localPath, username);
|
|
16277
|
-
const { config: memberConfig, changed: memberChanged } = mergeMemberConfig(existingMember, {
|
|
16635
|
+
const reportsConfig = {
|
|
16636
|
+
repo: { localPath, remote: repoInfo.httpsUrl },
|
|
16278
16637
|
username,
|
|
16279
|
-
|
|
16280
|
-
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16284
|
-
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
|
|
16289
|
-
|
|
16290
|
-
|
|
16291
|
-
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
|
|
16638
|
+
scope,
|
|
16639
|
+
projectRoot,
|
|
16640
|
+
additionalRoles: []
|
|
16641
|
+
};
|
|
16642
|
+
if (createdSkeleton && !options.dryRun) {
|
|
16643
|
+
try {
|
|
16644
|
+
await pushRepoDirectly(localPath, "[teamai] Initialize team repo skeleton", [
|
|
16645
|
+
"teamai.yaml",
|
|
16646
|
+
"skills/.gitkeep",
|
|
16647
|
+
"rules/.gitkeep",
|
|
16648
|
+
"docs/.gitkeep",
|
|
16649
|
+
"env/.gitkeep",
|
|
16650
|
+
"members/.gitkeep"
|
|
16651
|
+
]);
|
|
16652
|
+
} catch (e) {
|
|
16653
|
+
log.warn(`Push failed (you can push manually later): ${e.message}`);
|
|
16654
|
+
}
|
|
16655
|
+
}
|
|
16656
|
+
let isNewMember = true;
|
|
16657
|
+
if (!options.dryRun) {
|
|
16658
|
+
try {
|
|
16659
|
+
const { ensureReportsWorktree: ensureReportsWorktree2, commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
16660
|
+
const wt = await ensureReportsWorktree2(reportsConfig);
|
|
16661
|
+
const memberDir = path45.join(wt, "members");
|
|
16662
|
+
await ensureDir(memberDir);
|
|
16663
|
+
const memberPath = path45.join(memberDir, `${username}.yaml`);
|
|
16664
|
+
isNewMember = !await pathExists(memberPath);
|
|
16665
|
+
const existingMember = await getMemberConfig(wt, username);
|
|
16666
|
+
const { config: memberConfig, changed: memberChanged } = mergeMemberConfig(existingMember, {
|
|
16667
|
+
username,
|
|
16668
|
+
projects: resolvedProjects
|
|
16669
|
+
});
|
|
16670
|
+
if (memberChanged) {
|
|
16671
|
+
await writeFile(memberPath, YAML11.stringify(memberConfig));
|
|
16672
|
+
log.success(isNewMember ? `Registered as team member: ${username}` : `Updated member roster: ${username}${memberConfig.projects ? ` (projects: ${memberConfig.projects.join(", ")})` : ""}`);
|
|
16673
|
+
const pushed = await commitAndPushReports2(reportsConfig, isNewMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`, ["members/"]);
|
|
16674
|
+
if (pushed) {
|
|
16675
|
+
log.success(isNewMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
16676
|
+
} else {
|
|
16677
|
+
log.warn("Member registration could not be pushed (no write access?). You are still set up locally.");
|
|
16678
|
+
}
|
|
16679
|
+
} else {
|
|
16680
|
+
log.info(`Member ${username} already registered`);
|
|
16681
|
+
isNewMember = false;
|
|
16297
16682
|
}
|
|
16683
|
+
} catch (e) {
|
|
16684
|
+
log.warn(`Member registration skipped (non-blocking): ${e.message}`);
|
|
16298
16685
|
}
|
|
16299
16686
|
} else {
|
|
16300
|
-
log.info(`
|
|
16687
|
+
log.info(`[dry-run] Would register member ${username} on the teamai-reports branch`);
|
|
16301
16688
|
}
|
|
16302
16689
|
const currentConfig = await loadTeamConfig(localPath);
|
|
16303
16690
|
const hasReviewers = currentConfig?.reviewers && currentConfig.reviewers.length > 0;
|
|
@@ -16425,6 +16812,7 @@ var init_init = __esm({
|
|
|
16425
16812
|
init_git2();
|
|
16426
16813
|
init_git2();
|
|
16427
16814
|
init_providers();
|
|
16815
|
+
init_repo_url5();
|
|
16428
16816
|
init_fs();
|
|
16429
16817
|
init_logger();
|
|
16430
16818
|
init_types();
|
|
@@ -19700,6 +20088,12 @@ function mergeDeltas(local, remote) {
|
|
|
19700
20088
|
votes[docId].last_upvoted_at = localEntry.last_upvoted_at;
|
|
19701
20089
|
}
|
|
19702
20090
|
}
|
|
20091
|
+
if (votes[docId].recalled_count === 0) {
|
|
20092
|
+
votes[docId].last_recalled_at = "";
|
|
20093
|
+
}
|
|
20094
|
+
if (votes[docId].upvoted_count === 0) {
|
|
20095
|
+
delete votes[docId].last_upvoted_at;
|
|
20096
|
+
}
|
|
19703
20097
|
}
|
|
19704
20098
|
return { version: 2, votes, deltas: {} };
|
|
19705
20099
|
}
|
|
@@ -19739,11 +20133,16 @@ async function recallFeedback(opts) {
|
|
|
19739
20133
|
return;
|
|
19740
20134
|
}
|
|
19741
20135
|
const existingDelta = data.deltas[opts.negative] ?? { recalled_delta: 0, upvoted_delta: 0 };
|
|
20136
|
+
const decrementedCount = entry.upvoted_count - 1;
|
|
20137
|
+
const updatedEntry = { ...entry, upvoted_count: decrementedCount };
|
|
20138
|
+
if (decrementedCount === 0) {
|
|
20139
|
+
delete updatedEntry.last_upvoted_at;
|
|
20140
|
+
}
|
|
19742
20141
|
const updated = {
|
|
19743
20142
|
...data,
|
|
19744
20143
|
votes: {
|
|
19745
20144
|
...data.votes,
|
|
19746
|
-
[opts.negative]:
|
|
20145
|
+
[opts.negative]: updatedEntry
|
|
19747
20146
|
},
|
|
19748
20147
|
deltas: {
|
|
19749
20148
|
...data.deltas,
|
|
@@ -21473,10 +21872,11 @@ async function generateDigest(options) {
|
|
|
21473
21872
|
const localConfig = projectConfig ?? (await requireInit()).localConfig;
|
|
21474
21873
|
const repoPath = localConfig.repo.localPath;
|
|
21475
21874
|
let reportsRoot = repoPath;
|
|
21476
|
-
|
|
21875
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
21876
|
+
if (usesReportsBranch2(localConfig)) {
|
|
21477
21877
|
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
21478
|
-
await refreshReportsWorktree2(localConfig);
|
|
21479
|
-
reportsRoot = await ensureReportsWorktree2(localConfig);
|
|
21878
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
21879
|
+
reportsRoot = await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
21480
21880
|
}
|
|
21481
21881
|
const teamStats = await loadTeamStats(reportsRoot);
|
|
21482
21882
|
if (teamStats.length === 0) {
|
|
@@ -21629,9 +22029,10 @@ async function loadReportedStats() {
|
|
|
21629
22029
|
const config = await detectProjectConfig() ?? await loadLocalConfig();
|
|
21630
22030
|
if (!config) return null;
|
|
21631
22031
|
let statsRoot = config.repo.localPath;
|
|
21632
|
-
|
|
22032
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
22033
|
+
if (usesReportsBranch2(config)) {
|
|
21633
22034
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
21634
|
-
statsRoot = await ensureReportsWorktree2(config);
|
|
22035
|
+
statsRoot = await ensureReportsWorktree2(config, { pushIfCreated: false });
|
|
21635
22036
|
}
|
|
21636
22037
|
const statsPath = path62.join(statsRoot, "stats", `${config.username}.yaml`);
|
|
21637
22038
|
const content = await readFileSafe(statsPath);
|
|
@@ -22105,8 +22506,9 @@ function filterEventsByScope(events, opts) {
|
|
|
22105
22506
|
return events;
|
|
22106
22507
|
}
|
|
22107
22508
|
async function reportUsageToTeam(repoPath, username, options) {
|
|
22108
|
-
const
|
|
22109
|
-
const
|
|
22509
|
+
const reportsConfig = options?.selfConfig;
|
|
22510
|
+
const useReportsBranch = !!reportsConfig && usesReportsBranch(reportsConfig);
|
|
22511
|
+
let restoreStats;
|
|
22110
22512
|
try {
|
|
22111
22513
|
const events = await readUsageEvents();
|
|
22112
22514
|
const filesToPush = [];
|
|
@@ -22136,19 +22538,19 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22136
22538
|
const hasPromptTokens = hasPromptTokenDelta(promptTokenDelta);
|
|
22137
22539
|
const hasDaily = hasDailyDelta(dailyDelta);
|
|
22138
22540
|
let writeRoot = repoPath;
|
|
22139
|
-
if (
|
|
22541
|
+
if (useReportsBranch && reportsConfig) {
|
|
22140
22542
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22141
|
-
writeRoot = await ensureReportsWorktree2(
|
|
22543
|
+
writeRoot = await ensureReportsWorktree2(reportsConfig);
|
|
22142
22544
|
} else {
|
|
22143
22545
|
const git = createGit2(repoPath);
|
|
22144
22546
|
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
22145
22547
|
log.debug(`Skipping report: ${repoPath} is not a dedicated team-repo root (safety guard)`);
|
|
22146
|
-
return;
|
|
22548
|
+
return false;
|
|
22147
22549
|
}
|
|
22148
22550
|
const { isImportInProgress: isImportInProgress2 } = await Promise.resolve().then(() => (init_import_lock(), import_lock_exports));
|
|
22149
22551
|
if (await isImportInProgress2(repoPath)) {
|
|
22150
22552
|
log.debug(`Skipping report: import in progress for ${repoPath} (would reset uncommitted artifacts)`);
|
|
22151
|
-
return;
|
|
22553
|
+
return false;
|
|
22152
22554
|
}
|
|
22153
22555
|
const yamlPath = path64.join(repoPath, "teamai.yaml");
|
|
22154
22556
|
const workingContent = await readFileSafe(yamlPath);
|
|
@@ -22168,6 +22570,10 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22168
22570
|
await ensureDir(statsDir);
|
|
22169
22571
|
const statsPath = path64.join(statsDir, `${username}.yaml`);
|
|
22170
22572
|
const existing = await readExistingStats(statsPath);
|
|
22573
|
+
if (useReportsBranch) {
|
|
22574
|
+
const previousContent = await readFileSafe(statsPath);
|
|
22575
|
+
restoreStats = () => writeFile(statsPath, previousContent ?? "");
|
|
22576
|
+
}
|
|
22171
22577
|
const newStats = hasUsage ? aggregateUsage(events) : [];
|
|
22172
22578
|
const merged = mergeStats(existing, username, newStats);
|
|
22173
22579
|
if (hasInterventions) {
|
|
@@ -22197,23 +22603,20 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22197
22603
|
}
|
|
22198
22604
|
if (filesToPush.length === 0) {
|
|
22199
22605
|
log.debug("No usage events or votes to report");
|
|
22200
|
-
return;
|
|
22606
|
+
return true;
|
|
22201
22607
|
}
|
|
22202
22608
|
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 (
|
|
22609
|
+
if (useReportsBranch && reportsConfig) {
|
|
22204
22610
|
const { commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22205
|
-
await
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22611
|
+
if (!await commitAndPushReports2(reportsConfig, commitMsg, filesToPush, { pushIfUnchanged: true })) {
|
|
22612
|
+
log.debug("Auto-report push was not confirmed; keeping local report data");
|
|
22613
|
+
await restoreStats?.();
|
|
22614
|
+
return false;
|
|
22615
|
+
}
|
|
22210
22616
|
} else {
|
|
22211
|
-
await
|
|
22212
|
-
pushRepoDirectly(repoPath, commitMsg, filesToPush),
|
|
22213
|
-
5e3,
|
|
22214
|
-
"Auto-report timeout (5s)"
|
|
22215
|
-
);
|
|
22617
|
+
await pushRepoDirectly(repoPath, commitMsg, filesToPush);
|
|
22216
22618
|
}
|
|
22619
|
+
restoreStats = void 0;
|
|
22217
22620
|
if (hasUsage && !options?.skipTruncate) {
|
|
22218
22621
|
await truncateUsageAfterReport(events.length);
|
|
22219
22622
|
log.debug(`Reported ${events.length} usage events to team repo`);
|
|
@@ -22238,8 +22641,15 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22238
22641
|
if (!hasUsage && !hasInterventions && !hasPromptTokens && !hasDaily) {
|
|
22239
22642
|
log.debug("Pushed pending votes to team repo");
|
|
22240
22643
|
}
|
|
22644
|
+
return true;
|
|
22241
22645
|
} catch (e) {
|
|
22646
|
+
try {
|
|
22647
|
+
await restoreStats?.();
|
|
22648
|
+
} catch (restoreError) {
|
|
22649
|
+
log.error(`Could not restore report stats after failure: ${restoreError.message}`);
|
|
22650
|
+
}
|
|
22242
22651
|
log.error(`Auto-report skipped: ${e.message}`);
|
|
22652
|
+
return false;
|
|
22243
22653
|
}
|
|
22244
22654
|
}
|
|
22245
22655
|
var init_team_push = __esm({
|
|
@@ -22249,7 +22659,6 @@ var init_team_push = __esm({
|
|
|
22249
22659
|
init_stats();
|
|
22250
22660
|
init_dashboard_collector();
|
|
22251
22661
|
init_git2();
|
|
22252
|
-
init_async();
|
|
22253
22662
|
init_fs();
|
|
22254
22663
|
init_logger();
|
|
22255
22664
|
init_types();
|
|
@@ -22693,11 +23102,6 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22693
23102
|
const scopeLabel = localConfig.scope;
|
|
22694
23103
|
const revisionField = policy.revisionField ?? "lastPullRev";
|
|
22695
23104
|
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
23105
|
const pullSpin = spinner(`[${scopeLabel}] Pulling team repo...`).start();
|
|
22702
23106
|
let currentRev = null;
|
|
22703
23107
|
let reportingOnly = false;
|
|
@@ -22714,39 +23118,39 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22714
23118
|
pullSpin.fail(`[${scopeLabel}] Pull failed: ${e.message}`);
|
|
22715
23119
|
return;
|
|
22716
23120
|
}
|
|
23121
|
+
const freshConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
23122
|
+
if (!freshConfig) {
|
|
23123
|
+
log.warn(`[${scopeLabel}] Team config (teamai.yaml) not found. Skipping.`);
|
|
23124
|
+
return;
|
|
23125
|
+
}
|
|
22717
23126
|
let currentTargets = null;
|
|
22718
23127
|
if (!options.force && !options.dryRun && !submodulesChanged) {
|
|
22719
23128
|
try {
|
|
22720
23129
|
const state = await loadStateForScope(localConfig);
|
|
22721
23130
|
if (currentRev && state[revisionField] && state[revisionField] === currentRev) {
|
|
22722
|
-
currentTargets = await getInstalledResourceTargets(
|
|
23131
|
+
currentTargets = await getInstalledResourceTargets(freshConfig, localConfig);
|
|
22723
23132
|
const previousTargets = state[targetsField];
|
|
22724
23133
|
const syncedTargets = new Set(previousTargets ?? []);
|
|
22725
23134
|
const targetSetMatches = previousTargets !== void 0 && previousTargets.length === currentTargets.length && currentTargets.every((target) => syncedTargets.has(target));
|
|
22726
23135
|
if (targetSetMatches) {
|
|
22727
23136
|
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
|
-
}
|
|
23137
|
+
const skipRecall = !isRecallEnabled(localConfig, freshConfig);
|
|
23138
|
+
try {
|
|
23139
|
+
const { deployBuiltinAgents: deployBuiltinAgents2 } = await Promise.resolve().then(() => (init_builtin_agents(), builtin_agents_exports));
|
|
23140
|
+
await deployBuiltinAgents2(freshConfig, localConfig, { skipRecall });
|
|
23141
|
+
} catch {
|
|
23142
|
+
}
|
|
23143
|
+
try {
|
|
23144
|
+
const { deployBuiltinRules: deployBuiltinRules2 } = await Promise.resolve().then(() => (init_builtin_rules(), builtin_rules_exports));
|
|
23145
|
+
await deployBuiltinRules2(freshConfig, localConfig, { skipRecall });
|
|
23146
|
+
} catch {
|
|
23147
|
+
}
|
|
23148
|
+
try {
|
|
23149
|
+
const { deployBuiltinSkills: deployBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
23150
|
+
await deployBuiltinSkills2(freshConfig, localConfig, { reportingOnly, skipRecall });
|
|
23151
|
+
} catch {
|
|
22749
23152
|
}
|
|
23153
|
+
await injectRecallBlockIntoTools(freshConfig, localConfig, scopeLabel);
|
|
22750
23154
|
return;
|
|
22751
23155
|
}
|
|
22752
23156
|
log.debug(`[${scopeLabel}] Repo unchanged; resource target set changed, syncing`);
|
|
@@ -22755,11 +23159,6 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22755
23159
|
log.debug(`[${scopeLabel}] Rev check failed, proceeding with full sync`);
|
|
22756
23160
|
}
|
|
22757
23161
|
}
|
|
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
23162
|
let roleContext = null;
|
|
22764
23163
|
try {
|
|
22765
23164
|
roleContext = await buildRolePullContext(localConfig);
|
|
@@ -22962,21 +23361,29 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
22962
23361
|
if (totalSynced === 0) {
|
|
22963
23362
|
log.info(`[${scopeLabel}] No resources to sync`);
|
|
22964
23363
|
}
|
|
23364
|
+
let reportsReadRoot;
|
|
23365
|
+
const resolveReportsReadRoot = () => {
|
|
23366
|
+
reportsReadRoot ??= (async () => {
|
|
23367
|
+
if (!usesReportsBranch(localConfig)) return localConfig.repo.localPath;
|
|
23368
|
+
try {
|
|
23369
|
+
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
23370
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
23371
|
+
return await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
23372
|
+
} catch (e) {
|
|
23373
|
+
log.debug(`reports worktree unavailable: ${e.message}`);
|
|
23374
|
+
return void 0;
|
|
23375
|
+
}
|
|
23376
|
+
})();
|
|
23377
|
+
return reportsReadRoot;
|
|
23378
|
+
};
|
|
22965
23379
|
if (!options.dryRun) {
|
|
22966
23380
|
try {
|
|
22967
23381
|
const learningsRepoDir = path66.join(localConfig.repo.localPath, "learnings");
|
|
22968
23382
|
const docsRepoDir = path66.join(localConfig.repo.localPath, "docs");
|
|
22969
23383
|
const rulesRepoDir = path66.join(localConfig.repo.localPath, "rules");
|
|
22970
23384
|
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
|
-
}
|
|
23385
|
+
const reportsRoot = await resolveReportsReadRoot();
|
|
23386
|
+
const votesDir = reportsRoot ? path66.join(reportsRoot, "votes") : void 0;
|
|
22980
23387
|
const activeLearningsNamespaces = roleContext?.activeNamespaces.learnings ?? [];
|
|
22981
23388
|
const countLearnings = async (baseDir) => {
|
|
22982
23389
|
let n = (await listFiles(baseDir)).filter((f) => f.endsWith(".md")).length;
|
|
@@ -23010,7 +23417,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23010
23417
|
const repoCodebaseDir = path66.join(localConfig.repo.localPath, "docs", "team-codebase");
|
|
23011
23418
|
const effectiveCodebaseDir = await pathExists(repoCodebaseDir) ? repoCodebaseDir : void 0;
|
|
23012
23419
|
if (hasAnySource || effectiveCodebaseDir) {
|
|
23013
|
-
const votesExist = await pathExists(votesDir);
|
|
23420
|
+
const votesExist = votesDir ? await pathExists(votesDir) : false;
|
|
23014
23421
|
const teamaiHome = getDataHome(localConfig);
|
|
23015
23422
|
const indexPath = path66.join(teamaiHome, "search-index.json");
|
|
23016
23423
|
const { buildIndex: buildIndex2 } = await Promise.resolve().then(() => (init_search_index(), search_index_exports));
|
|
@@ -23156,16 +23563,9 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23156
23563
|
const YAML25 = (await import("yaml")).default;
|
|
23157
23564
|
const { listFiles: listFiles2, readFileSafe: readFileSafe5 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
23158
23565
|
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);
|
|
23566
|
+
const reportsRoot = await resolveReportsReadRoot();
|
|
23567
|
+
const statsDir = reportsRoot ? path66.join(reportsRoot, "stats") : void 0;
|
|
23568
|
+
const files = statsDir ? await listFiles2(statsDir) : [];
|
|
23169
23569
|
const teamStats = [];
|
|
23170
23570
|
for (const file of files) {
|
|
23171
23571
|
if (!file.endsWith(".yaml")) continue;
|
|
@@ -23394,6 +23794,7 @@ async function pull(options) {
|
|
|
23394
23794
|
const needsHookMigration = await legacyHooksNeedReinject().catch(() => false);
|
|
23395
23795
|
const contended = /* @__PURE__ */ new Set();
|
|
23396
23796
|
const heldLocks = /* @__PURE__ */ new Map();
|
|
23797
|
+
let usageReport;
|
|
23397
23798
|
const lockScope = async (config) => {
|
|
23398
23799
|
if (config.repo.kind === "http") return true;
|
|
23399
23800
|
const lock = path66.join(getDataHome(config), SYNC_LOCK_FILENAME);
|
|
@@ -23469,49 +23870,62 @@ async function pull(options) {
|
|
|
23469
23870
|
await reconcileHooksAllScopes(reconcileUser, reconcileProject, options);
|
|
23470
23871
|
await reconcileMcpAllScopes(reconcileUser, reconcileProject, options);
|
|
23471
23872
|
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
|
-
|
|
23873
|
+
if (!options.dryRun && !pendingUsageReport) {
|
|
23874
|
+
pendingUsageReport = (async () => {
|
|
23875
|
+
try {
|
|
23876
|
+
const { reportUsageToTeam: reportUsageToTeam2 } = await Promise.resolve().then(() => (init_team_push(), team_push_exports));
|
|
23877
|
+
const { truncateUsageAfterReport: truncateUsageAfterReport2, readUsageEvents: readUsageEvents2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
23878
|
+
const targets = [];
|
|
23879
|
+
if (reconcileProject && reconcileProject.repo.kind !== "http" && !await usageReportDisabled(reconcileProject.repo.localPath)) {
|
|
23880
|
+
targets.push({
|
|
23881
|
+
repoPath: reconcileProject.repo.localPath,
|
|
23882
|
+
username: reconcileProject.username,
|
|
23883
|
+
opts: {
|
|
23884
|
+
skipTruncate: true,
|
|
23885
|
+
projectRoot: reconcileProject.projectRoot,
|
|
23886
|
+
// Non-HTTP repos route stats/votes to the teamai-reports orphan branch.
|
|
23887
|
+
selfConfig: reconcileProject
|
|
23888
|
+
}
|
|
23889
|
+
});
|
|
23890
|
+
}
|
|
23891
|
+
if (reconcileUser && reconcileUser.repo.kind !== "http" && !await usageReportDisabled(reconcileUser.repo.localPath)) {
|
|
23892
|
+
targets.push({
|
|
23893
|
+
repoPath: reconcileUser.repo.localPath,
|
|
23894
|
+
username: reconcileUser.username,
|
|
23895
|
+
opts: {
|
|
23896
|
+
skipTruncate: true,
|
|
23897
|
+
excludeProjectRoots: projectConfig?.projectRoot ? [projectConfig.projectRoot] : [],
|
|
23898
|
+
// Non-HTTP repos route stats/votes to the teamai-reports orphan branch —
|
|
23899
|
+
// never reset/pull the default branch (or, in self mode, the business tree).
|
|
23900
|
+
selfConfig: reconcileUser
|
|
23901
|
+
}
|
|
23902
|
+
});
|
|
23903
|
+
}
|
|
23904
|
+
const eventCount = (await readUsageEvents2()).length;
|
|
23905
|
+
let allReported = true;
|
|
23906
|
+
for (const t of targets) {
|
|
23907
|
+
try {
|
|
23908
|
+
const reported = await reportUsageToTeam2(t.repoPath, t.username, t.opts);
|
|
23909
|
+
if (!reported) allReported = false;
|
|
23910
|
+
} catch (e) {
|
|
23911
|
+
allReported = false;
|
|
23912
|
+
log.error(`Auto-report to ${t.repoPath} skipped: ${e.message}`);
|
|
23499
23913
|
}
|
|
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
23914
|
}
|
|
23915
|
+
if (allReported && eventCount > 0 && targets.length > 0) {
|
|
23916
|
+
await truncateUsageAfterReport2(eventCount);
|
|
23917
|
+
}
|
|
23918
|
+
} catch (e) {
|
|
23919
|
+
log.debug(`Auto-report skipped: ${e.message}`);
|
|
23509
23920
|
}
|
|
23510
|
-
|
|
23511
|
-
|
|
23512
|
-
|
|
23921
|
+
})().finally(() => {
|
|
23922
|
+
pendingUsageReport = void 0;
|
|
23923
|
+
});
|
|
23924
|
+
usageReport = pendingUsageReport;
|
|
23925
|
+
try {
|
|
23926
|
+
await withTimeout(pendingUsageReport, 5e3, "Auto-report is still running after 5s");
|
|
23513
23927
|
} catch (e) {
|
|
23514
|
-
log.debug(
|
|
23928
|
+
log.debug(e.message);
|
|
23515
23929
|
}
|
|
23516
23930
|
}
|
|
23517
23931
|
const sourceConfig = reconcileProject ?? reconcileUser;
|
|
@@ -23524,8 +23938,15 @@ async function pull(options) {
|
|
|
23524
23938
|
}
|
|
23525
23939
|
}
|
|
23526
23940
|
} finally {
|
|
23527
|
-
|
|
23528
|
-
await releaseLock(lock);
|
|
23941
|
+
const releaseSyncLocks = async () => {
|
|
23942
|
+
for (const lock of heldLocks.values()) await releaseLock(lock);
|
|
23943
|
+
};
|
|
23944
|
+
if (usageReport && usageReport === pendingUsageReport) {
|
|
23945
|
+
void usageReport.then(releaseSyncLocks, releaseSyncLocks).catch((e) => {
|
|
23946
|
+
log.error(`Could not release report sync locks: ${e.message}`);
|
|
23947
|
+
});
|
|
23948
|
+
} else {
|
|
23949
|
+
await releaseSyncLocks();
|
|
23529
23950
|
}
|
|
23530
23951
|
}
|
|
23531
23952
|
}
|
|
@@ -23600,7 +24021,7 @@ async function reconcileCoAuthorAllScopes(userConfig, projectConfig, options) {
|
|
|
23600
24021
|
}
|
|
23601
24022
|
}
|
|
23602
24023
|
}
|
|
23603
|
-
var CONTRIBUTORS_FILE2;
|
|
24024
|
+
var pendingUsageReport, CONTRIBUTORS_FILE2;
|
|
23604
24025
|
var init_pull = __esm({
|
|
23605
24026
|
"src/pull.ts"() {
|
|
23606
24027
|
"use strict";
|
|
@@ -23621,6 +24042,7 @@ var init_pull = __esm({
|
|
|
23621
24042
|
init_home();
|
|
23622
24043
|
init_update();
|
|
23623
24044
|
init_learnings_mirror();
|
|
24045
|
+
init_async();
|
|
23624
24046
|
CONTRIBUTORS_FILE2 = "CONTRIBUTORS";
|
|
23625
24047
|
}
|
|
23626
24048
|
});
|
|
@@ -25120,21 +25542,22 @@ async function projectsSet(ids, _options) {
|
|
|
25120
25542
|
}
|
|
25121
25543
|
async function projectsMembers(projectId, _options) {
|
|
25122
25544
|
const { localConfig } = await autoDetectInit();
|
|
25123
|
-
|
|
25124
|
-
|
|
25545
|
+
const knowledgePath = localConfig.repo.localPath;
|
|
25546
|
+
let membersRoot = knowledgePath;
|
|
25547
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
25548
|
+
if (usesReportsBranch2(localConfig)) {
|
|
25125
25549
|
const { ensureReportsWorktree: ensureReportsWorktree2, refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
25126
|
-
await refreshReportsWorktree2(localConfig);
|
|
25127
|
-
|
|
25550
|
+
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
25551
|
+
membersRoot = await ensureReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
25128
25552
|
} else {
|
|
25129
|
-
|
|
25130
|
-
await pullRepo(repoPath).catch(() => {
|
|
25553
|
+
await pullRepo(knowledgePath).catch(() => {
|
|
25131
25554
|
});
|
|
25132
25555
|
}
|
|
25133
|
-
const manifest = await loadProjectsManifest(
|
|
25556
|
+
const manifest = await loadProjectsManifest(knowledgePath);
|
|
25134
25557
|
if (manifest && !listProjectIds(manifest).includes(projectId)) {
|
|
25135
25558
|
log.warn(`Project "${projectId}" is not defined in manifest/projects.yaml.`);
|
|
25136
25559
|
}
|
|
25137
|
-
const membersDir = path72.join(
|
|
25560
|
+
const membersDir = path72.join(membersRoot, "members");
|
|
25138
25561
|
const files = (await listFiles(membersDir)).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
25139
25562
|
const members = [];
|
|
25140
25563
|
for (const file of files) {
|
|
@@ -25513,8 +25936,8 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
25513
25936
|
const agentsDir = path74.join(baseDir, toolPath.agents);
|
|
25514
25937
|
if (await pathExists(agentsDir)) {
|
|
25515
25938
|
for (const file of await listFiles(agentsDir)) {
|
|
25516
|
-
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
25517
|
-
const name = path74.basename(file).replace(/\.(md|toml)$/, "");
|
|
25939
|
+
if (!file.endsWith(".md") && !file.endsWith(".toml") && !file.endsWith(".json")) continue;
|
|
25940
|
+
const name = path74.basename(file).replace(/\.(md|toml|json)$/, "");
|
|
25518
25941
|
if (!teamAgentNames.has(name) && !BUILTIN_AGENT_NAMES.has(name)) continue;
|
|
25519
25942
|
res.agentFiles.push(path74.join(agentsDir, file));
|
|
25520
25943
|
}
|
|
@@ -26209,7 +26632,8 @@ async function hooksList(_options) {
|
|
|
26209
26632
|
for (const d of teamDefs) {
|
|
26210
26633
|
const matcher = d.matcher ? ` [${d.matcher}]` : "";
|
|
26211
26634
|
const tools = d.tools && d.tools.length > 0 ? d.tools.join(",") : "all";
|
|
26212
|
-
|
|
26635
|
+
const roles = d.roles ? `, roles: ${d.roles.length > 0 ? d.roles.join(",") : "nobody"}` : "";
|
|
26636
|
+
console.log(` [${d.key}] ${d.event}${matcher} \u2192 ${d.command} (tools: ${tools}${roles})`);
|
|
26213
26637
|
}
|
|
26214
26638
|
}
|
|
26215
26639
|
console.log("");
|
|
@@ -26269,6 +26693,7 @@ async function mcpList(_options) {
|
|
|
26269
26693
|
console.log(` ${s.name} [${s.transport}]`);
|
|
26270
26694
|
if (s.description) console.log(` ${s.description}`);
|
|
26271
26695
|
console.log(` endpoint: ${endpoint}`);
|
|
26696
|
+
if (s.roles) console.log(` roles: ${s.roles.length > 0 ? s.roles.join(", ") : "nobody"}`);
|
|
26272
26697
|
const needed = referencedVars(s);
|
|
26273
26698
|
if (needed.length > 0) {
|
|
26274
26699
|
const missing = needed.filter((v) => !vars[v]);
|
|
@@ -26539,7 +26964,7 @@ async function saveSession(options) {
|
|
|
26539
26964
|
log.info(`[dry-run] Would push session summary to sessions/${username}/${monthKey(summary)}.md`);
|
|
26540
26965
|
return;
|
|
26541
26966
|
}
|
|
26542
|
-
if (localConfig
|
|
26967
|
+
if (usesReportsBranch(localConfig)) {
|
|
26543
26968
|
const spin2 = spinner("Pushing session summary to team...").start();
|
|
26544
26969
|
try {
|
|
26545
26970
|
const { ensureReportsWorktree: ensureReportsWorktree2, commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
@@ -28670,9 +29095,10 @@ async function resolveVizRoot(opts) {
|
|
|
28670
29095
|
}
|
|
28671
29096
|
const config = await detectProjectConfig() ?? await loadLocalConfig();
|
|
28672
29097
|
if (config?.repo?.localPath) {
|
|
28673
|
-
|
|
29098
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
29099
|
+
if (usesReportsBranch2(config)) {
|
|
28674
29100
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
28675
|
-
await ensureReportsWorktree2(config);
|
|
29101
|
+
await ensureReportsWorktree2(config, { pushIfCreated: false });
|
|
28676
29102
|
}
|
|
28677
29103
|
const knowledgeRoot = getKnowledgeDir(config);
|
|
28678
29104
|
const reportsRoot = getReportsDir(config);
|
|
@@ -30439,6 +30865,17 @@ var init_mr_hint = __esm({
|
|
|
30439
30865
|
|
|
30440
30866
|
// src/hook-handlers.ts
|
|
30441
30867
|
import path92 from "path";
|
|
30868
|
+
async function teamCorrectionKeywords(stdin) {
|
|
30869
|
+
if (typeof stdin.prompt !== "string") return [];
|
|
30870
|
+
try {
|
|
30871
|
+
const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
30872
|
+
const { getInterventionSharing: getInterventionSharing2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
30873
|
+
const { teamConfig } = await autoDetectInit2();
|
|
30874
|
+
return getInterventionSharing2(teamConfig).correctionKeywords;
|
|
30875
|
+
} catch {
|
|
30876
|
+
return [];
|
|
30877
|
+
}
|
|
30878
|
+
}
|
|
30442
30879
|
async function contributeHintAllowed() {
|
|
30443
30880
|
const { isContributeHintEnabled: isContributeHintEnabled2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
30444
30881
|
try {
|
|
@@ -30539,7 +30976,7 @@ var init_hook_handlers = __esm({
|
|
|
30539
30976
|
async execute(stdin, tool) {
|
|
30540
30977
|
const { parseHookEvent: parseHookEvent2, appendEvent: appendEvent2, compactEvents: compactEvents2 } = await Promise.resolve().then(() => (init_dashboard_collector(), dashboard_collector_exports));
|
|
30541
30978
|
const raw = JSON.stringify(stdin);
|
|
30542
|
-
const event = await parseHookEvent2(raw, tool);
|
|
30979
|
+
const event = await parseHookEvent2(raw, tool, { correctionKeywords: await teamCorrectionKeywords(stdin) });
|
|
30543
30980
|
if (event) {
|
|
30544
30981
|
await appendEvent2(event);
|
|
30545
30982
|
compactEvents2().catch(() => {
|
|
@@ -30664,7 +31101,8 @@ var init_hook_handlers = __esm({
|
|
|
30664
31101
|
if (verifiedDocIds.length > 0) {
|
|
30665
31102
|
await incrementUpvoted2(votePath, verifiedDocIds);
|
|
30666
31103
|
}
|
|
30667
|
-
|
|
31104
|
+
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31105
|
+
if (usesReportsBranch2(localConfig)) {
|
|
30668
31106
|
try {
|
|
30669
31107
|
const { ensureReportsWorktree: ensureReportsWorktree2, commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
30670
31108
|
const wt = await ensureReportsWorktree2(localConfig);
|
|
@@ -30767,7 +31205,8 @@ var init_hook_handlers = __esm({
|
|
|
30767
31205
|
// src/hook-dispatch-cli.ts
|
|
30768
31206
|
var hook_dispatch_cli_exports = {};
|
|
30769
31207
|
__export(hook_dispatch_cli_exports, {
|
|
30770
|
-
hookDispatchCli: () => hookDispatchCli
|
|
31208
|
+
hookDispatchCli: () => hookDispatchCli,
|
|
31209
|
+
parseStdin: () => parseStdin
|
|
30771
31210
|
});
|
|
30772
31211
|
import { spawn as spawn3 } from "child_process";
|
|
30773
31212
|
async function readStdin4() {
|
|
@@ -30828,10 +31267,15 @@ function parseStdin(raw, event) {
|
|
|
30828
31267
|
try {
|
|
30829
31268
|
stdin = JSON.parse(raw);
|
|
30830
31269
|
} catch {
|
|
30831
|
-
|
|
30832
|
-
|
|
31270
|
+
const preview = raw.length > 160 ? `${raw.slice(0, 80)}...${raw.slice(-80)}` : raw;
|
|
31271
|
+
log.debug(
|
|
31272
|
+
`hook-dispatch: failed to parse STDIN JSON for event=${event} (len=${raw.length}, body=${JSON.stringify(preview)})`
|
|
31273
|
+
);
|
|
30833
31274
|
}
|
|
30834
31275
|
}
|
|
31276
|
+
if (!stdin || typeof stdin !== "object" || Array.isArray(stdin)) {
|
|
31277
|
+
stdin = {};
|
|
31278
|
+
}
|
|
30835
31279
|
if (!stdin.hook_event_name) {
|
|
30836
31280
|
const EVENT_MAP2 = {
|
|
30837
31281
|
"session-start": "SessionStart",
|
|
@@ -30857,7 +31301,6 @@ async function hookDispatchCli(event, tool, matcher, bgOnly = false) {
|
|
|
30857
31301
|
try {
|
|
30858
31302
|
const raw = await readStdin4();
|
|
30859
31303
|
const stdin = parseStdin(raw, event);
|
|
30860
|
-
if (stdin === null) return;
|
|
30861
31304
|
const { loadLocalConfig: loadLocalConfig4, detectProjectConfig: detectProjectConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
30862
31305
|
const cwd = resolveHookCwd(stdin);
|
|
30863
31306
|
if (cwd) {
|
|
@@ -30925,7 +31368,8 @@ async function rebuildIndexAfterContribute(localConfig) {
|
|
|
30925
31368
|
const docsRepoDir = path93.join(repoPath, "docs");
|
|
30926
31369
|
const rulesRepoDir = path93.join(repoPath, "rules");
|
|
30927
31370
|
const skillsRepoDir = path93.join(repoPath, "skills");
|
|
30928
|
-
const
|
|
31371
|
+
const { getReportsDir: getReportsDir2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31372
|
+
const votesDir = path93.join(getReportsDir2(localConfig), "votes");
|
|
30929
31373
|
let effectiveLearningsDir;
|
|
30930
31374
|
const activeLearningsNamespaces = await resolveActiveLearningsNamespaces(
|
|
30931
31375
|
repoPath,
|
|
@@ -31075,7 +31519,7 @@ async function contributeSelf(localConfig, content, options) {
|
|
|
31075
31519
|
let votesDir;
|
|
31076
31520
|
try {
|
|
31077
31521
|
const { ensureReportsWorktree: ensureReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
31078
|
-
const candidate = path93.join(await ensureReportsWorktree2(localConfig), "votes");
|
|
31522
|
+
const candidate = path93.join(await ensureReportsWorktree2(localConfig, { pushIfCreated: false }), "votes");
|
|
31079
31523
|
if (await pathExists3(candidate)) votesDir = candidate;
|
|
31080
31524
|
} catch {
|
|
31081
31525
|
}
|
|
@@ -42412,7 +42856,7 @@ var init_extract_mr = __esm({
|
|
|
42412
42856
|
// src/maintenance/paths.ts
|
|
42413
42857
|
import path130 from "path";
|
|
42414
42858
|
async function resolveMaintenancePaths(localConfig) {
|
|
42415
|
-
if (
|
|
42859
|
+
if (usesReportsBranch(localConfig)) {
|
|
42416
42860
|
const { refreshReportsWorktree: refreshReportsWorktree2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
42417
42861
|
await refreshReportsWorktree2(localConfig, { pushIfCreated: false });
|
|
42418
42862
|
}
|