cuelo 0.9.4 → 0.9.6

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 (127) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +6 -6
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +1 -1
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +1 -1
  18. package/.next/server/app/_not-found.rsc +3 -3
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  20. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/server/app/api/agent/[id]/account/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/events/route.js +1 -1
  27. package/.next/server/app/api/agent/[id]/route.js +1 -1
  28. package/.next/server/app/api/agent/new/route.js +1 -1
  29. package/.next/server/app/api/agent/running/route.js +1 -1
  30. package/.next/server/app/api/attachments/[id]/transcription/route.js +1 -1
  31. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  32. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  33. package/.next/server/app/api/gpt6/token/route.js +1 -1
  34. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  35. package/.next/server/app/api/live/call/[callId]/route.js +1 -1
  36. package/.next/server/app/api/live/events/route.js +1 -1
  37. package/.next/server/app/api/live/offer/route.js +1 -1
  38. package/.next/server/app/api/live/voices/route.js +1 -1
  39. package/.next/server/app/api/lounge/events/route.js +1 -1
  40. package/.next/server/app/api/lounge/route.js +1 -1
  41. package/.next/server/app/api/project-trust/route.js +1 -1
  42. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  43. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  44. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  45. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  46. package/.next/server/app/api/sessions/route.js +1 -1
  47. package/.next/server/app/api/tool-image/route.js +1 -1
  48. package/.next/server/app/api/update-maintenance/route.js +59 -21
  49. package/.next/server/app/api/updates/route.js +1 -1
  50. package/.next/server/app/index.html +1 -1
  51. package/.next/server/app/index.rsc +6 -6
  52. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  53. package/.next/server/app/index.segments/_full.segment.rsc +6 -6
  54. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  55. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  56. package/.next/server/app/index.segments/_tree.segment.rsc +3 -3
  57. package/.next/server/app/office/page_client-reference-manifest.js +1 -1
  58. package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
  59. package/.next/server/app/office-preview.html +1 -1
  60. package/.next/server/app/office-preview.rsc +3 -3
  61. package/.next/server/app/office-preview.segments/_full.segment.rsc +3 -3
  62. package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
  63. package/.next/server/app/office-preview.segments/_index.segment.rsc +3 -3
  64. package/.next/server/app/office-preview.segments/_tree.segment.rsc +2 -2
  65. package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
  66. package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
  67. package/.next/server/app/office.html +1 -1
  68. package/.next/server/app/office.rsc +6 -6
  69. package/.next/server/app/office.segments/_full.segment.rsc +6 -6
  70. package/.next/server/app/office.segments/_head.segment.rsc +1 -1
  71. package/.next/server/app/office.segments/_index.segment.rsc +3 -3
  72. package/.next/server/app/office.segments/_tree.segment.rsc +3 -3
  73. package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +4 -4
  74. package/.next/server/app/office.segments/office.segment.rsc +1 -1
  75. package/.next/server/app/page_client-reference-manifest.js +1 -1
  76. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/recover.html +1 -1
  78. package/.next/server/app/recover.rsc +3 -3
  79. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  80. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  81. package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
  82. package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
  83. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  84. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  85. package/.next/server/app-paths-manifest.json +6 -6
  86. package/.next/server/chunks/1008.js +15 -0
  87. package/.next/server/chunks/5489.js +12 -12
  88. package/.next/server/chunks/5582.js +1 -1
  89. package/.next/server/chunks/6429.js +6 -6
  90. package/.next/server/chunks/7121.js +4 -1
  91. package/.next/server/chunks/7362.js +1 -1
  92. package/.next/server/instrumentation.js +1 -1
  93. package/.next/server/instrumentation.js.nft.json +1 -1
  94. package/.next/server/middleware-build-manifest.js +1 -1
  95. package/.next/server/pages/404.html +1 -1
  96. package/.next/server/pages/500.html +1 -1
  97. package/.next/server/server-reference-manifest.json +1 -1
  98. package/.next/static/chunks/6110-004be57c825af4d9.js +1 -0
  99. package/.next/static/chunks/9575-4a84cf066c2f1257.js +22 -0
  100. package/.next/static/chunks/app/{layout-76b01721b96d2c1a.js → layout-15c56b62f49b1c1d.js} +1 -1
  101. package/.next/static/css/00f20b26266e86ed.css +1 -0
  102. package/.next/static/css/7a264466e19eaafd.css +1 -0
  103. package/.next/trace +108 -108
  104. package/.next/trace-build +1 -1
  105. package/README.md +11 -3
  106. package/Tools/CUELO_Setup/files/native-runtime-patch.js +1 -1
  107. package/Tools/OMP_Global_Config/agent/AGENTS.md +6 -0
  108. package/Tools/OMP_Global_Config/agent/agents/maker.md +11 -5
  109. package/Tools/OMP_Global_Config/agent/extensions/command-guard/task-guard.ts +35 -14
  110. package/Tools/OMP_Global_Config/agent/extensions/gitnexus-autosync.ts +89 -19
  111. package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +194 -0
  112. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +32 -15
  113. package/Tools/OMP_Global_Config/agent/extensions/lib/prepared-task.ts +4 -3
  114. package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +7 -1
  115. package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +24 -9
  116. package/Tools/OMP_Global_Config/agent/rules/subagent.md +23 -4
  117. package/Tools/OMP_Global_Config/agent/rules/task-guard.md +1 -0
  118. package/Tools/OMP_Global_Config/agent/sop/_writer.md +10 -4
  119. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +303 -194
  120. package/install.mjs +1 -0
  121. package/package.json +1 -1
  122. package/.next/static/chunks/6110-691c0f77641fc07e.js +0 -1
  123. package/.next/static/chunks/9575-08609cfc6c1cf3e1.js +0 -22
  124. package/.next/static/css/1ffe2d5d654f7eea.css +0 -1
  125. package/.next/static/css/81a05eb6255ef12e.css +0 -1
  126. /package/.next/static/{6tGLEaiCe3Wa8PkWKUhB4 → kRJ4zgHxJf680foSBrfpf}/_buildManifest.js +0 -0
  127. /package/.next/static/{6tGLEaiCe3Wa8PkWKUhB4 → kRJ4zgHxJf680foSBrfpf}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":115136424,"timestamp":678029,"id":14,"parentId":1,"tags":{},"startTime":1791135180839,"traceId":"febc2744d0c65baa"},{"name":"run-typescript","duration":21720895,"timestamp":116238110,"id":11025,"parentId":1,"tags":{},"startTime":1791135296400,"traceId":"febc2744d0c65baa"},{"name":"static-check","duration":4345717,"timestamp":138063881,"id":11028,"parentId":1,"tags":{},"startTime":1791135318226,"traceId":"febc2744d0c65baa"},{"name":"static-generation","duration":12288780,"timestamp":142523785,"id":11198,"parentId":1,"tags":{},"startTime":1791135322686,"traceId":"febc2744d0c65baa"},{"name":"collect-build-traces","duration":22080325,"timestamp":142483913,"id":11195,"parentId":1,"tags":{},"startTime":1791135322646,"traceId":"febc2744d0c65baa"},{"name":"telemetry-flush","duration":87,"timestamp":164569114,"id":11207,"parentId":1,"tags":{},"startTime":1791135344731,"traceId":"febc2744d0c65baa"},{"name":"next-build","duration":164014653,"timestamp":554771,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1791135180716,"traceId":"febc2744d0c65baa"}]
