agent-work-loop 0.6.26 → 0.6.30

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 (47) hide show
  1. package/README.md +28 -0
  2. package/dist/{brief-JIXESFMO.js → brief-O6IGLOPY.js} +5 -5
  3. package/dist/{changelog-PBF6JBOC.js → changelog-4XKSWJX2.js} +5 -5
  4. package/dist/{chunk-PUI2B27T.js → chunk-3CR74VLL.js} +111 -14
  5. package/dist/{chunk-YODF2HTK.js → chunk-45X7T64Y.js} +12 -37
  6. package/dist/{chunk-F34MITMC.js → chunk-7WDDWO2B.js} +3 -3
  7. package/dist/{chunk-XD4IDIEM.js → chunk-7WE2E76P.js} +6 -6
  8. package/dist/{chunk-2YWYGWCZ.js → chunk-AYFHV27J.js} +70 -32
  9. package/dist/{chunk-JDCW2Y2G.js → chunk-FJKJTXN4.js} +7 -7
  10. package/dist/{chunk-NXH5EBBJ.js → chunk-FOTWJOWF.js} +8 -8
  11. package/dist/{chunk-OPPRCJT2.js → chunk-FUP7DKT3.js} +7 -7
  12. package/dist/{chunk-AMQFBJCO.js → chunk-IEFYCUWX.js} +18 -8
  13. package/dist/{chunk-SQFGUNTY.js → chunk-ILXQGAUE.js} +5 -5
  14. package/dist/{chunk-KEVDO3C3.js → chunk-R4LFOY5M.js} +9 -9
  15. package/dist/{chunk-B6AEYNF3.js → chunk-RAMHRXFX.js} +40 -23
  16. package/dist/{chunk-PLHEP5QE.js → chunk-RXG6BUQ6.js} +5 -5
  17. package/dist/{chunk-6DXZYTDA.js → chunk-TTLFDLK6.js} +6 -6
  18. package/dist/{chunk-XQ7JJXBY.js → chunk-VX23A4FW.js} +7 -7
  19. package/dist/{chunk-FQLRA2YC.js → chunk-WZCIAGNJ.js} +1 -1
  20. package/dist/cli.js +61 -53
  21. package/dist/{commit-OUZNLCNL.js → commit-2UFIIERX.js} +6 -6
  22. package/dist/{config-26FHYODU.js → config-D7LUIZYA.js} +3 -3
  23. package/dist/{doctor-UHUA6ULF.js → doctor-IAK57PIS.js} +8 -8
  24. package/dist/{evolve-V5BQCSHC.js → evolve-RMKOBGSK.js} +7 -7
  25. package/dist/{feedback-UJ7WSARN.js → feedback-RT63F57C.js} +8 -8
  26. package/dist/{gotchas-L3NXDDDI.js → gotchas-DM7BXF77.js} +11 -11
  27. package/dist/{hold-recheck-QINLKCPM.js → hold-recheck-I6JMGTP6.js} +18 -18
  28. package/dist/{init-6R4MT6FQ.js → init-XUJTEY2Y.js} +6 -2
  29. package/dist/lane-OZ5WGGDT.js +41 -0
  30. package/dist/{loop-summary-FVJICIWA.js → loop-summary-7HMFHKUD.js} +10 -10
  31. package/dist/{metrics-NVJFKQ4J.js → metrics-F3YDMURU.js} +4 -4
  32. package/dist/{record-JJJSGU5E.js → record-ZNGMMZHF.js} +5 -5
  33. package/dist/{review-I4QMZPTI.js → review-R5I4KYFN.js} +16 -16
  34. package/dist/{rules-OTUONMXO.js → rules-GVDXKROQ.js} +8 -8
  35. package/dist/{state-UC45ECTI.js → state-B45D6VSF.js} +4 -4
  36. package/dist/status-7SBNSHTV.js +40 -0
  37. package/dist/{uninstall-HHKLOXF5.js → uninstall-Q6KJYH5E.js} +17 -17
  38. package/dist/update-WHHGVXIY.js +135 -0
  39. package/dist/{verify-A64P77RU.js → verify-ZBDWID5U.js} +10 -10
  40. package/dist/version-check-3NVRLYUF.js +14 -0
  41. package/dist/{work-LMM5W2PQ.js → work-EVYCDIPE.js} +14 -14
  42. package/engine/version.json +1 -1
  43. package/package.json +1 -1
  44. package/dist/lane-CJVXEV3A.js +0 -41
  45. package/dist/status-TU36KJDH.js +0 -40
  46. package/dist/update-BXRWE7OH.js +0 -61
  47. package/dist/version-check-UBXBVPTS.js +0 -14
