teamai-cli 0.23.0-beta.1 → 0.23.0-beta.3
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/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/dist/index.js +294 -159
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://www.npmjs.com/package/teamai-cli)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
|
-
TeamAI manages your team's skills, rules, MCP, and knowledge across Claude Code, Codex, CodeBuddy, WorkBuddy, OpenCode, Cursor, and other AI agents.
|
|
14
|
+
TeamAI manages your team's skills, rules, MCP, and knowledge across Claude Code, Codex, CodeBuddy, WorkBuddy, OpenCode, Cursor, Qoder, and other AI agents.
|
|
15
15
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
@@ -74,6 +74,7 @@ Once initialized, every AI session automatically pulls the latest skills / rules
|
|
|
74
74
|
<tr><td>Claude Code</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
75
75
|
<tr><td>Codex</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
76
76
|
<tr><td>Cursor</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
77
|
+
<tr><td>Qoder</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
77
78
|
<tr><td>CodeBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
78
79
|
<tr><td>OpenCode</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td></tr>
|
|
79
80
|
<tr><td>WorkBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
package/README.zh-CN.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://www.npmjs.com/package/teamai-cli)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
|
-
TeamAI 统一管理团队的 Skills、Rules、MCP 和知识,驾驭 Claude Code、Codex、CodeBuddy、WorkBuddy、OpenCode、Cursor 等 AI Agents。
|
|
14
|
+
TeamAI 统一管理团队的 Skills、Rules、MCP 和知识,驾驭 Claude Code、Codex、CodeBuddy、WorkBuddy、OpenCode、Cursor、Qoder 等 AI Agents。
|
|
15
15
|
|
|
16
16
|
## 快速开始
|
|
17
17
|
|
|
@@ -74,6 +74,7 @@ teamai init https://github.com/yourorg/yourrepo --scope user
|
|
|
74
74
|
<tr><td>Claude Code</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
75
75
|
<tr><td>Codex</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
76
76
|
<tr><td>Cursor</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
77
|
+
<tr><td>Qoder</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
77
78
|
<tr><td>CodeBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
|
78
79
|
<tr><td>OpenCode</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">—</td><td align="center">—</td></tr>
|
|
79
80
|
<tr><td>WorkBuddy</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">—</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td><td align="center">✓</td></tr>
|
package/dist/index.js
CHANGED
|
@@ -536,6 +536,20 @@ var init_types = __esm({
|
|
|
536
536
|
tclaude: { skills: ".tclaude/skills", rules: ".tclaude/rules", settings: ".tclaude/settings.json", claudemd: ".tclaude/CLAUDE.md", agents: ".tclaude/agents", mcp: ".tclaude/.claude.json" },
|
|
537
537
|
tcodex: { skills: ".tcodex/skills", rules: ".tcodex/rules", settings: ".tcodex/hooks.json", agents: ".tcodex/agents" },
|
|
538
538
|
cursor: { skills: ".cursor/skills", rules: ".cursor/rules", settings: ".cursor/hooks.json", agents: ".cursor/agents", mcp: ".cursor/mcp.json", mcpProject: ".cursor/mcp.json" },
|
|
539
|
+
// JoyCode: skills, rules (.mdc), and subagents are synced to .joycode/.
|
|
540
|
+
// JoyCode currently does not provide a lifecycle hooks system or startup
|
|
541
|
+
// adapter, so it intentionally has no `settings` path. Hook reconciliation
|
|
542
|
+
// skips JoyCode cleanly without generating ghost files; users must sync
|
|
543
|
+
// manually via `teamai pull`.
|
|
544
|
+
joycode: { skills: ".joycode/skills", rules: ".joycode/rules", agents: ".joycode/agents" },
|
|
545
|
+
qoder: {
|
|
546
|
+
skills: ".qoder/skills",
|
|
547
|
+
rules: ".qoder/rules",
|
|
548
|
+
settings: ".qoder/settings.json",
|
|
549
|
+
agents: ".qoder/agents",
|
|
550
|
+
mcp: ".qoder/settings.json",
|
|
551
|
+
mcpProject: ".qoder/settings.json"
|
|
552
|
+
},
|
|
539
553
|
codebuddy: { skills: ".codebuddy/skills", rules: ".codebuddy/rules", settings: ".codebuddy/settings.json", claudemd: ".codebuddy/CODEBUDDY.md", agents: ".codebuddy/agents", mcp: ".codebuddy/mcp.json", mcpProject: ".codebuddy/mcp.json" },
|
|
540
554
|
openclaw: { skills: ".openclaw/skills", rules: ".openclaw/rules", claudemd: ".openclaw/workspace/AGENTS.md" },
|
|
541
555
|
hermes: { skills: ".hermes/skills", claudemd: "AGENTS.md" },
|
|
@@ -1202,10 +1216,10 @@ var init_base = __esm({
|
|
|
1202
1216
|
|
|
1203
1217
|
// src/resources/rule-format.ts
|
|
1204
1218
|
function ruleFileExtensionForTool(tool) {
|
|
1205
|
-
return tool
|
|
1219
|
+
return usesCursorMdcRules(tool) ? ".mdc" : ".md";
|
|
1206
1220
|
}
|
|
1207
1221
|
function usesCursorMdcRules(tool) {
|
|
1208
|
-
return tool
|
|
1222
|
+
return CURSOR_MDC_RULE_TOOLS.has(tool);
|
|
1209
1223
|
}
|
|
1210
1224
|
function ruleStemFromFilename(filename) {
|
|
1211
1225
|
if (filename.endsWith(".mdc")) return filename.slice(0, -".mdc".length);
|
|
@@ -1215,9 +1229,11 @@ function ruleStemFromFilename(filename) {
|
|
|
1215
1229
|
function isLegacyCursorRuleFile(tool, filename) {
|
|
1216
1230
|
return usesCursorMdcRules(tool) && filename.endsWith(".md");
|
|
1217
1231
|
}
|
|
1232
|
+
var CURSOR_MDC_RULE_TOOLS;
|
|
1218
1233
|
var init_rule_format = __esm({
|
|
1219
1234
|
"src/resources/rule-format.ts"() {
|
|
1220
1235
|
"use strict";
|
|
1236
|
+
CURSOR_MDC_RULE_TOOLS = /* @__PURE__ */ new Set(["cursor", "joycode"]);
|
|
1221
1237
|
}
|
|
1222
1238
|
});
|
|
1223
1239
|
|
|
@@ -1446,7 +1462,7 @@ async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, last
|
|
|
1446
1462
|
const rulesDir = path6.join(baseDir, toolPath.rules);
|
|
1447
1463
|
if (!await pathExists(rulesDir)) continue;
|
|
1448
1464
|
const ext = ruleFileExtensionForTool(tool);
|
|
1449
|
-
const
|
|
1465
|
+
const isMdcTool = usesCursorMdcRules(tool);
|
|
1450
1466
|
const files = await listFilesRecursive(rulesDir);
|
|
1451
1467
|
for (const file of files) {
|
|
1452
1468
|
if (!file.endsWith(ext)) continue;
|
|
@@ -1456,7 +1472,7 @@ async function syncRulesToLocal(teamConfig, localConfig, repoPath, baseDir, last
|
|
|
1456
1472
|
const teamRelPath = `rules/${name}.md`;
|
|
1457
1473
|
const teamFilePath = path6.join(teamRulesDir, `${name}.md`);
|
|
1458
1474
|
if (!await pathExists(teamFilePath)) continue;
|
|
1459
|
-
if (
|
|
1475
|
+
if (isMdcTool) {
|
|
1460
1476
|
const localRaw = await readFileSafe(localFilePath);
|
|
1461
1477
|
const teamRaw = await readFileSafe(teamFilePath);
|
|
1462
1478
|
if (localRaw === null || teamRaw === null) continue;
|
|
@@ -7405,7 +7421,7 @@ var CLAUDE_TOOLS, CURSOR_TOOLS2, CODEX_TOOLS2, BUDDY_TOOLS, OPENCODE_TOOLS, MCP_
|
|
|
7405
7421
|
var init_mcp_format = __esm({
|
|
7406
7422
|
"src/resources/mcp-format.ts"() {
|
|
7407
7423
|
"use strict";
|
|
7408
|
-
CLAUDE_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude"]);
|
|
7424
|
+
CLAUDE_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "qoder"]);
|
|
7409
7425
|
CURSOR_TOOLS2 = /* @__PURE__ */ new Set(["cursor"]);
|
|
7410
7426
|
CODEX_TOOLS2 = /* @__PURE__ */ new Set(["codex", "codex-internal", "tcodex"]);
|
|
7411
7427
|
BUDDY_TOOLS = /* @__PURE__ */ new Set(["codebuddy", "workbuddy"]);
|
|
@@ -10956,7 +10972,7 @@ var init_rules = __esm({
|
|
|
10956
10972
|
const rulesDir = path29.join(resolveBaseDir(localConfig), rulesPath);
|
|
10957
10973
|
if (!await pathExists(rulesDir)) continue;
|
|
10958
10974
|
const ext = ruleFileExtensionForTool(tool);
|
|
10959
|
-
const
|
|
10975
|
+
const isMdcTool = usesCursorMdcRules(tool);
|
|
10960
10976
|
const files = await listFilesRecursive(rulesDir);
|
|
10961
10977
|
for (const file of files) {
|
|
10962
10978
|
if (!file.endsWith(ext)) continue;
|
|
@@ -10967,7 +10983,7 @@ var init_rules = __esm({
|
|
|
10967
10983
|
const teamFileName = `${name}.md`;
|
|
10968
10984
|
if (teamRules.has(teamFileName)) {
|
|
10969
10985
|
const teamFilePath = path29.join(teamRulesDir, teamFileName);
|
|
10970
|
-
const equal =
|
|
10986
|
+
const equal = isMdcTool ? cursorMdcBodyEqualsTeamMd(
|
|
10971
10987
|
await readFileSafe(localFilePath) ?? "",
|
|
10972
10988
|
await readTeamRule(teamFilePath)
|
|
10973
10989
|
) : await fileContentEqual(localFilePath, teamFilePath);
|
|
@@ -10978,7 +10994,7 @@ var init_rules = __esm({
|
|
|
10978
10994
|
candidates.set(name, { sourcePath: localFilePath, mtime, status: "modified" });
|
|
10979
10995
|
}
|
|
10980
10996
|
} else {
|
|
10981
|
-
if (
|
|
10997
|
+
if (isMdcTool) continue;
|
|
10982
10998
|
const existing = candidates.get(name);
|
|
10983
10999
|
if (!existing) {
|
|
10984
11000
|
const mtime = await getFileMtime(localFilePath);
|
|
@@ -11113,6 +11129,7 @@ var init_rules = __esm({
|
|
|
11113
11129
|
await this.pullItem(rule, teamConfig, localConfig);
|
|
11114
11130
|
}
|
|
11115
11131
|
const teamRuleNames = new Set(rules.map((r) => r.name));
|
|
11132
|
+
const tombstones = await this.readTombstones(localConfig);
|
|
11116
11133
|
const baseDir = resolveBaseDir(localConfig);
|
|
11117
11134
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
11118
11135
|
if (!toolPath.rules) continue;
|
|
@@ -11124,6 +11141,7 @@ var init_rules = __esm({
|
|
|
11124
11141
|
for (const localFile of localFiles) {
|
|
11125
11142
|
const ruleName = ruleStemFromFilename(localFile);
|
|
11126
11143
|
if (ruleName === null) continue;
|
|
11144
|
+
if (tool === "joycode" && !tombstones.has(ruleName)) continue;
|
|
11127
11145
|
if (isLegacyCursorRuleFile(tool, localFile)) {
|
|
11128
11146
|
await remove(path29.join(destDir, localFile));
|
|
11129
11147
|
log.debug(`Removed legacy .md rule ${localFile} from ${tool}`);
|
|
@@ -11551,6 +11569,12 @@ function renderForCodebuddy(spec) {
|
|
|
11551
11569
|
content: renderMarkdownAgent(spec, spec.tool_extras?.["codebuddy"])
|
|
11552
11570
|
};
|
|
11553
11571
|
}
|
|
11572
|
+
function renderForJoycode(spec) {
|
|
11573
|
+
return {
|
|
11574
|
+
ext: agentFileExtensionForTool("joycode"),
|
|
11575
|
+
content: renderMarkdownAgent(spec, spec.tool_extras?.["joycode"])
|
|
11576
|
+
};
|
|
11577
|
+
}
|
|
11554
11578
|
function renderForCodex(spec) {
|
|
11555
11579
|
return {
|
|
11556
11580
|
ext: agentFileExtensionForTool("codex"),
|
|
@@ -11669,6 +11693,15 @@ function reverseFromCodebuddy(filePath, content) {
|
|
|
11669
11693
|
}
|
|
11670
11694
|
return { ok: true, spec };
|
|
11671
11695
|
}
|
|
11696
|
+
function reverseFromJoycode(filePath, content) {
|
|
11697
|
+
const result = reverseFromClaude(filePath, content);
|
|
11698
|
+
if (!result.ok) return result;
|
|
11699
|
+
const spec = result.spec;
|
|
11700
|
+
if (spec.tool_extras?.["claude"]) {
|
|
11701
|
+
spec.tool_extras = { joycode: spec.tool_extras["claude"] };
|
|
11702
|
+
}
|
|
11703
|
+
return { ok: true, spec };
|
|
11704
|
+
}
|
|
11672
11705
|
function reverseFromCodex(filePath, content) {
|
|
11673
11706
|
let parsed;
|
|
11674
11707
|
try {
|
|
@@ -11813,6 +11846,10 @@ function renderForTool(spec, tool) {
|
|
|
11813
11846
|
return renderForCodex(spec);
|
|
11814
11847
|
case "cursor":
|
|
11815
11848
|
return renderForCursor(spec);
|
|
11849
|
+
case "joycode":
|
|
11850
|
+
return renderForJoycode(spec);
|
|
11851
|
+
case "qoder":
|
|
11852
|
+
return renderForClaude(spec);
|
|
11816
11853
|
case "opencode":
|
|
11817
11854
|
return renderForOpencode(spec);
|
|
11818
11855
|
}
|
|
@@ -11830,6 +11867,8 @@ var init_agent_format = __esm({
|
|
|
11830
11867
|
"codex-internal",
|
|
11831
11868
|
"tcodex",
|
|
11832
11869
|
"cursor",
|
|
11870
|
+
"joycode",
|
|
11871
|
+
"qoder",
|
|
11833
11872
|
"opencode"
|
|
11834
11873
|
];
|
|
11835
11874
|
COMMON_CLAUDE_FIELDS = /* @__PURE__ */ new Set(["name", "description", "model", "tools"]);
|
|
@@ -11953,6 +11992,49 @@ var init_builtin_agents = __esm({
|
|
|
11953
11992
|
|
|
11954
11993
|
// src/resources/agents.ts
|
|
11955
11994
|
import path34 from "path";
|
|
11995
|
+
import { isDeepStrictEqual } from "util";
|
|
11996
|
+
import { parse as parseYaml2 } from "yaml";
|
|
11997
|
+
function mergeCanonicalEdits(canonical, perTool) {
|
|
11998
|
+
const merged = { ...canonical };
|
|
11999
|
+
const extras = { ...canonical.tool_extras };
|
|
12000
|
+
const changes = /* @__PURE__ */ new Map();
|
|
12001
|
+
const conflicts = [];
|
|
12002
|
+
const propose = (key, value, apply) => {
|
|
12003
|
+
const previous = changes.get(key);
|
|
12004
|
+
if (previous && !isDeepStrictEqual(previous.value, value)) {
|
|
12005
|
+
conflicts.push({ field: key, values: { previous: previous.value, next: value } });
|
|
12006
|
+
} else {
|
|
12007
|
+
changes.set(key, { value, apply });
|
|
12008
|
+
}
|
|
12009
|
+
};
|
|
12010
|
+
for (const [tool, edited] of Object.entries(perTool)) {
|
|
12011
|
+
const rendered = renderForTool(canonical, tool);
|
|
12012
|
+
const baseline = reverseByTool(tool, `${canonical.name}${rendered.ext}`, rendered.content);
|
|
12013
|
+
if (!baseline.ok) return { ok: false, conflicts: [{ field: tool, values: { error: baseline.reason } }] };
|
|
12014
|
+
for (const field of ["name", "description", "instructions", "model", "tools"]) {
|
|
12015
|
+
if (isDeepStrictEqual(baseline.spec[field], edited[field])) continue;
|
|
12016
|
+
propose(field, edited[field], () => {
|
|
12017
|
+
const output = merged;
|
|
12018
|
+
if (edited[field] === void 0) delete output[field];
|
|
12019
|
+
else output[field] = edited[field];
|
|
12020
|
+
});
|
|
12021
|
+
}
|
|
12022
|
+
const extrasKey = tool === "tclaude" ? "claude" : tool === "tcodex" ? "codex" : tool;
|
|
12023
|
+
const before = Object.values(baseline.spec.tool_extras ?? {})[0] ?? {};
|
|
12024
|
+
const after = Object.values(edited.tool_extras ?? {})[0] ?? {};
|
|
12025
|
+
if (!isDeepStrictEqual(before, after)) {
|
|
12026
|
+
propose(`tool_extras.${extrasKey}`, after, () => {
|
|
12027
|
+
if (Object.keys(after).length) extras[extrasKey] = after;
|
|
12028
|
+
else delete extras[extrasKey];
|
|
12029
|
+
});
|
|
12030
|
+
}
|
|
12031
|
+
}
|
|
12032
|
+
if (conflicts.length) return { ok: false, conflicts };
|
|
12033
|
+
for (const change of changes.values()) change.apply();
|
|
12034
|
+
if (Object.keys(extras).length) merged.tool_extras = extras;
|
|
12035
|
+
else delete merged.tool_extras;
|
|
12036
|
+
return { ok: true, spec: merged };
|
|
12037
|
+
}
|
|
11956
12038
|
function getAgentStem(filename) {
|
|
11957
12039
|
if (filename.endsWith(".md")) return filename.slice(0, -3);
|
|
11958
12040
|
if (filename.endsWith(".toml")) return filename.slice(0, -5);
|
|
@@ -11986,6 +12068,10 @@ function reverseByTool(tool, filePath, content) {
|
|
|
11986
12068
|
return reverseFromCodex(filePath, content);
|
|
11987
12069
|
case "cursor":
|
|
11988
12070
|
return reverseFromCursor(filePath, content);
|
|
12071
|
+
case "joycode":
|
|
12072
|
+
return reverseFromJoycode(filePath, content);
|
|
12073
|
+
case "qoder":
|
|
12074
|
+
return reverseFromClaude(filePath, content);
|
|
11989
12075
|
case "opencode":
|
|
11990
12076
|
return reverseFromOpencode(filePath, content);
|
|
11991
12077
|
}
|
|
@@ -12070,8 +12156,36 @@ var init_agents = __esm({
|
|
|
12070
12156
|
const teamMdPath = path34.join(teamAgentsDir, `${stem}.md`);
|
|
12071
12157
|
const hasTeamYaml = await pathExists(teamYamlPath);
|
|
12072
12158
|
const hasTeamMd = await pathExists(teamMdPath);
|
|
12159
|
+
let canonicalSpec;
|
|
12160
|
+
if (hasTeamYaml) {
|
|
12161
|
+
const raw = await readFileSafe(teamYamlPath);
|
|
12162
|
+
const parsed = raw === null ? null : parseAgentYaml(raw, `${stem}.yaml`);
|
|
12163
|
+
if (!parsed?.ok) {
|
|
12164
|
+
items.push({
|
|
12165
|
+
name: stem,
|
|
12166
|
+
type: "agents",
|
|
12167
|
+
sourcePath: teamYamlPath,
|
|
12168
|
+
relativePath: `agents/${stem}.yaml`,
|
|
12169
|
+
status: "modified",
|
|
12170
|
+
skipReason: "cannot read or parse canonical agent YAML"
|
|
12171
|
+
});
|
|
12172
|
+
continue;
|
|
12173
|
+
}
|
|
12174
|
+
canonicalSpec = { ...parseYaml2(raw), ...parsed.spec };
|
|
12175
|
+
for (const [tool, filePath] of toolFiles) {
|
|
12176
|
+
if (!isKnownTool(tool) || isAgentDisabled(localConfig, tool) || canonicalSpec.targets && !canonicalSpec.targets.includes(tool)) {
|
|
12177
|
+
toolFiles.delete(tool);
|
|
12178
|
+
continue;
|
|
12179
|
+
}
|
|
12180
|
+
if (await readFileSafe(filePath) === renderForTool(canonicalSpec, tool).content) {
|
|
12181
|
+
toolFiles.delete(tool);
|
|
12182
|
+
}
|
|
12183
|
+
}
|
|
12184
|
+
}
|
|
12073
12185
|
let hasChange = false;
|
|
12074
|
-
if (
|
|
12186
|
+
if (hasTeamYaml) {
|
|
12187
|
+
hasChange = toolFiles.size > 0;
|
|
12188
|
+
} else if (!hasTeamMd) {
|
|
12075
12189
|
hasChange = true;
|
|
12076
12190
|
} else {
|
|
12077
12191
|
for (const [tool, filePath] of toolFiles) {
|
|
@@ -12104,22 +12218,27 @@ var init_agents = __esm({
|
|
|
12104
12218
|
for (const [tool, filePath] of toolFiles) {
|
|
12105
12219
|
if (!isKnownTool(tool)) continue;
|
|
12106
12220
|
const content = await readFileSafe(filePath);
|
|
12107
|
-
if (!content)
|
|
12221
|
+
if (!content) {
|
|
12222
|
+
if (canonicalSpec) skipReason = `cannot read edited agent file for ${tool}`;
|
|
12223
|
+
continue;
|
|
12224
|
+
}
|
|
12108
12225
|
const result = reverseByTool(tool, filePath, content);
|
|
12109
12226
|
if (result.ok) {
|
|
12110
12227
|
perToolSpecs[tool] = result.spec;
|
|
12111
12228
|
} else {
|
|
12229
|
+
if (canonicalSpec) skipReason = `cannot parse edited agent file for ${tool}: ${result.reason}`;
|
|
12112
12230
|
log.debug(`Reverse failed for ${stem} from ${tool}: ${result.reason}`);
|
|
12113
12231
|
}
|
|
12114
12232
|
}
|
|
12115
|
-
if (Object.keys(perToolSpecs).length === 0) {
|
|
12233
|
+
if (!skipReason && Object.keys(perToolSpecs).length === 0) {
|
|
12116
12234
|
skipReason = `could not reverse-parse any tool's agent file for ${stem}`;
|
|
12117
|
-
} else {
|
|
12118
|
-
const mergeResult = mergeReverseResults(perToolSpecs);
|
|
12235
|
+
} else if (!skipReason) {
|
|
12236
|
+
const mergeResult = canonicalSpec ? mergeCanonicalEdits(canonicalSpec, perToolSpecs) : mergeReverseResults(perToolSpecs);
|
|
12119
12237
|
if (!mergeResult.ok) {
|
|
12120
12238
|
const conflictSummary = mergeResult.conflicts.map((c) => `${c.field}: ${JSON.stringify(c.values)}`).join("; ");
|
|
12121
12239
|
skipReason = `conflicting values across tools \u2014 ${conflictSummary}`;
|
|
12122
12240
|
} else {
|
|
12241
|
+
if (canonicalSpec && isDeepStrictEqual(canonicalSpec, mergeResult.spec)) continue;
|
|
12123
12242
|
items.push({
|
|
12124
12243
|
name: stem,
|
|
12125
12244
|
type: "agents",
|
|
@@ -12208,7 +12327,7 @@ var init_agents = __esm({
|
|
|
12208
12327
|
* Pull an agent to every installed tool's agents/ directory.
|
|
12209
12328
|
*
|
|
12210
12329
|
* New format (.yaml): parses spec, respects spec.targets, renders per-tool native format.
|
|
12211
|
-
* Legacy format (.md): copies .md as-is to
|
|
12330
|
+
* Legacy format (.md): copies .md as-is to Claude-compatible tools.
|
|
12212
12331
|
*/
|
|
12213
12332
|
async pullItem(item, teamConfig, localConfig) {
|
|
12214
12333
|
const agentItem = item;
|
|
@@ -12287,10 +12406,10 @@ var init_agents = __esm({
|
|
|
12287
12406
|
}
|
|
12288
12407
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
12289
12408
|
/**
|
|
12290
|
-
* Legacy pull: copies .md as-is to
|
|
12409
|
+
* Legacy pull: copies .md as-is to Claude-compatible tools, including JoyCode.
|
|
12291
12410
|
*/
|
|
12292
12411
|
async pullLegacyMd(item, teamConfig, baseDir, localConfig) {
|
|
12293
|
-
const legacyTools = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy"]);
|
|
12412
|
+
const legacyTools = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
12294
12413
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12295
12414
|
if (!legacyTools.has(tool)) continue;
|
|
12296
12415
|
if (!toolPath.agents) {
|
|
@@ -13071,7 +13190,7 @@ async function ensureReportsWorktree(localConfig, options = {}) {
|
|
|
13071
13190
|
await writeWorktreeGitignore(wt);
|
|
13072
13191
|
const wtGit = createGit2(wt);
|
|
13073
13192
|
await wtGit.add([".gitignore"]);
|
|
13074
|
-
await wtGit
|
|
13193
|
+
await commitSkippingHooks(wtGit, "[teamai] Initialize reports branch");
|
|
13075
13194
|
if (options.pushIfCreated !== false) {
|
|
13076
13195
|
try {
|
|
13077
13196
|
await wtGit.push(["-u", "origin", REPORTS_BRANCH]);
|
|
@@ -13134,7 +13253,7 @@ async function commitAndPushReports(localConfig, message, files) {
|
|
|
13134
13253
|
log.debug("[reports] nothing to commit");
|
|
13135
13254
|
return false;
|
|
13136
13255
|
}
|
|
13137
|
-
await git
|
|
13256
|
+
await commitSkippingHooks(git, message);
|
|
13138
13257
|
for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {
|
|
13139
13258
|
try {
|
|
13140
13259
|
await git.push(["origin", REPORTS_BRANCH]);
|
|
@@ -13894,6 +14013,7 @@ __export(git_exports, {
|
|
|
13894
14013
|
autoPushViaMR: () => autoPushViaMR,
|
|
13895
14014
|
checkoutMaster: () => checkoutMaster,
|
|
13896
14015
|
commitPaths: () => commitPaths,
|
|
14016
|
+
commitSkippingHooks: () => commitSkippingHooks,
|
|
13897
14017
|
configureGitUser: () => configureGitUser,
|
|
13898
14018
|
createGit: () => createGit2,
|
|
13899
14019
|
generateBranchName: () => generateBranchName,
|
|
@@ -13929,6 +14049,9 @@ function createGit2(basePath) {
|
|
|
13929
14049
|
}
|
|
13930
14050
|
return simpleGit();
|
|
13931
14051
|
}
|
|
14052
|
+
function commitSkippingHooks(git, message) {
|
|
14053
|
+
return git.commit(message, { "--no-verify": null });
|
|
14054
|
+
}
|
|
13932
14055
|
async function isGitRepo(localPath) {
|
|
13933
14056
|
if (!await fse9.pathExists(localPath)) {
|
|
13934
14057
|
return false;
|
|
@@ -14198,7 +14321,7 @@ async function pushRepoBranch(localPath, message, files, branchName, opts = {})
|
|
|
14198
14321
|
await leaveAndDeletePushBranch(git, defaultBranch, branchName);
|
|
14199
14322
|
return false;
|
|
14200
14323
|
}
|
|
14201
|
-
await git
|
|
14324
|
+
await commitSkippingHooks(git, message);
|
|
14202
14325
|
if (opts.reuseBranch) {
|
|
14203
14326
|
if (await treeMatchesRemoteBranch(git, branchName)) {
|
|
14204
14327
|
log.debug(`Remote branch ${branchName} already holds this tree, skipping force-push`);
|
|
@@ -14503,7 +14626,7 @@ var init_known_agents = __esm({
|
|
|
14503
14626
|
init_types();
|
|
14504
14627
|
init_base();
|
|
14505
14628
|
init_home();
|
|
14506
|
-
SELF_MODE_AGENT_CHOICES = ["claude", "codex", "cursor", "codebuddy", "workbuddy"];
|
|
14629
|
+
SELF_MODE_AGENT_CHOICES = ["claude", "codex", "cursor", "joycode", "codebuddy", "workbuddy"];
|
|
14507
14630
|
KNOWN_AGENTS = [
|
|
14508
14631
|
// Coding agents already wired through teamConfig.toolPaths defaults
|
|
14509
14632
|
{ id: "claude", displayName: "Claude Code", category: "coding", skillsPath: ".claude/skills" },
|
|
@@ -14513,6 +14636,7 @@ var init_known_agents = __esm({
|
|
|
14513
14636
|
{ id: "codex-internal", displayName: "Codex CLI Internal", category: "coding", skillsPath: ".codex-internal/skills" },
|
|
14514
14637
|
{ id: "tcodex", displayName: "TCodex", category: "coding", skillsPath: ".tcodex/skills" },
|
|
14515
14638
|
{ id: "cursor", displayName: "Cursor", category: "coding", skillsPath: ".cursor/skills" },
|
|
14639
|
+
{ id: "joycode", displayName: "JoyCode", category: "coding", skillsPath: ".joycode/skills" },
|
|
14516
14640
|
{ id: "codebuddy", displayName: "CodeBuddy", category: "coding", skillsPath: ".codebuddy/skills" },
|
|
14517
14641
|
// Additional coding agents from skills-manage
|
|
14518
14642
|
{ id: "gemini", displayName: "Gemini CLI", category: "coding", skillsPath: ".gemini/skills" },
|
|
@@ -18456,14 +18580,6 @@ function filterEventsByScope(events, opts) {
|
|
|
18456
18580
|
async function reportUsageToTeam(repoPath, username, options) {
|
|
18457
18581
|
const selfConfig = options?.selfConfig;
|
|
18458
18582
|
const selfMode = selfConfig?.repo.kind === "self";
|
|
18459
|
-
const syncLock = selfMode ? null : path55.join(path55.dirname(repoPath), SYNC_LOCK_FILENAME);
|
|
18460
|
-
if (syncLock) {
|
|
18461
|
-
const locked = await acquireLock(syncLock);
|
|
18462
|
-
if (!locked) {
|
|
18463
|
-
log.debug("Skipping report: another pull/push holds the sync lock");
|
|
18464
|
-
return;
|
|
18465
|
-
}
|
|
18466
|
-
}
|
|
18467
18583
|
try {
|
|
18468
18584
|
const events = await readUsageEvents();
|
|
18469
18585
|
const filesToPush = [];
|
|
@@ -18583,8 +18699,6 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
18583
18699
|
}
|
|
18584
18700
|
} catch (e) {
|
|
18585
18701
|
log.error(`Auto-report skipped: ${e.message}`);
|
|
18586
|
-
} finally {
|
|
18587
|
-
if (syncLock) await releaseLock(syncLock);
|
|
18588
18702
|
}
|
|
18589
18703
|
}
|
|
18590
18704
|
var init_team_push = __esm({
|
|
@@ -18599,7 +18713,6 @@ var init_team_push = __esm({
|
|
|
18599
18713
|
init_logger();
|
|
18600
18714
|
init_types();
|
|
18601
18715
|
init_home();
|
|
18602
|
-
init_update();
|
|
18603
18716
|
}
|
|
18604
18717
|
});
|
|
18605
18718
|
|
|
@@ -18793,38 +18906,28 @@ async function refreshTeamRepo(localConfig) {
|
|
|
18793
18906
|
await migrateSelfModeGitignore2(localConfig);
|
|
18794
18907
|
} catch {
|
|
18795
18908
|
}
|
|
18796
|
-
let
|
|
18909
|
+
let version3 = null;
|
|
18797
18910
|
try {
|
|
18798
|
-
|
|
18911
|
+
version3 = await getHeadRev(localConfig.repo.localPath);
|
|
18799
18912
|
} catch {
|
|
18800
|
-
|
|
18913
|
+
version3 = null;
|
|
18801
18914
|
}
|
|
18802
|
-
return { label: "single-repo (knowledge on main)", version:
|
|
18915
|
+
return { label: "single-repo (knowledge on main)", version: version3, reportingOnly: false };
|
|
18803
18916
|
}
|
|
18804
|
-
const
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
18917
|
+
const result = await pullRepo(localConfig.repo.localPath);
|
|
18918
|
+
try {
|
|
18919
|
+
await flushPendingLearnings(localConfig.repo.localPath, localConfig.username);
|
|
18920
|
+
} catch (e) {
|
|
18921
|
+
log.debug(`pending-learnings flush skipped: ${e.message}`);
|
|
18809
18922
|
}
|
|
18923
|
+
let version2 = null;
|
|
18810
18924
|
try {
|
|
18811
|
-
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
log.debug(`pending-learnings flush skipped: ${e.message}`);
|
|
18816
|
-
}
|
|
18817
|
-
let version2 = null;
|
|
18818
|
-
try {
|
|
18819
|
-
version2 = await getHeadRev(localConfig.repo.localPath);
|
|
18820
|
-
} catch {
|
|
18821
|
-
log.debug("Rev check failed, proceeding with full sync");
|
|
18822
|
-
version2 = null;
|
|
18823
|
-
}
|
|
18824
|
-
return { label: result, version: version2, reportingOnly: false };
|
|
18825
|
-
} finally {
|
|
18826
|
-
await releaseLock(syncLock);
|
|
18925
|
+
version2 = await getHeadRev(localConfig.repo.localPath);
|
|
18926
|
+
} catch {
|
|
18927
|
+
log.debug("Rev check failed, proceeding with full sync");
|
|
18928
|
+
version2 = null;
|
|
18827
18929
|
}
|
|
18930
|
+
return { label: result, version: version2, reportingOnly: false };
|
|
18828
18931
|
}
|
|
18829
18932
|
async function buildRolePullContext(localConfig) {
|
|
18830
18933
|
if (!localConfig.primaryRole) return null;
|
|
@@ -18983,11 +19086,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
18983
19086
|
let currentRev = null;
|
|
18984
19087
|
let reportingOnly = false;
|
|
18985
19088
|
try {
|
|
18986
|
-
const { label, version: version2, reportingOnly: ro
|
|
18987
|
-
if (skipped) {
|
|
18988
|
-
pullSpin.succeed(`[${scopeLabel}] Team repo: ${label}`);
|
|
18989
|
-
return;
|
|
18990
|
-
}
|
|
19089
|
+
const { label, version: version2, reportingOnly: ro } = await refreshTeamRepo(localConfig);
|
|
18991
19090
|
currentRev = version2;
|
|
18992
19091
|
reportingOnly = ro;
|
|
18993
19092
|
pullSpin.succeed(`[${scopeLabel}] Team repo: ${label}`);
|
|
@@ -19618,18 +19717,21 @@ async function collectClaudemdFiles(repoPath, roleContext) {
|
|
|
19618
19717
|
}
|
|
19619
19718
|
return contents;
|
|
19620
19719
|
}
|
|
19621
|
-
async function
|
|
19720
|
+
async function legacyHooksNeedReinject() {
|
|
19622
19721
|
const home = getUserHome();
|
|
19623
19722
|
const primarySettings = path57.join(home, ".claude", "settings.json");
|
|
19624
|
-
if (!await pathExists(primarySettings)) return;
|
|
19723
|
+
if (!await pathExists(primarySettings)) return false;
|
|
19625
19724
|
const content = await readFileSafe(primarySettings);
|
|
19626
|
-
if (!content) return;
|
|
19627
|
-
if (content.includes("hook-dispatch")) return;
|
|
19628
|
-
if (!content.includes("teamai")) return;
|
|
19725
|
+
if (!content) return false;
|
|
19726
|
+
if (content.includes("hook-dispatch")) return false;
|
|
19727
|
+
if (!content.includes("teamai")) return false;
|
|
19728
|
+
return true;
|
|
19729
|
+
}
|
|
19730
|
+
async function reinjectLegacyHooks(localConfig) {
|
|
19629
19731
|
log.debug("Auto-migrating hooks to dispatch format...");
|
|
19630
|
-
const
|
|
19732
|
+
const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
19733
|
+
if (!teamConfig) return;
|
|
19631
19734
|
const { injectHooksToAllTools: injectHooksToAllTools2 } = await Promise.resolve().then(() => (init_hooks2(), hooks_exports));
|
|
19632
|
-
const { localConfig, teamConfig } = await autoDetectInit2();
|
|
19633
19735
|
const { baseDir } = resolveHookScope(localConfig);
|
|
19634
19736
|
const disabled = localConfig.disabledAgents;
|
|
19635
19737
|
let hookFilter = localConfig.enabledAgents;
|
|
@@ -19641,108 +19743,141 @@ async function autoMigrateHooksIfNeeded() {
|
|
|
19641
19743
|
log.debug("Hooks migrated to dispatch format");
|
|
19642
19744
|
}
|
|
19643
19745
|
async function pull(options) {
|
|
19746
|
+
const needsHookMigration = await legacyHooksNeedReinject().catch(() => false);
|
|
19747
|
+
const contended = /* @__PURE__ */ new Set();
|
|
19748
|
+
const heldLocks = /* @__PURE__ */ new Map();
|
|
19749
|
+
const lockScope = async (config) => {
|
|
19750
|
+
if (config.repo.kind && config.repo.kind !== "git") return true;
|
|
19751
|
+
const lock = path57.join(getDataHome(config), SYNC_LOCK_FILENAME);
|
|
19752
|
+
if (await acquireLock(lock)) {
|
|
19753
|
+
heldLocks.set(config, lock);
|
|
19754
|
+
return true;
|
|
19755
|
+
}
|
|
19756
|
+
log.info(`[${config.scope}] sync in progress elsewhere \u2014 skipped (another pull/push holds the lock)`);
|
|
19757
|
+
contended.add(config);
|
|
19758
|
+
return false;
|
|
19759
|
+
};
|
|
19644
19760
|
try {
|
|
19645
|
-
|
|
19646
|
-
} catch {
|
|
19647
|
-
}
|
|
19648
|
-
let projectConfig = null;
|
|
19649
|
-
try {
|
|
19650
|
-
projectConfig = await detectProjectConfig();
|
|
19651
|
-
} catch (e) {
|
|
19652
|
-
log.warn(`Project-scope detection error: ${e.message}`);
|
|
19653
|
-
}
|
|
19654
|
-
const projectMode = projectConfig !== null;
|
|
19655
|
-
const inheritUserScope = projectConfig?.inheritUserScope === true;
|
|
19656
|
-
let activeUserConfig = null;
|
|
19657
|
-
let inheritedUserConfig = null;
|
|
19658
|
-
if (projectMode && !inheritUserScope) {
|
|
19659
|
-
log.info("project scope detected, skipped user scope");
|
|
19660
|
-
} else {
|
|
19761
|
+
let projectConfig = null;
|
|
19661
19762
|
try {
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19666
|
-
|
|
19667
|
-
|
|
19668
|
-
|
|
19669
|
-
|
|
19670
|
-
|
|
19763
|
+
projectConfig = await detectProjectConfig();
|
|
19764
|
+
} catch (e) {
|
|
19765
|
+
log.warn(`Project-scope detection error: ${e.message}`);
|
|
19766
|
+
}
|
|
19767
|
+
const projectMode = projectConfig !== null;
|
|
19768
|
+
const inheritUserScope = projectConfig?.inheritUserScope === true;
|
|
19769
|
+
let activeUserConfig = null;
|
|
19770
|
+
let inheritedUserConfig = null;
|
|
19771
|
+
if (projectMode && !inheritUserScope) {
|
|
19772
|
+
log.info("project scope detected, skipped user scope");
|
|
19773
|
+
} else {
|
|
19774
|
+
try {
|
|
19775
|
+
const loadedUserConfig = await loadLocalConfigForScope("user");
|
|
19776
|
+
if (loadedUserConfig) {
|
|
19777
|
+
if (inheritUserScope) {
|
|
19778
|
+
inheritedUserConfig = loadedUserConfig;
|
|
19779
|
+
log.info("project scope detected, inheriting user-scope resources and knowledge");
|
|
19780
|
+
if (await lockScope(inheritedUserConfig)) {
|
|
19781
|
+
await pullForScope(inheritedUserConfig, options, {
|
|
19782
|
+
resourceTypes: ["skills", "rules", "docs", "agents"],
|
|
19783
|
+
revisionField: "lastInheritedPullRev"
|
|
19784
|
+
});
|
|
19785
|
+
}
|
|
19786
|
+
} else {
|
|
19787
|
+
activeUserConfig = loadedUserConfig;
|
|
19788
|
+
if (await lockScope(activeUserConfig)) {
|
|
19789
|
+
await pullForScope(activeUserConfig, options);
|
|
19790
|
+
}
|
|
19791
|
+
}
|
|
19792
|
+
} else if (inheritUserScope) {
|
|
19793
|
+
log.warn("user-scope inheritance is enabled, but user scope is not initialized");
|
|
19671
19794
|
} else {
|
|
19672
|
-
|
|
19673
|
-
await pullForScope(activeUserConfig, options);
|
|
19795
|
+
log.debug("No user-scope config found, skipping user pull");
|
|
19674
19796
|
}
|
|
19675
|
-
}
|
|
19676
|
-
log.warn(
|
|
19677
|
-
} else {
|
|
19678
|
-
log.debug("No user-scope config found, skipping user pull");
|
|
19797
|
+
} catch (e) {
|
|
19798
|
+
log.warn(`User-scope pull error: ${e.message}`);
|
|
19679
19799
|
}
|
|
19680
|
-
} catch (e) {
|
|
19681
|
-
log.warn(`User-scope pull error: ${e.message}`);
|
|
19682
19800
|
}
|
|
19683
|
-
|
|
19684
|
-
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19688
|
-
|
|
19801
|
+
if (projectConfig) {
|
|
19802
|
+
try {
|
|
19803
|
+
if (await lockScope(projectConfig)) {
|
|
19804
|
+
await pullForScope(projectConfig, options);
|
|
19805
|
+
}
|
|
19806
|
+
} catch (e) {
|
|
19807
|
+
log.warn(`Project-scope pull error: ${e.message}`);
|
|
19808
|
+
}
|
|
19689
19809
|
}
|
|
19690
|
-
|
|
19691
|
-
|
|
19692
|
-
|
|
19693
|
-
|
|
19694
|
-
|
|
19695
|
-
|
|
19696
|
-
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
if (projectConfig && projectConfig.repo.kind !== "http") {
|
|
19700
|
-
targets.push({
|
|
19701
|
-
repoPath: projectConfig.repo.localPath,
|
|
19702
|
-
username: projectConfig.username,
|
|
19703
|
-
opts: {
|
|
19704
|
-
skipTruncate: true,
|
|
19705
|
-
projectRoot: projectConfig.projectRoot,
|
|
19706
|
-
// Self mode routes stats/votes to the teamai-reports orphan branch.
|
|
19707
|
-
...projectConfig.repo.kind === "self" ? { selfConfig: projectConfig } : {}
|
|
19708
|
-
}
|
|
19709
|
-
});
|
|
19810
|
+
const reconcileUser = activeUserConfig && !contended.has(activeUserConfig) ? activeUserConfig : null;
|
|
19811
|
+
const reconcileProject = projectConfig && !contended.has(projectConfig) ? projectConfig : null;
|
|
19812
|
+
if (needsHookMigration) {
|
|
19813
|
+
const migrateScope = reconcileProject ?? reconcileUser;
|
|
19814
|
+
if (migrateScope) {
|
|
19815
|
+
try {
|
|
19816
|
+
await reinjectLegacyHooks(migrateScope);
|
|
19817
|
+
} catch {
|
|
19818
|
+
}
|
|
19710
19819
|
}
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
|
|
19716
|
-
|
|
19717
|
-
|
|
19718
|
-
|
|
19719
|
-
|
|
19720
|
-
|
|
19820
|
+
}
|
|
19821
|
+
await reconcileHooksAllScopes(reconcileUser, reconcileProject, options);
|
|
19822
|
+
await reconcileMcpAllScopes(reconcileUser, reconcileProject, options);
|
|
19823
|
+
await reconcileCoAuthorAllScopes(reconcileUser, reconcileProject, options);
|
|
19824
|
+
if (!options.dryRun) {
|
|
19825
|
+
try {
|
|
19826
|
+
const { reportUsageToTeam: reportUsageToTeam2 } = await Promise.resolve().then(() => (init_team_push(), team_push_exports));
|
|
19827
|
+
const { truncateUsageAfterReport: truncateUsageAfterReport2, readUsageEvents: readUsageEvents2 } = await Promise.resolve().then(() => (init_usage_tracker(), usage_tracker_exports));
|
|
19828
|
+
const targets = [];
|
|
19829
|
+
if (reconcileProject && reconcileProject.repo.kind !== "http") {
|
|
19830
|
+
targets.push({
|
|
19831
|
+
repoPath: reconcileProject.repo.localPath,
|
|
19832
|
+
username: reconcileProject.username,
|
|
19833
|
+
opts: {
|
|
19834
|
+
skipTruncate: true,
|
|
19835
|
+
projectRoot: reconcileProject.projectRoot,
|
|
19836
|
+
// Self mode routes stats/votes to the teamai-reports orphan branch.
|
|
19837
|
+
...reconcileProject.repo.kind === "self" ? { selfConfig: reconcileProject } : {}
|
|
19838
|
+
}
|
|
19839
|
+
});
|
|
19840
|
+
}
|
|
19841
|
+
if (reconcileUser && reconcileUser.repo.kind !== "http") {
|
|
19842
|
+
targets.push({
|
|
19843
|
+
repoPath: reconcileUser.repo.localPath,
|
|
19844
|
+
username: reconcileUser.username,
|
|
19845
|
+
opts: {
|
|
19846
|
+
skipTruncate: true,
|
|
19847
|
+
excludeProjectRoots: projectConfig?.projectRoot ? [projectConfig.projectRoot] : [],
|
|
19848
|
+
// Self mode routes stats/votes to the teamai-reports orphan branch —
|
|
19849
|
+
// never reset/pull the business repo working tree.
|
|
19850
|
+
...reconcileUser.repo.kind === "self" ? { selfConfig: reconcileUser } : {}
|
|
19851
|
+
}
|
|
19852
|
+
});
|
|
19853
|
+
}
|
|
19854
|
+
const eventCount = (await readUsageEvents2()).length;
|
|
19855
|
+
for (const t of targets) {
|
|
19856
|
+
try {
|
|
19857
|
+
await reportUsageToTeam2(t.repoPath, t.username, t.opts);
|
|
19858
|
+
} catch (e) {
|
|
19859
|
+
log.error(`Auto-report to ${t.repoPath} skipped: ${e.message}`);
|
|
19721
19860
|
}
|
|
19722
|
-
});
|
|
19723
|
-
}
|
|
19724
|
-
const eventCount = (await readUsageEvents2()).length;
|
|
19725
|
-
for (const t of targets) {
|
|
19726
|
-
try {
|
|
19727
|
-
await reportUsageToTeam2(t.repoPath, t.username, t.opts);
|
|
19728
|
-
} catch (e) {
|
|
19729
|
-
log.error(`Auto-report to ${t.repoPath} skipped: ${e.message}`);
|
|
19730
19861
|
}
|
|
19862
|
+
if (eventCount > 0 && targets.length > 0) {
|
|
19863
|
+
await truncateUsageAfterReport2(eventCount);
|
|
19864
|
+
}
|
|
19865
|
+
} catch (e) {
|
|
19866
|
+
log.debug(`Auto-report skipped: ${e.message}`);
|
|
19731
19867
|
}
|
|
19732
|
-
|
|
19733
|
-
|
|
19868
|
+
}
|
|
19869
|
+
const sourceConfig = reconcileProject ?? reconcileUser;
|
|
19870
|
+
if (sourceConfig) {
|
|
19871
|
+
try {
|
|
19872
|
+
const { pullSources: pullSources2 } = await Promise.resolve().then(() => (init_source(), source_exports));
|
|
19873
|
+
await pullSources2(sourceConfig, options);
|
|
19874
|
+
} catch (e) {
|
|
19875
|
+
log.debug(`Source pull skipped: ${e.message}`);
|
|
19734
19876
|
}
|
|
19735
|
-
} catch (e) {
|
|
19736
|
-
log.debug(`Auto-report skipped: ${e.message}`);
|
|
19737
19877
|
}
|
|
19738
|
-
}
|
|
19739
|
-
|
|
19740
|
-
|
|
19741
|
-
try {
|
|
19742
|
-
const { pullSources: pullSources2 } = await Promise.resolve().then(() => (init_source(), source_exports));
|
|
19743
|
-
await pullSources2(sourceConfig, options);
|
|
19744
|
-
} catch (e) {
|
|
19745
|
-
log.debug(`Source pull skipped: ${e.message}`);
|
|
19878
|
+
} finally {
|
|
19879
|
+
for (const lock of heldLocks.values()) {
|
|
19880
|
+
await releaseLock(lock);
|
|
19746
19881
|
}
|
|
19747
19882
|
}
|
|
19748
19883
|
}
|
|
@@ -36315,7 +36450,7 @@ var init_schema = __esm({
|
|
|
36315
36450
|
|
|
36316
36451
|
// src/repo-list/store.ts
|
|
36317
36452
|
import fs40 from "fs-extra";
|
|
36318
|
-
import { parse as
|
|
36453
|
+
import { parse as parseYaml3 } from "yaml";
|
|
36319
36454
|
async function loadRepoList(filePath) {
|
|
36320
36455
|
const exists3 = await fs40.pathExists(filePath);
|
|
36321
36456
|
if (!exists3) {
|
|
@@ -36326,7 +36461,7 @@ async function loadRepoList(filePath) {
|
|
|
36326
36461
|
throw new Error(`${filePath} exceeds max allowed size 10MB`);
|
|
36327
36462
|
}
|
|
36328
36463
|
const raw = await fs40.readFile(filePath, "utf8");
|
|
36329
|
-
const parsed =
|
|
36464
|
+
const parsed = parseYaml3(raw);
|
|
36330
36465
|
const result = RepoListFileSchema.parse(parsed);
|
|
36331
36466
|
return result;
|
|
36332
36467
|
}
|