MCPcopy Create free account
hub / github.com/Tencent/teamai-cli / syncRulesToLocal

Function syncRulesToLocal

src/utils/pre-push-sync.ts:60–128  ·  view source on GitHub ↗

* Sync rules: for each tool's rules/ directory, find rule files that differ * from the team repo and check whether the diff is from a team update.

(
  teamConfig: TeamaiConfig,
  localConfig: LocalConfig,
  repoPath: string,
  baseDir: string,
  lastPullRev: string,
)

Source from the content-addressed store, hash-verified

58 * from the team repo and check whether the diff is from a team update.
59 */
60async function syncRulesToLocal(
61 teamConfig: TeamaiConfig,
62 localConfig: LocalConfig,
63 repoPath: string,
64 baseDir: string,
65 lastPullRev: string,
66): Promise<void> {
67 const teamRulesDir = path.join(repoPath, 'rules');
68 if (!await pathExists(teamRulesDir)) return;
69
70 for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
71 if (!toolPath.rules) continue;
72 if (!await ResourceHandler.isToolInstalled(toolPath.rules, baseDir)) continue;
73
74 const rulesDir = path.join(baseDir, toolPath.rules);
75 if (!await pathExists(rulesDir)) continue;
76
77 // Cursor-compatible copies are `.mdc` with derived frontmatter; every other
78 // tool's are verbatim `.md`. Matching only `.md` would skip `.mdc` tools,
79 // leaving a stale copy that push then reports as "modified" and sends
80 // upstream — silently reverting the teammate's update.
81 const ext = ruleFileExtensionForTool(tool);
82 const isMdcTool = usesCursorMdcRules(tool);
83
84 const files = await listFilesRecursive(rulesDir);
85 for (const file of files) {
86 if (!file.endsWith(ext)) continue;
87 const name = file.slice(0, -ext.length);
88 if (EXCLUDED_RULE_NAMES.has(name)) continue;
89
90 const localFilePath = path.join(rulesDir, file);
91 // The team repo always stores the tool-neutral `.md`.
92 const teamRelPath = `rules/${name}.md`;
93 const teamFilePath = path.join(teamRulesDir, `${name}.md`);
94
95 // Only process files that exist in both places but differ
96 if (!await pathExists(teamFilePath)) continue;
97 if (isMdcTool) {
98 const localRaw = await readFileSafe(localFilePath);
99 const teamRaw = await readFileSafe(teamFilePath);
100 if (localRaw === null || teamRaw === null) continue;
101 if (cursorMdcBodyEqualsTeamMd(localRaw, teamRaw)) continue;
102
103 const oldContent = await getFileContentAtRev(repoPath, lastPullRev, teamRelPath);
104 if (oldContent === null) continue; // Didn't exist at lastPullRev — ambiguous, skip
105
106 // Compare bodies: the local `.mdc` never matched the team `.md` byte for byte.
107 if (cursorMdcBodyEqualsTeamMd(localRaw, oldContent.toString('utf-8'))) {
108 await writeFile(localFilePath, teamRuleToCursorMdc(teamRaw));
109 log.debug(`Pre-push sync: updated ${tool} rule ${name} to match team repo`);
110 }
111 continue;
112 }
113
114 if (await fileContentEqual(localFilePath, teamFilePath)) continue;
115
116 // They differ — check if local matches the old team repo version
117 const oldContent = await getFileContentAtRev(repoPath, lastPullRev, teamRelPath);

Callers 1

syncTeamUpdatesToLocalFunction · 0.85

Calls 13

pathExistsFunction · 0.85
scopedToolPathsFunction · 0.85
ruleFileExtensionForToolFunction · 0.85
usesCursorMdcRulesFunction · 0.85
listFilesRecursiveFunction · 0.85
getFileContentAtRevFunction · 0.85
writeFileFunction · 0.85
teamRuleToCursorMdcFunction · 0.85
fileContentEqualFunction · 0.85
fileContentEqualToBufferFunction · 0.85
copyFileFunction · 0.85

Tested by

no test coverage detected