package/README.md CHANGED
@@ -43,6 +43,34 @@ yarn global add agent-work-loop
43
43
  > ```
44
44
  > `dist/cli.js`가 `awl` bin으로 등록됩니다. 정식 npm 배포가 되면 이 문단은 지웁니다.
45
45
 
46
+ ### 업데이트 하기
47
+
48
+ ```
49
+ 업데이트 하기:
50
+ 1. npm i -g agent-work-loop@latest npm에 배포된 최신 버전을 받습니다
51
+ 2. awl update 설치된 패키지로 전역 엔진(~/.awl/engine)을 갱신합니다
52
+ ```
53
+
54
+ pnpm이나 yarn을 쓴다면 1번만 그 도구로 바꿉니다:
55
+
56
+ ```bash
57
+ pnpm add -g agent-work-loop@latest
58
+ yarn global add agent-work-loop@latest
59
+ ```
60
+
61
+ `awl update`(옵션 없음)는 `awl update --global`과 같습니다. 전역 엔진만 갱신하고 어떤 프로젝트도 건드리지 않습니다. 대부분은 이 두 줄이면 끝입니다.
62
+
63
+ **여러 프로젝트를 관리하는 입장이라면** 등록된 프로젝트들의 로컬 스킬(`.claude/skills`, `AGENTS.md`, `.awl/config.json`)까지 한 번에 맞추고 싶을 수 있습니다:
64
+
65
+ ```bash
66
+ npm i -g agent-work-loop@latest
67
+ awl update --all
68
+ ```
69
+
70
+ `--all`은 전역 엔진과 `~/.awl/projects.json`에 등록된 프로젝트 전부의 로컬 스킬을 갱신하고, 끝에 바뀐 프로젝트 목록과 "커밋하세요" 안내를 보여줍니다. 등록된 프로젝트만 로컬을 건드리고 싶으면 `--local`만 씁니다.
71
+
72
+ 이렇게 갱신한 프로젝트를 커밋해서 push하면, 그 저장소를 쓰는 다른 사람들은 다음에 pull할 때 최신 스킬을 받습니다. **다만 자동으로 알림이 가는 구조는 아닙니다.** 각자 컴퓨터의 `~/.awl/engine`이 낡았는지는 그 사람이 `awl doctor`/`awl version-check`를 실행하거나 `/awl-loop`·`/awl-pipeline`을 실행할 때(버전 확인이 맨 처음 단계로 들어있습니다) 노란 경고로 보게 됩니다.
73
+
46
74
  설치가 됐는지 확인합니다:
47
75
 
48
76
  ```bash
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  loadProjectName,
3
3
  readRecords
4
- } from "./chunk-6DXZYTDA.js";
4
+ } from "./chunk-TTLFDLK6.js";
5
5
  import {
6
6
  loadState
7
- } from "./chunk-XD4IDIEM.js";
7
+ } from "./chunk-7WE2E76P.js";
8
8
  import {
9
9
  resolveProjectRoot,
10
10
  run
11
- } from "./chunk-B6AEYNF3.js";
12
- import "./chunk-2YWYGWCZ.js";
13
- import "./chunk-YODF2HTK.js";
11
+ } from "./chunk-RAMHRXFX.js";
12
+ import "./chunk-AYFHV27J.js";
13
+ import "./chunk-45X7T64Y.js";
14
14
 
15
15
  // src/commands/brief.ts
16
16
  var KST_OFFSET_MS = 9 * 60 * 60 * 1e3;
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  readRecords
3
- } from "./chunk-6DXZYTDA.js";
3
+ } from "./chunk-TTLFDLK6.js";
4
4
  import {
5
5
  loadState
6
- } from "./chunk-XD4IDIEM.js";
6
+ } from "./chunk-7WE2E76P.js";
7
7
  import {
8
8
  resolveProjectRoot
9
- } from "./chunk-B6AEYNF3.js";
10
- import "./chunk-2YWYGWCZ.js";
9
+ } from "./chunk-RAMHRXFX.js";
10
+ import "./chunk-AYFHV27J.js";
11
11
  import {
12
12
  caps,
13
13
  signal
14
- } from "./chunk-YODF2HTK.js";
14
+ } from "./chunk-45X7T64Y.js";
15
15
 
16
16
  // src/commands/changelog.ts
17
17
  function runChangelogDraft(opts) {
@@ -7,39 +7,42 @@ import {
7
7
  isCheckPassed,
8
8
  runVerifyChecks,
9
9
  writeVerifyBaseline
10
- } from "./chunk-XQ7JJXBY.js";
10
+ } from "./chunk-VX23A4FW.js";
11
11
  import {
12
12
  gitBranch
13
- } from "./chunk-NXH5EBBJ.js";
13
+ } from "./chunk-FOTWJOWF.js";
14
14
  import {
15
15
  parseRuleFile
16
- } from "./chunk-PLHEP5QE.js";
16
+ } from "./chunk-RXG6BUQ6.js";
17
17
  import {
18
18
  nextGotchaId
19
- } from "./chunk-SQFGUNTY.js";
19
+ } from "./chunk-ILXQGAUE.js";
20
+ import {
21
+ loadProjectName
22
+ } from "./chunk-TTLFDLK6.js";
20
23
  import {
21
24
  loadState,
22
25
  migrateState,
23
26
  writeState
24
- } from "./chunk-XD4IDIEM.js";
27
+ } from "./chunk-7WE2E76P.js";
25
28
  import {
26
29
  loadConfig,
27
30
  resolveProjectRoot,
28
31
  run
29
- } from "./chunk-B6AEYNF3.js";
32
+ } from "./chunk-RAMHRXFX.js";
30
33
  import {
31
34
  installClaudeSkill
32
- } from "./chunk-2YWYGWCZ.js";
35
+ } from "./chunk-AYFHV27J.js";
33
36
  import {
34
37
  caps,
35
- card,
36
38
  feedback,
37
39
  globalRoot,
38
40
  makeColors,
39
41
  makeSymbols,
40
42
  makeTokens,
43
+ sectionBox,
41
44
  signal
42
- } from "./chunk-YODF2HTK.js";
45
+ } from "./chunk-45X7T64Y.js";
43
46
 
