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,904 @@
1
+ import {
2
+ DEFAULT_USAGE_PATH,
3
+ readCostSnapshot
4
+ } from "./chunk-D5OINC3G.js";
5
+ import {
6
+ buildVerifyBaseline,
7
+ isCheckPassed,
8
+ runVerifyChecks,
9
+ writeVerifyBaseline
10
+ } from "./chunk-QE2CLNBG.js";
11
+ import {
12
+ gitBranch
13
+ } from "./chunk-IXMAFR4Y.js";
14
+ import {
15
+ parseRuleFile
16
+ } from "./chunk-IMB46O6S.js";
17
+ import {
18
+ nextGotchaId
19
+ } from "./chunk-G5LAJ5TV.js";
20
+ import {
21
+ loadState,
22
+ migrateState,
23
+ writeState
24
+ } from "./chunk-4OCSYHYB.js";
25
+ import {
26
+ loadConfig,
27
+ resolveProjectRoot,
28
+ run
29
+ } from "./chunk-UOPWVM2H.js";
30
+ import {
31
+ installClaudeSkill
32
+ } from "./chunk-BUWGQVHT.js";
33
+ import {
34
+ caps,
35
+ card,
36
+ feedback,
37
+ globalRoot,
38
+ makeColors,
39
+ makeSymbols,
40
+ makeTokens,
41
+ signal
42
+ } from "./chunk-7SYRDDTX.js";
43
+
44
+ // src/commands/work.ts
45
+ import fs2 from "fs";
46
+ import path2 from "path";
47
+
48
+ // src/commands/learning-merge.ts
49
+ import fs from "fs";
50
+ import path from "path";
51
+ var PARENT_MARKER = ".awl-parent";
52
+ function lessonKey(g) {
53
+ return (g.lesson ?? "").trim();
54
+ }
55
+ function mergeGotchaLists(from, to) {
56
+ const merged = [...to];
57
+ const added = [];
58
+ const idMap = {};
59
+ const byLesson = /* @__PURE__ */ new Map();
60
+ for (const g of to) {
61
+ const k = lessonKey(g);
62
+ if (k !== "") {
63
+ byLesson.set(k, g);
64
+ }
65
+ }
66
+ const fromSorted = [...from].sort((a, b) => a.id.localeCompare(b.id));
67
+ for (const g of fromSorted) {
68
+ const key = lessonKey(g);
69
+ const dup = key !== "" ? byLesson.get(key) : void 0;
70
+ if (dup) {
71
+ idMap[g.id] = dup.id;
72
+ continue;
73
+ }
74
+ const newId = nextGotchaId(merged);
75
+ idMap[g.id] = newId;
76
+ const shell = { ...g, id: newId };
77
+ merged.push(shell);
78
+ if (key !== "") {
79
+ byLesson.set(key, shell);
80
+ }
81
+ added.push(shell);
82
+ }
83
+ for (const g of added) {
84
+ if (g.relations) {
85
+ g.relations = g.relations.map((r) => ({ ...r, target: idMap[r.target] ?? r.target }));
86
+ }
87
+ if (g.sameAs) {
88
+ g.sameAs = idMap[g.sameAs] ?? g.sameAs;
89
+ }
90
+ }
91
+ return { merged, added, idMap };
92
+ }
93
+ function readGotchasFrom(root) {
94
+ const dir = path.join(root, "gotchas");
95
+ let files;
96
+ try {
97
+ files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
98
+ } catch {
99
+ return [];
100
+ }
101
+ const out = [];
102
+ for (const f of files.sort()) {
103
+ try {
104
+ out.push(JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")));
105
+ } catch {
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+ function readRulesFrom(root) {
111
+ const dir = path.join(root, "rules", "active");
112
+ let files;
113
+ try {
114
+ files = fs.readdirSync(dir).filter((f) => f.endsWith(".md"));
115
+ } catch {
116
+ return [];
117
+ }
118
+ const out = [];
119
+ for (const f of files.sort()) {
120
+ let text;
121
+ try {
122
+ text = fs.readFileSync(path.join(dir, f), "utf8");
123
+ } catch {
124
+ continue;
125
+ }
126
+ const parsed = parseRuleFile(text, f);
127
+ if (parsed.rule) {
128
+ out.push({ text, body: parsed.rule.body });
129
+ }
130
+ }
131
+ return out;
132
+ }
133
+ function nextRuleNum(fileNames) {
134
+ let max = 0;
135
+ for (const f of fileNames) {
136
+ const m = /^R-(\d+)\.md$/.exec(f);
137
+ if (m?.[1]) {
138
+ max = Math.max(max, Number(m[1]));
139
+ }
140
+ }
141
+ return max + 1;
142
+ }
143
+ function mergeRules(fromRoot, toRoot, idMap) {
144
+ const fromRules = readRulesFrom(fromRoot);
145
+ if (fromRules.length === 0) {
146
+ return 0;
147
+ }
148
+ const toActive = path.join(toRoot, "rules", "active");
149
+ const toRules = readRulesFrom(toRoot);
150
+ const toBodies = new Set(toRules.map((r) => r.body.trim()));
151
+ let toFiles = [];
152
+ try {
153
+ toFiles = fs.readdirSync(toActive).filter((f) => f.endsWith(".md"));
154
+ } catch {
155
+ }
156
+ let ruleNum = nextRuleNum(toFiles);
157
+ let added = 0;
158
+ for (const r of fromRules) {
159
+ if (toBodies.has(r.body.trim())) {
160
+ continue;
161
+ }
162
+ const newId = `R-${String(ruleNum).padStart(3, "0")}`;
163
+ ruleNum += 1;
164
+ const rewritten = r.text.replace(/^id:[ \t]*.*$/m, `id: ${newId}`).replace(
165
+ /^source:[ \t]*(\S+)(.*)$/m,
166
+ (_m, src, rest) => `source: ${idMap[src] ?? src}${rest}`
167
+ );
168
+ fs.mkdirSync(toActive, { recursive: true });
169
+ fs.writeFileSync(path.join(toActive, `${newId}.md`), rewritten);
170
+ toBodies.add(r.body.trim());
171
+ added += 1;
172
+ }
173
+ return added;
174
+ }
175
+ function copyGenerations(fromRoot, toRoot) {
176
+ const fromGen = path.join(fromRoot, "generations");
177
+ if (!fs.existsSync(fromGen)) {
178
+ return 0;
179
+ }
180
+ let copied = 0;
181
+ const walk = (rel) => {
182
+ const abs = path.join(fromGen, rel);
183
+ for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
184
+ const childRel = path.join(rel, entry.name);
185
+ if (entry.isDirectory()) {
186
+ walk(childRel);
187
+ } else if (entry.isFile()) {
188
+ const dest = path.join(toRoot, "generations", childRel);
189
+ if (!fs.existsSync(dest)) {
190
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
191
+ fs.copyFileSync(path.join(fromGen, childRel), dest);
192
+ copied += 1;
193
+ }
194
+ }
195
+ }
196
+ };
197
+ walk(".");
198
+ return copied;
199
+ }
200
+ function mergeIsolatedLearning(fromRoot, toRoot) {
201
+ const fromGotchas = readGotchasFrom(fromRoot);
202
+ const toGotchas = readGotchasFrom(toRoot);
203
+ const { added, idMap } = mergeGotchaLists(fromGotchas, toGotchas);
204
+ if (added.length > 0) {
205
+ const dir = path.join(toRoot, "gotchas");
206
+ fs.mkdirSync(dir, { recursive: true });
207
+ for (const g of added) {
208
+ fs.writeFileSync(path.join(dir, `${g.id}.json`), `${JSON.stringify(g, null, 2)}
209
+ `);
210
+ }
211
+ }
212
+ const rulesAdded = mergeRules(fromRoot, toRoot, idMap);
213
+ const generationsAdded = copyGenerations(fromRoot, toRoot);
214
+ return { gotchasAdded: added.length, rulesAdded, generationsAdded };
215
+ }
216
+ function writeParentMarker(isolatedHome) {
217
+ try {
218
+ fs.writeFileSync(path.join(isolatedHome, PARENT_MARKER), `${globalRoot()}
219
+ `);
220
+ } catch {
221
+ }
222
+ }
223
+ function resolveParentGlobal(isolatedHome) {
224
+ try {
225
+ const raw = fs.readFileSync(path.join(isolatedHome, PARENT_MARKER), "utf8").trim();
226
+ if (raw !== "") {
227
+ return path.resolve(raw);
228
+ }
229
+ } catch {
230
+ }
231
+ return globalRoot();
232
+ }
233
+ function mergeIsolatedHome(isolatedHome) {
234
+ if (!fs.existsSync(isolatedHome)) {
235
+ return null;
236
+ }
237
+ const toRoot = resolveParentGlobal(isolatedHome);
238
+ if (path.resolve(isolatedHome) === path.resolve(toRoot)) {
239
+ return null;
240
+ }
241
+ return mergeIsolatedLearning(isolatedHome, toRoot);
242
+ }
243
+
244
+ // src/commands/work.ts
245
+ function countCriteria(criteria) {
246
+ const arr = Array.isArray(criteria) ? criteria : [];
247
+ return { passed: arr.filter((c) => c.status === "passed").length, total: arr.length };
248
+ }
249
+ function summarizeWorkitems(state) {
250
+ const out = [];
251
+ const currentId = typeof state.workitem === "string" ? state.workitem : null;
252
+ if (currentId) {
253
+ const { passed, total } = countCriteria(state.criteria);
254
+ out.push({
255
+ id: currentId,
256
+ status: "active",
257
+ passed,
258
+ total,
259
+ current: true,
260
+ branch: typeof state.workitemBranch === "string" ? state.workitemBranch : void 0,
261
+ createdAt: typeof state.workitemCreatedAt === "string" ? state.workitemCreatedAt : void 0,
262
+ worktreePath: typeof state.workitemWorktreePath === "string" ? state.workitemWorktreePath : void 0
263
+ });
264
+ }
265
+ const registry = state.workitems && typeof state.workitems === "object" ? state.workitems : {};
266
+ for (const [id, raw] of Object.entries(registry)) {
267
+ if (!raw || typeof raw !== "object") {
268
+ continue;
269
+ }
270
+ const entry = raw;
271
+ const { passed, total } = countCriteria(entry.criteria);
272
+ out.push({
273
+ id,
274
+ status: typeof entry.status === "string" ? entry.status : "paused",
275
+ passed,
276
+ total,
277
+ current: false,
278
+ branch: typeof entry.branch === "string" ? entry.branch : void 0,
279
+ createdAt: typeof entry.createdAt === "string" ? entry.createdAt : void 0,
280
+ worktreePath: typeof entry.worktreePath === "string" ? entry.worktreePath : void 0
281
+ });
282
+ }
283
+ return out;
284
+ }
285
+ function statusColored(t, status) {
286
+ if (status === "active" || status === "done") {
287
+ return t.success(status);
288
+ }
289
+ if (status === "paused") {
290
+ return t.warning(status);
291
+ }
292
+ if (status === "abandoned") {
293
+ return t.muted(status);
294
+ }
295
+ return status;
296
+ }
297
+ function renderWorkList(list, c) {
298
+ const color = makeColors(c.color);
299
+ const t = makeTokens(c);
300
+ const s = makeSymbols(c);
301
+ if (list.length === 0) {
302
+ return card(
303
+ "\uC6CC\uD06C\uC544\uC774\uD15C",
304
+ [
305
+ `${signal(c, "info")} \uB4F1\uB85D\uB41C \uC6CC\uD06C\uC544\uC774\uD15C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`,
306
+ "",
307
+ `${s.lastBranch} ${color.dim("awl work new <ID> \uB85C \uC2DC\uC791\uD558\uC138\uC694.")}`
308
+ ],
309
+ c
310
+ );
311
+ }
312
+ const idWidth = Math.max(...list.map((w) => w.id.length), 2) + 2;
313
+ const statusWidth = Math.max(...list.map((w) => w.status.length), 6) + 2;
314
+ const out = [];
315
+ for (const w of list) {
316
+ const marker = w.current ? color.green("*") : " ";
317
+ const statusPad = " ".repeat(Math.max(0, statusWidth - w.status.length));
318
+ let line = `${marker} ${w.id.padEnd(idWidth, " ")}${statusColored(t, w.status)}${statusPad}${t.emphasis(`${w.passed}/${w.total}`)} \uD1B5\uACFC`;
319
+ if (w.branch) {
320
+ line += ` ${color.dim(w.branch)}`;
321
+ }
322
+ out.push(line);
323
+ if (w.worktreePath) {
324
+ out.push(` ${s.lastBranch} ${color.dim(`worktree: ${w.worktreePath}`)}`);
325
+ }
326
+ }
327
+ return card("\uC6CC\uD06C\uC544\uC774\uD15C", out, c);
328
+ }
329
+ function registryOf(state) {
330
+ return state.workitems && typeof state.workitems === "object" ? state.workitems : {};
331
+ }
332
+ function archiveCurrent(state, status, now) {
333
+ const migrated = migrateState(state);
334
+ const currentId = typeof migrated.workitem === "string" ? migrated.workitem : null;
335
+ if (!currentId) {
336
+ return migrated;
337
+ }
338
+ const entry = {
339
+ status,
340
+ createdAt: typeof migrated.workitemCreatedAt === "string" ? migrated.workitemCreatedAt : now,
341
+ ...typeof migrated.workitemBranch === "string" ? { branch: migrated.workitemBranch } : {},
342
+ ...typeof migrated.workitemDescription === "string" ? { description: migrated.workitemDescription } : {},
343
+ ...typeof migrated.raw_request === "string" ? { raw_request: migrated.raw_request } : {},
344
+ phase: migrated.phase ?? null,
345
+ loop: migrated.loop ?? null,
346
+ criteria: Array.isArray(migrated.criteria) ? migrated.criteria : [],
347
+ ...typeof migrated.currentFocus === "string" ? { currentFocus: migrated.currentFocus } : {},
348
+ ...typeof migrated.workitemWorktreePath === "string" ? { worktreePath: migrated.workitemWorktreePath } : {}
349
+ };
350
+ const {
351
+ workitem: _w,
352
+ phase: _p,
353
+ loop: _l,
354
+ criteria: _c,
355
+ workitemBranch: _b,
356
+ workitemCreatedAt: _ca,
357
+ workitemDescription: _d,
358
+ raw_request: _rr,
359
+ // currentFocus/worktreePath 는 워크아이템별 상태다(리뷰 지적 AC-09, 같은 실수를
360
+ // worktreePath 에서 반복하지 않는다) — rest 로 흘려보내면 다음(새) 워크아이템의
361
+ // 최상위로 그대로 새어 들어간다. entry 스냅샷에만 담고 여기선 제거한다.
362
+ currentFocus: _cf,
363
+ workitemWorktreePath: _wtp,
364
+ ...rest
365
+ } = migrated;
366
+ return {
367
+ ...rest,
368
+ workitem: null,
369
+ phase: null,
370
+ loop: null,
371
+ criteria: [],
372
+ workitems: { ...registryOf(migrated), [currentId]: entry }
373
+ };
374
+ }
375
+ function createWorkitem(state, id, now, branch, description, worktreePath, experiment) {
376
+ const trimmed = id.trim();
377
+ if (!trimmed) {
378
+ return { state, error: "\uC6CC\uD06C\uC544\uC774\uD15C ID \uB97C \uC785\uB825\uD558\uC138\uC694." };
379
+ }
380
+ const currentId = typeof state.workitem === "string" ? state.workitem : null;
381
+ if (currentId && trimmed.toLowerCase() === currentId.toLowerCase()) {
382
+ return { state, error: `\uC774\uBBF8 \uD604\uC7AC \uC6CC\uD06C\uC544\uC774\uD15C\uC785\uB2C8\uB2E4: ${currentId}` };
383
+ }
384
+ const existingKey = Object.keys(registryOf(state)).find(
385
+ (k) => k.toLowerCase() === trimmed.toLowerCase()
386
+ );
387
+ if (existingKey) {
388
+ return {
389
+ state,
390
+ error: `\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uC6CC\uD06C\uC544\uC774\uD15C\uC785\uB2C8\uB2E4: ${existingKey} (awl work switch ${existingKey} \uB97C \uC4F0\uC138\uC694)`
391
+ };
392
+ }
393
+ const archived = archiveCurrent(state, "paused", now);
394
+ return {
395
+ state: {
396
+ ...archived,
397
+ workitem: trimmed,
398
+ phase: "awaiting-gate1",
399
+ loop: null,
400
+ criteria: [],
401
+ workitemCreatedAt: now,
402
+ ...branch ? { workitemBranch: branch } : {},
403
+ ...description ? { workitemDescription: description } : {},
404
+ raw_request: description ?? "",
405
+ ...worktreePath ? { workitemWorktreePath: worktreePath } : {},
406
+ // 실험 케이스 메타(model/mode/taskType). D-15 자유 필드로 보존 —
407
+ // evolve 가 세대 스냅샷에 실어 metrics --compare 가 케이스별로 비교한다.
408
+ ...experiment ? { workitemExperiment: experiment } : {}
409
+ }
410
+ };
411
+ }
412
+ function restoreWorkitem(state, id, now, branch) {
413
+ const trimmed = id.trim();
414
+ const migrated = migrateState(state);
415
+ const registry = registryOf(migrated);
416
+ const key = Object.keys(registry).find((k) => k.toLowerCase() === trimmed.toLowerCase());
417
+ const currentId = typeof migrated.workitem === "string" ? migrated.workitem : null;
418
+ if (currentId && trimmed.toLowerCase() === currentId.toLowerCase()) {
419
+ return { state, error: `\uC774\uBBF8 \uD604\uC7AC \uC6CC\uD06C\uC544\uC774\uD15C\uC785\uB2C8\uB2E4: ${currentId}` };
420
+ }
421
+ if (!key) {
422
+ return {
423
+ state,
424
+ error: `\uADF8\uB7F0 \uC6CC\uD06C\uC544\uC774\uD15C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${trimmed} (awl work new ${trimmed} \uB85C \uC0C8\uB85C \uB9CC\uB4DC\uC138\uC694)`
425
+ };
426
+ }
427
+ const entry = registry[key];
428
+ const archived = archiveCurrent(migrated, "paused", now);
429
+ const remainingRegistry = { ...registryOf(archived) };
430
+ delete remainingRegistry[key];
431
+ const nextState = {
432
+ ...archived,
433
+ workitem: key,
434
+ phase: entry.phase ?? null,
435
+ loop: entry.loop ?? null,
436
+ criteria: entry.criteria,
437
+ workitemCreatedAt: entry.createdAt,
438
+ ...entry.branch ? { workitemBranch: entry.branch } : {},
439
+ ...entry.description ? { workitemDescription: entry.description } : {},
440
+ ...typeof entry.raw_request === "string" ? { raw_request: entry.raw_request } : {},
441
+ ...entry.currentFocus ? { currentFocus: entry.currentFocus } : {},
442
+ ...entry.worktreePath ? { workitemWorktreePath: entry.worktreePath } : {},
443
+ workitems: remainingRegistry
444
+ };
445
+ const warnings = [];
446
+ if (entry.branch && branch && entry.branch !== branch) {
447
+ warnings.push(
448
+ `\uACBD\uACE0: ${key} \uB294 \uBE0C\uB79C\uCE58 ${entry.branch} \uC5D0\uC11C \uB9CC\uB4E4\uC5B4\uC84C\uB294\uB370 \uC9C0\uAE08\uC740 ${branch} \uBE0C\uB79C\uCE58\uC785\uB2C8\uB2E4.`
449
+ );
450
+ }
451
+ if (entry.status === "abandoned") {
452
+ warnings.push(`\uACBD\uACE0: ${key} \uB294 \uC911\uB2E8(abandoned) \uCC98\uB9AC\uB41C \uC6CC\uD06C\uC544\uC774\uD15C\uC785\uB2C8\uB2E4.`);
453
+ }
454
+ return { state: nextState, warning: warnings.length > 0 ? warnings.join(" ") : void 0 };
455
+ }
456
+ function abandonWorkitem(state, id, now) {
457
+ const trimmed = id.trim();
458
+ const migrated = migrateState(state);
459
+ const currentId = typeof migrated.workitem === "string" ? migrated.workitem : null;
460
+ if (currentId && trimmed.toLowerCase() === currentId.toLowerCase()) {
461
+ return { state: archiveCurrent(migrated, "abandoned", now) };
462
+ }
463
+ const registry = registryOf(migrated);
464
+ const key = Object.keys(registry).find((k) => k.toLowerCase() === trimmed.toLowerCase());
465
+ if (!key) {
466
+ return { state, error: `\uADF8\uB7F0 \uC6CC\uD06C\uC544\uC774\uD15C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${trimmed}` };
467
+ }
468
+ const entry = registry[key];
469
+ return {
470
+ state: {
471
+ ...migrated,
472
+ workitems: { ...registry, [key]: { ...entry, status: "abandoned" } }
473
+ }
474
+ };
475
+ }
476
+ function stripCriteriaSnapshots(criteria) {
477
+ return criteria.map((c) => {
478
+ const { untrackedAtStart: _u, snapshot: _s, ...rest } = c;
479
+ return rest;
480
+ });
481
+ }
482
+ function markWorkitemDone(state, id, now) {
483
+ const trimmed = id.trim();
484
+ const migrated = migrateState(state);
485
+ const currentId = typeof migrated.workitem === "string" ? migrated.workitem : null;
486
+ if (currentId && trimmed.toLowerCase() === currentId.toLowerCase()) {
487
+ const wtPath2 = typeof migrated.workitemWorktreePath === "string" ? migrated.workitemWorktreePath : void 0;
488
+ const archived = archiveCurrent(migrated, "done", now);
489
+ const reg = registryOf(archived);
490
+ const entry2 = reg[currentId];
491
+ const next2 = {
492
+ ...archived,
493
+ workitems: {
494
+ ...reg,
495
+ [currentId]: { ...entry2, criteria: stripCriteriaSnapshots(entry2.criteria ?? []) }
496
+ }
497
+ };
498
+ return { state: next2, ...wtPath2 ? { worktree: { path: wtPath2 } } : {} };
499
+ }
500
+ const registry = registryOf(migrated);
501
+ const key = Object.keys(registry).find((k) => k.toLowerCase() === trimmed.toLowerCase());
502
+ if (!key) {
503
+ return { state, error: `\uADF8\uB7F0 \uC6CC\uD06C\uC544\uC774\uD15C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${trimmed}` };
504
+ }
505
+ const entry = registry[key];
506
+ const wtPath = typeof entry.worktreePath === "string" ? entry.worktreePath : void 0;
507
+ const next = {
508
+ ...migrated,
509
+ workitems: {
510
+ ...registry,
511
+ [key]: { ...entry, status: "done", criteria: stripCriteriaSnapshots(entry.criteria ?? []) }
512
+ }
513
+ };
514
+ return { state: next, ...wtPath ? { worktree: { path: wtPath } } : {} };
515
+ }
516
+ function removeWorkitemFromState(state, ids) {
517
+ const migrated = migrateState(state);
518
+ const wanted = new Set(ids.map((s) => s.trim().toLowerCase()).filter(Boolean));
519
+ if (wanted.size === 0) {
520
+ return { state: migrated, removed: false };
521
+ }
522
+ let next = migrated;
523
+ let removed = false;
524
+ const currentId = typeof migrated.workitem === "string" ? migrated.workitem : null;
525
+ if (currentId && wanted.has(currentId.toLowerCase())) {
526
+ const {
527
+ workitem: _w,
528
+ phase: _p,
529
+ loop: _l,
530
+ criteria: _c,
531
+ workitemBranch: _b,
532
+ workitemCreatedAt: _ca,
533
+ workitemDescription: _d,
534
+ raw_request: _rr,
535
+ currentFocus: _cf,
536
+ workitemWorktreePath: _wtp,
537
+ workitemExperiment: _we,
538
+ ...rest
539
+ } = next;
540
+ next = { ...rest, workitem: null, phase: null, loop: null, criteria: [] };
541
+ removed = true;
542
+ }
543
+ const registry = registryOf(next);
544
+ const hitKeys = Object.keys(registry).filter((k) => wanted.has(k.toLowerCase()));
545
+ if (hitKeys.length > 0) {
546
+ const nextRegistry = { ...registry };
547
+ for (const k of hitKeys) {
548
+ delete nextRegistry[k];
549
+ }
550
+ next = { ...next, workitems: nextRegistry };
551
+ removed = true;
552
+ }
553
+ return { state: next, removed };
554
+ }
555
+ function requireRoot() {
556
+ const root = resolveProjectRoot();
557
+ if (!root) {
558
+ process.stderr.write("\n \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. awl init \uC744 \uC2E4\uD589\uD558\uC138\uC694.\n");
559
+ process.exit(1);
560
+ }
561
+ return root;
562
+ }
563
+ function runWorkList(opts) {
564
+ const root = requireRoot();
565
+ const list = summarizeWorkitems(loadState(root));
566
+ if (opts.json) {
567
+ process.stdout.write(`${JSON.stringify(list, null, 2)}
568
+ `);
569
+ } else {
570
+ process.stdout.write(`${renderWorkList(list, caps())}
571
+ `);
572
+ }
573
+ }
574
+ function sanitizeForGit(s) {
575
+ return s.trim().replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
576
+ }
577
+ async function createGitWorktree(root, targetPath, branchName) {
578
+ const r = await run({
579
+ cmd: "git",
580
+ args: ["worktree", "add", targetPath, "-b", branchName],
581
+ cwd: root,
582
+ timeoutMs: 3e4
583
+ });
584
+ if (r.exitCode !== 0) {
585
+ return { ok: false, error: (r.stderr || r.stdout).trim() };
586
+ }
587
+ return { ok: true };
588
+ }
589
+ async function removeGitWorktree(root, targetPath, branchName) {
590
+ const rm = await run({
591
+ cmd: "git",
592
+ args: ["worktree", "remove", "--force", targetPath],
593
+ cwd: root,
594
+ timeoutMs: 3e4
595
+ });
596
+ if (rm.exitCode !== 0) {
597
+ return { ok: false, error: (rm.stderr || rm.stdout).trim() };
598
+ }
599
+ const br = await run({
600
+ cmd: "git",
601
+ args: ["branch", "-D", branchName],
602
+ cwd: root,
603
+ timeoutMs: 1e4
604
+ });
605
+ if (br.exitCode !== 0) {
606
+ return { ok: false, error: (br.stderr || br.stdout).trim() };
607
+ }
608
+ return { ok: true };
609
+ }
610
+ function ensureGitignored(root, target) {
611
+ const gi = path2.join(root, ".gitignore");
612
+ const content = fs2.existsSync(gi) ? fs2.readFileSync(gi, "utf8") : "";
613
+ if (content.split(/\r?\n/).some((line) => line.trim() === target)) {
614
+ return;
615
+ }
616
+ const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
617
+ fs2.writeFileSync(gi, `${content}${prefix}${target}
618
+ `);
619
+ }
620
+ function withCostAtStart(state, usagePath = DEFAULT_USAGE_PATH) {
621
+ const costAtStart = readCostSnapshot(usagePath);
622
+ return costAtStart ? { ...state, costAtStart } : state;
623
+ }
624
+ async function runWorkNew(id, description, opts = {}) {
625
+ const root = requireRoot();
626
+ const now = (/* @__PURE__ */ new Date()).toISOString();
627
+ const branch = await gitBranch(root);
628
+ let worktreePath;
629
+ let branchName;
630
+ if (opts.worktree) {
631
+ branchName = typeof opts.worktree === "string" ? opts.worktree : `work/${sanitizeForGit(id)}`;
632
+ worktreePath = path2.join(root, ".awl-worktrees", sanitizeForGit(id));
633
+ }
634
+ const stateRoot = opts.isolated && worktreePath ? worktreePath : root;
635
+ const precheck = createWorkitem(loadState(stateRoot), id, now, branch, description);
636
+ if (precheck.error) {
637
+ process.stderr.write(`
638
+ ${precheck.error}
639
+ `);
640
+ process.exit(1);
641
+ }
642
+ if (opts.worktree && worktreePath && branchName) {
643
+ const created = await createGitWorktree(root, worktreePath, branchName);
644
+ if (!created.ok) {
645
+ process.stderr.write(`
646
+ \uC6CC\uD06C\uD2B8\uB9AC\uB97C \uB9CC\uB4E4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${created.error}
647
+ `);
648
+ process.exit(1);
649
+ }
650
+ ensureGitignored(root, ".awl-worktrees/");
651
+ }
652
+ let isolatedHome;
653
+ if (opts.isolated) {
654
+ isolatedHome = path2.join(worktreePath ?? root, ".awl", "home");
655
+ fs2.mkdirSync(isolatedHome, { recursive: true });
656
+ writeParentMarker(isolatedHome);
657
+ ensureGitignored(root, ".awl/home/");
658
+ }
659
+ const result = createWorkitem(
660
+ loadState(stateRoot),
661
+ id,
662
+ now,
663
+ branch,
664
+ description,
665
+ worktreePath,
666
+ opts.experiment
667
+ );
668
+ if (result.error) {
669
+ if (worktreePath && branchName) {
670
+ const cleaned = await removeGitWorktree(root, worktreePath, branchName);
671
+ if (!cleaned.ok) {
672
+ process.stderr.write(
673
+ `
674
+ \uC6CC\uD06C\uD2B8\uB9AC \uC815\uB9AC\uC5D0\uB3C4 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC218\uB3D9\uC73C\uB85C \uD655\uC778\uD558\uC138\uC694: ${cleaned.error}
675
+ `
676
+ );
677
+ }
678
+ }
679
+ process.stderr.write(`
680
+ ${result.error}
681
+ `);
682
+ process.exit(1);
683
+ }
684
+ const stateToWrite = withCostAtStart(result.state);
685
+ writeState(stateRoot, stateToWrite);
686
+ const c = caps();
687
+ const color = makeColors(c.color);
688
+ process.stdout.write(`
689
+ ${feedback(c, "ok", `\uC6CC\uD06C\uC544\uC774\uD15C \uC0DD\uC131 ${color.bold(id)}`)}
690
+ `);
691
+ if (worktreePath) {
692
+ process.stdout.write(` ${color.dim(`\uC6CC\uD06C\uD2B8\uB9AC ${worktreePath}`)}
693
+ `);
694
+ try {
695
+ if (installClaudeSkill(worktreePath)) {
696
+ process.stdout.write(` ${color.dim("\uC2A4\uD0AC \uC7AC\uC124\uCE58 .claude/skills/ (engine)")}
697
+ `);
698
+ } else {
699
+ process.stderr.write(
700
+ `
701
+ ${feedback(c, "warn", "\uC6CC\uD06C\uD2B8\uB9AC \uC2A4\uD0AC \uC7AC\uC124\uCE58 \uC0DD\uB7B5", "engine \uC2A4\uD0AC \uC6D0\uBCF8\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC774 \uC6CC\uD06C\uD2B8\uB9AC\uC5D0\uC11C awl init \uB85C \uC218\uB3D9 \uC124\uCE58\uD558\uC138\uC694")}
702
+ `
703
+ );
704
+ }
705
+ } catch (e) {
706
+ process.stderr.write(
707
+ `
708
+ ${feedback(c, "warn", "\uC6CC\uD06C\uD2B8\uB9AC \uC2A4\uD0AC \uC7AC\uC124\uCE58 \uC2E4\uD328", `${String(e)} \u2014 \uC774 \uC6CC\uD06C\uD2B8\uB9AC\uC5D0\uC11C awl init \uB85C \uC218\uB3D9 \uC124\uCE58\uD558\uC138\uC694`)}
709
+ `
710
+ );
711
+ }
712
+ }
713
+ const verifyRoot = worktreePath ?? root;
714
+ if (opts.skipBaseline) {
715
+ process.stdout.write(
716
+ ` ${color.dim("--skip-baseline: \uAC80\uC99D \uBCA0\uC774\uC2A4\uB77C\uC778 \uC0DD\uB7B5 (\uB098\uC911\uC5D0 awl verify --since-baseline \uC744 \uBABB \uC501\uB2C8\uB2E4)")}
717
+ `
718
+ );
719
+ } else {
720
+ const loaded = loadConfig(verifyRoot);
721
+ if (loaded.config) {
722
+ const report = await runVerifyChecks(loaded.config.verify, verifyRoot, { bail: false });
723
+ try {
724
+ writeVerifyBaseline(verifyRoot, buildVerifyBaseline(report, now, id.trim()));
725
+ process.stdout.write(
726
+ ` ${color.dim(`\uAC80\uC99D \uBCA0\uC774\uC2A4\uB77C\uC778 \uC800\uC7A5 ${report.results.map((r) => `${r.name}:${isCheckPassed(r) ? "\uD1B5\uACFC" : "\uC2E4\uD328"}`).join(", ")}`)}
727
+ `
728
+ );
729
+ } catch (e) {
730
+ process.stderr.write(
731
+ `
732
+ ${feedback(c, "warn", "\uAC80\uC99D \uBCA0\uC774\uC2A4\uB77C\uC778 \uC800\uC7A5 \uC2E4\uD328", `${String(e)} \u2014 \uB098\uC911\uC5D0 awl verify --since-baseline \uC744 \uBABB \uC501\uB2C8\uB2E4`)}
733
+ `
734
+ );
735
+ }
736
+ } else {
737
+ process.stdout.write(
738
+ ` ${color.dim("config \uC5C6\uC74C \u2014 \uAC80\uC99D \uBCA0\uC774\uC2A4\uB77C\uC778 \uC0DD\uB7B5 (\uB098\uC911\uC5D0 awl verify --since-baseline \uC744 \uBABB \uC501\uB2C8\uB2E4)")}
739
+ `
740
+ );
741
+ }
742
+ }
743
+ process.stdout.write(
744
+ ` ${color.dim(worktreePath ? `\uB2E4\uC74C \u2192 cd ${worktreePath} \uD6C4 awl-loop \uC2DC\uC791` : "\uB2E4\uC74C \u2192 awl-loop \uC2DC\uC791")}
745
+ `
746
+ );
747
+ if (isolatedHome) {
748
+ process.stdout.write(` ${color.dim(`export AWL_HOME=${isolatedHome}`)}
749
+ `);
750
+ process.stdout.write(
751
+ ` ${color.dim("(records \uB97C \uC774 \uC6CC\uD06C\uC544\uC774\uD15C \uC804\uC6A9\uC73C\uB85C \uACA9\uB9AC \u2014 \uC2E4\uC81C \uACA9\uB9AC\uB294 \uC774 export \uB97C \uC801\uC6A9\uD574\uC57C \uBC1C\uC0DD\uD569\uB2C8\uB2E4)")}
752
+ `
753
+ );
754
+ } else if (worktreePath) {
755
+ process.stdout.write(
756
+ ` ${color.dim("\uCC38\uACE0 \u2192 worktree \uB294 git \uB9CC \uACA9\uB9AC\uD569\uB2C8\uB2E4. records(~/.awl)\uB294 \uC804\uC5ED \uACF5\uC720\uC774\uB2C8, \uBCD1\uB82C \uC138\uC158\uC774\uBA74 AWL_HOME \uC744 \uB530\uB85C \uB450\uAC70\uB098 --isolated \uB97C \uC4F0\uC138\uC694.")}
757
+ `
758
+ );
759
+ }
760
+ }
761
+ async function runWorkSwitch(id) {
762
+ const root = requireRoot();
763
+ const now = (/* @__PURE__ */ new Date()).toISOString();
764
+ const branch = await gitBranch(root);
765
+ const result = restoreWorkitem(loadState(root), id, now, branch);
766
+ if (result.error) {
767
+ process.stderr.write(`
768
+ ${result.error}
769
+ `);
770
+ process.exit(1);
771
+ }
772
+ writeState(root, result.state);
773
+ const c = caps();
774
+ process.stdout.write(`
775
+ ${feedback(c, "ok", `\uC804\uD658 ${id}`)}
776
+ `);
777
+ if (result.warning) {
778
+ process.stderr.write(` ${result.warning}
779
+ `);
780
+ }
781
+ }
782
+ function runWorkAbandon(id) {
783
+ const root = requireRoot();
784
+ const now = (/* @__PURE__ */ new Date()).toISOString();
785
+ const result = abandonWorkitem(loadState(root), id, now);
786
+ if (result.error) {
787
+ process.stderr.write(`
788
+ ${result.error}
789
+ `);
790
+ process.exit(1);
791
+ }
792
+ writeState(root, result.state);
793
+ const c = caps();
794
+ process.stdout.write(
795
+ `
796
+ ${feedback(c, "ok", `\uC911\uB2E8 \uCC98\uB9AC ${id}`, "\uAE30\uB85D\uC740 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4 (\uC0AD\uC81C\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)")}
797
+ `
798
+ );
799
+ }
800
+ async function worktreeDirtyTracked(root, targetPath) {
801
+ const status = await run({
802
+ cmd: "git",
803
+ args: ["-C", targetPath, "status", "--porcelain", "--untracked-files=no"],
804
+ cwd: root,
805
+ timeoutMs: 1e4
806
+ });
807
+ const lines = status.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
808
+ return { dirty: lines.length > 0, count: lines.length, first: lines[0] };
809
+ }
810
+ async function removeWorktreeDir(root, targetPath, force) {
811
+ if (!force) {
812
+ const d = await worktreeDirtyTracked(root, targetPath);
813
+ if (d.dirty) {
814
+ return { ok: false, error: `\uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD ${d.count}\uAC74 (\uC608: ${d.first})` };
815
+ }
816
+ }
817
+ const r = await run({
818
+ cmd: "git",
819
+ args: ["worktree", "remove", "--force", targetPath],
820
+ cwd: root,
821
+ timeoutMs: 3e4
822
+ });
823
+ if (r.exitCode !== 0) {
824
+ return { ok: false, error: (r.stderr || r.stdout).trim() };
825
+ }
826
+ return { ok: true };
827
+ }
828
+ async function runWorkDone(id, opts = {}) {
829
+ const root = requireRoot();
830
+ const now = (/* @__PURE__ */ new Date()).toISOString();
831
+ const result = markWorkitemDone(loadState(root), id, now);
832
+ if (result.error) {
833
+ process.stderr.write(`
834
+ ${result.error}
835
+ `);
836
+ process.exit(1);
837
+ }
838
+ const c = caps();
839
+ const color = makeColors(c.color);
840
+ let worktreeNote = null;
841
+ let mergeNote = null;
842
+ const isolatedHome = result.worktree ? path2.join(result.worktree.path, ".awl", "home") : path2.join(root, ".awl", "home");
843
+ try {
844
+ const merged = mergeIsolatedHome(isolatedHome);
845
+ if (merged && (merged.gotchasAdded > 0 || merged.rulesAdded > 0 || merged.generationsAdded > 0)) {
846
+ mergeNote = `\uD559\uC2B5 \uC804\uC5ED \uBCD1\uD569 gotcha ${merged.gotchasAdded} \xB7 rule ${merged.rulesAdded} \xB7 generation ${merged.generationsAdded}`;
847
+ }
848
+ } catch (e) {
849
+ process.stderr.write(
850
+ `
851
+ ${feedback(c, "error", "\uACA9\uB9AC \uD559\uC2B5 \uC804\uC5ED \uBCD1\uD569 \uC2E4\uD328 \u2014 \uC644\uB8CC\uB97C \uC911\uB2E8\uD569\uB2C8\uB2E4", e instanceof Error ? e.message : String(e))}
852
+ `
853
+ );
854
+ process.exit(1);
855
+ }
856
+ if (result.worktree && fs2.existsSync(result.worktree.path)) {
857
+ const removed = await removeWorktreeDir(root, result.worktree.path, opts.force ?? false);
858
+ if (!removed.ok) {
859
+ process.stderr.write(
860
+ `
861
+ ${feedback(c, "warn", "\uC6CC\uD06C\uD2B8\uB9AC\uB97C \uC815\uB9AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4", removed.error)}
862
+ \uC815\uB9AC\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC744 \uD655\uC778\uD558\uAC70\uB098 awl work done ${id} --force \uB85C \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.
863
+ (\uBE0C\uB79C\uCE58\uB294 \uC9C0\uC6B0\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uCEE4\uBC0B\uB41C \uC791\uC5C5\uC740 \uC548\uC804\uD569\uB2C8\uB2E4.)
864
+ `
865
+ );
866
+ process.exit(1);
867
+ }
868
+ worktreeNote = `\uC6CC\uD06C\uD2B8\uB9AC \uC81C\uAC70 ${result.worktree.path}`;
869
+ }
870
+ writeState(root, result.state);
871
+ process.stdout.write(
872
+ `
873
+ ${feedback(c, "ok", `\uC644\uB8CC \uCC98\uB9AC ${color.bold(id)}`, "\uC0C1\uD0DC \uC2A4\uB0C5\uC0F7\uC744 \uD68C\uC218\uD588\uC2B5\uB2C8\uB2E4 (\uAE30\uB85D\uC740 \uB0A8\uC2B5\uB2C8\uB2E4)")}
874
+ `
875
+ );
876
+ if (worktreeNote) {
877
+ process.stdout.write(` ${color.dim(worktreeNote)}
878
+ `);
879
+ }
880
+ if (mergeNote) {
881
+ process.stdout.write(` ${color.dim(mergeNote)}
882
+ `);
883
+ }
884
+ }
885
+
886
+ export {
887
+ mergeIsolatedHome,
888
+ summarizeWorkitems,
889
+ renderWorkList,
890
+ createWorkitem,
891
+ restoreWorkitem,
892
+ abandonWorkitem,
893
+ markWorkitemDone,
894
+ removeWorkitemFromState,
895
+ runWorkList,
896
+ sanitizeForGit,
897
+ removeGitWorktree,
898
+ withCostAtStart,
899
+ runWorkNew,
900
+ runWorkSwitch,
901
+ runWorkAbandon,
902
+ worktreeDirtyTracked,
903
+ runWorkDone
904
+ };