agent-work-loop 0.6.27 → 0.6.31

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 +19 -8
  2. package/dist/{brief-TQND6XIK.js → brief-ZZSQ6RQS.js} +5 -5
  3. package/dist/{changelog-J4LP6ZJI.js → changelog-JWLVVHZL.js} +5 -5
  4. package/dist/{chunk-J7EKKHVW.js → chunk-22SUY5RE.js} +6 -6
  5. package/dist/{chunk-PIOV3MQT.js → chunk-2DAP3QBU.js} +70 -31
  6. package/dist/{chunk-H4EMS47F.js → chunk-3UPF7LZT.js} +15 -5
  7. package/dist/{chunk-M4H4VNN6.js → chunk-45X7T64Y.js} +12 -11
  8. package/dist/{chunk-OSKS2YRU.js → chunk-5APWNDRG.js} +2 -2
  9. package/dist/{chunk-UB4RLTLJ.js → chunk-77EZYCLF.js} +2 -2
  10. package/dist/{chunk-PFXDX4JF.js → chunk-7NOHUUXV.js} +108 -11
  11. package/dist/{chunk-DR7OZZ4N.js → chunk-DIRYW77S.js} +5 -5
  12. package/dist/{chunk-CLWQEWEG.js → chunk-JPFGMDQ4.js} +38 -21
  13. package/dist/{chunk-DGPZEEFJ.js → chunk-Q7V3WVKT.js} +4 -4
  14. package/dist/{chunk-GKKWXRQA.js → chunk-RNZCRXE3.js} +5 -5
  15. package/dist/{chunk-MLD2GOVM.js → chunk-RRQYSRRT.js} +5 -5
  16. package/dist/{chunk-MCNUMMKK.js → chunk-SV4D72NX.js} +3 -3
  17. package/dist/{chunk-KJG2ULS4.js → chunk-TTZY2GLK.js} +1 -1
  18. package/dist/{chunk-JXFQHNHV.js → chunk-YP4VCWPV.js} +3 -3
  19. package/dist/{chunk-KNBSZQGN.js → chunk-Z3QCPOOA.js} +2 -2
  20. package/dist/cli.js +60 -52
  21. package/dist/{commit-2WWILQRB.js → commit-3UZE7XJM.js} +6 -6
  22. package/dist/{config-XMM3V7MT.js → config-DAD4LIWR.js} +3 -3
  23. package/dist/{doctor-HZEAQNY7.js → doctor-AW65MZKN.js} +8 -8
  24. package/dist/{evolve-EQ3N6F6M.js → evolve-K4WKRA6U.js} +7 -7
  25. package/dist/{feedback-4FT67LSI.js → feedback-3C33YHKF.js} +5 -5
  26. package/dist/{gotchas-QJBUVYJV.js → gotchas-IGEQILOO.js} +7 -7
  27. package/dist/{hold-recheck-L3DAS3MV.js → hold-recheck-THSJKF5G.js} +16 -16
  28. package/dist/{init-PTTYGP2D.js → init-MRJEU7IR.js} +6 -2
  29. package/dist/lane-ZQVD7EDE.js +41 -0
  30. package/dist/{loop-summary-OSYJB57M.js → loop-summary-MPEKQ2EC.js} +6 -6
  31. package/dist/{metrics-BC7ZUCGU.js → metrics-64WBBQQX.js} +4 -4
  32. package/dist/{record-CKR6G6UZ.js → record-GAWMF7KL.js} +5 -5
  33. package/dist/{uninstall-JECZJNRJ.js → remove-ELP4MFVE.js} +19 -19
  34. package/dist/{review-UJH6C7F4.js → review-A5Q7NSOD.js} +13 -13
  35. package/dist/{rules-E52GU3GA.js → rules-BEQT7YVN.js} +8 -8
  36. package/dist/{state-X7DLLBOD.js → state-RDKIOU25.js} +4 -4
  37. package/dist/status-FP73XOZS.js +40 -0
  38. package/dist/update-7RQQJ2TH.js +135 -0
  39. package/dist/{verify-KKQ2OQMO.js → verify-UUJ6KD37.js} +10 -10
  40. package/dist/version-check-H2T3WW3I.js +14 -0
  41. package/dist/{work-3SONYYYB.js → work-HWJSUWBR.js} +14 -14
  42. package/engine/version.json +1 -1
  43. package/package.json +1 -1
  44. package/dist/lane-WINQEOEW.js +0 -41
  45. package/dist/status-NGSQ4XK3.js +0 -40
  46. package/dist/update-GA6I4AMF.js +0 -61
  47. package/dist/version-check-7XE232L3.js +0 -14