1
+ [{"name":"run-webpack","duration":117204564,"timestamp":3666764,"id":14,"parentId":1,"tags":{},"startTime":1791206991136,"traceId":"a3ba25d1527e8fbc"},{"name":"run-typescript","duration":22577490,"timestamp":121308907,"id":11060,"parentId":1,"tags":{},"startTime":1791207108780,"traceId":"a3ba25d1527e8fbc"},{"name":"static-check","duration":4608235,"timestamp":143997002,"id":11063,"parentId":1,"tags":{},"startTime":1791207131469,"traceId":"a3ba25d1527e8fbc"},{"name":"static-generation","duration":13069934,"timestamp":148722789,"id":11233,"parentId":1,"tags":{},"startTime":1791207136195,"traceId":"a3ba25d1527e8fbc"},{"name":"collect-build-traces","duration":22219970,"timestamp":148687543,"id":11230,"parentId":1,"tags":{},"startTime":1791207136159,"traceId":"a3ba25d1527e8fbc"},{"name":"telemetry-flush","duration":77,"timestamp":170911847,"id":11242,"parentId":1,"tags":{},"startTime":1791207158384,"traceId":"a3ba25d1527e8fbc"},{"name":"next-build","duration":169697942,"timestamp":1214006,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1791206988683,"traceId":"a3ba25d1527e8fbc"}]
package/README.md CHANGED
@@ -20,6 +20,10 @@ Use the same `~/.omp/agent` sessions, credentials and model configuration from t
20
20
 
21
21
  CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규칙·실행 절차에 반영해, 사람이 같은 문제를 다시 지적하지 않아도 다음 작업에서 실수를 줄이는 것입니다. 원인을 확인하고 필요한 부분만 바꾸며, 다음 작업에서 적용과 효과를 확인하는 것까지를 지향합니다. 기록을 저장했다는 이유만으로 자동 개선이 끝났다고 보지는 않습니다.
22
22
 
23
+ 주제별로 저장한 교훈을 정정하면 최초 기록과 최신 정정의 출처를 구분해 조회할 수 있습니다. 회상 목록에도 확인 가능한 전역·프로젝트 범위와 정정 정보를 표시합니다. 기억은 현재 요청과 저장소의 적용 조건을 대조하는 참고 자료이며 실행 승인을 대신하지 않습니다. 이전 교훈 본문을 별도 이력으로 쌓지는 않습니다. 자세한 보존 범위는 [하네스 안내](./docs/harness.md)를 참고하세요.
24
+
25
+ 작업을 시작할 때 회상된 교훈과 현재 요청의 적용 조건·예외·충돌을 점검해 에이전트에게 참고 안내를 제공합니다. 같은 입력은 반복 점검하지 않으며, 판단이 불명확하거나 점검에 실패해도 작업을 막지 않습니다. 현재 지시와 승인 범위가 우선입니다.
26
+
23
27
  **Keywords:** AI coding agent · agent harness · multi-agent orchestration · omp · oh-my-pi · model routing · developer tools
24
28
 
25
29
  ## 이 저장소로 되는 것과 따로 필요한 것
@@ -45,7 +49,7 @@ CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규
45
49
  - 입력창에 이미지·문서 외에 음성(받아쓰기), 코드·설정 파일(본문 첨부), 그 밖의 파일(저장 후 경로 첨부)을 붙여넣기·드롭·선택으로 넣습니다. 저장형 첨부는 기본 파일당 100MB이며 설정에서 한도를 바꿀 수 있습니다. 큰 음성은 처리 계획과 계정 사용량 안내를 확인한 뒤 압축·분할 전사를 시작합니다. 음성 받아쓰기는 Google Antigravity 계정이 필요합니다. 대화·분기·초안에서 사용 중인 파일은 보존하고, 미사용이 확인된 첨부만 7일 유예 후 자동 정리합니다. [첨부와 자동 정리 안내](./docs/attachments.md)를 확인하세요.
46
50
  - 에이전트가 생성하거나 확인한 이미지가 그 턴의 대화 흐름에 썸네일로 보이고, 누르면 크게 열립니다.
47
51
  - Ctrl+K 팔레트로 대화 내용을 검색해 해당 메시지로 이동합니다. 압축으로 사라진 과거 메시지는 제외하고, 현재 볼 수 있는 메시지를 세션당 최대 3건 보여 줍니다. 긴 대화 안에서는 Ctrl+F로 찾고, 오른쪽 질문 레일로 내가 한 질문 사이를 오갑니다.
