agent-work-loop 0.0.0 → 0.6.22

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-4OCSYHYB.js +274 -0
  5. package/dist/chunk-6E7XEQOH.js +27 -0
  6. package/dist/chunk-7SYRDDTX.js +516 -0
  7. package/dist/chunk-BUWGQVHT.js +1243 -0
  8. package/dist/chunk-C7TN3LM3.js +448 -0
  9. package/dist/chunk-CCMG377E.js +771 -0
  10. package/dist/chunk-D5OINC3G.js +52 -0
  11. package/dist/chunk-F5LHXBH7.js +209 -0
  12. package/dist/chunk-FBEUJR2P.js +446 -0
  13. package/dist/chunk-I77CXOEX.js +693 -0
  14. package/dist/chunk-LMWAVN7B.js +904 -0
  15. package/dist/chunk-O7GRZZPJ.js +347 -0
  16. package/dist/chunk-TKPHC32G.js +96 -0
  17. package/dist/chunk-UOPWVM2H.js +727 -0
  18. package/dist/chunk-VU6IPRRM.js +166 -0
  19. package/dist/chunk-X5LMP5J7.js +307 -0
  20. package/dist/chunk-ZLTOL3D3.js +286 -0
  21. package/dist/cli.js +373 -11
  22. package/dist/commit-APXIVOSD.js +411 -0
  23. package/dist/config-TFMW7O4T.js +34 -0
  24. package/dist/doctor-BU6HWXE4.js +29 -0
  25. package/dist/evolve-NNQX2A43.js +38 -0
  26. package/dist/feedback-KAXNFMUY.js +125 -0
  27. package/dist/gotchas-CW4KZDEF.js +43 -0
  28. package/dist/hold-recheck-SLI7NDLU.js +133 -0
  29. package/dist/init-UDM5AXKI.js +79 -0
  30. package/dist/lane-MIVLTDEU.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-NTZ3HY3N.js +118 -0
  35. package/dist/rules-4VDP5LZW.js +33 -0
  36. package/dist/state-XM7NZ2HA.js +37 -0
  37. package/dist/status-I3MQOCQM.js +40 -0
  38. package/dist/uninstall-MOHJDMQH.js +545 -0
  39. package/dist/update-AYTBYAHI.js +61 -0
  40. package/dist/verify-6YF5FXWM.js +37 -0
  41. package/dist/version-check-G27JYMTZ.js +14 -0
  42. package/dist/work-6MMIQMUC.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 +83 -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 +9 -3
