@tienne/gestalt 0.122.0 → 0.124.0
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/dist/package.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/skills/_shared/pr-base.md +169 -0
- package/dist/plugin/skills/_shared/proactive-routing.md +1 -1
- package/dist/plugin/skills/jira-create/SKILL.md +39 -4
- package/dist/plugin/skills/pr/SKILL.md +32 -12
- package/dist/plugin/skills/review/SKILL.md +94 -29
- package/dist/plugin/skills/review/references/approve-when-no-blocking.md +7 -0
- package/dist/plugin/skills/review/references/post-confirm.md +30 -0
- package/dist/plugin/skills/review/references/result-lines.md +32 -0
- package/dist/plugin/skills/review/references/user-profile.md +19 -0
- package/dist/plugin/skills/review-loop/SKILL.md +83 -20
- package/dist/plugin/skills/review-reply/SKILL.md +4 -0
- package/dist/plugin/skills/ship/SKILL.md +276 -29
- package/dist/src/architecture/ir-schema.d.ts +26 -26
- package/dist/src/cli/commands/profile.d.ts +13 -0
- package/dist/src/cli/commands/profile.d.ts.map +1 -0
- package/dist/src/cli/commands/profile.js +26 -0
- package/dist/src/cli/commands/profile.js.map +1 -0
- package/dist/src/cli/commands/ship.d.ts +35 -0
- package/dist/src/cli/commands/ship.d.ts.map +1 -0
- package/dist/src/cli/commands/ship.js +146 -0
- package/dist/src/cli/commands/ship.js.map +1 -0
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +52 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/config.d.ts +12 -12
- package/dist/src/core/types.d.ts +64 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/user-profile.d.ts +29 -0
- package/dist/src/core/user-profile.d.ts.map +1 -0
- package/dist/src/core/user-profile.js +97 -0
- package/dist/src/core/user-profile.js.map +1 -0
- package/dist/src/digest/blocks.d.ts +34 -34
- package/dist/src/digest/comments.d.ts +6 -6
- package/dist/src/digest/lens-schema.d.ts +2 -2
- package/dist/src/digest/lint-lens-context.d.ts +4 -4
- package/dist/src/digest/schema.d.ts +55 -55
- package/dist/src/lens/attach.d.ts +10 -10
- package/dist/src/mcp/schemas.d.ts +238 -225
- package/dist/src/mcp/schemas.d.ts.map +1 -1
- package/dist/src/mcp/schemas.js +86 -16
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/tools/review-passthrough.d.ts.map +1 -1
- package/dist/src/mcp/tools/review-passthrough.js +61 -2
- package/dist/src/mcp/tools/review-passthrough.js.map +1 -1
- package/dist/src/mcp/tools/status.d.ts +1 -1
- package/dist/src/research/schema.d.ts +66 -66
- package/dist/src/research/target.d.ts +6 -6
- package/dist/src/review/consensus-input.d.ts +11 -0
- package/dist/src/review/consensus-input.d.ts.map +1 -0
- package/dist/src/review/consensus-input.js +102 -0
- package/dist/src/review/consensus-input.js.map +1 -0
- package/dist/src/review/passthrough-engine.d.ts +9 -2
- package/dist/src/review/passthrough-engine.d.ts.map +1 -1
- package/dist/src/review/passthrough-engine.js +58 -10
- package/dist/src/review/passthrough-engine.js.map +1 -1
- package/dist/src/review/verification.d.ts +48 -7
- package/dist/src/review/verification.d.ts.map +1 -1
- package/dist/src/review/verification.js +109 -23
- package/dist/src/review/verification.js.map +1 -1
- package/dist/src/review/verifier-record.d.ts +55 -0
- package/dist/src/review/verifier-record.d.ts.map +1 -0
- package/dist/src/review/verifier-record.js +228 -0
- package/dist/src/review/verifier-record.js.map +1 -0
- package/dist/src/ship/checkpoint.d.ts +145 -0
- package/dist/src/ship/checkpoint.d.ts.map +1 -0
- package/dist/src/ship/checkpoint.js +282 -0
- package/dist/src/ship/checkpoint.js.map +1 -0
- package/dist/src/ship/ci.d.ts +110 -0
- package/dist/src/ship/ci.d.ts.map +1 -0
- package/dist/src/ship/ci.js +270 -0
- package/dist/src/ship/ci.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/skills/_shared/pr-base.md +169 -0
- package/plugin/skills/_shared/proactive-routing.md +1 -1
- package/plugin/skills/jira-create/SKILL.md +39 -4
- package/plugin/skills/pr/SKILL.md +32 -12
- package/plugin/skills/review/SKILL.md +94 -29
- package/plugin/skills/review/references/approve-when-no-blocking.md +7 -0
- package/plugin/skills/review/references/post-confirm.md +30 -0
- package/plugin/skills/review/references/result-lines.md +32 -0
- package/plugin/skills/review/references/user-profile.md +19 -0
- package/plugin/skills/review-loop/SKILL.md +83 -20
- package/plugin/skills/review-reply/SKILL.md +4 -0
- package/plugin/skills/ship/SKILL.md +276 -29
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ok, err } from '../core/result.js';
|
|
1
2
|
/**
|
|
2
3
|
* 제안 검증이 판정에서 뺄 수 없는 이슈인가.
|
|
3
4
|
*
|
|
@@ -6,11 +7,12 @@
|
|
|
6
7
|
* category에 `secur`나 `appsec`이 들어 있으면 자리와 구분자와 상관없이 security로 본다.
|
|
7
8
|
* 접두만 보면 `app-security`, 콜론만 보면 `security/xss`처럼 모양만 바꾼 이슈가 빠져나간다.
|
|
8
9
|
* 넓게 막는 쪽이 싸다.
|
|
10
|
+
* 비어 있는 칸은 금지 근거가 못 된다. 그 자리는 review_submit 원본 대조가 맡는다.
|
|
9
11
|
*/
|
|
10
12
|
export function isUndroppable(issue) {
|
|
11
13
|
if (issue.severity === 'critical')
|
|
12
14
|
return true;
|
|
13
|
-
return /secur|appsec/.test(issue.category.toLowerCase());
|
|
15
|
+
return /secur|appsec/.test((issue.category ?? '').toLowerCase());
|
|
14
16
|
}
|
|
15
17
|
/** 엔진 이벤트와 consensus 응답, 리포트가 같은 수를 쓰도록 한 자리에서 센다 */
|
|
16
18
|
export function verificationStats(consensus) {
|
|
@@ -33,51 +35,126 @@ export function verificationStats(consensus) {
|
|
|
33
35
|
return stats;
|
|
34
36
|
}
|
|
35
37
|
/** id와 reportedBy는 대소문자와 앞뒤 공백을 빼고 비교한다. 표기만 바꿔 대조를 피하지 못하게 */
|
|
36
|
-
function norm(value) {
|
|
38
|
+
export function norm(value) {
|
|
37
39
|
return value.trim().toLowerCase();
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
|
-
*
|
|
42
|
+
* 이슈의 review_submit 원본 후보를 찾는다. 앞에 올수록 더 확실한 후보다.
|
|
41
43
|
*
|
|
42
44
|
* 메인이 id를 고유화하는 꼴(`<reportedBy>:<원래 id>`)과 맞는 원본이 있으면 그것을 본다.
|
|
43
|
-
* 그 접두와
|
|
45
|
+
* 그 접두와 이슈의 reportedBy가 어긋나면 reportedBy 쪽 리뷰어의 같은 id 원본도 후보에 넣는다.
|
|
44
46
|
* 어느 쪽이 맞는지 가릴 수 없으면 둘 다 보는 게 안전해서다.
|
|
45
47
|
* 접두 꼴 원본이 없으면 id가 같은 원본을 reportedBy와 상관없이 전부 후보로 둔다. 병합하면서 대표
|
|
46
48
|
* reportedBy를 바꾸는 일이 흔해서 reportedBy까지 맞추라고 하면 그 자리로 금지를 피할 수 있다.
|
|
47
49
|
*/
|
|
48
|
-
function findOriginals(
|
|
50
|
+
export function findOriginals(issue, reviewResults) {
|
|
49
51
|
const all = reviewResults.flatMap((result) => result.issues);
|
|
50
|
-
const id = norm(
|
|
51
|
-
const by = norm(dropped.reportedBy);
|
|
52
|
+
const id = norm(issue.id);
|
|
52
53
|
const prefixed = all.filter((orig) => norm(`${orig.reportedBy}:${orig.id}`) === id);
|
|
53
54
|
if (prefixed.length === 0)
|
|
54
55
|
return all.filter((orig) => norm(orig.id) === id);
|
|
56
|
+
if (issue.reportedBy === undefined)
|
|
57
|
+
return prefixed;
|
|
58
|
+
const by = norm(issue.reportedBy);
|
|
55
59
|
if (prefixed.every((orig) => norm(orig.reportedBy) === by))
|
|
56
60
|
return prefixed;
|
|
57
61
|
const suffixes = new Set(prefixed.map((orig) => norm(orig.id)));
|
|
58
62
|
const sameReporter = all.filter((orig) => norm(orig.reportedBy) === by && suffixes.has(norm(orig.id)));
|
|
59
63
|
return [...prefixed, ...sameReporter];
|
|
60
64
|
}
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
/**
|
|
66
|
+
* 글자나 숫자가 하나라도 있는 값인가. 공백과 제로폭 문자, 한글 채움 문자, 기호만 있는 값은 이유나
|
|
67
|
+
* 증거로 치지 않는다. 검증기 응답이 비었는지 가를 때도 쓴다.
|
|
68
|
+
*
|
|
69
|
+
* 한글 채움 문자는 글자 범주(Lo)라서 정규식만으로는 내용으로 잡히는데 화면에는 안 보인다.
|
|
70
|
+
* 화면에 안 보이게 처리되는 글자(Default_Ignorable_Code_Point)를 먼저 걷어낸다.
|
|
71
|
+
*/
|
|
72
|
+
export function hasContent(text) {
|
|
73
|
+
return /[\p{L}\p{N}]/u.test(text.replace(/\p{Default_Ignorable_Code_Point}/gu, ''));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 적어 보낸 reportedBy를 쓸 수 있는 값으로 고른다. 글자나 숫자가 없는 값은 안 적은 것으로 본다.
|
|
77
|
+
* 제로폭 문자만 있는 값이 눈에 안 보이는 작성자로 게시되지 않게 hasContent로 가른다.
|
|
78
|
+
* mergedIssues와 droppedIssues가 같은 규칙으로 채우도록 두 쪽이 이 함수를 같이 쓴다.
|
|
79
|
+
*/
|
|
80
|
+
export function givenReportedBy(value) {
|
|
81
|
+
return value === undefined || !hasContent(value) ? undefined : value;
|
|
82
|
+
}
|
|
83
|
+
/** 뺀 이슈가 비워 보내도 원본에서 채우는 칸 */
|
|
84
|
+
const FILLABLE_DROP_FIELDS = ['severity', 'category', 'file', 'message', 'suggestion'];
|
|
85
|
+
function missingFieldsOf(issue) {
|
|
86
|
+
const fields = FILLABLE_DROP_FIELDS.filter((field) => issue[field] === undefined);
|
|
87
|
+
if (issue.reportedBy === undefined)
|
|
88
|
+
fields.push('reportedBy');
|
|
89
|
+
return fields;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 빈 칸을 채울 review_submit 원본 하나를 고른다.
|
|
93
|
+
*
|
|
94
|
+
* reportedBy를 적었으면 그 리뷰어의 원본만 쓴다. 비웠는데 후보가 여러 리뷰어에 걸쳐 있으면
|
|
95
|
+
* 고르지 않는다. 리뷰어마다 `issue-1` 같은 흔한 id를 쓰면 첫 후보로 채운 작성자와 본문이 다른
|
|
96
|
+
* 리뷰어 것이 된다. 금지 판단은 여기와 상관없이 후보 전체로 한다.
|
|
97
|
+
*/
|
|
98
|
+
export function pickFillSource(reportedBy, originals) {
|
|
99
|
+
if (reportedBy !== undefined) {
|
|
100
|
+
const by = norm(reportedBy);
|
|
101
|
+
return originals.find((orig) => norm(orig.reportedBy) === by);
|
|
102
|
+
}
|
|
103
|
+
const reporters = new Set(originals.map((orig) => norm(orig.reportedBy)));
|
|
104
|
+
return reporters.size > 1 ? 'ambiguous' : originals[0];
|
|
105
|
+
}
|
|
106
|
+
/** 같은 id의 원본이 여러 리뷰어에게 있어 채울 원본을 못 고를 때의 안내 */
|
|
107
|
+
export function ambiguousSourceMessage(id) {
|
|
108
|
+
return `${id}: 같은 id의 review_submit 원본이 여러 리뷰어에게 있어 어느 쪽으로 채울지 고를 수 없다. reportedBy를 직접 적는다`;
|
|
64
109
|
}
|
|
65
110
|
/**
|
|
66
|
-
*
|
|
111
|
+
* 빈 칸을 원본에서 채운다. 채울 수 없으면 거부 문구를 돌려준다.
|
|
112
|
+
* 적어 보낸 값은 그대로 둔다. 뺄 수 있는지는 이 결과가 아니라 원본 후보 전체로 판단한다.
|
|
113
|
+
*/
|
|
114
|
+
function fillDropped(issue, originals) {
|
|
115
|
+
const missing = missingFieldsOf(issue);
|
|
116
|
+
if (missing.length === 0)
|
|
117
|
+
return issue;
|
|
118
|
+
const reportedBy = issue.reportedBy;
|
|
119
|
+
const source = pickFillSource(reportedBy, originals);
|
|
120
|
+
if (source === 'ambiguous')
|
|
121
|
+
return ambiguousSourceMessage(issue.id);
|
|
122
|
+
if (!source) {
|
|
123
|
+
const owner = reportedBy === undefined ? '' : `${reportedBy}가 낸 `;
|
|
124
|
+
return `${issue.id}: ${missing.join(', ')} 칸이 비었는데 ${owner}같은 id의 review_submit 원본이 없어 채울 수 없다. 그 칸을 적거나 원본 id를 그대로 쓴다`;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...issue,
|
|
128
|
+
severity: issue.severity ?? source.severity,
|
|
129
|
+
category: issue.category ?? source.category,
|
|
130
|
+
file: issue.file ?? source.file,
|
|
131
|
+
message: issue.message ?? source.message,
|
|
132
|
+
suggestion: issue.suggestion ?? source.suggestion,
|
|
133
|
+
reportedBy: reportedBy ?? source.reportedBy,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* droppedIssues의 빈 칸을 원본에서 채우고 우회로로 쓰이지 않았는지 본다.
|
|
67
138
|
*
|
|
68
139
|
* 애매하면 거부한다. 후보 원본 중 하나라도 금지 대상이면 막는다.
|
|
69
|
-
* 병합하면서 severity나 category를
|
|
70
|
-
* 대조한다. 원본 후보가 하나도
|
|
71
|
-
*
|
|
140
|
+
* 병합하면서 severity나 category를 바꾸거나 비워 금지를 피하는 길도 막으려고 review_submit
|
|
141
|
+
* 원본과 대조한다. 원본 후보가 하나도 없으면 적어 보낸 값으로만 판단한다. 그때 빈 칸이 있으면
|
|
142
|
+
* 채울 근거가 없으니 거부한다. 메인이 id를 다른 꼴로 바꿨을 수도 있는데 칸을 다 적은 정상
|
|
143
|
+
* 호출까지 막으면 이 검사를 끄자는 쪽으로 가게 된다.
|
|
72
144
|
*/
|
|
73
|
-
export function
|
|
74
|
-
const dropped = consensus.droppedIssues
|
|
145
|
+
export function resolveDroppedIssues(consensus, reviewResults) {
|
|
146
|
+
const dropped = consensus.droppedIssues;
|
|
147
|
+
if (dropped === undefined)
|
|
148
|
+
return ok(undefined);
|
|
75
149
|
if (dropped.length === 0)
|
|
76
|
-
return
|
|
150
|
+
return ok([]);
|
|
77
151
|
const mergedIds = new Set(consensus.mergedIssues.map((issue) => norm(issue.id)));
|
|
78
152
|
const seenDropped = new Set();
|
|
79
153
|
const problems = [];
|
|
80
|
-
|
|
154
|
+
const resolved = [];
|
|
155
|
+
for (const raw of dropped) {
|
|
156
|
+
// 빈 reportedBy는 안 적은 것과 같게 다룬다. 원본 대조와 채움이 같은 값을 보게 여기서 한 번만 정한다
|
|
157
|
+
const issue = { ...raw, reportedBy: givenReportedBy(raw.reportedBy) };
|
|
81
158
|
if (seenDropped.has(norm(issue.id))) {
|
|
82
159
|
problems.push(`${issue.id}: droppedIssues 안에 같은 id가 두 번 있다`);
|
|
83
160
|
continue;
|
|
@@ -87,24 +164,33 @@ export function checkDroppedIssues(consensus, reviewResults) {
|
|
|
87
164
|
problems.push(`${issue.id}: security category이거나 critical이라 뺄 수 없다`);
|
|
88
165
|
continue;
|
|
89
166
|
}
|
|
90
|
-
const
|
|
167
|
+
const originals = findOriginals(issue, reviewResults);
|
|
168
|
+
const undroppableOriginal = originals.find(isUndroppable);
|
|
91
169
|
if (undroppableOriginal) {
|
|
92
170
|
problems.push(`${issue.id}: review_submit 원본이 ${undroppableOriginal.severity}/${undroppableOriginal.category}라 뺄 수 없다`);
|
|
93
171
|
continue;
|
|
94
172
|
}
|
|
173
|
+
// 금지 판단이 끝난 뒤에 채운다. 채우는 값은 위에서 이미 걸러진 원본에서만 온다
|
|
174
|
+
const filled = fillDropped(issue, originals);
|
|
175
|
+
if (typeof filled === 'string') {
|
|
176
|
+
problems.push(filled);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
95
179
|
if (!hasContent(issue.dropReason) || !hasContent(issue.dropEvidence)) {
|
|
96
180
|
problems.push(`${issue.id}: dropReason과 dropEvidence가 둘 다 있어야 뺄 수 있다`);
|
|
97
181
|
continue;
|
|
98
182
|
}
|
|
99
183
|
if (mergedIds.has(norm(issue.id))) {
|
|
100
184
|
problems.push(`${issue.id}: mergedIssues에도 같은 id가 있다. 어느 한쪽에만 둔다`);
|
|
185
|
+
continue;
|
|
101
186
|
}
|
|
187
|
+
resolved.push(filled);
|
|
102
188
|
}
|
|
103
189
|
if (problems.length === 0)
|
|
104
|
-
return
|
|
105
|
-
return [
|
|
106
|
-
'droppedIssues를 받을 수 없다.
|
|
190
|
+
return ok(resolved);
|
|
191
|
+
return err(new Error([
|
|
192
|
+
'droppedIssues를 받을 수 없다. 뺄 수 없는 이슈는 mergedIssues로 되돌려 keep이나 revise로 다시 부르고 나머지는 줄마다 적힌 대로 고친다.',
|
|
107
193
|
...problems.map((p) => `- ${p}`),
|
|
108
|
-
].join('\n');
|
|
194
|
+
].join('\n')));
|
|
109
195
|
}
|
|
110
196
|
//# sourceMappingURL=verification.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../../../src/review/verification.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../../../src/review/verification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReviewConsensusResult, ReviewSession, ReviewVerifierAtConsensus, ReviewVerifierRecord } from '../core/types.js';
|
|
2
|
+
import { type Result } from '../core/result.js';
|
|
3
|
+
/**
|
|
4
|
+
* 검증기 응답을 읽어 기록으로 만든다.
|
|
5
|
+
*
|
|
6
|
+
* 응답은 서브에이전트가 쓴 글이라 JSON 앞뒤에 말이 붙거나 코드펜스에 싸여 올 수 있다.
|
|
7
|
+
* 통째로, 펜스 안, 첫 `{`부터 마지막 `}`까지 순서로 읽어 보고 처음 읽힌 것을 쓴다.
|
|
8
|
+
* 하나도 못 읽으면 서브에이전트 실패로 본다. 원문은 남기지 않는다 — 판정과 개수만 쓰면
|
|
9
|
+
* 되고 응답 문자열이 로그나 경로로 흘러갈 자리를 만들 이유가 없다.
|
|
10
|
+
*
|
|
11
|
+
* 판정은 프로토타입 없는 객체에 id를 norm으로 맞춰 담는다. 응답의 issueId가 `__proto__`여도
|
|
12
|
+
* 판정으로 남고 `constructor` 같은 id를 찾을 때 상속된 값이 걸리지 않는다.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseVerifierOutput(raw: string, submitCount: number): ReviewVerifierRecord;
|
|
15
|
+
/** 이벤트 로그와 review_verify 응답이 같은 수를 싣도록 한 자리에서 센다 */
|
|
16
|
+
export declare function summarizeVerifierRecord(record: ReviewVerifierRecord): {
|
|
17
|
+
verdictCount: number;
|
|
18
|
+
skippedCount: number;
|
|
19
|
+
duplicateCount: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 코드펜스 기호와 줄바꿈이 바로 뒤따르는 언어 표시를 지운다. ```json만 있는 응답이 `json`이라는
|
|
23
|
+
* 글자 때문에 내용이 있는 것으로 읽히지 않게 빈 응답 판정에서만 쓴다. 줄바꿈이 안 따르는
|
|
24
|
+
* ```timeout 같은 한 단어는 내용이라 남긴다. 공용 hasContent는 그대로 둔다.
|
|
25
|
+
*/
|
|
26
|
+
export declare function stripCodeFences(text: string): string;
|
|
27
|
+
/** 합의를 받는 지금 검증 기록이 어떤 상태인지 */
|
|
28
|
+
export declare function verifierAtConsensus(session: ReviewSession): ReviewVerifierAtConsensus;
|
|
29
|
+
/**
|
|
30
|
+
* 합의가 검증 기록과 어긋나는지 본다. 어긋나면 문제를 줄마다 적어 돌려준다.
|
|
31
|
+
*
|
|
32
|
+
* 기록이 없거나 낡았으면 여기서는 보지 않는다. GitHub에 올리는 리뷰와 PR 없는 리뷰도
|
|
33
|
+
* 이 합의를 거치는데 거기까지 막으면 그 경로가 통째로 멈춘다. 게시를 막는 자리는
|
|
34
|
+
* review_publish다.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkConsensusAgainstVerifier(at: ReviewVerifierAtConsensus, consensus: Pick<ReviewConsensusResult, 'mergedIssues' | 'droppedIssues'>): string | null;
|
|
37
|
+
export type PublishVerifierStatus = 'ran' | 'failed' | 'no_drafts';
|
|
38
|
+
export interface PublishVerifierCheck {
|
|
39
|
+
status: PublishVerifierStatus;
|
|
40
|
+
/** ran일 때 합의 이슈(남긴 것과 뺀 것) 가운데 검증 기록에 판정이 있는 수와 전체 수 */
|
|
41
|
+
coverage?: {
|
|
42
|
+
judged: number;
|
|
43
|
+
total: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 게시 전에 3.7단계 기록을 확인한다.
|
|
48
|
+
*
|
|
49
|
+
* 건너뛰어도 되는 경우는 엔진이 세션으로 가른다. 이슈 초안이 하나도 없었거나(review_submit
|
|
50
|
+
* 이슈도 합의 이슈도 없다) 검증기 응답을 못 읽었을 때다. 초안 수를 합의만으로 세면 이슈를
|
|
51
|
+
* 전부 빼고 보낸 합의가 초안 0개로 통과하므로 review_submit 원본도 함께 센다. 부르는 쪽이
|
|
52
|
+
* 사유를 적어 보낼 자리는 두지 않는다.
|
|
53
|
+
*/
|
|
54
|
+
export declare function checkVerifierBeforePublish(session: ReviewSession, consensus: ReviewConsensusResult): Result<PublishVerifierCheck>;
|
|
55
|
+
//# sourceMappingURL=verifier-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier-record.d.ts","sourceRoot":"","sources":["../../../src/review/verifier-record.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,aAAa,EACb,yBAAyB,EACzB,oBAAoB,EAErB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,MAAM,EAAW,MAAM,mBAAmB,CAAC;AAKzD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAwC1F;AAYD,oDAAoD;AACpD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,oBAAoB,GAAG;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB,CAMA;AAkBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAqCD,+BAA+B;AAC/B,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,yBAAyB,CAKrF;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,yBAAyB,EAC7B,SAAS,EAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,GAAG,eAAe,CAAC,GACvE,MAAM,GAAG,IAAI,CA0Cf;AAED,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,qBAAqB,GAC/B,MAAM,CAAC,oBAAoB,CAAC,CA+C9B"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { ok, err } from '../core/result.js';
|
|
2
|
+
import { hasContent, norm } from './verification.js';
|
|
3
|
+
const VERDICTS = new Set(['keep', 'revise', 'drop']);
|
|
4
|
+
/**
|
|
5
|
+
* 검증기 응답을 읽어 기록으로 만든다.
|
|
6
|
+
*
|
|
7
|
+
* 응답은 서브에이전트가 쓴 글이라 JSON 앞뒤에 말이 붙거나 코드펜스에 싸여 올 수 있다.
|
|
8
|
+
* 통째로, 펜스 안, 첫 `{`부터 마지막 `}`까지 순서로 읽어 보고 처음 읽힌 것을 쓴다.
|
|
9
|
+
* 하나도 못 읽으면 서브에이전트 실패로 본다. 원문은 남기지 않는다 — 판정과 개수만 쓰면
|
|
10
|
+
* 되고 응답 문자열이 로그나 경로로 흘러갈 자리를 만들 이유가 없다.
|
|
11
|
+
*
|
|
12
|
+
* 판정은 프로토타입 없는 객체에 id를 norm으로 맞춰 담는다. 응답의 issueId가 `__proto__`여도
|
|
13
|
+
* 판정으로 남고 `constructor` 같은 id를 찾을 때 상속된 값이 걸리지 않는다.
|
|
14
|
+
*/
|
|
15
|
+
export function parseVerifierOutput(raw, submitCount) {
|
|
16
|
+
const list = readVerificationList(raw.trim());
|
|
17
|
+
if (!list) {
|
|
18
|
+
return {
|
|
19
|
+
status: 'failed',
|
|
20
|
+
failure: 'unparseable',
|
|
21
|
+
submitCount,
|
|
22
|
+
verdicts: {},
|
|
23
|
+
skippedCount: 0,
|
|
24
|
+
duplicateCount: 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const verdicts = Object.create(null);
|
|
28
|
+
let skippedCount = 0;
|
|
29
|
+
let duplicateCount = 0;
|
|
30
|
+
for (const entry of list) {
|
|
31
|
+
const read = readVerdict(entry);
|
|
32
|
+
if (!read) {
|
|
33
|
+
skippedCount++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (Object.hasOwn(verdicts, read.id)) {
|
|
37
|
+
duplicateCount++;
|
|
38
|
+
if (conservativeness(read.verdict) <= conservativeness(verdicts[read.id]))
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
verdicts[read.id] = read.verdict;
|
|
42
|
+
}
|
|
43
|
+
if (Object.keys(verdicts).length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
status: 'failed',
|
|
46
|
+
failure: 'no_verifications',
|
|
47
|
+
submitCount,
|
|
48
|
+
verdicts: {},
|
|
49
|
+
skippedCount,
|
|
50
|
+
duplicateCount,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { status: 'ran', submitCount, verdicts, skippedCount, duplicateCount };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 같은 id의 판정이 겹칠 때 어느 쪽을 남길지 가르는 값. 클수록 이슈를 빼지 않는 쪽이다.
|
|
57
|
+
* 겹치면 뒤에 온 것이 아니라 이슈를 덜 빼는 쪽을 남긴다. 응답 끝에 근거 붙은 drop 하나를
|
|
58
|
+
* 덧붙여 이슈를 빼는 걸 막으려고 둔 규칙이다. 값이 같으면 먼저 읽은 것을 둔다.
|
|
59
|
+
*/
|
|
60
|
+
function conservativeness(verdict) {
|
|
61
|
+
if (verdict.verdict !== 'drop')
|
|
62
|
+
return 2;
|
|
63
|
+
return verdict.hasEvidence ? 0 : 1;
|
|
64
|
+
}
|
|
65
|
+
/** 이벤트 로그와 review_verify 응답이 같은 수를 싣도록 한 자리에서 센다 */
|
|
66
|
+
export function summarizeVerifierRecord(record) {
|
|
67
|
+
return {
|
|
68
|
+
verdictCount: Object.keys(record.verdicts).length,
|
|
69
|
+
skippedCount: record.skippedCount,
|
|
70
|
+
duplicateCount: record.duplicateCount,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function readVerdict(entry) {
|
|
74
|
+
if (typeof entry !== 'object' || entry === null)
|
|
75
|
+
return null;
|
|
76
|
+
const { issueId, verdict, evidence } = entry;
|
|
77
|
+
if (typeof issueId !== 'string' || issueId.trim() === '')
|
|
78
|
+
return null;
|
|
79
|
+
if (typeof verdict !== 'string')
|
|
80
|
+
return null;
|
|
81
|
+
const normalized = verdict.trim().toLowerCase();
|
|
82
|
+
if (!VERDICTS.has(normalized))
|
|
83
|
+
return null;
|
|
84
|
+
return {
|
|
85
|
+
id: norm(issueId),
|
|
86
|
+
verdict: {
|
|
87
|
+
verdict: normalized,
|
|
88
|
+
hasEvidence: typeof evidence === 'string' && hasContent(evidence),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 코드펜스 기호와 줄바꿈이 바로 뒤따르는 언어 표시를 지운다. ```json만 있는 응답이 `json`이라는
|
|
94
|
+
* 글자 때문에 내용이 있는 것으로 읽히지 않게 빈 응답 판정에서만 쓴다. 줄바꿈이 안 따르는
|
|
95
|
+
* ```timeout 같은 한 단어는 내용이라 남긴다. 공용 hasContent는 그대로 둔다.
|
|
96
|
+
*/
|
|
97
|
+
export function stripCodeFences(text) {
|
|
98
|
+
return text.replace(/```(?:[\w+-]*(?=\r?\n))?/g, '');
|
|
99
|
+
}
|
|
100
|
+
function readVerificationList(text) {
|
|
101
|
+
const candidates = [text];
|
|
102
|
+
for (const match of text.matchAll(/```(?:json)?\s*\n([\s\S]*?)```/g))
|
|
103
|
+
candidates.push(match[1]);
|
|
104
|
+
const start = text.indexOf('{');
|
|
105
|
+
const end = text.lastIndexOf('}');
|
|
106
|
+
if (start !== -1 && end > start)
|
|
107
|
+
candidates.push(text.slice(start, end + 1));
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(candidate);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(parsed))
|
|
117
|
+
return parsed;
|
|
118
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
119
|
+
const list = parsed.verifications;
|
|
120
|
+
if (Array.isArray(list))
|
|
121
|
+
return list;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 이슈 id로 판정을 찾는다. 세션을 JSON에서 되살리면 프로토타입이 붙은 객체가 되므로
|
|
128
|
+
* 저장할 때와 상관없이 자기 키만 본다.
|
|
129
|
+
*/
|
|
130
|
+
function verdictOf(verdicts, id) {
|
|
131
|
+
const key = norm(id);
|
|
132
|
+
return Object.hasOwn(verdicts, key) ? verdicts[key] : undefined;
|
|
133
|
+
}
|
|
134
|
+
/** 합의를 받는 지금 검증 기록이 어떤 상태인지 */
|
|
135
|
+
export function verifierAtConsensus(session) {
|
|
136
|
+
const record = session.verifier;
|
|
137
|
+
if (!record)
|
|
138
|
+
return { status: 'missing' };
|
|
139
|
+
if (record.submitCount !== session.reviewResults.length)
|
|
140
|
+
return { status: 'stale', record };
|
|
141
|
+
return { status: record.status, record };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 합의가 검증 기록과 어긋나는지 본다. 어긋나면 문제를 줄마다 적어 돌려준다.
|
|
145
|
+
*
|
|
146
|
+
* 기록이 없거나 낡았으면 여기서는 보지 않는다. GitHub에 올리는 리뷰와 PR 없는 리뷰도
|
|
147
|
+
* 이 합의를 거치는데 거기까지 막으면 그 경로가 통째로 멈춘다. 게시를 막는 자리는
|
|
148
|
+
* review_publish다.
|
|
149
|
+
*/
|
|
150
|
+
export function checkConsensusAgainstVerifier(at, consensus) {
|
|
151
|
+
const dropped = consensus.droppedIssues ?? [];
|
|
152
|
+
if (at.status === 'failed') {
|
|
153
|
+
const judged = consensus.mergedIssues.filter((i) => i.verification).map((i) => i.id);
|
|
154
|
+
if (dropped.length === 0 && judged.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
const ids = [...dropped.map((i) => i.id), ...judged].join(', ');
|
|
157
|
+
return (`검증기가 돌려준 응답에서 판정을 못 읽었는데 판정이 붙은 이슈가 있다: ${ids}. ` +
|
|
158
|
+
'검증기가 실패했으면 droppedIssues와 verification 없이 초안 그대로 보낸다. ' +
|
|
159
|
+
'검증기를 다시 돌렸으면 그 응답을 review_verify로 먼저 넘긴다.');
|
|
160
|
+
}
|
|
161
|
+
if (at.status !== 'ran')
|
|
162
|
+
return null;
|
|
163
|
+
const { verdicts } = at.record;
|
|
164
|
+
const ids = [...consensus.mergedIssues, ...dropped].map((i) => i.id);
|
|
165
|
+
if (ids.length > 0 && !ids.some((id) => verdictOf(verdicts, id))) {
|
|
166
|
+
return ('검증기가 돌려준 응답의 issueId가 이번 이슈 id와 하나도 안 맞는다. ' +
|
|
167
|
+
'검증기에 넘긴 초안의 id를 그대로 쓰거나 지금 초안으로 검증을 다시 돌린다.');
|
|
168
|
+
}
|
|
169
|
+
const problems = [];
|
|
170
|
+
for (const issue of dropped) {
|
|
171
|
+
const judged = verdictOf(verdicts, issue.id);
|
|
172
|
+
if (!judged) {
|
|
173
|
+
problems.push(`- ${issue.id}: 검증기 응답에 없는 이슈라 뺄 수 없다`);
|
|
174
|
+
}
|
|
175
|
+
else if (judged.verdict !== 'drop') {
|
|
176
|
+
problems.push(`- ${issue.id}: 검증기 응답에서 ${judged.verdict}였다. drop으로 낸 이슈만 뺀다`);
|
|
177
|
+
}
|
|
178
|
+
else if (!judged.hasEvidence) {
|
|
179
|
+
problems.push(`- ${issue.id}: 검증기가 근거 없이 drop을 냈다. keep으로 mergedIssues에 둔다`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (problems.length === 0)
|
|
183
|
+
return null;
|
|
184
|
+
return [
|
|
185
|
+
'droppedIssues가 검증기 응답과 어긋난다. 해당 이슈를 mergedIssues로 되돌려 다시 부른다.',
|
|
186
|
+
...problems,
|
|
187
|
+
].join('\n');
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 게시 전에 3.7단계 기록을 확인한다.
|
|
191
|
+
*
|
|
192
|
+
* 건너뛰어도 되는 경우는 엔진이 세션으로 가른다. 이슈 초안이 하나도 없었거나(review_submit
|
|
193
|
+
* 이슈도 합의 이슈도 없다) 검증기 응답을 못 읽었을 때다. 초안 수를 합의만으로 세면 이슈를
|
|
194
|
+
* 전부 빼고 보낸 합의가 초안 0개로 통과하므로 review_submit 원본도 함께 센다. 부르는 쪽이
|
|
195
|
+
* 사유를 적어 보낼 자리는 두지 않는다.
|
|
196
|
+
*/
|
|
197
|
+
export function checkVerifierBeforePublish(session, consensus) {
|
|
198
|
+
const consensusIds = [...consensus.mergedIssues, ...(consensus.droppedIssues ?? [])].map((i) => i.id);
|
|
199
|
+
const submitted = session.reviewResults.reduce((n, r) => n + r.issues.length, 0);
|
|
200
|
+
const draftCount = Math.max(submitted, consensusIds.length);
|
|
201
|
+
if (draftCount === 0)
|
|
202
|
+
return ok({ status: 'no_drafts' });
|
|
203
|
+
const at = session.verifierAtConsensus ?? { status: 'missing' };
|
|
204
|
+
if (at.status === 'failed')
|
|
205
|
+
return ok({ status: 'failed' });
|
|
206
|
+
if (at.status === 'ran') {
|
|
207
|
+
const { verdicts } = at.record;
|
|
208
|
+
const judged = consensusIds.filter((id) => verdictOf(verdicts, id)).length;
|
|
209
|
+
return ok({ status: 'ran', coverage: { judged, total: consensusIds.length } });
|
|
210
|
+
}
|
|
211
|
+
const latest = session.verifier;
|
|
212
|
+
const cameAfterConsensus = latest !== undefined &&
|
|
213
|
+
(at.status === 'missing' || latest !== at.record) &&
|
|
214
|
+
latest.submitCount === session.reviewResults.length;
|
|
215
|
+
if (cameAfterConsensus) {
|
|
216
|
+
return err(new Error('review_verify가 review_consensus 뒤에 들어와 지금 합의에는 제안 검증이 반영되지 않았다. ' +
|
|
217
|
+
'검증 결과를 반영해 review_consensus를 다시 부른 뒤 게시한다.'));
|
|
218
|
+
}
|
|
219
|
+
if (at.status === 'stale') {
|
|
220
|
+
const added = session.reviewResults.length - at.record.submitCount;
|
|
221
|
+
return err(new Error(`제안 검증 뒤에 review_submit이 ${added}개 더 들어와 그 기록으로는 게시하지 않는다. ` +
|
|
222
|
+
'늘어난 초안까지 다시 검증해 review_verify로 넘기고 review_consensus를 다시 부른다.'));
|
|
223
|
+
}
|
|
224
|
+
return err(new Error(`제안 검증(3.7단계) 기록이 없어 게시하지 않는다. 이슈 ${draftCount}개를 suggestion-verifier로 ` +
|
|
225
|
+
'검증하고 그 응답을 review_verify로 넘긴 뒤 review_consensus를 다시 부른다. ' +
|
|
226
|
+
'서브에이전트가 실패했으면 돌아온 응답을 그대로 넘긴다. 응답이 비었으면 검증기를 한 번 다시 띄운다.'));
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=verifier-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier-record.js","sourceRoot":"","sources":["../../../src/review/verifier-record.ts"],"names":[],"mappings":"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"}
|