48
- - 서버나 PC가 꺼져 실행이 끊긴 대화를 열면 중단 알림을 보여 줍니다. 컨텍스트 압축 중에는 경과 시간을 표시합니다.
52
+ - 서버나 PC가 갑자기 꺼지면, 복구 대상인 진행 중 대화를 서버 재시작 뒤 자동으로 이어갑니다. 사용자 답변·승인을 기다리던 대화와 이전 버전의 기록은 자동으로 재개하지 않으며, 이어지지 않은 대화에는 중단 알림을 표시합니다. 컨텍스트 압축 중에는 경과 시간을 표시합니다.
49
53
  - 모델 선택기 맨 위에 자주 고른 모델이 나오고, 팔레트에서 넓은 대화 폭을 켤 수 있습니다.
50
54
  - 입력창 아래 줄은 Main 프리셋 → 그 제공자의 모델 → 추론 강도 순서입니다. "자동, 최대 high"처럼 상한을 고를 수 있고, Auto가 실제로 적용한 값은 「현재 추론 강도」로 따로 표시합니다. 실행 중에 지시를 덧붙이면 Auto가 다시 판단해 강도를 올릴 수 있고, 같은 답변 안에서는 내리지 않습니다. 새 대화는 선택한 상한 적용을 마친 뒤 첫 메시지를 보내며, 적용에 실패하면 재전송 때 다시 시도합니다.
51
55
  - 도구 옆 시간에는 모델 응답 생성 시간을 포함하지 않습니다. 실행 전에 취소된 도구나 완료 시각을 확인할 수 없는 옛 기록에는 시간을 추정해 붙이지 않습니다.
@@ -69,12 +73,14 @@ CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규
69
73
  - 개인 캐릭터 모델(선택): agent 데이터 디렉터리(기본 `~/.omp/agent`) 아래 `office-avatars/`에 `rin.vrm`·`mio.vrm`·`nova.vrm`·`yuki.vrm`·`isana.vrm`·`shion.vrm`·`hikari.vrm`을 두면 그 캐릭터를 VRM 모델로 그립니다. 파일 이름은 이 일곱 개만 쓰며, 서버는 이 고정 이름의 파일만 기존 접속 보호(호스트 확인·비밀번호 잠금)를 거친 `/api/office-avatars/<이름>`으로 내주고 다른 경로는 받지 않습니다. 모델은 저장소·npm 패키지·`public/`에 들어 있지 않습니다. 파일이 없는 캐릭터와 캐릭터를 특정하지 못한 참여자는 코드로 그린 캐릭터 그대로이고, 파일이 있는데 받거나 읽지 못하면 오류를 숨기지 않고 자리 카드와 「다시 시도」로 넘어갑니다. 파일을 넣거나 바꾼 뒤에는 오피스를 다시 열면 됩니다.
70
74
  - 한 캐릭터는 작업이 몇 개든 사무실에 한 명만 섭니다. Main과 같은 캐릭터의 Maker, 같은 계정의 Maker 여럿은 한 몸에 모이고, 머리 위 말풍선에 작업마다 한 줄(이름·상태)이 보여 실패·대기 작업도 가려지지 않습니다. 그 캐릭터를 누르면 작업이 하나일 때는 바로 열리고, 여럿일 때는 말풍선의 첫 줄로 초점이 가서 줄마다 골라 엽니다. 몸의 자리와 동작은 일하는 작업 → 기다리는 작업 → 사람을 부르거나 실패한 작업 → 쉬거나 끝난 작업 순으로 고른 대표 작업을 따릅니다. 하단 참여자 줄과 자리 카드는 계속 작업마다 따로 보입니다. Maker의 캐릭터는 오피스를 열면 각 Maker 기록의 마지막 답에 남은 계정으로 바로 정해지고, 계정 근거가 없는 참여자는 다른 캐릭터에 합치지 않고 회색 「이름 없는 참여자」로 따로 섭니다.
71
75
  - 자식 에이전트의 발화가 해당 턴 아래 대화 흐름에 끼어들어 누가 무슨 말을 했는지 이어서 읽힙니다.
