agent-work-loop 0.0.0 → 0.6.23

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.
Files changed (54) hide show
  1. package/README.md +272 -12
  2. package/dist/brief-Z3JKXEUP.js +181 -0
  3. package/dist/changelog-R7BNBF2C.js +62 -0
  4. package/dist/chunk-46HZN6UB.js +446 -0
  5. package/dist/chunk-4OCSYHYB.js +274 -0
  6. package/dist/chunk-6E7XEQOH.js +27 -0
  7. package/dist/chunk-7SYRDDTX.js +516 -0
  8. package/dist/chunk-BUWGQVHT.js +1243 -0
  9. package/dist/chunk-C7BR2DCS.js +96 -0
  10. package/dist/chunk-D5OINC3G.js +52 -0
  11. package/dist/chunk-DP4O5ME2.js +307 -0
  12. package/dist/chunk-F5LHXBH7.js +209 -0
  13. package/dist/chunk-G5LAJ5TV.js +453 -0
  14. package/dist/chunk-I77CXOEX.js +693 -0
  15. package/dist/chunk-IMB46O6S.js +286 -0
  16. package/dist/chunk-IXMAFR4Y.js +771 -0
  17. package/dist/chunk-QE2CLNBG.js +347 -0
  18. package/dist/chunk-UOPWVM2H.js +727 -0
  19. package/dist/chunk-YTAHVR4P.js +166 -0
  20. package/dist/chunk-ZE6HXOYG.js +904 -0
  21. package/dist/cli.js +374 -13
  22. package/dist/commit-APXIVOSD.js +411 -0
  23. package/dist/config-TFMW7O4T.js +34 -0
  24. package/dist/doctor-SSKNLPGH.js +29 -0
  25. package/dist/evolve-QPD7TWGO.js +38 -0
  26. package/dist/feedback-KAXNFMUY.js +125 -0
  27. package/dist/gotchas-MCA5Y76R.js +43 -0
  28. package/dist/hold-recheck-WN5EG7HD.js +133 -0
  29. package/dist/init-UDM5AXKI.js +79 -0
  30. package/dist/lane-DAZISODH.js +41 -0
  31. package/dist/loop-summary-XAI6KOGB.js +361 -0
  32. package/dist/metrics-WLRZZRTK.js +25 -0
  33. package/dist/record-UKDIUJ5T.js +68 -0
  34. package/dist/review-ZTHDJ47V.js +118 -0
  35. package/dist/rules-R2UZPIVW.js +33 -0
  36. package/dist/state-XM7NZ2HA.js +37 -0
  37. package/dist/status-L6U5KO6T.js +40 -0
  38. package/dist/uninstall-5DFEOFL5.js +545 -0
  39. package/dist/update-AYTBYAHI.js +61 -0
  40. package/dist/verify-L7ARTK42.js +37 -0
  41. package/dist/version-check-LKGU2DNF.js +14 -0
  42. package/dist/work-KEPTGZ6H.js +50 -0
  43. package/engine/skills/claude/awl-loop/SKILL.md +292 -0
  44. package/engine/skills/claude/awl-loop/reference.md +131 -0
  45. package/engine/skills/claude/awl-pipeline/SKILL.md +89 -0
  46. package/engine/skills/claude/awl-pipeline-exec/SKILL.md +200 -0
  47. package/engine/skills/claude/awl-pipeline-plan/SKILL.md +71 -0
  48. package/engine/skills/claude/awl-pipeline-review/SKILL.md +149 -0
  49. package/engine/skills/codex/AGENTS.awl.md +117 -0
  50. package/engine/templates/block-publish.mjs +9 -0
  51. package/engine/templates/pre-push.sample +7 -0
  52. package/engine/templates/related-cmd-examples.md +37 -0
  53. package/engine/version.json +2 -2
  54. package/package.json +10 -4
