@tienne/gestalt 0.68.0 → 0.70.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/CLAUDE.md +10 -2
- package/README.md +1 -0
- package/dist/package.json +2 -1
- package/dist/plugin/review-agents/quality-reviewer/AGENT.md +1 -1
- package/dist/plugin/review-agents/writing-reviewer/AGENT.md +2 -2
- package/dist/plugin/role-agents/humanize-monolith/AGENT.md +89 -11
- package/dist/plugin/skills/local-pr/SKILL.md +201 -0
- package/dist/plugin/skills/pr/SKILL.md +73 -4
- package/dist/plugin/skills/review/SKILL.md +190 -18
- package/dist/plugin/skills/review-reply/SKILL.md +144 -7
- package/dist/src/cli/commands/humanize-check.d.ts +2 -0
- package/dist/src/cli/commands/humanize-check.d.ts.map +1 -1
- package/dist/src/cli/commands/humanize-check.js +11 -5
- package/dist/src/cli/commands/humanize-check.js.map +1 -1
- package/dist/src/cli/commands/humanize-scan.d.ts +23 -0
- package/dist/src/cli/commands/humanize-scan.d.ts.map +1 -0
- package/dist/src/cli/commands/humanize-scan.js +30 -0
- package/dist/src/cli/commands/humanize-scan.js.map +1 -0
- package/dist/src/cli/commands/pr.d.ts +133 -0
- package/dist/src/cli/commands/pr.d.ts.map +1 -0
- package/dist/src/cli/commands/pr.js +489 -0
- package/dist/src/cli/commands/pr.js.map +1 -0
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +96 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/config.d.ts.map +1 -1
- package/dist/src/core/config.js +4 -3
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/home.d.ts +20 -0
- package/dist/src/core/home.d.ts.map +1 -0
- package/dist/src/core/home.js +52 -0
- package/dist/src/core/home.js.map +1 -0
- package/dist/src/core/types.d.ts +38 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/core/version.d.ts.map +1 -1
- package/dist/src/core/version.js +4 -7
- package/dist/src/core/version.js.map +1 -1
- package/dist/src/events/store.d.ts +32 -0
- package/dist/src/events/store.d.ts.map +1 -1
- package/dist/src/events/store.js +68 -3
- package/dist/src/events/store.js.map +1 -1
- package/dist/src/humanize/check.d.ts +75 -7
- package/dist/src/humanize/check.d.ts.map +1 -1
- package/dist/src/humanize/check.js +241 -30
- package/dist/src/humanize/check.js.map +1 -1
- package/dist/src/humanize/detectors.d.ts.map +1 -1
- package/dist/src/humanize/detectors.js +7 -8
- package/dist/src/humanize/detectors.js.map +1 -1
- package/dist/src/humanize/index.d.ts +3 -2
- package/dist/src/humanize/index.d.ts.map +1 -1
- package/dist/src/humanize/index.js +3 -2
- package/dist/src/humanize/index.js.map +1 -1
- package/dist/src/humanize/rules.d.ts +12 -0
- package/dist/src/humanize/rules.d.ts.map +1 -1
- package/dist/src/humanize/rules.js +4 -0
- package/dist/src/humanize/rules.js.map +1 -1
- package/dist/src/humanize/scan.d.ts +24 -0
- package/dist/src/humanize/scan.d.ts.map +1 -0
- package/dist/src/humanize/scan.js +65 -0
- package/dist/src/humanize/scan.js.map +1 -0
- package/dist/src/local-pr/engine.d.ts +176 -0
- package/dist/src/local-pr/engine.d.ts.map +1 -0
- package/dist/src/local-pr/engine.js +373 -0
- package/dist/src/local-pr/engine.js.map +1 -0
- package/dist/src/local-pr/git.d.ts +190 -0
- package/dist/src/local-pr/git.d.ts.map +1 -0
- package/dist/src/local-pr/git.js +580 -0
- package/dist/src/local-pr/git.js.map +1 -0
- package/dist/src/local-pr/index.d.ts +6 -0
- package/dist/src/local-pr/index.d.ts.map +1 -0
- package/dist/src/local-pr/index.js +8 -0
- package/dist/src/local-pr/index.js.map +1 -0
- package/dist/src/local-pr/policy.d.ts +77 -0
- package/dist/src/local-pr/policy.d.ts.map +1 -0
- package/dist/src/local-pr/policy.js +80 -0
- package/dist/src/local-pr/policy.js.map +1 -0
- package/dist/src/local-pr/registry.d.ts +79 -0
- package/dist/src/local-pr/registry.d.ts.map +1 -0
- package/dist/src/local-pr/registry.js +307 -0
- package/dist/src/local-pr/registry.js.map +1 -0
- package/dist/src/local-pr/repository.d.ts +67 -0
- package/dist/src/local-pr/repository.d.ts.map +1 -0
- package/dist/src/local-pr/repository.js +251 -0
- package/dist/src/local-pr/repository.js.map +1 -0
- package/dist/src/local-pr/types.d.ts +151 -0
- package/dist/src/local-pr/types.d.ts.map +1 -0
- package/dist/src/local-pr/types.js +9 -0
- package/dist/src/local-pr/types.js.map +1 -0
- package/dist/src/local-pr-web/engine.d.ts +24 -0
- package/dist/src/local-pr-web/engine.d.ts.map +1 -0
- package/dist/src/local-pr-web/engine.js +112 -0
- package/dist/src/local-pr-web/engine.js.map +1 -0
- package/dist/src/local-pr-web/html-generator.d.ts +24 -0
- package/dist/src/local-pr-web/html-generator.d.ts.map +1 -0
- package/dist/src/local-pr-web/html-generator.js +385 -0
- package/dist/src/local-pr-web/html-generator.js.map +1 -0
- package/dist/src/local-pr-web/index.d.ts +5 -0
- package/dist/src/local-pr-web/index.d.ts.map +1 -0
- package/dist/src/local-pr-web/index.js +5 -0
- package/dist/src/local-pr-web/index.js.map +1 -0
- package/dist/src/local-pr-web/server.d.ts +126 -0
- package/dist/src/local-pr-web/server.d.ts.map +1 -0
- package/dist/src/local-pr-web/server.js +394 -0
- package/dist/src/local-pr-web/server.js.map +1 -0
- package/dist/src/local-pr-web/types.d.ts +11 -0
- package/dist/src/local-pr-web/types.d.ts.map +1 -0
- package/dist/src/local-pr-web/types.js +2 -0
- package/dist/src/local-pr-web/types.js.map +1 -0
- package/dist/src/mcp/schemas.d.ts +79 -4
- package/dist/src/mcp/schemas.d.ts.map +1 -1
- package/dist/src/mcp/schemas.js +80 -2
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +13 -2
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/pr.d.ts +5 -0
- package/dist/src/mcp/tools/pr.d.ts.map +1 -0
- package/dist/src/mcp/tools/pr.js +165 -0
- package/dist/src/mcp/tools/pr.js.map +1 -0
- package/dist/src/mcp/tools/review-passthrough.d.ts.map +1 -1
- package/dist/src/mcp/tools/review-passthrough.js +264 -15
- package/dist/src/mcp/tools/review-passthrough.js.map +1 -1
- package/dist/src/memory/user-profile-store.d.ts.map +1 -1
- package/dist/src/memory/user-profile-store.js +3 -4
- package/dist/src/memory/user-profile-store.js.map +1 -1
- package/dist/src/review/context-collector.d.ts +1 -1
- package/dist/src/review/context-collector.js +1 -1
- package/dist/src/review/passthrough-engine.d.ts +1 -0
- package/dist/src/review/passthrough-engine.d.ts.map +1 -1
- package/dist/src/review/passthrough-engine.js +7 -2
- package/dist/src/review/passthrough-engine.js.map +1 -1
- package/dist/src/spec/text-based-spec-generator.d.ts.map +1 -1
- package/dist/src/spec/text-based-spec-generator.js +5 -2
- package/dist/src/spec/text-based-spec-generator.js.map +1 -1
- package/package.json +2 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/review-agents/quality-reviewer/AGENT.md +1 -1
- package/plugin/review-agents/writing-reviewer/AGENT.md +2 -2
- package/plugin/role-agents/humanize-monolith/AGENT.md +89 -11
- package/plugin/skills/local-pr/SKILL.md +201 -0
- package/plugin/skills/pr/SKILL.md +73 -4
- package/plugin/skills/review/SKILL.md +190 -18
- package/plugin/skills/review-reply/SKILL.md +144 -7
package/dist/src/events/store.js
CHANGED
|
@@ -10,6 +10,16 @@ const require = createRequire(import.meta.url);
|
|
|
10
10
|
export class EventStore {
|
|
11
11
|
db = null;
|
|
12
12
|
jsonlPath = null;
|
|
13
|
+
/**
|
|
14
|
+
* append가 쓰는 INSERT. 한 번만 컴파일한다.
|
|
15
|
+
*
|
|
16
|
+
* 호출마다 `prepare`하면 같은 SQL을 매번 다시 파싱하고 계획을 다시 세운다. 코멘트
|
|
17
|
+
* 수백 개를 한 번에 옮기는 흐름(`commentMany`)이 이 자리를 그대로 밟는다.
|
|
18
|
+
*
|
|
19
|
+
* 트랜잭션으로 묶지는 않는다. `commentMany`는 중간에 던져도 그때까지 쓴 건 남아
|
|
20
|
+
* 있어야 재시도가 이어 붙는다 — 한 트랜잭션이면 그게 통째로 사라진다.
|
|
21
|
+
*/
|
|
22
|
+
appendStmt = null;
|
|
13
23
|
constructor(dbPath, options = {}) {
|
|
14
24
|
if (!options.forceJsonl) {
|
|
15
25
|
try {
|
|
@@ -66,11 +76,11 @@ export class EventStore {
|
|
|
66
76
|
};
|
|
67
77
|
try {
|
|
68
78
|
if (this.db) {
|
|
69
|
-
|
|
79
|
+
this.appendStmt ??= this.db.prepare(`
|
|
70
80
|
INSERT INTO events (id, aggregate_type, aggregate_id, event_type, payload, timestamp)
|
|
71
81
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
72
82
|
`);
|
|
73
|
-
|
|
83
|
+
this.appendStmt.run(event.id, event.aggregateType, event.aggregateId, event.eventType, JSON.stringify(event.payload), event.timestamp);
|
|
74
84
|
}
|
|
75
85
|
else {
|
|
76
86
|
this.appendJsonl(event);
|
|
@@ -104,14 +114,66 @@ export class EventStore {
|
|
|
104
114
|
if (!this.db) {
|
|
105
115
|
return this.readJsonlEvents().filter((event) => event.aggregateType === aggregateType && event.aggregateId === aggregateId);
|
|
106
116
|
}
|
|
117
|
+
// rowid로 정렬한다. timestamp로 안 하는 건 그게 밀리초 ISO 문자열이라 같은 ms에
|
|
118
|
+
// 여러 건이 들어가서다 — 코멘트를 연속으로 붙이는 흐름은 그 자리를 통상적으로
|
|
119
|
+
// 밟는다. 그러면 timestamp만으로는 순서가 안 정해진다. tiebreaker로 rowid를
|
|
120
|
+
// 얹으면 정렬 기준이 둘이 된다.
|
|
121
|
+
//
|
|
122
|
+
// rowid 하나로 충분하다. append는 INSERT 한 번이고 sqlite의 rowid는 그때마다
|
|
123
|
+
// 단조 증가한다 — 이건 문서화된 성질이다. 삽입 순서가 곧 재생 순서다. 이 테이블은
|
|
124
|
+
// 붙이기만 하고 지우는 자리가 없어서 최대 rowid가 재사용되는 갈래도 안 열린다.
|
|
125
|
+
//
|
|
126
|
+
// 겸사겸사 이 쿼리에서는 정렬이 사라진다. timestamp를 앞에 두면 이 절을 받쳐 줄
|
|
127
|
+
// 인덱스가 없어 sqlite가 매번 임시 b-tree를 세웠다. rowid만 남기면
|
|
128
|
+
// idx_events_aggregate로 좁힌 뒤 rowid 순서로 훑고 끝난다. 여기 얘기지
|
|
129
|
+
// `getAllByAggregateType` 얘기가 아니다 — 거기는 사정이 다르고 그 자리에 적었다.
|
|
130
|
+
//
|
|
131
|
+
// 재생 순서는 이 도메인이 상태를 만드는 근거라 저장 엔진의 문서화되지 않은
|
|
132
|
+
// 성질에 기대고 싶지 않았다. 그 취지가 rowid 쪽에 더 맞는다
|
|
107
133
|
const stmt = this.db.prepare(`
|
|
108
134
|
SELECT * FROM events
|
|
109
135
|
WHERE aggregate_type = ? AND aggregate_id = ?
|
|
110
|
-
ORDER BY
|
|
136
|
+
ORDER BY rowid ASC
|
|
111
137
|
`);
|
|
112
138
|
const rows = stmt.all(aggregateType, aggregateId);
|
|
113
139
|
return rows.map(parseRow);
|
|
114
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* 이 타입의 모든 이벤트를 aggregate별로 묶어 한 번에 돌려준다.
|
|
143
|
+
*
|
|
144
|
+
* aggregate마다 replay를 부르면 PR 수만큼 쿼리가 나간다. sqlite가 없어 JSONL로
|
|
145
|
+
* 떨어진 런타임에서는 파일 전체를 PR 수만큼 다시 읽는다. 목록 화면과 CLI list가
|
|
146
|
+
* 부르는 가장 뜨거운 자리라 그 비용이 그대로 보인다.
|
|
147
|
+
*
|
|
148
|
+
* 정렬은 rowid만 본다. 결과를 aggregate별로 다시 묶으므로 전역 시간 순서는 쓸
|
|
149
|
+
* 데가 없다. 필요한 건 묶음 안의 상대 순서뿐이다. 순서를 rowid로 정하는 근거는
|
|
150
|
+
* `getByAggregate`에 적었다.
|
|
151
|
+
*
|
|
152
|
+
* 다만 임시 b-tree는 여기서 안 없어진다. 플래너가 `aggregate_type = ?` 하나만 보고도
|
|
153
|
+
* idx_events_aggregate를 고르는데, 그 인덱스 순서는 rowid 순서가 아니라 정렬이 또
|
|
154
|
+
* 필요하다. 어느 플랜을 고를지는 통계를 탄다 — `ANALYZE`를 돌려 sqlite_stat1이
|
|
155
|
+
* 있으면 `SCAN events`로 붙어 정렬이 사라진다. 없으면 인덱스를 골라 임시 b-tree가
|
|
156
|
+
* 남는다. 이 스토어는 ANALYZE를 안 돌리므로 실물은 후자다.
|
|
157
|
+
*
|
|
158
|
+
* 그래도 빨라지는 건 정렬 키가 ISO 문자열 비교에서 정수 비교로 바뀌어서다. 이벤트
|
|
159
|
+
* 2만 건에 aggregate 200개로 20회 평균 23.1 → 20.7 ms였다(sqlite 3.53.4, macOS).
|
|
160
|
+
*/
|
|
161
|
+
getAllByAggregateType(aggregateType) {
|
|
162
|
+
const grouped = new Map();
|
|
163
|
+
const events = this.db
|
|
164
|
+
? this.db
|
|
165
|
+
.prepare(`SELECT * FROM events WHERE aggregate_type = ? ORDER BY rowid ASC`)
|
|
166
|
+
.all(aggregateType).map(parseRow)
|
|
167
|
+
: this.readJsonlEvents().filter((e) => e.aggregateType === aggregateType);
|
|
168
|
+
for (const event of events) {
|
|
169
|
+
const list = grouped.get(event.aggregateId);
|
|
170
|
+
if (list)
|
|
171
|
+
list.push(event);
|
|
172
|
+
else
|
|
173
|
+
grouped.set(event.aggregateId, [event]);
|
|
174
|
+
}
|
|
175
|
+
return grouped;
|
|
176
|
+
}
|
|
115
177
|
getByType(eventType, limit = 100) {
|
|
116
178
|
if (!this.db) {
|
|
117
179
|
return this.readJsonlEvents()
|
|
@@ -206,6 +268,9 @@ export class EventStore {
|
|
|
206
268
|
return counts;
|
|
207
269
|
}
|
|
208
270
|
close() {
|
|
271
|
+
// 캐시한 statement는 닫힌 db를 붙잡고 있다. 먼저 놓아야 닫은 뒤에 잘못 쓰는 길이
|
|
272
|
+
// 안 남는다
|
|
273
|
+
this.appendStmt = null;
|
|
209
274
|
this.db?.close();
|
|
210
275
|
}
|
|
211
276
|
appendJsonl(event) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/events/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/events/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import { type Register, type
|
|
1
|
+
import { type Register, type RuleScanOptions } from './rules.js';
|
|
2
2
|
export type Verdict = 'pass' | 'warn' | 'abort';
|
|
3
3
|
export declare const EXIT_CODE: Record<Verdict | 'unknown', number>;
|
|
4
4
|
export declare const THRESHOLD: {
|
|
5
5
|
readonly changeWarn: 0.3;
|
|
6
6
|
readonly changeAbort: 0.5;
|
|
7
7
|
readonly sentenceDrop: 0.25;
|
|
8
|
+
/** S1을 이만큼도 못 줄이면 경고. 한 건도 못 줄인 쪽은 아래 idleChange 와 함께 본다 */
|
|
9
|
+
readonly s1RemovalWarn: 0.5;
|
|
10
|
+
/**
|
|
11
|
+
* 손을 대긴 했다고 볼 최소 변경률.
|
|
12
|
+
*
|
|
13
|
+
* 제거율만 보면 탐지기가 가리는 룰만 세게 된다. 탐지기 없는 S1(doc 기준 일곱 개)만
|
|
14
|
+
* 제대로 고친 윤문본은 제거율이 0이라 채택 금지로 떨어진다. 원문을 그대로 돌려준
|
|
15
|
+
* 것과 같은 판정을 받는 셈인데, 그쪽은 변경률도 0이라 여기서 갈린다.
|
|
16
|
+
*
|
|
17
|
+
* 이 값으로 가르는 건 "윤문을 아예 안 했나"까지다. 문턱을 넘었다고 제대로 고쳤다는
|
|
18
|
+
* 뜻은 아니다 — 그건 이 검사의 판정 범위 밖이라 경고로 넘긴다.
|
|
19
|
+
*
|
|
20
|
+
* 변경률은 비율이라 문서가 길수록 같은 양의 교정이 낮은 값으로 나온다. 실측하면
|
|
21
|
+
* 한 곳을 고칠 때 2문단에서 20%, 50문단에서 0.75%다. 그래서 문턱 아래라는 게 안
|
|
22
|
+
* 고쳤다는 뜻이 아니다. 문구도 "텍스트가 그대로다"가 아니라 "변경이 문턱 아래다"이다.
|
|
23
|
+
*/
|
|
24
|
+
readonly idleChange: 0.05;
|
|
8
25
|
};
|
|
9
26
|
export interface AxisResult {
|
|
10
|
-
axis: 'change-rate' | 'residual-s1' | 'preservation' | 'structure' | 'report-register';
|
|
27
|
+
axis: 'change-rate' | 'residual-s1' | 'preservation' | 'structure' | 'introduced' | 'report-register';
|
|
11
28
|
verdict: Verdict;
|
|
12
29
|
detail: string;
|
|
13
30
|
evidence?: string[];
|
|
@@ -18,6 +35,10 @@ export interface CheckReport {
|
|
|
18
35
|
exitCode: number;
|
|
19
36
|
changeRate: number;
|
|
20
37
|
changeRateNoMarkup: number;
|
|
38
|
+
/** 원문 대비 S1을 얼마나 걷어냈나. 원문에 S1이 없었으면 1 */
|
|
39
|
+
s1Removal: number;
|
|
40
|
+
s1Before: number;
|
|
41
|
+
s1After: number;
|
|
21
42
|
axes: AxisResult[];
|
|
22
43
|
residualS1: {
|
|
23
44
|
ruleId: string;
|
|
@@ -30,10 +51,57 @@ export interface CheckReport {
|
|
|
30
51
|
after: number;
|
|
31
52
|
}[];
|
|
32
53
|
}
|
|
33
|
-
export interface
|
|
34
|
-
register
|
|
35
|
-
|
|
54
|
+
export interface PrescanReport {
|
|
55
|
+
register: Register;
|
|
56
|
+
/** 이 말투 기준 S1 총 건수 */
|
|
57
|
+
s1Total: number;
|
|
58
|
+
/** 룰 ID별 건수. runCheck 에 그대로 넘겨 기준선으로 쓴다 */
|
|
59
|
+
s1ByRule: Map<string, number>;
|
|
60
|
+
/** 걸리는 게 없으면 윤문할 이유도 없다 */
|
|
61
|
+
worthHumanizing: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 윤문 전에 원문을 훑는다.
|
|
65
|
+
*
|
|
66
|
+
* 두 가지를 한다. 첫째, S1이 0건이면 윤문 자체를 건너뛴다 — 고칠 게 없는 글에
|
|
67
|
+
* 윤문을 돌리면 남는 건 과윤문뿐이다. 둘째, 여기서 센 건수를 runCheck의 prescanned로
|
|
68
|
+
* 넘기면 그 값이 제거율의 기준선이 된다.
|
|
69
|
+
*
|
|
70
|
+
* 비용은 문서 크기가 아니라 걸린 룰 종류 수에 비례한다. scan 을 거치며 라벨과 처방을
|
|
71
|
+
* 조회하지만 그건 걸린 룰마다 한 번씩이라, 2.5KB 와 5MB 사이에서 오버헤드가 커지지 않는다.
|
|
72
|
+
* 룰북이 수백 룰로 자라면 그때 다시 본다.
|
|
73
|
+
*
|
|
74
|
+
* 기준선이 실제로 의미를 가지려면 윤문 전에 불러 그 값을 들고 있어야 한다. 원문과
|
|
75
|
+
* 윤문본을 한꺼번에 받는 자리(예: humanize-check CLI)에서는 검사 시점에 다시 세는
|
|
76
|
+
* 것과 결과가 같다. 그 자리에서 이 함수는 편의일 뿐이다. 기준선이 갈릴 수 있는
|
|
77
|
+
* 흐름은 윤문 파이프라인이 원문을 먼저 훑는 경우다.
|
|
78
|
+
*/
|
|
79
|
+
export declare function prescan(text: string, options?: RuleScanOptions): PrescanReport;
|
|
80
|
+
/** @deprecated rules.ts 의 RuleScanOptions 를 쓴다. 이름이 runCheck 전용처럼 보인다 */
|
|
81
|
+
export type RunCheckOptions = RuleScanOptions;
|
|
82
|
+
export interface RunCheckWithPrescan extends RuleScanOptions {
|
|
83
|
+
/** prescan() 이 센 원문 기준선. 안 넘기면 여기서 다시 센다 */
|
|
84
|
+
prescanned?: ReadonlyMap<string, number>;
|
|
85
|
+
}
|
|
86
|
+
export declare function runCheck(before: string, after: string, options?: RunCheckWithPrescan): CheckReport;
|
|
87
|
+
/** 검사 결과가 지시하는 다음 행동 */
|
|
88
|
+
export type NextAction = 'accept' | 'accept-with-warning' | 'retry' | 'fallback';
|
|
89
|
+
export interface Decision {
|
|
90
|
+
action: NextAction;
|
|
91
|
+
message: string;
|
|
36
92
|
}
|
|
37
|
-
|
|
38
|
-
export declare
|
|
93
|
+
/** 윤문은 한 번만 다시 시킨다. 두 번째도 막히면 원문이 답이다 */
|
|
94
|
+
export declare const MAX_ATTEMPTS = 2;
|
|
95
|
+
/**
|
|
96
|
+
* 검사 결과를 다음 행동으로 옮긴다.
|
|
97
|
+
*
|
|
98
|
+
* "exit 2면 채택하지 않는다"를 문서에만 적어두면 모델이 자기 결과를 스스로
|
|
99
|
+
* 버려야 성립한다. 그건 지켜지지 않는다. 판정과 지시를 코드가 같이 낸다.
|
|
100
|
+
* 실행하는 쪽은 따르기만 한다.
|
|
101
|
+
*
|
|
102
|
+
* 재시도가 소진되면 윤문본을 버리고 원문을 낸다. 윤문이 원문보다 나아졌다고
|
|
103
|
+
* 증명하지 못했으니, 남는 선택지는 원문뿐이다.
|
|
104
|
+
*/
|
|
105
|
+
export declare function decide(report: CheckReport, attempt?: number, maxAttempts?: number): Decision;
|
|
106
|
+
export declare function formatReport(report: CheckReport, attempt?: number): string;
|
|
39
107
|
//# sourceMappingURL=check.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/humanize/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/humanize/check.ts"],"names":[],"mappings":"AAwBA,OAAO,EAIL,KAAK,QAAQ,EAEb,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,SAAS,EAAE,MAAM,CAKzD,CAAC;AAEF,eAAO,MAAM,SAAS;;;;IAIpB,4DAA4D;;IAE5D;;;;;;;;;;;;;OAaG;;CAEK,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,IAAI,EACA,aAAa,GACb,aAAa,GACb,cAAc,GACd,WAAW,GACX,YAAY,GACZ,iBAAiB,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAChE,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjE;AA0OD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,2BAA2B;IAC3B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,aAAa,CAWlF;AAED,yEAAyE;AACzE,MAAM,MAAM,eAAe,GAAG,eAAe,CAAC;AAE9C,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,4CAA4C;IAC5C,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAChC,WAAW,CA4Db;AAED,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,qBAAqB,GAAG,OAAO,GAAG,UAAU,CAAC;AAEjF,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,yCAAyC;AACzC,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CACpB,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,MAAU,EACnB,WAAW,GAAE,MAAqB,GACjC,QAAQ,CA8BV;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,MAAU,GAAG,MAAM,CAgB7E"}
|
|
@@ -3,11 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 모델이 스스로 매긴 변경률·등급은 참고값이다. 문자 기반 변경률 하나로는
|
|
5
5
|
* 구조 편집이 안 보인다 — 변경률 2.8%인데 문장 3할이 갈려나간 사례가 있다.
|
|
6
|
-
* 그래서 변경률, 잔존, 보존,
|
|
6
|
+
* 그래서 변경률, 잔존, 보존, 구조, 유입 다섯 측면에서 각각 측정하고, 판단한다.
|
|
7
|
+
*
|
|
8
|
+
* 채택 금지(abort)는 양방향이다. 너무 많이 바꾼 쪽(변경률 50%, 보호 토큰 유실)만
|
|
9
|
+
* 막으면 아무것도 안 고친 윤문이 그대로 통과한다 — 원문을 그대로 돌려줘도
|
|
10
|
+
* 변경률 0%에 유실 0건이라 경고 하나로 끝났다. 그래서 못 줄인 쪽과 새로 심은 쪽도
|
|
11
|
+
* 막는다. 다만 두 쪽의 조건이 다르다. 신규 유입은 그 말투의 S1이 새로 생겼을 때
|
|
12
|
+
* 채택 금지이고 S2 이하는 경고다. 제거율 0은 변경까지 문턱 아래일 때만 채택 금지다.
|
|
13
|
+
*
|
|
14
|
+
* 잔존 축의 판정 근거는 탐지기가 있는 룰의 건수다. 제거율이 0인 자리에서만 변경률을
|
|
15
|
+
* 문턱으로 함께 보는데, 그건 change-rate 축이 계산한 값을 빌려 쓰는 것이다. 그 밖에서
|
|
16
|
+
* 무엇을 고쳤는지는 판정 범위에 없다. 그 자리는 사람에게 넘긴다.
|
|
7
17
|
*/
|
|
8
18
|
import { changeRate } from './change-rate.js';
|
|
9
19
|
import { countByRule, missingProtectedTokens, reportRegisterStats, structureStats, } from './detectors.js';
|
|
10
|
-
import { parseRuleBook, ruleLabel, s1Ids } from './rules.js';
|
|
20
|
+
import { parseRuleBook, ruleLabel, s1Ids, } from './rules.js';
|
|
21
|
+
import { scan } from './scan.js';
|
|
11
22
|
export const EXIT_CODE = {
|
|
12
23
|
pass: 0,
|
|
13
24
|
warn: 1,
|
|
@@ -18,6 +29,23 @@ export const THRESHOLD = {
|
|
|
18
29
|
changeWarn: 0.3,
|
|
19
30
|
changeAbort: 0.5,
|
|
20
31
|
sentenceDrop: 0.25,
|
|
32
|
+
/** S1을 이만큼도 못 줄이면 경고. 한 건도 못 줄인 쪽은 아래 idleChange 와 함께 본다 */
|
|
33
|
+
s1RemovalWarn: 0.5,
|
|
34
|
+
/**
|
|
35
|
+
* 손을 대긴 했다고 볼 최소 변경률.
|
|
36
|
+
*
|
|
37
|
+
* 제거율만 보면 탐지기가 가리는 룰만 세게 된다. 탐지기 없는 S1(doc 기준 일곱 개)만
|
|
38
|
+
* 제대로 고친 윤문본은 제거율이 0이라 채택 금지로 떨어진다. 원문을 그대로 돌려준
|
|
39
|
+
* 것과 같은 판정을 받는 셈인데, 그쪽은 변경률도 0이라 여기서 갈린다.
|
|
40
|
+
*
|
|
41
|
+
* 이 값으로 가르는 건 "윤문을 아예 안 했나"까지다. 문턱을 넘었다고 제대로 고쳤다는
|
|
42
|
+
* 뜻은 아니다 — 그건 이 검사의 판정 범위 밖이라 경고로 넘긴다.
|
|
43
|
+
*
|
|
44
|
+
* 변경률은 비율이라 문서가 길수록 같은 양의 교정이 낮은 값으로 나온다. 실측하면
|
|
45
|
+
* 한 곳을 고칠 때 2문단에서 20%, 50문단에서 0.75%다. 그래서 문턱 아래라는 게 안
|
|
46
|
+
* 고쳤다는 뜻이 아니다. 문구도 "텍스트가 그대로다"가 아니라 "변경이 문턱 아래다"이다.
|
|
47
|
+
*/
|
|
48
|
+
idleChange: 0.05,
|
|
21
49
|
};
|
|
22
50
|
const WORST = { pass: 0, warn: 1, abort: 2 };
|
|
23
51
|
function worst(verdicts) {
|
|
@@ -36,38 +64,123 @@ function checkChangeRate(rate, rateNoMarkup) {
|
|
|
36
64
|
}
|
|
37
65
|
return { axis: 'change-rate', verdict: 'pass', detail };
|
|
38
66
|
}
|
|
39
|
-
|
|
67
|
+
/**
|
|
68
|
+
* S1을 얼마나 걷어냈는지 본다.
|
|
69
|
+
*
|
|
70
|
+
* 잔존 건수만 보면 "5건 → 5건"과 "5건 → 1건"이 같은 경고로 끝난다. 앞은 윤문이
|
|
71
|
+
* 아무 일도 안 한 것이고 뒤는 8할을 걷어낸 것이라, 같은 판정을 받을 수 없다.
|
|
72
|
+
* 원문 건수를 기준선으로 두고 제거율로 가른다.
|
|
73
|
+
*
|
|
74
|
+
* 세는 대상은 탐지기가 있는 룰뿐이다. 그래서 제거율 0이 곧 "아무것도 안 했다"는
|
|
75
|
+
* 아니다 — 탐지기 없는 S1만 고친 윤문본도 여기서는 0으로 보인다.
|
|
76
|
+
*
|
|
77
|
+
* 그래서 제거율이 0일 때 여기서 확정되는 건 하나다 — 변경까지 문턱 아래면 윤문을 안
|
|
78
|
+
* 한 것이다. 변경이 문턱을 넘었으면 탐지기 밖에서 고쳤을 수 있고 그건 이 코드로
|
|
79
|
+
* 확인되지 않으므로 경고로 넘긴다.
|
|
80
|
+
*/
|
|
81
|
+
function checkResidualS1(book, register, counts, rate) {
|
|
40
82
|
const targets = s1Ids(book, register);
|
|
41
|
-
const beforeCounts =
|
|
42
|
-
const afterCounts =
|
|
43
|
-
const
|
|
44
|
-
.map((ruleId) => ({
|
|
83
|
+
const beforeCounts = counts.before;
|
|
84
|
+
const afterCounts = counts.after;
|
|
85
|
+
const rows = targets.map((ruleId) => ({
|
|
45
86
|
ruleId,
|
|
46
87
|
before: beforeCounts.get(ruleId) ?? 0,
|
|
47
88
|
after: afterCounts.get(ruleId) ?? 0,
|
|
48
|
-
}))
|
|
49
|
-
|
|
50
|
-
|
|
89
|
+
}));
|
|
90
|
+
const beforeTotal = rows.reduce((sum, row) => sum + row.before, 0);
|
|
91
|
+
const afterTotal = rows.reduce((sum, row) => sum + row.after, 0);
|
|
92
|
+
const residual = rows.filter((row) => row.after > 0);
|
|
93
|
+
// 원문에 S1이 없었으면 줄일 것도 없다. 그 자리에 새로 심었다면 아래에서 걸린다
|
|
94
|
+
const removal = beforeTotal === 0 ? 1 : (beforeTotal - afterTotal) / beforeTotal;
|
|
95
|
+
const base = { residual, removal, beforeTotal, afterTotal };
|
|
96
|
+
if (afterTotal === 0) {
|
|
97
|
+
return { ...base, axis: { axis: 'residual-s1', verdict: 'pass', detail: 'S1 잔존 0건' } };
|
|
98
|
+
}
|
|
99
|
+
const evidence = residual.map((row) => `${ruleLabel(book, row.ruleId)}: ${row.before} → ${row.after}`);
|
|
100
|
+
const scale = `S1 ${beforeTotal} → ${afterTotal}건`;
|
|
101
|
+
// 원문에 없던 S1이 생긴 경우는 introduced 축이 판정한다. 여기서 함께 abort를 내면
|
|
102
|
+
// 같은 사실을 두 축이 각자 판단하는 꼴이라, 한쪽이 회귀해도 다른 쪽이 가려서 안 잡힌다.
|
|
103
|
+
if (beforeTotal === 0) {
|
|
51
104
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
105
|
+
...base,
|
|
106
|
+
axis: {
|
|
107
|
+
axis: 'residual-s1',
|
|
108
|
+
verdict: 'warn',
|
|
109
|
+
detail: `원문에 없던 S1이 ${afterTotal}건 생김 — 유입 측면을 본다`,
|
|
110
|
+
evidence,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (removal <= 0) {
|
|
115
|
+
// 확정할 수 있는 데까지만 간다. 변경이 문턱 아래면 윤문을 안 한 것이다. 문턱을
|
|
116
|
+
// 넘었으면 탐지기 밖에서 고쳤을 수도 있다. 그 자리는 이 검사의 범위 밖이다.
|
|
117
|
+
const idle = rate < THRESHOLD.idleChange;
|
|
118
|
+
const grew = afterTotal > beforeTotal;
|
|
119
|
+
return {
|
|
120
|
+
...base,
|
|
121
|
+
axis: {
|
|
122
|
+
axis: 'residual-s1',
|
|
123
|
+
verdict: idle ? 'abort' : 'warn',
|
|
124
|
+
detail: idle
|
|
125
|
+
? `${scale}, 한 건도 못 줄이고 변경도 문턱 아래다 (변경률 ${pct(rate)}) — 채택 금지`
|
|
126
|
+
: `${scale}, ${grew ? '탐지 가능한 룰이 오히려 늘었다' : '탐지 가능한 룰은 안 줄었다'} (변경률 ${pct(rate)}) — 탐지기 밖 룰은 이 검사로 확인되지 않는다`,
|
|
127
|
+
evidence,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (removal < THRESHOLD.s1RemovalWarn) {
|
|
132
|
+
return {
|
|
133
|
+
...base,
|
|
134
|
+
axis: {
|
|
135
|
+
axis: 'residual-s1',
|
|
136
|
+
verdict: 'warn',
|
|
137
|
+
detail: `${scale} (제거율 ${pct(removal)}) — 절반도 못 줄임`,
|
|
138
|
+
evidence,
|
|
139
|
+
},
|
|
54
140
|
};
|
|
55
141
|
}
|
|
56
|
-
const evidence = residual.map((row) => `${ruleLabel(book, row.ruleId)}: ${row.before} → ${row.after}`);
|
|
57
142
|
return {
|
|
143
|
+
...base,
|
|
58
144
|
axis: {
|
|
59
145
|
axis: 'residual-s1',
|
|
60
146
|
verdict: 'warn',
|
|
61
|
-
detail:
|
|
147
|
+
detail: `${scale} (제거율 ${pct(removal)}), ${residual.length}종 잔존`,
|
|
62
148
|
evidence,
|
|
63
149
|
},
|
|
64
|
-
residual,
|
|
65
150
|
};
|
|
66
151
|
}
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
152
|
+
/**
|
|
153
|
+
* 철칙 — AI 티는 빼기만 하고 넣지 않는다.
|
|
154
|
+
*
|
|
155
|
+
* 구조 이상의 한 항목으로 묻어두면 새 티를 심어도 경고에서 그친다. 심각도가
|
|
156
|
+
* S1인 룰이 새로 들어왔으면 그건 윤문이 원문을 나쁘게 만든 것이라 채택하지 않는다.
|
|
157
|
+
*/
|
|
158
|
+
function checkIntroduced(book, register, introduced) {
|
|
159
|
+
if (introduced.length === 0) {
|
|
160
|
+
return { axis: 'introduced', verdict: 'pass', detail: '새로 심은 AI-tell 없음' };
|
|
161
|
+
}
|
|
162
|
+
const evidence = introduced
|
|
163
|
+
.slice(0, 5)
|
|
164
|
+
.map((row) => `${ruleLabel(book, row.ruleId)} ${row.before} → ${row.after}`);
|
|
165
|
+
const s1 = new Set(s1Ids(book, register));
|
|
166
|
+
const s1Rows = introduced.filter((row) => s1.has(row.ruleId));
|
|
167
|
+
if (s1Rows.length > 0) {
|
|
168
|
+
return {
|
|
169
|
+
axis: 'introduced',
|
|
170
|
+
verdict: 'abort',
|
|
171
|
+
detail: `S1 ${s1Rows.length}종을 새로 심음 — 채택 금지`,
|
|
172
|
+
evidence,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
axis: 'introduced',
|
|
177
|
+
verdict: 'warn',
|
|
178
|
+
detail: `${introduced.length}종 신규 유입`,
|
|
179
|
+
evidence,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** 늘어난 룰을 추린다. 판정은 checkIntroduced가 한다 */
|
|
183
|
+
function findIntroduced(beforeCounts, afterCounts) {
|
|
71
184
|
return [...afterCounts.entries()]
|
|
72
185
|
.map(([ruleId, count]) => ({ ruleId, before: beforeCounts.get(ruleId) ?? 0, after: count }))
|
|
73
186
|
.filter((row) => row.after > row.before)
|
|
@@ -85,7 +198,7 @@ function checkPreservation(before, after) {
|
|
|
85
198
|
evidence: missing.slice(0, 10),
|
|
86
199
|
};
|
|
87
200
|
}
|
|
88
|
-
function checkStructure(
|
|
201
|
+
function checkStructure(before, after) {
|
|
89
202
|
const a = structureStats(before);
|
|
90
203
|
const b = structureStats(after);
|
|
91
204
|
const problems = [];
|
|
@@ -99,9 +212,6 @@ function checkStructure(book, before, after, introduced) {
|
|
|
99
212
|
if (b.links < a.links) {
|
|
100
213
|
problems.push(`링크 ${a.links} → ${b.links}`);
|
|
101
214
|
}
|
|
102
|
-
for (const row of introduced.slice(0, 5)) {
|
|
103
|
-
problems.push(`${ruleLabel(book, row.ruleId)} 신규 유입 ${row.before} → ${row.after}`);
|
|
104
|
-
}
|
|
105
215
|
if (problems.length === 0) {
|
|
106
216
|
return { axis: 'structure', verdict: 'pass', detail: '구조·정보량 보존' };
|
|
107
217
|
}
|
|
@@ -128,18 +238,73 @@ function checkReportRegister(after) {
|
|
|
128
238
|
detail: '보고 본문 어미 일관',
|
|
129
239
|
};
|
|
130
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* 윤문 전에 원문을 훑는다.
|
|
243
|
+
*
|
|
244
|
+
* 두 가지를 한다. 첫째, S1이 0건이면 윤문 자체를 건너뛴다 — 고칠 게 없는 글에
|
|
245
|
+
* 윤문을 돌리면 남는 건 과윤문뿐이다. 둘째, 여기서 센 건수를 runCheck의 prescanned로
|
|
246
|
+
* 넘기면 그 값이 제거율의 기준선이 된다.
|
|
247
|
+
*
|
|
248
|
+
* 비용은 문서 크기가 아니라 걸린 룰 종류 수에 비례한다. scan 을 거치며 라벨과 처방을
|
|
249
|
+
* 조회하지만 그건 걸린 룰마다 한 번씩이라, 2.5KB 와 5MB 사이에서 오버헤드가 커지지 않는다.
|
|
250
|
+
* 룰북이 수백 룰로 자라면 그때 다시 본다.
|
|
251
|
+
*
|
|
252
|
+
* 기준선이 실제로 의미를 가지려면 윤문 전에 불러 그 값을 들고 있어야 한다. 원문과
|
|
253
|
+
* 윤문본을 한꺼번에 받는 자리(예: humanize-check CLI)에서는 검사 시점에 다시 세는
|
|
254
|
+
* 것과 결과가 같다. 그 자리에서 이 함수는 편의일 뿐이다. 기준선이 갈릴 수 있는
|
|
255
|
+
* 흐름은 윤문 파이프라인이 원문을 먼저 훑는 경우다.
|
|
256
|
+
*/
|
|
257
|
+
export function prescan(text, options = {}) {
|
|
258
|
+
// 세는 일은 scan 한 곳에만 둔다. 여기서 다시 세면 같은 집계가 두 벌이 된다.
|
|
259
|
+
// 룰북 해석이 갈리는 날 어느 쪽이 맞는지 알 수 없게 된다.
|
|
260
|
+
const report = scan(text, options);
|
|
261
|
+
return {
|
|
262
|
+
register: report.register,
|
|
263
|
+
s1Total: report.s1Total,
|
|
264
|
+
s1ByRule: new Map(report.hits.map((hit) => [hit.ruleId, hit.count])),
|
|
265
|
+
worthHumanizing: report.worthHumanizing,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
131
268
|
export function runCheck(before, after, options = {}) {
|
|
132
269
|
const register = options.register ?? 'doc';
|
|
133
270
|
const book = options.book ?? parseRuleBook();
|
|
134
271
|
const rate = changeRate(before, after);
|
|
135
272
|
const rateNoMarkup = changeRate(before, after, { ignoreMarkup: true });
|
|
273
|
+
// 탐지기는 텍스트당 한 번만 돌린다. 잔존 축은 S1 부분집합을, 유입 축은 전체를 보는데
|
|
274
|
+
// 룰 필터만 다른 같은 순회라, 따로 부르면 같은 문자열에 정규식이 두 번씩 돌아간다.
|
|
275
|
+
//
|
|
276
|
+
// 다만 이 절약은 detect 안에서만이다. 아래 checkStructure의 structureStats가
|
|
277
|
+
// proseOnly를 다시 계산한다. report 말투면 reportRegisterStats가 한 번 더 한다.
|
|
278
|
+
//
|
|
279
|
+
// 안 건드리는 근거는 실측으로 확인했다. 그 중복이 차지하는 비중이 1KB에서 1MB까지
|
|
280
|
+
// 어느 구간에서도 7퍼센트를 안 넘는다. changeRate가 지배한다는 말은 어절 6000개
|
|
281
|
+
// 이하에서만 맞고 그 위로는 절반으로 준다. 다만 그 자리를 메우는 건 detect 본연의
|
|
282
|
+
// 계산이지 이 중복이 아니라, 결론은 전 구간에서 같다.
|
|
283
|
+
const beforeAll = countByRule(before);
|
|
284
|
+
const afterAll = countByRule(after);
|
|
285
|
+
// prescanned는 윤문에 들어가기 전에 확정한 기준선이라 지금 센 값보다 우선한다
|
|
286
|
+
const counts = { before: options.prescanned ?? beforeAll, after: afterAll };
|
|
287
|
+
// 유입 축도 잔존 축과 같은 기준선을 봐야 한다. 잔존 축이 prescanned 기준으로
|
|
288
|
+
// "원문에 S1이 없었다"며 유입 축에 판정을 넘겼는데, 유입 축이 지금 센 원문을 보고
|
|
289
|
+
// "안 늘었다"고 하면 두 축 사이로 빠져나가는 자리가 생긴다.
|
|
290
|
+
const introducedBase = options.prescanned
|
|
291
|
+
? new Map([...beforeAll, ...options.prescanned])
|
|
292
|
+
: beforeAll;
|
|
136
293
|
const changeAxis = checkChangeRate(rate, rateNoMarkup);
|
|
137
|
-
const
|
|
294
|
+
const s1 = checkResidualS1(book, register, counts, rate);
|
|
138
295
|
const preservationAxis = checkPreservation(before, after);
|
|
139
|
-
const introduced = findIntroduced(
|
|
140
|
-
const
|
|
296
|
+
const introduced = findIntroduced(introducedBase, afterAll);
|
|
297
|
+
const introducedAxis = checkIntroduced(book, register, introduced);
|
|
298
|
+
const structureAxis = checkStructure(before, after);
|
|
141
299
|
const reportAxis = register === 'report' ? [checkReportRegister(after)] : [];
|
|
142
|
-
const axes = [
|
|
300
|
+
const axes = [
|
|
301
|
+
changeAxis,
|
|
302
|
+
s1.axis,
|
|
303
|
+
preservationAxis,
|
|
304
|
+
structureAxis,
|
|
305
|
+
introducedAxis,
|
|
306
|
+
...reportAxis,
|
|
307
|
+
];
|
|
143
308
|
const verdict = worst(axes.map((a) => a.verdict));
|
|
144
309
|
return {
|
|
145
310
|
register,
|
|
@@ -147,13 +312,57 @@ export function runCheck(before, after, options = {}) {
|
|
|
147
312
|
exitCode: EXIT_CODE[verdict],
|
|
148
313
|
changeRate: rate,
|
|
149
314
|
changeRateNoMarkup: rateNoMarkup,
|
|
315
|
+
s1Removal: s1.removal,
|
|
316
|
+
s1Before: s1.beforeTotal,
|
|
317
|
+
s1After: s1.afterTotal,
|
|
150
318
|
axes,
|
|
151
|
-
residualS1: residual,
|
|
319
|
+
residualS1: s1.residual,
|
|
152
320
|
introduced,
|
|
153
321
|
};
|
|
154
322
|
}
|
|
155
|
-
|
|
156
|
-
|
|
323
|
+
/** 윤문은 한 번만 다시 시킨다. 두 번째도 막히면 원문이 답이다 */
|
|
324
|
+
export const MAX_ATTEMPTS = 2;
|
|
325
|
+
/**
|
|
326
|
+
* 검사 결과를 다음 행동으로 옮긴다.
|
|
327
|
+
*
|
|
328
|
+
* "exit 2면 채택하지 않는다"를 문서에만 적어두면 모델이 자기 결과를 스스로
|
|
329
|
+
* 버려야 성립한다. 그건 지켜지지 않는다. 판정과 지시를 코드가 같이 낸다.
|
|
330
|
+
* 실행하는 쪽은 따르기만 한다.
|
|
331
|
+
*
|
|
332
|
+
* 재시도가 소진되면 윤문본을 버리고 원문을 낸다. 윤문이 원문보다 나아졌다고
|
|
333
|
+
* 증명하지 못했으니, 남는 선택지는 원문뿐이다.
|
|
334
|
+
*/
|
|
335
|
+
export function decide(report, attempt = 1, maxAttempts = MAX_ATTEMPTS) {
|
|
336
|
+
if (report.verdict === 'pass') {
|
|
337
|
+
return { action: 'accept', message: '검사 통과. 윤문본을 채택한다.' };
|
|
338
|
+
}
|
|
339
|
+
if (report.verdict === 'warn') {
|
|
340
|
+
const flagged = report.axes
|
|
341
|
+
.filter((axis) => axis.verdict === 'warn')
|
|
342
|
+
.map((axis) => axis.axis)
|
|
343
|
+
.join(', ');
|
|
344
|
+
return {
|
|
345
|
+
action: 'accept-with-warning',
|
|
346
|
+
message: `윤문본을 채택하되 걸린 측면을 요약에 적는다: ${flagged}`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const blocking = report.axes
|
|
350
|
+
.filter((axis) => axis.verdict === 'abort')
|
|
351
|
+
.map((axis) => axis.detail)
|
|
352
|
+
.join(' / ');
|
|
353
|
+
if (attempt < maxAttempts) {
|
|
354
|
+
return {
|
|
355
|
+
action: 'retry',
|
|
356
|
+
message: `${blocking} — 윤문본을 버리고 원문에서 한 번 더 윤문한 뒤 --attempt ${attempt + 1}로 다시 검사한다.`,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
action: 'fallback',
|
|
361
|
+
message: `${blocking} — 재시도(${maxAttempts}회)를 소진했다. 윤문본을 버리고 원문을 그대로 낸다.`,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
export function formatReport(report, attempt = 1) {
|
|
365
|
+
const head = `검사 ${report.verdict.toUpperCase()} (exit ${report.exitCode}) / 말투 ${report.register} / 시도 ${attempt}`;
|
|
157
366
|
const lines = [head, ''];
|
|
158
367
|
for (const axis of report.axes) {
|
|
159
368
|
const mark = axis.verdict === 'pass' ? 'OK' : axis.verdict === 'warn' ? '경고' : '중단';
|
|
@@ -162,6 +371,8 @@ export function formatReport(report) {
|
|
|
162
371
|
lines.push(` ${item}`);
|
|
163
372
|
}
|
|
164
373
|
}
|
|
374
|
+
const decision = decide(report, attempt);
|
|
375
|
+
lines.push('', `[다음] ${decision.action} — ${decision.message}`);
|
|
165
376
|
return lines.join('\n');
|
|
166
377
|
}
|
|
167
378
|
//# sourceMappingURL=check.js.map
|