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.
- package/README.md +272 -12
- package/dist/brief-Z3JKXEUP.js +181 -0
- package/dist/changelog-R7BNBF2C.js +62 -0
- package/dist/chunk-46HZN6UB.js +446 -0
- package/dist/chunk-4OCSYHYB.js +274 -0
- package/dist/chunk-6E7XEQOH.js +27 -0
- package/dist/chunk-7SYRDDTX.js +516 -0
- package/dist/chunk-BUWGQVHT.js +1243 -0
- package/dist/chunk-C7BR2DCS.js +96 -0
- package/dist/chunk-D5OINC3G.js +52 -0
- package/dist/chunk-DP4O5ME2.js +307 -0
- package/dist/chunk-F5LHXBH7.js +209 -0
- package/dist/chunk-G5LAJ5TV.js +453 -0
- package/dist/chunk-I77CXOEX.js +693 -0
- package/dist/chunk-IMB46O6S.js +286 -0
- package/dist/chunk-IXMAFR4Y.js +771 -0
- package/dist/chunk-QE2CLNBG.js +347 -0
- package/dist/chunk-UOPWVM2H.js +727 -0
- package/dist/chunk-YTAHVR4P.js +166 -0
- package/dist/chunk-ZE6HXOYG.js +904 -0
- package/dist/cli.js +374 -13
- package/dist/commit-APXIVOSD.js +411 -0
- package/dist/config-TFMW7O4T.js +34 -0
- package/dist/doctor-SSKNLPGH.js +29 -0
- package/dist/evolve-QPD7TWGO.js +38 -0
- package/dist/feedback-KAXNFMUY.js +125 -0
- package/dist/gotchas-MCA5Y76R.js +43 -0
- package/dist/hold-recheck-WN5EG7HD.js +133 -0
- package/dist/init-UDM5AXKI.js +79 -0
- package/dist/lane-DAZISODH.js +41 -0
- package/dist/loop-summary-XAI6KOGB.js +361 -0
- package/dist/metrics-WLRZZRTK.js +25 -0
- package/dist/record-UKDIUJ5T.js +68 -0
- package/dist/review-ZTHDJ47V.js +118 -0
- package/dist/rules-R2UZPIVW.js +33 -0
- package/dist/state-XM7NZ2HA.js +37 -0
- package/dist/status-L6U5KO6T.js +40 -0
- package/dist/uninstall-5DFEOFL5.js +545 -0
- package/dist/update-AYTBYAHI.js +61 -0
- package/dist/verify-L7ARTK42.js +37 -0
- package/dist/version-check-LKGU2DNF.js +14 -0
- package/dist/work-KEPTGZ6H.js +50 -0
- package/engine/skills/claude/awl-loop/SKILL.md +292 -0
- package/engine/skills/claude/awl-loop/reference.md +131 -0
- package/engine/skills/claude/awl-pipeline/SKILL.md +89 -0
- package/engine/skills/claude/awl-pipeline-exec/SKILL.md +200 -0
- package/engine/skills/claude/awl-pipeline-plan/SKILL.md +71 -0
- package/engine/skills/claude/awl-pipeline-review/SKILL.md +149 -0
- package/engine/skills/codex/AGENTS.awl.md +117 -0
- package/engine/templates/block-publish.mjs +9 -0
- package/engine/templates/pre-push.sample +7 -0
- package/engine/templates/related-cmd-examples.md +37 -0
- package/engine/version.json +2 -2
- package/package.json +10 -4
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WORKTREES_DIR
|
|
3
|
+
} from "./chunk-DP4O5ME2.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
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveProjectRoot
|
|
3
|
+
} from "./chunk-UOPWVM2H.js";
|
|
4
|
+
import {
|
|
5
|
+
caps,
|
|
6
|
+
card,
|
|
7
|
+
makeColors
|
|
8
|
+
} from "./chunk-7SYRDDTX.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/state.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
function statePath(projectRoot) {
|
|
14
|
+
return path.join(projectRoot, ".awl", "state.json");
|
|
15
|
+
}
|
|
16
|
+
function migrateState(raw) {
|
|
17
|
+
if ("workitems" in raw) {
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
return { ...raw, workitems: {} };
|
|
21
|
+
}
|
|
22
|
+
function loadState(projectRoot) {
|
|
23
|
+
try {
|
|
24
|
+
const raw = JSON.parse(fs.readFileSync(statePath(projectRoot), "utf8"));
|
|
25
|
+
return migrateState(raw);
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function mergeState(current, patch) {
|
|
31
|
+
const merged = { ...current, ...patch };
|
|
32
|
+
if (Array.isArray(patch.criteria)) {
|
|
33
|
+
let acc = { ...current };
|
|
34
|
+
for (const c of patch.criteria) {
|
|
35
|
+
if (c && typeof c.id === "string") {
|
|
36
|
+
acc = setCriterion(acc, c.id, c);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
merged.criteria = acc.criteria;
|
|
40
|
+
}
|
|
41
|
+
return merged;
|
|
42
|
+
}
|
|
43
|
+
function getCriterion(state, id) {
|
|
44
|
+
const criteria = Array.isArray(state.criteria) ? state.criteria : [];
|
|
45
|
+
return criteria.find((c) => c.id === id);
|
|
46
|
+
}
|
|
47
|
+
function setCriterion(state, id, patch) {
|
|
48
|
+
const criteria = Array.isArray(state.criteria) ? [...state.criteria] : [];
|
|
49
|
+
const idx = criteria.findIndex((c) => c.id === id);
|
|
50
|
+
if (idx >= 0) {
|
|
51
|
+
criteria[idx] = { ...criteria[idx], ...patch };
|
|
52
|
+
} else {
|
|
53
|
+
criteria.push({ id, ...patch });
|
|
54
|
+
}
|
|
55
|
+
return { ...state, criteria };
|
|
56
|
+
}
|
|
57
|
+
function writeState(projectRoot, state) {
|
|
58
|
+
const p = statePath(projectRoot);
|
|
59
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
60
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
61
|
+
fs.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}
|
|
62
|
+
`);
|
|
63
|
+
fs.renameSync(tmp, p);
|
|
64
|
+
}
|
|
65
|
+
var STATE_LOCK_STALE_MS = 1e4;
|
|
66
|
+
function stateLockFile(projectRoot) {
|
|
67
|
+
return path.join(projectRoot, ".awl", "state.lock");
|
|
68
|
+
}
|
|
69
|
+
function sessionToken() {
|
|
70
|
+
return `proc-${process.pid}`;
|
|
71
|
+
}
|
|
72
|
+
function lockAgeAt(p) {
|
|
73
|
+
try {
|
|
74
|
+
const raw = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
75
|
+
const at = typeof raw.at === "string" ? Date.parse(raw.at) : Number.NaN;
|
|
76
|
+
if (!Number.isNaN(at)) {
|
|
77
|
+
return at;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return fs.statSync(p).mtimeMs;
|
|
83
|
+
} catch {
|
|
84
|
+
return Date.now();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function acquireStateLock(projectRoot, token) {
|
|
88
|
+
const p = stateLockFile(projectRoot);
|
|
89
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
90
|
+
const tryCreate = () => {
|
|
91
|
+
const tmp = `${p}.${process.pid}.acq`;
|
|
92
|
+
try {
|
|
93
|
+
fs.writeFileSync(tmp, JSON.stringify({ token, at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
94
|
+
fs.linkSync(tmp, p);
|
|
95
|
+
return true;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e.code === "EEXIST") {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
throw e;
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
fs.unlinkSync(tmp);
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (tryCreate()) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (Date.now() - lockAgeAt(p) > STATE_LOCK_STALE_MS) {
|
|
112
|
+
const claimed = `${p}.${process.pid}.steal`;
|
|
113
|
+
try {
|
|
114
|
+
fs.renameSync(p, claimed);
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
fs.unlinkSync(claimed);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
return tryCreate();
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
function releaseStateLock(projectRoot, token) {
|
|
127
|
+
const p = stateLockFile(projectRoot);
|
|
128
|
+
if (token !== void 0) {
|
|
129
|
+
const held = readRawLockToken(p);
|
|
130
|
+
if (held !== null && held !== token) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(p);
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function readRawLockToken(p) {
|
|
140
|
+
try {
|
|
141
|
+
const raw = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
142
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function readStateLock(projectRoot) {
|
|
148
|
+
try {
|
|
149
|
+
const raw = JSON.parse(fs.readFileSync(stateLockFile(projectRoot), "utf8"));
|
|
150
|
+
const at = typeof raw.at === "string" ? raw.at : "";
|
|
151
|
+
const atMs = at ? Date.parse(at) : Number.NaN;
|
|
152
|
+
if (Number.isNaN(atMs) || Date.now() - atMs > STATE_LOCK_STALE_MS) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return { token: typeof raw.token === "string" ? raw.token : "", at };
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function applyVerificationAttempts(state, passed) {
|
|
161
|
+
const criteria = Array.isArray(state.criteria) ? state.criteria : [];
|
|
162
|
+
const focus = typeof state.currentFocus === "string" ? state.currentFocus : void 0;
|
|
163
|
+
const targets = focus ? criteria.filter((c) => c.id === focus) : criteria.filter((c) => c.status === "in_progress");
|
|
164
|
+
if (targets.length === 0) return { state, blocked: [] };
|
|
165
|
+
const blocked = [];
|
|
166
|
+
const next = criteria.map((criterion) => {
|
|
167
|
+
if (!targets.includes(criterion)) return criterion;
|
|
168
|
+
if (passed) return { ...criterion, attempts: 0 };
|
|
169
|
+
const attempts = typeof criterion.attempts === "number" ? criterion.attempts + 1 : 1;
|
|
170
|
+
if (attempts >= 3) {
|
|
171
|
+
blocked.push(String(criterion.id));
|
|
172
|
+
return { ...criterion, attempts, status: "blocked", autoBlocked: true };
|
|
173
|
+
}
|
|
174
|
+
return { ...criterion, attempts };
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
state: blocked.length ? { ...state, criteria: next, phase: "blocked", loop: "blocked" } : { ...state, criteria: next },
|
|
178
|
+
blocked
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function requireRoot() {
|
|
182
|
+
const root = resolveProjectRoot();
|
|
183
|
+
if (!root) {
|
|
184
|
+
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");
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
return root;
|
|
188
|
+
}
|
|
189
|
+
function renderState(state, c) {
|
|
190
|
+
const color = makeColors(c.color);
|
|
191
|
+
if (Object.keys(state).length === 0) {
|
|
192
|
+
return card("\uD604\uC7AC \uC0C1\uD0DC", ["\uC0C1\uD0DC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."], c);
|
|
193
|
+
}
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const [k, v] of Object.entries(state)) {
|
|
196
|
+
const val = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
197
|
+
out.push(`${k.padEnd(14, " ")}${color.dim(val)}`);
|
|
198
|
+
}
|
|
199
|
+
return card("\uD604\uC7AC \uC0C1\uD0DC", out, c);
|
|
200
|
+
}
|
|
201
|
+
function runStateGet(opts) {
|
|
202
|
+
const state = loadState(requireRoot());
|
|
203
|
+
if (opts.json) {
|
|
204
|
+
process.stdout.write(`${JSON.stringify(state, null, 2)}
|
|
205
|
+
`);
|
|
206
|
+
} else {
|
|
207
|
+
process.stdout.write(`${renderState(state, caps())}
|
|
208
|
+
`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function runStateSet(jsonPatch, opts = {}) {
|
|
212
|
+
const root = requireRoot();
|
|
213
|
+
let patch;
|
|
214
|
+
try {
|
|
215
|
+
patch = JSON.parse(jsonPatch);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
process.stderr.write(`
|
|
218
|
+
\uAC31\uC2E0 JSON \uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${String(e)}
|
|
219
|
+
`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
if (typeof patch !== "object" || patch === null || Array.isArray(patch)) {
|
|
223
|
+
process.stderr.write("\n \uAC31\uC2E0\uC740 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.\n");
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
const p = patch;
|
|
227
|
+
const token = sessionToken();
|
|
228
|
+
if (!acquireStateLock(root, token)) {
|
|
229
|
+
process.stderr.write(
|
|
230
|
+
"\n \uB2E4\uB978 \uC138\uC158\uC774 \uC9C0\uAE08 state \uB97C \uC4F0\uB294 \uC911\uC785\uB2C8\uB2E4(.awl/state.lock). \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.\n"
|
|
231
|
+
);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
let gateRejected = false;
|
|
235
|
+
try {
|
|
236
|
+
const current = loadState(root);
|
|
237
|
+
if (p.phase === "loop" && opts.requireGateForLoop) {
|
|
238
|
+
const workitem = typeof current.workitem === "string" ? current.workitem : void 0;
|
|
239
|
+
gateRejected = !opts.requireGateForLoop(workitem);
|
|
240
|
+
}
|
|
241
|
+
if (!gateRejected) {
|
|
242
|
+
const merged = mergeState(current, p);
|
|
243
|
+
writeState(root, merged);
|
|
244
|
+
process.stdout.write(`${JSON.stringify(merged, null, 2)}
|
|
245
|
+
`);
|
|
246
|
+
}
|
|
247
|
+
} finally {
|
|
248
|
+
releaseStateLock(root, token);
|
|
249
|
+
}
|
|
250
|
+
if (gateRejected) {
|
|
251
|
+
process.stderr.write(
|
|
252
|
+
"\n \uAC8C\uC774\uD2B8 1 \uC2B9\uC778 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. awl record gate \uB85C \uACC4\uD68D\uC744 \uC2B9\uC778(approved)\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.\n"
|
|
253
|
+
);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export {
|
|
259
|
+
statePath,
|
|
260
|
+
migrateState,
|
|
261
|
+
loadState,
|
|
262
|
+
mergeState,
|
|
263
|
+
getCriterion,
|
|
264
|
+
setCriterion,
|
|
265
|
+
writeState,
|
|
266
|
+
stateLockFile,
|
|
267
|
+
sessionToken,
|
|
268
|
+
acquireStateLock,
|
|
269
|
+
releaseStateLock,
|
|
270
|
+
readStateLock,
|
|
271
|
+
applyVerificationAttempts,
|
|
272
|
+
runStateGet,
|
|
273
|
+
runStateSet
|
|
274
|
+
};
|