superlab 0.1.45 → 0.1.46
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/bin/superlab.cjs +35 -4
- package/lib/auto_contracts.cjs +145 -0
- package/lib/auto_runner.cjs +181 -18
- package/lib/auto_state.cjs +146 -1
- package/lib/install.cjs +8 -2
- package/package-assets/shared/lab/context/auto-mode.md +3 -0
- package/package.json +1 -1
package/bin/superlab.cjs
CHANGED
|
@@ -39,7 +39,7 @@ Usage:
|
|
|
39
39
|
superlab init [--target <dir>] [--platform codex|claude|both|all] [--lang en|zh] [--force]
|
|
40
40
|
superlab install [--target <dir>] [--platform codex|claude|both|all] [--lang en|zh] [--force]
|
|
41
41
|
superlab paper attach-template --path <dir> [--target <dir>]
|
|
42
|
-
superlab auto start [--target <dir>]
|
|
42
|
+
superlab auto start [--target <dir>] [--objective <text>] [--campaign-kind <kind>] [--allowed-stages <csv>]
|
|
43
43
|
superlab auto status [--target <dir>]
|
|
44
44
|
superlab auto stop [--target <dir>]
|
|
45
45
|
superlab update [--target <dir>]
|
|
@@ -222,10 +222,34 @@ function parseAutoArgs(argv) {
|
|
|
222
222
|
if (!["start", "status", "stop"].includes(action || "")) {
|
|
223
223
|
throw new Error(`Unknown auto action: ${action || "(missing)"}`);
|
|
224
224
|
}
|
|
225
|
-
|
|
225
|
+
const options = {
|
|
226
226
|
action,
|
|
227
|
-
|
|
227
|
+
targetDir: process.cwd(),
|
|
228
|
+
requestedObjective: "",
|
|
229
|
+
requestedCampaignKind: "",
|
|
230
|
+
requestedAllowedStages: "",
|
|
228
231
|
};
|
|
232
|
+
|
|
233
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
234
|
+
const value = rest[index];
|
|
235
|
+
if (value === "--target") {
|
|
236
|
+
options.targetDir = path.resolve(rest[index + 1]);
|
|
237
|
+
index += 1;
|
|
238
|
+
} else if (action === "start" && value === "--objective") {
|
|
239
|
+
options.requestedObjective = rest[index + 1] || "";
|
|
240
|
+
index += 1;
|
|
241
|
+
} else if (action === "start" && value === "--campaign-kind") {
|
|
242
|
+
options.requestedCampaignKind = rest[index + 1] || "";
|
|
243
|
+
index += 1;
|
|
244
|
+
} else if (action === "start" && value === "--allowed-stages") {
|
|
245
|
+
options.requestedAllowedStages = rest[index + 1] || "";
|
|
246
|
+
index += 1;
|
|
247
|
+
} else {
|
|
248
|
+
throw new Error(`Unknown option: ${value}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return options;
|
|
229
253
|
}
|
|
230
254
|
|
|
231
255
|
function printVersion(options) {
|
|
@@ -963,7 +987,14 @@ async function main() {
|
|
|
963
987
|
return;
|
|
964
988
|
}
|
|
965
989
|
if (options.action === "start") {
|
|
966
|
-
const result = await startAutoMode({
|
|
990
|
+
const result = await startAutoMode({
|
|
991
|
+
targetDir: options.targetDir,
|
|
992
|
+
requestedContract: {
|
|
993
|
+
objective: options.requestedObjective,
|
|
994
|
+
campaignKind: options.requestedCampaignKind,
|
|
995
|
+
allowedStages: options.requestedAllowedStages,
|
|
996
|
+
},
|
|
997
|
+
});
|
|
967
998
|
const verb = result.status.status === "stopped" ? "stopped" : "completed";
|
|
968
999
|
console.log(`auto mode ${verb} in ${options.targetDir}`);
|
|
969
1000
|
console.log(`objective: ${result.mode.objective}`);
|
package/lib/auto_contracts.cjs
CHANGED
|
@@ -43,6 +43,146 @@ const PROMOTION_CANONICAL_FILES = [
|
|
|
43
43
|
path.join(".lab", "context", "workflow-state.md"),
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
+
function isLocalProcessAlive(ownerId) {
|
|
47
|
+
const pid = parseInteger(ownerId, null);
|
|
48
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
process.kill(pid, 0);
|
|
53
|
+
return true;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error && error.code === "EPERM") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function inferCampaignKind({ campaignKind = "", allowedStages = [] }) {
|
|
63
|
+
if (isMeaningful(campaignKind)) {
|
|
64
|
+
return campaignKind.trim().toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
const stageSet = new Set((allowedStages || []).map((stage) => stage.trim().toLowerCase()).filter(Boolean));
|
|
67
|
+
const hasPlanning = ["idea", "data", "framing", "spec"].some((stage) => stageSet.has(stage));
|
|
68
|
+
const hasExecution = ["run", "iterate"].some((stage) => stageSet.has(stage));
|
|
69
|
+
const hasWriteOnly = stageSet.has("write") && !hasExecution;
|
|
70
|
+
|
|
71
|
+
if (hasPlanning && !hasExecution) {
|
|
72
|
+
return "spec";
|
|
73
|
+
}
|
|
74
|
+
if (hasExecution) {
|
|
75
|
+
return "experiment-loop";
|
|
76
|
+
}
|
|
77
|
+
if (hasWriteOnly || stageSet.has("report")) {
|
|
78
|
+
return "report-polish";
|
|
79
|
+
}
|
|
80
|
+
return "generic";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeRequestedAutoContract(requested = {}) {
|
|
84
|
+
const allowedStages = Array.isArray(requested.allowedStages)
|
|
85
|
+
? requested.allowedStages.map((stage) => String(stage).trim().toLowerCase()).filter(Boolean)
|
|
86
|
+
: normalizeList(requested.allowedStages || "").map((stage) => stage.toLowerCase());
|
|
87
|
+
return {
|
|
88
|
+
objective: (requested.objective || "").trim(),
|
|
89
|
+
campaignKind:
|
|
90
|
+
isMeaningful(requested.campaignKind || "") || allowedStages.length > 0
|
|
91
|
+
? inferCampaignKind({
|
|
92
|
+
campaignKind: requested.campaignKind || "",
|
|
93
|
+
allowedStages,
|
|
94
|
+
})
|
|
95
|
+
: "",
|
|
96
|
+
allowedStages,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sameStageSet(left, right) {
|
|
101
|
+
if (left.length !== right.length) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const leftSet = new Set(left);
|
|
105
|
+
return right.every((value) => leftSet.has(value));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function hasLiveOwner(status, ledger) {
|
|
109
|
+
const normalizedStatus = (status.status || "").trim().toLowerCase();
|
|
110
|
+
const observedState = (ledger.observedState || "").trim().toLowerCase();
|
|
111
|
+
if ((ledger.ownerType || "").trim().toLowerCase() === "local-process" && isLocalProcessAlive(ledger.ownerId)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (["local-runner", "remote-runner"].includes((ledger.ownerType || "").trim().toLowerCase())) {
|
|
115
|
+
return ["running", "retrying", "resuming", "checkpointed"].includes(observedState);
|
|
116
|
+
}
|
|
117
|
+
return normalizedStatus === "running" || ["running", "retrying", "resuming"].includes(observedState);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function classifyAutoContractFit({ mode, status, ledger, requested }) {
|
|
121
|
+
const normalizedRequest = normalizeRequestedAutoContract(requested);
|
|
122
|
+
const requestIsEmpty =
|
|
123
|
+
!isMeaningful(normalizedRequest.objective) &&
|
|
124
|
+
!isMeaningful(normalizedRequest.campaignKind) &&
|
|
125
|
+
normalizedRequest.allowedStages.length === 0;
|
|
126
|
+
const currentCampaignKind = inferCampaignKind(mode);
|
|
127
|
+
if (requestIsEmpty) {
|
|
128
|
+
return {
|
|
129
|
+
classification: "fit",
|
|
130
|
+
reason: "no requested contract override",
|
|
131
|
+
currentCampaignKind,
|
|
132
|
+
requestedCampaignKind: "",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const requestedCampaignKind = normalizedRequest.campaignKind || currentCampaignKind;
|
|
137
|
+
const requestedStages = normalizedRequest.allowedStages.length > 0
|
|
138
|
+
? normalizedRequest.allowedStages
|
|
139
|
+
: mode.allowedStages;
|
|
140
|
+
const stageOutsideCurrentEnvelope = requestedStages.some((stage) => !mode.allowedStages.includes(stage));
|
|
141
|
+
const objectiveDiffers =
|
|
142
|
+
isMeaningful(normalizedRequest.objective) &&
|
|
143
|
+
isMeaningful(mode.objective) &&
|
|
144
|
+
normalizedRequest.objective.trim() !== mode.objective.trim();
|
|
145
|
+
const kindDiffers = requestedCampaignKind !== currentCampaignKind;
|
|
146
|
+
|
|
147
|
+
if (hasLiveOwner(status, ledger) && (kindDiffers || stageOutsideCurrentEnvelope || objectiveDiffers)) {
|
|
148
|
+
return {
|
|
149
|
+
classification: "live-conflict",
|
|
150
|
+
reason: "requested campaign conflicts with a live auto campaign",
|
|
151
|
+
currentCampaignKind,
|
|
152
|
+
requestedCampaignKind,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (kindDiffers || stageOutsideCurrentEnvelope) {
|
|
157
|
+
return {
|
|
158
|
+
classification: "hard-mismatch",
|
|
159
|
+
reason: stageOutsideCurrentEnvelope
|
|
160
|
+
? "requested stages fall outside the current auto-stage envelope"
|
|
161
|
+
: "requested campaign kind differs from the current auto campaign",
|
|
162
|
+
currentCampaignKind,
|
|
163
|
+
requestedCampaignKind,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!sameStageSet(requestedStages, mode.allowedStages) || objectiveDiffers) {
|
|
168
|
+
return {
|
|
169
|
+
classification: "soft-mismatch",
|
|
170
|
+
reason: !sameStageSet(requestedStages, mode.allowedStages)
|
|
171
|
+
? "requested stages differ but stay inside the current envelope"
|
|
172
|
+
: "requested objective differs inside the same campaign family",
|
|
173
|
+
currentCampaignKind,
|
|
174
|
+
requestedCampaignKind,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
classification: "fit",
|
|
180
|
+
reason: "requested campaign matches current auto contract",
|
|
181
|
+
currentCampaignKind,
|
|
182
|
+
requestedCampaignKind,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
46
186
|
function resolveFrozenCoreEntries(rawValue) {
|
|
47
187
|
const normalized = normalizeList(rawValue);
|
|
48
188
|
const paths = new Set();
|
|
@@ -380,9 +520,14 @@ module.exports = {
|
|
|
380
520
|
REVIEW_CONTEXT_FILES,
|
|
381
521
|
VALID_APPROVAL_STATUSES,
|
|
382
522
|
VALID_TERMINAL_GOAL_TYPES,
|
|
523
|
+
classifyAutoContractFit,
|
|
383
524
|
changedSnapshotPaths,
|
|
384
525
|
detectFrozenCoreChanges,
|
|
526
|
+
hasLiveOwner,
|
|
385
527
|
hashPathState,
|
|
528
|
+
inferCampaignKind,
|
|
529
|
+
isLocalProcessAlive,
|
|
530
|
+
normalizeRequestedAutoContract,
|
|
386
531
|
resolveFrozenCoreEntries,
|
|
387
532
|
resolveStageCommand,
|
|
388
533
|
snapshotFrozenCore,
|
package/lib/auto_runner.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
2
3
|
const { spawn } = require("node:child_process");
|
|
3
4
|
const { refreshContext } = require("./context.cjs");
|
|
4
5
|
const { parseEvalProtocol, validateEvalProtocol } = require("./eval_protocol.cjs");
|
|
@@ -9,7 +10,11 @@ const {
|
|
|
9
10
|
sleep,
|
|
10
11
|
} = require("./auto_common.cjs");
|
|
11
12
|
const {
|
|
13
|
+
classifyAutoContractFit,
|
|
12
14
|
detectFrozenCoreChanges,
|
|
15
|
+
inferCampaignKind,
|
|
16
|
+
isLocalProcessAlive,
|
|
17
|
+
normalizeRequestedAutoContract,
|
|
13
18
|
resolveStageCommand,
|
|
14
19
|
snapshotFrozenCore,
|
|
15
20
|
snapshotPaths,
|
|
@@ -28,6 +33,7 @@ const {
|
|
|
28
33
|
readWorkflowLanguage,
|
|
29
34
|
resolveRequiredArtifact,
|
|
30
35
|
writeAutoLedger,
|
|
36
|
+
writeAutoMode,
|
|
31
37
|
writeAutoOutcome,
|
|
32
38
|
writeAutoStatus,
|
|
33
39
|
} = require("./auto_state.cjs");
|
|
@@ -48,22 +54,6 @@ function isStopTransition(value) {
|
|
|
48
54
|
return ["stop", "campaign-stop", "terminal-stop"].includes((value || "").trim().toLowerCase());
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
function isLocalProcessAlive(ownerId) {
|
|
52
|
-
const pid = parseInteger(ownerId, null);
|
|
53
|
-
if (!Number.isInteger(pid) || pid <= 0) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
process.kill(pid, 0);
|
|
58
|
-
return true;
|
|
59
|
-
} catch (error) {
|
|
60
|
-
if (error && error.code === "EPERM") {
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
57
|
function resolveResumePlan({ mode, evalProtocol, status, ledger, now }) {
|
|
68
58
|
const hasLedgerState = [
|
|
69
59
|
ledger.campaignId,
|
|
@@ -143,6 +133,146 @@ function resolveResumePlan({ mode, evalProtocol, status, ledger, now }) {
|
|
|
143
133
|
return { blockingIssue: "", resumePlan: null };
|
|
144
134
|
}
|
|
145
135
|
|
|
136
|
+
function makeCampaignId({ requested, now }) {
|
|
137
|
+
const raw = isMeaningful(requested.campaignKind)
|
|
138
|
+
? `${requested.campaignKind}-${now.toISOString()}`
|
|
139
|
+
: `auto-${now.toISOString()}`;
|
|
140
|
+
return raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function archiveAutoArtifact(targetDir, relativePath, campaignId, now) {
|
|
144
|
+
const absolutePath = path.join(targetDir, relativePath);
|
|
145
|
+
if (!fs.existsSync(absolutePath)) {
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
148
|
+
const archiveDir = path.join(targetDir, ".lab", "context", "archive");
|
|
149
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
150
|
+
const baseName = path.basename(relativePath);
|
|
151
|
+
const archivePath = path.join(
|
|
152
|
+
archiveDir,
|
|
153
|
+
`${now.toISOString().slice(0, 10)}-${campaignId}-${baseName}`
|
|
154
|
+
);
|
|
155
|
+
fs.copyFileSync(absolutePath, archivePath);
|
|
156
|
+
return archivePath;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildRolledOverAutoMode(mode, requested, now) {
|
|
160
|
+
const allowedStages = requested.allowedStages.length > 0 ? requested.allowedStages : mode.allowedStages;
|
|
161
|
+
const campaignKind = requested.campaignKind || inferCampaignKind({ allowedStages });
|
|
162
|
+
return {
|
|
163
|
+
campaignId: makeCampaignId({ requested: { ...requested, campaignKind }, now }),
|
|
164
|
+
campaignKind,
|
|
165
|
+
campaignStartedAt: now.toISOString(),
|
|
166
|
+
objective: requested.objective || mode.objective,
|
|
167
|
+
autonomyLevel: mode.autonomyLevel || "l2",
|
|
168
|
+
approvalStatus: "draft",
|
|
169
|
+
allowedStages,
|
|
170
|
+
successCriteria: "",
|
|
171
|
+
terminalGoalType: "",
|
|
172
|
+
terminalGoalTarget: "",
|
|
173
|
+
requiredTerminalArtifact: "",
|
|
174
|
+
primaryGate: "",
|
|
175
|
+
secondaryGuard: "",
|
|
176
|
+
promotionCondition: "",
|
|
177
|
+
stopReason: "",
|
|
178
|
+
escalationReason: "",
|
|
179
|
+
maxIterations: mode.maxIterations,
|
|
180
|
+
maxWallClockTime: mode.maxWallClockTime,
|
|
181
|
+
maxFailures: mode.maxFailures,
|
|
182
|
+
pollInterval: mode.pollInterval,
|
|
183
|
+
stageCommands: {
|
|
184
|
+
run: "",
|
|
185
|
+
iterate: "",
|
|
186
|
+
review: "",
|
|
187
|
+
report: "",
|
|
188
|
+
write: "",
|
|
189
|
+
},
|
|
190
|
+
successCheckCommand: "",
|
|
191
|
+
stopCheckCommand: mode.stopCheckCommand,
|
|
192
|
+
promotionCheckCommand: "",
|
|
193
|
+
promotionCommand: "",
|
|
194
|
+
promotionPolicy: "",
|
|
195
|
+
frozenCore: mode.frozenCore,
|
|
196
|
+
explorationEnvelope: "",
|
|
197
|
+
stopConditions: "",
|
|
198
|
+
escalationConditions: "",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function rolloverAutoCampaign({ targetDir, mode, status, ledger, requested, lang, now }) {
|
|
203
|
+
const newMode = buildRolledOverAutoMode(mode, requested, now);
|
|
204
|
+
const archivedPaths = [
|
|
205
|
+
archiveAutoArtifact(targetDir, path.join(".lab", "context", "auto-mode.md"), mode.campaignId || "auto-campaign", now),
|
|
206
|
+
archiveAutoArtifact(targetDir, path.join(".lab", "context", "auto-status.md"), mode.campaignId || "auto-campaign", now),
|
|
207
|
+
].filter(Boolean);
|
|
208
|
+
|
|
209
|
+
const hasMeaningfulLedger = [
|
|
210
|
+
ledger.campaignId,
|
|
211
|
+
ledger.ownerType,
|
|
212
|
+
ledger.ownerId,
|
|
213
|
+
ledger.observedState,
|
|
214
|
+
ledger.activeRung,
|
|
215
|
+
].some((value) => isMeaningful(value));
|
|
216
|
+
if (hasMeaningfulLedger) {
|
|
217
|
+
const archivedLedger = archiveAutoArtifact(
|
|
218
|
+
targetDir,
|
|
219
|
+
path.join(".lab", "context", "auto-ledger.md"),
|
|
220
|
+
mode.campaignId || "auto-campaign",
|
|
221
|
+
now
|
|
222
|
+
);
|
|
223
|
+
if (archivedLedger) {
|
|
224
|
+
archivedPaths.push(archivedLedger);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
writeAutoMode(targetDir, newMode, { lang });
|
|
229
|
+
writeAutoStatus(
|
|
230
|
+
targetDir,
|
|
231
|
+
{
|
|
232
|
+
status: "idle",
|
|
233
|
+
currentStage: "",
|
|
234
|
+
currentCommand: "",
|
|
235
|
+
activeRunId: "",
|
|
236
|
+
iterationCount: "0",
|
|
237
|
+
currentRung: "",
|
|
238
|
+
watchTarget: "",
|
|
239
|
+
nextRung: "",
|
|
240
|
+
startedAt: "",
|
|
241
|
+
lastHeartbeat: now.toISOString(),
|
|
242
|
+
lastCheckpoint: "",
|
|
243
|
+
lastSummary: `rolled over from previous auto campaign${archivedPaths.length > 0 ? `; archived: ${archivedPaths.join(", ")}` : ""}`,
|
|
244
|
+
decision: "fill the new auto-mode contract and approve it before starting the next campaign",
|
|
245
|
+
},
|
|
246
|
+
{ lang }
|
|
247
|
+
);
|
|
248
|
+
writeAutoLedger(
|
|
249
|
+
targetDir,
|
|
250
|
+
{
|
|
251
|
+
campaignId: newMode.campaignId,
|
|
252
|
+
objective: newMode.objective,
|
|
253
|
+
activeStage: "",
|
|
254
|
+
activeRung: "",
|
|
255
|
+
ownerType: "",
|
|
256
|
+
ownerId: "",
|
|
257
|
+
command: "",
|
|
258
|
+
watchTarget: "",
|
|
259
|
+
startedAt: "",
|
|
260
|
+
lastObservedAt: now.toISOString(),
|
|
261
|
+
observedState: "draft",
|
|
262
|
+
lastCheckpoint: "",
|
|
263
|
+
checkpointSummary: "new campaign draft created by controlled rollover",
|
|
264
|
+
nextTransition: "",
|
|
265
|
+
continueBoundary: "Fill and approve the new contract before starting the campaign.",
|
|
266
|
+
stopBoundary: "",
|
|
267
|
+
escalationBoundary: "",
|
|
268
|
+
requiredReadSet: ".lab/context/eval-protocol.md, .lab/context/auto-mode.md, .lab/context/auto-status.md, .lab/context/auto-ledger.md, .lab/context/auto-outcome.md",
|
|
269
|
+
resumeCommand: "",
|
|
270
|
+
},
|
|
271
|
+
{ lang }
|
|
272
|
+
);
|
|
273
|
+
return { newMode, archivedPaths };
|
|
274
|
+
}
|
|
275
|
+
|
|
146
276
|
async function runCommandWithPolling({
|
|
147
277
|
targetDir,
|
|
148
278
|
stage,
|
|
@@ -333,17 +463,51 @@ async function evaluateTerminalGoal({ mode, iteration, targetDir, deadlineMs })
|
|
|
333
463
|
};
|
|
334
464
|
}
|
|
335
465
|
|
|
336
|
-
async function startAutoMode({ targetDir, now = new Date() }) {
|
|
466
|
+
async function startAutoMode({ targetDir, now = new Date(), requestedContract = null }) {
|
|
337
467
|
const mode = parseAutoMode(targetDir);
|
|
338
468
|
const existingStatus = parseAutoStatus(targetDir);
|
|
339
469
|
const existingLedger = parseAutoLedger(targetDir);
|
|
340
470
|
const evalProtocol = parseEvalProtocol(targetDir);
|
|
471
|
+
const lang = readWorkflowLanguage(targetDir);
|
|
341
472
|
const missingSchemaFields = listMissingCurrentAutoModeFields(mode);
|
|
342
473
|
if (missingSchemaFields.length > 0) {
|
|
343
474
|
throw new Error(
|
|
344
475
|
`auto-mode.md is missing current contract fields: ${missingSchemaFields.join(", ")}; run \`superlab update --target ${targetDir}\` to apply the managed schema migration, then fill the new fields before starting auto mode`
|
|
345
476
|
);
|
|
346
477
|
}
|
|
478
|
+
if (requestedContract) {
|
|
479
|
+
const fit = classifyAutoContractFit({
|
|
480
|
+
mode,
|
|
481
|
+
status: existingStatus,
|
|
482
|
+
ledger: existingLedger,
|
|
483
|
+
requested: requestedContract,
|
|
484
|
+
});
|
|
485
|
+
if (fit.classification === "live-conflict") {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`requested auto campaign conflicts with the current live campaign; stop the live campaign before rolling over (current: ${fit.currentCampaignKind}, requested: ${fit.requestedCampaignKind})`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (fit.classification === "hard-mismatch") {
|
|
491
|
+
const normalizedRequest = normalizeRequestedAutoContract(requestedContract);
|
|
492
|
+
const { newMode, archivedPaths } = rolloverAutoCampaign({
|
|
493
|
+
targetDir,
|
|
494
|
+
mode,
|
|
495
|
+
status: existingStatus,
|
|
496
|
+
ledger: existingLedger,
|
|
497
|
+
requested: normalizedRequest,
|
|
498
|
+
lang,
|
|
499
|
+
now,
|
|
500
|
+
});
|
|
501
|
+
throw new Error(
|
|
502
|
+
`current auto contract does not fit the requested campaign and was rolled over to a new draft (${newMode.campaignId}); archived old campaign files: ${archivedPaths.join(", ")}; fill the new contract fields and approve it before starting auto mode`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
if (fit.classification === "soft-mismatch") {
|
|
506
|
+
throw new Error(
|
|
507
|
+
`current auto contract only partially fits the requested campaign: ${fit.reason}; update auto-mode.md or narrow the request before starting auto mode`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
347
511
|
const issues = validateAutoMode(mode, null, evalProtocol);
|
|
348
512
|
if (issues.length > 0) {
|
|
349
513
|
throw new Error(issues.join(" | "));
|
|
@@ -366,7 +530,6 @@ async function startAutoMode({ targetDir, now = new Date() }) {
|
|
|
366
530
|
throw new Error(blockingIssue);
|
|
367
531
|
}
|
|
368
532
|
|
|
369
|
-
const lang = readWorkflowLanguage(targetDir);
|
|
370
533
|
const timestamp = now.toISOString();
|
|
371
534
|
const status = {
|
|
372
535
|
status: "running",
|
package/lib/auto_state.cjs
CHANGED
|
@@ -16,6 +16,9 @@ function parseAutoMode(targetDir) {
|
|
|
16
16
|
return {
|
|
17
17
|
path: contextFile(targetDir, "auto-mode.md"),
|
|
18
18
|
text,
|
|
19
|
+
campaignId: extractValue(text, ["Campaign id", "Campaign ID", "活动 id"]),
|
|
20
|
+
campaignKind: normalizeScalar(extractValue(text, ["Campaign kind", "活动类型"])),
|
|
21
|
+
campaignStartedAt: extractValue(text, ["Campaign started at", "活动开始时间"]),
|
|
19
22
|
objective: extractValue(text, ["Objective", "目标"]),
|
|
20
23
|
autonomyLevel: normalizeScalar(extractValue(text, ["Autonomy level", "自治级别"])),
|
|
21
24
|
approvalStatus: normalizeScalar(extractValue(text, ["Approval status", "批准状态"])),
|
|
@@ -52,7 +55,10 @@ function parseAutoMode(targetDir) {
|
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
const
|
|
58
|
+
const CURRENT_AUTO_MODE_MIGRATION_FIELDS = [
|
|
59
|
+
["Campaign id", "campaignId"],
|
|
60
|
+
["Campaign kind", "campaignKind"],
|
|
61
|
+
["Campaign started at", "campaignStartedAt"],
|
|
56
62
|
["Autonomy level", "autonomyLevel"],
|
|
57
63
|
["Approval status", "approvalStatus"],
|
|
58
64
|
["Terminal goal type", "terminalGoalType"],
|
|
@@ -65,12 +71,22 @@ const CURRENT_AUTO_MODE_SCHEMA_FIELDS = [
|
|
|
65
71
|
["Escalation reason", "escalationReason"],
|
|
66
72
|
];
|
|
67
73
|
|
|
74
|
+
const CURRENT_AUTO_MODE_SCHEMA_FIELDS = CURRENT_AUTO_MODE_MIGRATION_FIELDS.filter(
|
|
75
|
+
([label]) => !["Campaign id", "Campaign kind", "Campaign started at"].includes(label)
|
|
76
|
+
);
|
|
77
|
+
|
|
68
78
|
function listMissingCurrentAutoModeFields(mode) {
|
|
69
79
|
return CURRENT_AUTO_MODE_SCHEMA_FIELDS
|
|
70
80
|
.filter(([, key]) => !isMeaningful(mode[key]))
|
|
71
81
|
.map(([label]) => label);
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
function listMissingMigratedAutoModeFields(mode) {
|
|
85
|
+
return CURRENT_AUTO_MODE_MIGRATION_FIELDS
|
|
86
|
+
.filter(([, key]) => !isMeaningful(mode[key]))
|
|
87
|
+
.map(([label]) => label);
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
function parseAutoStatus(targetDir) {
|
|
75
91
|
const text = readFileIfExists(contextFile(targetDir, "auto-status.md"));
|
|
76
92
|
return {
|
|
@@ -392,18 +408,147 @@ function resolveRequiredArtifact(targetDir, configuredPath) {
|
|
|
392
408
|
};
|
|
393
409
|
}
|
|
394
410
|
|
|
411
|
+
function renderAutoModeContract(mode, { lang = "en" } = {}) {
|
|
412
|
+
const allowedStages = Array.isArray(mode.allowedStages) ? mode.allowedStages.join(", ") : (mode.allowedStages || "");
|
|
413
|
+
if (lang === "zh") {
|
|
414
|
+
return `# 自动模式契约
|
|
415
|
+
|
|
416
|
+
用这个文件定义 \`/lab:auto\` 的有边界自治执行范围。
|
|
417
|
+
|
|
418
|
+
## 目标
|
|
419
|
+
|
|
420
|
+
- Campaign id: ${mode.campaignId || ""}
|
|
421
|
+
- 活动类型: ${mode.campaignKind || ""}
|
|
422
|
+
- 活动开始时间: ${mode.campaignStartedAt || ""}
|
|
423
|
+
- Objective: ${mode.objective || ""}
|
|
424
|
+
- 自治级别: ${mode.autonomyLevel || "L2"}
|
|
425
|
+
- 批准状态: ${mode.approvalStatus || "draft"}
|
|
426
|
+
- 允许阶段: ${allowedStages}
|
|
427
|
+
- 成功标准: ${mode.successCriteria || ""}
|
|
428
|
+
- 终止目标类型: ${mode.terminalGoalType || ""}
|
|
429
|
+
- 终止目标目标值: ${mode.terminalGoalTarget || ""}
|
|
430
|
+
- 终止目标工件: ${mode.requiredTerminalArtifact || ""}
|
|
431
|
+
- 主 gate: ${mode.primaryGate || ""}
|
|
432
|
+
- 次级 guard: ${mode.secondaryGuard || ""}
|
|
433
|
+
- 升格条件: ${mode.promotionCondition || ""}
|
|
434
|
+
- 停止原因: ${mode.stopReason || ""}
|
|
435
|
+
- 升级原因: ${mode.escalationReason || ""}
|
|
436
|
+
|
|
437
|
+
## 循环预算
|
|
438
|
+
|
|
439
|
+
- Max iterations: ${mode.maxIterations || ""}
|
|
440
|
+
- Max wall-clock time: ${mode.maxWallClockTime || ""}
|
|
441
|
+
- Max failures: ${mode.maxFailures || ""}
|
|
442
|
+
- Poll interval: ${mode.pollInterval || ""}
|
|
443
|
+
|
|
444
|
+
## 阶段命令
|
|
445
|
+
|
|
446
|
+
- Run command: ${mode.stageCommands?.run || ""}
|
|
447
|
+
- Iterate command: ${mode.stageCommands?.iterate || ""}
|
|
448
|
+
- Review command: ${mode.stageCommands?.review || ""}
|
|
449
|
+
- Report command: ${mode.stageCommands?.report || ""}
|
|
450
|
+
- Write command: ${mode.stageCommands?.write || ""}
|
|
451
|
+
- Success check command: ${mode.successCheckCommand || ""}
|
|
452
|
+
- Stop check command: ${mode.stopCheckCommand || ""}
|
|
453
|
+
- Promotion check command: ${mode.promotionCheckCommand || ""}
|
|
454
|
+
- Promotion command: ${mode.promotionCommand || ""}
|
|
455
|
+
|
|
456
|
+
## 升格策略
|
|
457
|
+
|
|
458
|
+
- Promotion policy: ${mode.promotionPolicy || ""}
|
|
459
|
+
|
|
460
|
+
## 边界
|
|
461
|
+
|
|
462
|
+
- Frozen core: ${mode.frozenCore || ""}
|
|
463
|
+
- Exploration envelope: ${mode.explorationEnvelope || ""}
|
|
464
|
+
|
|
465
|
+
## 停止条件
|
|
466
|
+
|
|
467
|
+
- Stop conditions: ${mode.stopConditions || ""}
|
|
468
|
+
- Escalation conditions: ${mode.escalationConditions || ""}
|
|
469
|
+
`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return `# Auto Mode Contract
|
|
473
|
+
|
|
474
|
+
Use this file to define the bounded autonomous execution envelope for \`/lab:auto\`.
|
|
475
|
+
|
|
476
|
+
## Objective
|
|
477
|
+
|
|
478
|
+
- Campaign id: ${mode.campaignId || ""}
|
|
479
|
+
- Campaign kind: ${mode.campaignKind || ""}
|
|
480
|
+
- Campaign started at: ${mode.campaignStartedAt || ""}
|
|
481
|
+
- Objective: ${mode.objective || ""}
|
|
482
|
+
- Autonomy level: ${mode.autonomyLevel || "L2"}
|
|
483
|
+
- Approval status: ${mode.approvalStatus || "draft"}
|
|
484
|
+
- Allowed stages: ${allowedStages}
|
|
485
|
+
- Success criteria: ${mode.successCriteria || ""}
|
|
486
|
+
- Terminal goal type: ${mode.terminalGoalType || ""}
|
|
487
|
+
- Terminal goal target: ${mode.terminalGoalTarget || ""}
|
|
488
|
+
- Required terminal artifact: ${mode.requiredTerminalArtifact || ""}
|
|
489
|
+
- Primary gate: ${mode.primaryGate || ""}
|
|
490
|
+
- Secondary guard: ${mode.secondaryGuard || ""}
|
|
491
|
+
- Promotion condition: ${mode.promotionCondition || ""}
|
|
492
|
+
- Stop reason: ${mode.stopReason || ""}
|
|
493
|
+
- Escalation reason: ${mode.escalationReason || ""}
|
|
494
|
+
|
|
495
|
+
## Loop Budget
|
|
496
|
+
|
|
497
|
+
- Max iterations: ${mode.maxIterations || ""}
|
|
498
|
+
- Max wall-clock time: ${mode.maxWallClockTime || ""}
|
|
499
|
+
- Max failures: ${mode.maxFailures || ""}
|
|
500
|
+
- Poll interval: ${mode.pollInterval || ""}
|
|
501
|
+
|
|
502
|
+
## Stage Commands
|
|
503
|
+
|
|
504
|
+
- Run command: ${mode.stageCommands?.run || ""}
|
|
505
|
+
- Iterate command: ${mode.stageCommands?.iterate || ""}
|
|
506
|
+
- Review command: ${mode.stageCommands?.review || ""}
|
|
507
|
+
- Report command: ${mode.stageCommands?.report || ""}
|
|
508
|
+
- Write command: ${mode.stageCommands?.write || ""}
|
|
509
|
+
- Success check command: ${mode.successCheckCommand || ""}
|
|
510
|
+
- Stop check command: ${mode.stopCheckCommand || ""}
|
|
511
|
+
- Promotion check command: ${mode.promotionCheckCommand || ""}
|
|
512
|
+
- Promotion command: ${mode.promotionCommand || ""}
|
|
513
|
+
|
|
514
|
+
## Promotion Policy
|
|
515
|
+
|
|
516
|
+
- Promotion policy: ${mode.promotionPolicy || ""}
|
|
517
|
+
|
|
518
|
+
## Boundaries
|
|
519
|
+
|
|
520
|
+
- Frozen core: ${mode.frozenCore || ""}
|
|
521
|
+
- Exploration envelope: ${mode.explorationEnvelope || ""}
|
|
522
|
+
|
|
523
|
+
## Stop Conditions
|
|
524
|
+
|
|
525
|
+
- Stop conditions: ${mode.stopConditions || ""}
|
|
526
|
+
- Escalation conditions: ${mode.escalationConditions || ""}
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function writeAutoMode(targetDir, mode, { lang = "en" } = {}) {
|
|
531
|
+
const filePath = contextFile(targetDir, "auto-mode.md");
|
|
532
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
533
|
+
fs.writeFileSync(filePath, renderAutoModeContract(mode, { lang }).trimEnd() + "\n");
|
|
534
|
+
}
|
|
535
|
+
|
|
395
536
|
module.exports = {
|
|
537
|
+
CURRENT_AUTO_MODE_MIGRATION_FIELDS,
|
|
396
538
|
CURRENT_AUTO_MODE_SCHEMA_FIELDS,
|
|
397
539
|
listMissingCurrentAutoModeFields,
|
|
540
|
+
listMissingMigratedAutoModeFields,
|
|
398
541
|
parseAutoLedger,
|
|
399
542
|
parseAutoMode,
|
|
400
543
|
parseAutoStatus,
|
|
401
544
|
readWorkflowLanguage,
|
|
402
545
|
renderAutoLedger,
|
|
546
|
+
renderAutoModeContract,
|
|
403
547
|
renderAutoOutcome,
|
|
404
548
|
renderAutoStatus,
|
|
405
549
|
resolveRequiredArtifact,
|
|
406
550
|
writeAutoLedger,
|
|
551
|
+
writeAutoMode,
|
|
407
552
|
writeAutoOutcome,
|
|
408
553
|
writeAutoStatus,
|
|
409
554
|
};
|
package/lib/install.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const os = require("node:os");
|
|
3
3
|
const path = require("node:path");
|
|
4
|
-
const {
|
|
4
|
+
const { CURRENT_AUTO_MODE_MIGRATION_FIELDS } = require("./auto_state.cjs");
|
|
5
5
|
const { getLocalizedContent } = require("./i18n.cjs");
|
|
6
6
|
|
|
7
7
|
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
@@ -156,6 +156,9 @@ function installLabAssets(targetDir, force) {
|
|
|
156
156
|
function autoModeSchemaFieldLine(label, lang) {
|
|
157
157
|
if (lang === "zh") {
|
|
158
158
|
const zhDefaults = {
|
|
159
|
+
"Campaign id": "- Campaign id: ",
|
|
160
|
+
"Campaign kind": "- 活动类型: ",
|
|
161
|
+
"Campaign started at": "- 活动开始时间: ",
|
|
159
162
|
"Autonomy level": "- 自治级别: L2",
|
|
160
163
|
"Approval status": "- 批准状态: draft",
|
|
161
164
|
"Terminal goal type": "- 终止目标类型: ",
|
|
@@ -171,6 +174,9 @@ function autoModeSchemaFieldLine(label, lang) {
|
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
const defaults = {
|
|
177
|
+
"Campaign id": "- Campaign id: ",
|
|
178
|
+
"Campaign kind": "- Campaign kind: ",
|
|
179
|
+
"Campaign started at": "- Campaign started at: ",
|
|
174
180
|
"Autonomy level": "- Autonomy level: L2",
|
|
175
181
|
"Approval status": "- Approval status: draft",
|
|
176
182
|
"Terminal goal type": "- Terminal goal type: ",
|
|
@@ -192,7 +198,7 @@ function migrateAutoModeContext(targetDir, lang) {
|
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
const existing = fs.readFileSync(filePath, "utf8");
|
|
195
|
-
const missing =
|
|
201
|
+
const missing = CURRENT_AUTO_MODE_MIGRATION_FIELDS
|
|
196
202
|
.map(([label]) => label)
|
|
197
203
|
.filter((label) => {
|
|
198
204
|
const localizedLabel = autoModeSchemaFieldLine(label, lang).replace(/^- /, "").split(":")[0];
|
|
@@ -7,6 +7,9 @@ Use `.lab/context/auto-ledger.md` as the live runtime ledger for ownership, chec
|
|
|
7
7
|
|
|
8
8
|
## Objective
|
|
9
9
|
|
|
10
|
+
- Campaign id:
|
|
11
|
+
- Campaign kind:
|
|
12
|
+
- Campaign started at:
|
|
10
13
|
- Objective:
|
|
11
14
|
- Autonomy level: L2
|
|
12
15
|
- Autonomy level controls execution privilege, not paper layer or table number.
|