72
- - 공개 하네스는 시작을 기다리거나 재작업 중인 에이전트에게 같은 파일을 중복 배정하지 않도록 확인합니다. 완료 결과와 Main이 검수한 결과의 출처를 구분하고, 재작업 이력과 최종 수용을 따로 남깁니다. 적용 경계는 [하네스 안내](./docs/harness.md#발주와-검수-계약)를 따릅니다.
76
+ - 공개 하네스는 시작을 기다리거나 재작업 중인 에이전트에게 같은 파일을 중복 배정하지 않도록 확인합니다. 잘못된 작업 유형은 발주 준비 단계에서 알려주며, 에이전트 역할을 생략하면 메이커를 사용합니다. 완료 결과와 Main이 검수한 결과의 출처를 구분하고, 재작업 이력과 최종 수용을 따로 남깁니다. 적용 경계는 [하네스 안내](./docs/harness.md#발주와-검수-계약)를 따릅니다.
77
+ - 여러 작업을 한꺼번에 맡길 때 공통 준비 확인의 반복을 줄였습니다. 작업별 담당 경로와 충돌 확인은 유지하며, 다음 발주에서는 바뀐 조건을 다시 확인합니다.
78
+ - 완료된 위임 작업은 검수 판정과 기록을 함께 마감하고, 최종 답변 전에 판정 기록 누락을 확인합니다. 근거가 부족한 결과를 자동 합격으로 처리하지 않습니다.
73
79
  - 메이커의 기본 추론 강도와 허용 범위는 별개입니다. 작업별 허용 범위 안에서도 선택한 모델이 실제로 지원하는 강도만 사용하며, 상한을 높여도 기본 강도를 자동으로 최대로 바꾸지 않습니다.
74
80
  - 필요한 MCP 서버만 요청별로 연결하는 선택 모드를 지원합니다. 설치·인증·권한 변경은 먼저 승인을 받고 에이전트가 지원합니다. 설정과 적용 범위는 [요청별 MCP 선택 연결](./docs/harness.md#요청별-mcp-선택-연결)을 확인하세요.
75
81
  - 사이드 챗(`/btw`): 본 대화를 끊지 않고 옆 패널에서 따로 묻습니다. 답변은 본 대화와 같은 캐릭터 얼굴·스티커·마크다운으로 보이고, 세션마다 초안이 따로 보관됩니다. 사이드 챗 문답은 본 대화에 남지 않으며, 답변의 「메인에 보내기」를 누를 때만 문답과 추가 지시가 본 대화로 전달됩니다(본 대화가 실행 중이면 steer, 아니면 새 요청).
76
82
  - ChatGPT 상담(SHION): 로컬 shim이 전용 ChatGPT 탭에서 받은 답을 현재 omp 세션에 투영하고, 상담 기록과 실패 기록이 대화창에 표시됩니다.
77
- - 실시간 음성: 브라우저 마이크로 Codex live 음성 세션을 엽니다. “끊어”, “아… 일단 끊어”, “통화 종료해 줘”처럼 단독 종료 요청을 말하면 최종 전사를 받은 뒤 마이크와 통화를 종료합니다. 선택 기능인 캐릭터 음성은 **설정 → 통화 음성**에서 Cartesia API 키를 입력하고 음성 준비에 동의하면 현재 Main 캐릭터에 맞춰 적용됩니다. 통화 답변은 실제로 말한 캐릭터의 얼굴과 이름으로 표시되고, 캐릭터 음성 대신 기본 음성으로 말했으면 「기본 음성」이라고 함께 보입니다. 입력창에는 통화 시작·종료 버튼만 남으며, 좁은 화면에서는 모델 선택 등 다른 조작부와 겹치지 않게 줄바꿈합니다. 별도 음성 ID 입력은 필요하지 않으며, 키는 서버에만 보관합니다. 지원 플랜·크레딧 사용·전송 범위는 [캐릭터 통화 설정](./docs/live-voice.md)을 확인하세요.
83
+ - 실시간 음성: 브라우저 마이크로 Codex live 음성 세션을 엽니다. “끊어”, “알았어, 끊어봐”, “아니 통화 종료하라고”처럼 단독 종료 요청을 말하면 최종 전사를 받은 뒤 마이크와 통화를 종료합니다. 선택 기능인 캐릭터 음성은 **설정 → 통화 음성**에서 Cartesia API 키를 입력하고 음성 준비에 동의하면 현재 Main 캐릭터에 맞춰 적용됩니다. 통화 답변은 실제로 말한 캐릭터의 얼굴과 이름으로 표시되고, 캐릭터 음성 대신 기본 음성으로 말했으면 「기본 음성」이라고 함께 보입니다. 입력창에는 통화 시작·종료 버튼만 남으며, 좁은 화면에서는 모델 선택 등 다른 조작부와 겹치지 않게 줄바꿈합니다. 별도 음성 ID 입력은 필요하지 않으며, 키는 서버에만 보관합니다. 지원 플랜·크레딧 사용·전송 범위는 [캐릭터 통화 설정](./docs/live-voice.md)을 확인하세요.
78
84
 
79
85
  **단톡방**
80
86
 
@@ -160,6 +166,8 @@ bun bin/cuelo.js --reset-password # 비밀번호 재설정
160
166
 
161
167
  새 CUELO 릴리스가 나오면 사이드바 아래에 알림이 뜹니다. CUELO는 스스로 설치하지 않습니다. 소스에서 실행 중이면 서비스를 종료한 뒤 소스를 갱신하고 다시 설치·시작하세요. 실행 중인 개발 서버의 `.next/`에 빌드를 겹쳐 쓰지 마세요.
162
168
 
169
+ 업데이트 대기 화면에서 실패가 확인되더라도 서버가 다시 요청을 받을 수 있고 원래 대화가 확인되면 그 대화로 자동 복귀합니다. 복귀한 화면은 진행 중인 답변이나 이미 끝난 실패 안내를 따라잡으며, 복귀 자체가 명령을 다시 보내지는 않습니다.
170
+
163
171
  기존 프로필의 하네스·역할 설정은 앱 업데이트만으로 자동 덮어쓰지 않습니다. `0.6.1`의 설정 전환은 [기존 하네스 갱신](./docs/installation.md#061로-기존-하네스-갱신)을 확인하고, 반영 후 CUELO와 `omp`를 다시 시작하세요.
164
172
 
165
173
  ```bash
@@ -9,7 +9,7 @@ const MEMORY_RE = /(const phase1Model = await resolveMemoryModel\(\{[^}]*?fallba
9
9
  const MEMORY_DONE = /const phase1Model = await resolveMemoryModel\(\{[^}]*?fallbackRole: "smol"/;
10
10
  const EXPECTED = {
11
11
  name: 'cuelo',
12
- version: '0.9.4',
12
+ version: '0.9.6',
13
13
  distribution: 'cuelo',
14
14
  coreVersion: '18.6.1',
15
15
  };
@@ -67,6 +67,12 @@ Main은 확정 delta와 그 증거를 누적 검수하며, 최종 수용은 수
67
67
 
68
68
  실제 dependency/synthesis barrier에서만 Main 전용 `wait`를 쓰고(SubAgent에는 없다), 다른 일이 남으면 먼저 한다. 장기 job은 `read proc://<id>`와 산출물로 관측하며 침묵·`running`만으로 진행을 단정하지 않는다. peer 메시지는 `write agent://<id>`, 취소는 `write proc://<id>/kill`, 살아 있어야 하는 프로세스는 세션마다 고유한 이름의 `bash` `name` 서비스다. 결과 전달·취소·개입·yield 전 job 회수는 `rule://subagent` 「병렬과 대기」와 `harness-policy.json` `mainLane.waitContract`가 정본이다.
69
69
 
70
+ **TODO는 실시간 작업 현황판이다.** Main은 착수·검증 완료·외부 대기·재개·실패/재작업의 상태 전환을 관측한 자리에서 해당 항목을 즉시 갱신한다. 다음 단계나 최종 보고까지 미루거나 채팅 설명으로 대신하지 않는다. 사용자 interjection에는 먼저 즉답한 뒤 같은 턴에서 필요한 갱신을 잇고, 진행 보고 때는 TODO와 실제 상태가 일치하는지 확인한다.
71
+
72
+ - 완료 증거를 수용하면 바로 `done`한다. child의 종료만으로 완료 처리하지 않으며, 국소 구현·검증과 통합·배포 완료를 구분한다.
73
+ - agent·job·사용자 결과를 기다리면 `block`에 누가 무엇을 진행 중이고 무엇이 남았는지 적는다. 이미 시작한 작업을 이유 없는 `pending`으로 남기지 않는다. 결과가 도착해 실행 가능해지면 `unblock`하고 이어 간다.
74
+ - 도구의 자동 활성 포인터는 가장 이른 미완 항목으로 이동한다. 갱신 결과의 실제 상태를 확인하고, 앞선 통합·배포가 아직 대기 중이면 그 사유를 명시해 실제 작업을 가리지 않게 한다. 포인터를 맞추려고 미완을 완료로 표시하거나 완료 항목을 다시 열지 않는다.
75
+
70
76
  ## 착수 계약과 Skill
71
77
 
72
78
  Main은 사용자 요구·수용 조건·보존 동작·승인·관측 경로를 먼저 확정한다. 위임 브리프와 첫 조사에서 요구를 대조하는 절차는 `rule://verdict`·`rule://subagent` 및 `harness-policy.json` `briefContextRelay`·`routing.dispatchAssumptionCheck`를 따른다. `task` 발주 시 `TASK_GUARD` 뒤의 공유 메타 `TASK_TITLE`·`TODO_TASKS`도 `rule://task-guard`에 따라 전달한다. Skill은 설치만으로 열지 않고 이번 작업에 필요하거나 사용자가 요청했을 때만 연다. UI 작업은 `rule://frontend`를 따른다. 사용자가 명시 요청하지 않으면 `goal` 도구에 `token_budget`을 임의 설정하지 않는다.
@@ -6,7 +6,7 @@ thinking-level: medium
6
6
  tools: [read, bash, edit, write, grep, glob, skim, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, checkpoint]
7
7
  ---
8
8
 
9
- <!-- omp-global-config:generated source-hash=816f54313240
9
+ <!-- omp-global-config:generated source-hash=126d76a842c3
10
10
  이 파일은 patches/build-agents.mjs 가 만든 빌드 산출물이다. 직접 수정하지 마라.
11
11
  고칠 곳: agent/sop/maker.md · agent/sop/_writer.md · agent/sop/_common.md
12
12
  재생성: node patches/build-agents.mjs 검사: node patches/build-agents.mjs --check
@@ -121,10 +121,12 @@ is yours end to end.
121
121
  an injected message; you have no `wait`, so the checkpoint never blocks the session. Never send a
122
122
  diff body, raw output, file contents, or a second message per edit step. Only trigger code is held
123
123
  for that reply: keep doing read-only investigation and the non-trigger work in the same slice.
124
- When independent work runs out before the reply arrives, say in prose that you are holding for the
125
- checkpoint reply and end the turn; Main's message wakes you in a new turn. No reply is never
126
- approval - there is no implicit or time-based approval, and trigger edits proceed only on Main's
127
- explicit reply. Main retains the
124
+ When independent work runs out before the reply arrives, use `yield` to report the pending
125
+ checkpoint, completed evidence, and unstarted work as blocked/partial, never as accepted or done.
126
+ Prose alone ends a model turn but leaves the task executor asking for `yield`. A shared-workspace
127
+ Maker resumes on Main's first reply; an isolated worktree is not resumable after terminal yield,
128
+ so preserve the handoff for Main instead of promising a wake. No reply is never approval: trigger
129
+ edits require Main's explicit reply. Main retains the
128
130
  approved location and invariants for the final drift check, so a revision that edited trigger
129
131
  code without an approved checkpoint cannot be closed. This checkpoint is the first contact point
130
132
  of review, not a separate gate: the final judgement covers only deltas not yet reviewed and the
@@ -163,6 +165,10 @@ is yours end to end.
163
165
  build error. Never put a broken intermediate state on the user's screen. Use the user's browser
164
166
  only when their live session is genuinely required, or when Main tells you to hand a FINISHED
165
167
  surface over for judgement — do not open it on your own initiative.
168
+ Do not launch Chrome/Edge executables directly from bash or eval (including `Bun.spawn`) for
169
+ verification. Use the managed browser helpers or the project's supported verification commands.
170
+ Such commands must use an absolute, isolated profile path: even headless startup can show a
171
+ native desktop error dialog when its profile path is invalid.
166
172
  - Run surface checks in the environment Main provides; create a common isolation environment or
167
173
  run a full build yourself only when the brief explicitly delegates that run. When you do launch a
168
174
  server (delegated, or no environment exists
@@ -84,6 +84,8 @@ type SpawnItem = {
84
84
  agent: string;
85
85
  task: string;
86
86
  name?: string;
87
+ /** 호출이 agent를 생략했다. 위임 정의가 maker 하나뿐이라 maker로 해석하고 반환 input에도 기록한다. */
88
+ agentOmitted?: true;
87
89
  };
88
90
 
89
91
  export type TaskCallInput = Record<string, unknown>;
@@ -155,7 +157,7 @@ function readField(block: string, name: string): string | undefined {
155
157
  return value || undefined;
156
158
  }
157
159
 
158
- const WORK_CLASSES = ["feature", "maintenance", "diagnostic"] as const;
160
+ export const WORK_CLASSES = ["feature", "maintenance", "diagnostic"] as const;
159
161
  const PURPOSES = ["primary", "rework"] as const;
160
162
 
161
163
  /**
@@ -280,12 +282,13 @@ function normalizeSpawnItems(input: TaskCallInput): SpawnItem[] | string {
280
282
  return "[SpawnGuard] tasks[] 항목 형식이 올바르지 않습니다.";
281
283
  }
282
284
  const item = rawItem as Record<string, unknown>;
283
- if (typeof item.agent !== "string" || typeof item.task !== "string") {
284
- return "[SpawnGuard] 모든 task 항목은 agent와 task를 명시해야 합니다.";
285
+ if (typeof item.task !== "string" || (item.agent !== undefined && typeof item.agent !== "string")) {
286
+ return "[SpawnGuard] 모든 task 항목은 task 문자열을 가져야 하고 agent는 생략하거나 문자열이어야 합니다.";
285
287
  }
286
288
  items.push({
287
- agent: item.agent.trim().toLowerCase(),
289
+ agent: item.agent === undefined ? "maker" : item.agent.trim().toLowerCase(),
288
290
  task: item.task,
291
+ ...(item.agent === undefined ? { agentOmitted: true as const } : {}),
289
292
  ...(typeof item.name === "string" && item.name.trim()
290
293
  ? { name: item.name.trim() }
291
294
  : {}),
@@ -294,13 +297,14 @@ function normalizeSpawnItems(input: TaskCallInput): SpawnItem[] | string {
294
297
  return items;
295
298
  }
296
299
 
297
- if (typeof input.agent !== "string" || typeof input.task !== "string") {
298
- return "[SpawnGuard] task 호출은 agent와 task를 명시해야 합니다.";
300
+ if (typeof input.task !== "string" || (input.agent !== undefined && typeof input.agent !== "string")) {
301
+ return "[SpawnGuard] task 호출은 task 문자열을 가져야 하고 agent는 생략하거나 문자열이어야 합니다.";
299
302
  }
300
303
  return [
301
304
  {
302
- agent: input.agent.trim().toLowerCase(),
305
+ agent: input.agent === undefined ? "maker" : input.agent.trim().toLowerCase(),
303
306
  task: input.task,
307
+ ...(input.agent === undefined ? { agentOmitted: true as const } : {}),
304
308
  ...(typeof input.name === "string" && input.name.trim()
305
309
  ? { name: input.name.trim() }
306
310
  : {}),
@@ -412,6 +416,7 @@ export function reserveTaskCall(
412
416
  const makers: MakerSpawn[] = [];
413
417
  // 파생이 일어난 항목만 정규 블록으로 교체한다. 나머지 항목은 원본 문자열을 그대로 쓴다.
414
418
  const rewritten = new Map<number, string>();
419
+ const agentFilled = new Set<number>();
415
420
 
416
421
  for (const [index, item] of normalized.entries()) {
417
422
  const raw = parseGuardMetadata(item.task);
@@ -446,6 +451,7 @@ export function reserveTaskCall(
446
451
  raw.blocksPrimary === undefined ||
447
452
  raw.primaryDeliverable === undefined;
448
453
  if (derived) rewritten.set(index, canonicalizeTask(item.task, raw, metadata, item.agent));
454
+ if (item.agentOmitted) agentFilled.add(index);
449
455
  }
450
456
 
451
457
  const overBudget = budgetReason(state.usage, delta);
@@ -459,21 +465,36 @@ export function reserveTaskCall(
459
465
  makers,
460
466
  });
461
467
  for (const entry of refundable) state.refundable.set(entry.name, entry.delta);
462
- if (rewritten.size === 0) return { ok: true };
463
- return { ok: true, input: replaceTaskBodies(input, rewritten) };
468
+ if (rewritten.size === 0 && agentFilled.size === 0) return { ok: true };
469
+ return { ok: true, input: replaceTaskBodies(input, rewritten, agentFilled) };
464
470
  }
465
471
 
466
472
  /**
467
- * 원본 입력을 제자리에서 바꾸지 않고 교체 본문만 담은 얕은 복사본을 만든다. tasks[]의 다른 항목과
468
- * context·model·isolated 같은 나머지 키는 그대로 보존한다.
473
+ * 원본 입력을 제자리에서 바꾸지 않고 교체 본문과 생략된 agent의 maker 기록만 담은 얕은 복사본을 만든다.
474
+ * tasks[]의 다른 항목과 context·model·isolated 같은 나머지 키는 그대로 보존한다.
469
475
  */
470
- function replaceTaskBodies(input: TaskCallInput, rewritten: Map<number, string>): TaskCallInput {
471
- if (!Array.isArray(input.tasks)) return { ...input, task: rewritten.get(0) };
476
+ function replaceTaskBodies(
477
+ input: TaskCallInput,
478
+ rewritten: Map<number, string>,
479
+ agentFilled: Set<number>,
480
+ ): TaskCallInput {
481
+ if (!Array.isArray(input.tasks)) {
482
+ return {
483
+ ...input,
484
+ ...(rewritten.has(0) ? { task: rewritten.get(0) } : {}),
485
+ ...(agentFilled.has(0) ? { agent: "maker" } : {}),
486
+ };
487
+ }
472
488
  return {
473
489
  ...input,
474
490
  tasks: input.tasks.map((item, index) => {
475
491
  const task = rewritten.get(index);
476
- return task === undefined ? item : { ...(item as Record<string, unknown>), task };
492
+ if (task === undefined && !agentFilled.has(index)) return item;
493
+ return {
494
+ ...(item as Record<string, unknown>),
495
+ ...(task === undefined ? {} : { task }),
496
+ ...(agentFilled.has(index) ? { agent: "maker" } : {}),
497
+ };
477
498
  }),
478
499
  };
479
500
  }
@@ -20,10 +20,17 @@ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
20
20
  * 쓰기 도중 충돌할 수 있으므로, 워커가 DB 파일을 공유 없이 열어 보고 다른 프로세스가 쥐고 있으면
21
21
  * 이번에는 건너뛴다(다음 세션이 다시 시도한다).
22
22
  * - 실패는 세션으로 올리지 않고 gitignore된 `.gitnexus/autosync.log`에만 남긴다.
23
+ * - analyze가 끝나면(그리고 색인이 HEAD와 같아도 이 커밋의 검사 기록이 없으면 analyze 없이도) 색인 무결성을 읽는다.
24
+ * GitNexus 1.6.12 색인에서 Function 행의 id(심하면 이름까지)가 비는 손상을 실제로 봤다. 그러면 `impact`가 알려진
25
+ * 심볼을 못 찾거나 부풀리고 `detect-changes`가 모든 프로세스를 영향으로 보고한다(증거: doc/history/2026/10/05-five-improvements/
26
+ * maker-gitnexus.md). 손상이 있으면 `analyze --index-only --force`로 한 번 다시 만들고, 결과를
27
+ * `.gitnexus/autosync-health.json`에 남겨 같은 커밋을 다시 검사하지 않는다. 손상의 유발 조건은 최소 재현하지 못했다.
23
28
  */
24
29
 
25
30
  const LOCK_NAME = "autosync.lock";
26
31
  const LOG_NAME = "autosync.log";
32
+ const ANALYZE_ARGS = ["analyze", "--index-only"];
33
+ const HEALTH_NAME = "autosync-health.json";
27
34
  /** 살아 있는 pid라도 이보다 오래된 잠금은 pid 재사용·고착으로 보고 회수한다. */
28
35
  const LOCK_MAX_AGE_MS = 6 * 60 * 60 * 1000;
29
36
  const GIT_TIMEOUT_MS = 10_000;
@@ -43,8 +50,12 @@ export interface WorkerJob {
43
50
  head: string;
44
51
  lockPath: string;
45
52
  logPath: string;
53
+ /** 색인 무결성 검사 결과를 남기는 파일. */
54
+ healthPath: string;
55
+ /** 색인이 이미 HEAD와 같아 analyze 없이 무결성 검사만 한다. */
56
+ verifyOnly: boolean;
46
57
  token: string;
47
- /** analyze를 실행할 프로그램과 인자. */
58
+ /** analyze를 실행할 프로그램과 인자. 마지막 두 항목 앞이 실행기 접두이며 cypher 검사에도 쓴다. */
48
59
  command: string[];
49
60
  /** Windows `.cmd` 실행기처럼 셸이 필요한 경우. */
50
61
  shell: boolean;
@@ -121,7 +132,38 @@ const dbInUse = () => {
121
132
  const readLastCommit = () => {
122
133
  try { return JSON.parse(fs.readFileSync(job.root + "/.gitnexus/meta.json", "utf8")).lastCommit; } catch { return undefined; }
123
134
  };
135
+ // 실행기 접두(analyze 앞부분)에 인자를 붙여 실행한다. capture면 stdout을 모아 돌려주고, 아니면 로그 fd로 보낸다.
136
+ const base = job.command.slice(0, job.command.indexOf("analyze"));
137
+ const quote = (text) => (job.shell ? '"' + text + '"' : text);
138
+ const execute = (env, args, out) => {
139
+ const { promise, resolve } = Promise.withResolvers();
140
+ let stdout = "";
141
+ const child = spawn(base[0], base.slice(1).concat(args), {
142
+ cwd: job.root, env, stdio: ["ignore", out === undefined ? "pipe" : out, out === undefined ? "ignore" : out], windowsHide: true, shell: job.shell,
143
+ });
144
+ if (out === undefined) child.stdout.on("data", (chunk) => { stdout += chunk; });
145
+ child.on("error", (error) => { log("spawn error: " + error.message); resolve({ code: -1, stdout }); });
146
+ child.on("close", (exitCode, signal) => resolve({ code: exitCode === null ? "signal " + signal : exitCode, stdout }));
147
+ return promise;
148
+ };
149
+ const readCount = async (env, query) => {
150
+ const { code, stdout } = await execute(env, ["cypher", quote(query)]);
151
+ if (code !== 0) return undefined;
152
+ try {
153
+ const json = JSON.parse(stdout.slice(stdout.indexOf("{"), stdout.lastIndexOf("}") + 1));
154
+ const cell = String(json.markdown).split("\n").pop().split("|").map((s) => s.trim()).filter(Boolean)[0];
155
+ const count = Number(cell);
156
+ return Number.isInteger(count) ? count : undefined;
157
+ } catch { return undefined; }
158
+ };
159
+ // Function 행 수와 id가 온전한(Function: 접두) 행 수. id가 빈 행이 있으면 impact와 detect-changes가 틀린다.
160
+ const functionRows = async (env) => {
161
+ const total = await readCount(env, "MATCH (n:Function) RETURN count(n) AS c");
162
+ const intact = await readCount(env, "MATCH (n:Function) WHERE n.id STARTS WITH 'Function:' RETURN count(n) AS c");
163
+ return total === undefined || intact === undefined ? undefined : { total, intact };
164
+ };
124
165
  (async () => {
166
+ let out;
125
167
  try {
126
168
  if (dbInUse()) {
127
169
  log("skip: index DB is open by another process (e.g. a GitNexus MCP reader); next session retries");
@@ -132,23 +174,42 @@ const readLastCommit = () => {
132
174
  const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") || "PATH";
133
175
  env[key] = job.pathPrefix + (process.platform === "win32" ? ";" : ":") + (env[key] || "");
134
176
  }
135
- const out = fs.openSync(job.logPath, "a");
136
- const started = Date.now();
137
- log("start: " + job.command.join(" ") + " (head " + job.head.slice(0, 12) + ")");
138
- const code = await new Promise((resolve) => {
139
- const child = spawn(job.command[0], job.command.slice(1), {
140
- cwd: job.root, env, stdio: ["ignore", out, out], windowsHide: true, shell: job.shell,
141
- });
142
- child.on("error", (error) => { log("spawn error: " + error.message); resolve(-1); });
143
- child.on("exit", (exitCode, signal) => resolve(exitCode === null ? "signal " + signal : exitCode));
144
- });
145
- fs.closeSync(out);
146
- const indexed = readLastCommit();
147
- log("end: exit=" + code + " seconds=" + ((Date.now() - started) / 1000).toFixed(1) +
148
- " lastCommit=" + String(indexed).slice(0, 12) + (indexed === job.head ? " (matches head)" : " (differs from head)"));
177
+ out = fs.openSync(job.logPath, "a");
178
+ const analyzeArgs = job.command.slice(base.length);
179
+ if (!job.verifyOnly) {
180
+ const started = Date.now();
181
+ log("start: " + job.command.join(" ") + " (head " + job.head.slice(0, 12) + ")");
182
+ const { code } = await execute(env, analyzeArgs, out);
183
+ const indexed = readLastCommit();
184
+ log("end: exit=" + code + " seconds=" + ((Date.now() - started) / 1000).toFixed(1) +
185
+ " lastCommit=" + String(indexed).slice(0, 12) + (indexed === job.head ? " (matches head)" : " (differs from head)"));
186
+ if (code !== 0) return;
187
+ } else {
188
+ log("verify: index is at head " + job.head.slice(0, 12) + "; checking index integrity only");
189
+ }
190
+ let rows = await functionRows(env);
191
+ // 손상이 보이면 증분 갱신 뒤든 검사만 하는 경우든 --force 전체 재빌드를 한 번 한다.
192
+ if (rows && rows.intact < rows.total) {
193
+ log("integrity: " + (rows.total - rows.intact) + " of " + rows.total + " Function rows lost their id; rebuilding with --force");
194
+ if (dbInUse()) {
195
+ log("skip: index DB is open by another process; rebuild deferred");
196
+ return;
197
+ } else {
198
+ const started = Date.now();
199
+ const { code } = await execute(env, analyzeArgs.concat("--force"), out);
200
+ log("forced rebuild end: exit=" + code + " seconds=" + ((Date.now() - started) / 1000).toFixed(1));
201
+ rows = await functionRows(env);
202
+ }
203
+ }
204
+ log("integrity: " + (rows ? rows.intact + "/" + rows.total + " Function rows have their id" : "unreadable"));
205
+ // 같은 커밋을 세션마다 다시 검사하지 않도록 결과를 남긴다.
206
+ try {
207
+ fs.writeFileSync(job.healthPath, JSON.stringify({ lastCommit: readLastCommit(), checkedAt: Date.now(), rows: rows ?? null }));
208
+ } catch {}
149
209
  } catch (error) {
150
210
  log("worker error: " + (error && error.message ? error.message : String(error)));
151
211
  } finally {
212
+ if (out !== undefined) try { fs.closeSync(out); } catch {}
152
213
  release();
153
214
  }
154
215
  })();
@@ -248,11 +309,11 @@ function resolveRunner(deps: AutoSyncDeps): { command: string[]; shell: boolean;
248
309
  const installed = deps.which("gitnexus");
249
310
  if (installed) {
250
311
  const shell = deps.platform === "win32" && /\.(cmd|bat)$/i.test(installed);
251
- return { command: [shell ? `"${installed}"` : installed, "analyze", "--index-only"], shell, nodePath };
312
+ return { command: [shell ? `"${installed}"` : installed, ...ANALYZE_ARGS], shell, nodePath };
252
313
  }
253
314
  // --no-install: 캐시에 없으면 내려받지 않고 바로 실패한다.
254
315
  const bun = deps.which("bun");
255
- if (bun) return { command: [bun, "x", "--no-install", "gitnexus", "analyze", "--index-only"], shell: false, nodePath };
316
+ if (bun) return { command: [bun, "x", "--no-install", "gitnexus", ...ANALYZE_ARGS], shell: false, nodePath };
256
317
  return undefined;
257
318
  }
258
319
 
@@ -282,7 +343,10 @@ export async function syncGitNexusIndex(cwd: string, deps: AutoSyncDeps = defaul
282
343
  try {
283
344
  const lastCommit = readJsonObject(metaPath)?.lastCommit;
284
345
  const head = await deps.git(["rev-parse", "HEAD"], root);
285
- if (lastCommit === head) return "fresh";
346
+ // 색인이 HEAD와 같아도 무결성을 한 번도 확인하지 않은 커밋이면 analyze 없이 검사만 하는 워커를 띄운다.
347
+ const healthPath = path.join(storage, HEALTH_NAME);
348
+ const verifyOnly = lastCommit === head;
349
+ if (verifyOnly && readJsonObject(healthPath)?.lastCommit === head) return "fresh";
286
350
  const runner = resolveRunner(deps);
287
351
  if (!runner) {
288
352
  appendLog(logPath, deps.now(), "skip: no node or GitNexus runner available");
@@ -296,6 +360,8 @@ export async function syncGitNexusIndex(cwd: string, deps: AutoSyncDeps = defaul
296
360
  head,
297
361
  lockPath,
298
362
  logPath,
363
+ healthPath,
364
+ verifyOnly,
299
365
  token,
300
366
  command: runner.command,
301
367
  shell: runner.shell,
@@ -312,7 +378,11 @@ export async function syncGitNexusIndex(cwd: string, deps: AutoSyncDeps = defaul
312
378
  // 잠금 소유자를 실제 워커로 넘겨 이 세션이 끝나도 워커가 사는 동안 잠금이 유지되게 한다.
313
379
  // 워커가 이미 끝나 잠금을 지웠다면 되살리지 않는다.
314
380
  if (readLock(lockPath)?.token === token) writeLockAtomically(lockPath, { pid, token, startedAt: deps.now() });
315
- appendLog(logPath, deps.now(), `spawned worker pid ${pid} (index ${String(lastCommit).slice(0, 12)} -> head ${head.slice(0, 12)})`);
381
+ appendLog(
382
+ logPath,
383
+ deps.now(),
384
+ `spawned ${verifyOnly ? "verify-only " : ""}worker pid ${pid} (index ${String(lastCommit).slice(0, 12)} -> head ${head.slice(0, 12)})`,
385
+ );
316
386
  return "started";
317
387
  } catch (error) {
318
388
  appendLog(logPath, deps.now(), `error: ${error instanceof Error ? error.message : String(error)}`);