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.
- package/README.md +272 -12
- package/dist/brief-Z3JKXEUP.js +181 -0
- package/dist/changelog-R7BNBF2C.js +62 -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-C7TN3LM3.js +448 -0
- package/dist/chunk-CCMG377E.js +771 -0
- package/dist/chunk-D5OINC3G.js +52 -0
- package/dist/chunk-F5LHXBH7.js +209 -0
- package/dist/chunk-FBEUJR2P.js +446 -0
- package/dist/chunk-I77CXOEX.js +693 -0
- package/dist/chunk-LMWAVN7B.js +904 -0
- package/dist/chunk-O7GRZZPJ.js +347 -0
- package/dist/chunk-TKPHC32G.js +96 -0
- package/dist/chunk-UOPWVM2H.js +727 -0
- package/dist/chunk-VU6IPRRM.js +166 -0
- package/dist/chunk-X5LMP5J7.js +307 -0
- package/dist/chunk-ZLTOL3D3.js +286 -0
- package/dist/cli.js +373 -11
- package/dist/commit-APXIVOSD.js +411 -0
- package/dist/config-TFMW7O4T.js +34 -0
- package/dist/doctor-BU6HWXE4.js +29 -0
- package/dist/evolve-NNQX2A43.js +38 -0
- package/dist/feedback-KAXNFMUY.js +125 -0
- package/dist/gotchas-CW4KZDEF.js +43 -0
- package/dist/hold-recheck-SLI7NDLU.js +133 -0
- package/dist/init-UDM5AXKI.js +79 -0
- package/dist/lane-MIVLTDEU.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-NTZ3HY3N.js +118 -0
- package/dist/rules-4VDP5LZW.js +33 -0
- package/dist/state-XM7NZ2HA.js +37 -0
- package/dist/status-I3MQOCQM.js +40 -0
- package/dist/uninstall-MOHJDMQH.js +545 -0
- package/dist/update-AYTBYAHI.js +61 -0
- package/dist/verify-6YF5FXWM.js +37 -0
- package/dist/version-check-G27JYMTZ.js +14 -0
- package/dist/work-6MMIQMUC.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 +83 -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 +9 -3
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import {
|
|
2
|
+
protectedFilesMessage
|
|
3
|
+
} from "./chunk-6E7XEQOH.js";
|
|
4
|
+
import {
|
|
5
|
+
hasApprovedGate1
|
|
6
|
+
} from "./chunk-I77CXOEX.js";
|
|
7
|
+
import {
|
|
8
|
+
getCriterion,
|
|
9
|
+
loadState,
|
|
10
|
+
setCriterion,
|
|
11
|
+
writeState
|
|
12
|
+
} from "./chunk-4OCSYHYB.js";
|
|
13
|
+
import {
|
|
14
|
+
loadConfig,
|
|
15
|
+
resolveProjectRoot,
|
|
16
|
+
run
|
|
17
|
+
} from "./chunk-UOPWVM2H.js";
|
|
18
|
+
import "./chunk-BUWGQVHT.js";
|
|
19
|
+
import {
|
|
20
|
+
caps,
|
|
21
|
+
feedback,
|
|
22
|
+
makeColors,
|
|
23
|
+
makeTokens,
|
|
24
|
+
signal
|
|
25
|
+
} from "./chunk-7SYRDDTX.js";
|
|
26
|
+
|
|
27
|
+
// src/commands/commit.ts
|
|
28
|
+
import fs from "fs";
|
|
29
|
+
import os from "os";
|
|
30
|
+
import path from "path";
|
|
31
|
+
var STAGED_FILES_NOTICE_THRESHOLD = 5;
|
|
32
|
+
async function git(args, cwd) {
|
|
33
|
+
const r = await run({ cmd: "git", args, cwd, timeoutMs: 3e4 });
|
|
34
|
+
return { stdout: r.stdout, stderr: r.stderr, exitCode: r.exitCode };
|
|
35
|
+
}
|
|
36
|
+
function lines(s) {
|
|
37
|
+
return s.trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
async function namesZ(args, cwd) {
|
|
40
|
+
const out = (await git([...args, "-z"], cwd)).stdout;
|
|
41
|
+
return out.split("\0").filter((f) => f !== "");
|
|
42
|
+
}
|
|
43
|
+
async function captureSnapshot(cwd) {
|
|
44
|
+
const created = await git(["stash", "create"], cwd);
|
|
45
|
+
const sha = created.stdout.trim();
|
|
46
|
+
if (sha) {
|
|
47
|
+
return sha;
|
|
48
|
+
}
|
|
49
|
+
return (await git(["rev-parse", "HEAD"], cwd)).stdout.trim();
|
|
50
|
+
}
|
|
51
|
+
var AWL_SELF_PREFIXES = [".awl-worktrees/", ".awl/"];
|
|
52
|
+
async function listUntracked(cwd) {
|
|
53
|
+
const untracked = await namesZ(["ls-files", "--others", "--exclude-standard"], cwd);
|
|
54
|
+
return untracked.filter((f) => !AWL_SELF_PREFIXES.some((prefix) => f.startsWith(prefix)));
|
|
55
|
+
}
|
|
56
|
+
function sanitizeRefComponent(s) {
|
|
57
|
+
return s.replace(/[^A-Za-z0-9._-]/g, "_").replace(/\.{2,}/g, "_");
|
|
58
|
+
}
|
|
59
|
+
function baselineRefPath(cwd, ac) {
|
|
60
|
+
const state = loadState(cwd);
|
|
61
|
+
const workitem = typeof state.workitem === "string" ? state.workitem : null;
|
|
62
|
+
const safeAc = sanitizeRefComponent(ac);
|
|
63
|
+
return workitem ? `refs/awl/baseline/${sanitizeRefComponent(workitem)}/${safeAc}` : `refs/awl/baseline/${safeAc}`;
|
|
64
|
+
}
|
|
65
|
+
async function pinBaselineRef(cwd, ac, sha) {
|
|
66
|
+
const refPath = baselineRefPath(cwd, ac);
|
|
67
|
+
const result = await git(["update-ref", refPath, sha], cwd);
|
|
68
|
+
if (result.exitCode !== 0) {
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
` ${signal(caps(), "warn")} baseline \uBCF4\uD638\uC6A9 git ref(${refPath})\uB97C \uBABB \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4(\uAE30\uB2A5\uC5D4 \uC601\uD5A5 \uC5C6\uC74C): ${result.stderr.trim()}
|
|
71
|
+
`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function startBaseline(cwd, ac) {
|
|
76
|
+
const head = (await git(["rev-parse", "HEAD"], cwd)).stdout.trim();
|
|
77
|
+
const snapshot = await captureSnapshot(cwd);
|
|
78
|
+
const untracked = await listUntracked(cwd);
|
|
79
|
+
await pinBaselineRef(cwd, ac, snapshot);
|
|
80
|
+
return { snapshot, head, untracked };
|
|
81
|
+
}
|
|
82
|
+
async function isolatedCommit(cwd, ac, message, snapshot, untrackedAtStart = [], expectedHead) {
|
|
83
|
+
const empty = { stagedFiles: [], excludedFiles: [], extraFiles: [] };
|
|
84
|
+
if (expectedHead !== void 0) {
|
|
85
|
+
const currentHead = (await git(["rev-parse", "HEAD"], cwd)).stdout.trim();
|
|
86
|
+
if (currentHead !== expectedHead) {
|
|
87
|
+
const log = (await git(["log", "--oneline", `${expectedHead}..${currentHead}`], cwd)).stdout.trim();
|
|
88
|
+
const commits = lines(log);
|
|
89
|
+
return {
|
|
90
|
+
committed: false,
|
|
91
|
+
reason: [
|
|
92
|
+
`HEAD \uAC00 \uC774 \uC644\uB8CC\uC870\uAC74\uC758 \uBCA0\uC774\uC2A4\uB77C\uC778\uC744 \uC7A1\uC740 \uB4A4 \uB2E4\uB978 \uCEE4\uBC0B ${commits.length}\uAC1C\uB85C \uC774\uB3D9\uD588\uC2B5\uB2C8\uB2E4:`,
|
|
93
|
+
...commits.map((c) => ` ${c}`),
|
|
94
|
+
"\uB2E4\uB978 \uC644\uB8CC\uC870\uAC74\uC744 \uC2E4\uC218\uB85C \uC5EC\uAE30\uC11C \uCEE4\uBC0B\uD588\uB2E4\uBA74 \uADF8\uCABD\uC744 \uB9C8\uC800 \uC815\uB9AC\uD558\uC138\uC694.",
|
|
95
|
+
"\uC758\uB3C4\uD55C \uAC83\uC774\uBA74(\uB3D9\uC2DC \uC791\uC5C5 \uB4F1) \uC9C0\uAE08 \uC774 \uC6CC\uD0B9\uD2B8\uB9AC\uC758 \uBCC0\uACBD\uC744 \uBA3C\uC800 \uD655\uC778\uD55C \uB4A4",
|
|
96
|
+
`awl commit ${ac} --start \uB85C \uBCA0\uC774\uC2A4\uB77C\uC778\uC744 \uC0C8\uB85C \uC7A1\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
|
|
97
|
+
].join("\n"),
|
|
98
|
+
selfCheckOk: false,
|
|
99
|
+
...empty
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
await git(["reset", "-q"], cwd);
|
|
104
|
+
const diff = (await git(["diff", snapshot], cwd)).stdout;
|
|
105
|
+
const nowUntracked = await listUntracked(cwd);
|
|
106
|
+
const newUntracked = nowUntracked.filter((f) => !untrackedAtStart.includes(f));
|
|
107
|
+
if (diff.trim() === "" && newUntracked.length === 0) {
|
|
108
|
+
return { committed: false, reason: "\uCEE4\uBC0B\uD560 \uB0B4 \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.", selfCheckOk: true, ...empty };
|
|
109
|
+
}
|
|
110
|
+
if (diff.trim() !== "") {
|
|
111
|
+
const patchFile = path.join(os.tmpdir(), `awl-commit-${ac}-${process.pid}.patch`);
|
|
112
|
+
fs.writeFileSync(patchFile, diff);
|
|
113
|
+
const applied = await git(["apply", "--cached", "--whitespace=nowarn", patchFile], cwd);
|
|
114
|
+
fs.rmSync(patchFile, { force: true });
|
|
115
|
+
if (applied.exitCode !== 0) {
|
|
116
|
+
await git(["reset", "-q"], cwd);
|
|
117
|
+
return {
|
|
118
|
+
committed: false,
|
|
119
|
+
reason: `\uB0B4 \uBCC0\uACBD\uC744 \uC548\uC804\uD558\uAC8C \uBD84\uB9AC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(hunk \uAC00 \uB0A8\uC758 \uBCC0\uACBD\uACFC \uACB9\uCE60 \uC218 \uC788\uC2B5\uB2C8\uB2E4). \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC0AC\uB78C\uC774 \uD655\uC778\uD558\uC138\uC694.
|
|
120
|
+
${applied.stderr.trim()}`,
|
|
121
|
+
selfCheckOk: false,
|
|
122
|
+
...empty
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const f of newUntracked) {
|
|
127
|
+
const added = await git(["add", "--", f], cwd);
|
|
128
|
+
if (added.exitCode !== 0) {
|
|
129
|
+
await git(["reset", "-q"], cwd);
|
|
130
|
+
return {
|
|
131
|
+
committed: false,
|
|
132
|
+
reason: `\uC0C8 \uD30C\uC77C\uC744 \uC2A4\uD14C\uC774\uC9D5\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${f}
|
|
133
|
+
${added.stderr.trim()}`,
|
|
134
|
+
selfCheckOk: false,
|
|
135
|
+
...empty
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const stagedFiles = await namesZ(["diff", "--cached", "--name-only"], cwd);
|
|
140
|
+
const excludedFiles = [
|
|
141
|
+
...await namesZ(["diff", "--name-only"], cwd),
|
|
142
|
+
...nowUntracked.filter((f) => untrackedAtStart.includes(f))
|
|
143
|
+
];
|
|
144
|
+
const msg = message.includes(ac) ? message : `${message} [${ac}]`;
|
|
145
|
+
const committed = await git(["commit", "-q", "-m", msg], cwd);
|
|
146
|
+
if (committed.exitCode !== 0) {
|
|
147
|
+
await git(["reset", "-q"], cwd);
|
|
148
|
+
return {
|
|
149
|
+
committed: false,
|
|
150
|
+
reason: `\uCEE4\uBC0B \uC2E4\uD328: ${committed.stderr.trim()}`,
|
|
151
|
+
selfCheckOk: false,
|
|
152
|
+
stagedFiles,
|
|
153
|
+
excludedFiles,
|
|
154
|
+
extraFiles: []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const commit = (await git(["rev-parse", "HEAD"], cwd)).stdout.trim();
|
|
158
|
+
const committedFiles = await namesZ(["show", "--name-only", "--format=", commit], cwd);
|
|
159
|
+
const extraFiles = committedFiles.filter((f) => !stagedFiles.includes(f));
|
|
160
|
+
return {
|
|
161
|
+
committed: true,
|
|
162
|
+
commit,
|
|
163
|
+
stagedFiles,
|
|
164
|
+
excludedFiles,
|
|
165
|
+
selfCheckOk: extraFiles.length === 0,
|
|
166
|
+
extraFiles
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function renderCommitSuccess(outcome, c, message) {
|
|
170
|
+
const color = makeColors(c.color);
|
|
171
|
+
const head = `
|
|
172
|
+
${feedback(c, "ok", `\uCEE4\uBC0B\uB428: ${makeTokens(c).emphasis(outcome.commit?.slice(0, 10) ?? "")}`, message)}
|
|
173
|
+
`;
|
|
174
|
+
if (!outcome.selfCheckOk) {
|
|
175
|
+
return {
|
|
176
|
+
stdout: head,
|
|
177
|
+
stderr: ` ${signal(c, "warn")} \uB0B4\uBD80 \uAC80\uC99D \uACBD\uACE0: \uC2A4\uD14C\uC774\uC9D5\uD55C \uD30C\uC77C\uACFC \uC2E4\uC81C \uCEE4\uBC0B \uB0B4\uC6A9\uC774 \uB2E4\uB985\uB2C8\uB2E4: ${outcome.extraFiles.join(", ")}
|
|
178
|
+
`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
stdout: `${head} ${color.dim("\uB0B4\uBD80 \uAC80\uC99D: \uC2A4\uD14C\uC774\uC9D5\uD55C \uB0B4\uC6A9 \uADF8\uB300\uB85C \uCEE4\uBC0B\uB428.")}
|
|
183
|
+
`,
|
|
184
|
+
stderr: ""
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function buildRescueGuidance(reason) {
|
|
188
|
+
if (!reason || !reason.includes("hunk")) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return [
|
|
192
|
+
"",
|
|
193
|
+
" \uACA9\uB9AC\uB41C \uC6CC\uD06C\uD2B8\uB9AC\uB85C \uC62E\uAE30\uB294 \uBC29\uBC95(\uB0B4 \uBCC0\uACBD\uC744 \uADF8\uB300\uB85C \uB2E4\uB978 \uACF3\uC73C\uB85C \uC774\uB3D9):",
|
|
194
|
+
' git stash push -u -m "rescue" # \uB0B4 \uBCC0\uACBD(\uCD94\uC801+\uBBF8\uCD94\uC801 \uC804\uBD80)\uC744 \uC2A4\uD0DD\uC5D0 \uB2F4\uB294\uB2E4',
|
|
195
|
+
" git worktree add ../<\uC0C8-\uB514\uB809\uD1A0\uB9AC> -b <\uC0C8-\uBE0C\uB79C\uCE58>",
|
|
196
|
+
" cd ../<\uC0C8-\uB514\uB809\uD1A0\uB9AC> && git stash pop # \uC62E\uAE34 \uC6CC\uD06C\uD2B8\uB9AC\uC5D0\uC11C \uBCC0\uACBD\uC744 \uBCF5\uC6D0",
|
|
197
|
+
" (\uB610\uB294 awl work new <ID> --worktree \uB85C \uC0C8 \uC6CC\uD06C\uC544\uC774\uD15C + \uACA9\uB9AC \uC6CC\uD06C\uD2B8\uB9AC\uB97C \uD55C \uBC88\uC5D0 \uB9CC\uB4E4\uACE0 \uC704 \uC21C\uC11C\uB85C \uC62E\uAE30\uC138\uC694.)"
|
|
198
|
+
].join("\n");
|
|
199
|
+
}
|
|
200
|
+
function buildTrailGapWarning(activeWorkitem, c) {
|
|
201
|
+
if (activeWorkitem) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return ` ${signal(c, "warn")} \uD65C\uC131 \uC6CC\uD06C\uC544\uC774\uD15C \uC5C6\uC774 \uCEE4\uBC0B\uD569\uB2C8\uB2E4 \u2014 record \uD2B8\uB808\uC77C\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. /awl-loop \uAC8C\uC774\uD2B8(work new\u2192gate)\uB97C \uBC1F\uC558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
async function checkBaseDrift(cwd, base, myFiles) {
|
|
208
|
+
let baseRef = base;
|
|
209
|
+
if (!baseRef) {
|
|
210
|
+
const up = await git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"], cwd);
|
|
211
|
+
if (up.exitCode !== 0 || up.stdout.trim() === "") {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
baseRef = up.stdout.trim();
|
|
215
|
+
}
|
|
216
|
+
const mb = await git(["merge-base", "HEAD", baseRef], cwd);
|
|
217
|
+
if (mb.exitCode !== 0) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const mergeBase = mb.stdout.trim();
|
|
221
|
+
const ahead = Number(
|
|
222
|
+
(await git(["rev-list", "--count", `${mergeBase}..${baseRef}`], cwd)).stdout.trim() || "0"
|
|
223
|
+
);
|
|
224
|
+
if (ahead === 0) {
|
|
225
|
+
return { ahead: 0, overlap: [], base: baseRef };
|
|
226
|
+
}
|
|
227
|
+
const changed = lines((await git(["diff", "--name-only", mergeBase, baseRef], cwd)).stdout);
|
|
228
|
+
const overlap = changed.filter((f) => myFiles.includes(f));
|
|
229
|
+
return { ahead, overlap, base: baseRef };
|
|
230
|
+
}
|
|
231
|
+
function requireRoot() {
|
|
232
|
+
const root = resolveProjectRoot();
|
|
233
|
+
if (!root) {
|
|
234
|
+
process.stderr.write(
|
|
235
|
+
`
|
|
236
|
+
${signal(caps(), "error")} \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. awl init \uC744 \uC2E4\uD589\uD558\uC138\uC694.
|
|
237
|
+
`
|
|
238
|
+
);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
return root;
|
|
242
|
+
}
|
|
243
|
+
async function runCommit(ac, opts) {
|
|
244
|
+
const root = requireRoot();
|
|
245
|
+
const c = caps();
|
|
246
|
+
const color = makeColors(c.color);
|
|
247
|
+
const gateState = loadState(root);
|
|
248
|
+
const gateWorkitem = typeof gateState.workitem === "string" ? gateState.workitem : void 0;
|
|
249
|
+
if (gateWorkitem && !hasApprovedGate1(gateWorkitem)) {
|
|
250
|
+
process.stderr.write(
|
|
251
|
+
`
|
|
252
|
+
${signal(c, "warn")} Gate 1 \uC2B9\uC778\uC774 \uBA3C\uC800 \uD544\uC694\uD569\uB2C8\uB2E4. awl record gate \uB85C \uACC4\uD68D\uC744 \uC2B9\uC778\uD55C \uB4A4 \uCEE4\uBC0B\uD558\uC138\uC694.
|
|
253
|
+
`
|
|
254
|
+
);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
if (!opts.force) {
|
|
258
|
+
const protection = await protectedFilesMessage(root, loadConfig(root).config?.protectedFiles);
|
|
259
|
+
if (protection) {
|
|
260
|
+
process.stderr.write(`
|
|
261
|
+
${signal(c, "error")} ${protection}
|
|
262
|
+
`);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
267
|
+
if (opts.start) {
|
|
268
|
+
const { snapshot: snapshot2, head, untracked } = await startBaseline(root, ac);
|
|
269
|
+
const existing = getCriterion(loadState(root), ac);
|
|
270
|
+
const firstBaseline = typeof existing?.firstBaseline === "string" ? existing.firstBaseline : head;
|
|
271
|
+
const state2 = setCriterion(loadState(root), ac, {
|
|
272
|
+
status: "in_progress",
|
|
273
|
+
baseline: head,
|
|
274
|
+
snapshot: snapshot2,
|
|
275
|
+
untrackedAtStart: untracked,
|
|
276
|
+
startedAt: now,
|
|
277
|
+
firstBaseline
|
|
278
|
+
});
|
|
279
|
+
writeState(root, state2);
|
|
280
|
+
process.stdout.write(`
|
|
281
|
+
${ac} \uBCA0\uC774\uC2A4\uB77C\uC778\uC744 \uC7A1\uC558\uC2B5\uB2C8\uB2E4: ${head.slice(0, 10)}
|
|
282
|
+
`);
|
|
283
|
+
process.stdout.write(
|
|
284
|
+
` ${color.dim(`\uC774\uC81C \uC791\uC5C5\uD55C \uB4A4 awl commit ${ac} -m "..." \uB85C \uACA9\uB9AC \uCEE4\uBC0B\uD558\uC138\uC694.`)}
|
|
285
|
+
`
|
|
286
|
+
);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!opts.message) {
|
|
290
|
+
process.stderr.write(
|
|
291
|
+
`
|
|
292
|
+
${signal(c, "error")} \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: awl commit ${ac} -m "..."
|
|
293
|
+
`
|
|
294
|
+
);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
const state = loadState(root);
|
|
298
|
+
const crit = getCriterion(state, ac);
|
|
299
|
+
const snapshot = crit && typeof crit.snapshot === "string" ? crit.snapshot : void 0;
|
|
300
|
+
if (!snapshot) {
|
|
301
|
+
const lines2 = [
|
|
302
|
+
`
|
|
303
|
+
${signal(c, "error")} ${ac} \uC758 \uBCA0\uC774\uC2A4\uB77C\uC778\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uB0B4 \uBCC0\uACBD\uC744 \uB0A8\uC758 \uBCC0\uACBD\uACFC \uAD6C\uBD84\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`,
|
|
304
|
+
"",
|
|
305
|
+
" \uC544\uC9C1 \uAD6C\uD604 \uC804\uC774\uBA74 \u2014 \uBA3C\uC800 \uBCA0\uC774\uC2A4\uB77C\uC778\uC744 \uC7A1\uC73C\uC138\uC694:",
|
|
306
|
+
` awl commit ${ac} --start`,
|
|
307
|
+
"",
|
|
308
|
+
" \uC774\uBBF8 \uAD6C\uD604\uD588\uB2E4\uBA74 \u2014 \uBCC0\uACBD\uC744 \uC7A0\uAE50 \uCE58\uC6E0\uB2E4\uAC00 \uB418\uB3CC\uB824 \uACA9\uB9AC \uCEE4\uBC0B\uD558\uC138\uC694(\uACA9\uB9AC \uBAA8\uB378 \uC720\uC9C0):",
|
|
309
|
+
" git stash push -u",
|
|
310
|
+
` awl commit ${ac} --start`,
|
|
311
|
+
" git stash pop",
|
|
312
|
+
` awl commit ${ac} -m "..."`,
|
|
313
|
+
""
|
|
314
|
+
];
|
|
315
|
+
process.stderr.write(`${lines2.join("\n")}
|
|
316
|
+
`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
const trailWarning = buildTrailGapWarning(gateWorkitem, c);
|
|
320
|
+
if (trailWarning) {
|
|
321
|
+
process.stderr.write(trailWarning);
|
|
322
|
+
}
|
|
323
|
+
const untrackedAtStart = Array.isArray(crit?.untrackedAtStart) ? crit.untrackedAtStart : [];
|
|
324
|
+
const expectedHead = typeof crit?.baseline === "string" ? crit.baseline : void 0;
|
|
325
|
+
const outcome = await isolatedCommit(
|
|
326
|
+
root,
|
|
327
|
+
ac,
|
|
328
|
+
opts.message,
|
|
329
|
+
snapshot,
|
|
330
|
+
untrackedAtStart,
|
|
331
|
+
expectedHead
|
|
332
|
+
);
|
|
333
|
+
process.stdout.write("\n");
|
|
334
|
+
if (outcome.stagedFiles.length > 0) {
|
|
335
|
+
process.stdout.write(" \uCEE4\uBC0B\uD560 \uB0B4 \uBCC0\uACBD:\n");
|
|
336
|
+
for (const f of outcome.stagedFiles) {
|
|
337
|
+
process.stdout.write(` ${color.green("+")} ${f}
|
|
338
|
+
`);
|
|
339
|
+
}
|
|
340
|
+
if (outcome.stagedFiles.length > STAGED_FILES_NOTICE_THRESHOLD) {
|
|
341
|
+
process.stdout.write(
|
|
342
|
+
` ${color.yellow(`\uD30C\uC77C ${outcome.stagedFiles.length}\uAC1C`)} \u2014 \uC774 \uC644\uB8CC\uC870\uAC74\uC758 \uBC94\uC704\uC640 \uC77C\uCE58\uD558\uB294\uC9C0 \uC704 \uBAA9\uB85D\uC744 \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.
|
|
343
|
+
`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (outcome.excludedFiles.length > 0) {
|
|
348
|
+
process.stdout.write(
|
|
349
|
+
` ${color.yellow("\uC81C\uC678")}(\uB0A8\uC758 \uBBF8\uCEE4\uBC0B \uBCC0\uACBD, \uC6CC\uD0B9\uD2B8\uB9AC\uC5D0 \uADF8\uB300\uB85C \uB461\uB2C8\uB2E4):
|
|
350
|
+
`
|
|
351
|
+
);
|
|
352
|
+
for (const f of outcome.excludedFiles) {
|
|
353
|
+
process.stdout.write(` ${color.dim("-")} ${f}
|
|
354
|
+
`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (!outcome.committed) {
|
|
358
|
+
process.stderr.write(`
|
|
359
|
+
${signal(c, "error")} \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. ${outcome.reason}
|
|
360
|
+
`);
|
|
361
|
+
const guidance = buildRescueGuidance(outcome.reason);
|
|
362
|
+
if (guidance) {
|
|
363
|
+
process.stderr.write(`${guidance}
|
|
364
|
+
`);
|
|
365
|
+
}
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
const rendered = renderCommitSuccess(outcome, c, opts.message);
|
|
369
|
+
process.stdout.write(rendered.stdout);
|
|
370
|
+
if (rendered.stderr) {
|
|
371
|
+
process.stderr.write(rendered.stderr);
|
|
372
|
+
}
|
|
373
|
+
const drift = await checkBaseDrift(root, opts.base, outcome.stagedFiles);
|
|
374
|
+
if (drift && drift.ahead > 0) {
|
|
375
|
+
process.stdout.write(
|
|
376
|
+
`
|
|
377
|
+
${color.yellow("\uACBD\uACE0")}: \uC774 \uBE0C\uB79C\uCE58\uAC00 \uAC08\uB77C\uC9C4 \uB4A4 ${drift.base} \uC5D0 ${drift.ahead}\uAC1C \uCEE4\uBC0B\uC774 \uC313\uC600\uC2B5\uB2C8\uB2E4.
|
|
378
|
+
`
|
|
379
|
+
);
|
|
380
|
+
if (drift.overlap.length > 0) {
|
|
381
|
+
process.stdout.write(` \uACB9\uCE58\uB294 \uD30C\uC77C: ${drift.overlap.join(", ")}
|
|
382
|
+
`);
|
|
383
|
+
process.stdout.write(" \uBCD1\uD569 \uC804\uC5D0 \uD655\uC778\uD558\uC138\uC694.\n");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const newSnap = await captureSnapshot(root);
|
|
387
|
+
const newUntracked = await listUntracked(root);
|
|
388
|
+
await pinBaselineRef(root, ac, newSnap);
|
|
389
|
+
writeState(
|
|
390
|
+
root,
|
|
391
|
+
setCriterion(loadState(root), ac, {
|
|
392
|
+
snapshot: newSnap,
|
|
393
|
+
baseline: outcome.commit,
|
|
394
|
+
// baseline 은 다음 격리 커밋의 diff 기준점이라 --start 때 HEAD 로 리셋된다.
|
|
395
|
+
// commit 은 리셋 안 되는 "이 AC 의 마지막 격리 커밋" 전용 필드 — status 의
|
|
396
|
+
// 캐노니컬 HEAD 검증(wi8-F3)이 이 값이 HEAD 조상인지 대조한다.
|
|
397
|
+
commit: outcome.commit,
|
|
398
|
+
untrackedAtStart: newUntracked
|
|
399
|
+
})
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
export {
|
|
403
|
+
buildRescueGuidance,
|
|
404
|
+
buildTrailGapWarning,
|
|
405
|
+
captureSnapshot,
|
|
406
|
+
checkBaseDrift,
|
|
407
|
+
isolatedCommit,
|
|
408
|
+
renderCommitSuccess,
|
|
409
|
+
runCommit,
|
|
410
|
+
startBaseline
|
|
411
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KNOWN_LANGUAGES,
|
|
3
|
+
KNOWN_NAMING_CONVENTIONS,
|
|
4
|
+
SETTABLE_KEYS,
|
|
5
|
+
VERIFY_ORDER,
|
|
6
|
+
applyConfigValue,
|
|
7
|
+
interactiveEditMenu,
|
|
8
|
+
loadConfig,
|
|
9
|
+
parseConfigKey,
|
|
10
|
+
parseVerifyValue,
|
|
11
|
+
requireConfig,
|
|
12
|
+
resolveProjectRoot,
|
|
13
|
+
runConfig,
|
|
14
|
+
runConfigSet,
|
|
15
|
+
validateConfig
|
|
16
|
+
} from "./chunk-UOPWVM2H.js";
|
|
17
|
+
import "./chunk-BUWGQVHT.js";
|
|
18
|
+
import "./chunk-7SYRDDTX.js";
|
|
19
|
+
export {
|
|
20
|
+
KNOWN_LANGUAGES,
|
|
21
|
+
KNOWN_NAMING_CONVENTIONS,
|
|
22
|
+
SETTABLE_KEYS,
|
|
23
|
+
VERIFY_ORDER,
|
|
24
|
+
applyConfigValue,
|
|
25
|
+
interactiveEditMenu,
|
|
26
|
+
loadConfig,
|
|
27
|
+
parseConfigKey,
|
|
28
|
+
parseVerifyValue,
|
|
29
|
+
requireConfig,
|
|
30
|
+
resolveProjectRoot,
|
|
31
|
+
runConfig,
|
|
32
|
+
runConfigSet,
|
|
33
|
+
validateConfig
|
|
34
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectChecks,
|
|
3
|
+
computeFileSizeOutliers,
|
|
4
|
+
detectNamingConvention,
|
|
5
|
+
detectRecordTrailGap,
|
|
6
|
+
gitBranch,
|
|
7
|
+
gitDirtyFiles,
|
|
8
|
+
gitHasCommits,
|
|
9
|
+
renderText,
|
|
10
|
+
runDoctor
|
|
11
|
+
} from "./chunk-CCMG377E.js";
|
|
12
|
+
import "./chunk-TKPHC32G.js";
|
|
13
|
+
import "./chunk-VU6IPRRM.js";
|
|
14
|
+
import "./chunk-I77CXOEX.js";
|
|
15
|
+
import "./chunk-4OCSYHYB.js";
|
|
16
|
+
import "./chunk-UOPWVM2H.js";
|
|
17
|
+
import "./chunk-BUWGQVHT.js";
|
|
18
|
+
import "./chunk-7SYRDDTX.js";
|
|
19
|
+
export {
|
|
20
|
+
collectChecks,
|
|
21
|
+
computeFileSizeOutliers,
|
|
22
|
+
detectNamingConvention,
|
|
23
|
+
detectRecordTrailGap,
|
|
24
|
+
gitBranch,
|
|
25
|
+
gitDirtyFiles,
|
|
26
|
+
gitHasCommits,
|
|
27
|
+
renderText,
|
|
28
|
+
runDoctor
|
|
29
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GOTCHA_RELATION_TYPES,
|
|
3
|
+
acquireLock,
|
|
4
|
+
collectEvolve,
|
|
5
|
+
gotchaCluster,
|
|
6
|
+
gotchasBySource,
|
|
7
|
+
loadGotchaList,
|
|
8
|
+
migrateDeltasToGotchas,
|
|
9
|
+
nextGotchaId,
|
|
10
|
+
normalizeRelations,
|
|
11
|
+
recordGotcha,
|
|
12
|
+
releaseLock,
|
|
13
|
+
runEvolveCollect,
|
|
14
|
+
runEvolveRecord,
|
|
15
|
+
writeGeneration
|
|
16
|
+
} from "./chunk-C7TN3LM3.js";
|
|
17
|
+
import "./chunk-F5LHXBH7.js";
|
|
18
|
+
import "./chunk-I77CXOEX.js";
|
|
19
|
+
import "./chunk-4OCSYHYB.js";
|
|
20
|
+
import "./chunk-UOPWVM2H.js";
|
|
21
|
+
import "./chunk-BUWGQVHT.js";
|
|
22
|
+
import "./chunk-7SYRDDTX.js";
|
|
23
|
+
export {
|
|
24
|
+
GOTCHA_RELATION_TYPES,
|
|
25
|
+
acquireLock,
|
|
26
|
+
collectEvolve,
|
|
27
|
+
gotchaCluster,
|
|
28
|
+
gotchasBySource,
|
|
29
|
+
loadGotchaList,
|
|
30
|
+
migrateDeltasToGotchas,
|
|
31
|
+
nextGotchaId,
|
|
32
|
+
normalizeRelations,
|
|
33
|
+
recordGotcha,
|
|
34
|
+
releaseLock,
|
|
35
|
+
runEvolveCollect,
|
|
36
|
+
runEvolveRecord,
|
|
37
|
+
writeGeneration
|
|
38
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readRecords
|
|
3
|
+
} from "./chunk-I77CXOEX.js";
|
|
4
|
+
import "./chunk-4OCSYHYB.js";
|
|
5
|
+
import "./chunk-UOPWVM2H.js";
|
|
6
|
+
import "./chunk-BUWGQVHT.js";
|
|
7
|
+
import {
|
|
8
|
+
caps,
|
|
9
|
+
card,
|
|
10
|
+
makeColors,
|
|
11
|
+
signal
|
|
12
|
+
} from "./chunk-7SYRDDTX.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/feedback.ts
|
|
15
|
+
var SEVERITY_RANK = { high: 0, medium: 1, low: 2 };
|
|
16
|
+
function loadAwlFeedback(filter = {}) {
|
|
17
|
+
let records = readRecords().filter((r) => r.type === "awl-feedback");
|
|
18
|
+
if (filter.area) {
|
|
19
|
+
records = records.filter((r) => r.area === filter.area);
|
|
20
|
+
}
|
|
21
|
+
if (filter.severity) {
|
|
22
|
+
records = records.filter((r) => r.severity === filter.severity);
|
|
23
|
+
}
|
|
24
|
+
if (filter.since) {
|
|
25
|
+
const sinceMs = new Date(filter.since).getTime();
|
|
26
|
+
if (!Number.isNaN(sinceMs)) {
|
|
27
|
+
records = records.filter((r) => {
|
|
28
|
+
if (typeof r.at !== "string") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const atMs = new Date(r.at).getTime();
|
|
32
|
+
return !Number.isNaN(atMs) && atMs >= sinceMs;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return records;
|
|
37
|
+
}
|
|
38
|
+
function isInvalidSince(since) {
|
|
39
|
+
return typeof since === "string" && since !== "" && Number.isNaN(new Date(since).getTime());
|
|
40
|
+
}
|
|
41
|
+
function buildFeedbackReport(records) {
|
|
42
|
+
const areas = {};
|
|
43
|
+
const workitems = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const r of records) {
|
|
45
|
+
if (typeof r.workitem === "string") {
|
|
46
|
+
workitems.add(r.workitem);
|
|
47
|
+
}
|
|
48
|
+
const area = typeof r.area === "string" ? r.area : "\uAE30\uD0C0";
|
|
49
|
+
const g = areas[area] ?? { count: 0, repeated: false, items: [] };
|
|
50
|
+
g.count += 1;
|
|
51
|
+
g.items.push(r);
|
|
52
|
+
areas[area] = g;
|
|
53
|
+
}
|
|
54
|
+
for (const g of Object.values(areas)) {
|
|
55
|
+
g.repeated = g.count >= 2;
|
|
56
|
+
g.items.sort(
|
|
57
|
+
(a, b) => (SEVERITY_RANK[String(a.severity)] ?? 3) - (SEVERITY_RANK[String(b.severity)] ?? 3)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const prioritized = Object.entries(areas).filter(([, g]) => g.count >= 2).sort(([, a], [, b]) => b.count - a.count).map(([area]) => area);
|
|
61
|
+
return { collectedFrom: workitems.size, areas, prioritized };
|
|
62
|
+
}
|
|
63
|
+
function renderFeedback(report, c) {
|
|
64
|
+
const color = makeColors(c.color);
|
|
65
|
+
const entries = Object.entries(report.areas).sort(([, a], [, b]) => b.count - a.count);
|
|
66
|
+
if (entries.length === 0) {
|
|
67
|
+
return card(
|
|
68
|
+
"awl \uC790\uCCB4 \uD53C\uB4DC\uBC31",
|
|
69
|
+
[
|
|
70
|
+
"\uC544\uC9C1 \uC218\uC9D1\uB41C awl-feedback \uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
71
|
+
"",
|
|
72
|
+
color.dim(
|
|
73
|
+
"\uC6CC\uD06C\uC544\uC774\uD15C\uC744 \uB2EB\uC744 \uB54C(evolve) awl \uB3C4\uAD6C \uC790\uCCB4\uAC00 \uBD88\uD3B8\uD588\uB2E4\uBA74 awl record awl-feedback \uC73C\uB85C \uB0A8\uAE30\uC138\uC694."
|
|
74
|
+
)
|
|
75
|
+
],
|
|
76
|
+
c
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const [area, g] of entries) {
|
|
81
|
+
const tag = g.repeated ? ` ${signal(c, "warn")} \uBC18\uBCF5` : "";
|
|
82
|
+
out.push(`${color.bold(area)} ${g.count}\uAC74${tag}`);
|
|
83
|
+
for (const item of g.items) {
|
|
84
|
+
const wi = typeof item.workitem === "string" ? item.workitem : "?";
|
|
85
|
+
const sev = String(item.severity ?? "?");
|
|
86
|
+
out.push(` ${color.dim("-")} ${String(item.what ?? "")} ${color.dim(`(${wi}, ${sev})`)}`);
|
|
87
|
+
}
|
|
88
|
+
out.push("");
|
|
89
|
+
}
|
|
90
|
+
if (report.prioritized.length > 0) {
|
|
91
|
+
const list = report.prioritized.map((a) => `${a}(${report.areas[a]?.count})`).join(", ");
|
|
92
|
+
out.push(color.yellow(`2\uD68C \uC774\uC0C1 \uBC18\uBCF5\uB41C area: ${list}`));
|
|
93
|
+
out.push(
|
|
94
|
+
color.dim(
|
|
95
|
+
"\u2192 \uC774 area \uB97C \uC6B0\uC120 \uAC80\uD1A0\uD558\uC138\uC694. \uBB34\uC5C7\uC744 \uACE0\uCE60\uC9C0\uB294 \uC0AC\uB78C\uC774 \uC815\uD569\uB2C8\uB2E4(awl \uC740 \uD310\uB2E8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)."
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return card(`awl \uC790\uCCB4 \uD53C\uB4DC\uBC31 \xB7 \uC6CC\uD06C\uC544\uC774\uD15C ${report.collectedFrom}\uAC1C\uC5D0\uC11C \uC218\uC9D1`, out, c);
|
|
100
|
+
}
|
|
101
|
+
function runFeedback(opts) {
|
|
102
|
+
if (isInvalidSince(opts.since)) {
|
|
103
|
+
process.stderr.write(
|
|
104
|
+
`
|
|
105
|
+
${signal(caps(), "warn")} --since '${opts.since}' \uB97C \uB0A0\uC9DC\uB85C \uC77D\uC9C0 \uBABB\uD574 \uBB34\uC2DC\uD569\uB2C8\uB2E4. \uC608: 2026-07-01
|
|
106
|
+
`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const records = loadAwlFeedback({ area: opts.area, severity: opts.severity, since: opts.since });
|
|
110
|
+
const report = buildFeedbackReport(records);
|
|
111
|
+
if (opts.json) {
|
|
112
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
113
|
+
`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
process.stdout.write(`${renderFeedback(report, caps())}
|
|
117
|
+
`);
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
buildFeedbackReport,
|
|
121
|
+
isInvalidSince,
|
|
122
|
+
loadAwlFeedback,
|
|
123
|
+
renderFeedback,
|
|
124
|
+
runFeedback
|
|
125
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadGotchaList
|
|
3
|
+
} from "./chunk-C7TN3LM3.js";
|
|
4
|
+
import "./chunk-F5LHXBH7.js";
|
|
5
|
+
import "./chunk-I77CXOEX.js";
|
|
6
|
+
import "./chunk-4OCSYHYB.js";
|
|
7
|
+
import "./chunk-UOPWVM2H.js";
|
|
8
|
+
import "./chunk-BUWGQVHT.js";
|
|
9
|
+
import {
|
|
10
|
+
caps,
|
|
11
|
+
card,
|
|
12
|
+
makeColors
|
|
13
|
+
} from "./chunk-7SYRDDTX.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/gotchas.ts
|
|
16
|
+
function loadGotchas() {
|
|
17
|
+
return loadGotchaList();
|
|
18
|
+
}
|
|
19
|
+
function renderGotchas(gotchas, c) {
|
|
20
|
+
const color = makeColors(c.color);
|
|
21
|
+
if (gotchas.length === 0) {
|
|
22
|
+
return card("\uD568\uC815", ["\uD568\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."], c);
|
|
23
|
+
}
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const g of gotchas) {
|
|
26
|
+
out.push(`${color.dim("\xB7")} ${g.lesson || "(\uC694\uC57D \uC5C6\uC74C)"}`);
|
|
27
|
+
}
|
|
28
|
+
return card(`\uD568\uC815 ${gotchas.length}\uAC1C \xB7 \uC544\uC9C1 \uADDC\uCE59 \uC544\uB2D8`, out, c);
|
|
29
|
+
}
|
|
30
|
+
function runGotchas(opts) {
|
|
31
|
+
const gotchas = loadGotchas();
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
process.stdout.write(`${JSON.stringify(gotchas, null, 2)}
|
|
34
|
+
`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(`${renderGotchas(gotchas, caps())}
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
loadGotchas,
|
|
42
|
+
runGotchas
|
|
43
|
+
};
|