ccjk 14.0.0 → 14.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/agents.mjs +1 -1
- package/dist/chunks/ccr.mjs +13 -14
- package/dist/chunks/check-updates.mjs +1 -3
- package/dist/chunks/claude-code-incremental-manager.mjs +1 -1
- package/dist/chunks/code-type-resolver.mjs +878 -0
- package/dist/chunks/config.mjs +42 -2
- package/dist/chunks/config2.mjs +2 -2
- package/dist/chunks/doctor.mjs +1 -1
- package/dist/chunks/index10.mjs +55 -11
- package/dist/chunks/init.mjs +42 -14
- package/dist/chunks/installer.mjs +2 -2
- package/dist/chunks/mcp.mjs +1 -1
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/quick-setup.mjs +1 -3
- package/dist/chunks/research.mjs +733 -6
- package/dist/chunks/sessions.mjs +1 -1
- package/dist/chunks/skills-sync.mjs +5 -5126
- package/dist/chunks/slash-commands.mjs +1 -1
- package/dist/chunks/status.mjs +63 -16
- package/dist/chunks/uninstall.mjs +1 -3
- package/dist/cli.mjs +101 -20
- package/dist/i18n/locales/en/configuration.json +6 -2
- package/dist/i18n/locales/zh-CN/configuration.json +6 -2
- package/dist/index.d.mts +64 -17
- package/dist/index.d.ts +64 -17
- package/dist/index.mjs +11 -720
- package/dist/shared/ccjk.BO45TPXJ.mjs +807 -0
- package/dist/shared/{ccjk.BOO14f66.mjs → ccjk.CNhnT6uQ.mjs} +42 -6
- package/dist/shared/{ccjk.vO3d1ABk.mjs → ccjk.UhjQ1seV.mjs} +1 -1
- package/dist/shared/ccjk.y-a_1vK4.mjs +5127 -0
- package/package.json +1 -1
- package/dist/chunks/intent-engine.mjs +0 -142
- package/dist/chunks/smart-defaults.mjs +0 -425
- package/dist/shared/ccjk.DJuyfrlL.mjs +0 -348
- package/dist/shared/ccjk.yYQMbHH3.mjs +0 -115
package/dist/chunks/research.mjs
CHANGED
|
@@ -2,14 +2,19 @@ import a from './index5.mjs';
|
|
|
2
2
|
import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { n as nanoid } from '../shared/ccjk.BoApaI4j.mjs';
|
|
5
|
-
import { T as TaskPersistence } from '../shared/ccjk.
|
|
5
|
+
import { T as TaskPersistence } from '../shared/ccjk.CNhnT6uQ.mjs';
|
|
6
6
|
import { e as executeCommand } from '../shared/ccjk.BnsY5WxD.mjs';
|
|
7
7
|
import { j as join, d as dirname } from '../shared/ccjk.bQ7Dh1g4.mjs';
|
|
8
|
+
import { m as matter } from '../shared/ccjk.y-a_1vK4.mjs';
|
|
9
|
+
import { writeFile, readFile } from './fs-operations.mjs';
|
|
8
10
|
import '../shared/ccjk.BAGoDD49.mjs';
|
|
9
11
|
import 'node:crypto';
|
|
10
12
|
import 'better-sqlite3';
|
|
11
13
|
import 'node:child_process';
|
|
12
14
|
import 'node:util';
|
|
15
|
+
import 'fs';
|
|
16
|
+
import '../shared/ccjk.COweQ1RR.mjs';
|
|
17
|
+
import 'node:fs/promises';
|
|
13
18
|
|
|
14
19
|
class TaskQueue {
|
|
15
20
|
queue = [];
|
|
@@ -270,7 +275,7 @@ class ResearchCommandError extends Error {
|
|
|
270
275
|
this.name = "ResearchCommandError";
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
|
-
function createPersistence(dbPath) {
|
|
278
|
+
function createPersistence$1(dbPath) {
|
|
274
279
|
return new TaskPersistence(dbPath);
|
|
275
280
|
}
|
|
276
281
|
function getResearchDataDir(dbPath) {
|
|
@@ -580,7 +585,7 @@ function normalizeRunOptions(options) {
|
|
|
580
585
|
}
|
|
581
586
|
async function runResearchExperiment(options) {
|
|
582
587
|
const normalized = normalizeRunOptions(options);
|
|
583
|
-
const persistence = createPersistence(normalized.dbPath);
|
|
588
|
+
const persistence = createPersistence$1(normalized.dbPath);
|
|
584
589
|
const queue = new TaskQueue({
|
|
585
590
|
concurrency: 1,
|
|
586
591
|
defaultTimeout: normalized.budgetMs,
|
|
@@ -802,7 +807,7 @@ async function runResearchExperiment(options) {
|
|
|
802
807
|
};
|
|
803
808
|
}
|
|
804
809
|
function listResearchSessions(limit = DEFAULT_SESSION_LIMIT, dbPath) {
|
|
805
|
-
const persistence = createPersistence(dbPath);
|
|
810
|
+
const persistence = createPersistence$1(dbPath);
|
|
806
811
|
return persistence.listSessions(Math.max(limit * 5, 50)).filter((session) => session.metadata?.kind === RESEARCH_SESSION_KIND).slice(0, limit).map((session) => ({
|
|
807
812
|
id: session.id,
|
|
808
813
|
createdAt: session.createdAt,
|
|
@@ -821,7 +826,7 @@ function getLatestResearchSession(dbPath) {
|
|
|
821
826
|
return listResearchSessions(1, dbPath)[0];
|
|
822
827
|
}
|
|
823
828
|
function getResearchSessionStatus(sessionId, dbPath) {
|
|
824
|
-
const persistence = createPersistence(dbPath);
|
|
829
|
+
const persistence = createPersistence$1(dbPath);
|
|
825
830
|
const restored = persistence.restoreContext(sessionId);
|
|
826
831
|
if (!restored) {
|
|
827
832
|
return null;
|
|
@@ -926,6 +931,618 @@ function getResearchReport(sessionId, dbPath) {
|
|
|
926
931
|
};
|
|
927
932
|
}
|
|
928
933
|
|
|
934
|
+
const DEFAULT_RESEARCH_PROGRAM_PATH = ".ccjk/research/program.md";
|
|
935
|
+
const DEFAULT_RESEARCH_MAX_ROUNDS = 10;
|
|
936
|
+
const DEFAULT_RESEARCH_MAX_NO_IMPROVE_ROUNDS = 3;
|
|
937
|
+
const DEFAULT_RESEARCH_BUDGET_MS = 5 * 60 * 1e3;
|
|
938
|
+
function normalizePositiveInteger(value, fallback) {
|
|
939
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
940
|
+
return value;
|
|
941
|
+
}
|
|
942
|
+
if (typeof value === "string" && value.trim()) {
|
|
943
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
944
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
945
|
+
return parsed;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return fallback;
|
|
949
|
+
}
|
|
950
|
+
function normalizeOptionalNumber(value) {
|
|
951
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
952
|
+
return value;
|
|
953
|
+
}
|
|
954
|
+
if (typeof value === "string" && value.trim()) {
|
|
955
|
+
const parsed = Number.parseFloat(value.trim());
|
|
956
|
+
if (Number.isFinite(parsed)) {
|
|
957
|
+
return parsed;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return void 0;
|
|
961
|
+
}
|
|
962
|
+
function normalizeObjective(value) {
|
|
963
|
+
return value === "maximize" || value === "minimize" || value === "auto" ? value : "auto";
|
|
964
|
+
}
|
|
965
|
+
function normalizeString(value) {
|
|
966
|
+
if (typeof value !== "string") {
|
|
967
|
+
return void 0;
|
|
968
|
+
}
|
|
969
|
+
const trimmed = value.trim();
|
|
970
|
+
return trimmed || void 0;
|
|
971
|
+
}
|
|
972
|
+
function resolveResearchProgramPath(programPath, cwd) {
|
|
973
|
+
return programPath ? join(cwd || process.cwd(), programPath) : join(cwd || process.cwd(), DEFAULT_RESEARCH_PROGRAM_PATH);
|
|
974
|
+
}
|
|
975
|
+
function createDefaultResearchProgramTemplate(cwd) {
|
|
976
|
+
const workingDirectory = cwd || process.cwd();
|
|
977
|
+
return `---
|
|
978
|
+
name: repo-research
|
|
979
|
+
metric: test_pass_rate
|
|
980
|
+
objective: maximize
|
|
981
|
+
baselineCommand: pnpm test:run
|
|
982
|
+
candidateCommand: pnpm test:run
|
|
983
|
+
cwd: ${workingDirectory}
|
|
984
|
+
maxRounds: ${DEFAULT_RESEARCH_MAX_ROUNDS}
|
|
985
|
+
maxNoImproveRounds: ${DEFAULT_RESEARCH_MAX_NO_IMPROVE_ROUNDS}
|
|
986
|
+
budgetMs: ${DEFAULT_RESEARCH_BUDGET_MS}
|
|
987
|
+
targetMetric:
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
# Research Goal
|
|
991
|
+
|
|
992
|
+
Describe the optimization target for this repository.
|
|
993
|
+
|
|
994
|
+
## Guardrails
|
|
995
|
+
|
|
996
|
+
- Keep changes scoped and measurable.
|
|
997
|
+
- Prefer one candidate change per round.
|
|
998
|
+
- Stop when the target metric is reached or repeated no-improvement rounds occur.
|
|
999
|
+
|
|
1000
|
+
## Notes
|
|
1001
|
+
|
|
1002
|
+
Document hypotheses, constraints, and any files or modules that must not change.
|
|
1003
|
+
`;
|
|
1004
|
+
}
|
|
1005
|
+
function parseResearchProgram(content, programPath = DEFAULT_RESEARCH_PROGRAM_PATH) {
|
|
1006
|
+
const parsed = matter(content);
|
|
1007
|
+
const data = parsed.data || {};
|
|
1008
|
+
const baselineCommand = normalizeString(data.baselineCommand);
|
|
1009
|
+
if (!baselineCommand) {
|
|
1010
|
+
throw new Error(`Missing required 'baselineCommand' in ${programPath}`);
|
|
1011
|
+
}
|
|
1012
|
+
const candidateCommand = normalizeString(data.candidateCommand);
|
|
1013
|
+
if (!candidateCommand) {
|
|
1014
|
+
throw new Error(`Missing required 'candidateCommand' in ${programPath}`);
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
name: normalizeString(data.name) || "repo-research",
|
|
1018
|
+
metric: normalizeString(data.metric),
|
|
1019
|
+
objective: normalizeObjective(data.objective),
|
|
1020
|
+
baselineCommand,
|
|
1021
|
+
candidateCommand,
|
|
1022
|
+
cwd: normalizeString(data.cwd) || process.cwd(),
|
|
1023
|
+
maxRounds: normalizePositiveInteger(data.maxRounds, DEFAULT_RESEARCH_MAX_ROUNDS),
|
|
1024
|
+
maxNoImproveRounds: normalizePositiveInteger(data.maxNoImproveRounds, DEFAULT_RESEARCH_MAX_NO_IMPROVE_ROUNDS),
|
|
1025
|
+
budgetMs: normalizePositiveInteger(data.budgetMs, DEFAULT_RESEARCH_BUDGET_MS),
|
|
1026
|
+
targetMetric: normalizeOptionalNumber(data.targetMetric),
|
|
1027
|
+
notes: parsed.content.trim(),
|
|
1028
|
+
programPath
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function readResearchProgram(programPath, cwd) {
|
|
1032
|
+
const resolvedPath = resolveResearchProgramPath(programPath, cwd);
|
|
1033
|
+
const content = readFile(resolvedPath);
|
|
1034
|
+
return parseResearchProgram(content, resolvedPath);
|
|
1035
|
+
}
|
|
1036
|
+
function initResearchProgram(programPath, cwd) {
|
|
1037
|
+
const resolvedPath = resolveResearchProgramPath(programPath, cwd);
|
|
1038
|
+
const content = createDefaultResearchProgramTemplate(cwd);
|
|
1039
|
+
writeFile(resolvedPath, content);
|
|
1040
|
+
return {
|
|
1041
|
+
programPath: resolvedPath,
|
|
1042
|
+
content
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const RESEARCH_LOOP_SESSION_KIND = "research-loop";
|
|
1047
|
+
const DEFAULT_FAILURE_STREAK_LIMIT = 2;
|
|
1048
|
+
function createPersistence(dbPath) {
|
|
1049
|
+
return new TaskPersistence(dbPath);
|
|
1050
|
+
}
|
|
1051
|
+
function toPositiveInteger(value, fallback) {
|
|
1052
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
1053
|
+
}
|
|
1054
|
+
function toOptionalFiniteNumber(value) {
|
|
1055
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1056
|
+
}
|
|
1057
|
+
function applyProgramOverrides(program, overrides = {}) {
|
|
1058
|
+
return {
|
|
1059
|
+
...program,
|
|
1060
|
+
cwd: overrides.cwd || program.cwd,
|
|
1061
|
+
budgetMs: toPositiveInteger(overrides.budgetMs, program.budgetMs),
|
|
1062
|
+
maxRounds: toPositiveInteger(overrides.maxRounds, program.maxRounds),
|
|
1063
|
+
maxNoImproveRounds: toPositiveInteger(overrides.maxNoImproveRounds, program.maxNoImproveRounds),
|
|
1064
|
+
targetMetric: toOptionalFiniteNumber(overrides.targetMetric) ?? program.targetMetric
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function nowIso() {
|
|
1068
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1069
|
+
}
|
|
1070
|
+
function toObjective(value, metricName) {
|
|
1071
|
+
if (value === "maximize" || value === "minimize") {
|
|
1072
|
+
return value;
|
|
1073
|
+
}
|
|
1074
|
+
const normalized = (metricName || "").toLowerCase();
|
|
1075
|
+
if (!normalized) {
|
|
1076
|
+
return "maximize";
|
|
1077
|
+
}
|
|
1078
|
+
const lowerIsBetter = ["loss", "error", "bpb", "perplexity", "ppl", "latency", "duration", "time", "cost", "price", "wer", "cer"];
|
|
1079
|
+
return lowerIsBetter.some((keyword) => normalized.includes(keyword)) ? "minimize" : "maximize";
|
|
1080
|
+
}
|
|
1081
|
+
function isAcceptedResult(result) {
|
|
1082
|
+
return result.verdict === "PASS";
|
|
1083
|
+
}
|
|
1084
|
+
function cloneLoopMetadata(metadata, patch = {}) {
|
|
1085
|
+
return {
|
|
1086
|
+
...metadata,
|
|
1087
|
+
...patch,
|
|
1088
|
+
acceptedRoundSessionIds: patch.acceptedRoundSessionIds || [...metadata.acceptedRoundSessionIds],
|
|
1089
|
+
rejectedRoundSessionIds: patch.rejectedRoundSessionIds || [...metadata.rejectedRoundSessionIds],
|
|
1090
|
+
roundSessionIds: patch.roundSessionIds || [...metadata.roundSessionIds],
|
|
1091
|
+
updatedAt: patch.updatedAt || nowIso()
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function ensureLoopMetadata(raw, sessionId) {
|
|
1095
|
+
const createdAt = typeof raw.createdAt === "string" && raw.createdAt ? raw.createdAt : nowIso();
|
|
1096
|
+
return {
|
|
1097
|
+
kind: "research-loop",
|
|
1098
|
+
name: typeof raw.name === "string" && raw.name ? raw.name : sessionId,
|
|
1099
|
+
programPath: typeof raw.programPath === "string" ? raw.programPath : ".ccjk/research/program.md",
|
|
1100
|
+
metric: typeof raw.metric === "string" && raw.metric ? raw.metric : void 0,
|
|
1101
|
+
objective: raw.objective === "maximize" || raw.objective === "minimize" || raw.objective === "auto" ? raw.objective : "auto",
|
|
1102
|
+
cwd: typeof raw.cwd === "string" && raw.cwd ? raw.cwd : process.cwd(),
|
|
1103
|
+
budgetMs: toPositiveInteger(raw.budgetMs, 5 * 60 * 1e3),
|
|
1104
|
+
maxRounds: toPositiveInteger(raw.maxRounds, 10),
|
|
1105
|
+
maxNoImproveRounds: toPositiveInteger(raw.maxNoImproveRounds, 3),
|
|
1106
|
+
targetMetric: typeof raw.targetMetric === "number" && Number.isFinite(raw.targetMetric) ? raw.targetMetric : void 0,
|
|
1107
|
+
status: raw.status === "running" || raw.status === "stopped" || raw.status === "completed" || raw.status === "failed" ? raw.status : "running",
|
|
1108
|
+
baselineSessionId: typeof raw.baselineSessionId === "string" && raw.baselineSessionId ? raw.baselineSessionId : void 0,
|
|
1109
|
+
currentRound: toPositiveInteger(raw.currentRound, 0) - 1 >= 0 ? toPositiveInteger(raw.currentRound, 0) : 0,
|
|
1110
|
+
bestSessionId: typeof raw.bestSessionId === "string" && raw.bestSessionId ? raw.bestSessionId : void 0,
|
|
1111
|
+
bestMetricValue: typeof raw.bestMetricValue === "number" && Number.isFinite(raw.bestMetricValue) ? raw.bestMetricValue : void 0,
|
|
1112
|
+
acceptedRoundSessionIds: Array.isArray(raw.acceptedRoundSessionIds) ? raw.acceptedRoundSessionIds.filter((value) => typeof value === "string") : [],
|
|
1113
|
+
rejectedRoundSessionIds: Array.isArray(raw.rejectedRoundSessionIds) ? raw.rejectedRoundSessionIds.filter((value) => typeof value === "string") : [],
|
|
1114
|
+
roundSessionIds: Array.isArray(raw.roundSessionIds) ? raw.roundSessionIds.filter((value) => typeof value === "string") : [],
|
|
1115
|
+
noImproveStreak: typeof raw.noImproveStreak === "number" && raw.noImproveStreak >= 0 ? raw.noImproveStreak : 0,
|
|
1116
|
+
failureStreak: typeof raw.failureStreak === "number" && raw.failureStreak >= 0 ? raw.failureStreak : 0,
|
|
1117
|
+
stopReason: raw.stopReason,
|
|
1118
|
+
createdAt,
|
|
1119
|
+
updatedAt: typeof raw.updatedAt === "string" && raw.updatedAt ? raw.updatedAt : createdAt,
|
|
1120
|
+
lastRoundSessionId: typeof raw.lastRoundSessionId === "string" && raw.lastRoundSessionId ? raw.lastRoundSessionId : void 0,
|
|
1121
|
+
notes: typeof raw.notes === "string" && raw.notes ? raw.notes : void 0
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function getLoopSessionRecord(sessionId, dbPath) {
|
|
1125
|
+
const persistence = createPersistence(dbPath);
|
|
1126
|
+
const session = persistence.getSession(sessionId);
|
|
1127
|
+
if (!session || session.metadata?.kind !== RESEARCH_LOOP_SESSION_KIND) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
id: session.id,
|
|
1132
|
+
createdAt: session.createdAt,
|
|
1133
|
+
updatedAt: session.updatedAt,
|
|
1134
|
+
metadata: ensureLoopMetadata(session.metadata, session.id)
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function getLatestLoopSession(dbPath) {
|
|
1138
|
+
const persistence = createPersistence(dbPath);
|
|
1139
|
+
const session = persistence.listSessions(100).find((item) => item.metadata?.kind === RESEARCH_LOOP_SESSION_KIND);
|
|
1140
|
+
if (!session) {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
id: session.id,
|
|
1145
|
+
createdAt: session.createdAt,
|
|
1146
|
+
metadata: ensureLoopMetadata(session.metadata, session.id)
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function getResolvedLoopSessionId(sessionId, dbPath) {
|
|
1150
|
+
return sessionId || getLatestLoopSession(dbPath)?.id;
|
|
1151
|
+
}
|
|
1152
|
+
function saveLoopMetadata(sessionId, metadata, dbPath) {
|
|
1153
|
+
const persistence = createPersistence(dbPath);
|
|
1154
|
+
const normalized = ensureLoopMetadata({ ...metadata, updatedAt: nowIso() }, sessionId);
|
|
1155
|
+
persistence.saveSession(sessionId, normalized);
|
|
1156
|
+
return normalized;
|
|
1157
|
+
}
|
|
1158
|
+
function buildRoundRecord(round, result) {
|
|
1159
|
+
return {
|
|
1160
|
+
round,
|
|
1161
|
+
sessionId: result.sessionId,
|
|
1162
|
+
name: result.name,
|
|
1163
|
+
status: result.status,
|
|
1164
|
+
verdict: result.verdict,
|
|
1165
|
+
metricName: result.metricName,
|
|
1166
|
+
metricValue: result.metricValue,
|
|
1167
|
+
accepted: isAcceptedResult(result),
|
|
1168
|
+
objective: result.objective,
|
|
1169
|
+
command: result.command,
|
|
1170
|
+
reason: result.verdictReason,
|
|
1171
|
+
baselineSessionId: result.baselineSessionId,
|
|
1172
|
+
comparison: result.comparison,
|
|
1173
|
+
createdAt: result.createdAt
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function getRoundRecords(metadata, dbPath) {
|
|
1177
|
+
return metadata.roundSessionIds.map((sessionId, index) => {
|
|
1178
|
+
const report = getResearchReport(sessionId, dbPath);
|
|
1179
|
+
if (!report) {
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
const record = {
|
|
1183
|
+
round: index + 1,
|
|
1184
|
+
sessionId,
|
|
1185
|
+
name: report.name,
|
|
1186
|
+
status: report.status,
|
|
1187
|
+
verdict: report.verdict,
|
|
1188
|
+
metricName: report.metricName,
|
|
1189
|
+
metricValue: report.metricValue,
|
|
1190
|
+
accepted: metadata.acceptedRoundSessionIds.includes(sessionId),
|
|
1191
|
+
objective: report.objective || toObjective(metadata.objective, metadata.metric),
|
|
1192
|
+
command: report.command,
|
|
1193
|
+
reason: report.verdictReason,
|
|
1194
|
+
baselineSessionId: report.baselineSessionId,
|
|
1195
|
+
comparison: report.comparison,
|
|
1196
|
+
createdAt: report.createdAt
|
|
1197
|
+
};
|
|
1198
|
+
return record;
|
|
1199
|
+
}).filter((value) => value !== null);
|
|
1200
|
+
}
|
|
1201
|
+
function isTargetReached(metricValue, objective, targetMetric) {
|
|
1202
|
+
if (metricValue === void 0 || targetMetric === void 0) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
return objective === "minimize" ? metricValue <= targetMetric : metricValue >= targetMetric;
|
|
1206
|
+
}
|
|
1207
|
+
function evaluateLoopStopCondition(input) {
|
|
1208
|
+
const objective = toObjective(input.metadata.objective, input.metadata.metric);
|
|
1209
|
+
if (input.metadata.status === "completed" || input.metadata.status === "failed") {
|
|
1210
|
+
return { shouldStop: true, reason: input.metadata.stopReason, status: input.metadata.status };
|
|
1211
|
+
}
|
|
1212
|
+
if (input.metadata.status === "stopped") {
|
|
1213
|
+
return { shouldStop: true, reason: input.metadata.stopReason || "manual-stop", status: "stopped" };
|
|
1214
|
+
}
|
|
1215
|
+
if (input.phase === "baseline" && input.latestResult && !input.latestResult.success) {
|
|
1216
|
+
return { shouldStop: true, reason: "baseline-failed", status: "failed" };
|
|
1217
|
+
}
|
|
1218
|
+
if (input.metadata.currentRound >= input.program.maxRounds) {
|
|
1219
|
+
return { shouldStop: true, reason: "max-rounds-reached", status: "completed" };
|
|
1220
|
+
}
|
|
1221
|
+
if (input.metadata.noImproveStreak >= input.program.maxNoImproveRounds) {
|
|
1222
|
+
return { shouldStop: true, reason: "max-no-improve-rounds-reached", status: "completed" };
|
|
1223
|
+
}
|
|
1224
|
+
if (isTargetReached(input.metadata.bestMetricValue, objective, input.program.targetMetric)) {
|
|
1225
|
+
return { shouldStop: true, reason: "target-metric-reached", status: "completed" };
|
|
1226
|
+
}
|
|
1227
|
+
if (input.phase === "round" && input.metadata.failureStreak >= DEFAULT_FAILURE_STREAK_LIMIT) {
|
|
1228
|
+
return { shouldStop: true, reason: "candidate-failed-repeatedly", status: "failed" };
|
|
1229
|
+
}
|
|
1230
|
+
return { shouldStop: false };
|
|
1231
|
+
}
|
|
1232
|
+
function createLoopSession(program, dbPath) {
|
|
1233
|
+
const sessionId = `research-loop-${Date.now()}-${nanoid(6)}`;
|
|
1234
|
+
const createdAt = nowIso();
|
|
1235
|
+
const metadata = {
|
|
1236
|
+
kind: "research-loop",
|
|
1237
|
+
name: program.name,
|
|
1238
|
+
programPath: program.programPath,
|
|
1239
|
+
metric: program.metric,
|
|
1240
|
+
objective: program.objective,
|
|
1241
|
+
cwd: program.cwd,
|
|
1242
|
+
budgetMs: program.budgetMs,
|
|
1243
|
+
maxRounds: program.maxRounds,
|
|
1244
|
+
maxNoImproveRounds: program.maxNoImproveRounds,
|
|
1245
|
+
targetMetric: program.targetMetric,
|
|
1246
|
+
status: "running",
|
|
1247
|
+
baselineSessionId: void 0,
|
|
1248
|
+
currentRound: 0,
|
|
1249
|
+
bestSessionId: void 0,
|
|
1250
|
+
bestMetricValue: void 0,
|
|
1251
|
+
acceptedRoundSessionIds: [],
|
|
1252
|
+
rejectedRoundSessionIds: [],
|
|
1253
|
+
roundSessionIds: [],
|
|
1254
|
+
noImproveStreak: 0,
|
|
1255
|
+
failureStreak: 0,
|
|
1256
|
+
createdAt,
|
|
1257
|
+
updatedAt: createdAt,
|
|
1258
|
+
notes: program.notes
|
|
1259
|
+
};
|
|
1260
|
+
return {
|
|
1261
|
+
sessionId,
|
|
1262
|
+
metadata: saveLoopMetadata(sessionId, metadata, dbPath)
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
async function ensureBaseline(sessionId, metadata, program, dbPath) {
|
|
1266
|
+
if (metadata.baselineSessionId) {
|
|
1267
|
+
const report = getResearchReport(metadata.baselineSessionId, dbPath);
|
|
1268
|
+
if (report) {
|
|
1269
|
+
return {
|
|
1270
|
+
metadata,
|
|
1271
|
+
baseline: {
|
|
1272
|
+
sessionId: report.sessionId,
|
|
1273
|
+
taskId: "",
|
|
1274
|
+
name: report.name,
|
|
1275
|
+
command: report.command,
|
|
1276
|
+
cwd: report.cwd,
|
|
1277
|
+
metricName: report.metricName,
|
|
1278
|
+
metricValue: report.metricValue,
|
|
1279
|
+
success: report.status === "completed",
|
|
1280
|
+
status: report.status,
|
|
1281
|
+
exitCode: report.exitCode,
|
|
1282
|
+
stdout: "",
|
|
1283
|
+
stderr: "",
|
|
1284
|
+
durationMs: report.durationMs,
|
|
1285
|
+
phase: "baseline",
|
|
1286
|
+
objective: report.objective || toObjective(program.objective, program.metric),
|
|
1287
|
+
verdict: report.verdict,
|
|
1288
|
+
verdictReason: report.verdictReason,
|
|
1289
|
+
baselineSessionId: report.baselineSessionId,
|
|
1290
|
+
comparison: report.comparison,
|
|
1291
|
+
phaseHistory: report.phaseHistory,
|
|
1292
|
+
createdAt: report.createdAt
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const baseline = await runResearchExperiment({
|
|
1298
|
+
name: `${program.name}-baseline`,
|
|
1299
|
+
command: program.baselineCommand,
|
|
1300
|
+
metricName: program.metric,
|
|
1301
|
+
budgetMs: program.budgetMs,
|
|
1302
|
+
cwd: program.cwd,
|
|
1303
|
+
objective: program.objective,
|
|
1304
|
+
dbPath
|
|
1305
|
+
});
|
|
1306
|
+
const nextMetadata = cloneLoopMetadata(metadata, {
|
|
1307
|
+
baselineSessionId: baseline.sessionId,
|
|
1308
|
+
bestSessionId: baseline.sessionId,
|
|
1309
|
+
bestMetricValue: baseline.metricValue,
|
|
1310
|
+
failureStreak: baseline.success ? 0 : metadata.failureStreak + 1
|
|
1311
|
+
});
|
|
1312
|
+
return {
|
|
1313
|
+
metadata: saveLoopMetadata(sessionId, nextMetadata, dbPath),
|
|
1314
|
+
baseline
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
async function executeResearchRound(sessionId, metadata, program, dbPath) {
|
|
1318
|
+
const roundNumber = metadata.currentRound + 1;
|
|
1319
|
+
const baselineSessionId = metadata.bestSessionId || metadata.baselineSessionId;
|
|
1320
|
+
const result = await runResearchExperiment({
|
|
1321
|
+
name: `${program.name}-round-${roundNumber}`,
|
|
1322
|
+
command: program.candidateCommand,
|
|
1323
|
+
metricName: program.metric,
|
|
1324
|
+
budgetMs: program.budgetMs,
|
|
1325
|
+
cwd: program.cwd,
|
|
1326
|
+
objective: program.objective,
|
|
1327
|
+
baselineSessionId,
|
|
1328
|
+
dbPath
|
|
1329
|
+
});
|
|
1330
|
+
const accepted = isAcceptedResult(result);
|
|
1331
|
+
const acceptedRoundSessionIds = accepted ? [...metadata.acceptedRoundSessionIds, result.sessionId] : [...metadata.acceptedRoundSessionIds];
|
|
1332
|
+
const rejectedRoundSessionIds = accepted ? [...metadata.rejectedRoundSessionIds] : [...metadata.rejectedRoundSessionIds, result.sessionId];
|
|
1333
|
+
const bestSessionId = accepted ? result.sessionId : metadata.bestSessionId;
|
|
1334
|
+
const bestMetricValue = accepted ? result.metricValue : metadata.bestMetricValue;
|
|
1335
|
+
const nextMetadata = cloneLoopMetadata(metadata, {
|
|
1336
|
+
currentRound: roundNumber,
|
|
1337
|
+
bestSessionId,
|
|
1338
|
+
bestMetricValue,
|
|
1339
|
+
roundSessionIds: [...metadata.roundSessionIds, result.sessionId],
|
|
1340
|
+
acceptedRoundSessionIds,
|
|
1341
|
+
rejectedRoundSessionIds,
|
|
1342
|
+
noImproveStreak: accepted ? 0 : metadata.noImproveStreak + 1,
|
|
1343
|
+
failureStreak: result.success ? 0 : metadata.failureStreak + 1,
|
|
1344
|
+
lastRoundSessionId: result.sessionId
|
|
1345
|
+
});
|
|
1346
|
+
return {
|
|
1347
|
+
metadata: saveLoopMetadata(sessionId, nextMetadata, dbPath),
|
|
1348
|
+
result,
|
|
1349
|
+
round: buildRoundRecord(roundNumber, result)
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function getResearchLoopStatusInternal(sessionId, dbPath) {
|
|
1353
|
+
const loopSession = getLoopSessionRecord(sessionId, dbPath);
|
|
1354
|
+
if (!loopSession) {
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
const baselineReport = loopSession.metadata.baselineSessionId ? getResearchReport(loopSession.metadata.baselineSessionId, dbPath) : null;
|
|
1358
|
+
const rounds = getRoundRecords(loopSession.metadata, dbPath);
|
|
1359
|
+
return {
|
|
1360
|
+
sessionId,
|
|
1361
|
+
metadata: loopSession.metadata,
|
|
1362
|
+
baseline: baselineReport ? {
|
|
1363
|
+
sessionId: baselineReport.sessionId,
|
|
1364
|
+
taskId: "",
|
|
1365
|
+
name: baselineReport.name,
|
|
1366
|
+
command: baselineReport.command,
|
|
1367
|
+
cwd: baselineReport.cwd,
|
|
1368
|
+
metricName: baselineReport.metricName,
|
|
1369
|
+
metricValue: baselineReport.metricValue,
|
|
1370
|
+
success: baselineReport.status === "completed",
|
|
1371
|
+
status: baselineReport.status,
|
|
1372
|
+
exitCode: baselineReport.exitCode,
|
|
1373
|
+
stdout: "",
|
|
1374
|
+
stderr: "",
|
|
1375
|
+
durationMs: baselineReport.durationMs,
|
|
1376
|
+
phase: "baseline",
|
|
1377
|
+
objective: baselineReport.objective || toObjective(loopSession.metadata.objective, loopSession.metadata.metric),
|
|
1378
|
+
verdict: baselineReport.verdict,
|
|
1379
|
+
verdictReason: baselineReport.verdictReason,
|
|
1380
|
+
baselineSessionId: baselineReport.baselineSessionId,
|
|
1381
|
+
comparison: baselineReport.comparison,
|
|
1382
|
+
phaseHistory: baselineReport.phaseHistory,
|
|
1383
|
+
createdAt: baselineReport.createdAt
|
|
1384
|
+
} : null,
|
|
1385
|
+
latestRound: rounds[rounds.length - 1],
|
|
1386
|
+
rounds
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
async function startResearchLoop(options = {}) {
|
|
1390
|
+
const program = applyProgramOverrides(readResearchProgram(options.programPath, options.cwd), options.overrides);
|
|
1391
|
+
const { sessionId } = createLoopSession(program, options.dbPath);
|
|
1392
|
+
return await resumeResearchLoop({ ...options, sessionId });
|
|
1393
|
+
}
|
|
1394
|
+
async function runResearchRound(options = {}) {
|
|
1395
|
+
const resolvedSessionId = getResolvedLoopSessionId(options.sessionId, options.dbPath);
|
|
1396
|
+
if (!resolvedSessionId) {
|
|
1397
|
+
throw new Error("No research loop session found.");
|
|
1398
|
+
}
|
|
1399
|
+
const loopSession = getLoopSessionRecord(resolvedSessionId, options.dbPath);
|
|
1400
|
+
if (!loopSession) {
|
|
1401
|
+
throw new Error(`Research loop session not found: ${resolvedSessionId}`);
|
|
1402
|
+
}
|
|
1403
|
+
const program = applyProgramOverrides(readResearchProgram(loopSession.metadata.programPath, options.cwd), options.overrides);
|
|
1404
|
+
let metadata = saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(loopSession.metadata, { status: "running", stopReason: void 0 }), options.dbPath);
|
|
1405
|
+
const baselineState = await ensureBaseline(resolvedSessionId, metadata, program, options.dbPath);
|
|
1406
|
+
metadata = baselineState.metadata;
|
|
1407
|
+
const stopAfterBaseline = evaluateLoopStopCondition({ metadata, latestResult: baselineState.baseline, program, phase: "baseline" });
|
|
1408
|
+
if (stopAfterBaseline.shouldStop) {
|
|
1409
|
+
metadata = saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(metadata, {
|
|
1410
|
+
status: stopAfterBaseline.status || metadata.status,
|
|
1411
|
+
stopReason: stopAfterBaseline.reason
|
|
1412
|
+
}), options.dbPath);
|
|
1413
|
+
return getResearchLoopStatusInternal(resolvedSessionId, options.dbPath);
|
|
1414
|
+
}
|
|
1415
|
+
const roundState = await executeResearchRound(resolvedSessionId, metadata, program, options.dbPath);
|
|
1416
|
+
metadata = roundState.metadata;
|
|
1417
|
+
const stopState = evaluateLoopStopCondition({ metadata, latestResult: roundState.result, program, phase: "round" });
|
|
1418
|
+
if (stopState.shouldStop) {
|
|
1419
|
+
metadata = saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(metadata, {
|
|
1420
|
+
status: stopState.status || metadata.status,
|
|
1421
|
+
stopReason: stopState.reason
|
|
1422
|
+
}), options.dbPath);
|
|
1423
|
+
}
|
|
1424
|
+
return getResearchLoopStatusInternal(resolvedSessionId, options.dbPath);
|
|
1425
|
+
}
|
|
1426
|
+
async function resumeResearchLoop(options = {}) {
|
|
1427
|
+
const resolvedSessionId = getResolvedLoopSessionId(options.sessionId, options.dbPath);
|
|
1428
|
+
if (!resolvedSessionId) {
|
|
1429
|
+
throw new Error("No research loop session found.");
|
|
1430
|
+
}
|
|
1431
|
+
const loopSession = getLoopSessionRecord(resolvedSessionId, options.dbPath);
|
|
1432
|
+
if (!loopSession) {
|
|
1433
|
+
throw new Error(`Research loop session not found: ${resolvedSessionId}`);
|
|
1434
|
+
}
|
|
1435
|
+
const program = applyProgramOverrides(readResearchProgram(loopSession.metadata.programPath, options.cwd), options.overrides);
|
|
1436
|
+
let metadata = saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(loopSession.metadata, {
|
|
1437
|
+
status: loopSession.metadata.status === "completed" || loopSession.metadata.status === "failed" ? loopSession.metadata.status : "running",
|
|
1438
|
+
stopReason: loopSession.metadata.status === "stopped" ? void 0 : loopSession.metadata.stopReason
|
|
1439
|
+
}), options.dbPath);
|
|
1440
|
+
const baselineState = await ensureBaseline(resolvedSessionId, metadata, program, options.dbPath);
|
|
1441
|
+
metadata = baselineState.metadata;
|
|
1442
|
+
while (true) {
|
|
1443
|
+
const stopBeforeRound = evaluateLoopStopCondition({ metadata, program });
|
|
1444
|
+
if (stopBeforeRound.shouldStop) {
|
|
1445
|
+
metadata = saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(metadata, {
|
|
1446
|
+
status: stopBeforeRound.status || metadata.status,
|
|
1447
|
+
stopReason: stopBeforeRound.reason
|
|
1448
|
+
}), options.dbPath);
|
|
1449
|
+
return getResearchLoopStatusInternal(resolvedSessionId, options.dbPath);
|
|
1450
|
+
}
|
|
1451
|
+
const roundState = await executeResearchRound(resolvedSessionId, metadata, program, options.dbPath);
|
|
1452
|
+
metadata = roundState.metadata;
|
|
1453
|
+
const stopAfterRound = evaluateLoopStopCondition({ metadata, latestResult: roundState.result, program, phase: "round" });
|
|
1454
|
+
if (stopAfterRound.shouldStop) {
|
|
1455
|
+
metadata = saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(metadata, {
|
|
1456
|
+
status: stopAfterRound.status || metadata.status,
|
|
1457
|
+
stopReason: stopAfterRound.reason || "resume-complete"
|
|
1458
|
+
}), options.dbPath);
|
|
1459
|
+
return getResearchLoopStatusInternal(resolvedSessionId, options.dbPath);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
function stopResearchLoop(options = {}) {
|
|
1464
|
+
const resolvedSessionId = getResolvedLoopSessionId(options.sessionId, options.dbPath);
|
|
1465
|
+
if (!resolvedSessionId) {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
const loopSession = getLoopSessionRecord(resolvedSessionId, options.dbPath);
|
|
1469
|
+
if (!loopSession) {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
saveLoopMetadata(resolvedSessionId, cloneLoopMetadata(loopSession.metadata, {
|
|
1473
|
+
status: "stopped",
|
|
1474
|
+
stopReason: "manual-stop"
|
|
1475
|
+
}), options.dbPath);
|
|
1476
|
+
return getResearchLoopStatusInternal(resolvedSessionId, options.dbPath);
|
|
1477
|
+
}
|
|
1478
|
+
function getResearchLoopStatus(sessionId, dbPath) {
|
|
1479
|
+
const resolvedSessionId = getResolvedLoopSessionId(sessionId, dbPath);
|
|
1480
|
+
if (!resolvedSessionId) {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
return getResearchLoopStatusInternal(resolvedSessionId, dbPath);
|
|
1484
|
+
}
|
|
1485
|
+
function getResearchLoopReport(sessionId, dbPath) {
|
|
1486
|
+
const status = getResearchLoopStatus(sessionId, dbPath);
|
|
1487
|
+
if (!status) {
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
const lines = [];
|
|
1491
|
+
lines.push("# CCJK Research Loop Report");
|
|
1492
|
+
lines.push("");
|
|
1493
|
+
lines.push(`**Session**: ${status.sessionId}`);
|
|
1494
|
+
lines.push(`**Name**: ${status.metadata.name}`);
|
|
1495
|
+
lines.push(`**Status**: ${status.metadata.status}`);
|
|
1496
|
+
lines.push(`**Stop Reason**: ${status.metadata.stopReason || "running"}`);
|
|
1497
|
+
lines.push(`**Rounds**: ${status.metadata.currentRound}/${status.metadata.maxRounds}`);
|
|
1498
|
+
lines.push(`**No-Improve Streak**: ${status.metadata.noImproveStreak}/${status.metadata.maxNoImproveRounds}`);
|
|
1499
|
+
lines.push(`**Metric**: ${status.metadata.metric || "not configured"}`);
|
|
1500
|
+
lines.push(`**Objective**: ${status.metadata.objective}`);
|
|
1501
|
+
lines.push(`**Target Metric**: ${status.metadata.targetMetric ?? "not configured"}`);
|
|
1502
|
+
lines.push(`**Best Session**: ${status.metadata.bestSessionId || "none"}`);
|
|
1503
|
+
lines.push(`**Best Metric**: ${status.metadata.bestMetricValue ?? "not found"}`);
|
|
1504
|
+
lines.push(`**Created**: ${status.metadata.createdAt}`);
|
|
1505
|
+
lines.push(`**Updated**: ${status.metadata.updatedAt}`);
|
|
1506
|
+
if (status.baseline) {
|
|
1507
|
+
lines.push("");
|
|
1508
|
+
lines.push("## Baseline");
|
|
1509
|
+
lines.push("");
|
|
1510
|
+
lines.push(`- Session: ${status.baseline.sessionId}`);
|
|
1511
|
+
lines.push(`- Verdict: ${status.baseline.verdict}`);
|
|
1512
|
+
lines.push(`- Metric: ${status.baseline.metricName ? `${status.baseline.metricName}=${status.baseline.metricValue ?? "not found"}` : "not configured"}`);
|
|
1513
|
+
lines.push(`- Reason: ${status.baseline.verdictReason}`);
|
|
1514
|
+
}
|
|
1515
|
+
if (status.rounds.length > 0) {
|
|
1516
|
+
lines.push("");
|
|
1517
|
+
lines.push("## Rounds");
|
|
1518
|
+
lines.push("");
|
|
1519
|
+
for (const round of status.rounds) {
|
|
1520
|
+
lines.push(`- Round ${round.round}: ${round.sessionId} \xB7 ${round.verdict} \xB7 ${round.metricName ? `${round.metricName}=${round.metricValue ?? "not found"}` : "no metric"} \xB7 ${round.accepted ? "accepted" : "rejected"}`);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return {
|
|
1524
|
+
sessionId: status.sessionId,
|
|
1525
|
+
name: status.metadata.name,
|
|
1526
|
+
status: status.metadata.status,
|
|
1527
|
+
stopReason: status.metadata.stopReason,
|
|
1528
|
+
currentRound: status.metadata.currentRound,
|
|
1529
|
+
maxRounds: status.metadata.maxRounds,
|
|
1530
|
+
noImproveStreak: status.metadata.noImproveStreak,
|
|
1531
|
+
maxNoImproveRounds: status.metadata.maxNoImproveRounds,
|
|
1532
|
+
acceptedRounds: status.metadata.acceptedRoundSessionIds.length,
|
|
1533
|
+
rejectedRounds: status.metadata.rejectedRoundSessionIds.length,
|
|
1534
|
+
bestSessionId: status.metadata.bestSessionId,
|
|
1535
|
+
bestMetricValue: status.metadata.bestMetricValue,
|
|
1536
|
+
metric: status.metadata.metric,
|
|
1537
|
+
objective: status.metadata.objective,
|
|
1538
|
+
targetMetric: status.metadata.targetMetric,
|
|
1539
|
+
baselineSessionId: status.metadata.baselineSessionId,
|
|
1540
|
+
createdAt: status.metadata.createdAt,
|
|
1541
|
+
updatedAt: status.metadata.updatedAt,
|
|
1542
|
+
content: lines.join("\n")
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
|
|
929
1546
|
function printDivider() {
|
|
930
1547
|
console.log(a.dim("\u2500".repeat(60)));
|
|
931
1548
|
}
|
|
@@ -958,11 +1575,56 @@ function getLatestTaskData(status) {
|
|
|
958
1575
|
const latestTask = status.tasks[status.tasks.length - 1];
|
|
959
1576
|
return latestTask?.output?.data || {};
|
|
960
1577
|
}
|
|
1578
|
+
function getLoopOverrides(options) {
|
|
1579
|
+
return {
|
|
1580
|
+
cwd: options.cwd,
|
|
1581
|
+
budgetMs: options.budgetMs,
|
|
1582
|
+
maxRounds: options.maxRounds,
|
|
1583
|
+
maxNoImproveRounds: options.maxNoImproveRounds,
|
|
1584
|
+
targetMetric: options.targetMetric
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function printResearchLoopStatus(status) {
|
|
1588
|
+
console.log("");
|
|
1589
|
+
console.log(a.bold.cyan("\u{1F501} Research Loop Status"));
|
|
1590
|
+
printDivider();
|
|
1591
|
+
console.log(a.gray(`Session: ${status.sessionId}`));
|
|
1592
|
+
console.log(a.gray(`Name: ${status.metadata.name}`));
|
|
1593
|
+
console.log(a.gray(`Status: ${status.metadata.status}`));
|
|
1594
|
+
console.log(a.gray(`Stop: ${status.metadata.stopReason || "running"}`));
|
|
1595
|
+
console.log(a.gray(`Rounds: ${status.metadata.currentRound}/${status.metadata.maxRounds}`));
|
|
1596
|
+
console.log(a.gray(`Objective: ${status.metadata.objective}`));
|
|
1597
|
+
console.log(a.gray(`Metric: ${formatMetric(status.metadata.metric, status.metadata.bestMetricValue)}`));
|
|
1598
|
+
console.log(a.gray(`Baseline: ${status.metadata.baselineSessionId || "none"}`));
|
|
1599
|
+
console.log(a.gray(`Best: ${status.metadata.bestSessionId || "none"}`));
|
|
1600
|
+
console.log(a.gray(`Accepted: ${status.metadata.acceptedRoundSessionIds.length}`));
|
|
1601
|
+
console.log(a.gray(`Rejected: ${status.metadata.rejectedRoundSessionIds.length}`));
|
|
1602
|
+
console.log(a.gray(`Streak: ${status.metadata.noImproveStreak}/${status.metadata.maxNoImproveRounds}`));
|
|
1603
|
+
if (status.latestRound) {
|
|
1604
|
+
console.log(a.gray(`Latest: round ${status.latestRound.round} \xB7 ${status.latestRound.verdict} \xB7 ${formatMetric(status.latestRound.metricName, status.latestRound.metricValue)}`));
|
|
1605
|
+
}
|
|
1606
|
+
console.log("");
|
|
1607
|
+
}
|
|
961
1608
|
async function researchCommand(action, args = [], options = {}) {
|
|
962
1609
|
switch (action) {
|
|
963
1610
|
case "run":
|
|
964
1611
|
await runResearchCommand(options);
|
|
965
1612
|
return;
|
|
1613
|
+
case "init":
|
|
1614
|
+
await initResearchCommand(options);
|
|
1615
|
+
return;
|
|
1616
|
+
case "loop":
|
|
1617
|
+
await startResearchLoopCommand(options);
|
|
1618
|
+
return;
|
|
1619
|
+
case "round":
|
|
1620
|
+
await runResearchRoundCommand(args[0] || void 0, options);
|
|
1621
|
+
return;
|
|
1622
|
+
case "resume":
|
|
1623
|
+
await resumeResearchLoopCommand(args[0] || void 0, options);
|
|
1624
|
+
return;
|
|
1625
|
+
case "stop":
|
|
1626
|
+
await stopResearchLoopCommand(args[0] || void 0, options);
|
|
1627
|
+
return;
|
|
966
1628
|
case "status":
|
|
967
1629
|
await showResearchStatus(args[0] || void 0, options);
|
|
968
1630
|
return;
|
|
@@ -1033,7 +1695,56 @@ async function runResearchCommand(options) {
|
|
|
1033
1695
|
}
|
|
1034
1696
|
console.log("");
|
|
1035
1697
|
}
|
|
1698
|
+
async function initResearchCommand(options) {
|
|
1699
|
+
const { programPath } = initResearchProgram(options.program, options.cwd);
|
|
1700
|
+
console.log("");
|
|
1701
|
+
console.log(a.bold.cyan("\u{1F9ED} Research Program"));
|
|
1702
|
+
printDivider();
|
|
1703
|
+
console.log(a.gray(`Program: ${programPath}`));
|
|
1704
|
+
console.log("");
|
|
1705
|
+
}
|
|
1706
|
+
async function startResearchLoopCommand(options) {
|
|
1707
|
+
const status = await startResearchLoop({
|
|
1708
|
+
programPath: options.program,
|
|
1709
|
+
cwd: options.cwd,
|
|
1710
|
+
dbPath: options.dbPath,
|
|
1711
|
+
overrides: getLoopOverrides(options)
|
|
1712
|
+
});
|
|
1713
|
+
printResearchLoopStatus(status);
|
|
1714
|
+
}
|
|
1715
|
+
async function runResearchRoundCommand(sessionId, options) {
|
|
1716
|
+
const status = await runResearchRound({
|
|
1717
|
+
sessionId,
|
|
1718
|
+
cwd: options.cwd,
|
|
1719
|
+
dbPath: options.dbPath,
|
|
1720
|
+
overrides: getLoopOverrides(options)
|
|
1721
|
+
});
|
|
1722
|
+
printResearchLoopStatus(status);
|
|
1723
|
+
}
|
|
1724
|
+
async function resumeResearchLoopCommand(sessionId, options) {
|
|
1725
|
+
const status = await resumeResearchLoop({
|
|
1726
|
+
sessionId,
|
|
1727
|
+
cwd: options.cwd,
|
|
1728
|
+
dbPath: options.dbPath,
|
|
1729
|
+
overrides: getLoopOverrides(options)
|
|
1730
|
+
});
|
|
1731
|
+
printResearchLoopStatus(status);
|
|
1732
|
+
}
|
|
1733
|
+
async function stopResearchLoopCommand(sessionId, options) {
|
|
1734
|
+
const status = stopResearchLoop({ sessionId, dbPath: options.dbPath });
|
|
1735
|
+
if (!status) {
|
|
1736
|
+
console.log(a.yellow(sessionId ? `Research loop session not found: ${sessionId}` : "No research loop sessions found."));
|
|
1737
|
+
console.log("");
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
printResearchLoopStatus(status);
|
|
1741
|
+
}
|
|
1036
1742
|
async function showResearchStatus(sessionId, options) {
|
|
1743
|
+
const loopStatus = getResearchLoopStatus(sessionId, options.dbPath);
|
|
1744
|
+
if (loopStatus) {
|
|
1745
|
+
printResearchLoopStatus(loopStatus);
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1037
1748
|
const resolvedSessionId = sessionId || getLatestResearchSession(options.dbPath)?.id;
|
|
1038
1749
|
if (!resolvedSessionId) {
|
|
1039
1750
|
console.log(a.yellow("No research sessions found."));
|
|
@@ -1128,6 +1839,15 @@ async function showResearchResults(options) {
|
|
|
1128
1839
|
console.log("");
|
|
1129
1840
|
}
|
|
1130
1841
|
async function showResearchReport(sessionId, options) {
|
|
1842
|
+
const loopReport = getResearchLoopReport(sessionId, options.dbPath);
|
|
1843
|
+
if (loopReport) {
|
|
1844
|
+
console.log("");
|
|
1845
|
+
console.log(a.bold.cyan("\u{1F4DD} Research Loop Report"));
|
|
1846
|
+
printDivider();
|
|
1847
|
+
console.log(loopReport.content);
|
|
1848
|
+
console.log("");
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1131
1851
|
const report = getResearchReport(sessionId, options.dbPath);
|
|
1132
1852
|
if (!report) {
|
|
1133
1853
|
console.log(a.yellow(sessionId ? `Research session not found: ${sessionId}` : "No research sessions found."));
|
|
@@ -1162,14 +1882,21 @@ function showResearchHelp() {
|
|
|
1162
1882
|
console.log("");
|
|
1163
1883
|
console.log(a.bold.cyan("ccjk research"));
|
|
1164
1884
|
printDivider();
|
|
1885
|
+
console.log(" init Create a research program template");
|
|
1886
|
+
console.log(" loop Start a persisted research loop");
|
|
1887
|
+
console.log(" round Run one candidate round for a loop");
|
|
1888
|
+
console.log(" resume Continue a persisted research loop until stop");
|
|
1889
|
+
console.log(" stop Stop a running research loop");
|
|
1165
1890
|
console.log(" run Run a persisted research experiment with optional baseline comparison");
|
|
1166
1891
|
console.log(" status Show the latest or selected research session status");
|
|
1167
1892
|
console.log(" sessions List recent research sessions");
|
|
1168
1893
|
console.log(" results Show recent result rows and the current best run");
|
|
1169
1894
|
console.log(" report Render a compact persisted research report");
|
|
1170
1895
|
console.log("");
|
|
1896
|
+
console.log(a.dim("Example: ccjk research init"));
|
|
1897
|
+
console.log(a.dim("Example: ccjk research loop --program .ccjk/research/program.md"));
|
|
1898
|
+
console.log(a.dim("Example: ccjk research round research-loop-123"));
|
|
1171
1899
|
console.log(a.dim('Example: ccjk research run --name baseline --cmd "python train.py" --metric val_bpb --objective minimize'));
|
|
1172
|
-
console.log(a.dim('Example: ccjk research run --name candidate --cmd "python train.py --lr 1e-4" --metric val_bpb --baseline research-123'));
|
|
1173
1900
|
console.log(a.dim("Example: ccjk research report research-123"));
|
|
1174
1901
|
console.log("");
|
|
1175
1902
|
}
|