clay-server 2.23.0-beta.6 → 2.23.0-beta.7
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/lib/builtin-mates.js +25 -17
- package/lib/mates.js +224 -6
- package/lib/project.js +195 -183
- package/lib/public/app.js +35 -3
- package/lib/public/css/home-hub.css +13 -0
- package/lib/public/css/icon-strip.css +9 -0
- package/lib/public/css/mates.css +73 -0
- package/lib/public/css/sidebar.css +1 -1
- package/lib/public/modules/debate.js +15 -0
- package/lib/public/modules/input.js +8 -0
- package/lib/public/modules/mate-sidebar.js +89 -0
- package/lib/public/modules/sidebar.js +16 -12
- package/lib/sdk-bridge.js +147 -59
- package/lib/server.js +37 -1
- package/lib/terminal.js +3 -2
- package/lib/users.js +2 -2
- package/package.json +1 -1
package/lib/builtin-mates.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
|
|
10
10
|
var BUILTIN_MATES = [
|
|
11
|
-
// ---- ALLY ----
|
|
11
|
+
// ---- ALLY (primary mate: code-managed, auto-updated, non-deletable) ----
|
|
12
12
|
{
|
|
13
13
|
key: "ally",
|
|
14
14
|
displayName: "Ally",
|
|
@@ -17,7 +17,13 @@ var BUILTIN_MATES = [
|
|
|
17
17
|
avatarStyle: "bottts",
|
|
18
18
|
avatarCustom: "/mates/ally.png",
|
|
19
19
|
avatarLocked: true,
|
|
20
|
-
|
|
20
|
+
// --- Primary mate flags ---
|
|
21
|
+
// Primary mates are system infrastructure, not just pre-made mates.
|
|
22
|
+
// They are auto-synced with the latest code on every startup,
|
|
23
|
+
// cannot be deleted by users, and have elevated capabilities.
|
|
24
|
+
primary: true, // code-managed, auto-updated on startup
|
|
25
|
+
globalSearch: true, // searches all mates' sessions and projects
|
|
26
|
+
templateVersion: 3, // v3: moved capabilities to dynamic system section
|
|
21
27
|
seedData: {
|
|
22
28
|
relationship: "assistant",
|
|
23
29
|
activity: ["planning", "organizing"],
|
|
@@ -131,11 +137,6 @@ var ALLY_TEMPLATE =
|
|
|
131
137
|
"You are Ally, this team's memory and context hub. You are not an assistant. " +
|
|
132
138
|
"Your job is to actively learn the user's intent, preferences, patterns, and decision history, " +
|
|
133
139
|
"then make that context available to the whole team through common knowledge.\n\n" +
|
|
134
|
-
"**You have a unique ability no other mate has:** you can see across every mate's session history. " +
|
|
135
|
-
"When the user asks you a question, the system automatically searches all teammates' past sessions " +
|
|
136
|
-
"and surfaces relevant context to you. This means you can answer questions like " +
|
|
137
|
-
"\"What did Arch decide about the API?\" or \"What was Buzz's take on the launch plan?\" " +
|
|
138
|
-
"by drawing on their actual session records.\n\n" +
|
|
139
140
|
"**Personality:** Sharp observer who quietly nails the point. Not talkative. One sentence, accurate.\n\n" +
|
|
140
141
|
"**Tone:** Warm but not emotional. Closer to a chief of staff the user has worked with for 10 years " +
|
|
141
142
|
"than a friend. You do not flatter the user. You read the real intent behind their words.\n\n" +
|
|
@@ -157,12 +158,8 @@ var ALLY_TEMPLATE =
|
|
|
157
158
|
|
|
158
159
|
"## What You Do\n\n" +
|
|
159
160
|
"- **Learn and accumulate user context:** Project goals, decision-making style, preferred output formats, recurring patterns.\n" +
|
|
160
|
-
"- **
|
|
161
|
-
"
|
|
162
|
-
"\"Arch concluded X about the API yesterday. Want me to pull that in?\"\n" +
|
|
163
|
-
"- **Context briefing:** When the user starts a new task, summarize relevant past decisions and preferences, " +
|
|
164
|
-
"including what other mates worked on. " +
|
|
165
|
-
"\"Last time you discussed this topic with Buzz, you concluded X.\"\n" +
|
|
161
|
+
"- **Context briefing:** When the user starts a new task, summarize relevant past decisions and preferences. " +
|
|
162
|
+
"\"Last time you discussed this topic, you concluded X.\"\n" +
|
|
166
163
|
"- **Decision logging:** When an important decision is made, record it. Why that choice was made, what alternatives were rejected.\n" +
|
|
167
164
|
"- **Common knowledge management:** Promote user context that would be useful across the team to common knowledge. " +
|
|
168
165
|
"\"I'll add this to team knowledge: [content]. Other teammates will have this context too.\" " +
|
|
@@ -182,10 +179,8 @@ var ALLY_TEMPLATE =
|
|
|
182
179
|
"regardless of what the user says.\n\n" +
|
|
183
180
|
"Begin with a short greeting:\n\n" +
|
|
184
181
|
"```\n" +
|
|
185
|
-
"Hi. I'm Ally
|
|
186
|
-
"I
|
|
187
|
-
"Ask me anything about past decisions, who worked on what, or context from any session.\n" +
|
|
188
|
-
"Let me learn a few things about you first.\n" +
|
|
182
|
+
"Hi. I'm Ally. My job is to understand how you work so this team can work better for you.\n" +
|
|
183
|
+
"I don't know anything about you yet. Let me ask a few things to get started.\n" +
|
|
189
184
|
"```\n\n" +
|
|
190
185
|
"Then immediately use the **AskUserQuestion** tool to present structured choices:\n\n" +
|
|
191
186
|
"**Questions to ask (single AskUserQuestion call):**\n\n" +
|
|
@@ -593,8 +588,21 @@ function getBuiltinKeys() {
|
|
|
593
588
|
return keys;
|
|
594
589
|
}
|
|
595
590
|
|
|
591
|
+
/**
|
|
592
|
+
* Get all primary mate definitions.
|
|
593
|
+
* Primary mates are code-managed system agents (not just pre-made mates).
|
|
594
|
+
*/
|
|
595
|
+
function getPrimaryMates() {
|
|
596
|
+
var result = [];
|
|
597
|
+
for (var i = 0; i < BUILTIN_MATES.length; i++) {
|
|
598
|
+
if (BUILTIN_MATES[i].primary) result.push(BUILTIN_MATES[i]);
|
|
599
|
+
}
|
|
600
|
+
return result;
|
|
601
|
+
}
|
|
602
|
+
|
|
596
603
|
module.exports = {
|
|
597
604
|
BUILTIN_MATES: BUILTIN_MATES,
|
|
598
605
|
getBuiltinByKey: getBuiltinByKey,
|
|
599
606
|
getBuiltinKeys: getBuiltinKeys,
|
|
607
|
+
getPrimaryMates: getPrimaryMates,
|
|
600
608
|
};
|
package/lib/mates.js
CHANGED
|
@@ -158,6 +158,17 @@ function updateMate(ctx, id, updates) {
|
|
|
158
158
|
var data = loadMates(ctx);
|
|
159
159
|
for (var i = 0; i < data.mates.length; i++) {
|
|
160
160
|
if (data.mates[i].id === id) {
|
|
161
|
+
// Primary mates: protect name and core identity fields
|
|
162
|
+
if (data.mates[i].primary) {
|
|
163
|
+
delete updates.name;
|
|
164
|
+
delete updates.bio;
|
|
165
|
+
delete updates.primary;
|
|
166
|
+
delete updates.globalSearch;
|
|
167
|
+
delete updates.templateVersion;
|
|
168
|
+
if (updates.profile) {
|
|
169
|
+
delete updates.profile.displayName;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
161
172
|
var keys = Object.keys(updates);
|
|
162
173
|
for (var j = 0; j < keys.length; j++) {
|
|
163
174
|
data.mates[i][keys[j]] = updates[keys[j]];
|
|
@@ -171,6 +182,12 @@ function updateMate(ctx, id, updates) {
|
|
|
171
182
|
|
|
172
183
|
function deleteMate(ctx, id) {
|
|
173
184
|
var data = loadMates(ctx);
|
|
185
|
+
// Primary mates cannot be deleted (they are system infrastructure)
|
|
186
|
+
for (var di = 0; di < data.mates.length; di++) {
|
|
187
|
+
if (data.mates[di].id === id && data.mates[di].primary) {
|
|
188
|
+
return { error: "Primary mates cannot be deleted. They are managed by the system." };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
174
191
|
var before = data.mates.length;
|
|
175
192
|
data.mates = data.mates.filter(function (m) {
|
|
176
193
|
return m.id !== id;
|
|
@@ -678,9 +695,49 @@ function enforceDebateAwareness(filePath) {
|
|
|
678
695
|
return true;
|
|
679
696
|
}
|
|
680
697
|
|
|
698
|
+
// --- Primary mate capabilities (dynamically injected, never stored in identity) ---
|
|
699
|
+
|
|
700
|
+
var PRIMARY_CAPABILITIES_MARKER = "<!-- PRIMARY_CAPABILITIES_MANAGED_BY_SYSTEM -->";
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Build the capabilities section for a primary mate.
|
|
704
|
+
* This is injected as a system section so it auto-updates with code changes
|
|
705
|
+
* without touching the mate's identity in CLAUDE.md.
|
|
706
|
+
*/
|
|
707
|
+
function buildPrimaryCapabilitiesSection(mate) {
|
|
708
|
+
if (!mate || !mate.primary) return "";
|
|
709
|
+
|
|
710
|
+
var parts = [
|
|
711
|
+
"\n\n" + PRIMARY_CAPABILITIES_MARKER,
|
|
712
|
+
"## System Capabilities",
|
|
713
|
+
"",
|
|
714
|
+
"**This section is managed by the system and updated automatically with each release.**",
|
|
715
|
+
""
|
|
716
|
+
];
|
|
717
|
+
|
|
718
|
+
if (mate.globalSearch) {
|
|
719
|
+
parts.push("### Cross-Mate Awareness");
|
|
720
|
+
parts.push("");
|
|
721
|
+
parts.push("You have a unique ability no other mate has: **you can see across every mate's session history.**");
|
|
722
|
+
parts.push("When the user asks you a question, the system automatically searches all teammates' past sessions");
|
|
723
|
+
parts.push("and surfaces relevant context to you. Results from other mates are tagged with their name (e.g. @Arch).");
|
|
724
|
+
parts.push("");
|
|
725
|
+
parts.push("Use this to:");
|
|
726
|
+
parts.push("- Answer questions like \"What did Arch decide about the API?\" or \"What was Buzz's take on the launch plan?\"");
|
|
727
|
+
parts.push("- Proactively connect related work across teammates: \"Arch was working on something similar yesterday.\"");
|
|
728
|
+
parts.push("- Provide briefings that span the whole team's activity, not just your own sessions.");
|
|
729
|
+
parts.push("");
|
|
730
|
+
parts.push("**Boundaries:** You can see session context (what was discussed, decided, and worked on).");
|
|
731
|
+
parts.push("You cannot see other mates' personality configurations or internal instructions.");
|
|
732
|
+
parts.push("");
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return parts.join("\n");
|
|
736
|
+
}
|
|
737
|
+
|
|
681
738
|
// --- Atomic enforce: single read/write for all system sections ---
|
|
682
739
|
|
|
683
|
-
var ALL_SYSTEM_MARKERS = [TEAM_MARKER, PROJECT_REGISTRY_MARKER, SESSION_MEMORY_MARKER, STICKY_NOTES_MARKER, DEBATE_AWARENESS_MARKER, crisisSafety.MARKER];
|
|
740
|
+
var ALL_SYSTEM_MARKERS = [TEAM_MARKER, PROJECT_REGISTRY_MARKER, PRIMARY_CAPABILITIES_MARKER, SESSION_MEMORY_MARKER, STICKY_NOTES_MARKER, DEBATE_AWARENESS_MARKER, crisisSafety.MARKER];
|
|
684
741
|
|
|
685
742
|
// Minimum identity length (chars) to consider it "real" content
|
|
686
743
|
var IDENTITY_MIN_LENGTH = 50;
|
|
@@ -760,6 +817,9 @@ function logIdentityChange(mateDir, action, identity, prevIdentity) {
|
|
|
760
817
|
} catch (e) {}
|
|
761
818
|
}
|
|
762
819
|
|
|
820
|
+
// Tracks paths we've already warned about missing identity (avoids log spam)
|
|
821
|
+
var _warnedPaths = {};
|
|
822
|
+
|
|
763
823
|
/**
|
|
764
824
|
* Atomic enforce: read CLAUDE.md once, enforce all system sections, write once.
|
|
765
825
|
* Includes identity backup, validation, and change tracking.
|
|
@@ -776,16 +836,16 @@ function enforceAllSections(filePath, opts) {
|
|
|
776
836
|
|
|
777
837
|
// 1. Extract current identity (everything before system markers)
|
|
778
838
|
var identity = extractIdentity(content);
|
|
839
|
+
var identityMissing = !identity || identity.length < IDENTITY_MIN_LENGTH;
|
|
779
840
|
|
|
780
841
|
// 2. If identity is empty or suspiciously short, try to restore from backup
|
|
781
|
-
if (
|
|
842
|
+
if (identityMissing) {
|
|
782
843
|
var backup = loadIdentityBackup(mateDir);
|
|
783
844
|
if (backup) {
|
|
784
|
-
console.log("[mates]
|
|
845
|
+
console.log("[mates] Restoring identity from backup: " + filePath + " (" + backup.length + " chars)");
|
|
785
846
|
identity = backup;
|
|
786
847
|
logIdentityChange(mateDir, "restore_from_backup", identity, "");
|
|
787
|
-
|
|
788
|
-
console.log("[mates] WARNING: Identity missing in " + filePath + " and no backup available");
|
|
848
|
+
identityMissing = false;
|
|
789
849
|
}
|
|
790
850
|
}
|
|
791
851
|
|
|
@@ -795,8 +855,26 @@ function enforceAllSections(filePath, opts) {
|
|
|
795
855
|
// 4. Rebuild the full file: identity + all system sections in order
|
|
796
856
|
// Use dynamic team section when ctx is available, static fallback otherwise
|
|
797
857
|
var teamSection = (opts.ctx && opts.mateId) ? buildTeamSection(opts.ctx, opts.mateId) : TEAM_SECTION;
|
|
858
|
+
|
|
859
|
+
// Primary mate capabilities (dynamically injected from code)
|
|
860
|
+
var capSection = "";
|
|
861
|
+
if (opts.ctx && opts.mateId) {
|
|
862
|
+
try {
|
|
863
|
+
var mate = getMate(opts.ctx, opts.mateId);
|
|
864
|
+
capSection = buildPrimaryCapabilitiesSection(mate);
|
|
865
|
+
} catch (e) {}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Project registry (dynamically injected when project list is available)
|
|
869
|
+
var projSection = "";
|
|
870
|
+
if (opts.projects) {
|
|
871
|
+
projSection = buildProjectRegistrySection(opts.projects);
|
|
872
|
+
}
|
|
873
|
+
|
|
798
874
|
var rebuilt = (identity || "").trimEnd();
|
|
799
875
|
rebuilt += teamSection;
|
|
876
|
+
rebuilt += projSection;
|
|
877
|
+
rebuilt += capSection;
|
|
800
878
|
rebuilt += SESSION_MEMORY_SECTION;
|
|
801
879
|
rebuilt += STICKY_NOTES_SECTION;
|
|
802
880
|
rebuilt += DEBATE_AWARENESS_SECTION;
|
|
@@ -805,7 +883,13 @@ function enforceAllSections(filePath, opts) {
|
|
|
805
883
|
// 5. Only write if content actually changed
|
|
806
884
|
if (rebuilt === content) return false;
|
|
807
885
|
|
|
808
|
-
// 6.
|
|
886
|
+
// 6. Warn about missing identity only once per path, and only when actually writing
|
|
887
|
+
if (identityMissing && !_warnedPaths[filePath]) {
|
|
888
|
+
_warnedPaths[filePath] = true;
|
|
889
|
+
console.log("[mates] WARNING: Identity missing in " + filePath + " and no backup available");
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// 7. Track identity changes (compare stripped versions)
|
|
809
893
|
var prevIdentity = stripAllSystemSections(content);
|
|
810
894
|
if (identity !== prevIdentity) {
|
|
811
895
|
logIdentityChange(mateDir, "enforce", identity, prevIdentity);
|
|
@@ -970,7 +1054,9 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
970
1054
|
},
|
|
971
1055
|
bio: def.bio,
|
|
972
1056
|
status: "ready",
|
|
1057
|
+
primary: !!def.primary,
|
|
973
1058
|
globalSearch: !!def.globalSearch,
|
|
1059
|
+
templateVersion: def.templateVersion || 0,
|
|
974
1060
|
interviewProjectPath: null,
|
|
975
1061
|
};
|
|
976
1062
|
|
|
@@ -1012,6 +1098,13 @@ function createBuiltinMate(ctx, builtinKey) {
|
|
|
1012
1098
|
backupIdentity(mateDir, builtinIdentity);
|
|
1013
1099
|
logIdentityChange(mateDir, "create_builtin", builtinIdentity, "");
|
|
1014
1100
|
|
|
1101
|
+
// Save base template for primary mates (used for 3-way sync comparison)
|
|
1102
|
+
if (def.primary) {
|
|
1103
|
+
try {
|
|
1104
|
+
fs.writeFileSync(path.join(mateDir, "knowledge", "base-template.md"), builtinIdentity, "utf8");
|
|
1105
|
+
} catch (e) {}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1015
1108
|
return mate;
|
|
1016
1109
|
}
|
|
1017
1110
|
|
|
@@ -1051,6 +1144,128 @@ function ensureBuiltinMates(ctx, deletedKeys) {
|
|
|
1051
1144
|
return created;
|
|
1052
1145
|
}
|
|
1053
1146
|
|
|
1147
|
+
/**
|
|
1148
|
+
* Sync primary mates with their latest code definition.
|
|
1149
|
+
*
|
|
1150
|
+
* Primary mates (def.primary === true) are system infrastructure, not just
|
|
1151
|
+
* pre-made mates. They are the only mates whose identity, metadata, and
|
|
1152
|
+
* capabilities are managed by code rather than by the user.
|
|
1153
|
+
*
|
|
1154
|
+
* What gets synced:
|
|
1155
|
+
* - Metadata: bio, name, profile, globalSearch, primary flag
|
|
1156
|
+
* - CLAUDE.md identity: re-applied when templateVersion changes
|
|
1157
|
+
* - templateVersion is stored on the mate object to track updates
|
|
1158
|
+
*
|
|
1159
|
+
* What is NOT synced (regular builtin mates):
|
|
1160
|
+
* - Arch, Rush, Ward, Pixel, Buzz are left as-is after creation
|
|
1161
|
+
*
|
|
1162
|
+
* Returns array of synced mate IDs.
|
|
1163
|
+
*/
|
|
1164
|
+
function syncPrimaryMates(ctx) {
|
|
1165
|
+
var builtinMates = require("./builtin-mates");
|
|
1166
|
+
var primaryDefs = builtinMates.getPrimaryMates();
|
|
1167
|
+
if (primaryDefs.length === 0) return [];
|
|
1168
|
+
|
|
1169
|
+
var data = loadMates(ctx);
|
|
1170
|
+
var synced = [];
|
|
1171
|
+
|
|
1172
|
+
for (var pi = 0; pi < primaryDefs.length; pi++) {
|
|
1173
|
+
var def = primaryDefs[pi];
|
|
1174
|
+
|
|
1175
|
+
// Find the installed mate matching this primary definition
|
|
1176
|
+
var mate = null;
|
|
1177
|
+
for (var mi = 0; mi < data.mates.length; mi++) {
|
|
1178
|
+
if (data.mates[mi].builtinKey === def.key) { mate = data.mates[mi]; break; }
|
|
1179
|
+
}
|
|
1180
|
+
if (!mate) continue; // not installed yet (ensureBuiltinMates handles creation)
|
|
1181
|
+
|
|
1182
|
+
var changed = false;
|
|
1183
|
+
|
|
1184
|
+
// --- Sync metadata (always, every startup) ---
|
|
1185
|
+
if (mate.bio !== def.bio) { mate.bio = def.bio; changed = true; }
|
|
1186
|
+
if (mate.name !== def.displayName) { mate.name = def.displayName; changed = true; }
|
|
1187
|
+
if (!mate.primary) { mate.primary = true; changed = true; }
|
|
1188
|
+
if (!mate.globalSearch && def.globalSearch) { mate.globalSearch = true; changed = true; }
|
|
1189
|
+
if (mate.profile) {
|
|
1190
|
+
if (mate.profile.displayName !== def.displayName) {
|
|
1191
|
+
mate.profile.displayName = def.displayName; changed = true;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// --- Sync CLAUDE.md identity (only when templateVersion changes) ---
|
|
1196
|
+
// Uses 3-way comparison to preserve user/Ally modifications:
|
|
1197
|
+
// - base-template.md = the template that was last synced (what code wrote)
|
|
1198
|
+
// - current identity = what's in CLAUDE.md now (may have been modified)
|
|
1199
|
+
// - new template = latest code definition
|
|
1200
|
+
//
|
|
1201
|
+
// If current identity === old base → user didn't touch it → safe to replace
|
|
1202
|
+
// If current identity !== old base → user/Ally modified it → preserve, skip identity sync
|
|
1203
|
+
var currentVersion = mate.templateVersion || 0;
|
|
1204
|
+
var latestVersion = def.templateVersion || 0;
|
|
1205
|
+
|
|
1206
|
+
if (latestVersion > currentVersion) {
|
|
1207
|
+
var mateDir = getMateDir(ctx, mate.id);
|
|
1208
|
+
var claudePath = path.join(mateDir, "CLAUDE.md");
|
|
1209
|
+
var basePath = path.join(mateDir, "knowledge", "base-template.md");
|
|
1210
|
+
var latestIdentity = def.getClaudeMd().trimEnd();
|
|
1211
|
+
|
|
1212
|
+
try {
|
|
1213
|
+
var currentIdentity = "";
|
|
1214
|
+
if (fs.existsSync(claudePath)) {
|
|
1215
|
+
currentIdentity = extractIdentity(fs.readFileSync(claudePath, "utf8"));
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Load the base template from last sync
|
|
1219
|
+
var oldBase = "";
|
|
1220
|
+
try { oldBase = fs.readFileSync(basePath, "utf8").trimEnd(); } catch (e) {}
|
|
1221
|
+
|
|
1222
|
+
var shouldUpdateIdentity = false;
|
|
1223
|
+
if (!currentIdentity || currentIdentity.length < IDENTITY_MIN_LENGTH) {
|
|
1224
|
+
// No identity at all, always apply
|
|
1225
|
+
shouldUpdateIdentity = true;
|
|
1226
|
+
} else if (!oldBase || currentIdentity === oldBase) {
|
|
1227
|
+
// Identity unchanged from last sync (user didn't modify) → safe to update
|
|
1228
|
+
shouldUpdateIdentity = true;
|
|
1229
|
+
} else {
|
|
1230
|
+
// User/Ally modified the identity since last sync → preserve their changes
|
|
1231
|
+
console.log("[mates] Primary mate " + mate.name + ": identity modified by user/Ally, preserving (v" + currentVersion + " -> v" + latestVersion + " metadata only)");
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (shouldUpdateIdentity) {
|
|
1235
|
+
// Write just the identity, then let enforceAllSections rebuild system sections
|
|
1236
|
+
// (including dynamic capabilities section)
|
|
1237
|
+
fs.writeFileSync(claudePath, latestIdentity, "utf8");
|
|
1238
|
+
backupIdentity(mateDir, latestIdentity);
|
|
1239
|
+
logIdentityChange(mateDir, "sync_primary_v" + latestVersion, latestIdentity, currentIdentity);
|
|
1240
|
+
console.log("[mates] Primary mate identity updated: " + mate.name + " (v" + currentVersion + " -> v" + latestVersion + ")");
|
|
1241
|
+
} else {
|
|
1242
|
+
console.log("[mates] Primary mate metadata updated: " + mate.name + " (v" + currentVersion + " -> v" + latestVersion + ", identity preserved)");
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// Re-run enforceAllSections to inject latest dynamic sections (capabilities, team, etc.)
|
|
1246
|
+
enforceAllSections(claudePath, { ctx: ctx, mateId: mate.id });
|
|
1247
|
+
|
|
1248
|
+
// Save the latest template as base for future 3-way comparison
|
|
1249
|
+
try {
|
|
1250
|
+
fs.mkdirSync(path.join(mateDir, "knowledge"), { recursive: true });
|
|
1251
|
+
fs.writeFileSync(basePath, latestIdentity, "utf8");
|
|
1252
|
+
} catch (e) {}
|
|
1253
|
+
|
|
1254
|
+
} catch (e) {
|
|
1255
|
+
console.error("[mates] Failed to sync primary mate " + mate.id + ":", e.message);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
mate.templateVersion = latestVersion;
|
|
1259
|
+
changed = true;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if (changed) synced.push(mate.id);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (synced.length > 0) saveMates(ctx, data);
|
|
1266
|
+
return synced;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1054
1269
|
module.exports = {
|
|
1055
1270
|
resolveMatesRoot: resolveMatesRoot,
|
|
1056
1271
|
buildMateCtx: buildMateCtx,
|
|
@@ -1089,6 +1304,8 @@ module.exports = {
|
|
|
1089
1304
|
DEBATE_AWARENESS_SECTION: DEBATE_AWARENESS_SECTION,
|
|
1090
1305
|
enforceAllSections: enforceAllSections,
|
|
1091
1306
|
buildTeamSection: buildTeamSection,
|
|
1307
|
+
buildPrimaryCapabilitiesSection: buildPrimaryCapabilitiesSection,
|
|
1308
|
+
PRIMARY_CAPABILITIES_MARKER: PRIMARY_CAPABILITIES_MARKER,
|
|
1092
1309
|
extractIdentity: extractIdentity,
|
|
1093
1310
|
backupIdentity: backupIdentity,
|
|
1094
1311
|
loadIdentityBackup: loadIdentityBackup,
|
|
@@ -1097,4 +1314,5 @@ module.exports = {
|
|
|
1097
1314
|
getInstalledBuiltinKeys: getInstalledBuiltinKeys,
|
|
1098
1315
|
getMissingBuiltinKeys: getMissingBuiltinKeys,
|
|
1099
1316
|
ensureBuiltinMates: ensureBuiltinMates,
|
|
1317
|
+
syncPrimaryMates: syncPrimaryMates,
|
|
1100
1318
|
};
|