package/README.md CHANGED
@@ -43,22 +43,33 @@ yarn global add agent-work-loop
43
43
  > ```
44
44
  > `dist/cli.js`가 `awl` bin으로 등록됩니다. 정식 npm 배포가 되면 이 문단은 지웁니다.
45
45
 
46
- ### 나중에 업데이트하려면
46
+ ### 업데이트 하기
47
47
 
48
- 설치에 쓴 도구로 그대로 다시 실행하면 최신 버전을 받습니다(권장은 npm).
49
-
50
- ```bash
51
- npm install -g agent-work-loop@latest
48
+ ```
49
+ 업데이트 하기:
50
+ 1. npm i -g agent-work-loop@latest npm에 배포된 최신 버전을 받습니다
51
+ 2. awl update 설치된 패키지로 전역 엔진(~/.awl/engine)을 갱신합니다
52
52
  ```
53
53
 
54
- pnpm이나 yarn을 쓴다면:
54
+ pnpm이나 yarn을 쓴다면 1번만 그 도구로 바꿉니다:
55
55
 
56
56
  ```bash
57
57
  pnpm add -g agent-work-loop@latest
58
58
  yarn global add agent-work-loop@latest
59
59
  ```
60
60
 
61
- 이건 CLI 패키지 자체를 새로 받는 것이고, 버전의 엔진(스킬 템플릿)을 `~/.awl`에 반영하는 `awl update`가 따로 합니다.
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`을 실행할 때(버전 확인이 맨 처음 단계로 들어있습니다) 노란 경고로 보게 됩니다.
62
73
 
63
74
  설치가 됐는지 확인합니다:
64
75
 
@@ -349,7 +360,7 @@ awl work switch <ID> # 보관된 워크아이템으로 전환
349
360
  스킬 문서가 게이트에서 반드시 도구를 호출하도록 지시돼 있습니다. 계속 안 멈추면 스킬이 최신인지 확인하세요. `awl doctor`가 `claude-skill-vs-engine` 불일치를 경고하면 `awl init --yes`로 재설치합니다.
350
361
 
351
362
  **awl을 완전히 제거하려면**
352
- `awl uninstall`을 실행합니다. 기본은 드라이런이라 먼저 무엇이 지워질지 보여주고, 실제로 지우려면 `--yes`를 붙입니다. 기본은 이 프로젝트만 지우고, `~/.awl`까지 지우려면 `--global`을 명시해야 합니다. 자세한 플래그와 실제 출력은 [`commands.md`의 `awl uninstall`](docs/presentation/commands.md#awl-uninstall) 참고하세요.
363
+ `awl remove`를 실행합니다. 기본은 드라이런이라 먼저 무엇이 지워질지 보여주고, 실제로 지우려면 `--yes`를 붙입니다. 기본은 이 프로젝트만 지우고, `~/.awl`까지 지우려면 `--global`을 명시해야 합니다. 자세한 플래그와 실제 출력은 [`commands.md`의 `awl remove`](docs/presentation/commands.md#awl-remove) 참고하세요.
353
364
 
354
365
  ---
355
366
 
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  loadProjectName,
3
3
  readRecords
4
- } from "./chunk-JXFQHNHV.js";
4
+ } from "./chunk-YP4VCWPV.js";
5
5
  import {
6
6
  loadState
7
- } from "./chunk-UB4RLTLJ.js";
7
+ } from "./chunk-77EZYCLF.js";
8
8
  import {
9
9
  resolveProjectRoot,
10
10
  run
11
- } from "./chunk-CLWQEWEG.js";
12
- import "./chunk-PIOV3MQT.js";
13
- import "./chunk-M4H4VNN6.js";
11
+ } from "./chunk-JPFGMDQ4.js";
12
+ import "./chunk-2DAP3QBU.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-JXFQHNHV.js";
3
+ } from "./chunk-YP4VCWPV.js";
4
4
  import {
5
5
  loadState
6
- } from "./chunk-UB4RLTLJ.js";
6
+ } from "./chunk-77EZYCLF.js";
7
7
  import {
8
8
  resolveProjectRoot
9
- } from "./chunk-CLWQEWEG.js";
10
- import "./chunk-PIOV3MQT.js";
9
+ } from "./chunk-JPFGMDQ4.js";
10
+ import "./chunk-2DAP3QBU.js";
11
11
  import {
12
12
  caps,
13
13
  signal
14
- } from "./chunk-M4H4VNN6.js";
14
+ } from "./chunk-45X7T64Y.js";
15
15
 
16
16
  // src/commands/changelog.ts
17
17
  function runChangelogDraft(opts) {
@@ -1,22 +1,22 @@
1
1
  import {
2
2
  gatherVersionInputs
3
- } from "./chunk-DGPZEEFJ.js";
3
+ } from "./chunk-Q7V3WVKT.js";
4
4
  import {
5
5
  checkVersions
6
- } from "./chunk-MCNUMMKK.js";
6
+ } from "./chunk-SV4D72NX.js";
7
7
  import {
8
8
  loadProjectName,
9
9
  readRecords
10
- } from "./chunk-JXFQHNHV.js";
10
+ } from "./chunk-YP4VCWPV.js";
11
11
  import {
12
12
  loadState,
13
13
  readStateLock
14
- } from "./chunk-UB4RLTLJ.js";
14
+ } from "./chunk-77EZYCLF.js";
15
15
  import {
16
16
  CommandNotFoundError,
17
17
  run,
18
18
  tokenize
19
- } from "./chunk-CLWQEWEG.js";
19
+ } from "./chunk-JPFGMDQ4.js";
20
20
  import {
21
21
  caps,
22
22
  clipToWidth,
@@ -32,7 +32,7 @@ import {
32
32
  sectionBox,
33
33
  signal,
34
34
  visibleWidth
35
- } from "./chunk-M4H4VNN6.js";
35
+ } from "./chunk-45X7T64Y.js";
36
36
 
37
37
  // src/commands/doctor.ts
38
38
  import fs from "fs";
@@ -12,7 +12,7 @@ import {
12
12
  sectionBox,
13
13
  signal,
14
14
  stringWidth
15
- } from "./chunk-M4H4VNN6.js";
15
+ } from "./chunk-45X7T64Y.js";
16
16
 
17
17
  // src/commands/init.ts
18
18
  import fs from "fs";
@@ -311,7 +311,7 @@ function anyWorkspaceMemberHasTsconfig(cwd, pkg) {
311
311
  }
312
312
  return false;
313
313
  }
314
- function detectLanguage(cwd) {
314
+ function detectJsLanguage(cwd) {
315
315
  if (exists(path.join(cwd, "tsconfig.json"))) {
316
316
  return "typescript";
317
317
  }
@@ -323,11 +323,25 @@ function detectLanguage(cwd) {
323
323
  }
324
324
  return "javascript";
325
325
  }
326
- if (exists(path.join(cwd, "pyproject.toml")) || exists(path.join(cwd, "setup.py")) || exists(path.join(cwd, "requirements.txt"))) {
327
- return "python";
328
- }
329
326
  return null;
330
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
+ }
331
345
  function splitEnv(script) {
332
346
  const env = {};
333
347
  let rest = script.trim();
@@ -608,7 +622,7 @@ function nonInteractiveInputs(projectRoot) {
608
622
  const skills = detected.claude || detected.codex ? detected : { claude: true, codex: false };
609
623
  return {
610
624
  project: path.basename(projectRoot),
611
- mainLanguage: detectLanguage(projectRoot) ?? "",
625
+ mainLanguage: detectLanguages(projectRoot),
612
626
  character: "",
613
627
  verify: detectVerify(projectRoot),
614
628
  skills
@@ -736,14 +750,17 @@ function renderResult(result, inputs, c) {
736
750
  }
737
751
  var LANG_OPTIONS = ["TypeScript", "JavaScript", "Python", "\uC9C1\uC811 \uC785\uB825"];
738
752
  var LANG_VALUES = ["typescript", "javascript", "python", ""];
739
- function langDefaultIndex(projectRoot) {
740
- const detected = detectLanguage(projectRoot);
741
- const idx = detected ? LANG_VALUES.indexOf(detected) : 0;
742
- 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];
743
760
  }
744
761
  function buildScreens(projectRoot, hasGlobal, c) {
745
762
  const project = path.basename(projectRoot);
746
- const defLang = langDefaultIndex(projectRoot);
763
+ const defLangIndices = langDefaultIndices(projectRoot);
747
764
  const verify = detectVerify(projectRoot);
748
765
  const agents = detectAgents(projectRoot);
749
766
  const welcome = hasGlobal ? null : [
@@ -760,12 +777,13 @@ function buildScreens(projectRoot, hasGlobal, c) {
760
777
  " \uADDC\uCE59\uACFC \uAD50\uD6C8\uC740 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2C8\uB77C \uB2F9\uC2E0\uC5D0\uAC8C \uC313\uC774\uACE0,",
761
778
  " \uB4F1\uB85D\uB41C \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uD568\uAED8 \uC4F0\uC785\uB2C8\uB2E4."
762
779
  ].join("\n");
780
+ const detectedLangLabels = defLangIndices.map((i) => LANG_OPTIONS[i] ?? "TypeScript").join(", ");
763
781
  const langLines = [
764
- `\uC790\uB3D9 \uAC10\uC9C0: ${LANG_OPTIONS[defLang] ?? "TypeScript"}`,
782
+ `\uC790\uB3D9 \uAC10\uC9C0: ${detectedLangLabels || "TypeScript"}`,
765
783
  "",
766
- "\uBC14\uB85C \uC544\uB798\uC758 \uC120\uD0DD\uAE30\uC5D0\uC11C \uACE0\uB985\uB2C8\uB2E4.",
767
- "\uD654\uC0B4\uD45C \uB610\uB294 j/k \uB85C \uC774\uB3D9\uD558\uACE0 Enter \uB85C \uD655\uC815\uD558\uC138\uC694.",
768
- "\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."
769
787
  ];
770
788
  return {
771
789
  welcome,
@@ -904,21 +922,19 @@ async function interactiveInputs(projectRoot, hasGlobal, c) {
904
922
  ${screens.lang}
905
923
 
906
924
  `);
