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,693 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCriterion,
|
|
3
|
+
loadState,
|
|
4
|
+
writeState
|
|
5
|
+
} from "./chunk-4OCSYHYB.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveProjectRoot,
|
|
8
|
+
run
|
|
9
|
+
} from "./chunk-UOPWVM2H.js";
|
|
10
|
+
import {
|
|
11
|
+
caps,
|
|
12
|
+
card,
|
|
13
|
+
makeColors,
|
|
14
|
+
recordsDir,
|
|
15
|
+
signal
|
|
16
|
+
} from "./chunk-7SYRDDTX.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/record.ts
|
|
19
|
+
import crypto from "crypto";
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
var NARRATIVE_KINDS = [
|
|
23
|
+
"gate-caught",
|
|
24
|
+
"reviewer-caught",
|
|
25
|
+
"spike-prevented",
|
|
26
|
+
"blocked-discarded",
|
|
27
|
+
"tool-failed"
|
|
28
|
+
];
|
|
29
|
+
var GATE1_DECISIONS = ["approved", "modified", "rejected", "split"];
|
|
30
|
+
var GATE2_DECISIONS = ["approved", "more-work", "abandoned"];
|
|
31
|
+
var AWL_FEEDBACK_AREAS = [
|
|
32
|
+
"commit",
|
|
33
|
+
"review",
|
|
34
|
+
"gate",
|
|
35
|
+
"verify",
|
|
36
|
+
"state",
|
|
37
|
+
"init",
|
|
38
|
+
"cli",
|
|
39
|
+
"\uAE30\uD0C0"
|
|
40
|
+
];
|
|
41
|
+
var AWL_FEEDBACK_SEVERITIES = ["high", "medium", "low"];
|
|
42
|
+
var REFACTOR_KINDS = [
|
|
43
|
+
"split",
|
|
44
|
+
"dedup",
|
|
45
|
+
"abstraction",
|
|
46
|
+
"rename",
|
|
47
|
+
"inline",
|
|
48
|
+
"\uAE30\uD0C0"
|
|
49
|
+
];
|
|
50
|
+
var DEFER_SEVERITIES = ["high", "medium", "low"];
|
|
51
|
+
function shouldDefer(severity, threshold = "high") {
|
|
52
|
+
const levels = DEFER_SEVERITIES;
|
|
53
|
+
const sevRank = levels.indexOf(severity);
|
|
54
|
+
if (sevRank === -1) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const thrRank = levels.indexOf(threshold);
|
|
58
|
+
const effectiveThr = thrRank === -1 ? 0 : thrRank;
|
|
59
|
+
return sevRank <= effectiveThr;
|
|
60
|
+
}
|
|
61
|
+
var SCHEMAS = {
|
|
62
|
+
audit: { required: ["scope", "findings"], arrays: ["findings"] },
|
|
63
|
+
spike: { required: ["question", "found"] },
|
|
64
|
+
criteria: { required: ["items"], arrays: ["items"] },
|
|
65
|
+
// WI-U: why/how/alternatives 는 diff 크기(diffTier)에 따라 조건부로 요구된다
|
|
66
|
+
// (buildRecord 의 attempt 전용 분기가 처리). what/result 만 무조건 필수.
|
|
67
|
+
// result:'verified' 는 코드 변경 없이 확인만 한 가드/검증형 완료조건 — 직전 커밋을
|
|
68
|
+
// 재지 않고 why/how 를 면제한다(what 만으로 통과, 피드백 F-3).
|
|
69
|
+
attempt: { required: ["what", "result"] },
|
|
70
|
+
blocked: { required: ["what", "why", "tried", "lesson"], arrays: ["tried"] },
|
|
71
|
+
// WI-S: target/verdict(이분법) 를 reviewId/criteria/findings/cheatingDetected/
|
|
72
|
+
// verifyPassedBefore 로 전면 교체 — target≈criteria, verdict≈findings.length 로
|
|
73
|
+
// 정보 손실 없이 표현되므로 예전 필드는 없앤다(과거 기록은 append-only 로 그대로
|
|
74
|
+
// 유효하게 남는다, D-33 원칙). criteria 만 비어있지 않은 배열을 강제한다 —
|
|
75
|
+
// findings/cheatingDetected 는 존재는 필수지만 빈 배열(지적/부정행위 없음)도
|
|
76
|
+
// 정당한 결과라 비어있어도 통과한다.
|
|
77
|
+
review: {
|
|
78
|
+
required: ["reviewId", "criteria", "findings", "cheatingDetected", "verifyPassedBefore"],
|
|
79
|
+
arrays: ["criteria"],
|
|
80
|
+
arraysAllowEmpty: ["findings", "cheatingDetected"]
|
|
81
|
+
},
|
|
82
|
+
decision: { required: ["question", "decision", "rationale"] },
|
|
83
|
+
"gotcha-applied": { required: ["gotchaId", "what"] },
|
|
84
|
+
"gotcha-missed": { required: ["gotchaId", "what", "why"] },
|
|
85
|
+
narrative: { required: ["kind", "counterfactual"] },
|
|
86
|
+
gate: { required: ["gate", "decision", "presentedCriteria"], arrays: ["presentedCriteria"] },
|
|
87
|
+
clarify: { required: ["questions"], arrays: ["questions"] },
|
|
88
|
+
// awl-feedback(0.6.x): awl 도구 자체가 아팠던 점. gotcha(작업 코드 교훈)와 다른
|
|
89
|
+
// 종류다 — records/ 에 쌓이고 gotcha 로 승격되지 않는다. area 가 모으기의 키.
|
|
90
|
+
// suggestion 은 선택(개선 아이디어, 강제 아님 — 번역은 사람 몫).
|
|
91
|
+
"awl-feedback": { required: ["area", "what", "impact", "severity"] },
|
|
92
|
+
// 자율 통과를 보류하고 사람에게 최종 문의할 중요 항목(보류 큐).
|
|
93
|
+
// recommendation(자율로 택했을 권장 결정)/gate(어느 게이트)/addresses 는 선택(D-15).
|
|
94
|
+
defer: { required: ["severity", "what", "why"] },
|
|
95
|
+
// 반복 루프 리팩토링 체크포인트에서 실제 리팩토링이 일어났을 때 남긴다
|
|
96
|
+
// (loop-refactor-checkpoint). kind 가 성격, what 이 무엇을 정리했나.
|
|
97
|
+
refactor: { required: ["what", "kind"] }
|
|
98
|
+
};
|
|
99
|
+
var RECORD_TYPES = Object.keys(SCHEMAS);
|
|
100
|
+
var BANNED_QUALITATIVE_WORDS = [
|
|
101
|
+
"\uC800\uC704\uD5D8",
|
|
102
|
+
"\uC8FC\uC694\uD55C",
|
|
103
|
+
"\uC801\uC808\uD55C",
|
|
104
|
+
"\uAC00\uB2A5\uD55C \uB9CC\uD07C",
|
|
105
|
+
"\uD544\uC694\uC2DC"
|
|
106
|
+
];
|
|
107
|
+
function isHangulSyllable(ch) {
|
|
108
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
109
|
+
return code >= 44032 && code <= 55203;
|
|
110
|
+
}
|
|
111
|
+
function includesBannedWord(text, word) {
|
|
112
|
+
let idx = text.indexOf(word);
|
|
113
|
+
while (idx !== -1) {
|
|
114
|
+
const before = idx > 0 ? text[idx - 1] : void 0;
|
|
115
|
+
const after = idx + word.length < text.length ? text[idx + word.length] : void 0;
|
|
116
|
+
const attachedToHangul = before !== void 0 && isHangulSyllable(before) || after !== void 0 && isHangulSyllable(after);
|
|
117
|
+
if (!attachedToHangul) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
idx = text.indexOf(word, idx + 1);
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function newRecordId() {
|
|
125
|
+
return `rec_${crypto.randomBytes(9).toString("hex")}`;
|
|
126
|
+
}
|
|
127
|
+
function buildRecord(type, data, defaults) {
|
|
128
|
+
const missing = [];
|
|
129
|
+
const project = typeof data.project === "string" && data.project.trim() !== "" && data.project || defaults.project;
|
|
130
|
+
if (!project) {
|
|
131
|
+
missing.push("project");
|
|
132
|
+
}
|
|
133
|
+
const workitem = typeof data.workitem === "string" && data.workitem.trim() !== "" && data.workitem || defaults.workitem;
|
|
134
|
+
const schema = SCHEMAS[type];
|
|
135
|
+
for (const field of schema.required) {
|
|
136
|
+
const v = data[field];
|
|
137
|
+
if (v === void 0 || v === null || v === "") {
|
|
138
|
+
missing.push(field);
|
|
139
|
+
} else if (schema.arrays?.includes(field) && (!Array.isArray(v) || v.length === 0)) {
|
|
140
|
+
missing.push(`${field} (\uBE44\uC5B4\uC788\uC9C0 \uC54A\uC740 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD568)`);
|
|
141
|
+
} else if (schema.arraysAllowEmpty?.includes(field) && !Array.isArray(v)) {
|
|
142
|
+
missing.push(`${field} (\uBC30\uC5F4\uC774\uC5B4\uC57C \uD568)`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (type === "criteria" && Array.isArray(data.items)) {
|
|
146
|
+
for (const item of data.items) {
|
|
147
|
+
const text = JSON.stringify(item);
|
|
148
|
+
for (const word of BANNED_QUALITATIVE_WORDS) {
|
|
149
|
+
if (includesBannedWord(text, word)) {
|
|
150
|
+
missing.push(
|
|
151
|
+
`items(${String(item?.id ?? "?")}) \uC5D0 \uAE08\uC9C0\uB41C \uC9C8\uC801 \uD45C\uD604 "${word}" \u2014 \uC5F4\uAC70 \uAC00\uB2A5\uD558\uAC70\uB098 \uC218\uCE58\uD654 \uAC00\uB2A5\uD558\uAC8C \uB2E4\uC2DC \uC4F0\uC138\uC694`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (type === "attempt") {
|
|
158
|
+
const tier = typeof data.diffTier === "string" ? data.diffTier : void 0;
|
|
159
|
+
const isFailed = data.result === "failed";
|
|
160
|
+
const isVerified = data.result === "verified";
|
|
161
|
+
const requiresFullDetail = isFailed || !isVerified && tier !== "minimal";
|
|
162
|
+
if (requiresFullDetail) {
|
|
163
|
+
for (const field of ["why", "how"]) {
|
|
164
|
+
const v = data[field];
|
|
165
|
+
if (v === void 0 || v === null || v === "") {
|
|
166
|
+
missing.push(field);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (tier === "detailed") {
|
|
171
|
+
const alt = data.alternatives;
|
|
172
|
+
if (!Array.isArray(alt) || alt.length === 0) {
|
|
173
|
+
missing.push(
|
|
174
|
+
"alternatives (\uBE44\uC5B4\uC788\uC9C0 \uC54A\uC740 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD568 \u2014 diff \uAC00 \uD06C\uBA74 \uC124\uACC4 \uB300\uC548\uC744 \uB0A8\uACA8\uC57C \uD569\uB2C8\uB2E4)"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (type === "decision" && data.performanceSensitive === true) {
|
|
180
|
+
const alt = data.alternatives;
|
|
181
|
+
if (!Array.isArray(alt) || alt.length === 0) {
|
|
182
|
+
missing.push(
|
|
183
|
+
"alternatives (\uBE44\uC5B4\uC788\uC9C0 \uC54A\uC740 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD568 \u2014 performanceSensitive:true \uC778 \uACB0\uC815\uC740 \uB300\uC548\uC744 \uB0A8\uACA8\uC57C \uD569\uB2C8\uB2E4)"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (type === "narrative") {
|
|
188
|
+
const kindMissing = data.kind === void 0 || data.kind === null || data.kind === "";
|
|
189
|
+
if (!kindMissing && !NARRATIVE_KINDS.includes(data.kind)) {
|
|
190
|
+
missing.push(`kind (\uB2E4\uC74C \uC911 \uD558\uB098\uC5EC\uC57C \uD568: ${NARRATIVE_KINDS.join(", ")})`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (type === "gate") {
|
|
194
|
+
const gateMissing = data.gate === void 0 || data.gate === null || data.gate === "";
|
|
195
|
+
if (!gateMissing && data.gate !== 1 && data.gate !== 2) {
|
|
196
|
+
missing.push("gate (1 \uB610\uB294 2\uC5EC\uC57C \uD568)");
|
|
197
|
+
}
|
|
198
|
+
const decisionMissing = data.decision === void 0 || data.decision === null || data.decision === "";
|
|
199
|
+
if (!decisionMissing && (data.gate === 1 || data.gate === 2)) {
|
|
200
|
+
const allowed = data.gate === 1 ? GATE1_DECISIONS : GATE2_DECISIONS;
|
|
201
|
+
if (!allowed.includes(data.decision)) {
|
|
202
|
+
missing.push(
|
|
203
|
+
`decision (gate ${data.gate} \uC5D0\uC11C\uB294 \uB2E4\uC74C \uC911 \uD558\uB098\uC5EC\uC57C \uD568: ${allowed.join(", ")})`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (type === "awl-feedback") {
|
|
209
|
+
const areaMissing = data.area === void 0 || data.area === null || data.area === "";
|
|
210
|
+
if (!areaMissing && !AWL_FEEDBACK_AREAS.includes(data.area)) {
|
|
211
|
+
missing.push(`area (\uB2E4\uC74C \uC911 \uD558\uB098\uC5EC\uC57C \uD568: ${AWL_FEEDBACK_AREAS.join(", ")})`);
|
|
212
|
+
}
|
|
213
|
+
const sevMissing = data.severity === void 0 || data.severity === null || data.severity === "";
|
|
214
|
+
if (!sevMissing && !AWL_FEEDBACK_SEVERITIES.includes(data.severity)) {
|
|
215
|
+
missing.push(`severity (\uB2E4\uC74C \uC911 \uD558\uB098\uC5EC\uC57C \uD568: ${AWL_FEEDBACK_SEVERITIES.join(", ")})`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (type === "defer") {
|
|
219
|
+
const sevMissing = data.severity === void 0 || data.severity === null || data.severity === "";
|
|
220
|
+
if (!sevMissing && !DEFER_SEVERITIES.includes(data.severity)) {
|
|
221
|
+
missing.push(`severity (\uB2E4\uC74C \uC911 \uD558\uB098\uC5EC\uC57C \uD568: ${DEFER_SEVERITIES.join(", ")})`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (type === "refactor") {
|
|
225
|
+
const kindMissing = data.kind === void 0 || data.kind === null || data.kind === "";
|
|
226
|
+
if (!kindMissing && !REFACTOR_KINDS.includes(data.kind)) {
|
|
227
|
+
missing.push(`kind (\uB2E4\uC74C \uC911 \uD558\uB098\uC5EC\uC57C \uD568: ${REFACTOR_KINDS.join(", ")})`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (missing.length > 0) {
|
|
231
|
+
return { missing };
|
|
232
|
+
}
|
|
233
|
+
const { workitem: _dataWorkitem, ...dataWithoutWorkitem } = data;
|
|
234
|
+
const record = {
|
|
235
|
+
id: defaults.id,
|
|
236
|
+
at: defaults.at,
|
|
237
|
+
project,
|
|
238
|
+
type,
|
|
239
|
+
...dataWithoutWorkitem
|
|
240
|
+
};
|
|
241
|
+
record.project = project;
|
|
242
|
+
record.type = type;
|
|
243
|
+
record.id = defaults.id;
|
|
244
|
+
record.at = defaults.at;
|
|
245
|
+
if (workitem) {
|
|
246
|
+
record.workitem = workitem;
|
|
247
|
+
}
|
|
248
|
+
return { record, missing: [] };
|
|
249
|
+
}
|
|
250
|
+
function resolveBlockedBaseline(data, state) {
|
|
251
|
+
const focus = typeof data.criterion === "string" && data.criterion || (typeof state.currentFocus === "string" ? state.currentFocus : void 0);
|
|
252
|
+
if (!focus) {
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
const crit = getCriterion(state, focus);
|
|
256
|
+
return crit && typeof crit.baseline === "string" ? crit.baseline : void 0;
|
|
257
|
+
}
|
|
258
|
+
function detailTierFor(size) {
|
|
259
|
+
if (size.files >= 3 || size.lines >= 50) {
|
|
260
|
+
return "detailed";
|
|
261
|
+
}
|
|
262
|
+
if (size.files <= 1 && size.lines < 10) {
|
|
263
|
+
return "minimal";
|
|
264
|
+
}
|
|
265
|
+
return "brief";
|
|
266
|
+
}
|
|
267
|
+
async function measureDiffSize(cwd, args) {
|
|
268
|
+
const r = await run({ cmd: "git", args, cwd, timeoutMs: 1e4 });
|
|
269
|
+
if (r.exitCode !== 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const rows = r.stdout.split("\n").filter((l) => l.trim() !== "");
|
|
273
|
+
let lines = 0;
|
|
274
|
+
for (const row of rows) {
|
|
275
|
+
const [add, del] = row.split(" ");
|
|
276
|
+
lines += (Number(add) || 0) + (Number(del) || 0);
|
|
277
|
+
}
|
|
278
|
+
return { files: rows.length, lines };
|
|
279
|
+
}
|
|
280
|
+
function computeCoverage(auditRecords, criteria, criteriaRecords = []) {
|
|
281
|
+
const findingIds = /* @__PURE__ */ new Set();
|
|
282
|
+
for (const r of auditRecords) {
|
|
283
|
+
const findings = Array.isArray(r.findings) ? r.findings : [];
|
|
284
|
+
for (const f of findings) {
|
|
285
|
+
if (f && typeof f === "object" && typeof f.id === "string") {
|
|
286
|
+
findingIds.add(f.id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const addressedRefs = /* @__PURE__ */ new Set();
|
|
291
|
+
const stateHasAddresses = /* @__PURE__ */ new Set();
|
|
292
|
+
for (const c of criteria) {
|
|
293
|
+
if (Array.isArray(c.addresses)) {
|
|
294
|
+
stateHasAddresses.add(String(c.id));
|
|
295
|
+
for (const a of c.addresses) {
|
|
296
|
+
if (typeof a === "string") {
|
|
297
|
+
addressedRefs.add(a);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (const rec of criteriaRecords) {
|
|
303
|
+
const items = Array.isArray(rec.items) ? rec.items : [];
|
|
304
|
+
for (const item of items) {
|
|
305
|
+
const id = item && typeof item === "object" ? item.id : void 0;
|
|
306
|
+
if (typeof id !== "string" || stateHasAddresses.has(id)) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const addresses = item.addresses;
|
|
310
|
+
if (Array.isArray(addresses)) {
|
|
311
|
+
for (const a of addresses) {
|
|
312
|
+
if (typeof a === "string") {
|
|
313
|
+
addressedRefs.add(a);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const auditFindingIds = [...findingIds];
|
|
320
|
+
const addressedIds = auditFindingIds.filter((id) => addressedRefs.has(id));
|
|
321
|
+
const excludedIds = auditFindingIds.filter((id) => !addressedRefs.has(id));
|
|
322
|
+
return { auditFindingIds, addressedIds, excludedIds };
|
|
323
|
+
}
|
|
324
|
+
function monthFile(at) {
|
|
325
|
+
const month = at.slice(0, 7);
|
|
326
|
+
return path.join(recordsDir(), `${month}.jsonl`);
|
|
327
|
+
}
|
|
328
|
+
function appendRecord(record) {
|
|
329
|
+
const file = monthFile(String(record.at));
|
|
330
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
331
|
+
fs.appendFileSync(file, `${JSON.stringify(record)}
|
|
332
|
+
`);
|
|
333
|
+
return file;
|
|
334
|
+
}
|
|
335
|
+
async function captureDiff(id, at, cwd) {
|
|
336
|
+
const r = await run({ cmd: "git", args: ["diff", "HEAD"], cwd, timeoutMs: 1e4 });
|
|
337
|
+
if (r.exitCode !== 0 && r.stdout.trim() === "") {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const diffsDir = path.join(recordsDir(), "diffs");
|
|
341
|
+
fs.mkdirSync(diffsDir, { recursive: true });
|
|
342
|
+
const name = `${at.slice(0, 10)}-${id}.patch`;
|
|
343
|
+
fs.writeFileSync(path.join(diffsDir, name), r.stdout);
|
|
344
|
+
return path.join("diffs", name);
|
|
345
|
+
}
|
|
346
|
+
function selectMonthFiles(files, filter = {}) {
|
|
347
|
+
const jsonl = files.filter((f) => f.endsWith(".jsonl"));
|
|
348
|
+
const monthOf = (f) => f.slice(0, 7);
|
|
349
|
+
if (Array.isArray(filter.months)) {
|
|
350
|
+
const set = new Set(filter.months);
|
|
351
|
+
return jsonl.filter((f) => set.has(monthOf(f)));
|
|
352
|
+
}
|
|
353
|
+
if (filter.from !== void 0 || filter.to !== void 0) {
|
|
354
|
+
const from = filter.from ?? "0000-00";
|
|
355
|
+
const to = filter.to ?? "9999-99";
|
|
356
|
+
return jsonl.filter((f) => {
|
|
357
|
+
const m = monthOf(f);
|
|
358
|
+
return m >= from && m <= to;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return jsonl;
|
|
362
|
+
}
|
|
363
|
+
function readRecords(filter = {}) {
|
|
364
|
+
const dir = recordsDir();
|
|
365
|
+
let files;
|
|
366
|
+
try {
|
|
367
|
+
files = selectMonthFiles(fs.readdirSync(dir), filter);
|
|
368
|
+
} catch {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
const records = [];
|
|
372
|
+
for (const f of files.sort()) {
|
|
373
|
+
let text;
|
|
374
|
+
try {
|
|
375
|
+
text = fs.readFileSync(path.join(dir, f), "utf8");
|
|
376
|
+
} catch {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
for (const line of text.split("\n")) {
|
|
380
|
+
if (line.trim() === "") {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
records.push(JSON.parse(line));
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const filtered = records.filter((r) => {
|
|
390
|
+
if (filter.type && r.type !== filter.type) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
if (filter.workitem && r.workitem !== filter.workitem) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
});
|
|
398
|
+
filtered.sort((a, b) => String(b.at).localeCompare(String(a.at)));
|
|
399
|
+
return filtered;
|
|
400
|
+
}
|
|
401
|
+
function collectDeferred(records) {
|
|
402
|
+
const levels = DEFER_SEVERITIES;
|
|
403
|
+
const rank = (s) => {
|
|
404
|
+
const i = levels.indexOf(s);
|
|
405
|
+
return i === -1 ? levels.length : i;
|
|
406
|
+
};
|
|
407
|
+
return records.filter((r) => r.type === "defer").map((r) => ({
|
|
408
|
+
severity: String(r.severity ?? ""),
|
|
409
|
+
what: String(r.what ?? ""),
|
|
410
|
+
why: String(r.why ?? ""),
|
|
411
|
+
recommendation: typeof r.recommendation === "string" ? r.recommendation : void 0,
|
|
412
|
+
gate: r.gate,
|
|
413
|
+
addresses: r.addresses,
|
|
414
|
+
at: String(r.at ?? "")
|
|
415
|
+
})).sort((a, b) => {
|
|
416
|
+
const d = rank(a.severity) - rank(b.severity);
|
|
417
|
+
return d !== 0 ? d : b.at.localeCompare(a.at);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
function renderDeferSummary(items) {
|
|
421
|
+
if (items.length === 0) {
|
|
422
|
+
return "\uBCF4\uB958 \uD050\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4(\uC0AC\uB78C \uD655\uC778\uC774 \uD544\uC694\uD55C \uC911\uC694 \uD56D\uBAA9 \uC5C6\uC74C).";
|
|
423
|
+
}
|
|
424
|
+
const lines = [`\uBCF4\uB958 ${items.length}\uAC74 \u2014 \uC0AC\uB78C \uCD5C\uC885 \uD655\uC778 \uD544\uC694:`];
|
|
425
|
+
for (const it of items) {
|
|
426
|
+
lines.push(` [${it.severity}] ${it.what}`);
|
|
427
|
+
lines.push(` \uC65C \uC911\uC694: ${it.why}`);
|
|
428
|
+
if (it.recommendation !== void 0) {
|
|
429
|
+
lines.push(` \uAD8C\uC7A5(\uC790\uC728 \uC2DC): ${it.recommendation}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return lines.join("\n");
|
|
433
|
+
}
|
|
434
|
+
function runDeferSummary(opts) {
|
|
435
|
+
const projectRoot = resolveProjectRoot();
|
|
436
|
+
let workitem = opts.workitem;
|
|
437
|
+
if (workitem === void 0 && projectRoot) {
|
|
438
|
+
const state = loadState(projectRoot);
|
|
439
|
+
workitem = typeof state.workitem === "string" && state.workitem.trim() !== "" ? state.workitem : void 0;
|
|
440
|
+
}
|
|
441
|
+
const items = collectDeferred(readRecords({ type: "defer", workitem }));
|
|
442
|
+
if (opts.json === true) {
|
|
443
|
+
process.stdout.write(`${JSON.stringify({ workitem, count: items.length, items }, null, 2)}
|
|
444
|
+
`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
process.stdout.write(`${renderDeferSummary(items)}
|
|
448
|
+
`);
|
|
449
|
+
}
|
|
450
|
+
function hasApprovedGate1(workitem) {
|
|
451
|
+
if (typeof workitem !== "string" || workitem === "") {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
return readRecords({ type: "gate", workitem }).some(
|
|
455
|
+
(r) => r.gate === 1 && r.decision === "approved"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
function summaryOf(r) {
|
|
459
|
+
if (r.type === "review" && typeof r.reviewId === "string") {
|
|
460
|
+
const findings = Array.isArray(r.findings) ? r.findings.length : 0;
|
|
461
|
+
const cheating = Array.isArray(r.cheatingDetected) ? r.cheatingDetected.length : 0;
|
|
462
|
+
const cheatingNote = cheating > 0 ? `, \uBD80\uC815\uD589\uC704 ${cheating}\uAC74` : "";
|
|
463
|
+
return `${r.reviewId} \u2014 findings ${findings}\uAC74${cheatingNote}`;
|
|
464
|
+
}
|
|
465
|
+
const cand = r.what ?? r.scope ?? r.question ?? r.target ?? r.decision ?? "(\uC694\uC57D \uC5C6\uC74C)";
|
|
466
|
+
return String(cand);
|
|
467
|
+
}
|
|
468
|
+
function renderRecords(records, c) {
|
|
469
|
+
const color = makeColors(c.color);
|
|
470
|
+
if (records.length === 0) {
|
|
471
|
+
return card("\uAE30\uB85D", ["\uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."], c);
|
|
472
|
+
}
|
|
473
|
+
const out = [];
|
|
474
|
+
for (const r of records) {
|
|
475
|
+
const type = String(r.type).padEnd(9, " ");
|
|
476
|
+
const wi = r.workitem ? `${String(r.workitem)} ` : "";
|
|
477
|
+
const date = String(r.at).slice(0, 10);
|
|
478
|
+
out.push(`${color.dim(date)} ${color.bold(type)} ${color.dim(wi)}${summaryOf(r)}`);
|
|
479
|
+
}
|
|
480
|
+
out.push("");
|
|
481
|
+
out.push(color.dim("\uC0C1\uC138\uB294 awl records --json \uB610\uB294 ~/.awl/records/ \uB97C \uBCF4\uC138\uC694."));
|
|
482
|
+
return card(`\uAE30\uB85D ${records.length}\uAC1C \xB7 \uCD5C\uADFC\uC21C`, out, c);
|
|
483
|
+
}
|
|
484
|
+
async function runRecord(type, opts) {
|
|
485
|
+
if (!RECORD_TYPES.includes(type)) {
|
|
486
|
+
process.stderr.write(
|
|
487
|
+
`
|
|
488
|
+
${signal(caps(), "error")} \uC54C \uC218 \uC5C6\uB294 \uAE30\uB85D \uD0C0\uC785: ${type}
|
|
489
|
+
\uAC00\uB2A5: ${RECORD_TYPES.join(", ")}
|
|
490
|
+
`
|
|
491
|
+
);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
let data = {};
|
|
495
|
+
try {
|
|
496
|
+
if (opts.file) {
|
|
497
|
+
data = JSON.parse(fs.readFileSync(opts.file, "utf8"));
|
|
498
|
+
} else if (opts.json) {
|
|
499
|
+
data = JSON.parse(opts.json);
|
|
500
|
+
}
|
|
501
|
+
} catch (e) {
|
|
502
|
+
process.stderr.write(
|
|
503
|
+
`
|
|
504
|
+
${signal(caps(), "error")} \uB370\uC774\uD130 JSON \uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${String(e)}
|
|
505
|
+
`
|
|
506
|
+
);
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
if (typeof data !== "object" || data === null) {
|
|
510
|
+
process.stderr.write(`
|
|
511
|
+
${signal(caps(), "error")} \uB370\uC774\uD130\uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.
|
|
512
|
+
`);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
const projectRoot = resolveProjectRoot();
|
|
516
|
+
let projectFromConfig;
|
|
517
|
+
let currentWorkitem;
|
|
518
|
+
let state = {};
|
|
519
|
+
if (projectRoot) {
|
|
520
|
+
projectFromConfig = loadProjectName(projectRoot);
|
|
521
|
+
state = loadState(projectRoot);
|
|
522
|
+
currentWorkitem = typeof state.workitem === "string" && state.workitem.trim() !== "" ? state.workitem : void 0;
|
|
523
|
+
}
|
|
524
|
+
const dataWorkitem = typeof data.workitem === "string" && data.workitem.trim() !== "" ? data.workitem : void 0;
|
|
525
|
+
const cliWorkitem = typeof opts.workitem === "string" && opts.workitem.trim() !== "" ? opts.workitem : void 0;
|
|
526
|
+
const defaultWorkitem = cliWorkitem ?? currentWorkitem;
|
|
527
|
+
if (!dataWorkitem && !defaultWorkitem) {
|
|
528
|
+
const hint = projectRoot ? "" : " (\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 awl init \uC744 \uC2E4\uD589\uD588\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.)";
|
|
529
|
+
process.stderr.write(
|
|
530
|
+
`
|
|
531
|
+
${signal(caps(), "error")} \uD65C\uC131 \uC6CC\uD06C\uC544\uC774\uD15C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.${hint} awl work new <id> [\uC124\uBA85] \uC73C\uB85C \uC2DC\uC791\uD558\uC138\uC694.
|
|
532
|
+
(\uC774 \uAE30\uB85D \uD558\uB098\uB9CC \uB2E4\uB978 \uC6CC\uD06C\uC544\uC774\uD15C\uC73C\uB85C \uB0A8\uAE30\uB824\uBA74 --workitem <id> \uB97C \uC4F0\uC138\uC694)
|
|
533
|
+
`
|
|
534
|
+
);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
const id = newRecordId();
|
|
538
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
539
|
+
if (opts.diff && type === "blocked" && projectRoot && data.diff === void 0) {
|
|
540
|
+
const rel = await captureDiff(id, at, projectRoot);
|
|
541
|
+
if (rel) {
|
|
542
|
+
data.diff = rel;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (type === "blocked" && projectRoot && data.baseline === void 0) {
|
|
546
|
+
const baseline = resolveBlockedBaseline(data, state);
|
|
547
|
+
if (baseline) {
|
|
548
|
+
data.baseline = baseline;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (type === "attempt" && projectRoot && data.diffTier === void 0 && data.result !== "verified") {
|
|
552
|
+
const diffArgs = data.result === "failed" ? ["diff", "--numstat", "HEAD"] : ["show", "--numstat", "--format=", "HEAD"];
|
|
553
|
+
const size = await measureDiffSize(projectRoot, diffArgs);
|
|
554
|
+
if (size) {
|
|
555
|
+
const tier = detailTierFor(size);
|
|
556
|
+
data.diffTier = tier;
|
|
557
|
+
const guidance = tier === "minimal" ? "what \uB9CC \uC788\uC73C\uBA74 \uB429\uB2C8\uB2E4." : tier === "detailed" ? "what/why/how \uC640 alternatives(\uC124\uACC4 \uB300\uC548)\uB97C \uCC44\uC6B0\uC138\uC694." : "what/why/how \uB97C \uCC44\uC6B0\uC138\uC694.";
|
|
558
|
+
process.stderr.write(`
|
|
559
|
+
\uC774 \uBCC0\uACBD\uC740 ${size.lines}\uC904/${size.files}\uD30C\uC77C\uC785\uB2C8\uB2E4. ${guidance}
|
|
560
|
+
`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const { record, missing } = buildRecord(type, data, {
|
|
564
|
+
project: projectFromConfig,
|
|
565
|
+
workitem: defaultWorkitem,
|
|
566
|
+
id,
|
|
567
|
+
at
|
|
568
|
+
});
|
|
569
|
+
if (!record) {
|
|
570
|
+
process.stderr.write(
|
|
571
|
+
`
|
|
572
|
+
${signal(caps(), "error")} \uAE30\uB85D\uC744 \uAC70\uBD80\uD588\uC2B5\uB2C8\uB2E4. \uBE60\uC9C4 \uD544\uC218 \uD544\uB4DC: ${missing.join(", ")}
|
|
573
|
+
`
|
|
574
|
+
);
|
|
575
|
+
process.stderr.write(
|
|
576
|
+
` ${type} \uC5D0 \uD544\uC694\uD55C \uD544\uB4DC: ${SCHEMAS[type].required.join(", ")}
|
|
577
|
+
`
|
|
578
|
+
);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
if (type === "gate" && data.gate === 1) {
|
|
582
|
+
const workitemForCheck = typeof record.workitem === "string" ? record.workitem : void 0;
|
|
583
|
+
if (workitemForCheck) {
|
|
584
|
+
const criteria = Array.isArray(state.criteria) ? state.criteria : [];
|
|
585
|
+
const auditRecords = readRecords({ type: "audit", workitem: workitemForCheck });
|
|
586
|
+
const criteriaRecords = readRecords({ type: "criteria", workitem: workitemForCheck });
|
|
587
|
+
const coverage = computeCoverage(auditRecords, criteria, criteriaRecords);
|
|
588
|
+
if (coverage.excludedIds.length > 0) {
|
|
589
|
+
const presented = Array.isArray(data.presentedExclusions) ? data.presentedExclusions : [];
|
|
590
|
+
const presentedIds = new Set(
|
|
591
|
+
presented.map((p) => typeof p === "string" ? p : p?.id).filter((id2) => typeof id2 === "string")
|
|
592
|
+
);
|
|
593
|
+
const uncovered = coverage.excludedIds.filter((id2) => !presentedIds.has(id2));
|
|
594
|
+
if (uncovered.length > 0) {
|
|
595
|
+
process.stderr.write(
|
|
596
|
+
`
|
|
597
|
+
${signal(caps(), "error")} \uAC8C\uC774\uD2B8 1 \uAE30\uB85D\uC744 \uAC70\uBD80\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC74C \uBC1C\uACAC\uC774 \uC644\uB8CC \uC870\uAC74\uC758 addresses \uC5D0\uB3C4, presentedExclusions \uC5D0\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4: ${uncovered.join(", ")}
|
|
598
|
+
\uC644\uB8CC \uC870\uAC74\uC5D0 addresses \uB85C \uC5F0\uACB0\uD558\uAC70\uB098, presentedExclusions \uC5D0 \uB2F4\uC544 \uC0AC\uB78C\uC5D0\uAC8C \uC81C\uC2DC\uD558\uC138\uC694.
|
|
599
|
+
`
|
|
600
|
+
);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const file = appendRecord(record);
|
|
607
|
+
if (projectRoot && type === "gate" && data.gate === 1 && data.decision === "approved") {
|
|
608
|
+
writeState(projectRoot, { ...state, phase: "loop", loop: "loop" });
|
|
609
|
+
}
|
|
610
|
+
if (type === "gate" && data.gate === 2) {
|
|
611
|
+
const passedCount = Array.isArray(state.criteria) ? state.criteria.filter((c) => c.status === "passed").length : 0;
|
|
612
|
+
const workitemForCheck = typeof record.workitem === "string" ? record.workitem : void 0;
|
|
613
|
+
if (passedCount >= 3 && workitemForCheck) {
|
|
614
|
+
const hasReview = readRecords({ type: "review", workitem: workitemForCheck }).length > 0;
|
|
615
|
+
if (!hasReview) {
|
|
616
|
+
process.stderr.write(
|
|
617
|
+
`
|
|
618
|
+
${signal(caps(), "warn")} \uC644\uB8CC \uC870\uAC74 ${passedCount}\uAC1C\uAC00 \uD1B5\uACFC\uD588\uC73C\uB098 \uB9AC\uBDF0 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.
|
|
619
|
+
\uB9AC\uBDF0\uB97C \uAC74\uB108\uB6F0\uC5C8\uC2B5\uB2C8\uAE4C?
|
|
620
|
+
`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const criteria = Array.isArray(state.criteria) ? state.criteria : [];
|
|
625
|
+
const allPassedFirstTry = criteria.length > 0 && criteria.every((c) => c.status === "passed" && (Number(c.attempts) || 0) === 0);
|
|
626
|
+
if (allPassedFirstTry) {
|
|
627
|
+
const auditRecords = workitemForCheck ? readRecords({ type: "audit", workitem: workitemForCheck }) : [];
|
|
628
|
+
const criteriaRecords = workitemForCheck ? readRecords({ type: "criteria", workitem: workitemForCheck }) : [];
|
|
629
|
+
const coverage = computeCoverage(auditRecords, criteria, criteriaRecords);
|
|
630
|
+
process.stderr.write(
|
|
631
|
+
`
|
|
632
|
+
\uC644\uB8CC \uC870\uAC74 ${criteria.length}\uAC1C \uC804\uBD80 1\uCC28 \uD1B5\uACFC. \uB9C9\uD798 0\uAC74.
|
|
633
|
+
\uC870\uC0AC\uC5D0\uC11C \uBC1C\uACAC\uD55C ${coverage.auditFindingIds.length}\uAC74 \uC911 ${coverage.addressedIds.length}\uAC74\uC744 \uB2E4\uB918\uC2B5\uB2C8\uB2E4.
|
|
634
|
+
\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uBD84\uD788 \uC57C\uC2EC\uCC3C\uC2B5\uB2C8\uAE4C?
|
|
635
|
+
`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
process.stdout.write(`${JSON.stringify({ id, at, file })}
|
|
640
|
+
`);
|
|
641
|
+
}
|
|
642
|
+
function loadProjectName(projectRoot) {
|
|
643
|
+
try {
|
|
644
|
+
const p = path.join(projectRoot, ".awl", "config.json");
|
|
645
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
646
|
+
return typeof j.project === "string" ? j.project : void 0;
|
|
647
|
+
} catch {
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function runRecords(opts) {
|
|
652
|
+
const records = readRecords({ type: opts.type, workitem: opts.workitem });
|
|
653
|
+
if (opts.json) {
|
|
654
|
+
process.stdout.write(`${JSON.stringify(records, null, 2)}
|
|
655
|
+
`);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
process.stdout.write(`${renderRecords(records, caps())}
|
|
659
|
+
`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export {
|
|
663
|
+
NARRATIVE_KINDS,
|
|
664
|
+
GATE1_DECISIONS,
|
|
665
|
+
GATE2_DECISIONS,
|
|
666
|
+
AWL_FEEDBACK_AREAS,
|
|
667
|
+
AWL_FEEDBACK_SEVERITIES,
|
|
668
|
+
REFACTOR_KINDS,
|
|
669
|
+
DEFER_SEVERITIES,
|
|
670
|
+
shouldDefer,
|
|
671
|
+
SCHEMAS,
|
|
672
|
+
RECORD_TYPES,
|
|
673
|
+
BANNED_QUALITATIVE_WORDS,
|
|
674
|
+
newRecordId,
|
|
675
|
+
buildRecord,
|
|
676
|
+
resolveBlockedBaseline,
|
|
677
|
+
detailTierFor,
|
|
678
|
+
measureDiffSize,
|
|
679
|
+
computeCoverage,
|
|
680
|
+
monthFile,
|
|
681
|
+
appendRecord,
|
|
682
|
+
captureDiff,
|
|
683
|
+
selectMonthFiles,
|
|
684
|
+
readRecords,
|
|
685
|
+
collectDeferred,
|
|
686
|
+
renderDeferSummary,
|
|
687
|
+
runDeferSummary,
|
|
688
|
+
hasApprovedGate1,
|
|
689
|
+
renderRecords,
|
|
690
|
+
runRecord,
|
|
691
|
+
loadProjectName,
|
|
692
|
+
runRecords
|
|
693
|
+
};
|