44
47
  // src/commands/work.ts
45
48
  import fs2 from "fs";
@@ -213,6 +216,88 @@ function mergeIsolatedLearning(fromRoot, toRoot) {
213
216
  const generationsAdded = copyGenerations(fromRoot, toRoot);
214
217
  return { gotchasAdded: added.length, rulesAdded, generationsAdded };
215
218
  }
219
+ function recordFiles(root) {
220
+ const dir = path.join(root, "records");
221
+ try {
222
+ return fs.readdirSync(dir).filter((f) => f.endsWith(".jsonl")).sort();
223
+ } catch {
224
+ return [];
225
+ }
226
+ }
227
+ function monthOf(at) {
228
+ return at.slice(0, 7);
229
+ }
230
+ function copyRecordDiffs(fromRoot, toRoot) {
231
+ const fromDir = path.join(fromRoot, "records", "diffs");
232
+ if (!fs.existsSync(fromDir)) {
233
+ return;
234
+ }
235
+ const toDir = path.join(toRoot, "records", "diffs");
236
+ for (const f of fs.readdirSync(fromDir)) {
237
+ const dest = path.join(toDir, f);
238
+ if (!fs.existsSync(dest)) {
239
+ fs.mkdirSync(toDir, { recursive: true });
240
+ fs.copyFileSync(path.join(fromDir, f), dest);
241
+ }
242
+ }
243
+ }
244
+ function mergeIsolatedRecords(fromRoot, toRoot) {
245
+ let recordsMerged = 0;
246
+ for (const f of recordFiles(fromRoot)) {
247
+ const content = fs.readFileSync(path.join(fromRoot, "records", f), "utf8");
248
+ for (const line of content.split("\n")) {
249
+ const trimmed = line.trim();
250
+ if (trimmed === "") {
251
+ continue;
252
+ }
253
+ let record;
254
+ try {
255
+ record = JSON.parse(trimmed);
256
+ } catch {
257
+ continue;
258
+ }
259
+ const at = typeof record.at === "string" ? record.at : void 0;
260
+ if (!at) {
261
+ continue;
262
+ }
263
+ const dest = path.join(toRoot, "records", `${monthOf(at)}.jsonl`);
264
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
265
+ fs.appendFileSync(dest, `${trimmed}
266
+ `);
267
+ recordsMerged += 1;
268
+ }
269
+ }
270
+ if (recordsMerged > 0) {
271
+ copyRecordDiffs(fromRoot, toRoot);
272
+ }
273
+ return { recordsMerged };
274
+ }
275
+ function archiveIsolatedRecords(fromRoot, toRoot, meta) {
276
+ const lines = [];
277
+ for (const f of recordFiles(fromRoot)) {
278
+ const content = fs.readFileSync(path.join(fromRoot, "records", f), "utf8");
279
+ for (const line of content.split("\n")) {
280
+ if (line.trim() !== "") {
281
+ lines.push(line);
282
+ }
283
+ }
284
+ }
285
+ if (lines.length === 0) {
286
+ return null;
287
+ }
288
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
289
+ const dir = path.join(toRoot, "records", "archive", meta.project);
290
+ fs.mkdirSync(dir, { recursive: true });
291
+ let dest = path.join(dir, `${date}-${meta.lane}.jsonl`);
292
+ let n = 2;
293
+ while (fs.existsSync(dest)) {
294
+ dest = path.join(dir, `${date}-${meta.lane}-${n}.jsonl`);
295
+ n += 1;
296
+ }
297
+ fs.writeFileSync(dest, `${lines.join("\n")}
298
+ `);
299
+ return dest;
300
+ }
216
301
  function writeParentMarker(isolatedHome) {
217
302
  try {
218
303
  fs.writeFileSync(path.join(isolatedHome, PARENT_MARKER), `${globalRoot()}
@@ -230,7 +315,7 @@ function resolveParentGlobal(isolatedHome) {
230
315
  }
231
316
  return globalRoot();
232
317
  }
233
- function mergeIsolatedHome(isolatedHome) {
318
+ function mergeIsolatedHome(isolatedHome, meta) {
234
319
  if (!fs.existsSync(isolatedHome)) {
235
320
  return null;
236
321
  }
@@ -238,7 +323,10 @@ function mergeIsolatedHome(isolatedHome) {
238
323
  if (path.resolve(isolatedHome) === path.resolve(toRoot)) {
239
324
  return null;
240
325
  }
241
- return mergeIsolatedLearning(isolatedHome, toRoot);
326
+ const learning = mergeIsolatedLearning(isolatedHome, toRoot);
327
+ const recordsArchivePath = archiveIsolatedRecords(isolatedHome, toRoot, meta);
328
+ const { recordsMerged } = mergeIsolatedRecords(isolatedHome, toRoot);
329
+ return { ...learning, recordsMerged, recordsArchivePath };
242
330
  }
243
331
 
244
332
  // src/commands/work.ts
@@ -299,7 +387,7 @@ function renderWorkList(list, c) {
299
387
  const t = makeTokens(c);
300
388
  const s = makeSymbols(c);
301
389
  if (list.length === 0) {
302
- return card(
390
+ return sectionBox(
303
391
  "\uC6CC\uD06C\uC544\uC774\uD15C",
304
392
  [
305
393
  `${signal(c, "info")} \uB4F1\uB85D\uB41C \uC6CC\uD06C\uC544\uC774\uD15C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`,
@@ -324,7 +412,7 @@ function renderWorkList(list, c) {
324
412
  out.push(` ${s.lastBranch} ${color.dim(`worktree: ${w.worktreePath}`)}`);
325
413
  }
326
414
  }
327
- return card("\uC6CC\uD06C\uC544\uC774\uD15C", out, c);
415
+ return sectionBox("\uC6CC\uD06C\uC544\uC774\uD15C", out, c);
328
416
  }
329
417
  function registryOf(state) {
330
418
  return state.workitems && typeof state.workitems === "object" ? state.workitems : {};
@@ -839,12 +927,17 @@ async function runWorkDone(id, opts = {}) {
839
927
  const color = makeColors(c.color);
840
928
  let worktreeNote = null;
841
929
  let mergeNote = null;
930
+ let recordsNote = null;
842
931
  const isolatedHome = result.worktree ? path2.join(result.worktree.path, ".awl", "home") : path2.join(root, ".awl", "home");
843
932
  try {
844
- const merged = mergeIsolatedHome(isolatedHome);
933
+ const project = loadProjectName(root) ?? path2.basename(root);
934
+ const merged = mergeIsolatedHome(isolatedHome, { project, lane: id });
845
935
  if (merged && (merged.gotchasAdded > 0 || merged.rulesAdded > 0 || merged.generationsAdded > 0)) {
846
936
  mergeNote = `\uD559\uC2B5 \uC804\uC5ED \uBCD1\uD569 gotcha ${merged.gotchasAdded} \xB7 rule ${merged.rulesAdded} \xB7 generation ${merged.generationsAdded}`;
847
937
  }
938
+ if (merged && merged.recordsMerged > 0) {
939
+ recordsNote = `records \uC804\uC5ED \uBCD1\uD569 ${merged.recordsMerged}\uAC74${merged.recordsArchivePath ? ` \xB7 \uC544\uCE74\uC774\uBE0C ${merged.recordsArchivePath}` : ""}`;
940
+ }
848
941
  } catch (e) {
849
942
  process.stderr.write(
850
943
  `
@@ -879,6 +972,10 @@ ${feedback(c, "ok", `\uC644\uB8CC \uCC98\uB9AC ${color.bold(id)}`, "\uC0C1\uD0D
879
972
  }
880
973
  if (mergeNote) {
881
974
  process.stdout.write(` ${color.dim(mergeNote)}
975
+ `);
976
+ }
977
+ if (recordsNote) {
978
+ process.stdout.write(` ${color.dim(recordsNote)}
882
979
  `);
883
980
  }
884
981
  }
@@ -390,31 +390,6 @@ function viewportWidth() {
390
390
  }
391
391
  return HARD_MAX_WIDTH;
392
392
  }
393
- function card(title, lines, c = caps(), minInnerWidth = 0) {
394
- const s = makeSymbols(c);
395
- const t = makeTokens(c);
396
- const pad = 2;
397
- const maxInner = Math.max(
398
- minInnerWidth,
399
- Math.min(HARD_MAX_WIDTH, viewportWidth() - (2 + pad * 2))
400
- );
401
- const wrapped = lines.flatMap((line) => wrapToWidth(line, maxInner, { hanging: true }));
402
- const inner = Math.min(
403
- maxInner,
404
- Math.max(minInnerWidth, visibleWidth(title), ...wrapped.map(visibleWidth), 0)
405
- );
406
- const frame = (text) => t.frame(text);
407
- const row = (text) => {
408
- const gap = Math.max(0, inner - visibleWidth(text));
409
- return `${frame(s.boxV)}${" ".repeat(pad)}${text}${" ".repeat(gap + pad)}${frame(s.boxV)}`;
410
- };
411
- const safeTitle = truncateToWidth(title, inner);
412
- const tvis = visibleWidth(safeTitle);
413
- const dash = Math.max(1, inner + pad * 2 - 3 - tvis);
414
- const top = frame(`${s.boxTL}${s.boxH} `) + t.emphasis(t.accent(safeTitle)) + frame(` ${s.boxH.repeat(dash)}${s.boxTR}`);
415
- const bottom = frame(`${s.boxBL}${s.boxH.repeat(inner + pad * 2)}${s.boxBR}`);
416
- return [top, ...wrapped.map(row), bottom].join("\n");
417
- }
418
393
  function sectionBox(title, lines, c = caps(), minInnerWidth = 0) {
419
394
  const s = makeSymbols(c);
420
395
  const t = makeTokens(c);
@@ -424,17 +399,18 @@ function sectionBox(title, lines, c = caps(), minInnerWidth = 0) {
424
399
  Math.min(HARD_MAX_WIDTH, viewportWidth() - (2 + pad * 2))
425
400
  );
426
401
  const wrapped = lines.flatMap((line) => wrapToWidth(line, maxInner, { hanging: true }));
427
- const inner = Math.min(
428
- maxInner,
429
- Math.max(minInnerWidth, visibleWidth(title), ...wrapped.map(visibleWidth), 0)
430
- );
431
402
  const frame = (text) => t.frame(text);
432
- const row = (text) => `${frame(s.boxV)}${" ".repeat(pad)}${text}`;
433
- const safeTitle = truncateToWidth(title, inner);
434
- const tvis = visibleWidth(safeTitle);
435
- const dash = Math.max(1, inner + pad * 2 - 3 - tvis);
436
- const top = frame(`${s.boxTL}${s.boxH} `) + t.emphasis(t.accent(safeTitle)) + frame(` ${s.boxH.repeat(dash)}`);
437
- return [top, ...wrapped.map(row)].join("\n");
403
+ const lastLogical = lines[lines.length - 1] ?? "";
404
+ const lastIsBranch = lastLogical.trimStart().startsWith(s.lastBranch);
405
+ const row = (text, isLast) => {
406
+ const closeThis = isLast && !lastIsBranch;
407
+ const left = closeThis ? `${s.boxBL}${s.boxH}` : s.boxV;
408
+ const gap = closeThis ? pad - 1 : pad;
409
+ return `${frame(left)}${" ".repeat(gap)}${text}`;
410
+ };
411
+ const safeTitle = truncateToWidth(title, maxInner);
412
+ const top = frame(`${s.boxTL}${s.boxH} `) + t.emphasis(t.accent(safeTitle));
413
+ return [top, ...wrapped.map((line, i) => row(line, i === wrapped.length - 1))].join("\n");
438
414
  }
439
415
  var RESET = "\x1B[0m";
440
416
  function ansi(code, str, enabled) {
@@ -455,7 +431,7 @@ function makeTokens(c) {
455
431
  }
456
432
  function signal(c, kind) {
457
433
  const color2 = makeColors(c.color);
458
- const raw = c.unicode ? { ok: "\u2705", warn: "\u26A0\uFE0F", error: "\u274C", info: "\u2139\uFE0F" }[kind] : { ok: "[ok]", warn: "[!]", error: "[x]", info: "[i]" }[kind];
434
+ const raw = { ok: "[ok]", warn: "[!]", error: "[x]", info: "[i]" }[kind];
459
435
  if (kind === "ok") {
460
436
  return color2.green(raw);
461
437
  }
@@ -536,7 +512,6 @@ export {
536
512
  clipToWidth,
537
513
  padEndDisplay,
538
514
  makeSymbols,
539
- card,
540
515
  sectionBox,
541
516
  makeTokens,
542
517
  signal,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  npmVersionCachePath
3
- } from "./chunk-YODF2HTK.js";
3
+ } from "./chunk-45X7T64Y.js";
4
4
 
5
5
  // package.json
6
6
  var name = "agent-work-loop";
7
- var version = "0.6.26";
7
+ var version = "0.6.30";
8
8
 
9
9
  // src/core/npm-registry.ts
10
10
  import fs from "fs";
@@ -80,7 +80,7 @@ function readCachedLatestVersion() {
80
80
  }
81
81
 
82
82
  // src/core/versions.ts
83
- var UPDATE_HINT = "npm i -g agent-work-loop@latest \uB85C \uAC31\uC2E0\uD558\uC138\uC694.";
83
+ var UPDATE_HINT = "npm i -g agent-work-loop@latest \uB85C \uAC31\uC2E0\uD55C \uB4A4 awl update \uB97C \uC2E4\uD589\uD558\uC138\uC694.";
84
84
  function parseSemver(version2) {
85
85
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(version2);
86
86
  if (!m) {
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  resolveProjectRoot
3
- } from "./chunk-B6AEYNF3.js";
3
+ } from "./chunk-RAMHRXFX.js";
4
4
  import {
5
5
  caps,
6
- card,
7
- makeColors
8
- } from "./chunk-YODF2HTK.js";
6
+ makeColors,
7
+ sectionBox
8
+ } from "./chunk-45X7T64Y.js";
9
9
 
10
10
  // src/commands/state.ts
11
11
  import fs from "fs";
@@ -189,14 +189,14 @@ function requireRoot() {
189
189
  function renderState(state, c) {
190
190
  const color = makeColors(c.color);
191
191
  if (Object.keys(state).length === 0) {
192
- return card("\uD604\uC7AC \uC0C1\uD0DC", ["\uC0C1\uD0DC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."], c);
192
+ return sectionBox("\uD604\uC7AC \uC0C1\uD0DC", ["\uC0C1\uD0DC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."], c);
193
193
  }
194
194
  const out = [];
195
195
  for (const [k, v] of Object.entries(state)) {
196
196
  const val = typeof v === "object" ? JSON.stringify(v) : String(v);
197
197
  out.push(`${k.padEnd(14, " ")}${color.dim(val)}`);
198
198
  }
199
- return card("\uD604\uC7AC \uC0C1\uD0DC", out, c);
199
+ return sectionBox("\uD604\uC7AC \uC0C1\uD0DC", out, c);
200
200
  }
201
201
  function runStateGet(opts) {
202
202
  const state = loadState(requireRoot());
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  caps,
3
- card,
4
3
  engineDir,
5
4
  globalRoot,
6
5
  installedEngineVersion,
@@ -13,7 +12,7 @@ import {
13
12
  sectionBox,
14
13
  signal,
15
14
  stringWidth
16
- } from "./chunk-YODF2HTK.js";
15
+ } from "./chunk-45X7T64Y.js";
17
16
 
18
17
  // src/commands/init.ts
19
18
  import fs from "fs";
@@ -312,7 +311,7 @@ function anyWorkspaceMemberHasTsconfig(cwd, pkg) {
312
311
  }
313
312
  return false;
314
313
  }
315
- function detectLanguage(cwd) {
314
+ function detectJsLanguage(cwd) {
316
315
  if (exists(path.join(cwd, "tsconfig.json"))) {
317
316
  return "typescript";
318
317
  }
@@ -324,11 +323,25 @@ function detectLanguage(cwd) {
324
323
  }
325
324
  return "javascript";
326
325
  }
327
- if (exists(path.join(cwd, "pyproject.toml")) || exists(path.join(cwd, "setup.py")) || exists(path.join(cwd, "requirements.txt"))) {
328
- return "python";
329
- }
330
326
  return null;
331
327
  }
328
+ function hasPythonMarkers(cwd) {
329
+ return exists(path.join(cwd, "pyproject.toml")) || exists(path.join(cwd, "setup.py")) || exists(path.join(cwd, "requirements.txt"));
330
+ }
331
+ function detectLanguage(cwd) {
332
+ return detectJsLanguage(cwd) ?? (hasPythonMarkers(cwd) ? "python" : null);
333
+ }
334
+ function detectLanguages(cwd) {
335
+ const out = [];
336
+ const js = detectJsLanguage(cwd);
337
+ if (js) {
338
+ out.push(js);
339
+ }
340
+ if (hasPythonMarkers(cwd)) {
341
+ out.push("python");
342
+ }
343
+ return out;
344
+ }
332
345
  function splitEnv(script) {
333
346
  const env = {};
334
347
  let rest = script.trim();
@@ -609,7 +622,7 @@ function nonInteractiveInputs(projectRoot) {
609
622
  const skills = detected.claude || detected.codex ? detected : { claude: true, codex: false };
610
623
  return {
611
624
  project: path.basename(projectRoot),
612
- mainLanguage: detectLanguage(projectRoot) ?? "",
625
+ mainLanguage: detectLanguages(projectRoot),
613
626
  character: "",
614
627
  verify: detectVerify(projectRoot),
615
628
  skills
@@ -737,14 +750,17 @@ function renderResult(result, inputs, c) {
737
750
  }
738
751
  var LANG_OPTIONS = ["TypeScript", "JavaScript", "Python", "\uC9C1\uC811 \uC785\uB825"];
739
752
  var LANG_VALUES = ["typescript", "javascript", "python", ""];
740
- function langDefaultIndex(projectRoot) {
741
- const detected = detectLanguage(projectRoot);
742
- const idx = detected ? LANG_VALUES.indexOf(detected) : 0;
743
- return idx < 0 ? 0 : idx;
753
+ function langDefaultIndices(projectRoot) {
754
+ const detected = detectLanguages(projectRoot);
755
+ if (detected.length === 0) {
756
+ return [0];
757
+ }
758
+ const indices = detected.map((lang) => LANG_VALUES.indexOf(lang)).filter((i) => i >= 0);
759
+ return indices.length > 0 ? indices : [0];
744
760
  }
745
761
  function buildScreens(projectRoot, hasGlobal, c) {
746
762
  const project = path.basename(projectRoot);
747
- const defLang = langDefaultIndex(projectRoot);
763
+ const defLangIndices = langDefaultIndices(projectRoot);
748
764
  const verify = detectVerify(projectRoot);
749
765
  const agents = detectAgents(projectRoot);
750
766
  const welcome = hasGlobal ? null : [
@@ -761,12 +777,13 @@ function buildScreens(projectRoot, hasGlobal, c) {
761
777
  " \uADDC\uCE59\uACFC \uAD50\uD6C8\uC740 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2C8\uB77C \uB2F9\uC2E0\uC5D0\uAC8C \uC313\uC774\uACE0,",
762
778
  " \uB4F1\uB85D\uB41C \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uD568\uAED8 \uC4F0\uC785\uB2C8\uB2E4."
763
779
  ].join("\n");
780
+ const detectedLangLabels = defLangIndices.map((i) => LANG_OPTIONS[i] ?? "TypeScript").join(", ");
764
781
  const langLines = [
765
- `\uC790\uB3D9 \uAC10\uC9C0: ${LANG_OPTIONS[defLang] ?? "TypeScript"}`,
782
+ `\uC790\uB3D9 \uAC10\uC9C0: ${detectedLangLabels || "TypeScript"}`,
766
783
  "",
767
- "\uBC14\uB85C \uC544\uB798\uC758 \uC120\uD0DD\uAE30\uC5D0\uC11C \uACE0\uB985\uB2C8\uB2E4.",
768
- "\uD654\uC0B4\uD45C \uB610\uB294 j/k \uB85C \uC774\uB3D9\uD558\uACE0 Enter \uB85C \uD655\uC815\uD558\uC138\uC694.",
769
- "\uD0A4 \uC785\uB825\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD130\uBBF8\uB110\uC5D0\uC11C\uB294 \uBC88\uD638\uB97C \uC785\uB825\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
784
+ "\uC5EC\uB7EC \uAC1C\uB97C \uC4F0\uB294 \uD504\uB85C\uC81D\uD2B8\uBA74(\uC608: TypeScript \uD504\uB860\uD2B8 + Python \uBC31\uC5D4\uB4DC) \uC804\uBD80 \uACE0\uB974\uC138\uC694.",
785
+ "\u2191\u2193 \uB610\uB294 j/k \uB85C \uC774\uB3D9, Space \uB85C \uC120\uD0DD, Enter \uB85C \uD655\uC815\uD558\uC138\uC694.",
786
+ "\uD0A4 \uC785\uB825\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD130\uBBF8\uB110\uC5D0\uC11C\uB294 \uBC88\uD638\uB97C \uC27C\uD45C\uB85C \uC785\uB825\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
770
787
  ];
771
788
  return {
772
789
  welcome,
@@ -905,21 +922,19 @@ async function interactiveInputs(projectRoot, hasGlobal, c) {
905
922
  ${screens.lang}
906
923
 
907
924
  `);
908
- const rawLanguage = useRawMode ? await runInteractiveSelect(LANG_OPTIONS, langDefaultIndex(projectRoot), false, c, [], {
925
+ const defaultLangChecked = langDefaultIndices(projectRoot);
926
+ const rawLanguage = useRawMode ? await runInteractiveSelect(LANG_OPTIONS, 0, true, c, defaultLangChecked, {
909
927
  title: "\uC8FC \uC5B8\uC5B4",
910
- hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0"
928
+ hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Space \uC120\uD0DD \xB7 Enter \uD655\uC815 \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0"
911
929
  }) : null;
912
- const langIdx = rawLanguage?.index ?? (useRawMode ? langDefaultIndex(projectRoot) : await selectSingle(
913
- prompt(),
914
- LANG_OPTIONS,
915
- langDefaultIndex(projectRoot),
916
- c,
917
- false,
918
- "\uC8FC \uC5B8\uC5B4"
919
- ));
920
- let mainLanguage = LANG_VALUES[langIdx] ?? "";
921
- if (langIdx === LANG_OPTIONS.length - 1) {
922
- mainLanguage = (await ask(prompt(), " \uC8FC \uC5B8\uC5B4\uB97C \uC785\uB825\uD558\uC138\uC694: ")).trim();
930
+ const langChecked = rawLanguage?.checked ?? (useRawMode ? defaultLangChecked : await selectMulti(prompt(), LANG_OPTIONS, defaultLangChecked, c, false, "\uC8FC \uC5B8\uC5B4"));
931
+ const manualLangIdx = LANG_OPTIONS.length - 1;
932
+ const mainLanguage = langChecked.filter((i) => i !== manualLangIdx).map((i) => LANG_VALUES[i]).filter((v) => typeof v === "string" && v !== "");
933
+ if (langChecked.includes(manualLangIdx)) {
934
+ const typed = (await ask(prompt(), " \uC8FC \uC5B8\uC5B4\uB97C \uC785\uB825\uD558\uC138\uC694: ")).trim();
935
+ if (typed) {
936
+ mainLanguage.push(typed);
937
+ }
923
938
  }
924
939
  const rootVerify = detectVerify(projectRoot);
925
940
  const located = await promptVerifyLocation(
@@ -1001,7 +1016,7 @@ async function handleExistingConfig(rl, projectRoot, c, now) {
1001
1016
  process.stdout.write("\n .awl/config.json \uC774 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. \uD300\uC6D0\uC774 \uC124\uC815\uD574\uB450\uC5C8\uAD70\uC694.\n\n");
1002
1017
  process.stdout.write(` \uD504\uB85C\uC81D\uD2B8 ${config?.project ?? "(\uC5C6\uC74C)"}
1003
1018
  `);
1004
- process.stdout.write(` \uC8FC \uC5B8\uC5B4 ${config?.mainLanguage ?? "(\uC5C6\uC74C)"}
1019
+ process.stdout.write(` \uC8FC \uC5B8\uC5B4 ${config?.mainLanguage?.join(", ") || "(\uC5C6\uC74C)"}
1005
1020
  `);
1006
1021
  process.stdout.write(` \uC131\uACA9 ${config?.character || "(\uC5C6\uC74C)"}
1007
1022
  `);
@@ -1019,7 +1034,7 @@ async function handleExistingConfig(rl, projectRoot, c, now) {
1019
1034
  process.stdout.write(" \uC774 \uC124\uC815\uC744 \uADF8\uB300\uB85C \uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n");
1020
1035
  const options = ["\uADF8\uB300\uB85C \uC4F4\uB2E4", "\uAC80\uC99D \uBA85\uB839\uC5B4\uB9CC \uACE0\uCE5C\uB2E4", "\uCC98\uC74C\uBD80\uD130 \uB2E4\uC2DC"];
1021
1036
  process.stdout.write(
1022
- `${card(
1037
+ `${sectionBox(
1023
1038
  "\uAE30\uC874 \uC124\uC815",
1024
1039
  options.map((option, i) => `${i + 1}. ${option}`),
1025
1040
  c
@@ -1057,7 +1072,7 @@ async function handleExistingConfig(rl, projectRoot, c, now) {
1057
1072
  }
1058
1073
  const merged = {
1059
1074
  project: config?.project ?? path.basename(projectRoot),
1060
- mainLanguage: config?.mainLanguage ?? "",
1075
+ mainLanguage: config?.mainLanguage ?? [],
1061
1076
  character: config?.character ?? "",
1062
1077
  verify,
1063
1078
  skills: { claude: false, codex: false }
@@ -1106,6 +1121,27 @@ function registeredProjectPaths() {
1106
1121
  return /* @__PURE__ */ new Set();
1107
1122
  }
1108
1123
  }
1124
+ function listRegisteredProjects() {
1125
+ try {
1126
+ const raw = JSON.parse(fs.readFileSync(projectsFile(), "utf8"));
1127
+ if (!Array.isArray(raw)) {
1128
+ return [];
1129
+ }
1130
+ const out = [];
1131
+ for (const entry of raw) {
1132
+ if (entry && typeof entry === "object" && typeof entry.path === "string") {
1133
+ const e = entry;
1134
+ out.push({
1135
+ name: typeof e.name === "string" ? e.name : path.basename(e.path),
1136
+ path: e.path
1137
+ });
1138
+ }
1139
+ }
1140
+ return out;
1141
+ } catch {
1142
+ return [];
1143
+ }
1144
+ }
1109
1145
  function excludeRegisteredProjects(candidates, registered) {
1110
1146
  if (registered.size === 0) {
1111
1147
  return candidates;
@@ -1242,6 +1278,7 @@ ${renderResult(result, inputs, c)}
1242
1278
  export {
1243
1279
  scanGitProjects,
1244
1280
  detectLanguage,
1281
+ detectLanguages,
1245
1282
  splitEnv,
1246
1283
  detectVerify,
1247
1284
  detectWorkspacePackages,
@@ -1277,6 +1314,7 @@ export {
1277
1314
  applyVerifyCwd,
1278
1315
  resolveProjectChoice,
1279
1316
  registeredProjectPaths,
1317
+ listRegisteredProjects,
1280
1318
  excludeRegisteredProjects,
1281
1319
  runInit
1282
1320
  };
@@ -3,23 +3,23 @@ import {
3
3
  getLatestVersionCached,
4
4
  name,
5
5
  version
6
- } from "./chunk-F34MITMC.js";
6
+ } from "./chunk-7WDDWO2B.js";
7
7
  import {
8
8
  resolveProjectRoot
9
- } from "./chunk-B6AEYNF3.js";
9
+ } from "./chunk-RAMHRXFX.js";
10
10
  import {
11
11
  packageEngineDir,
12
12
  skillsVersionPath
13
- } from "./chunk-2YWYGWCZ.js";
13
+ } from "./chunk-AYFHV27J.js";
14
14
  import {
15
15
  caps,
16
- card,
17
16
  installedEngineVersion,
18
17
  makeColors,
19
18
  makeSymbols,
20
19
  makeTokens,
20
+ sectionBox,
21
21
  signal
22
- } from "./chunk-YODF2HTK.js";
22
+ } from "./chunk-45X7T64Y.js";
23
23
 
24
24
  // src/commands/version-check.ts
25
25
  import fs from "fs";
@@ -67,14 +67,14 @@ function renderVersionCheck(result, c) {
67
67
  const s = makeSymbols(c);
68
68
  const t = makeTokens(c);
69
69
  if (result.ok) {
70
- return card("\uBC84\uC804 \uD655\uC778", [`${signal(c, "ok")} \uBC84\uC804\uC774 \uC804\uBD80 \uC77C\uCE58\uD569\uB2C8\uB2E4.`], c);
70
+ return sectionBox("\uBC84\uC804 \uD655\uC778", [`${signal(c, "ok")} \uBC84\uC804\uC774 \uC804\uBD80 \uC77C\uCE58\uD569\uB2C8\uB2E4.`], c);
71
71
  }
72
72
  const out = [];
73
73
  for (const m of result.mismatches) {
74
74
  out.push(`${signal(c, "warn")} ${m.kind}: ${t.emphasis(m.a)} / ${t.emphasis(m.b)}`);
75
75
  out.push(`${s.lastBranch} ${color.dim(m.hint)}`);
76
76
  }
77
- return card(`\uBC84\uC804 \uBD88\uC77C\uCE58 ${result.mismatches.length}\uAC74`, out, c);
77
+ return sectionBox(`\uBC84\uC804 \uBD88\uC77C\uCE58 ${result.mismatches.length}\uAC74`, out, c);
78
78
  }
79
79
  async function runVersionCheck(opts) {
80
80
  const projectRoot = resolveProjectRoot();