agent-work-loop 0.0.0 → 0.6.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +272 -12
  2. package/dist/brief-Z3JKXEUP.js +181 -0
  3. package/dist/changelog-R7BNBF2C.js +62 -0
  4. package/dist/chunk-46HZN6UB.js +446 -0
  5. package/dist/chunk-4OCSYHYB.js +274 -0
  6. package/dist/chunk-6E7XEQOH.js +27 -0
  7. package/dist/chunk-7SYRDDTX.js +516 -0
  8. package/dist/chunk-BUWGQVHT.js +1243 -0
  9. package/dist/chunk-C7BR2DCS.js +96 -0
  10. package/dist/chunk-D5OINC3G.js +52 -0
  11. package/dist/chunk-DP4O5ME2.js +307 -0
  12. package/dist/chunk-F5LHXBH7.js +209 -0
  13. package/dist/chunk-G5LAJ5TV.js +453 -0
  14. package/dist/chunk-I77CXOEX.js +693 -0
  15. package/dist/chunk-IMB46O6S.js +286 -0
  16. package/dist/chunk-IXMAFR4Y.js +771 -0
  17. package/dist/chunk-QE2CLNBG.js +347 -0
  18. package/dist/chunk-UOPWVM2H.js +727 -0
  19. package/dist/chunk-YTAHVR4P.js +166 -0
  20. package/dist/chunk-ZE6HXOYG.js +904 -0
  21. package/dist/cli.js +374 -13
  22. package/dist/commit-APXIVOSD.js +411 -0
  23. package/dist/config-TFMW7O4T.js +34 -0
  24. package/dist/doctor-SSKNLPGH.js +29 -0
  25. package/dist/evolve-QPD7TWGO.js +38 -0
  26. package/dist/feedback-KAXNFMUY.js +125 -0
  27. package/dist/gotchas-MCA5Y76R.js +43 -0
  28. package/dist/hold-recheck-WN5EG7HD.js +133 -0
  29. package/dist/init-UDM5AXKI.js +79 -0
  30. package/dist/lane-DAZISODH.js +41 -0
  31. package/dist/loop-summary-XAI6KOGB.js +361 -0
  32. package/dist/metrics-WLRZZRTK.js +25 -0
  33. package/dist/record-UKDIUJ5T.js +68 -0
  34. package/dist/review-ZTHDJ47V.js +118 -0
  35. package/dist/rules-R2UZPIVW.js +33 -0
  36. package/dist/state-XM7NZ2HA.js +37 -0
  37. package/dist/status-L6U5KO6T.js +40 -0
  38. package/dist/uninstall-5DFEOFL5.js +545 -0
  39. package/dist/update-AYTBYAHI.js +61 -0
  40. package/dist/verify-L7ARTK42.js +37 -0
  41. package/dist/version-check-LKGU2DNF.js +14 -0
  42. package/dist/work-KEPTGZ6H.js +50 -0
  43. package/engine/skills/claude/awl-loop/SKILL.md +292 -0
  44. package/engine/skills/claude/awl-loop/reference.md +131 -0
  45. package/engine/skills/claude/awl-pipeline/SKILL.md +89 -0
  46. package/engine/skills/claude/awl-pipeline-exec/SKILL.md +200 -0
  47. package/engine/skills/claude/awl-pipeline-plan/SKILL.md +71 -0
  48. package/engine/skills/claude/awl-pipeline-review/SKILL.md +149 -0
  49. package/engine/skills/codex/AGENTS.awl.md +117 -0
  50. package/engine/templates/block-publish.mjs +9 -0
  51. package/engine/templates/pre-push.sample +7 -0
  52. package/engine/templates/related-cmd-examples.md +37 -0
  53. package/engine/version.json +2 -2
  54. package/package.json +10 -4
