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,727 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LANG_OPTIONS,
|
|
3
|
+
LANG_VALUES,
|
|
4
|
+
ask,
|
|
5
|
+
buildScreens,
|
|
6
|
+
promptNumber
|
|
7
|
+
} from "./chunk-BUWGQVHT.js";
|
|
8
|
+
import {
|
|
9
|
+
caps,
|
|
10
|
+
card,
|
|
11
|
+
findProjectRoot,
|
|
12
|
+
makeColors,
|
|
13
|
+
makeSymbols,
|
|
14
|
+
signal
|
|
15
|
+
} from "./chunk-7SYRDDTX.js";
|
|
16
|
+
|
|
17
|
+
// src/commands/config.ts
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import readline from "readline";
|
|
21
|
+
|
|
22
|
+
// src/core/runner.ts
|
|
23
|
+
import { performance } from "perf_hooks";
|
|
24
|
+
import spawn from "cross-spawn";
|
|
25
|
+
var CommandNotFoundError = class extends Error {
|
|
26
|
+
command;
|
|
27
|
+
constructor(command) {
|
|
28
|
+
super(`\uBA85\uB839\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${command}`);
|
|
29
|
+
this.name = "CommandNotFoundError";
|
|
30
|
+
this.command = command;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function tokenize(cmd) {
|
|
34
|
+
const tokens = [];
|
|
35
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
36
|
+
let match = re.exec(cmd);
|
|
37
|
+
while (match !== null) {
|
|
38
|
+
tokens.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
39
|
+
match = re.exec(cmd);
|
|
40
|
+
}
|
|
41
|
+
return tokens;
|
|
42
|
+
}
|
|
43
|
+
function run(spec) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const tokens = spec.args ? [spec.cmd, ...spec.args] : tokenize(spec.cmd);
|
|
46
|
+
const program = tokens[0];
|
|
47
|
+
const args = tokens.slice(1);
|
|
48
|
+
if (!program) {
|
|
49
|
+
reject(new Error("\uBE48 \uBA85\uB839\uC785\uB2C8\uB2E4."));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const start = performance.now();
|
|
53
|
+
let stdout = "";
|
|
54
|
+
let stderr = "";
|
|
55
|
+
let timedOut = false;
|
|
56
|
+
let timer;
|
|
57
|
+
let settled = false;
|
|
58
|
+
const child = spawn(program, args, {
|
|
59
|
+
cwd: spec.cwd,
|
|
60
|
+
// process.env 를 상속하고 spec.env 로 덮어쓴다. PATH 등은 유지된다.
|
|
61
|
+
env: { ...process.env, ...spec.env },
|
|
62
|
+
shell: false,
|
|
63
|
+
signal: spec.signal,
|
|
64
|
+
windowsHide: true
|
|
65
|
+
});
|
|
66
|
+
const clearTimer = () => {
|
|
67
|
+
if (timer) {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
timer = void 0;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if (spec.timeoutMs !== void 0 && spec.timeoutMs > 0) {
|
|
73
|
+
timer = setTimeout(() => {
|
|
74
|
+
timedOut = true;
|
|
75
|
+
child.kill("SIGTERM");
|
|
76
|
+
}, spec.timeoutMs);
|
|
77
|
+
}
|
|
78
|
+
child.stdout?.on("data", (chunk) => {
|
|
79
|
+
stdout += chunk.toString();
|
|
80
|
+
});
|
|
81
|
+
child.stderr?.on("data", (chunk) => {
|
|
82
|
+
stderr += chunk.toString();
|
|
83
|
+
});
|
|
84
|
+
child.on("error", (err) => {
|
|
85
|
+
if (settled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
settled = true;
|
|
89
|
+
clearTimer();
|
|
90
|
+
if (err.code === "ENOENT") {
|
|
91
|
+
reject(new CommandNotFoundError(program));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
reject(err);
|
|
95
|
+
});
|
|
96
|
+
child.on("close", (code) => {
|
|
97
|
+
if (settled) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
settled = true;
|
|
101
|
+
clearTimer();
|
|
102
|
+
resolve({
|
|
103
|
+
exitCode: code,
|
|
104
|
+
stdout,
|
|
105
|
+
stderr,
|
|
106
|
+
durationMs: Math.round(performance.now() - start),
|
|
107
|
+
timedOut
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/commands/config.ts
|
|
114
|
+
var VERIFY_ORDER = ["typecheck", "lint", "test", "e2e"];
|
|
115
|
+
function resolveProjectRoot(cwd = process.cwd()) {
|
|
116
|
+
try {
|
|
117
|
+
return findProjectRoot(cwd);
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function isVerifyEntry(v) {
|
|
123
|
+
if (v === null) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (typeof v !== "object") {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const o = v;
|
|
130
|
+
if (typeof o.cmd !== "string") {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if ("cwd" in o && o.cwd !== void 0 && typeof o.cwd !== "string") {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if ("env" in o && o.env !== void 0 && (typeof o.env !== "object" || o.env === null)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
function validateConfig(obj) {
|
|
142
|
+
const errors = [];
|
|
143
|
+
if (typeof obj !== "object" || obj === null) {
|
|
144
|
+
errors.push("config \uAC00 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
|
|
145
|
+
return errors;
|
|
146
|
+
}
|
|
147
|
+
const o = obj;
|
|
148
|
+
if (typeof o.project !== "string" || o.project.trim() === "") {
|
|
149
|
+
errors.push("project \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 (\uBB38\uC790\uC5F4 \uD544\uC218)");
|
|
150
|
+
}
|
|
151
|
+
if (typeof o.engineVersion !== "string") {
|
|
152
|
+
errors.push("engineVersion \uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (\uBB38\uC790\uC5F4 \uD544\uC218)");
|
|
153
|
+
}
|
|
154
|
+
if (typeof o.verify !== "object" || o.verify === null) {
|
|
155
|
+
errors.push("verify \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 (\uAC1D\uCCB4 \uD544\uC218)");
|
|
156
|
+
} else {
|
|
157
|
+
const v = o.verify;
|
|
158
|
+
for (const k of VERIFY_ORDER) {
|
|
159
|
+
if (k in v && !isVerifyEntry(v[k])) {
|
|
160
|
+
errors.push(`verify.${k} \uD615\uC2DD \uC624\uB958 (null \uB610\uB294 { "cmd": "..." })`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if ("protectedFiles" in o && (!Array.isArray(o.protectedFiles) || !o.protectedFiles.every((p) => typeof p === "string"))) {
|
|
165
|
+
errors.push("protectedFiles \uD615\uC2DD \uC624\uB958 (\uBB38\uC790\uC5F4 \uBC30\uC5F4)");
|
|
166
|
+
}
|
|
167
|
+
return errors;
|
|
168
|
+
}
|
|
169
|
+
function jsonErrorLocation(text, err) {
|
|
170
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
171
|
+
const m = /position (\d+)/.exec(msg);
|
|
172
|
+
if (m?.[1]) {
|
|
173
|
+
const line = text.slice(0, Number(m[1])).split("\n").length;
|
|
174
|
+
return `${msg} (\uC57D ${line}\uBC88\uC9F8 \uC904)`;
|
|
175
|
+
}
|
|
176
|
+
return msg;
|
|
177
|
+
}
|
|
178
|
+
function loadConfig(projectRoot) {
|
|
179
|
+
const p = path.join(projectRoot, ".awl", "config.json");
|
|
180
|
+
if (!fs.existsSync(p)) {
|
|
181
|
+
return { config: null, errors: ["config.json \uC774 \uC5C6\uC2B5\uB2C8\uB2E4. awl init \uC744 \uC2E4\uD589\uD558\uC138\uC694."], path: p };
|
|
182
|
+
}
|
|
183
|
+
let text;
|
|
184
|
+
try {
|
|
185
|
+
text = fs.readFileSync(p, "utf8");
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return { config: null, errors: [`config.json \uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${String(e)}`], path: p };
|
|
188
|
+
}
|
|
189
|
+
let parsed;
|
|
190
|
+
try {
|
|
191
|
+
parsed = JSON.parse(text);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
return {
|
|
194
|
+
config: null,
|
|
195
|
+
errors: [`config.json JSON \uD30C\uC2F1 \uC624\uB958: ${jsonErrorLocation(text, e)}`],
|
|
196
|
+
path: p
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const errors = validateConfig(parsed);
|
|
200
|
+
if (errors.length > 0) {
|
|
201
|
+
return { config: null, errors, path: p };
|
|
202
|
+
}
|
|
203
|
+
const raw = parsed;
|
|
204
|
+
const rv = raw.verify;
|
|
205
|
+
const config = {
|
|
206
|
+
project: raw.project,
|
|
207
|
+
mainLanguage: typeof raw.mainLanguage === "string" ? raw.mainLanguage : "",
|
|
208
|
+
character: typeof raw.character === "string" ? raw.character : "",
|
|
209
|
+
engineVersion: raw.engineVersion,
|
|
210
|
+
...typeof raw.namingConvention === "string" ? { namingConvention: raw.namingConvention } : {},
|
|
211
|
+
...typeof raw.relatedCmd === "string" ? { relatedCmd: raw.relatedCmd } : {},
|
|
212
|
+
...Array.isArray(raw.protectedFiles) ? { protectedFiles: raw.protectedFiles } : {},
|
|
213
|
+
verify: {
|
|
214
|
+
typecheck: rv.typecheck ?? null,
|
|
215
|
+
lint: rv.lint ?? null,
|
|
216
|
+
test: rv.test ?? null,
|
|
217
|
+
e2e: rv.e2e ?? null
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
return { config, errors: [], path: p };
|
|
221
|
+
}
|
|
222
|
+
function requireConfig() {
|
|
223
|
+
const projectRoot = resolveProjectRoot();
|
|
224
|
+
if (!projectRoot) {
|
|
225
|
+
process.stderr.write(
|
|
226
|
+
`
|
|
227
|
+
${signal(caps(), "error")} \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(.git/.awl \uC5C6\uC74C). awl init \uC744 \uC2E4\uD589\uD558\uC138\uC694.
|
|
228
|
+
`
|
|
229
|
+
);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
const loaded = loadConfig(projectRoot);
|
|
233
|
+
if (!loaded.config) {
|
|
234
|
+
process.stderr.write(`
|
|
235
|
+
${signal(caps(), "error")} config \uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:
|
|
236
|
+
`);
|
|
237
|
+
for (const e of loaded.errors) {
|
|
238
|
+
process.stderr.write(` - ${e}
|
|
239
|
+
`);
|
|
240
|
+
}
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
return { projectRoot, config: loaded.config };
|
|
244
|
+
}
|
|
245
|
+
function parseVerifyValue(value) {
|
|
246
|
+
const trimmed = value.trim();
|
|
247
|
+
if (trimmed === "" || trimmed.toLowerCase() === "null" || trimmed === "none" || trimmed === "-") {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const env = {};
|
|
251
|
+
let rest = trimmed;
|
|
252
|
+
const re = /^(\w+)=(\S+)\s+/;
|
|
253
|
+
let m = re.exec(rest);
|
|
254
|
+
while (m !== null) {
|
|
255
|
+
env[m[1]] = m[2];
|
|
256
|
+
rest = rest.slice(m[0].length);
|
|
257
|
+
m = re.exec(rest);
|
|
258
|
+
}
|
|
259
|
+
return Object.keys(env).length > 0 ? { cmd: rest, env } : { cmd: rest };
|
|
260
|
+
}
|
|
261
|
+
var KNOWN_LANGUAGES = ["typescript", "javascript", "python"];
|
|
262
|
+
var KNOWN_NAMING_CONVENTIONS = ["kebab-case", "camelCase", "snake_case", "PascalCase"];
|
|
263
|
+
var SETTABLE_KEYS = [
|
|
264
|
+
"project",
|
|
265
|
+
"mainLanguage",
|
|
266
|
+
"character",
|
|
267
|
+
"namingConvention",
|
|
268
|
+
"relatedCmd",
|
|
269
|
+
"protectedFiles",
|
|
270
|
+
...VERIFY_ORDER.flatMap((n) => [`verify.${n}.cmd`, `verify.${n}.cwd`, `verify.${n}.env`])
|
|
271
|
+
];
|
|
272
|
+
function parseConfigKey(key) {
|
|
273
|
+
if (key === "project") {
|
|
274
|
+
return { kind: "project" };
|
|
275
|
+
}
|
|
276
|
+
if (key === "mainLanguage") {
|
|
277
|
+
return { kind: "mainLanguage" };
|
|
278
|
+
}
|
|
279
|
+
if (key === "character") {
|
|
280
|
+
return { kind: "character" };
|
|
281
|
+
}
|
|
282
|
+
if (key === "namingConvention") {
|
|
283
|
+
return { kind: "namingConvention" };
|
|
284
|
+
}
|
|
285
|
+
if (key === "relatedCmd") {
|
|
286
|
+
return { kind: "relatedCmd" };
|
|
287
|
+
}
|
|
288
|
+
if (key === "protectedFiles") return { kind: "protectedFiles" };
|
|
289
|
+
const names = VERIFY_ORDER.join("|");
|
|
290
|
+
const cmdMatch = new RegExp(`^verify\\.(${names})(?:\\.cmd)?$`).exec(key);
|
|
291
|
+
if (cmdMatch?.[1]) {
|
|
292
|
+
return { kind: "verify.cmd", verifyName: cmdMatch[1] };
|
|
293
|
+
}
|
|
294
|
+
const cwdMatch = new RegExp(`^verify\\.(${names})\\.cwd$`).exec(key);
|
|
295
|
+
if (cwdMatch?.[1]) {
|
|
296
|
+
return { kind: "verify.cwd", verifyName: cwdMatch[1] };
|
|
297
|
+
}
|
|
298
|
+
const envMatch = new RegExp(`^verify\\.(${names})\\.env$`).exec(key);
|
|
299
|
+
if (envMatch?.[1]) {
|
|
300
|
+
return { kind: "verify.env", verifyName: envMatch[1] };
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
async function verifyCommandExists(entry, cwd) {
|
|
305
|
+
const first = entry.cmd.split(/\s+/)[0] ?? "";
|
|
306
|
+
try {
|
|
307
|
+
const r = await run({ cmd: first, args: ["--version"], env: entry.env, cwd, timeoutMs: 5e3 });
|
|
308
|
+
return { ok: true, note: `\uC885\uB8CC \uCF54\uB4DC ${r.exitCode}` };
|
|
309
|
+
} catch (e) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
note: e instanceof CommandNotFoundError ? `\uBA85\uB839\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${first}` : `\uC2E4\uD589 \uC624\uB958: ${String(e)}`
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function resolveCwd(projectRoot, cwd) {
|
|
317
|
+
if (!cwd) {
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
return path.isAbsolute(cwd) ? cwd : path.join(projectRoot, cwd);
|
|
321
|
+
}
|
|
322
|
+
async function applyConfigValue(config, projectRoot, parsed, rawValue, opts) {
|
|
323
|
+
if (parsed.kind === "project") {
|
|
324
|
+
const v2 = rawValue.trim();
|
|
325
|
+
if (v2 === "") {
|
|
326
|
+
return { ok: false, message: "project \uB294 \uBE44\uC6B8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." };
|
|
327
|
+
}
|
|
328
|
+
config.project = v2;
|
|
329
|
+
return { ok: true, message: `project = ${v2}` };
|
|
330
|
+
}
|
|
331
|
+
if (parsed.kind === "mainLanguage") {
|
|
332
|
+
const v2 = rawValue.trim();
|
|
333
|
+
if (v2 === "") {
|
|
334
|
+
return { ok: false, message: "mainLanguage \uB294 \uBE44\uC6B8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." };
|
|
335
|
+
}
|
|
336
|
+
config.mainLanguage = v2;
|
|
337
|
+
if (!KNOWN_LANGUAGES.includes(v2)) {
|
|
338
|
+
return {
|
|
339
|
+
ok: true,
|
|
340
|
+
message: `mainLanguage = ${v2} (\uACBD\uACE0: \uC54C\uB824\uC9C4 \uAC12\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 ${KNOWN_LANGUAGES.join("/")})`
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return { ok: true, message: `mainLanguage = ${v2}` };
|
|
344
|
+
}
|
|
345
|
+
if (parsed.kind === "character") {
|
|
346
|
+
config.character = rawValue;
|
|
347
|
+
return { ok: true, message: `character = ${rawValue || "(\uBE44\uC6C0)"}` };
|
|
348
|
+
}
|
|
349
|
+
if (parsed.kind === "namingConvention") {
|
|
350
|
+
const v2 = rawValue.trim();
|
|
351
|
+
if (v2 === "") {
|
|
352
|
+
config.namingConvention = void 0;
|
|
353
|
+
return { ok: true, message: "namingConvention = (\uBE44\uC6C0)" };
|
|
354
|
+
}
|
|
355
|
+
config.namingConvention = v2;
|
|
356
|
+
if (!KNOWN_NAMING_CONVENTIONS.includes(v2)) {
|
|
357
|
+
return {
|
|
358
|
+
ok: true,
|
|
359
|
+
message: `namingConvention = ${v2} (\uACBD\uACE0: \uC54C\uB824\uC9C4 \uAC12\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 ${KNOWN_NAMING_CONVENTIONS.join("/")})`
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return { ok: true, message: `namingConvention = ${v2}` };
|
|
363
|
+
}
|
|
364
|
+
if (parsed.kind === "relatedCmd") {
|
|
365
|
+
const v2 = rawValue.trim();
|
|
366
|
+
if (v2 === "") {
|
|
367
|
+
config.relatedCmd = void 0;
|
|
368
|
+
return { ok: true, message: "relatedCmd = (\uBE44\uC6C0)" };
|
|
369
|
+
}
|
|
370
|
+
if (!v2.includes("{files}")) {
|
|
371
|
+
return {
|
|
372
|
+
ok: false,
|
|
373
|
+
message: "relatedCmd \uC5D0\uB294 {files} \uC790\uB9AC\uD45C\uC2DC\uC790\uAC00 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4(\uBCC0\uACBD \uD30C\uC77C \uBAA9\uB85D\uC73C\uB85C \uCE58\uD658\uB429\uB2C8\uB2E4)."
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
config.relatedCmd = v2;
|
|
377
|
+
return { ok: true, message: `relatedCmd = ${v2}` };
|
|
378
|
+
}
|
|
379
|
+
if (parsed.kind === "protectedFiles") {
|
|
380
|
+
let files;
|
|
381
|
+
try {
|
|
382
|
+
files = JSON.parse(rawValue);
|
|
383
|
+
} catch {
|
|
384
|
+
return { ok: false, message: "protectedFiles \uB294 JSON \uBB38\uC790\uC5F4 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4." };
|
|
385
|
+
}
|
|
386
|
+
if (!Array.isArray(files) || !files.every((file) => typeof file === "string" && file.trim() !== "")) {
|
|
387
|
+
return { ok: false, message: "protectedFiles \uB294 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uBB38\uC790\uC5F4 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4." };
|
|
388
|
+
}
|
|
389
|
+
config.protectedFiles = files;
|
|
390
|
+
return { ok: true, message: `protectedFiles = ${files.join(", ") || "(\uBE44\uC6C0)"}` };
|
|
391
|
+
}
|
|
392
|
+
const name = parsed.verifyName;
|
|
393
|
+
if (parsed.kind === "verify.cmd") {
|
|
394
|
+
const entry = parseVerifyValue(rawValue);
|
|
395
|
+
const prevCwd = config.verify[name]?.cwd;
|
|
396
|
+
if (entry) {
|
|
397
|
+
const check = await verifyCommandExists(entry, resolveCwd(projectRoot, prevCwd));
|
|
398
|
+
if (!check.ok && !opts.force) {
|
|
399
|
+
return {
|
|
400
|
+
ok: false,
|
|
401
|
+
message: `'${entry.cmd}' \uD655\uC778 \uC2E4\uD328: ${check.note}
|
|
402
|
+
\uADF8\uB798\uB3C4 \uC800\uC7A5\uD558\uB824\uBA74 --force \uB97C \uBD99\uC774\uC138\uC694.`
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
config.verify[name] = entry ? { ...entry, ...prevCwd ? { cwd: prevCwd } : {} } : null;
|
|
407
|
+
return { ok: true, message: `verify.${name}.cmd = ${entry ? entry.cmd : "null"}` };
|
|
408
|
+
}
|
|
409
|
+
const existing = config.verify[name];
|
|
410
|
+
if (!existing) {
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
message: `verify.${name} \uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 cmd \uB97C \uC124\uC815\uD558\uC138\uC694: awl config set verify.${name}.cmd "..."`
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if (parsed.kind === "verify.cwd") {
|
|
417
|
+
const v2 = rawValue.trim();
|
|
418
|
+
if (v2 === "" || v2.toLowerCase() === "null" || v2 === "-") {
|
|
419
|
+
existing.cwd = void 0;
|
|
420
|
+
return { ok: true, message: `verify.${name}.cwd = (\uC5C6\uC74C)` };
|
|
421
|
+
}
|
|
422
|
+
const abs = resolveCwd(projectRoot, v2);
|
|
423
|
+
const dirExists = fs.existsSync(abs) && fs.statSync(abs).isDirectory();
|
|
424
|
+
let warn = "";
|
|
425
|
+
if (path.isAbsolute(v2)) {
|
|
426
|
+
warn += "\n\uACBD\uACE0: \uC808\uB300 \uACBD\uB85C\uC785\uB2C8\uB2E4. \uB2E4\uB978 \uC0AC\uB78C\uC758 \uBA38\uC2E0\uC5D0\uC11C\uB294 \uB2E4\uB978 \uC704\uCE58\uB97C \uAC00\uB9AC\uD0AC \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
|
|
427
|
+
}
|
|
428
|
+
if (!dirExists) {
|
|
429
|
+
if (!opts.force) {
|
|
430
|
+
return {
|
|
431
|
+
ok: false,
|
|
432
|
+
message: `\uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: ${abs}
|
|
433
|
+
\uADF8\uB798\uB3C4 \uC800\uC7A5\uD558\uB824\uBA74 --force \uB97C \uBD99\uC774\uC138\uC694.`
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
warn += `
|
|
437
|
+
\uACBD\uACE0: \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: ${abs} (\uAC15\uC81C \uC800\uC7A5)`;
|
|
438
|
+
}
|
|
439
|
+
existing.cwd = v2;
|
|
440
|
+
return { ok: true, message: `verify.${name}.cwd = ${v2}${warn}` };
|
|
441
|
+
}
|
|
442
|
+
const v = rawValue.trim();
|
|
443
|
+
if (v === "" || v.toLowerCase() === "null" || v === "-") {
|
|
444
|
+
existing.env = void 0;
|
|
445
|
+
return { ok: true, message: `verify.${name}.env = (\uC5C6\uC74C)` };
|
|
446
|
+
}
|
|
447
|
+
let parsedEnv;
|
|
448
|
+
try {
|
|
449
|
+
parsedEnv = JSON.parse(v);
|
|
450
|
+
} catch (e) {
|
|
451
|
+
return { ok: false, message: `env \uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${String(e)}` };
|
|
452
|
+
}
|
|
453
|
+
if (typeof parsedEnv !== "object" || parsedEnv === null || Array.isArray(parsedEnv)) {
|
|
454
|
+
return { ok: false, message: 'env \uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uC608: {"NODE_ENV":"test"})' };
|
|
455
|
+
}
|
|
456
|
+
existing.env = parsedEnv;
|
|
457
|
+
return { ok: true, message: `verify.${name}.env = ${v}` };
|
|
458
|
+
}
|
|
459
|
+
function renderConfig(config, c) {
|
|
460
|
+
const color = makeColors(c.color);
|
|
461
|
+
const s = makeSymbols(c);
|
|
462
|
+
const out = [];
|
|
463
|
+
out.push(`${s.branch} \uC8FC \uC5B8\uC5B4 ${config.mainLanguage || "(\uC5C6\uC74C)"}`);
|
|
464
|
+
out.push(`${s.branch} \uC131\uACA9 ${config.character || "(\uC5C6\uC74C)"}`);
|
|
465
|
+
out.push(`${s.branch} \uC5D4\uC9C4 ${config.engineVersion}`);
|
|
466
|
+
out.push("");
|
|
467
|
+
for (const k of VERIFY_ORDER) {
|
|
468
|
+
const entry = config.verify[k];
|
|
469
|
+
out.push(`${s.branch} ${k.padEnd(10, " ")}${entry ? entry.cmd : "(\uC5C6\uC74C)"}`);
|
|
470
|
+
if (entry?.cwd) {
|
|
471
|
+
out.push(`${s.vGuide} ${s.lastBranch} cwd: ${entry.cwd}`);
|
|
472
|
+
}
|
|
473
|
+
if (entry?.env && Object.keys(entry.env).length > 0) {
|
|
474
|
+
out.push(`${s.vGuide} ${s.lastBranch} env: ${JSON.stringify(entry.env)}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
out.push("");
|
|
478
|
+
out.push(
|
|
479
|
+
`${s.lastBranch} ${color.dim('\uBA85\uB839\uC744 \uBC14\uAFB8\uB824\uBA74: awl config set verify.lint.cmd "biome check ."')}`
|
|
480
|
+
);
|
|
481
|
+
out.push(` ${color.dim("\uC9C1\uC811 \uD3B8\uC9D1\uB3C4 \uB429\uB2C8\uB2E4: .awl/config.json")}`);
|
|
482
|
+
return card(`${config.project} \uC124\uC815`, out, c);
|
|
483
|
+
}
|
|
484
|
+
function writeConfigFile(projectRoot, config) {
|
|
485
|
+
const p = path.join(projectRoot, ".awl", "config.json");
|
|
486
|
+
fs.writeFileSync(p, `${JSON.stringify(config, null, 2)}
|
|
487
|
+
`);
|
|
488
|
+
}
|
|
489
|
+
var EDIT_MENU = ["\uADF8\uB300\uB85C \uB454\uB2E4", "\uC8FC \uC5B8\uC5B4", "\uAC80\uC99D \uBA85\uB839\uC5B4", "\uC131\uACA9", "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984"];
|
|
490
|
+
async function editVerifyCommands(rl, config, projectRoot, c) {
|
|
491
|
+
const screens = buildScreens(projectRoot, true, c);
|
|
492
|
+
process.stdout.write(`
|
|
493
|
+
${screens.verify}
|
|
494
|
+
`);
|
|
495
|
+
for (const name of VERIFY_ORDER) {
|
|
496
|
+
const cur = config.verify[name];
|
|
497
|
+
const shown = cur ? cur.cmd : "(\uC5C6\uC74C)";
|
|
498
|
+
const answer = (await ask(rl, ` ${name} [${shown}]: `)).trim();
|
|
499
|
+
if (answer === "") {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const outcome = await applyConfigValue(
|
|
503
|
+
config,
|
|
504
|
+
projectRoot,
|
|
505
|
+
{ kind: "verify.cmd", verifyName: name },
|
|
506
|
+
answer,
|
|
507
|
+
{ force: false }
|
|
508
|
+
);
|
|
509
|
+
process.stdout.write(` ${outcome.message}
|
|
510
|
+
`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function editMainLanguage(rl, config, projectRoot, c) {
|
|
514
|
+
const screens = buildScreens(projectRoot, true, c);
|
|
515
|
+
process.stdout.write(`
|
|
516
|
+
\uD604\uC7AC \uC124\uC815: ${config.mainLanguage || "(\uC5C6\uC74C)"}
|
|
517
|
+
`);
|
|
518
|
+
process.stdout.write(`${screens.lang}
|
|
519
|
+
`);
|
|
520
|
+
const curIdx = LANG_VALUES.indexOf(config.mainLanguage);
|
|
521
|
+
const idx = await promptNumber(rl, curIdx >= 0 ? curIdx : 0, LANG_OPTIONS.length);
|
|
522
|
+
let value = LANG_VALUES[idx] ?? "";
|
|
523
|
+
if (idx === LANG_OPTIONS.length - 1) {
|
|
524
|
+
value = (await ask(rl, " \uC8FC \uC5B8\uC5B4\uB97C \uC785\uB825\uD558\uC138\uC694: ")).trim();
|
|
525
|
+
}
|
|
526
|
+
const outcome = await applyConfigValue(config, projectRoot, { kind: "mainLanguage" }, value, {
|
|
527
|
+
force: false
|
|
528
|
+
});
|
|
529
|
+
process.stdout.write(` ${outcome.message}
|
|
530
|
+
`);
|
|
531
|
+
}
|
|
532
|
+
async function editCharacter(rl, config, projectRoot, c) {
|
|
533
|
+
const screens = buildScreens(projectRoot, true, c);
|
|
534
|
+
process.stdout.write(`
|
|
535
|
+
${screens.character}
|
|
536
|
+
`);
|
|
537
|
+
process.stdout.write(` \uD604\uC7AC: ${config.character || "(\uBE44\uC6C0)"}
|
|
538
|
+
`);
|
|
539
|
+
const answer = await ask(rl, " > ");
|
|
540
|
+
const outcome = await applyConfigValue(config, projectRoot, { kind: "character" }, answer, {
|
|
541
|
+
force: false
|
|
542
|
+
});
|
|
543
|
+
process.stdout.write(` ${outcome.message}
|
|
544
|
+
`);
|
|
545
|
+
}
|
|
546
|
+
async function editProjectName(rl, config, projectRoot) {
|
|
547
|
+
const answer = (await ask(rl, ` \uD504\uB85C\uC81D\uD2B8 \uC774\uB984 [${config.project}]: `)).trim();
|
|
548
|
+
if (answer === "") {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const outcome = await applyConfigValue(config, projectRoot, { kind: "project" }, answer, {
|
|
552
|
+
force: false
|
|
553
|
+
});
|
|
554
|
+
process.stdout.write(` ${outcome.message}
|
|
555
|
+
`);
|
|
556
|
+
}
|
|
557
|
+
async function interactiveEditMenu(rl, config, projectRoot, c) {
|
|
558
|
+
process.stdout.write("\n \uC218\uC815\uD560 \uD56D\uBAA9\uC744 \uACE0\uB974\uC138\uC694.\n\n");
|
|
559
|
+
for (let i = 0; i < EDIT_MENU.length; i++) {
|
|
560
|
+
process.stdout.write(` ${i + 1} ${EDIT_MENU[i]}
|
|
561
|
+
`);
|
|
562
|
+
}
|
|
563
|
+
const idx = await promptNumber(rl, 0, EDIT_MENU.length);
|
|
564
|
+
if (idx === 0) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
if (idx === 1) {
|
|
568
|
+
await editMainLanguage(rl, config, projectRoot, c);
|
|
569
|
+
} else if (idx === 2) {
|
|
570
|
+
await editVerifyCommands(rl, config, projectRoot, c);
|
|
571
|
+
} else if (idx === 3) {
|
|
572
|
+
await editCharacter(rl, config, projectRoot, c);
|
|
573
|
+
} else if (idx === 4) {
|
|
574
|
+
await editProjectName(rl, config, projectRoot);
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
async function runConfig() {
|
|
579
|
+
const projectRoot = resolveProjectRoot();
|
|
580
|
+
if (!projectRoot) {
|
|
581
|
+
process.stderr.write(
|
|
582
|
+
`
|
|
583
|
+
${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.
|
|
584
|
+
`
|
|
585
|
+
);
|
|
586
|
+
process.exit(1);
|
|
587
|
+
}
|
|
588
|
+
const loaded = loadConfig(projectRoot);
|
|
589
|
+
if (!loaded.config) {
|
|
590
|
+
process.stderr.write(`
|
|
591
|
+
${signal(caps(), "error")} config.json \uC5D0 \uBB38\uC81C\uAC00 \uC788\uC2B5\uB2C8\uB2E4:
|
|
592
|
+
`);
|
|
593
|
+
for (const e of loaded.errors) {
|
|
594
|
+
process.stderr.write(` - ${e}
|
|
595
|
+
`);
|
|
596
|
+
}
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
const config = loaded.config;
|
|
600
|
+
const c = caps();
|
|
601
|
+
process.stdout.write(`${renderConfig(config, c)}
|
|
602
|
+
`);
|
|
603
|
+
const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
604
|
+
if (!interactive) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
608
|
+
try {
|
|
609
|
+
const changed = await interactiveEditMenu(rl, config, projectRoot, c);
|
|
610
|
+
if (changed) {
|
|
611
|
+
writeConfigFile(projectRoot, config);
|
|
612
|
+
process.stdout.write("\n \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.\n");
|
|
613
|
+
} else {
|
|
614
|
+
process.stdout.write("\n \uBC14\uB010 \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
|
|
615
|
+
}
|
|
616
|
+
} finally {
|
|
617
|
+
rl.close();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function renderSettableKeys(config, c) {
|
|
621
|
+
const color = makeColors(c.color);
|
|
622
|
+
const currentOf = (key) => {
|
|
623
|
+
if (key === "project") return config.project;
|
|
624
|
+
if (key === "mainLanguage") return config.mainLanguage || "(\uC5C6\uC74C)";
|
|
625
|
+
if (key === "character") return config.character || "(\uC5C6\uC74C)";
|
|
626
|
+
const m = /^verify\.(typecheck|lint|test|e2e)\.(cmd|cwd|env)$/.exec(key);
|
|
627
|
+
if (!m?.[1] || !m[2]) return "";
|
|
628
|
+
const entry = config.verify[m[1]];
|
|
629
|
+
if (!entry) return "(\uC5C6\uC74C)";
|
|
630
|
+
if (m[2] === "cmd") return entry.cmd;
|
|
631
|
+
if (m[2] === "cwd") return entry.cwd ?? "(\uC5C6\uC74C)";
|
|
632
|
+
return entry.env ? JSON.stringify(entry.env) : "(\uC5C6\uC74C)";
|
|
633
|
+
};
|
|
634
|
+
const keyWidth = Math.max(...SETTABLE_KEYS.map((k) => k.length)) + 2;
|
|
635
|
+
const out = ["", " \uC124\uC815 \uAC00\uB2A5\uD55C \uD0A4", ""];
|
|
636
|
+
for (const key of SETTABLE_KEYS) {
|
|
637
|
+
out.push(` ${key.padEnd(keyWidth, " ")}${color.dim(currentOf(key))}`);
|
|
638
|
+
}
|
|
639
|
+
out.push("");
|
|
640
|
+
out.push(` ${color.dim('\uC608: awl config set verify.lint.cmd "biome check ."')}`);
|
|
641
|
+
return out.join("\n");
|
|
642
|
+
}
|
|
643
|
+
async function runConfigSet(key, value, opts) {
|
|
644
|
+
const projectRoot = resolveProjectRoot();
|
|
645
|
+
if (!projectRoot) {
|
|
646
|
+
process.stderr.write(
|
|
647
|
+
`
|
|
648
|
+
${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.
|
|
649
|
+
`
|
|
650
|
+
);
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
const loaded = loadConfig(projectRoot);
|
|
654
|
+
if (!loaded.config) {
|
|
655
|
+
process.stderr.write(
|
|
656
|
+
`
|
|
657
|
+
${signal(caps(), "error")} config.json \uC5D0 \uBB38\uC81C\uAC00 \uC788\uC5B4 \uC218\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:
|
|
658
|
+
`
|
|
659
|
+
);
|
|
660
|
+
for (const e of loaded.errors) {
|
|
661
|
+
process.stderr.write(` - ${e}
|
|
662
|
+
`);
|
|
663
|
+
}
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
const config = loaded.config;
|
|
667
|
+
if (!key) {
|
|
668
|
+
process.stdout.write(`${renderSettableKeys(config, caps())}
|
|
669
|
+
`);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const parsed = parseConfigKey(key);
|
|
673
|
+
if (!parsed) {
|
|
674
|
+
process.stderr.write(
|
|
675
|
+
`
|
|
676
|
+
${signal(caps(), "error")} \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD0A4\uC785\uB2C8\uB2E4: ${key}
|
|
677
|
+
|
|
678
|
+
\uC124\uC815 \uAC00\uB2A5\uD55C \uD0A4:
|
|
679
|
+
`
|
|
680
|
+
);
|
|
681
|
+
for (const k of SETTABLE_KEYS) {
|
|
682
|
+
process.stderr.write(` ${k}
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
if (value === void 0) {
|
|
688
|
+
process.stdout.write(`${renderSettableKeys(config, caps())}
|
|
689
|
+
`);
|
|
690
|
+
process.stdout.write(`
|
|
691
|
+
\uAC12\uC744 \uC8FC\uC138\uC694: awl config set ${key} <\uAC12>
|
|
692
|
+
`);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const outcome = await applyConfigValue(config, projectRoot, parsed, value, {
|
|
696
|
+
force: opts.force
|
|
697
|
+
});
|
|
698
|
+
if (!outcome.ok) {
|
|
699
|
+
process.stderr.write(`
|
|
700
|
+
${signal(caps(), "error")} ${outcome.message}
|
|
701
|
+
`);
|
|
702
|
+
process.exit(1);
|
|
703
|
+
}
|
|
704
|
+
writeConfigFile(projectRoot, config);
|
|
705
|
+
process.stdout.write(` \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4: ${outcome.message}
|
|
706
|
+
`);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export {
|
|
710
|
+
CommandNotFoundError,
|
|
711
|
+
tokenize,
|
|
712
|
+
run,
|
|
713
|
+
VERIFY_ORDER,
|
|
714
|
+
resolveProjectRoot,
|
|
715
|
+
validateConfig,
|
|
716
|
+
loadConfig,
|
|
717
|
+
requireConfig,
|
|
718
|
+
parseVerifyValue,
|
|
719
|
+
KNOWN_LANGUAGES,
|
|
720
|
+
KNOWN_NAMING_CONVENTIONS,
|
|
721
|
+
SETTABLE_KEYS,
|
|
722
|
+
parseConfigKey,
|
|
723
|
+
applyConfigValue,
|
|
724
|
+
interactiveEditMenu,
|
|
725
|
+
runConfig,
|
|
726
|
+
runConfigSet
|
|
727
|
+
};
|