@wrongstack/core 0.303.0 → 0.305.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chronicle/project-server.js +18 -48
- package/dist/coordination/agents/index.js +530 -130
- package/dist/coordination/agents/project-agent-consolidation.d.ts +5 -0
- package/dist/coordination/agents/project-agent-directive-outcome.d.ts +57 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +26 -12
- package/dist/coordination/agents/project-agent-learning-policy.d.ts +22 -1
- package/dist/coordination/agents/project-agent-learning-structured.d.ts +46 -1
- package/dist/coordination/agents/project-agent-quarantine.d.ts +63 -0
- package/dist/coordination/agents/project-agent-skill-layer.d.ts +55 -10
- package/dist/coordination/agents/types.d.ts +10 -2
- package/dist/coordination/director-prompts.d.ts +19 -6
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/fleet.d.ts +0 -6
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.js +1581 -955
- package/dist/coordination/mailbox-project-server.js +28 -57
- package/dist/core/agent-types.d.ts +4 -2
- package/dist/core/agent.d.ts +1 -0
- package/dist/core/context.d.ts +15 -0
- package/dist/core/conversation-state.d.ts +14 -0
- package/dist/core/fallback-profile-manager.d.ts +70 -2
- package/dist/core/index.js +308 -108
- package/dist/core/system-prompt-blocks.d.ts +1 -1
- package/dist/core/system-prompt-builder.d.ts +13 -1
- package/dist/core/system-prompt-glossary.d.ts +73 -0
- package/dist/core/system-prompt-memory-skills.d.ts +2 -2
- package/dist/defaults/index.js +910 -693
- package/dist/execution/council-orchestrator.d.ts +3 -13
- package/dist/execution/index.js +211 -75
- package/dist/execution/one-shot-llm.d.ts +5 -0
- package/dist/hq/index.js +17 -7
- package/dist/hq/protocol/kanban.d.ts +21 -0
- package/dist/hq/protocol.js +5 -1
- package/dist/hq/redaction.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3505 -2539
- package/dist/infrastructure/index.js +247 -122
- package/dist/plugin/index.js +101 -3
- package/dist/registry/index.js +11 -0
- package/dist/registry/tool-registry.d.ts +8 -0
- package/dist/replay/hash.d.ts +9 -0
- package/dist/replay/index.js +14 -4
- package/dist/replay/replay-provider-runner.d.ts +31 -1
- package/dist/security/index.js +25 -20
- package/dist/security/secret-vault.d.ts +2 -0
- package/dist/session-catalog/index.js +62 -8
- package/dist/session-catalog/project-server.js +109 -78
- package/dist/session-catalog/protocol.d.ts +11 -4
- package/dist/session-catalog/store.d.ts +2 -2
- package/dist/storage/index.js +224 -67
- package/dist/storage/memory-consolidator.d.ts +4 -2
- package/dist/storage/session-resume-validation.d.ts +24 -0
- package/dist/storage/session-store/directory-scan.d.ts +5 -1
- package/dist/storage/session-store/fork-session.d.ts +13 -1
- package/dist/storage/session-store/load-cache.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +5 -0
- package/dist/storage/session-store.d.ts +18 -0
- package/dist/tools/index.js +174 -74
- package/dist/types/config/mcp-features.d.ts +31 -1
- package/dist/types/config/root.d.ts +12 -0
- package/dist/types/config/tools.d.ts +22 -0
- package/dist/types/config/ui.d.ts +7 -4
- package/dist/types/default-config.d.ts +1 -0
- package/dist/types/index.js +24 -1
- package/dist/types/session.d.ts +9 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +214 -76
- package/dist/utils/project-state-guard.d.ts +21 -0
- package/dist/utils/session-scoped-path.d.ts +17 -0
- package/dist/utils/todos-format.d.ts +20 -0
- package/instructions/leader-after-task.md +3 -4
- package/instructions/system-lite.md +10 -13
- package/instructions/system-pro.md +18 -25
- package/instructions/system.md +18 -23
- package/package.json +3 -3
- package/skills/wrongstack-kanban/SKILL.md +95 -124
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/coordination/agents/agent-prompts.ts
|
|
2
|
-
import { readFileSync as
|
|
3
|
-
import * as
|
|
2
|
+
import { readFileSync as readFileSync10, statSync as statSync2 } from "node:fs";
|
|
3
|
+
import * as path12 from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
// src/utils/wstack-paths.ts
|
|
@@ -143,8 +143,8 @@ function resolveWstackPaths(opts) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// src/coordination/agents/project-agent-identity.ts
|
|
146
|
-
import { existsSync as existsSync4, readFileSync as
|
|
147
|
-
import * as
|
|
146
|
+
import { existsSync as existsSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
147
|
+
import * as path11 from "node:path";
|
|
148
148
|
|
|
149
149
|
// src/security/capabilities.ts
|
|
150
150
|
var ToolCapabilities = {
|
|
@@ -777,8 +777,8 @@ function loadProjectAgentConfig(role, projectRoot) {
|
|
|
777
777
|
}
|
|
778
778
|
|
|
779
779
|
// src/coordination/agents/project-agent-consolidation.ts
|
|
780
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as
|
|
781
|
-
import * as
|
|
780
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
781
|
+
import * as path6 from "node:path";
|
|
782
782
|
|
|
783
783
|
// src/coordination/agents/project-agent-learning-entries.ts
|
|
784
784
|
function tokenOverlap(a, b) {
|
|
@@ -916,6 +916,21 @@ function normalizeForComparison(text) {
|
|
|
916
916
|
}
|
|
917
917
|
|
|
918
918
|
// src/coordination/agents/project-agent-learning-structured.ts
|
|
919
|
+
function directiveTrials(entry) {
|
|
920
|
+
const count = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
|
|
921
|
+
const applied = count(entry.applied);
|
|
922
|
+
const wins = Math.min(applied, count(entry.wins));
|
|
923
|
+
return { applied, wins, losses: applied - wins };
|
|
924
|
+
}
|
|
925
|
+
function directiveUtility(entry) {
|
|
926
|
+
const { applied, wins } = directiveTrials(entry);
|
|
927
|
+
return (wins + 1) / (applied + 2);
|
|
928
|
+
}
|
|
929
|
+
var DIRECTIVE_PROVEN_MIN_APPLIED = 5;
|
|
930
|
+
var DIRECTIVE_PROVEN_MIN_UTILITY = 0.7;
|
|
931
|
+
function isProvenDirective(entry) {
|
|
932
|
+
return directiveTrials(entry).applied >= DIRECTIVE_PROVEN_MIN_APPLIED && directiveUtility(entry) >= DIRECTIVE_PROVEN_MIN_UTILITY;
|
|
933
|
+
}
|
|
919
934
|
function parseLearnedEntryStamp(entry) {
|
|
920
935
|
const structuredMatch = entry.match(
|
|
921
936
|
/<!--\s*learned-stamp:\s*category=([\w-]+);\s*capturedAt=([^;]+?)\s*-->/
|
|
@@ -1034,6 +1049,10 @@ function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLear
|
|
|
1034
1049
|
const category = parseLearnedCategory(attributes["category"]) ?? "fact";
|
|
1035
1050
|
const capturedAt = attributes["capturedAt"] ?? "";
|
|
1036
1051
|
const skill = attributes["skill"];
|
|
1052
|
+
const trials = directiveTrials({
|
|
1053
|
+
applied: Number(attributes["applied"]),
|
|
1054
|
+
wins: Number(attributes["wins"])
|
|
1055
|
+
});
|
|
1037
1056
|
const start = (stamp.index ?? 0) + stamp[0].length;
|
|
1038
1057
|
const end = stamps[index + 1]?.index ?? raw.length;
|
|
1039
1058
|
const parsed = parseEntryBody(raw.slice(start, end));
|
|
@@ -1045,7 +1064,8 @@ function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLear
|
|
|
1045
1064
|
why: parsed.why || WHY_BY_CATEGORY[category],
|
|
1046
1065
|
how: parsed.how,
|
|
1047
1066
|
capturedAt,
|
|
1048
|
-
...skill ? { skill } : {}
|
|
1067
|
+
...skill ? { skill } : {},
|
|
1068
|
+
...trials.applied > 0 ? { applied: trials.applied, wins: trials.wins } : {}
|
|
1049
1069
|
});
|
|
1050
1070
|
}
|
|
1051
1071
|
if (structured.length === 0) {
|
|
@@ -1069,17 +1089,31 @@ function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLear
|
|
|
1069
1089
|
}
|
|
1070
1090
|
function mergeStructuredEntries(existing, fresh) {
|
|
1071
1091
|
const { what, why, how } = decomposeLearnedEntry(fresh.text, fresh.category);
|
|
1092
|
+
const key = directiveKey(fresh.text);
|
|
1093
|
+
const overlapping = existing.filter((entry) => tokenOverlap(entry.key, key) >= 0.55);
|
|
1094
|
+
const proven = overlapping.find(
|
|
1095
|
+
(entry) => entry.category === fresh.category && isProvenDirective(entry)
|
|
1096
|
+
);
|
|
1097
|
+
if (proven) return sortStructuredEntries([...existing]);
|
|
1098
|
+
const ancestor = overlapping.filter((entry) => entry.category === fresh.category).sort((a, b) => directiveTrials(b).applied - directiveTrials(a).applied)[0];
|
|
1099
|
+
const inherited = ancestor ? directiveTrials(ancestor) : { applied: 0, wins: 0 };
|
|
1072
1100
|
const freshEntry = {
|
|
1073
|
-
key
|
|
1101
|
+
key,
|
|
1074
1102
|
category: fresh.category,
|
|
1075
1103
|
what,
|
|
1076
1104
|
why,
|
|
1077
1105
|
how,
|
|
1078
1106
|
capturedAt: fresh.capturedAt,
|
|
1079
|
-
...fresh.skill ? { skill: fresh.skill } : {}
|
|
1107
|
+
...fresh.skill ? { skill: fresh.skill } : {},
|
|
1108
|
+
...inherited.applied > 0 ? { applied: inherited.applied, wins: inherited.wins } : {}
|
|
1080
1109
|
};
|
|
1081
|
-
const merged = existing.filter(
|
|
1110
|
+
const merged = existing.filter(
|
|
1111
|
+
(entry) => entry.category !== fresh.category || tokenOverlap(entry.key, key) < 0.55
|
|
1112
|
+
);
|
|
1082
1113
|
merged.push(freshEntry);
|
|
1114
|
+
return sortStructuredEntries(merged);
|
|
1115
|
+
}
|
|
1116
|
+
function sortStructuredEntries(merged) {
|
|
1083
1117
|
const order = {
|
|
1084
1118
|
warning: 0,
|
|
1085
1119
|
convention: 1,
|
|
@@ -1129,10 +1163,15 @@ function renderLearnedInstructions(role, entries, capturedAt) {
|
|
|
1129
1163
|
sections.push(SECTION_TITLE[category]);
|
|
1130
1164
|
sections.push("");
|
|
1131
1165
|
for (const entry of list) {
|
|
1166
|
+
const trials = directiveTrials(entry);
|
|
1132
1167
|
const attributes = [
|
|
1133
1168
|
`category=${entry.category}`,
|
|
1134
1169
|
`capturedAt=${entry.capturedAt}`,
|
|
1135
|
-
...entry.skill ? [`skill=${entry.skill}`] : []
|
|
1170
|
+
...entry.skill ? [`skill=${entry.skill}`] : [],
|
|
1171
|
+
// Zero counters are omitted so an entry with no track record renders
|
|
1172
|
+
// exactly as it did before this field existed — the parse→render
|
|
1173
|
+
// fixed point holds for every pre-existing buffer on disk.
|
|
1174
|
+
...trials.applied > 0 ? [`applied=${trials.applied}`, `wins=${trials.wins}`] : []
|
|
1136
1175
|
].join("; ");
|
|
1137
1176
|
sections.push(`<!-- learned-stamp: ${attributes} -->`);
|
|
1138
1177
|
sections.push(`- **${entry.what}**`);
|
|
@@ -1156,26 +1195,155 @@ function enforceLearnedBudget(entries, capturedAt, maxBytes, role = "agent") {
|
|
|
1156
1195
|
const kept = [...entries];
|
|
1157
1196
|
const dropped = [];
|
|
1158
1197
|
const size = (list) => Buffer.byteLength(renderLearnedInstructions(role, list, capturedAt), "utf8");
|
|
1198
|
+
const UTILITY_EPSILON = 1e-9;
|
|
1159
1199
|
while (kept.length > 1 && size(kept) > maxBytes) {
|
|
1160
1200
|
let victimIndex = 0;
|
|
1161
1201
|
for (let i = 1; i < kept.length; i++) {
|
|
1162
1202
|
const a = kept[i];
|
|
1163
1203
|
const b = kept[victimIndex];
|
|
1164
1204
|
const byPriority = DROP_PRIORITY[a.category] - DROP_PRIORITY[b.category];
|
|
1165
|
-
if (byPriority
|
|
1205
|
+
if (byPriority !== 0) {
|
|
1206
|
+
if (byPriority < 0) victimIndex = i;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
const byUtility = directiveUtility(a) - directiveUtility(b);
|
|
1210
|
+
if (Math.abs(byUtility) > UTILITY_EPSILON) {
|
|
1211
|
+
if (byUtility < 0) victimIndex = i;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (a.capturedAt < b.capturedAt) victimIndex = i;
|
|
1166
1215
|
}
|
|
1167
1216
|
dropped.push(...kept.splice(victimIndex, 1));
|
|
1168
1217
|
}
|
|
1169
1218
|
return { kept, dropped };
|
|
1170
1219
|
}
|
|
1171
1220
|
|
|
1221
|
+
// src/coordination/agents/project-agent-quarantine.ts
|
|
1222
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1223
|
+
import * as path5 from "node:path";
|
|
1224
|
+
var QUARANTINE_MAX_BYTES = 64 * 1024;
|
|
1225
|
+
var RETIRED_MATCH_THRESHOLD = 0.55;
|
|
1226
|
+
function quarantinePath(role, projectRoot) {
|
|
1227
|
+
return path5.join(roleDir(role, projectRoot), "quarantine.md");
|
|
1228
|
+
}
|
|
1229
|
+
function readTextOrEmpty(filePath) {
|
|
1230
|
+
try {
|
|
1231
|
+
return readFileSync3(filePath, "utf8");
|
|
1232
|
+
} catch {
|
|
1233
|
+
return "";
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function appendQuarantine(role, retired, at, projectRoot) {
|
|
1237
|
+
if (retired.length === 0) return;
|
|
1238
|
+
const filePath = quarantinePath(role, projectRoot);
|
|
1239
|
+
const existing = readTextOrEmpty(filePath);
|
|
1240
|
+
const header = existing ? "" : `# Retired directives for \`${role}\`
|
|
1241
|
+
|
|
1242
|
+
> Directives that were exercised repeatedly and kept correlating with
|
|
1243
|
+
> failure. They are no longer injected anywhere. Kept for audit \u2014 a directive
|
|
1244
|
+
> can be right about a project that has since changed.
|
|
1245
|
+
|
|
1246
|
+
`;
|
|
1247
|
+
const block = retired.map((entry) => {
|
|
1248
|
+
const { applied, wins, losses } = directiveTrials(entry);
|
|
1249
|
+
const attributes = [
|
|
1250
|
+
`at=${at}`,
|
|
1251
|
+
`category=${entry.category}`,
|
|
1252
|
+
...entry.skill ? [`skill=${entry.skill}`] : [],
|
|
1253
|
+
`applied=${applied}`,
|
|
1254
|
+
`wins=${wins}`
|
|
1255
|
+
].join("; ");
|
|
1256
|
+
return [
|
|
1257
|
+
`<!-- quarantined: ${attributes} -->`,
|
|
1258
|
+
`- **${entry.what}**`,
|
|
1259
|
+
` - *Record:* ${wins} succeeded / ${losses} failed of ${applied} applied`,
|
|
1260
|
+
""
|
|
1261
|
+
].join("\n");
|
|
1262
|
+
}).join("\n");
|
|
1263
|
+
let next = `${existing}${header}${block}`;
|
|
1264
|
+
if (Buffer.byteLength(next, "utf8") > QUARANTINE_MAX_BYTES) {
|
|
1265
|
+
const marker = "<!-- quarantined:";
|
|
1266
|
+
const overflow = Buffer.byteLength(next, "utf8") - QUARANTINE_MAX_BYTES;
|
|
1267
|
+
const cut = next.indexOf(marker, overflow);
|
|
1268
|
+
next = cut === -1 ? block : next.slice(cut);
|
|
1269
|
+
}
|
|
1270
|
+
writeTextAtomically(filePath, next);
|
|
1271
|
+
}
|
|
1272
|
+
function readQuarantinedDirectives(role, projectRoot) {
|
|
1273
|
+
const raw = readTextOrEmpty(quarantinePath(role, projectRoot));
|
|
1274
|
+
if (!raw) return [];
|
|
1275
|
+
const out = [];
|
|
1276
|
+
const stamps = [...raw.matchAll(/<!--\s*quarantined:\s*([^>]*?)\s*-->/g)];
|
|
1277
|
+
for (const [index, stamp] of stamps.entries()) {
|
|
1278
|
+
const attributes = {};
|
|
1279
|
+
for (const part of (stamp[1] ?? "").split(";")) {
|
|
1280
|
+
const eq = part.indexOf("=");
|
|
1281
|
+
if (eq === -1) continue;
|
|
1282
|
+
attributes[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
1283
|
+
}
|
|
1284
|
+
const start = (stamp.index ?? 0) + stamp[0].length;
|
|
1285
|
+
const end = stamps[index + 1]?.index ?? raw.length;
|
|
1286
|
+
const what = /^-\s+\*\*([\s\S]+?)\*\*\s*$/m.exec(raw.slice(start, end))?.[1]?.replace(/\s+/g, " ").trim();
|
|
1287
|
+
if (!what) continue;
|
|
1288
|
+
const skill = attributes["skill"];
|
|
1289
|
+
out.push({ what, ...skill ? { skill } : {} });
|
|
1290
|
+
}
|
|
1291
|
+
return out;
|
|
1292
|
+
}
|
|
1293
|
+
function bareClaim(line) {
|
|
1294
|
+
return line.replace(/^\s*[-*+]\s+/, "").replace(/^\s*\d+[.)]\s+/, "").replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
1295
|
+
}
|
|
1296
|
+
function indentOf(line) {
|
|
1297
|
+
return line.length - line.trimStart().length;
|
|
1298
|
+
}
|
|
1299
|
+
function scrubRetiredLines(text, retired) {
|
|
1300
|
+
if (!text.trim() || retired.length === 0) return text;
|
|
1301
|
+
const keys = retired.map((what) => normalizeForComparison(what));
|
|
1302
|
+
const lines = text.split("\n");
|
|
1303
|
+
const kept = [];
|
|
1304
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1305
|
+
const line = lines[i];
|
|
1306
|
+
const claim = bareClaim(line);
|
|
1307
|
+
if (claim.length >= MIN_INSTRUCTIVE_LENGTH && keys.some(
|
|
1308
|
+
(key) => tokenOverlap(key, normalizeForComparison(claim)) >= RETIRED_MATCH_THRESHOLD
|
|
1309
|
+
)) {
|
|
1310
|
+
const base = indentOf(line);
|
|
1311
|
+
while (i + 1 < lines.length) {
|
|
1312
|
+
const next = lines[i + 1];
|
|
1313
|
+
if (!next.trim() || indentOf(next) <= base) break;
|
|
1314
|
+
i++;
|
|
1315
|
+
}
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
kept.push(line);
|
|
1319
|
+
}
|
|
1320
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
1321
|
+
}
|
|
1322
|
+
function hasDirectiveContent(text) {
|
|
1323
|
+
return text.split("\n").some((line) => {
|
|
1324
|
+
const trimmed = line.trimStart();
|
|
1325
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(">")) return false;
|
|
1326
|
+
if (/^-{3,}\s*$/.test(trimmed)) return false;
|
|
1327
|
+
if (/^\*[^*].*\*$/.test(trimmed)) return false;
|
|
1328
|
+
return bareClaim(line).length >= MIN_INSTRUCTIVE_LENGTH;
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
function retiredDirectivesToWarnAbout(role, currentDirectives, projectRoot, skill) {
|
|
1332
|
+
const currentKeys = currentDirectives.map((text) => normalizeForComparison(text));
|
|
1333
|
+
return readQuarantinedDirectives(role, projectRoot).filter((entry) => skill ? entry.skill === skill : true).map((entry) => entry.what).filter(
|
|
1334
|
+
(what) => !currentKeys.some(
|
|
1335
|
+
(key) => tokenOverlap(key, normalizeForComparison(what)) >= RETIRED_MATCH_THRESHOLD
|
|
1336
|
+
)
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1172
1340
|
// src/coordination/agents/project-agent-consolidation.ts
|
|
1173
1341
|
function learnedPath(role, projectRoot) {
|
|
1174
|
-
return
|
|
1342
|
+
return path6.join(roleDir(role, projectRoot), "learned.md");
|
|
1175
1343
|
}
|
|
1176
1344
|
function loadProjectAgentLearnedText(role, projectRoot) {
|
|
1177
1345
|
try {
|
|
1178
|
-
return
|
|
1346
|
+
return readFileSync4(learnedPath(role, projectRoot), "utf8").trim();
|
|
1179
1347
|
} catch {
|
|
1180
1348
|
return "";
|
|
1181
1349
|
}
|
|
@@ -1185,28 +1353,27 @@ function readRawLearnedEntries(role, projectRoot) {
|
|
|
1185
1353
|
return parseStructuredLearnedEntriesFromContent(raw, splitLearnedEntries(raw));
|
|
1186
1354
|
}
|
|
1187
1355
|
function consolidationPath(role, projectRoot) {
|
|
1188
|
-
return
|
|
1356
|
+
return path6.join(roleDir(role, projectRoot), "consolidated.md");
|
|
1357
|
+
}
|
|
1358
|
+
function consolidatedDocumentPath(role, projectRoot) {
|
|
1359
|
+
return consolidationPath(assertProjectAgentRole(role), projectRoot);
|
|
1189
1360
|
}
|
|
1190
1361
|
function consolidationMetaPath(role, projectRoot) {
|
|
1191
|
-
return
|
|
1362
|
+
return path6.join(roleDir(role, projectRoot), "consolidation.json");
|
|
1192
1363
|
}
|
|
1193
1364
|
function archivePath(role, at, projectRoot) {
|
|
1194
|
-
return
|
|
1195
|
-
roleDir(role, projectRoot),
|
|
1196
|
-
"archive",
|
|
1197
|
-
`learned-${at.replace(/[:.]/g, "-")}.md`
|
|
1198
|
-
);
|
|
1365
|
+
return path6.join(roleDir(role, projectRoot), "archive", `learned-${at.replace(/[:.]/g, "-")}.md`);
|
|
1199
1366
|
}
|
|
1200
1367
|
function loadProjectAgentConsolidated(role, projectRoot) {
|
|
1201
1368
|
try {
|
|
1202
|
-
return
|
|
1369
|
+
return readFileSync4(consolidationPath(role, projectRoot), "utf8").trim();
|
|
1203
1370
|
} catch {
|
|
1204
1371
|
return "";
|
|
1205
1372
|
}
|
|
1206
1373
|
}
|
|
1207
1374
|
function loadConsolidationMetadata(role, projectRoot) {
|
|
1208
1375
|
try {
|
|
1209
|
-
const raw =
|
|
1376
|
+
const raw = readFileSync4(consolidationMetaPath(role, projectRoot), "utf8");
|
|
1210
1377
|
const parsed = JSON.parse(raw);
|
|
1211
1378
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.consolidatedAt === "string" && typeof parsed.sourceEntryCount === "number") {
|
|
1212
1379
|
return parsed;
|
|
@@ -1277,10 +1444,13 @@ function clearProjectAgentConsolidated(role, projectRoot) {
|
|
|
1277
1444
|
function buildConsolidationInstruction(role, projectRoot) {
|
|
1278
1445
|
const normalizedRole = assertProjectAgentRole(role);
|
|
1279
1446
|
const entries = readRawLearnedEntries(normalizedRole, projectRoot);
|
|
1280
|
-
const rawEntries = entries.map(
|
|
1281
|
-
|
|
1282
|
-
Anchors: ${entry.how.split("\n").join(", ")}`
|
|
1283
|
-
|
|
1447
|
+
const rawEntries = entries.map((entry) => {
|
|
1448
|
+
const lines = [entry.what];
|
|
1449
|
+
if (entry.how) lines.push(` Anchors: ${entry.how.split("\n").join(", ")}`);
|
|
1450
|
+
const { applied, wins } = directiveTrials(entry);
|
|
1451
|
+
if (applied > 0) lines.push(` Track record: applied ${applied}\xD7, ${wins} succeeded`);
|
|
1452
|
+
return lines.join("\n");
|
|
1453
|
+
});
|
|
1284
1454
|
const existingConsolidation = loadProjectAgentConsolidated(normalizedRole, projectRoot);
|
|
1285
1455
|
const rawBody = rawEntries.map((entry, i) => `### Entry ${i + 1}
|
|
1286
1456
|
|
|
@@ -1300,6 +1470,7 @@ ${entry}`).join("\n\n");
|
|
|
1300
1470
|
"6. **Structured format.** Organize the content under clear markdown headings (##) by topic. Use bullet points for individual facts. Group by category (Conventions, Patterns, Warnings, Project Facts) when it helps scannability.",
|
|
1301
1471
|
"7. **Preserve actionable anchors.** Keep exact file paths, command names, package names, and configuration values that make a directive concrete and runnable.",
|
|
1302
1472
|
"8. **No filler.** Do not include meta-commentary about the consolidation process. The document should read as if it were always a single authoritative reference.",
|
|
1473
|
+
"9. **Trust the track record over your own judgement of plausibility.** An entry may carry `Track record: applied N\xD7, M succeeded` \u2014 completed tasks that exercised it, and how many of those succeeded. Keep the ones that keep working, even when they read as obvious. An entry applied several times with few successes has been tested and has failed the test: drop it, or narrow it to the condition under which it actually holds. Entries with no track record are unproven rather than bad \u2014 judge them on merit, but do not let one displace a proven entry.",
|
|
1303
1474
|
"",
|
|
1304
1475
|
rawEntries.length > 0 ? `## Raw learned entries (${rawEntries.length} total)
|
|
1305
1476
|
|
|
@@ -1313,6 +1484,21 @@ ${rawBody}` : "## Raw learned entries\n\n(No raw entries yet \u2014 return an em
|
|
|
1313
1484
|
${existingConsolidation}`
|
|
1314
1485
|
);
|
|
1315
1486
|
}
|
|
1487
|
+
const retired = retiredDirectivesToWarnAbout(
|
|
1488
|
+
normalizedRole,
|
|
1489
|
+
entries.map((entry) => entry.what),
|
|
1490
|
+
projectRoot
|
|
1491
|
+
);
|
|
1492
|
+
if (retired.length > 0) {
|
|
1493
|
+
sections.push(
|
|
1494
|
+
"",
|
|
1495
|
+
`## Retired \u2014 do not reinstate (${retired.length})
|
|
1496
|
+
|
|
1497
|
+
These were exercised repeatedly and kept correlating with failed tasks. If the existing document above still states any of them, drop it.
|
|
1498
|
+
|
|
1499
|
+
${retired.map((what) => `- ${what}`).join("\n")}`
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1316
1502
|
sections.push(
|
|
1317
1503
|
"",
|
|
1318
1504
|
"## Output",
|
|
@@ -1392,7 +1578,7 @@ var BUILT_IN_KNOWLEDGE_MANIFESTS = {
|
|
|
1392
1578
|
};
|
|
1393
1579
|
|
|
1394
1580
|
// src/coordination/agents/project-agent-learning-policy.ts
|
|
1395
|
-
import { readFileSync as
|
|
1581
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1396
1582
|
var DEFAULT_LEARNING_POLICY = {
|
|
1397
1583
|
enabled: true,
|
|
1398
1584
|
lifetimeCaptureCount: 0
|
|
@@ -1400,23 +1586,26 @@ var DEFAULT_LEARNING_POLICY = {
|
|
|
1400
1586
|
function loadProjectAgentLearningPolicy(role, projectRoot) {
|
|
1401
1587
|
try {
|
|
1402
1588
|
const parsed = JSON.parse(
|
|
1403
|
-
|
|
1589
|
+
readFileSync5(learningPolicyPath(role, projectRoot), "utf8")
|
|
1404
1590
|
);
|
|
1405
1591
|
return {
|
|
1406
1592
|
enabled: parsed.enabled !== false,
|
|
1407
1593
|
lifetimeCaptureCount: typeof parsed.lifetimeCaptureCount === "number" && Number.isInteger(parsed.lifetimeCaptureCount) && parsed.lifetimeCaptureCount >= 0 ? parsed.lifetimeCaptureCount : 0,
|
|
1408
1594
|
...typeof parsed.lastCaptureAt === "string" ? { lastCaptureAt: parsed.lastCaptureAt } : {},
|
|
1409
|
-
...parsed.lastCaptureSource === "automatic" || parsed.lastCaptureSource === "manual" || parsed.lastCaptureSource === "taught" ? { lastCaptureSource: parsed.lastCaptureSource } : {}
|
|
1595
|
+
...parsed.lastCaptureSource === "automatic" || parsed.lastCaptureSource === "manual" || parsed.lastCaptureSource === "taught" ? { lastCaptureSource: parsed.lastCaptureSource } : {},
|
|
1596
|
+
...typeof parsed.lastOptimizeAt === "string" ? { lastOptimizeAt: parsed.lastOptimizeAt } : {}
|
|
1410
1597
|
};
|
|
1411
1598
|
} catch {
|
|
1412
1599
|
return { ...DEFAULT_LEARNING_POLICY };
|
|
1413
1600
|
}
|
|
1414
1601
|
}
|
|
1415
1602
|
function updateProjectAgentLearningPolicy(role, patch, projectRoot) {
|
|
1416
|
-
const
|
|
1603
|
+
const defined = Object.fromEntries(
|
|
1604
|
+
Object.entries(patch).filter(([, value]) => value !== void 0)
|
|
1605
|
+
);
|
|
1417
1606
|
const updated = {
|
|
1418
|
-
...
|
|
1419
|
-
...
|
|
1607
|
+
...loadProjectAgentLearningPolicy(role, projectRoot),
|
|
1608
|
+
...defined
|
|
1420
1609
|
};
|
|
1421
1610
|
writeTextAtomically(
|
|
1422
1611
|
learningPolicyPath(role, projectRoot),
|
|
@@ -1425,14 +1614,20 @@ function updateProjectAgentLearningPolicy(role, patch, projectRoot) {
|
|
|
1425
1614
|
);
|
|
1426
1615
|
return updated;
|
|
1427
1616
|
}
|
|
1617
|
+
function recordProjectAgentOptimizePass(role, projectRoot, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1618
|
+
try {
|
|
1619
|
+
updateProjectAgentLearningPolicy(role, { lastOptimizeAt: at }, projectRoot);
|
|
1620
|
+
} catch {
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1428
1623
|
|
|
1429
1624
|
// src/coordination/agents/project-agent-skill-layer.ts
|
|
1430
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
1431
|
-
import * as
|
|
1625
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync3 } from "node:fs";
|
|
1626
|
+
import * as path8 from "node:path";
|
|
1432
1627
|
|
|
1433
1628
|
// src/coordination/agents/project-agent-profile.ts
|
|
1434
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as
|
|
1435
|
-
import * as
|
|
1629
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync6 } from "node:fs";
|
|
1630
|
+
import * as path7 from "node:path";
|
|
1436
1631
|
function slugifyProjectAgentRole(name) {
|
|
1437
1632
|
const slug = name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "").slice(0, 96);
|
|
1438
1633
|
return assertProjectAgentRole(slug);
|
|
@@ -1440,7 +1635,7 @@ function slugifyProjectAgentRole(name) {
|
|
|
1440
1635
|
function loadProjectAgentProfile(role, projectRoot) {
|
|
1441
1636
|
try {
|
|
1442
1637
|
const parsed = JSON.parse(
|
|
1443
|
-
|
|
1638
|
+
readFileSync6(projectAgentProfilePath(role, projectRoot), "utf8")
|
|
1444
1639
|
);
|
|
1445
1640
|
const normalizedRole = assertProjectAgentRole(parsed.role ?? role);
|
|
1446
1641
|
const baseRole = assertProjectAgentRole(parsed.baseRole ?? "generic");
|
|
@@ -1511,7 +1706,7 @@ function createProjectAgent(input, projectRoot) {
|
|
|
1511
1706
|
""
|
|
1512
1707
|
].join("\n");
|
|
1513
1708
|
mkdirSync3(dir, { recursive: true });
|
|
1514
|
-
writeTextAtomically(
|
|
1709
|
+
writeTextAtomically(path7.join(dir, "identity.md"), identity);
|
|
1515
1710
|
writeTextAtomically(
|
|
1516
1711
|
projectAgentProfilePath(role, projectRoot),
|
|
1517
1712
|
`${JSON.stringify(profile, null, 2)}
|
|
@@ -1826,17 +2021,17 @@ function assertProjectSkillName(name) {
|
|
|
1826
2021
|
return normalized;
|
|
1827
2022
|
}
|
|
1828
2023
|
function projectSkillsDir(role, projectRoot) {
|
|
1829
|
-
return
|
|
2024
|
+
return path8.join(roleDir(role, projectRoot), "skills");
|
|
1830
2025
|
}
|
|
1831
2026
|
function projectSkillAugmentationPath(role, skill, projectRoot) {
|
|
1832
|
-
return
|
|
2027
|
+
return path8.join(projectSkillsDir(role, projectRoot), `${assertProjectSkillName(skill)}.md`);
|
|
1833
2028
|
}
|
|
1834
2029
|
function projectSkillAffinityPath(role, projectRoot) {
|
|
1835
|
-
return
|
|
2030
|
+
return path8.join(projectSkillsDir(role, projectRoot), "affinity.json");
|
|
1836
2031
|
}
|
|
1837
2032
|
function loadProjectSkillAugmentation(role, skill, projectRoot) {
|
|
1838
2033
|
try {
|
|
1839
|
-
return
|
|
2034
|
+
return readFileSync7(projectSkillAugmentationPath(role, skill, projectRoot), "utf8").trim();
|
|
1840
2035
|
} catch {
|
|
1841
2036
|
return "";
|
|
1842
2037
|
}
|
|
@@ -1845,13 +2040,32 @@ function saveProjectSkillAugmentation(role, skill, content, projectRoot) {
|
|
|
1845
2040
|
const filePath = projectSkillAugmentationPath(role, skill, projectRoot);
|
|
1846
2041
|
const trimmed = content.trim();
|
|
1847
2042
|
if (!trimmed) return filePath;
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
_(truncated at ${SKILL_AUGMENTATION_MAX_BYTES} bytes)_` : trimmed;
|
|
1851
|
-
writeTextAtomically(filePath, `${bounded}
|
|
2043
|
+
writeTextAtomically(filePath, `${boundAugmentation(trimmed)}
|
|
1852
2044
|
`);
|
|
1853
2045
|
return filePath;
|
|
1854
2046
|
}
|
|
2047
|
+
function boundAugmentation(text) {
|
|
2048
|
+
if (Buffer.byteLength(text, "utf8") <= SKILL_AUGMENTATION_MAX_BYTES) return text;
|
|
2049
|
+
const notice = `
|
|
2050
|
+
|
|
2051
|
+
_(truncated at ${SKILL_AUGMENTATION_MAX_BYTES} bytes)_`;
|
|
2052
|
+
const lines = text.split("\n");
|
|
2053
|
+
const title = lines[0] ?? "";
|
|
2054
|
+
let budget = SKILL_AUGMENTATION_MAX_BYTES - Buffer.byteLength(`${title}
|
|
2055
|
+
${notice}`, "utf8") - Buffer.byteLength("\u2026\n", "utf8");
|
|
2056
|
+
const tail = [];
|
|
2057
|
+
for (let i = lines.length - 1; i > 0; i--) {
|
|
2058
|
+
const cost = Buffer.byteLength(`${lines[i]}
|
|
2059
|
+
`, "utf8");
|
|
2060
|
+
if (cost > budget) break;
|
|
2061
|
+
tail.unshift(lines[i]);
|
|
2062
|
+
budget -= cost;
|
|
2063
|
+
}
|
|
2064
|
+
if (tail.length === 0) return `${title.slice(0, SKILL_AUGMENTATION_MAX_BYTES)}${notice}`;
|
|
2065
|
+
return `${title}
|
|
2066
|
+
\u2026
|
|
2067
|
+
${tail.join("\n").trimEnd()}${notice}`;
|
|
2068
|
+
}
|
|
1855
2069
|
function clearProjectSkillAugmentation(role, skill, projectRoot) {
|
|
1856
2070
|
const target = skill ? projectSkillAugmentationPath(role, skill, projectRoot) : projectSkillsDir(role, projectRoot);
|
|
1857
2071
|
try {
|
|
@@ -1866,7 +2080,25 @@ function listProjectSkillAugmentations(role, projectRoot) {
|
|
|
1866
2080
|
return [];
|
|
1867
2081
|
}
|
|
1868
2082
|
}
|
|
1869
|
-
function renderSkillAugmentation(role, skill, directives, updatedAt) {
|
|
2083
|
+
function renderSkillAugmentation(role, skill, directives, updatedAt, existing) {
|
|
2084
|
+
const priorBody = stripAugmentationFooter(existing ?? "");
|
|
2085
|
+
const priorLines = priorBody.split("\n").map((line) => normalizeForComparison(line.replace(/^[-*]\s+/, ""))).filter(Boolean);
|
|
2086
|
+
const fresh = directives.filter(
|
|
2087
|
+
(directive) => !priorLines.some((line) => tokenOverlap(line, normalizeForComparison(directive)) >= 0.55)
|
|
2088
|
+
);
|
|
2089
|
+
if (priorBody) {
|
|
2090
|
+
if (fresh.length === 0) return `${(existing ?? "").trim()}
|
|
2091
|
+
`;
|
|
2092
|
+
return [
|
|
2093
|
+
priorBody,
|
|
2094
|
+
"",
|
|
2095
|
+
...fresh.map((directive) => `- ${directive}`),
|
|
2096
|
+
"",
|
|
2097
|
+
"---",
|
|
2098
|
+
`*Distilled ${updatedAt} \xB7 ${fresh.length} new directive${fresh.length === 1 ? "" : "s"}*`,
|
|
2099
|
+
""
|
|
2100
|
+
].join("\n");
|
|
2101
|
+
}
|
|
1870
2102
|
return [
|
|
1871
2103
|
`# Project practice: \`${skill}\` (role: \`${role}\`)`,
|
|
1872
2104
|
"",
|
|
@@ -1880,7 +2112,10 @@ function renderSkillAugmentation(role, skill, directives, updatedAt) {
|
|
|
1880
2112
|
""
|
|
1881
2113
|
].join("\n");
|
|
1882
2114
|
}
|
|
1883
|
-
function
|
|
2115
|
+
function stripAugmentationFooter(text) {
|
|
2116
|
+
return text.replace(/\n*---\s*\n\*(?:Distilled|Truncated)[^\n]*\*\s*$/i, "").replace(/\n*_\(truncated at \d+ bytes\)_\s*$/i, "").trimEnd();
|
|
2117
|
+
}
|
|
2118
|
+
function buildSkillDistillInstruction(role, skill, directives, existing, retired = []) {
|
|
1884
2119
|
const sections = [
|
|
1885
2120
|
`You are refining the \`${skill}\` skill for the "${role}" agent **in this project**.`,
|
|
1886
2121
|
"",
|
|
@@ -1894,6 +2129,7 @@ function buildSkillDistillInstruction(role, skill, directives, existing) {
|
|
|
1894
2129
|
"4. **Merge, do not append.** Combine overlapping directives into one sharper rule. The output must be shorter than the input.",
|
|
1895
2130
|
"5. **Structure.** Short `##` sections when it helps (e.g. Commands, Conventions, Pitfalls); bullets otherwise.",
|
|
1896
2131
|
"6. **Budget.** Stay under 400 words. Drop the weakest directives rather than compressing everything into noise.",
|
|
2132
|
+
"7. **Weigh the track record.** Some directives carry one, shown as `[applied N\xD7, M ok]` \u2014 the number of completed tasks that exercised the directive and how many of those succeeded. Lead with the ones that keep working. A directive applied several times with few successes has been tested and found wanting: drop it, or state the narrower condition under which it actually holds. A directive with no record is unproven, not bad \u2014 keep it if it is sound, but do not let it displace a proven one.",
|
|
1897
2133
|
"",
|
|
1898
2134
|
`## Directives captured for \`${skill}\` (${directives.length})`,
|
|
1899
2135
|
"",
|
|
@@ -1902,6 +2138,16 @@ function buildSkillDistillInstruction(role, skill, directives, existing) {
|
|
|
1902
2138
|
if (existing?.trim()) {
|
|
1903
2139
|
sections.push("", "## Existing addendum (improve upon it)", "", existing.trim());
|
|
1904
2140
|
}
|
|
2141
|
+
if (retired.length > 0) {
|
|
2142
|
+
sections.push(
|
|
2143
|
+
"",
|
|
2144
|
+
`## Retired \u2014 do not reinstate (${retired.length})`,
|
|
2145
|
+
"",
|
|
2146
|
+
"These were exercised repeatedly and kept correlating with failed tasks. If the existing addendum still states any of them, drop it.",
|
|
2147
|
+
"",
|
|
2148
|
+
...retired.map((what) => `- ${what}`)
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
1905
2151
|
sections.push(
|
|
1906
2152
|
"",
|
|
1907
2153
|
"## Output",
|
|
@@ -1927,7 +2173,7 @@ function loadSkillAffinity(role, projectRoot) {
|
|
|
1927
2173
|
const normalizedRole = assertProjectAgentRole(role);
|
|
1928
2174
|
try {
|
|
1929
2175
|
const parsed = JSON.parse(
|
|
1930
|
-
|
|
2176
|
+
readFileSync7(projectSkillAffinityPath(normalizedRole, projectRoot), "utf8")
|
|
1931
2177
|
);
|
|
1932
2178
|
const entries = {};
|
|
1933
2179
|
for (const [skill, entry] of Object.entries(parsed.entries ?? {})) {
|
|
@@ -1976,9 +2222,11 @@ function recordSkillLoad(role, skills, projectRoot) {
|
|
|
1976
2222
|
});
|
|
1977
2223
|
}
|
|
1978
2224
|
function recordSkillOutcome(role, skills, ok, projectRoot) {
|
|
2225
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1979
2226
|
mutateAffinity(role, skills, projectRoot, (entry) => {
|
|
1980
2227
|
if (ok) entry.succeeded += 1;
|
|
1981
2228
|
else entry.failed += 1;
|
|
2229
|
+
entry.lastUsedAt = now;
|
|
1982
2230
|
});
|
|
1983
2231
|
}
|
|
1984
2232
|
function recordSkillLearned(role, skill, projectRoot) {
|
|
@@ -1992,24 +2240,30 @@ function setSkillPinned(role, skill, pinned, projectRoot) {
|
|
|
1992
2240
|
else delete entry.pinned;
|
|
1993
2241
|
});
|
|
1994
2242
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2243
|
+
var SKILL_EVIDENCE_HALF_LIFE_DAYS = 30;
|
|
2244
|
+
function scoreSkillAffinity(entry, now = Date.now()) {
|
|
2245
|
+
const record = entry ?? EMPTY_ENTRY;
|
|
2246
|
+
if (record.pinned) return Number.POSITIVE_INFINITY;
|
|
2247
|
+
const outcomes = record.succeeded + record.failed;
|
|
2248
|
+
const successRate = (record.succeeded + 1) / (outcomes + 2);
|
|
2249
|
+
const ageDays = record.lastUsedAt ? (now - Date.parse(record.lastUsedAt)) / 864e5 : Number.NaN;
|
|
2250
|
+
const recency = Number.isFinite(ageDays) ? 0.5 ** (Math.max(0, ageDays) / SKILL_EVIDENCE_HALF_LIFE_DAYS) : 1;
|
|
2251
|
+
const evidence = (successRate - 0.5) * 4 * recency;
|
|
2252
|
+
const exploration = 1 / Math.sqrt(1 + record.loaded);
|
|
2253
|
+
return Math.log1p(record.learned) * 2 + evidence + exploration;
|
|
2001
2254
|
}
|
|
2002
2255
|
function rankRoleSkills(role, candidates, projectRoot, limit = DEFAULT_EAGER_SKILL_LIMIT) {
|
|
2003
2256
|
const unique = [...new Set(candidates.filter(isProjectSkillName))];
|
|
2004
2257
|
if (unique.length <= limit) return unique;
|
|
2005
2258
|
const affinity = loadSkillAffinity(role, projectRoot);
|
|
2006
2259
|
const augmented = new Set(listProjectSkillAugmentations(role, projectRoot));
|
|
2260
|
+
const now = Date.now();
|
|
2007
2261
|
return unique.map((skill, index) => ({
|
|
2008
2262
|
skill,
|
|
2009
2263
|
index,
|
|
2010
2264
|
// An existing addendum is itself evidence the project developed this
|
|
2011
2265
|
// skill, even when the affinity file was reset or hand-edited.
|
|
2012
|
-
score: scoreSkillAffinity(affinity.entries[skill]) + (augmented.has(skill) ? 1 : 0)
|
|
2266
|
+
score: scoreSkillAffinity(affinity.entries[skill], now) + (augmented.has(skill) ? 1 : 0)
|
|
2013
2267
|
})).sort((a, b) => b.score === a.score ? a.index - b.index : b.score - a.score).slice(0, limit).map((item) => item.skill);
|
|
2014
2268
|
}
|
|
2015
2269
|
function resolveRoleSkillCandidates(role, projectRoot) {
|
|
@@ -2079,6 +2333,13 @@ function isTextBlock(b) {
|
|
|
2079
2333
|
// src/coordination/agents/project-agent-optimizer.ts
|
|
2080
2334
|
var DEFAULT_MAX_TOKENS = 8e3;
|
|
2081
2335
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
2336
|
+
function describeDirective(entry) {
|
|
2337
|
+
const parts = [entry.what];
|
|
2338
|
+
if (entry.how) parts.push(`(anchors: ${entry.how.split("\n").join(", ")})`);
|
|
2339
|
+
const { applied, wins } = directiveTrials(entry);
|
|
2340
|
+
if (applied > 0) parts.push(`[applied ${applied}\xD7, ${wins} ok]`);
|
|
2341
|
+
return parts.join(" ");
|
|
2342
|
+
}
|
|
2082
2343
|
function unwrapWholeDocumentFence(text) {
|
|
2083
2344
|
const wholeDocFence = /^```(?:markdown|md)?[^\n]*\n([\s\S]*?)\n?```\s*$/i;
|
|
2084
2345
|
const inner = wholeDocFence.exec(text.trim())?.[1];
|
|
@@ -2125,13 +2386,14 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
2125
2386
|
for (const entry of entries) {
|
|
2126
2387
|
if (!entry.skill) continue;
|
|
2127
2388
|
const bucket = bySkill.get(entry.skill) ?? [];
|
|
2128
|
-
bucket.push(
|
|
2389
|
+
bucket.push(describeDirective(entry));
|
|
2129
2390
|
bySkill.set(entry.skill, bucket);
|
|
2130
2391
|
}
|
|
2131
2392
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2132
2393
|
const refreshed = [];
|
|
2133
2394
|
for (const [skill, directives] of bySkill) {
|
|
2134
|
-
|
|
2395
|
+
const existing = loadProjectSkillAugmentation(normalizedRole, skill, projectRoot);
|
|
2396
|
+
let body = renderSkillAugmentation(normalizedRole, skill, directives, at, existing);
|
|
2135
2397
|
if (options.llm) {
|
|
2136
2398
|
try {
|
|
2137
2399
|
const synthesized = await complete(
|
|
@@ -2141,7 +2403,8 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
2141
2403
|
normalizedRole,
|
|
2142
2404
|
skill,
|
|
2143
2405
|
directives,
|
|
2144
|
-
|
|
2406
|
+
existing,
|
|
2407
|
+
retiredDirectivesToWarnAbout(normalizedRole, directives, projectRoot, skill)
|
|
2145
2408
|
),
|
|
2146
2409
|
options
|
|
2147
2410
|
);
|
|
@@ -2154,6 +2417,7 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
2154
2417
|
}
|
|
2155
2418
|
const { instruction } = buildConsolidationInstruction(normalizedRole, projectRoot);
|
|
2156
2419
|
if (!options.llm) {
|
|
2420
|
+
recordProjectAgentOptimizePass(normalizedRole, projectRoot, at);
|
|
2157
2421
|
return { ...base, status: "no-llm", skills: refreshed, instruction };
|
|
2158
2422
|
}
|
|
2159
2423
|
let content;
|
|
@@ -2168,6 +2432,7 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
2168
2432
|
};
|
|
2169
2433
|
}
|
|
2170
2434
|
if (!content) {
|
|
2435
|
+
recordProjectAgentOptimizePass(normalizedRole, projectRoot);
|
|
2171
2436
|
return { ...base, status: "empty-synthesis", skills: refreshed, model: options.llm.model };
|
|
2172
2437
|
}
|
|
2173
2438
|
const prune = options.prune ?? true;
|
|
@@ -2186,6 +2451,7 @@ async function optimizeProjectAgentLearning(role, projectRoot, options = {}) {
|
|
|
2186
2451
|
error: error instanceof Error ? error.message : "failed to persist consolidation"
|
|
2187
2452
|
};
|
|
2188
2453
|
}
|
|
2454
|
+
recordProjectAgentOptimizePass(normalizedRole, projectRoot);
|
|
2189
2455
|
return {
|
|
2190
2456
|
...base,
|
|
2191
2457
|
status: "optimized",
|
|
@@ -2223,17 +2489,16 @@ function resolveAutoOptimizePolicy(overrides) {
|
|
|
2223
2489
|
function evaluateAutoOptimize(role, projectRoot, policy, now = Date.now()) {
|
|
2224
2490
|
if (!policy.enabled) return { eligible: false, reason: "disabled" };
|
|
2225
2491
|
const normalizedRole = assertProjectAgentRole(role);
|
|
2226
|
-
|
|
2492
|
+
const learningPolicy = loadProjectAgentLearningPolicy(normalizedRole, projectRoot);
|
|
2493
|
+
if (!learningPolicy.enabled) {
|
|
2227
2494
|
return { eligible: false, reason: "learning-paused" };
|
|
2228
2495
|
}
|
|
2229
2496
|
const entries = readRawLearnedEntries(normalizedRole, projectRoot);
|
|
2230
2497
|
if (entries.length < policy.minEntries) return { eligible: false, reason: "too-few-entries" };
|
|
2231
2498
|
const meta = loadConsolidationMetadata(normalizedRole, projectRoot);
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
return { eligible: false, reason: "cooling-down" };
|
|
2236
|
-
}
|
|
2499
|
+
const lastPassAt = [meta?.consolidatedAt, learningPolicy.lastOptimizeAt].map((stamp) => stamp ? Date.parse(stamp) : Number.NaN).filter((value) => Number.isFinite(value));
|
|
2500
|
+
if (lastPassAt.length > 0 && now - Math.max(...lastPassAt) < policy.minIntervalMs) {
|
|
2501
|
+
return { eligible: false, reason: "cooling-down" };
|
|
2237
2502
|
}
|
|
2238
2503
|
const bytes = entries.reduce(
|
|
2239
2504
|
(sum, entry) => sum + Buffer.byteLength(`${entry.what}${entry.why}${entry.how}`, "utf8"),
|
|
@@ -2374,26 +2639,130 @@ var LearningOptimizationScheduler = class {
|
|
|
2374
2639
|
}
|
|
2375
2640
|
};
|
|
2376
2641
|
|
|
2642
|
+
// src/coordination/agents/project-agent-directive-outcome.ts
|
|
2643
|
+
import * as path9 from "node:path";
|
|
2644
|
+
var DIRECTIVE_QUARANTINE_MIN_APPLIED = 8;
|
|
2645
|
+
var DIRECTIVE_QUARANTINE_MAX_UTILITY = 0.3;
|
|
2646
|
+
var MIN_ANCHOR_LENGTH = 6;
|
|
2647
|
+
var DISTINCTIVE_TOKEN_LENGTH = 6;
|
|
2648
|
+
var MIN_DISTINCTIVE_TOKENS = 5;
|
|
2649
|
+
var DISTINCTIVE_COVERAGE = 0.6;
|
|
2650
|
+
function learnedPath2(role, projectRoot) {
|
|
2651
|
+
return path9.join(roleDir(role, projectRoot), "learned.md");
|
|
2652
|
+
}
|
|
2653
|
+
function directiveAnchors(entry) {
|
|
2654
|
+
return entry.how.split("\n").map((line) => line.replace(/`/g, "").trim().toLowerCase()).filter((anchor) => anchor.length >= MIN_ANCHOR_LENGTH);
|
|
2655
|
+
}
|
|
2656
|
+
function distinctiveTokens(text) {
|
|
2657
|
+
return [
|
|
2658
|
+
...new Set(
|
|
2659
|
+
text.toLowerCase().replace(/[^\w\s./@-]/g, " ").split(/\s+/).map((token) => token.replace(/^[./@-]+/, "").replace(/[./@-]+$/, "")).filter((token) => token.length >= DISTINCTIVE_TOKEN_LENGTH)
|
|
2660
|
+
)
|
|
2661
|
+
];
|
|
2662
|
+
}
|
|
2663
|
+
function directiveWasApplied(entry, report) {
|
|
2664
|
+
const haystack = report.toLowerCase();
|
|
2665
|
+
if (!haystack) return false;
|
|
2666
|
+
const anchors = directiveAnchors(entry);
|
|
2667
|
+
if (anchors.length > 0) {
|
|
2668
|
+
return anchors.some((anchor) => haystack.includes(anchor));
|
|
2669
|
+
}
|
|
2670
|
+
const tokens = distinctiveTokens(entry.what);
|
|
2671
|
+
if (tokens.length < MIN_DISTINCTIVE_TOKENS) return false;
|
|
2672
|
+
const hits = tokens.filter((token) => haystack.includes(token)).length;
|
|
2673
|
+
return hits >= MIN_DISTINCTIVE_TOKENS && hits / tokens.length >= DISTINCTIVE_COVERAGE;
|
|
2674
|
+
}
|
|
2675
|
+
function recordDirectiveOutcomes(role, report, succeeded, projectRoot) {
|
|
2676
|
+
const normalizedRole = assertProjectAgentRole(role);
|
|
2677
|
+
const empty = { role: normalizedRole, attributed: 0, quarantined: [] };
|
|
2678
|
+
if (!report.trim()) return empty;
|
|
2679
|
+
const entries = readRawLearnedEntries(normalizedRole, projectRoot);
|
|
2680
|
+
if (entries.length === 0) return empty;
|
|
2681
|
+
const updated = [];
|
|
2682
|
+
const retired = [];
|
|
2683
|
+
let attributed = 0;
|
|
2684
|
+
for (const entry of entries) {
|
|
2685
|
+
if (!directiveWasApplied(entry, report)) {
|
|
2686
|
+
updated.push(entry);
|
|
2687
|
+
continue;
|
|
2688
|
+
}
|
|
2689
|
+
attributed++;
|
|
2690
|
+
const trials = directiveTrials(entry);
|
|
2691
|
+
const next = {
|
|
2692
|
+
...entry,
|
|
2693
|
+
applied: trials.applied + 1,
|
|
2694
|
+
wins: trials.wins + (succeeded ? 1 : 0)
|
|
2695
|
+
};
|
|
2696
|
+
if (directiveTrials(next).applied >= DIRECTIVE_QUARANTINE_MIN_APPLIED && directiveUtility(next) < DIRECTIVE_QUARANTINE_MAX_UTILITY) {
|
|
2697
|
+
retired.push(next);
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
updated.push(next);
|
|
2701
|
+
}
|
|
2702
|
+
if (attributed === 0) return empty;
|
|
2703
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2704
|
+
const newest = updated.map((entry) => entry.capturedAt).filter(Boolean).sort().pop() ?? at;
|
|
2705
|
+
writeTextAtomically(
|
|
2706
|
+
learnedPath2(normalizedRole, projectRoot),
|
|
2707
|
+
renderLearnedInstructions(normalizedRole, updated, newest)
|
|
2708
|
+
);
|
|
2709
|
+
if (retired.length > 0) {
|
|
2710
|
+
appendQuarantine(normalizedRole, retired, at, projectRoot);
|
|
2711
|
+
scrubRetiredFromInjectedDocuments(normalizedRole, retired, projectRoot);
|
|
2712
|
+
}
|
|
2713
|
+
return {
|
|
2714
|
+
role: normalizedRole,
|
|
2715
|
+
attributed,
|
|
2716
|
+
quarantined: retired.map((entry) => entry.what)
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
function scrubRetiredFromInjectedDocuments(role, retired, projectRoot) {
|
|
2720
|
+
const texts = retired.map((entry) => entry.what);
|
|
2721
|
+
for (const skill of new Set(retired.map((entry) => entry.skill).filter(Boolean))) {
|
|
2722
|
+
try {
|
|
2723
|
+
const current = loadProjectSkillAugmentation(role, skill, projectRoot);
|
|
2724
|
+
if (!current) continue;
|
|
2725
|
+
const scrubbed = scrubRetiredLines(current, texts);
|
|
2726
|
+
if (scrubbed === current.trim()) continue;
|
|
2727
|
+
if (!hasDirectiveContent(scrubbed)) {
|
|
2728
|
+
clearProjectSkillAugmentation(role, skill, projectRoot);
|
|
2729
|
+
continue;
|
|
2730
|
+
}
|
|
2731
|
+
writeTextAtomically(projectSkillAugmentationPath(role, skill, projectRoot), `${scrubbed}
|
|
2732
|
+
`);
|
|
2733
|
+
} catch {
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
try {
|
|
2737
|
+
const consolidated = loadProjectAgentConsolidated(role, projectRoot);
|
|
2738
|
+
if (!consolidated) return;
|
|
2739
|
+
const scrubbed = scrubRetiredLines(consolidated, texts);
|
|
2740
|
+
if (scrubbed === consolidated.trim() || !hasDirectiveContent(scrubbed)) return;
|
|
2741
|
+
writeTextAtomically(consolidatedDocumentPath(role, projectRoot), scrubbed);
|
|
2742
|
+
} catch {
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2377
2746
|
// src/coordination/agents/project-agent-files.ts
|
|
2378
2747
|
import {
|
|
2379
2748
|
existsSync as existsSync3,
|
|
2380
2749
|
mkdirSync as mkdirSync4,
|
|
2381
2750
|
readdirSync as readdirSync3,
|
|
2382
|
-
readFileSync as
|
|
2751
|
+
readFileSync as readFileSync8,
|
|
2383
2752
|
rmSync as rmSync4
|
|
2384
2753
|
} from "node:fs";
|
|
2385
|
-
import * as
|
|
2754
|
+
import * as path10 from "node:path";
|
|
2386
2755
|
function updateProjectAgentLearned(role, content, projectRoot, mode = "append") {
|
|
2387
2756
|
const dir = roleDir(role, projectRoot);
|
|
2388
2757
|
mkdirSync4(dir, { recursive: true });
|
|
2389
|
-
const filePath =
|
|
2758
|
+
const filePath = path10.join(dir, "learned.md");
|
|
2390
2759
|
if (mode === "replace") {
|
|
2391
2760
|
writeTextAtomically(filePath, content);
|
|
2392
2761
|
return filePath;
|
|
2393
2762
|
}
|
|
2394
2763
|
const existing = (() => {
|
|
2395
2764
|
try {
|
|
2396
|
-
return
|
|
2765
|
+
return readFileSync8(filePath, "utf8");
|
|
2397
2766
|
} catch {
|
|
2398
2767
|
return "";
|
|
2399
2768
|
}
|
|
@@ -2421,7 +2790,7 @@ function updateProjectAgentLearned(role, content, projectRoot, mode = "append")
|
|
|
2421
2790
|
function updateProjectAgentIdentity(role, content, projectRoot) {
|
|
2422
2791
|
const dir = roleDir(role, projectRoot);
|
|
2423
2792
|
mkdirSync4(dir, { recursive: true });
|
|
2424
|
-
const filePath =
|
|
2793
|
+
const filePath = path10.join(dir, "identity.md");
|
|
2425
2794
|
writeTextAtomically(filePath, content);
|
|
2426
2795
|
return filePath;
|
|
2427
2796
|
}
|
|
@@ -2429,7 +2798,7 @@ function updateProjectAgentConfig(role, config, projectRoot) {
|
|
|
2429
2798
|
const validated = validateProjectAgentConfig(config);
|
|
2430
2799
|
const dir = roleDir(role, projectRoot);
|
|
2431
2800
|
mkdirSync4(dir, { recursive: true });
|
|
2432
|
-
const filePath =
|
|
2801
|
+
const filePath = path10.join(dir, "config.json");
|
|
2433
2802
|
writeTextAtomically(filePath, `${JSON.stringify(validated, null, 2)}
|
|
2434
2803
|
`);
|
|
2435
2804
|
return filePath;
|
|
@@ -2437,7 +2806,7 @@ function updateProjectAgentConfig(role, config, projectRoot) {
|
|
|
2437
2806
|
function updateProjectAgentKnowledge(role, manifest, projectRoot) {
|
|
2438
2807
|
const dir = roleDir(role, projectRoot);
|
|
2439
2808
|
mkdirSync4(dir, { recursive: true });
|
|
2440
|
-
const filePath =
|
|
2809
|
+
const filePath = path10.join(dir, "knowledge.json");
|
|
2441
2810
|
writeTextAtomically(filePath, `${JSON.stringify(manifest, null, 2)}
|
|
2442
2811
|
`);
|
|
2443
2812
|
return filePath;
|
|
@@ -2463,18 +2832,18 @@ function refreshProjectAgentIdentity(role, projectRoot) {
|
|
|
2463
2832
|
const dir = roleDir(role, projectRoot);
|
|
2464
2833
|
mkdirSync4(dir, { recursive: true });
|
|
2465
2834
|
for (const file of ["learned.md", "identity.md", "consolidated.md", "consolidation.json"]) {
|
|
2466
|
-
const fp =
|
|
2835
|
+
const fp = path10.join(dir, file);
|
|
2467
2836
|
try {
|
|
2468
2837
|
rmSync4(fp, { force: true });
|
|
2469
2838
|
} catch {
|
|
2470
2839
|
}
|
|
2471
2840
|
}
|
|
2472
2841
|
writeTextAtomically(
|
|
2473
|
-
|
|
2842
|
+
path10.join(dir, "learned.md"),
|
|
2474
2843
|
renderLearnedInstructions(role, [], (/* @__PURE__ */ new Date()).toISOString())
|
|
2475
2844
|
);
|
|
2476
2845
|
writeTextAtomically(
|
|
2477
|
-
|
|
2846
|
+
path10.join(dir, "identity.md"),
|
|
2478
2847
|
`# Project identity for ${role}
|
|
2479
2848
|
|
|
2480
2849
|
<!-- Describe how this agent should behave in the context of this project. -->
|
|
@@ -2487,7 +2856,7 @@ function listProjectAgentRoles(projectRoot) {
|
|
|
2487
2856
|
try {
|
|
2488
2857
|
return readdirSync3(dir).filter((name) => {
|
|
2489
2858
|
if (!isProjectAgentRoleName(name)) return false;
|
|
2490
|
-
const sub =
|
|
2859
|
+
const sub = path10.join(dir, name);
|
|
2491
2860
|
try {
|
|
2492
2861
|
return [
|
|
2493
2862
|
"learned.md",
|
|
@@ -2498,7 +2867,7 @@ function listProjectAgentRoles(projectRoot) {
|
|
|
2498
2867
|
"profile.json",
|
|
2499
2868
|
"consolidated.md",
|
|
2500
2869
|
"consolidation.json"
|
|
2501
|
-
].some((file) => existsSync3(
|
|
2870
|
+
].some((file) => existsSync3(path10.join(sub, file)));
|
|
2502
2871
|
} catch {
|
|
2503
2872
|
return false;
|
|
2504
2873
|
}
|
|
@@ -2565,25 +2934,25 @@ function createProjectAgentRoster(baseRoster, projectRoot) {
|
|
|
2565
2934
|
});
|
|
2566
2935
|
}
|
|
2567
2936
|
function loadProjectAgentIdentity(role, projectRoot) {
|
|
2568
|
-
const identityPath =
|
|
2937
|
+
const identityPath = path11.join(roleDir(role, projectRoot), "identity.md");
|
|
2569
2938
|
try {
|
|
2570
|
-
return
|
|
2939
|
+
return readFileSync9(identityPath, "utf8").trim();
|
|
2571
2940
|
} catch {
|
|
2572
2941
|
return "";
|
|
2573
2942
|
}
|
|
2574
2943
|
}
|
|
2575
2944
|
function loadProjectAgentLearned(role, projectRoot) {
|
|
2576
|
-
const
|
|
2945
|
+
const learnedPath3 = path11.join(roleDir(role, projectRoot), "learned.md");
|
|
2577
2946
|
try {
|
|
2578
|
-
return
|
|
2947
|
+
return readFileSync9(learnedPath3, "utf8").trim();
|
|
2579
2948
|
} catch {
|
|
2580
2949
|
return "";
|
|
2581
2950
|
}
|
|
2582
2951
|
}
|
|
2583
2952
|
function loadRoleKnowledgeManifest(role, projectRoot) {
|
|
2584
|
-
const projectPath =
|
|
2953
|
+
const projectPath = path11.join(roleDir(role, projectRoot), "knowledge.json");
|
|
2585
2954
|
try {
|
|
2586
|
-
const raw =
|
|
2955
|
+
const raw = readFileSync9(projectPath, "utf8");
|
|
2587
2956
|
return JSON.parse(raw);
|
|
2588
2957
|
} catch {
|
|
2589
2958
|
return BUILT_IN_KNOWLEDGE_MANIFESTS[role];
|
|
@@ -2724,7 +3093,7 @@ ${learnedContent}`);
|
|
|
2724
3093
|
}
|
|
2725
3094
|
}
|
|
2726
3095
|
const effectiveProjectRoot = projectRoot || process.cwd();
|
|
2727
|
-
const learnedFilePath =
|
|
3096
|
+
const learnedFilePath = path11.join(
|
|
2728
3097
|
effectiveProjectRoot,
|
|
2729
3098
|
".wrongstack",
|
|
2730
3099
|
"agents",
|
|
@@ -2751,6 +3120,8 @@ The file below stores **learning data for this project's "${role}" agent** \u201
|
|
|
2751
3120
|
- **Self-contained** \u2014 understandable without the surrounding session context.
|
|
2752
3121
|
- **Front-load concrete anchors** \u2014 commands in backticks, package names like \`@wrongstack/core\`, file paths like \`packages/core/src/.../foo.ts\`. The structured-list renderer extracts these as the "how" for the entry.
|
|
2753
3122
|
|
|
3123
|
+
**Your directives are scored against real outcomes.** After every task the runtime checks which stored directives were actually exercised \u2014 it matches their anchors against the report \u2014 and folds that task's success or failure into each one's record. A directive that keeps correlating with success outlives newer arrivals and survives rewording; one that has been exercised repeatedly and kept correlating with failure is retired and stops being injected. Two consequences for how you write them: anchors are what make a directive *measurable*, not just runnable, so an anchorless directive can never earn a record; and a directive you are unsure about costs nothing to write, because the loop will find out.
|
|
3124
|
+
|
|
2754
3125
|
**Tag the skill you are developing.** When a directive refines one of your skills, mark it: \`## LEARNED [skill: testing]\`. Tagged directives are distilled into that skill's project addendum, so the lesson arrives as part of the skill itself on every future run instead of as a loose fact. Untagged directives are routed automatically when the wording makes the target obvious, and stay role-level otherwise.
|
|
2755
3126
|
|
|
2756
3127
|
**Bad** (session log \u2014 rejected at capture time):
|
|
@@ -2816,7 +3187,7 @@ ${additions}
|
|
|
2816
3187
|
${contextEnd}`;
|
|
2817
3188
|
}
|
|
2818
3189
|
function resolveCaptureKey(role, projectRoot) {
|
|
2819
|
-
return `${
|
|
3190
|
+
return `${path11.resolve(projectRoot ?? process.cwd())}\0${assertProjectAgentRole(role)}`;
|
|
2820
3191
|
}
|
|
2821
3192
|
function canCaptureNewLearned(role, _existingSize, isManual, projectRoot) {
|
|
2822
3193
|
if (isManual) return void 0;
|
|
@@ -2834,13 +3205,13 @@ function canCaptureNewLearned(role, _existingSize, isManual, projectRoot) {
|
|
|
2834
3205
|
}
|
|
2835
3206
|
function getProjectAgentLearnStats(role, projectRoot) {
|
|
2836
3207
|
const dir = roleDir(role, projectRoot);
|
|
2837
|
-
const learnedPath_ =
|
|
2838
|
-
const identityPath_ =
|
|
2839
|
-
const configPath_ =
|
|
2840
|
-
const knowledgePath_ =
|
|
3208
|
+
const learnedPath_ = path11.join(dir, "learned.md");
|
|
3209
|
+
const identityPath_ = path11.join(dir, "identity.md");
|
|
3210
|
+
const configPath_ = path11.join(dir, "config.json");
|
|
3211
|
+
const knowledgePath_ = path11.join(dir, "knowledge.json");
|
|
2841
3212
|
let learnedText = "";
|
|
2842
3213
|
try {
|
|
2843
|
-
learnedText =
|
|
3214
|
+
learnedText = readFileSync9(learnedPath_, "utf8");
|
|
2844
3215
|
} catch {
|
|
2845
3216
|
}
|
|
2846
3217
|
const entries = parseStructuredLearnedEntries(role, projectRoot);
|
|
@@ -2854,12 +3225,18 @@ function getProjectAgentLearnStats(role, projectRoot) {
|
|
|
2854
3225
|
const lastTs = runtimeLastTs ?? (Number.isFinite(persistedLastTs) ? persistedLastTs : null);
|
|
2855
3226
|
const cooldownRemaining = lastTs ? Math.max(0, CAPTURE_COOLDOWN_MS - (Date.now() - lastTs)) : 0;
|
|
2856
3227
|
const freq = window.count;
|
|
3228
|
+
const trials = entries.map((entry) => directiveTrials(entry));
|
|
3229
|
+
const totalApplied = trials.reduce((sum, trial) => sum + trial.applied, 0);
|
|
3230
|
+
const totalWins = trials.reduce((sum, trial) => sum + trial.wins, 0);
|
|
2857
3231
|
return {
|
|
2858
3232
|
role,
|
|
2859
3233
|
exists,
|
|
2860
3234
|
entryCount: entries.length,
|
|
2861
3235
|
skills: skillAugmentations,
|
|
2862
3236
|
skilledEntryCount: entries.filter((entry) => entry.skill).length,
|
|
3237
|
+
appliedEntryCount: trials.filter((trial) => trial.applied > 0).length,
|
|
3238
|
+
deadEntryCount: trials.filter((trial) => trial.applied === 0).length,
|
|
3239
|
+
directiveHitRate: totalApplied > 0 ? totalWins / totalApplied : null,
|
|
2863
3240
|
totalBytes: Buffer.byteLength(learnedText, "utf8"),
|
|
2864
3241
|
lastCapture: lastTs ? new Date(lastTs).toISOString() : null,
|
|
2865
3242
|
lastCaptureTimestamp: lastTs,
|
|
@@ -3032,7 +3409,7 @@ function captureLearnedFromAgentOutputDetailed(output, role, projectRoot, isManu
|
|
|
3032
3409
|
normalizedRole
|
|
3033
3410
|
);
|
|
3034
3411
|
const newContent = renderLearnedInstructions(normalizedRole, budget.kept, nowIso);
|
|
3035
|
-
writeTextAtomically(
|
|
3412
|
+
writeTextAtomically(path11.join(roleDir(normalizedRole, projectRoot), "learned.md"), newContent);
|
|
3036
3413
|
recordCaptureAttempt(key, now.getTime());
|
|
3037
3414
|
for (const skill of new Set(routedSkills)) {
|
|
3038
3415
|
try {
|
|
@@ -3040,16 +3417,14 @@ function captureLearnedFromAgentOutputDetailed(output, role, projectRoot, isManu
|
|
|
3040
3417
|
} catch {
|
|
3041
3418
|
}
|
|
3042
3419
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
`${JSON.stringify(nextPolicy, null, 2)}
|
|
3052
|
-
`
|
|
3420
|
+
updateProjectAgentLearningPolicy(
|
|
3421
|
+
normalizedRole,
|
|
3422
|
+
{
|
|
3423
|
+
lifetimeCaptureCount: policy.lifetimeCaptureCount + captured,
|
|
3424
|
+
lastCaptureAt: now.toISOString(),
|
|
3425
|
+
lastCaptureSource: isManual ? "manual" : "automatic"
|
|
3426
|
+
},
|
|
3427
|
+
projectRoot
|
|
3053
3428
|
);
|
|
3054
3429
|
return {
|
|
3055
3430
|
role: normalizedRole,
|
|
@@ -3145,38 +3520,38 @@ function loadTechVersionPolicy() {
|
|
|
3145
3520
|
return techVersionPolicyBody;
|
|
3146
3521
|
}
|
|
3147
3522
|
function policyCandidateDirs() {
|
|
3148
|
-
const here =
|
|
3523
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
3149
3524
|
const profileInstructions = resolveWstackPaths({ projectRoot: process.cwd() }).globalInstructions;
|
|
3150
3525
|
return [
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3526
|
+
path12.resolve(here, "../../../../instructions/agents"),
|
|
3527
|
+
path12.resolve(here, "../../../instructions/agents"),
|
|
3528
|
+
path12.resolve(here, "../../instructions/agents"),
|
|
3529
|
+
path12.resolve(here, "../instructions/agents"),
|
|
3530
|
+
path12.resolve(here, "instructions/agents"),
|
|
3531
|
+
path12.join(profileInstructions, "agents"),
|
|
3157
3532
|
profileInstructions
|
|
3158
3533
|
].filter((dir, index, all) => all.indexOf(dir) === index);
|
|
3159
3534
|
}
|
|
3160
3535
|
function policyBody() {
|
|
3161
|
-
const here =
|
|
3536
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
3162
3537
|
const roots = [
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3538
|
+
path12.resolve(here, "_policy/tech-version.md"),
|
|
3539
|
+
path12.resolve(here, "../_policy/tech-version.md"),
|
|
3540
|
+
path12.resolve(here, "../../../instructions/agents/_policy/tech-version.md"),
|
|
3541
|
+
path12.resolve(here, "../../instructions/agents/_policy/tech-version.md"),
|
|
3542
|
+
path12.resolve(here, "../instructions/agents/_policy/tech-version.md"),
|
|
3543
|
+
path12.resolve(here, "../../../../instructions/agents/_policy/tech-version.md"),
|
|
3544
|
+
path12.resolve(here, "instructions/agents/_policy/tech-version.md")
|
|
3170
3545
|
];
|
|
3171
3546
|
for (const file of roots) {
|
|
3172
3547
|
try {
|
|
3173
|
-
return
|
|
3548
|
+
return readFileSync10(file, "utf8").trim();
|
|
3174
3549
|
} catch {
|
|
3175
3550
|
}
|
|
3176
3551
|
}
|
|
3177
3552
|
for (const dir of policyCandidateDirs()) {
|
|
3178
3553
|
try {
|
|
3179
|
-
return
|
|
3554
|
+
return readFileSync10(path12.join(dir, "_policy", "tech-version.md"), "utf8").trim();
|
|
3180
3555
|
} catch {
|
|
3181
3556
|
}
|
|
3182
3557
|
}
|
|
@@ -3195,7 +3570,7 @@ function agentPrompt(id) {
|
|
|
3195
3570
|
let resolved = "";
|
|
3196
3571
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
3197
3572
|
try {
|
|
3198
|
-
resolved =
|
|
3573
|
+
resolved = readFileSync10(path12.join(dir, fileName), "utf8").trim();
|
|
3199
3574
|
break;
|
|
3200
3575
|
} catch {
|
|
3201
3576
|
}
|
|
@@ -3221,16 +3596,16 @@ function agentPromptDirCandidates(envDir) {
|
|
|
3221
3596
|
const candKey = `${envDir}\0${profileInstructions}`;
|
|
3222
3597
|
const cached = candidateCache.get(candKey);
|
|
3223
3598
|
if (cached !== void 0) return cached;
|
|
3224
|
-
const here =
|
|
3599
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
3225
3600
|
const explicitDir = envDir || void 0;
|
|
3226
3601
|
const candidates = [
|
|
3227
|
-
...explicitDir ? [
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3602
|
+
...explicitDir ? [path12.resolve(explicitDir)] : [],
|
|
3603
|
+
path12.join(profileInstructions, "agents"),
|
|
3604
|
+
path12.resolve(here, "../../../../instructions/agents"),
|
|
3605
|
+
path12.resolve(here, "../../../instructions/agents"),
|
|
3606
|
+
path12.resolve(here, "../../instructions/agents"),
|
|
3607
|
+
path12.resolve(here, "../instructions/agents"),
|
|
3608
|
+
path12.resolve(here, "instructions/agents")
|
|
3234
3609
|
];
|
|
3235
3610
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
3236
3611
|
candidateCache.set(candKey, ordered);
|
|
@@ -3287,8 +3662,16 @@ var TOOLS = {
|
|
|
3287
3662
|
"test",
|
|
3288
3663
|
"mailbox"
|
|
3289
3664
|
],
|
|
3290
|
-
/**
|
|
3291
|
-
|
|
3665
|
+
/**
|
|
3666
|
+
* Version control.
|
|
3667
|
+
*
|
|
3668
|
+
* `mailbox` is in every preset on purpose: a subagent that hits a wall must
|
|
3669
|
+
* be able to say so. This was the one preset without it, which left the `git`
|
|
3670
|
+
* and `release` roles able to fail but not to ask — the two roles whose work
|
|
3671
|
+
* most often needs a decision from the leader (force-push, tag collision,
|
|
3672
|
+
* dirty tree) and least often has a safe default.
|
|
3673
|
+
*/
|
|
3674
|
+
vcs: ["read", "grep", "glob", "git", "diff", "mailbox"],
|
|
3292
3675
|
/** Dependency management + CVE audit. */
|
|
3293
3676
|
deps: ["read", "grep", "glob", "install", "outdated", "audit", "json", "mailbox"],
|
|
3294
3677
|
/** Documentation authoring. */
|
|
@@ -5783,6 +6166,10 @@ export {
|
|
|
5783
6166
|
DEFAULT_AUTO_OPTIMIZE_POLICY,
|
|
5784
6167
|
DEFAULT_EAGER_SKILL_LIMIT,
|
|
5785
6168
|
DELIVERY_AGENTS,
|
|
6169
|
+
DIRECTIVE_PROVEN_MIN_APPLIED,
|
|
6170
|
+
DIRECTIVE_PROVEN_MIN_UTILITY,
|
|
6171
|
+
DIRECTIVE_QUARANTINE_MAX_UTILITY,
|
|
6172
|
+
DIRECTIVE_QUARANTINE_MIN_APPLIED,
|
|
5786
6173
|
DISCOVERY_AGENTS,
|
|
5787
6174
|
DOMAIN_AGENTS,
|
|
5788
6175
|
HEAVY_BUDGET,
|
|
@@ -5801,6 +6188,7 @@ export {
|
|
|
5801
6188
|
RUNTIME_CAPABILITY_MANIFEST,
|
|
5802
6189
|
SHADOW_AGENT_SKILLS,
|
|
5803
6190
|
SKILL_AUGMENTATION_MAX_BYTES,
|
|
6191
|
+
SKILL_EVIDENCE_HALF_LIFE_DAYS,
|
|
5804
6192
|
SPECIALIST_TOOLS,
|
|
5805
6193
|
TOOLS,
|
|
5806
6194
|
VERIFY_AGENTS,
|
|
@@ -5828,10 +6216,14 @@ export {
|
|
|
5828
6216
|
classifyLearnedEntry,
|
|
5829
6217
|
clearProjectAgentConsolidated,
|
|
5830
6218
|
clearProjectSkillAugmentation,
|
|
6219
|
+
consolidatedDocumentPath,
|
|
5831
6220
|
createProjectAgent,
|
|
5832
6221
|
createProjectAgentRoster,
|
|
5833
6222
|
decomposeLearnedEntry,
|
|
5834
6223
|
detectLearnedConflicts,
|
|
6224
|
+
directiveTrials,
|
|
6225
|
+
directiveUtility,
|
|
6226
|
+
directiveWasApplied,
|
|
5835
6227
|
enforceLearnedBudget,
|
|
5836
6228
|
evaluateAutoOptimize,
|
|
5837
6229
|
existsSync4 as existsSync,
|
|
@@ -5842,6 +6234,7 @@ export {
|
|
|
5842
6234
|
isConsolidated,
|
|
5843
6235
|
isDeprecatedRuntimeToolAlias,
|
|
5844
6236
|
isKnownRuntimeCapability,
|
|
6237
|
+
isProvenDirective,
|
|
5845
6238
|
listProjectAgentLearnedEntries,
|
|
5846
6239
|
listProjectAgentRoles,
|
|
5847
6240
|
listProjectSkillAugmentations,
|
|
@@ -5864,8 +6257,12 @@ export {
|
|
|
5864
6257
|
parseLearnedEntryStamp,
|
|
5865
6258
|
parseStructuredLearnedEntries,
|
|
5866
6259
|
parseStructuredLearnedEntriesFromContent,
|
|
6260
|
+
quarantinePath,
|
|
5867
6261
|
rankRoleSkills,
|
|
6262
|
+
readQuarantinedDirectives,
|
|
5868
6263
|
readRawLearnedEntries,
|
|
6264
|
+
recordDirectiveOutcomes,
|
|
6265
|
+
recordProjectAgentOptimizePass,
|
|
5869
6266
|
recordSkillLoad,
|
|
5870
6267
|
recordSkillOutcome,
|
|
5871
6268
|
refreshProjectAgentIdentity,
|
|
@@ -5876,11 +6273,14 @@ export {
|
|
|
5876
6273
|
resetProjectAgentIdentity,
|
|
5877
6274
|
resolveAutoOptimizePolicy,
|
|
5878
6275
|
resolveRoleSkillCandidates,
|
|
6276
|
+
retiredDirectivesToWarnAbout,
|
|
5879
6277
|
routeDirectiveToSkill,
|
|
5880
6278
|
runtimeCapabilityForTool,
|
|
5881
6279
|
runtimeToolReferencesFromText,
|
|
5882
6280
|
saveProjectAgentConsolidated,
|
|
5883
6281
|
saveProjectSkillAugmentation,
|
|
6282
|
+
scoreSkillAffinity,
|
|
6283
|
+
scrubRetiredLines,
|
|
5884
6284
|
setSkillPinned,
|
|
5885
6285
|
slugifyProjectAgentRole,
|
|
5886
6286
|
splitLearnedEntries,
|