@@ -0,0 +1,96 @@
1
+ import {
2
+ checkVersions,
3
+ getLatestVersionCached,
4
+ name,
5
+ version
6
+ } from "./chunk-YTAHVR4P.js";
7
+ import {
8
+ resolveProjectRoot
9
+ } from "./chunk-UOPWVM2H.js";
10
+ import {
11
+ packageEngineDir,
12
+ skillsVersionPath
13
+ } from "./chunk-BUWGQVHT.js";
14
+ import {
15
+ caps,
16
+ card,
17
+ installedEngineVersion,
18
+ makeColors,
19
+ makeSymbols,
20
+ makeTokens,
21
+ signal
22
+ } from "./chunk-7SYRDDTX.js";
23
+
24
+ // src/commands/version-check.ts
25
+ import fs from "fs";
26
+ import path from "path";
27
+ function readJson(p) {
28
+ try {
29
+ return JSON.parse(fs.readFileSync(p, "utf8"));
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ function readVersionField(p, field) {
35
+ const raw = readJson(p);
36
+ if (raw && typeof raw === "object") {
37
+ const v = raw[field];
38
+ if (typeof v === "string") {
39
+ return v;
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ function gatherVersionInputs(projectRoot, npmLatestVersion = null) {
45
+ const engineSourceVersion = readVersionField(
46
+ path.join(packageEngineDir(), "version.json"),
47
+ "engineVersion"
48
+ );
49
+ const installed = installedEngineVersion();
50
+ const projectEngineVersion = projectRoot ? readVersionField(path.join(projectRoot, ".awl", "config.json"), "engineVersion") : null;
51
+ const skillsRaw = projectRoot ? readJson(skillsVersionPath(projectRoot)) : null;
52
+ const installedSkillVersions = {
53
+ claude: skillsRaw && typeof skillsRaw === "object" ? typeof skillsRaw.claude === "string" ? skillsRaw.claude : null : null,
54
+ codex: skillsRaw && typeof skillsRaw === "object" ? typeof skillsRaw.codex === "string" ? skillsRaw.codex : null : null
55
+ };
56
+ return {
57
+ packageVersion: version,
58
+ engineSourceVersion,
59
+ installedEngineVersion: installed,
60
+ projectEngineVersion,
61
+ installedSkillVersions,
62
+ npmLatestVersion
63
+ };
64
+ }
65
+ function renderVersionCheck(result, c) {
66
+ const color = makeColors(c.color);
67
+ const s = makeSymbols(c);
68
+ const t = makeTokens(c);
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);
71
+ }
72
+ const out = [];
73
+ for (const m of result.mismatches) {
74
+ out.push(`${signal(c, "warn")} ${m.kind}: ${t.emphasis(m.a)} / ${t.emphasis(m.b)}`);
75
+ out.push(`${s.lastBranch} ${color.dim(m.hint)}`);
76
+ }
77
+ return card(`\uBC84\uC804 \uBD88\uC77C\uCE58 ${result.mismatches.length}\uAC74`, out, c);
78
+ }
79
+ async function runVersionCheck(opts) {
80
+ const projectRoot = resolveProjectRoot();
81
+ const npmLatestVersion = await getLatestVersionCached(name);
82
+ const inputs = gatherVersionInputs(projectRoot, npmLatestVersion);
83
+ const result = checkVersions(inputs);
84
+ if (opts.json) {
85
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
86
+ `);
87
+ } else {
88
+ process.stdout.write(renderVersionCheck(result, caps()));
89
+ }
90
+ }
91
+
92
+ export {
93
+ gatherVersionInputs,
94
+ renderVersionCheck,
95
+ runVersionCheck
96
+ };
@@ -0,0 +1,52 @@
1
+ // src/core/usage.ts
2
+ import fs from "fs";
3
+ var DEFAULT_USAGE_PATH = "/tmp/cc-usage.json";
4
+ function readCostSnapshot(file = DEFAULT_USAGE_PATH) {
5
+ let text;
6
+ try {
7
+ text = fs.readFileSync(file, "utf8");
8
+ } catch {
9
+ return void 0;
10
+ }
11
+ let raw;
12
+ try {
13
+ raw = JSON.parse(text);
14
+ } catch {
15
+ return void 0;
16
+ }
17
+ const num = (v) => typeof v === "number" ? v : void 0;
18
+ const snap = {};
19
+ const cost = num(raw.cost);
20
+ if (cost !== void 0) {
21
+ snap.cost = cost;
22
+ }
23
+ const fiveH = num(raw.five_h_pct);
24
+ if (fiveH !== void 0) {
25
+ snap.five_h_pct = fiveH;
26
+ }
27
+ const sevenD = num(raw.seven_d_pct);
28
+ if (sevenD !== void 0) {
29
+ snap.seven_d_pct = sevenD;
30
+ }
31
+ const ts = num(raw.ts);
32
+ if (ts !== void 0) {
33
+ snap.ts = ts;
34
+ }
35
+ return snap;
36
+ }
37
+ function computeCostDelta(start, end) {
38
+ if (!start || !end || typeof start.cost !== "number" || typeof end.cost !== "number") {
39
+ return void 0;
40
+ }
41
+ const d = end.cost - start.cost;
42
+ if (d < 0) {
43
+ return void 0;
44
+ }
45
+ return Math.round(d * 100) / 100;
46
+ }
47
+
48
+ export {
49
+ DEFAULT_USAGE_PATH,
50
+ readCostSnapshot,
51
+ computeCostDelta
52
+ };
@@ -0,0 +1,307 @@
1
+ import {
2
+ mergeIsolatedHome,
3
+ removeGitWorktree,
4
+ removeWorkitemFromState,
5
+ runWorkNew,
6
+ sanitizeForGit,
7
+ worktreeDirtyTracked
8
+ } from "./chunk-ZE6HXOYG.js";
9
+ import {
10
+ loadState,
11
+ writeState
12
+ } from "./chunk-4OCSYHYB.js";
13
+ import {
14
+ resolveProjectRoot,
15
+ run
16
+ } from "./chunk-UOPWVM2H.js";
17
+ import {
18
+ caps,
19
+ card,
20
+ feedback,
21
+ makeColors,
22
+ signal
23
+ } from "./chunk-7SYRDDTX.js";
24
+
25
+ // src/commands/lane.ts
26
+ import fs from "fs";
27
+ import path from "path";
28
+ var WORKTREES_DIR = ".awl-worktrees";
29
+ var PIPELINE_TRIGGERS = ["/awl-pipeline-plan", "/awl-pipeline-exec", "/awl-pipeline-review"];
30
+ function requireRoot() {
31
+ const root = resolveProjectRoot();
32
+ if (!root) {
33
+ process.stderr.write("\n \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. awl init \uC744 \uC2E4\uD589\uD558\uC138\uC694.\n");
34
+ process.exit(1);
35
+ }
36
+ return root;
37
+ }
38
+ async function isGitWorkTree(root) {
39
+ const r = await run({
40
+ cmd: "git",
41
+ args: ["rev-parse", "--is-inside-work-tree"],
42
+ cwd: root,
43
+ timeoutMs: 1e4
44
+ });
45
+ return r.exitCode === 0 && r.stdout.trim() === "true";
46
+ }
47
+ async function runLaneNew(name, description) {
48
+ const root = requireRoot();
49
+ const c = caps();
50
+ const color = makeColors(c.color);
51
+ if (!await isGitWorkTree(root)) {
52
+ process.stderr.write(
53
+ `
54
+ ${feedback(c, "error", "\uB808\uC778\uC744 \uB9CC\uB4E4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4", "\uD604\uC7AC \uC704\uCE58\uAC00 git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 awl lane \uC740 git worktree \uB97C \uC501\uB2C8\uB2E4")}
55
+ `
56
+ );
57
+ process.exit(1);
58
+ }
59
+ const laneName = sanitizeForGit(name);
60
+ if (!laneName) {
61
+ process.stderr.write(`
62
+ ${feedback(c, "error", "\uB808\uC778 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694")}
63
+ `);
64
+ process.exit(1);
65
+ }
66
+ const lanePath = path.join(root, WORKTREES_DIR, laneName);
67
+ if (fs.existsSync(lanePath)) {
68
+ process.stderr.write(
69
+ `
70
+ ${feedback(c, "error", `\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uB808\uC778\uC785\uB2C8\uB2E4: ${laneName}`, `awl lane rm ${laneName} \uB85C \uBA3C\uC800 \uC815\uB9AC\uD558\uC138\uC694`)}
71
+ `
72
+ );
73
+ process.exit(1);
74
+ }
75
+ await runWorkNew(name, description, { worktree: true, isolated: true });
76
+ process.stdout.write(`
77
+ ${feedback(c, "ok", `\uB808\uC778 \uC900\uBE44 ${color.bold(laneName)}`)}
78
+ `);
79
+ process.stdout.write(` ${color.dim("\uC774 \uB808\uC778\uC758 \uC5ED\uD560 \uC138\uC158\uC5D0\uC11C \uC2A4\uD0AC \uD2B8\uB9AC\uAC70\uB97C \uC2E4\uD589\uD558\uC138\uC694:")}
80
+ `);
81
+ for (const t of PIPELINE_TRIGGERS) {
82
+ process.stdout.write(` ${color.dim(t)}
83
+ `);
84
+ }
85
+ }
86
+ function parseWorktreeBranches(porcelain) {
87
+ const map = /* @__PURE__ */ new Map();
88
+ let current = null;
89
+ for (const raw of porcelain.split("\n")) {
90
+ const line = raw.trim();
91
+ if (line.startsWith("worktree ")) {
92
+ current = line.slice("worktree ".length);
93
+ } else if (line.startsWith("branch ") && current) {
94
+ map.set(current, line.slice("branch ".length).replace(/^refs\/heads\//, ""));
95
+ } else if (line === "") {
96
+ current = null;
97
+ }
98
+ }
99
+ return map;
100
+ }
101
+ async function laneBranchMap(root) {
102
+ const r = await run({
103
+ cmd: "git",
104
+ args: ["worktree", "list", "--porcelain"],
105
+ cwd: root,
106
+ timeoutMs: 1e4
107
+ });
108
+ if (r.exitCode !== 0) {
109
+ return /* @__PURE__ */ new Map();
110
+ }
111
+ return parseWorktreeBranches(r.stdout);
112
+ }
113
+ async function unmergedCommitCount(root, branch) {
114
+ const r = await run({
115
+ cmd: "git",
116
+ args: ["rev-list", "--count", `HEAD..${branch}`],
117
+ cwd: root,
118
+ timeoutMs: 1e4
119
+ });
120
+ if (r.exitCode !== 0) {
121
+ return null;
122
+ }
123
+ const n = Number.parseInt(r.stdout.trim(), 10);
124
+ return Number.isNaN(n) ? null : n;
125
+ }
126
+ var AWL_INTERNAL_DIRS = /* @__PURE__ */ new Set([".awl", ".awl-worktrees", ".claude"]);
127
+ async function worktreeUntracked(root, targetPath) {
128
+ const r = await run({
129
+ cmd: "git",
130
+ args: ["-C", targetPath, "ls-files", "--others", "--exclude-standard"],
131
+ cwd: root,
132
+ timeoutMs: 1e4
133
+ });
134
+ const files = r.stdout.split("\n").map((l) => l.trim()).filter(Boolean).filter((f) => !AWL_INTERNAL_DIRS.has(f.split("/")[0] ?? ""));
135
+ return { untracked: files.length > 0, count: files.length, first: files[0] };
136
+ }
137
+ function branchOf(branches, lanePath) {
138
+ const direct = branches.get(lanePath);
139
+ if (direct) {
140
+ return direct;
141
+ }
142
+ try {
143
+ return branches.get(fs.realpathSync(lanePath));
144
+ } catch {
145
+ return void 0;
146
+ }
147
+ }
148
+ async function collectLanes(root) {
149
+ const base = path.join(root, WORKTREES_DIR);
150
+ let entries;
151
+ try {
152
+ entries = fs.readdirSync(base, { withFileTypes: true });
153
+ } catch {
154
+ return [];
155
+ }
156
+ const branches = await laneBranchMap(root);
157
+ const lanes = [];
158
+ for (const e of entries) {
159
+ if (!e.isDirectory()) {
160
+ continue;
161
+ }
162
+ const p = path.join(base, e.name);
163
+ lanes.push({ name: e.name, path: p, branch: branchOf(branches, p) ?? "(detached)" });
164
+ }
165
+ lanes.sort((a, b) => a.name.localeCompare(b.name));
166
+ return lanes;
167
+ }
168
+ function renderLaneList(lanes, c) {
169
+ const color = makeColors(c.color);
170
+ if (lanes.length === 0) {
171
+ return card(
172
+ "\uB808\uC778",
173
+ [
174
+ `${signal(c, "info")} \uB808\uC778\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`,
175
+ "",
176
+ color.dim("awl lane new <name> \uB85C \uACA9\uB9AC \uB808\uC778\uC744 \uB9CC\uB4DC\uC138\uC694.")
177
+ ],
178
+ c
179
+ );
180
+ }
181
+ const nameWidth = Math.max(...lanes.map((l) => l.name.length), 4) + 2;
182
+ const out = [];
183
+ for (const l of lanes) {
184
+ out.push(`${color.bold(l.name.padEnd(nameWidth, " "))}${color.dim(l.branch)}`);
185
+ out.push(` ${color.dim(l.path)}`);
186
+ }
187
+ return card("\uB808\uC778", out, c);
188
+ }
189
+ async function runLaneList(opts) {
190
+ const root = requireRoot();
191
+ const lanes = await collectLanes(root);
192
+ if (opts.json) {
193
+ process.stdout.write(`${JSON.stringify(lanes, null, 2)}
194
+ `);
195
+ } else {
196
+ process.stdout.write(`${renderLaneList(lanes, caps())}
197
+ `);
198
+ }
199
+ }
200
+ async function runLaneRemove(name, opts = {}) {
201
+ const root = requireRoot();
202
+ const c = caps();
203
+ const color = makeColors(c.color);
204
+ const laneName = sanitizeForGit(name);
205
+ if (!laneName) {
206
+ process.stderr.write(`
207
+ ${feedback(c, "error", "\uB808\uC778 \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694")}
208
+ `);
209
+ process.exit(1);
210
+ }
211
+ const lanePath = path.join(root, WORKTREES_DIR, laneName);
212
+ if (!fs.existsSync(lanePath)) {
213
+ process.stderr.write(
214
+ `
215
+ ${feedback(c, "error", `\uB808\uC778\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${laneName}`, "awl lane ls \uB85C \uD604\uC874 \uB808\uC778\uC744 \uD655\uC778\uD558\uC138\uC694")}
216
+ `
217
+ );
218
+ process.exit(1);
219
+ }
220
+ const branches = await laneBranchMap(root);
221
+ const branch = branchOf(branches, lanePath) ?? `work/${laneName}`;
222
+ if (!opts.force) {
223
+ const d = await worktreeDirtyTracked(root, lanePath);
224
+ if (d.dirty) {
225
+ process.stderr.write(
226
+ `
227
+ ${feedback(c, "error", `\uB808\uC778\uC5D0 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD ${d.count}\uAC74 (\uC608: ${d.first})`, "--force \uB85C \uAC15\uC81C \uC81C\uAC70\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4")}
228
+ `
229
+ );
230
+ process.exit(1);
231
+ }
232
+ const u = await worktreeUntracked(root, lanePath);
233
+ if (u.untracked) {
234
+ process.stderr.write(
235
+ `
236
+ ${feedback(c, "error", `\uB808\uC778\uC5D0 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uC0C8 \uD30C\uC77C ${u.count}\uAC74 (\uC608: ${u.first})`, "git add\xB7\uCEE4\uBC0B\uD558\uAC70\uB098 --force \uB85C \uAC15\uC81C \uC81C\uAC70\uD558\uC138\uC694")}
237
+ `
238
+ );
239
+ process.exit(1);
240
+ }
241
+ const unmerged = await unmergedCommitCount(root, branch);
242
+ if (unmerged === null) {
243
+ process.stderr.write(
244
+ `
245
+ ${feedback(c, "error", `\uB808\uC778 \uBE0C\uB79C\uCE58 ${branch} \uC758 \uBBF8\uBA38\uC9C0 \uCEE4\uBC0B \uC218\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (rm \uD558\uBA74 \uCEE4\uBC0B\uC774 \uD30C\uAE30\uB420 \uC218 \uC788\uC2B5\uB2C8\uB2E4)`, "\uBA3C\uC800 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD558\uAC70\uB098 --force \uB85C \uAC15\uC81C \uC81C\uAC70\uD558\uC138\uC694")}
246
+ `
247
+ );
248
+ process.exit(1);
249
+ }
250
+ if (unmerged > 0) {
251
+ process.stderr.write(
252
+ `
253
+ ${feedback(c, "error", `\uB808\uC778 \uBE0C\uB79C\uCE58 ${branch} \uC5D0 \uBCD1\uD569\uB418\uC9C0 \uC54A\uC740 \uCEE4\uBC0B ${unmerged}\uAC1C (rm \uD558\uBA74 \uD30C\uAE30\uB429\uB2C8\uB2E4)`, "\uBA3C\uC800 \uBCD1\uD569\xB7\uD478\uC2DC\uD558\uAC70\uB098 --force \uB85C \uAC15\uC81C \uC81C\uAC70\uD558\uC138\uC694")}
254
+ `
255
+ );
256
+ process.exit(1);
257
+ }
258
+ }
259
+ let merged = null;
260
+ try {
261
+ merged = mergeIsolatedHome(path.join(lanePath, ".awl", "home"));
262
+ } catch (e) {
263
+ process.stderr.write(
264
+ `
265
+ ${feedback(c, "error", "\uACA9\uB9AC \uD559\uC2B5 \uC804\uC5ED \uBCD1\uD569 \uC2E4\uD328 \u2014 \uB808\uC778\uC744 \uBCF4\uC874\uD569\uB2C8\uB2E4", e instanceof Error ? e.message : String(e))}
266
+ `
267
+ );
268
+ process.exit(1);
269
+ }
270
+ const removed = await removeGitWorktree(root, lanePath, branch);
271
+ if (!removed.ok) {
272
+ process.stderr.write(`
273
+ ${feedback(c, "error", "\uB808\uC778 \uC81C\uAC70 \uC2E4\uD328", removed.error ?? "")}
274
+ `);
275
+ process.exit(1);
276
+ }
277
+ if (fs.existsSync(lanePath)) {
278
+ fs.rmSync(lanePath, { recursive: true, force: true });
279
+ }
280
+ const cleaned = removeWorkitemFromState(loadState(root), [name.trim(), laneName]);
281
+ if (cleaned.removed) {
282
+ writeState(root, cleaned.state);
283
+ }
284
+ process.stdout.write(`
285
+ ${feedback(c, "ok", `\uB808\uC778 \uC81C\uAC70 ${color.bold(laneName)}`, branch)}
286
+ `);
287
+ if (merged && (merged.gotchasAdded > 0 || merged.rulesAdded > 0 || merged.generationsAdded > 0)) {
288
+ process.stdout.write(
289
+ ` ${color.dim(`\uD559\uC2B5 \uC804\uC5ED \uBCD1\uD569 gotcha ${merged.gotchasAdded} \xB7 rule ${merged.rulesAdded} \xB7 generation ${merged.generationsAdded}`)}
290
+ `
291
+ );
292
+ }
293
+ }
294
+
295
+ export {
296
+ WORKTREES_DIR,
297
+ runLaneNew,
298
+ parseWorktreeBranches,
299
+ laneBranchMap,
300
+ unmergedCommitCount,
301
+ worktreeUntracked,
302
+ branchOf,
303
+ collectLanes,
304
+ renderLaneList,
305
+ runLaneList,
306
+ runLaneRemove
307
+ };
@@ -0,0 +1,209 @@
1
+ import {
2
+ requireConfig
3
+ } from "./chunk-UOPWVM2H.js";
4
+ import {
5
+ caps,
6
+ card,
7
+ generationsDir,
8
+ makeColors,
9
+ padEndDisplay,
10
+ stringWidth
11
+ } from "./chunk-7SYRDDTX.js";
12
+
13
+ // src/commands/metrics.ts
14
+ import fs from "fs";
15
+ import path from "path";
16
+ function computeDurationMs(startedAt, closeAt) {
17
+ const s = Date.parse(String(startedAt));
18
+ const e = Date.parse(String(closeAt));
19
+ if (Number.isNaN(s) || Number.isNaN(e)) {
20
+ return void 0;
21
+ }
22
+ const d = e - s;
23
+ return d >= 0 ? d : void 0;
24
+ }
25
+ function renderMetricsCaveat() {
26
+ return "\uC6CC\uD06C\uC544\uC774\uD15C\uB9C8\uB2E4 \uB09C\uC774\uB3C4\uAC00 \uB2E4\uB985\uB2C8\uB2E4 \u2014 \uC138\uB300 \uAC04 \uC808\uB300 \uBE44\uAD50\uBCF4\uB2E4\uB294 \uACBD\uD5A5(\uCD94\uC138)\uB9CC \uCC38\uACE0\uD558\uC138\uC694.";
27
+ }
28
+ function readCoverage(raw) {
29
+ const c = raw ?? {};
30
+ const num = (v) => typeof v === "number" ? v : 0;
31
+ return {
32
+ auditFindingsTotal: num(c.auditFindingsTotal),
33
+ addressed: num(c.addressed),
34
+ excluded: num(c.excluded),
35
+ excludedApprovedByHuman: c.excludedApprovedByHuman === true
36
+ };
37
+ }
38
+ function loadGenerations(project) {
39
+ const dir = generationsDir(project);
40
+ let files;
41
+ try {
42
+ files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
43
+ } catch {
44
+ return [];
45
+ }
46
+ const num = (v) => typeof v === "number" ? v : 0;
47
+ const generations = [];
48
+ for (const f of files) {
49
+ let raw;
50
+ try {
51
+ raw = JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
52
+ } catch {
53
+ continue;
54
+ }
55
+ generations.push({
56
+ workitem: typeof raw.workitem === "string" ? raw.workitem : f.replace(/\.json$/, ""),
57
+ at: typeof raw.at === "string" ? raw.at : "",
58
+ criteriaTotal: num(raw.criteriaTotal),
59
+ avgAttempts: num(raw.avgAttempts),
60
+ blockedRatio: num(raw.blockedRatio),
61
+ reviewRejects: num(raw.reviewRejects),
62
+ proceduralErrors: num(raw.proceduralErrors),
63
+ gotchaApplied: num(raw.gotchaApplied),
64
+ gotchaMissed: num(raw.gotchaMissed),
65
+ refactorCount: num(raw.refactorCount),
66
+ coverage: readCoverage(raw.coverage),
67
+ // 배열은 experiment 로 인정하지 않는다 — 쓰기 경로(program.ts)가 Array.isArray 로
68
+ // 거부하는 것과 대칭. 손상/수기편집된 스냅샷의 배열이 유사 케이스로 오염되는 걸 막는다.
69
+ ...raw.experiment !== null && typeof raw.experiment === "object" && !Array.isArray(raw.experiment) ? { experiment: raw.experiment } : {},
70
+ ...typeof raw.startedAt === "string" ? { startedAt: raw.startedAt } : {},
71
+ ...typeof raw.durationMs === "number" ? { durationMs: raw.durationMs } : {}
72
+ });
73
+ }
74
+ generations.sort((a, b) => a.at.localeCompare(b.at));
75
+ return generations;
76
+ }
77
+ function renderMetrics(generations, c) {
78
+ const color = makeColors(c.color);
79
+ const caveat = color.dim(renderMetricsCaveat());
80
+ if (generations.length === 0) {
81
+ return card("\uC138\uB300 \uC9C0\uD45C", ["\uC138\uB300 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.", "", caveat], c);
82
+ }
83
+ const idWidth = Math.max(...generations.map((g) => g.workitem.length), 9) + 2;
84
+ const out = [];
85
+ out.push(
86
+ `${padEndDisplay("\uC6CC\uD06C\uC544\uC774\uD15C", idWidth)}\uC644\uB8CC\uC870\uAC74 \uC2DC\uB3C4\uD3C9\uADE0 \uB9C9\uD798\uBE44\uC728 \uB9AC\uBDF0\uC9C0\uC801 \uC808\uCC28\uC2E4\uC218 gotcha\uC801\uC6A9 gotcha\uB204\uB77D \uB9AC\uD329\uD1A0\uB9C1 \uCEE4\uBC84\uB9AC\uC9C0`
87
+ );
88
+ for (const g of generations) {
89
+ const coverage = `${g.coverage.addressed}/${g.coverage.auditFindingsTotal}`;
90
+ out.push(
91
+ `${padEndDisplay(g.workitem, idWidth)}${padEndDisplay(String(g.criteriaTotal), 10)}${padEndDisplay(String(g.avgAttempts), 10)}${padEndDisplay(String(g.blockedRatio), 10)}${padEndDisplay(String(g.reviewRejects), 10)}${padEndDisplay(String(g.proceduralErrors), 10)}${padEndDisplay(String(g.gotchaApplied), 12)}${padEndDisplay(String(g.gotchaMissed), 12)}${padEndDisplay(String(g.refactorCount), 10)}${coverage}`
92
+ );
93
+ }
94
+ out.push("");
95
+ out.push(caveat);
96
+ return card(`\uC138\uB300 ${generations.length}\uAC1C \xB7 \uC2DC\uAC04\uC21C`, out, c);
97
+ }
98
+ var str = (v) => typeof v === "string" && v !== "" ? v : "?";
99
+ var round2 = (n) => Math.round(n * 100) / 100;
100
+ function experimentKey(exp) {
101
+ return `${str(exp?.model)}/${str(exp?.mode)}/${str(exp?.taskType)}`;
102
+ }
103
+ function groupByExperiment(generations) {
104
+ const buckets = /* @__PURE__ */ new Map();
105
+ for (const g of generations) {
106
+ if (g.experiment === void 0) {
107
+ continue;
108
+ }
109
+ const key = experimentKey(g.experiment);
110
+ const arr = buckets.get(key) ?? [];
111
+ arr.push(g);
112
+ buckets.set(key, arr);
113
+ }
114
+ const groups = [];
115
+ for (const [key, gens] of buckets) {
116
+ const n = gens.length;
117
+ const exp = gens[0]?.experiment ?? {};
118
+ const durs = gens.map((g) => g.durationMs).filter((d) => typeof d === "number");
119
+ groups.push({
120
+ key,
121
+ model: str(exp.model),
122
+ mode: str(exp.mode),
123
+ taskType: str(exp.taskType),
124
+ count: n,
125
+ avgAttempts: round2(gens.reduce((s, g) => s + g.avgAttempts, 0) / n),
126
+ blockedRatio: round2(gens.reduce((s, g) => s + g.blockedRatio, 0) / n),
127
+ reviewRejects: gens.reduce((s, g) => s + g.reviewRejects, 0),
128
+ ...durs.length > 0 ? { avgDurationMs: Math.round(durs.reduce((s, d) => s + d, 0) / durs.length) } : {},
129
+ workitems: gens.map((g) => g.workitem)
130
+ });
131
+ }
132
+ groups.sort((a, b) => a.key.localeCompare(b.key));
133
+ return groups;
134
+ }
135
+ function fmtDuration(ms) {
136
+ if (ms === void 0) {
137
+ return "-";
138
+ }
139
+ const min = Math.round(ms / 6e4);
140
+ const h = Math.floor(min / 60);
141
+ const m = min % 60;
142
+ return h > 0 ? `${h}h ${m}m` : `${m}m`;
143
+ }
144
+ function renderCompare(groups, untagged, c) {
145
+ const color = makeColors(c.color);
146
+ const caveat = color.dim(renderMetricsCaveat());
147
+ if (groups.length === 0) {
148
+ return card(
149
+ "\uCF00\uC774\uC2A4 \uBE44\uAD50",
150
+ [`experiment \uD0DC\uADF8\uAC00 \uC788\uB294 \uC138\uB300\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4(\uD0DC\uADF8 \uC5C6\uB294 \uC138\uB300 ${untagged}\uAC1C).`, "", caveat],
151
+ c
152
+ );
153
+ }
154
+ const keyWidth = Math.max(stringWidth("\uCF00\uC774\uC2A4(model/mode/task)"), ...groups.map((g) => stringWidth(g.key))) + 2;
155
+ const out = [
156
+ `${padEndDisplay("\uCF00\uC774\uC2A4(model/mode/task)", keyWidth)}n \uC2DC\uB3C4\uD3C9\uADE0 \uB9C9\uD798\uBE44\uC728 \uB9AC\uBDF0\uC9C0\uC801 \uC18C\uC694\uD3C9\uADE0`
157
+ ];
158
+ for (const g of groups) {
159
+ out.push(
160
+ `${padEndDisplay(g.key, keyWidth)}${padEndDisplay(String(g.count), 4)}${padEndDisplay(String(g.avgAttempts), 10)}${padEndDisplay(String(g.blockedRatio), 10)}${padEndDisplay(String(g.reviewRejects), 10)}${fmtDuration(g.avgDurationMs)}`
161
+ );
162
+ }
163
+ if (untagged > 0) {
164
+ out.push("");
165
+ out.push(color.dim(`(\uD0DC\uADF8 \uC5C6\uB294 \uC138\uB300 ${untagged}\uAC1C\uB294 \uBE44\uAD50\uC5D0\uC11C \uC81C\uC678)`));
166
+ }
167
+ out.push("");
168
+ out.push(caveat);
169
+ return card(`\uCF00\uC774\uC2A4 ${groups.length}\uAC1C \uBE44\uAD50`, out, c);
170
+ }
171
+ function runMetrics(opts) {
172
+ const { config } = requireConfig();
173
+ const generations = loadGenerations(config.project);
174
+ if (opts.compare === true) {
175
+ const groups = groupByExperiment(generations);
176
+ const untagged = generations.filter((g) => g.experiment === void 0).length;
177
+ if (opts.json) {
178
+ process.stdout.write(
179
+ `${JSON.stringify({ cases: groups, untagged, caveat: renderMetricsCaveat() }, null, 2)}
180
+ `
181
+ );
182
+ return;
183
+ }
184
+ process.stdout.write(`${renderCompare(groups, untagged, caps())}
185
+ `);
186
+ return;
187
+ }
188
+ if (opts.json) {
189
+ process.stdout.write(
190
+ `${JSON.stringify({ generations, caveat: renderMetricsCaveat() }, null, 2)}
191
+ `
192
+ );
193
+ return;
194
+ }
195
+ process.stdout.write(`${renderMetrics(generations, caps())}
196
+ `);
197
+ }
198
+
199
+ export {
200
+ computeDurationMs,
201
+ renderMetricsCaveat,
202
+ loadGenerations,
203
+ renderMetrics,
204
+ experimentKey,
205
+ groupByExperiment,
206
+ fmtDuration,
207
+ renderCompare,
208
+ runMetrics
209
+ };