907
- 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, {
908
927
  title: "\uC8FC \uC5B8\uC5B4",
909
- 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"
910
929
  }) : null;
911
- const langIdx = rawLanguage?.index ?? (useRawMode ? langDefaultIndex(projectRoot) : await selectSingle(
912
- prompt(),
913
- LANG_OPTIONS,
914
- langDefaultIndex(projectRoot),
915
- c,
916
- false,
917
- "\uC8FC \uC5B8\uC5B4"
918
- ));
919
- let mainLanguage = LANG_VALUES[langIdx] ?? "";
920
- if (langIdx === LANG_OPTIONS.length - 1) {
921
- 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
+ }
922
938
  }
923
939
  const rootVerify = detectVerify(projectRoot);
924
940
  const located = await promptVerifyLocation(
@@ -1000,7 +1016,7 @@ async function handleExistingConfig(rl, projectRoot, c, now) {
1000
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");
1001
1017
  process.stdout.write(` \uD504\uB85C\uC81D\uD2B8 ${config?.project ?? "(\uC5C6\uC74C)"}
1002
1018
  `);
1003
- process.stdout.write(` \uC8FC \uC5B8\uC5B4 ${config?.mainLanguage ?? "(\uC5C6\uC74C)"}
1019
+ process.stdout.write(` \uC8FC \uC5B8\uC5B4 ${config?.mainLanguage?.join(", ") || "(\uC5C6\uC74C)"}
1004
1020
  `);
1005
1021
  process.stdout.write(` \uC131\uACA9 ${config?.character || "(\uC5C6\uC74C)"}
1006
1022
  `);
@@ -1056,7 +1072,7 @@ async function handleExistingConfig(rl, projectRoot, c, now) {
1056
1072
  }
1057
1073
  const merged = {
1058
1074
  project: config?.project ?? path.basename(projectRoot),
1059
- mainLanguage: config?.mainLanguage ?? "",
1075
+ mainLanguage: config?.mainLanguage ?? [],
1060
1076
  character: config?.character ?? "",
1061
1077
  verify,
1062
1078
  skills: { claude: false, codex: false }
@@ -1105,6 +1121,27 @@ function registeredProjectPaths() {
1105
1121
  return /* @__PURE__ */ new Set();
1106
1122
  }
1107
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
+ }
1108
1145
  function excludeRegisteredProjects(candidates, registered) {
1109
1146
  if (registered.size === 0) {
1110
1147
  return candidates;
@@ -1149,7 +1186,7 @@ async function pickProjectRoot(cwd, c) {
1149
1186
  }
1150
1187
  const candidates = excludeRegisteredProjects(scanGitProjects(cwd), registeredProjectPaths());
1151
1188
  const labels = [...candidates.map((p) => `${p.name} (${p.path})`), "\uC9C1\uC811 \uACBD\uB85C \uC785\uB825", "\uCDE8\uC18C"];
1152
- const title = candidates.length > 0 ? "\uD504\uB85C\uC81D\uD2B8\uB97C \uACE0\uB974\uC138\uC694 (\uCD5C\uADFC \uC218\uC815\uC21C, \uCD5C\uB300 20)" : "\uD558\uC704\uC5D0 git \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC9C1\uC811 \uC785\uB825\uD558\uAC70\uB098 \uCDE8\uC18C";
1189
+ const title = candidates.length > 0 ? "\uD504\uB85C\uC81D\uD2B8\uB97C \uACE0\uB974\uC138\uC694 (\uCD5C\uADFC \uC218\uC815\uC21C, \uCD5C\uB300 20)\n" : "\uD558\uC704\uC5D0 git \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC9C1\uC811 \uC785\uB825\uD558\uAC70\uB098 \uCDE8\uC18C\n";
1153
1190
  const choice = resolveProjectChoice(await select(labels, title), candidates);
1154
1191
  if (choice.kind === "path") {
1155
1192
  return choice.path;
@@ -1241,6 +1278,7 @@ ${renderResult(result, inputs, c)}
1241
1278
  export {
1242
1279
  scanGitProjects,
1243
1280
  detectLanguage,
1281
+ detectLanguages,
1244
1282
  splitEnv,
1245
1283
  detectVerify,
1246
1284
  detectWorkspacePackages,
@@ -1276,6 +1314,7 @@ export {
1276
1314
  applyVerifyCwd,
1277
1315
  resolveProjectChoice,
1278
1316
  registeredProjectPaths,
1317
+ listRegisteredProjects,
1279
1318
  excludeRegisteredProjects,
1280
1319
  runInit
1281
1320
  };
@@ -5,22 +5,25 @@ import {
5
5
  runWorkNew,
6
6
  sanitizeForGit,
7
7
  worktreeDirtyTracked
8
- } from "./chunk-PFXDX4JF.js";
8
+ } from "./chunk-7NOHUUXV.js";
9
+ import {
10
+ loadProjectName
11
+ } from "./chunk-YP4VCWPV.js";
9
12
  import {
10
13
  loadState,
11
14
  writeState
12
- } from "./chunk-UB4RLTLJ.js";
15
+ } from "./chunk-77EZYCLF.js";
13
16
  import {
14
17
  resolveProjectRoot,
15
18
  run
16
- } from "./chunk-CLWQEWEG.js";
19
+ } from "./chunk-JPFGMDQ4.js";
17
20
  import {
18
21
  caps,
19
22
  feedback,
20
23
  makeColors,
21
24
  sectionBox,
22
25
  signal
23
- } from "./chunk-M4H4VNN6.js";
26
+ } from "./chunk-45X7T64Y.js";
24
27
 
25
28
  // src/commands/lane.ts
26
29
  import fs from "fs";
@@ -258,7 +261,8 @@ ${feedback(c, "error", `\uB808\uC778 \uBE0C\uB79C\uCE58 ${branch} \uC5D0 \uBCD1\
258
261
  }
259
262
  let merged = null;
260
263
  try {
261
- merged = mergeIsolatedHome(path.join(lanePath, ".awl", "home"));
264
+ const project = loadProjectName(root) ?? path.basename(root);
265
+ merged = mergeIsolatedHome(path.join(lanePath, ".awl", "home"), { project, lane: laneName });
262
266
  } catch (e) {
263
267
  process.stderr.write(
264
268
  `
@@ -287,6 +291,12 @@ ${feedback(c, "ok", `\uB808\uC778 \uC81C\uAC70 ${color.bold(laneName)}`, branch
287
291
  if (merged && (merged.gotchasAdded > 0 || merged.rulesAdded > 0 || merged.generationsAdded > 0)) {
288
292
  process.stdout.write(
289
293
  ` ${color.dim(`\uD559\uC2B5 \uC804\uC5ED \uBCD1\uD569 gotcha ${merged.gotchasAdded} \xB7 rule ${merged.rulesAdded} \xB7 generation ${merged.generationsAdded}`)}
294
+ `
295
+ );
296
+ }
297
+ if (merged && merged.recordsMerged > 0) {
298
+ process.stdout.write(
299
+ ` ${color.dim(`records \uC804\uC5ED \uBCD1\uD569 ${merged.recordsMerged}\uAC74${merged.recordsArchivePath ? ` \xB7 \uC544\uCE74\uC774\uBE0C ${merged.recordsArchivePath}` : ""}`)}
290
300
  `
291
301
  );
292
302
  }
@@ -399,17 +399,18 @@ function sectionBox(title, lines, c = caps(), minInnerWidth = 0) {
399
399
  Math.min(HARD_MAX_WIDTH, viewportWidth() - (2 + pad * 2))
400
400
  );
401
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
402
  const frame = (text) => t.frame(text);
407
- const row = (text) => `${frame(s.boxV)}${" ".repeat(pad)}${text}`;
408
- const safeTitle = truncateToWidth(title, inner);
409
- const tvis = visibleWidth(safeTitle);
410
- const dash = Math.max(1, inner + pad * 2 - 3 - tvis);
411
- const top = frame(`${s.boxTL}${s.boxH} `) + t.emphasis(t.accent(safeTitle)) + frame(` ${s.boxH.repeat(dash)}`);
412
- 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");
413
414
  }
414
415
  var RESET = "\x1B[0m";
415
416
  function ansi(code, str, enabled) {
@@ -430,7 +431,7 @@ function makeTokens(c) {
430
431
  }
431
432
  function signal(c, kind) {
432
433
  const color2 = makeColors(c.color);
433
- 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];
434
435
  if (kind === "ok") {
435
436
  return color2.green(raw);
436
437
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  requireConfig
3
- } from "./chunk-CLWQEWEG.js";
3
+ } from "./chunk-JPFGMDQ4.js";
4
4
  import {
5
5
  caps,
6
6
  generationsDir,
@@ -8,7 +8,7 @@ import {
8
8
  padEndDisplay,
9
9
  sectionBox,
10
10
  stringWidth
11
- } from "./chunk-M4H4VNN6.js";
11
+ } from "./chunk-45X7T64Y.js";
12
12
 
13
13
  // src/commands/metrics.ts
14
14
  import fs from "fs";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  resolveProjectRoot
3
- } from "./chunk-CLWQEWEG.js";
3
+ } from "./chunk-JPFGMDQ4.js";
4
4
  import {
5
5
  caps,
6
6
  makeColors,
7
7
  sectionBox
8
- } from "./chunk-M4H4VNN6.js";
8
+ } from "./chunk-45X7T64Y.js";
9
9
 
10
10
  // src/commands/state.ts
11
11
  import fs from "fs";
@@ -7,29 +7,32 @@ import {
7
7
  isCheckPassed,
8
8
  runVerifyChecks,
9
9
  writeVerifyBaseline
10
- } from "./chunk-MLD2GOVM.js";
10
+ } from "./chunk-RRQYSRRT.js";
11
11
  import {
12
12
  gitBranch
13
- } from "./chunk-J7EKKHVW.js";
13
+ } from "./chunk-22SUY5RE.js";
14
14
  import {
15
15
  parseRuleFile
16
- } from "./chunk-KNBSZQGN.js";
16
+ } from "./chunk-Z3QCPOOA.js";
17
17
  import {
18
18
  nextGotchaId
19
- } from "./chunk-DR7OZZ4N.js";
19
+ } from "./chunk-DIRYW77S.js";
20
+ import {
21
+ loadProjectName
22
+ } from "./chunk-YP4VCWPV.js";
20
23
  import {
21
24
  loadState,
22
25
  migrateState,
23
26
  writeState
24
- } from "./chunk-UB4RLTLJ.js";
27
+ } from "./chunk-77EZYCLF.js";
25
28
  import {
26
29
  loadConfig,
27
30
  resolveProjectRoot,
28
31
  run
29
- } from "./chunk-CLWQEWEG.js";
32
+ } from "./chunk-JPFGMDQ4.js";
30
33
  import {
31
34
  installClaudeSkill
32
- } from "./chunk-PIOV3MQT.js";
35
+ } from "./chunk-2DAP3QBU.js";
33
36
  import {
34
37
  caps,
35
38
  feedback,
@@ -39,7 +42,7 @@ import {
39
42
  makeTokens,
40
43
  sectionBox,
41
44
  signal
42
- } from "./chunk-M4H4VNN6.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
@@ -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
  }