@@ -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,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
+ };
@@ -0,0 +1,446 @@
1
+ import {
2
+ WORKTREES_DIR
3
+ } from "./chunk-X5LMP5J7.js";
4
+ import {
5
+ readRecords
6
+ } from "./chunk-I77CXOEX.js";
7
+ import {
8
+ loadState
9
+ } from "./chunk-4OCSYHYB.js";
10
+ import {
11
+ resolveProjectRoot,
12
+ run
13
+ } from "./chunk-UOPWVM2H.js";
14
+ import {
15
+ caps,
16
+ card,
17
+ makeColors,
18
+ makeSymbols,
19
+ makeTokens,
20
+ padEndDisplay,
21
+ signal,
22
+ statusBadge,
23
+ stringWidth
24
+ } from "./chunk-7SYRDDTX.js";
25
+
26
+ // src/commands/status.ts
27
+ import fs2 from "fs";
28
+ import path2 from "path";
29
+
30
+ // src/commands/pipeline-archive.ts
31
+ import fs from "fs";
32
+ import path from "path";
33
+ var ARCHIVE_GRACE_MS = 3 * 24 * 60 * 60 * 1e3;
34
+ function execTakenPath(tasksDir, name) {
35
+ return path.join(tasksDir, "exec", `${name}.taken.md`);
36
+ }
37
+ function selectArchiveCandidates(tasksDir, now = Date.now(), graceMs = ARCHIVE_GRACE_MS) {
38
+ const lanes = pipelineLanes(
39
+ readDirNames(path.join(tasksDir, "plan")),
40
+ readDirNames(path.join(tasksDir, "exec")),
41
+ readDirNames(path.join(tasksDir, "review"))
42
+ );
43
+ const candidates = [];
44
+ for (const lane of lanes) {
45
+ if (lane.status !== "complete") {
46
+ continue;
47
+ }
48
+ let stat;
49
+ try {
50
+ stat = fs.statSync(execTakenPath(tasksDir, lane.name));
51
+ } catch {
52
+ continue;
53
+ }
54
+ if (now - stat.mtimeMs >= graceMs) {
55
+ candidates.push(lane.name);
56
+ }
57
+ }
58
+ return candidates;
59
+ }
60
+ function ownedFiles(dir, name) {
61
+ return readDirNames(dir).filter((f) => f.endsWith(".md") && markerBaseName(f) === name);
62
+ }
63
+ function moveWorkitemFiles(tasksDir, name) {
64
+ let movedAny = false;
65
+ for (const sub of ["plan", "exec", "review"]) {
66
+ const dir = path.join(tasksDir, sub);
67
+ const files = ownedFiles(dir, name);
68
+ if (files.length === 0) {
69
+ continue;
70
+ }
71
+ const destDir = path.join(tasksDir, "archive", name, sub);
72
+ fs.mkdirSync(destDir, { recursive: true });
73
+ for (const f of files) {
74
+ fs.renameSync(path.join(dir, f), path.join(destDir, f));
75
+ movedAny = true;
76
+ }
77
+ }
78
+ return movedAny;
79
+ }
80
+ function archiveCompletedWorkitems(tasksDir, now = Date.now(), graceMs = ARCHIVE_GRACE_MS) {
81
+ const candidates = selectArchiveCandidates(tasksDir, now, graceMs);
82
+ const archived = [];
83
+ for (const name of candidates) {
84
+ if (moveWorkitemFiles(tasksDir, name)) {
85
+ archived.push(name);
86
+ }
87
+ }
88
+ return archived;
89
+ }
90
+ function archiveAllLanes(root, now = Date.now(), graceMs = ARCHIVE_GRACE_MS) {
91
+ const result = {
92
+ main: archiveCompletedWorkitems(path.join(root, ".tasks"), now, graceMs)
93
+ };
94
+ const base = path.join(root, WORKTREES_DIR);
95
+ let entries;
96
+ try {
97
+ entries = fs.readdirSync(base, { withFileTypes: true });
98
+ } catch {
99
+ return result;
100
+ }
101
+ for (const e of entries) {
102
+ if (!e.isDirectory()) {
103
+ continue;
104
+ }
105
+ result[e.name] = archiveCompletedWorkitems(path.join(base, e.name, ".tasks"), now, graceMs);
106
+ }
107
+ return result;
108
+ }
109
+
110
+ // src/commands/status.ts
111
+ function buildGateStatus(records) {
112
+ const gateRecords = records.filter((r) => r.type === "gate");
113
+ return [1, 2].map((gate) => {
114
+ const rec = gateRecords.find((r) => r.gate === gate);
115
+ if (!rec) {
116
+ return { gate, recorded: false };
117
+ }
118
+ const presentedCriteria = Array.isArray(rec.presentedCriteria) ? rec.presentedCriteria : [];
119
+ const presentedExclusions = Array.isArray(rec.presentedExclusions) ? rec.presentedExclusions : [];
120
+ return {
121
+ gate,
122
+ recorded: true,
123
+ decision: typeof rec.decision === "string" ? rec.decision : void 0,
124
+ at: typeof rec.at === "string" ? rec.at : void 0,
125
+ presentedCriteriaCount: presentedCriteria.length,
126
+ presentedExclusionsCount: presentedExclusions.length,
127
+ auto: typeof rec.auto === "boolean" ? rec.auto : void 0
128
+ };
129
+ });
130
+ }
131
+ function computeBlockedByDeps(criteria) {
132
+ const passedIds = new Set(criteria.filter((c) => c.status === "passed").map((c) => String(c.id)));
133
+ const blocked = [];
134
+ for (const c of criteria) {
135
+ if (c.status === "passed") {
136
+ continue;
137
+ }
138
+ const dependsOn = Array.isArray(c.dependsOn) ? c.dependsOn : [];
139
+ const waitingOn = dependsOn.map(String).filter((d) => !passedIds.has(d));
140
+ if (waitingOn.length > 0) {
141
+ blocked.push({ id: String(c.id), waitingOn });
142
+ }
143
+ }
144
+ return blocked;
145
+ }
146
+ function buildStatus(projectRoot) {
147
+ const state = loadState(projectRoot);
148
+ const criteria = Array.isArray(state.criteria) ? state.criteria : [];
149
+ const count = (s) => criteria.filter((c) => c.status === s).length;
150
+ const records = readRecords();
151
+ const byType = {};
152
+ for (const r of records) {
153
+ const t = String(r.type);
154
+ byType[t] = (byType[t] ?? 0) + 1;
155
+ }
156
+ const latestAttempt = records.find((r) => r.type === "attempt");
157
+ const lastAttempt = latestAttempt && typeof latestAttempt.result === "string" ? latestAttempt.result : null;
158
+ const workitem = typeof state.workitem === "string" ? state.workitem : null;
159
+ const gates = buildGateStatus(records.filter((r) => r.workitem === workitem));
160
+ return {
161
+ generation: typeof state.generation === "number" ? state.generation : 1,
162
+ phase: typeof state.phase === "string" ? state.phase : null,
163
+ workitem,
164
+ gates,
165
+ criteria: {
166
+ total: criteria.length,
167
+ passed: count("passed"),
168
+ blocked: count("blocked"),
169
+ inProgress: count("in_progress"),
170
+ pending: count("pending"),
171
+ blockedByDeps: computeBlockedByDeps(criteria)
172
+ },
173
+ records: { total: records.length, byType },
174
+ lastAttempt
175
+ };
176
+ }
177
+ function classifyAncestorExit(exitCode) {
178
+ if (exitCode === 0) {
179
+ return "present";
180
+ }
181
+ if (exitCode === 1) {
182
+ return "diverged";
183
+ }
184
+ if (exitCode === 128) {
185
+ return "not-found";
186
+ }
187
+ return "unknown";
188
+ }
189
+ async function checkMissingAcCommits(projectRoot) {
190
+ const state = loadState(projectRoot);
191
+ const criteria = Array.isArray(state.criteria) ? state.criteria : [];
192
+ const withCommit = criteria.filter(
193
+ (c) => typeof c.commit === "string" && c.commit.length > 0
194
+ );
195
+ if (withCommit.length === 0) {
196
+ return [];
197
+ }
198
+ try {
199
+ const head = await run({
200
+ cmd: "git",
201
+ args: ["rev-parse", "--verify", "--quiet", "HEAD"],
202
+ cwd: projectRoot,
203
+ timeoutMs: 1e4
204
+ });
205
+ if (head.exitCode !== 0) {
206
+ return [];
207
+ }
208
+ const out = [];
209
+ for (const c of withCommit) {
210
+ const r = await run({
211
+ cmd: "git",
212
+ args: ["merge-base", "--is-ancestor", c.commit, "HEAD"],
213
+ cwd: projectRoot,
214
+ timeoutMs: 1e4
215
+ });
216
+ const kind = classifyAncestorExit(r.exitCode);
217
+ if (kind === "diverged" || kind === "not-found") {
218
+ out.push({ id: String(c.id), commit: c.commit, reason: kind });
219
+ }
220
+ }
221
+ return out;
222
+ } catch {
223
+ return [];
224
+ }
225
+ }
226
+ function decisionColored(t, decision) {
227
+ if (decision === "approved") {
228
+ return t.success(decision);
229
+ }
230
+ if (decision === "rejected" || decision === "abandoned") {
231
+ return t.danger(decision);
232
+ }
233
+ if (decision === "modified" || decision === "more-work" || decision === "split") {
234
+ return t.warning(decision);
235
+ }
236
+ return decision;
237
+ }
238
+ function renderStatus(report, c) {
239
+ const color = makeColors(c.color);
240
+ const t = makeTokens(c);
241
+ const s = makeSymbols(c);
242
+ if (report.phase === null && report.criteria.total === 0 && report.records.total === 0) {
243
+ return card(
244
+ "\uC9C4\uD589 \uC0C1\uD669",
245
+ [
246
+ `${signal(c, "info")} \uC544\uC9C1 \uC2DC\uC791 \uC804\uC785\uB2C8\uB2E4.`,
247
+ `${s.lastBranch} \uBAA9\uD45C\uB97C \uC8FC\uACE0 awl-loop \uB97C \uC2E4\uD589\uD558\uC138\uC694.`
248
+ ],
249
+ c
250
+ );
251
+ }
252
+ const cr = report.criteria;
253
+ const typeSummary = Object.entries(report.records.byType).map(([t2, n]) => `${t2} ${n}`).join(" \xB7 ");
254
+ const out = [];
255
+ out.push(
256
+ `\uB2E8\uACC4 ${report.phase ?? "(\uC5C6\uC74C)"}${report.workitem ? ` ${color.dim(report.workitem)}` : ""}`
257
+ );
258
+ out.push(
259
+ `${s.branch} \uC644\uB8CC \uC870\uAC74 ${color.bold(`${cr.passed}/${cr.total}`)} \uD1B5\uACFC ${color.dim(`(\uB9C9\uD798 ${cr.blocked}, \uC9C4\uD589 ${cr.inProgress}, \uB300\uAE30 ${cr.pending})`)}`
260
+ );
261
+ for (const b of cr.blockedByDeps) {
262
+ out.push(
263
+ `${s.vGuide} ${s.lastBranch} ${signal(c, "warn")} ${color.yellow(b.id)} \uBE14\uB85D\uB428 ${color.dim(`(\uB300\uAE30: ${b.waitingOn.join(", ")})`)}`
264
+ );
265
+ }
266
+ for (const m of report.missingAcCommits ?? []) {
267
+ const why = m.reason === "diverged" ? "\uB2E4\uB978 \uACC4\uBCF4" : "\uCEE4\uBC0B \uC5C6\uC74C";
268
+ out.push(
269
+ `${s.vGuide} ${s.lastBranch} ${signal(c, "warn")} ${color.yellow(m.id)} \uCEE4\uBC0B\uC774 HEAD\uC5D0 \uC5C6\uC74C ${color.dim(`(${m.commit.slice(0, 10)}, ${why})`)}`
270
+ );
271
+ }
272
+ out.push(
273
+ `${s.branch} \uAE30\uB85D ${report.records.total}\uAC1C ${color.dim(typeSummary ? `(${typeSummary})` : "")}`
274
+ );
275
+ out.push(`${s.lastBranch} \uCD5C\uADFC \uAC80\uC99D ${report.lastAttempt ?? color.dim("(\uC5C6\uC74C)")}`);
276
+ for (const g of report.gates) {
277
+ if (!g.recorded) {
278
+ out.push(` ${s.lastBranch} ${signal(c, "info")} \uAC8C\uC774\uD2B8 ${g.gate} ${color.dim("\uB300\uAE30\uC911")}`);
279
+ continue;
280
+ }
281
+ const when = g.at ? g.at.slice(0, 16).replace("T", " ") : "";
282
+ const summary = `\uC644\uB8CC\uC870\uAC74 ${g.presentedCriteriaCount ?? 0}\uAC1C, \uC81C\uC678 ${g.presentedExclusionsCount ?? 0}\uAC74`;
283
+ const autoTag = g.auto ? color.dim(" (\uC790\uB3D9)") : "";
284
+ out.push(
285
+ ` ${s.lastBranch} \uAC8C\uC774\uD2B8 ${g.gate} ${decisionColored(t, g.decision ?? "")}${autoTag} ${when} ${color.dim(summary)}`
286
+ );
287
+ }
288
+ return card(`\uC9C4\uD589 \uC0C1\uD669 \xB7 ${report.generation}\uC138\uB300`, out, c);
289
+ }
290
+ function markerBaseName(f) {
291
+ return f.replace(/\.md$/, "").replace(/\.(taken|hold|pass)$/, "");
292
+ }
293
+ function pipelineLanes(planFiles, execFiles, reviewFiles) {
294
+ const isMd = (f) => f.endsWith(".md");
295
+ const names = /* @__PURE__ */ new Set();
296
+ for (const f of [...planFiles, ...execFiles, ...reviewFiles]) {
297
+ if (isMd(f)) {
298
+ names.add(markerBaseName(f));
299
+ }
300
+ }
301
+ const lanes = [];
302
+ for (const name of names) {
303
+ let status;
304
+ if (reviewFiles.includes(`${name}.md`)) {
305
+ status = "blocked";
306
+ } else if (planFiles.includes(`${name}.hold.md`)) {
307
+ status = "blocked";
308
+ } else if (execFiles.includes(`${name}.taken.md`)) {
309
+ status = "complete";
310
+ } else if (execFiles.includes(`${name}.md`)) {
311
+ status = "reviewing";
312
+ } else if (planFiles.includes(`${name}.taken.md`)) {
313
+ status = "executing";
314
+ } else {
315
+ status = "pending";
316
+ }
317
+ lanes.push({ name, status });
318
+ }
319
+ lanes.sort((a, b) => a.name.localeCompare(b.name));
320
+ return lanes;
321
+ }
322
+ function nameColWidth(names) {
323
+ return Math.max(...names.map(stringWidth), 4) + 2;
324
+ }
325
+ function readDirNames(dir) {
326
+ try {
327
+ return fs2.readdirSync(dir);
328
+ } catch {
329
+ return [];
330
+ }
331
+ }
332
+ function collectPipelineLaneGroups(root) {
333
+ const base = path2.join(root, WORKTREES_DIR);
334
+ let entries;
335
+ try {
336
+ entries = fs2.readdirSync(base, { withFileTypes: true });
337
+ } catch {
338
+ return [];
339
+ }
340
+ const groups = [];
341
+ for (const e of entries) {
342
+ if (!e.isDirectory()) {
343
+ continue;
344
+ }
345
+ const tasks = path2.join(base, e.name, ".tasks");
346
+ const workitems = pipelineLanes(
347
+ readDirNames(path2.join(tasks, "plan")),
348
+ readDirNames(path2.join(tasks, "exec")),
349
+ readDirNames(path2.join(tasks, "review"))
350
+ );
351
+ groups.push({ name: e.name, workitems });
352
+ }
353
+ groups.sort((a, b) => a.name.localeCompare(b.name));
354
+ return groups;
355
+ }
356
+ function mainTreeGroup(root) {
357
+ const tasks = path2.join(root, ".tasks");
358
+ return {
359
+ name: "main",
360
+ workitems: pipelineLanes(
361
+ readDirNames(path2.join(tasks, "plan")),
362
+ readDirNames(path2.join(tasks, "exec")),
363
+ readDirNames(path2.join(tasks, "review"))
364
+ )
365
+ };
366
+ }
367
+ function renderPipelineGroups(groups, c) {
368
+ const color = makeColors(c.color);
369
+ const allNames = groups.flatMap((g) => g.workitems.map((w) => w.name));
370
+ const nameWidth = nameColWidth(allNames);
371
+ const out = [];
372
+ groups.forEach((g, i) => {
373
+ if (i > 0) {
374
+ out.push("");
375
+ }
376
+ out.push(color.bold(g.name));
377
+ if (g.workitems.length === 0) {
378
+ out.push(` ${color.dim("(workitem \uC5C6\uC74C)")}`);
379
+ return;
380
+ }
381
+ for (const w of g.workitems) {
382
+ out.push(` ${statusBadge(c, w.status)} ${padEndDisplay(w.name, nameWidth)}${w.status}`);
383
+ }
384
+ });
385
+ return card(`\uD30C\uC774\uD504\uB77C\uC778 ${groups.length}\uAC1C \uB808\uC778`, out, c);
386
+ }
387
+ async function runStatus(opts) {
388
+ const root = resolveProjectRoot();
389
+ if (!root) {
390
+ const cc = caps();
391
+ process.stderr.write(
392
+ `
393
+ ${signal(cc, "error")} \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
394
+ ${makeSymbols(cc).lastBranch} awl init \uC744 \uC2E4\uD589\uD558\uC138\uC694.
395
+ `
396
+ );
397
+ process.exit(1);
398
+ }
399
+ if (opts.pipeline === true) {
400
+ let archived;
401
+ if (opts.archive === true) {
402
+ archived = archiveAllLanes(root);
403
+ }
404
+ const groups = [mainTreeGroup(root), ...collectPipelineLaneGroups(root)];
405
+ if (opts.json) {
406
+ process.stdout.write(
407
+ `${JSON.stringify({ lanes: groups, ...archived ? { archived } : {} }, null, 2)}
408
+ `
409
+ );
410
+ } else {
411
+ if (archived) {
412
+ const total = Object.values(archived).reduce((n, names) => n + names.length, 0);
413
+ const color = makeColors(caps().color);
414
+ process.stdout.write(` \uBCF4\uAD00 ${color.bold(String(total))}\uAC74
415
+ `);
416
+ }
417
+ process.stdout.write(`${renderPipelineGroups(groups, caps())}
418
+ `);
419
+ }
420
+ return;
421
+ }
422
+ const report = {
423
+ ...buildStatus(root),
424
+ missingAcCommits: await checkMissingAcCommits(root)
425
+ };
426
+ if (opts.json) {
427
+ process.stdout.write(`${JSON.stringify(report, null, 2)}
428
+ `);
429
+ } else {
430
+ process.stdout.write(`${renderStatus(report, caps())}
431
+ `);
432
+ }
433
+ }
434
+
435
+ export {
436
+ buildStatus,
437
+ classifyAncestorExit,
438
+ checkMissingAcCommits,
439
+ renderStatus,
440
+ markerBaseName,
441
+ pipelineLanes,
442
+ readDirNames,
443
+ collectPipelineLaneGroups,
444
+ renderPipelineGroups,
445
+ runStatus
446
+ };