@@ -0,0 +1,453 @@
1
+ import {
2
+ computeDurationMs
3
+ } from "./chunk-F5LHXBH7.js";
4
+ import {
5
+ computeCoverage,
6
+ readRecords
7
+ } from "./chunk-I77CXOEX.js";
8
+ import {
9
+ loadState
10
+ } from "./chunk-4OCSYHYB.js";
11
+ import {
12
+ requireConfig
13
+ } from "./chunk-UOPWVM2H.js";
14
+ import {
15
+ caps,
16
+ generationsDir,
17
+ gotchasDir,
18
+ legacyDeltasDir,
19
+ lockFile,
20
+ makeColors,
21
+ signal
22
+ } from "./chunk-7SYRDDTX.js";
23
+
24
+ // src/commands/evolve.ts
25
+ import fs from "fs";
26
+ import path from "path";
27
+ function acquireLock() {
28
+ const p = lockFile();
29
+ try {
30
+ fs.mkdirSync(path.dirname(p), { recursive: true });
31
+ const fd = fs.openSync(p, "wx");
32
+ fs.writeSync(fd, JSON.stringify({ pid: process.pid }));
33
+ fs.closeSync(fd);
34
+ return true;
35
+ } catch (e) {
36
+ if (e.code === "EEXIST") {
37
+ return false;
38
+ }
39
+ throw e;
40
+ }
41
+ }
42
+ function releaseLock() {
43
+ try {
44
+ fs.unlinkSync(lockFile());
45
+ } catch {
46
+ }
47
+ }
48
+ var GOTCHA_RELATION_TYPES = ["duplicates", "refines", "supersedes"];
49
+ function normalizeRelations(raw) {
50
+ if (raw === void 0 || raw === null) {
51
+ return { ok: true };
52
+ }
53
+ if (!Array.isArray(raw)) {
54
+ return { ok: false, error: "relations \uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4." };
55
+ }
56
+ const out = [];
57
+ for (const item of raw) {
58
+ if (typeof item !== "object" || item === null) {
59
+ return { ok: false, error: "relations \uD56D\uBAA9\uC740 {type,target} \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4." };
60
+ }
61
+ const r = item;
62
+ if (typeof r.type !== "string" || !GOTCHA_RELATION_TYPES.includes(r.type)) {
63
+ return {
64
+ ok: false,
65
+ error: `relations.type \uC740 ${GOTCHA_RELATION_TYPES.join("/")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.`
66
+ };
67
+ }
68
+ if (typeof r.target !== "string" || r.target.trim() === "") {
69
+ return { ok: false, error: "relations.target(\uB300\uC0C1 gotcha id)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4." };
70
+ }
71
+ out.push({ type: r.type, target: r.target });
72
+ }
73
+ return { ok: true, relations: out.length > 0 ? out : void 0 };
74
+ }
75
+ function remapDeltaId(id) {
76
+ if (typeof id !== "string") {
77
+ return id;
78
+ }
79
+ const m = /^D-(\d+)$/.exec(id);
80
+ return m ? `G-${m[1]}` : id;
81
+ }
82
+ function migrateDeltasToGotchas() {
83
+ if (fs.existsSync(gotchasDir())) {
84
+ return { migrated: false, count: 0 };
85
+ }
86
+ const dDir = legacyDeltasDir();
87
+ let files;
88
+ try {
89
+ files = fs.readdirSync(dDir).filter((f) => f.endsWith(".json"));
90
+ } catch {
91
+ return { migrated: false, count: 0 };
92
+ }
93
+ if (files.length === 0) {
94
+ return { migrated: false, count: 0 };
95
+ }
96
+ const backupDir = `${dDir}.backup-${Date.now()}`;
97
+ fs.mkdirSync(backupDir, { recursive: true });
98
+ for (const f of files) {
99
+ fs.copyFileSync(path.join(dDir, f), path.join(backupDir, f));
100
+ }
101
+ fs.mkdirSync(gotchasDir(), { recursive: true });
102
+ let count = 0;
103
+ for (const f of files) {
104
+ try {
105
+ const raw = JSON.parse(fs.readFileSync(path.join(dDir, f), "utf8"));
106
+ const migrated = {
107
+ ...raw,
108
+ id: remapDeltaId(raw.id),
109
+ ...raw.sameAs !== void 0 ? { sameAs: remapDeltaId(raw.sameAs) } : {}
110
+ };
111
+ fs.writeFileSync(
112
+ path.join(gotchasDir(), `${migrated.id}.json`),
113
+ `${JSON.stringify(migrated, null, 2)}
114
+ `
115
+ );
116
+ count += 1;
117
+ } catch {
118
+ }
119
+ }
120
+ return { migrated: true, count, backupDir };
121
+ }
122
+ function loadGotchaList() {
123
+ migrateDeltasToGotchas();
124
+ let files;
125
+ try {
126
+ files = fs.readdirSync(gotchasDir()).filter((f) => f.endsWith(".json"));
127
+ } catch {
128
+ return [];
129
+ }
130
+ const out = [];
131
+ for (const f of files.sort()) {
132
+ try {
133
+ out.push(JSON.parse(fs.readFileSync(path.join(gotchasDir(), f), "utf8")));
134
+ } catch {
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+ function writeGotcha(g) {
140
+ fs.mkdirSync(gotchasDir(), { recursive: true });
141
+ fs.writeFileSync(path.join(gotchasDir(), `${g.id}.json`), `${JSON.stringify(g, null, 2)}
142
+ `);
143
+ }
144
+ function nextGotchaId(gotchas) {
145
+ let max = 0;
146
+ for (const g of gotchas) {
147
+ const m = /^G-(\d+)$/.exec(g.id);
148
+ if (m?.[1]) {
149
+ max = Math.max(max, Number(m[1]));
150
+ }
151
+ }
152
+ return `G-${String(max + 1).padStart(3, "0")}`;
153
+ }
154
+ function gotchaCluster(seedId, gotchas, maxHops = Number.POSITIVE_INFINITY) {
155
+ const byId = new Map(gotchas.map((g) => [g.id, g]));
156
+ const adj = /* @__PURE__ */ new Map();
157
+ const link = (a, b) => {
158
+ if (!adj.has(a)) adj.set(a, /* @__PURE__ */ new Set());
159
+ if (!adj.has(b)) adj.set(b, /* @__PURE__ */ new Set());
160
+ adj.get(a)?.add(b);
161
+ adj.get(b)?.add(a);
162
+ };
163
+ for (const g of gotchas) {
164
+ if (g.sameAs) link(g.id, g.sameAs);
165
+ for (const r of g.relations ?? []) link(g.id, r.target);
166
+ }
167
+ const visited = /* @__PURE__ */ new Set([seedId]);
168
+ let frontier = [seedId];
169
+ let hop = 0;
170
+ while (frontier.length > 0 && hop < maxHops) {
171
+ const next = [];
172
+ for (const id of frontier) {
173
+ for (const nb of adj.get(id) ?? []) {
174
+ if (!visited.has(nb)) {
175
+ visited.add(nb);
176
+ next.push(nb);
177
+ }
178
+ }
179
+ }
180
+ frontier = next;
181
+ hop += 1;
182
+ }
183
+ visited.delete(seedId);
184
+ const out = [];
185
+ for (const id of visited) {
186
+ const g = byId.get(id);
187
+ if (g) out.push(g);
188
+ }
189
+ return out;
190
+ }
191
+ function gotchasBySource(workitem, gotchas) {
192
+ return gotchas.filter((g) => {
193
+ const src = g.source;
194
+ return src !== void 0 && src.workitem === workitem;
195
+ });
196
+ }
197
+ function collectEvolve(project, workitem, state, scope) {
198
+ const records = readRecords({ ...workitem ? { workitem } : {}, ...scope ?? {} });
199
+ const blocked = records.filter((r) => r.type === "blocked");
200
+ const reviews = records.filter((r) => r.type === "review");
201
+ const retried = records.filter(
202
+ (r) => r.type === "attempt" && typeof r.attempt === "number" && r.attempt >= 2
203
+ );
204
+ const gotchaApplied = records.filter((r) => r.type === "gotcha-applied").length;
205
+ const gotchaMissed = records.filter((r) => r.type === "gotcha-missed").length;
206
+ const refactorCount = records.filter((r) => r.type === "refactor").length;
207
+ const awlFeedbackRecords = records.filter((r) => r.type === "awl-feedback");
208
+ const criteria = Array.isArray(state.criteria) ? state.criteria : [];
209
+ const num = (v) => typeof v === "number" ? v : 0;
210
+ const criteriaTotal = criteria.length;
211
+ const attemptsSum = criteria.reduce((s, c) => s + num(c.attempts), 0);
212
+ const blockedCount = criteria.filter((c) => c.status === "blocked").length;
213
+ const proceduralErrors = criteria.reduce((s, c) => s + num(c.proceduralErrors), 0);
214
+ const allGotchas = loadGotchaList();
215
+ const existingGotchas = allGotchas.map((g) => ({
216
+ id: g.id,
217
+ lesson: g.lesson,
218
+ count: g.count
219
+ }));
220
+ const relatedIds = /* @__PURE__ */ new Set();
221
+ if (workitem) {
222
+ for (const g of gotchasBySource(workitem, allGotchas)) {
223
+ relatedIds.add(g.id);
224
+ for (const rel of gotchaCluster(g.id, allGotchas)) {
225
+ relatedIds.add(rel.id);
226
+ }
227
+ }
228
+ }
229
+ const relatedGotchas = allGotchas.filter((g) => relatedIds.has(g.id)).map((g) => ({ id: g.id, lesson: g.lesson, count: g.count }));
230
+ const auditRecords = records.filter((r) => r.type === "audit");
231
+ const criteriaRecords = records.filter((r) => r.type === "criteria");
232
+ const gate1 = records.find((r) => r.type === "gate" && r.gate === 1);
233
+ const coverage = computeCoverage(auditRecords, criteria, criteriaRecords);
234
+ const coverageMetrics = {
235
+ auditFindingsTotal: coverage.auditFindingIds.length,
236
+ addressed: coverage.addressedIds.length,
237
+ excluded: coverage.excludedIds.length,
238
+ excludedApprovedByHuman: Boolean(gate1) && gate1?.auto !== true
239
+ };
240
+ const metrics = {
241
+ criteriaTotal,
242
+ avgAttempts: criteriaTotal > 0 ? Math.round(attemptsSum / criteriaTotal * 100) / 100 : 0,
243
+ blockedRatio: criteriaTotal > 0 ? Math.round(blockedCount / criteriaTotal * 100) / 100 : 0,
244
+ reviewRejects: reviews.length,
245
+ proceduralErrors,
246
+ gotchaApplied,
247
+ gotchaMissed,
248
+ refactorCount,
249
+ coverage: coverageMetrics
250
+ };
251
+ const awlFeedback = {
252
+ prompt: "\uC774\uBC88 \uC6CC\uD06C\uC544\uC774\uD15C\uC5D0\uC11C awl \uB3C4\uAD6C \uC790\uCCB4(\uC791\uC5C5 \uB300\uC0C1 \uCF54\uB4DC\uAC00 \uC544\uB2C8\uB77C)\uAC00 \uBD88\uD3B8\uD588\uB358 \uC810\uC774 \uC788\uB098? \uC788\uC73C\uBA74 awl record awl-feedback \uC73C\uB85C \uB0A8\uACA8\uB77C(area/what/impact/severity, suggestion \uC740 \uC120\uD0DD). \uC5C6\uC73C\uBA74 \uB118\uC5B4\uAC00\uB77C \u2014 \uB9E4\uB044\uB7EC\uC6E0\uC73C\uBA74 \uC88B\uC740 \uC2E0\uD638\uB2E4. gotcha(\uC791\uC5C5 \uCF54\uB4DC \uAD50\uD6C8)\uC640 \uB2E4\uB978 \uC885\uB958\uB2E4.",
253
+ recorded: awlFeedbackRecords
254
+ };
255
+ return {
256
+ workitem,
257
+ project,
258
+ blocked,
259
+ reviews,
260
+ retried,
261
+ existingGotchas,
262
+ relatedGotchas,
263
+ metrics,
264
+ awlFeedback
265
+ };
266
+ }
267
+ function writeGeneration(project, workitem, metrics, at, extra) {
268
+ const dir = generationsDir(project);
269
+ fs.mkdirSync(dir, { recursive: true });
270
+ const stamp = at.replace(/[:.]/g, "-");
271
+ const wi = workitem?.trim() ? workitem : "unknown";
272
+ const name = `${stamp}-${wi}.json`;
273
+ const file = path.join(dir, name);
274
+ fs.writeFileSync(
275
+ file,
276
+ `${JSON.stringify({ workitem: wi, at, ...metrics, ...extra }, null, 2)}
277
+ `
278
+ );
279
+ return file;
280
+ }
281
+ function recordGotcha(input, at) {
282
+ const gotchas = loadGotchaList();
283
+ if (input.sameAs) {
284
+ const existing = gotchas.find((g) => g.id === input.sameAs);
285
+ if (existing) {
286
+ existing.count += 1;
287
+ existing.history = [...existing.history ?? [], { at, source: input.source }];
288
+ writeGotcha(existing);
289
+ return { gotcha: existing, repeated: existing.count >= 2, created: false };
290
+ }
291
+ }
292
+ const id = nextGotchaId(gotchas);
293
+ const gotcha = {
294
+ id,
295
+ lesson: input.lesson,
296
+ context: input.context,
297
+ source: input.source,
298
+ sameAs: input.sameAs,
299
+ ...input.relations && input.relations.length > 0 ? { relations: input.relations } : {},
300
+ count: 1,
301
+ createdAt: at,
302
+ history: [{ at, source: input.source }]
303
+ };
304
+ writeGotcha(gotcha);
305
+ return { gotcha, repeated: false, created: true };
306
+ }
307
+ function renderRepeatNotice(gotcha, c) {
308
+ const color = makeColors(c.color);
309
+ const first = gotcha.history?.[0]?.source;
310
+ const last = gotcha.history?.[gotcha.history.length - 1]?.source;
311
+ const fmt = (s) => s ? `${s.workitem ?? "?"} (${s.project ?? "?"})` : "?";
312
+ return [
313
+ "",
314
+ ` gotcha ${color.bold(gotcha.id)} \uC774 ${gotcha.count}\uD68C \uBC18\uBCF5\uB410\uC2B5\uB2C8\uB2E4.`,
315
+ "",
316
+ ` "${gotcha.lesson}"`,
317
+ "",
318
+ ` \uCC98\uC74C: ${fmt(first)}`,
319
+ ` \uC774\uBC88: ${fmt(last)}`,
320
+ "",
321
+ ` ${color.dim(`awl rules promote ${gotcha.id} \uC73C\uB85C \uADDC\uCE59\uC744 \uB9CC\uB4E4 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`)}`,
322
+ " (\uC790\uB3D9 \uC2B9\uACA9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uB78C\uC774 \uD655\uC778\uD569\uB2C8\uB2E4.)"
323
+ ].join("\n");
324
+ }
325
+ function runEvolveCollect(opts) {
326
+ const { projectRoot, config } = requireConfig();
327
+ if (!acquireLock()) {
328
+ process.stderr.write(
329
+ `
330
+ ${signal(caps(), "warn")} \uB2E4\uB978 evolve \uAC00 \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4(~/.awl/.lock). \uB05D\uB09C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.
331
+ `
332
+ );
333
+ process.exit(1);
334
+ }
335
+ try {
336
+ const workitem = opts.workitem ?? null;
337
+ const state = loadState(projectRoot);
338
+ const scope = opts.months !== void 0 || opts.from !== void 0 || opts.to !== void 0 ? { months: opts.months, from: opts.from, to: opts.to } : void 0;
339
+ const collection = collectEvolve(config.project, workitem, state, scope);
340
+ const at = (/* @__PURE__ */ new Date()).toISOString();
341
+ const extra = {};
342
+ if (state.workitemExperiment !== void 0) {
343
+ extra.experiment = state.workitemExperiment;
344
+ }
345
+ if (typeof state.workitemCreatedAt === "string") {
346
+ extra.startedAt = state.workitemCreatedAt;
347
+ const dur = computeDurationMs(state.workitemCreatedAt, at);
348
+ if (dur !== void 0) {
349
+ extra.durationMs = dur;
350
+ }
351
+ }
352
+ writeGeneration(config.project, workitem, collection.metrics, at, extra);
353
+ process.stdout.write(`${JSON.stringify(collection, null, 2)}
354
+ `);
355
+ } finally {
356
+ releaseLock();
357
+ }
358
+ }
359
+ function runEvolveRecord(jsonInput) {
360
+ let parsed;
361
+ try {
362
+ parsed = JSON.parse(jsonInput);
363
+ } catch (e) {
364
+ process.stderr.write(
365
+ `
366
+ ${signal(caps(), "error")} \uAD50\uD6C8 JSON \uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${String(e)}
367
+ `
368
+ );
369
+ process.exit(1);
370
+ }
371
+ if (typeof parsed !== "object" || parsed === null) {
372
+ process.stderr.write(`
373
+ ${signal(caps(), "error")} \uAD50\uD6C8\uC740 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.
374
+ `);
375
+ process.exit(1);
376
+ }
377
+ const data = parsed;
378
+ if (typeof data.lesson !== "string" || data.lesson.trim() === "") {
379
+ process.stderr.write(
380
+ `
381
+ ${signal(caps(), "error")} lesson(\uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uAD50\uD6C8 \uBB38\uC7A5)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.
382
+ `
383
+ );
384
+ process.exit(1);
385
+ }
386
+ let source = data.source;
387
+ if (source === void 0) {
388
+ const { projectRoot, config } = requireConfig();
389
+ const state = loadState(projectRoot);
390
+ source = {
391
+ project: config.project,
392
+ ...typeof state.workitem === "string" ? { workitem: state.workitem } : {}
393
+ };
394
+ }
395
+ const rel = normalizeRelations(data.relations);
396
+ if (!rel.ok) {
397
+ process.stderr.write(`
398
+ ${signal(caps(), "error")} ${rel.error}
399
+ `);
400
+ process.exit(1);
401
+ }
402
+ if (!acquireLock()) {
403
+ process.stderr.write(
404
+ `
405
+ ${signal(caps(), "warn")} \uB2E4\uB978 evolve \uAC00 \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4(~/.awl/.lock).
406
+ `
407
+ );
408
+ process.exit(1);
409
+ }
410
+ try {
411
+ const input = {
412
+ lesson: data.lesson,
413
+ context: typeof data.context === "string" ? data.context : void 0,
414
+ source,
415
+ sameAs: typeof data.sameAs === "string" ? data.sameAs : void 0,
416
+ ...rel.relations ? { relations: rel.relations } : {}
417
+ };
418
+ const result = recordGotcha(input, (/* @__PURE__ */ new Date()).toISOString());
419
+ if (result.repeated) {
420
+ process.stdout.write(`${renderRepeatNotice(result.gotcha, caps())}
421
+ `);
422
+ } else if (result.created) {
423
+ process.stdout.write(`
424
+ \uAD50\uD6C8 ${result.gotcha.id} \uC744 \uAE30\uB85D\uD588\uC2B5\uB2C8\uB2E4.
425
+ `);
426
+ } else {
427
+ process.stdout.write(
428
+ `
429
+ \uAD50\uD6C8 ${result.gotcha.id} \uC758 \uBC18\uBCF5\uC744 \uAE30\uB85D\uD588\uC2B5\uB2C8\uB2E4(${result.gotcha.count}\uD68C).
430
+ `
431
+ );
432
+ }
433
+ } finally {
434
+ releaseLock();
435
+ }
436
+ }
437
+
438
+ export {
439
+ acquireLock,
440
+ releaseLock,
441
+ GOTCHA_RELATION_TYPES,
442
+ normalizeRelations,
443
+ migrateDeltasToGotchas,
444
+ loadGotchaList,
445
+ nextGotchaId,
446
+ gotchaCluster,
447
+ gotchasBySource,
448
+ collectEvolve,
449
+ writeGeneration,
450
+ recordGotcha,
451
+ runEvolveCollect,
452
+ runEvolveRecord
453
+ };