@tienne/gestalt 0.70.0 → 0.71.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/README.md +1 -1
- package/dist/package.json +1 -1
- package/dist/plugin/role-agents/_shared/references/README.md +1 -1
- package/dist/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +13 -9
- package/dist/plugin/role-agents/_shared/references/author-voice.md +8 -8
- package/dist/plugin/role-agents/_shared/references/style-guide.md +13 -0
- package/dist/plugin/role-agents/humanize-monolith/AGENT.md +1 -1
- package/dist/src/cli/commands/humanize-check.d.ts.map +1 -1
- package/dist/src/cli/commands/humanize-check.js +5 -6
- package/dist/src/cli/commands/humanize-check.js.map +1 -1
- package/dist/src/cli/commands/humanize-scan.d.ts +4 -2
- package/dist/src/cli/commands/humanize-scan.d.ts.map +1 -1
- package/dist/src/cli/commands/humanize-scan.js +15 -9
- package/dist/src/cli/commands/humanize-scan.js.map +1 -1
- package/dist/src/core/types.d.ts +1 -1
- package/dist/src/humanize/detectors.d.ts +31 -2
- package/dist/src/humanize/detectors.d.ts.map +1 -1
- package/dist/src/humanize/detectors.js +192 -7
- package/dist/src/humanize/detectors.js.map +1 -1
- package/dist/src/humanize/index.d.ts +1 -1
- package/dist/src/humanize/index.d.ts.map +1 -1
- package/dist/src/humanize/index.js +1 -1
- package/dist/src/humanize/index.js.map +1 -1
- package/dist/src/humanize/read-input.d.ts +21 -0
- package/dist/src/humanize/read-input.d.ts.map +1 -0
- package/dist/src/humanize/read-input.js +51 -0
- package/dist/src/humanize/read-input.js.map +1 -0
- package/dist/src/humanize/scan.d.ts +13 -0
- package/dist/src/humanize/scan.d.ts.map +1 -1
- package/dist/src/humanize/scan.js +16 -5
- package/dist/src/humanize/scan.js.map +1 -1
- package/dist/src/mcp/schemas.js +1 -1
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/role-agents/_shared/references/README.md +1 -1
- package/plugin/role-agents/_shared/references/ai-tell-quick-rules.md +13 -9
- package/plugin/role-agents/_shared/references/author-voice.md +8 -8
- package/plugin/role-agents/_shared/references/style-guide.md +13 -0
- package/plugin/role-agents/humanize-monolith/AGENT.md +1 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 룰 ID별 결정적 탐지기.
|
|
3
3
|
*
|
|
4
|
-
* 여기 있는 건
|
|
5
|
-
*
|
|
4
|
+
* 여기 있는 건 정규식으로 셀 수 있는 룰뿐이다. D-5 의인화 주어처럼 뜻을 봐야 판단되는
|
|
5
|
+
* 룰은 일부러 뺐다 — 멀쩡한 문장에서 검사가 멈추면 아무도 안 쓴다.
|
|
6
|
+
*
|
|
7
|
+
* 전부 정확히 걸러내지는 못한다. 잘못 감지가 남는 갈래는 그 자리 주석이 어디까지 새는지 적고
|
|
8
|
+
* 코퍼스가 그 경계를 잡는다.
|
|
6
9
|
* 탐지기가 없는 룰은 모델이 자체검증으로 본다.
|
|
7
10
|
*/
|
|
8
11
|
const SAMPLE_CAP = 3;
|
|
12
|
+
const SAMPLE_MAX = 80;
|
|
9
13
|
/**
|
|
10
14
|
* 룰을 적용할 산문 줄만 남긴다.
|
|
11
15
|
*
|
|
@@ -46,6 +50,20 @@ function proseOnly(text, options = {}) {
|
|
|
46
50
|
.join('\n')
|
|
47
51
|
.replace(/`[^`\n]+`/g, ' ');
|
|
48
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* 보고문에 실릴 조각을 눌러 둔다.
|
|
55
|
+
*
|
|
56
|
+
* 스캔 결과는 모델이 지시로 읽는 텍스트이고 sample 은 사용자 파일에서 잘라낸 원문이다.
|
|
57
|
+
* 보고문이 큰따옴표로 감싸는 틀이라 인용 경계와 코드 표기를 흉내 낼 문자를 지운다.
|
|
58
|
+
* 길이도 자른다 — D-9의 (?:\S+\s+)? 처럼 임의 토큰을 삼키는 탐지기가 있어서
|
|
59
|
+
* 매치 하나가 수백 자로 늘어날 수 있다.
|
|
60
|
+
*
|
|
61
|
+
* 어투 탐지기와 맞춤법 검사가 같이 지나는 자리에 둔다. 한쪽만 누르면 표면이 넓은
|
|
62
|
+
* 쪽이 그대로 새는데, 실제로 맞춤법만 누른 판이 그랬다.
|
|
63
|
+
*/
|
|
64
|
+
function clampSample(sample) {
|
|
65
|
+
return sample.replace(/[`"']|\p{C}/gu, ' ').slice(0, SAMPLE_MAX);
|
|
66
|
+
}
|
|
49
67
|
function matcher(ruleId, re) {
|
|
50
68
|
return {
|
|
51
69
|
ruleId,
|
|
@@ -78,6 +96,40 @@ export function splitSentences(text) {
|
|
|
78
96
|
// 한글 뒤에는 \b가 성립하지 않는다(자모가 \w가 아님). 어미 끝은 후행 부정 탐색으로 막는다.
|
|
79
97
|
const NOT_HANGUL = '(?![가-힣])';
|
|
80
98
|
const JOSA = '(?:는|도|만|요)?';
|
|
99
|
+
/**
|
|
100
|
+
* I-6 의 "산출" 갈래. 뒤에 무엇이 오는지로 판정 자리와 파생명사를 가른다.
|
|
101
|
+
*
|
|
102
|
+
* 닫힌 목록인 이유는 반대쪽이 안 닫히기 때문이다. 산출 뒤에 붙는 파생어(산출물,
|
|
103
|
+
* 산출량, 산출도구, 산출로직)는 합성 가능한 명사라 끝이 없는데, 활용 어미와 조사는
|
|
104
|
+
* 한국어 형태론상 유한하다. 막을 것보다 열 것을 세는 쪽이 닫힌다.
|
|
105
|
+
*
|
|
106
|
+
* 오른쪽을 막는 갈래는 둘이다. 활용형 하, 한, 할, 함, 해는 뒤 글자를 함께 봐서
|
|
107
|
+
* "산출하위", "산출한계", "산출할당량", "산출함수", "산출해상도"를 뺀다.
|
|
108
|
+
* 조사 갈래(은, 는, 이, 을, 과, 와, 에, 도, 만, 로)는 통째로 NOT_HANGUL 을 붙여
|
|
109
|
+
* "산출도구", "산출로직", "산출과정"을 뺀다.
|
|
110
|
+
* 했, 되, 된, 될, 됐, 돼는 합성어 첫 글자로 쓰일 일이 없어 안 막는다.
|
|
111
|
+
* 에서, 까지, 부터는 두 글자라 같은 이유로 안 막는다. 그래서 이 갈래만 "산출에서는"처럼
|
|
112
|
+
* 보조사가 더 붙은 꼴을 잡는다.
|
|
113
|
+
*
|
|
114
|
+
* 어미를 하나 늘릴 때는 이 배열에 줄을 더하고 코퍼스에도 hit 과 반대편 miss 를 함께
|
|
115
|
+
* 넣는다. 넓히면 합성어가 다시 열리는지는 코퍼스가 잡아 준다.
|
|
116
|
+
*
|
|
117
|
+
* 조사 갈래를 문자 클래스로 옮기면서 "으"를 뺐다. "산출으로"는 비문이라 잡을 자리가 아니다.
|
|
118
|
+
*/
|
|
119
|
+
const SANCHUL = [
|
|
120
|
+
'하[여는지고자기며면니게였죠다라세려겠신십]',
|
|
121
|
+
'하도록',
|
|
122
|
+
'한(?=다|[^가-힣])',
|
|
123
|
+
'했',
|
|
124
|
+
'할(?=[^가-힣])',
|
|
125
|
+
'함(?=[^가-힣]|으로|이|을|에|은|도)',
|
|
126
|
+
'해[야서도써라]',
|
|
127
|
+
'합니|됩니',
|
|
128
|
+
'되|된|될|됐|돼',
|
|
129
|
+
'에서|까지|부터',
|
|
130
|
+
`(?:[은는이을과와에도만]|로)${NOT_HANGUL}`,
|
|
131
|
+
].join('|');
|
|
132
|
+
const I6 = new RegExp(`실측|계측|오탐|산출(?=${SANCHUL})`, 'g');
|
|
81
133
|
const DETECTORS = [
|
|
82
134
|
matcher('A-1', new RegExp(`[가-힣)\\]]\\s*에\\s*대(?:해서|하여|해|한)${JOSA}${NOT_HANGUL}`, 'g')),
|
|
83
135
|
matcher('A-2', new RegExp(`[가-힣)\\]]\\s*(?:를|을)?\\s*통(?:해서|하여|해)${JOSA}${NOT_HANGUL}`, 'g')),
|
|
@@ -88,7 +140,7 @@ const DETECTORS = [
|
|
|
88
140
|
matcher('A-8', /되어\s*(?:진|지[다고며])|지게\s*(?:된|되[다었어고])|되어\s*졌/g),
|
|
89
141
|
matcher('A-9', new RegExp(`[가-힣)\\]]\\s*에\\s*의(?:해서|하여|해)${NOT_HANGUL}`, 'g')),
|
|
90
142
|
matcher('A-19', new RegExp(`[가-힣]\\s*(?:에서의|에로의|으로의|로의|에의|으로부터의)${NOT_HANGUL}`, 'g')),
|
|
91
|
-
// 미덕(virtue)
|
|
143
|
+
// 미덕(virtue)과 불투명(opaque)은 한국어에서 제 뜻으로도 쓰여 정규식이 잘못 감지한다. 표에만 두고 모델이 본다
|
|
92
144
|
// 의미론은 조사가 붙어 오므로 NOT_HANGUL을 못 쓴다. 형용사로 굳은 "의미론적"만 뺀다
|
|
93
145
|
matcher('B-5', /물질화|내구성\s*있|의미론(?!적)/g),
|
|
94
146
|
matcher('C-11', /[가-힣](?:하고|하며|되고|되며|고|며|지만|면서|아서|어서),/g),
|
|
@@ -99,22 +151,97 @@ const DETECTORS = [
|
|
|
99
151
|
matcher('D-4', /(?:파격적|압도적|획기적|혁신적|전례\s*없|폭발적)/g),
|
|
100
152
|
matcher('D-6', /(?:할\s*때다|할\s*때입니다|할\s*시점|지금이야말로|할\s*순간)/g),
|
|
101
153
|
matcher('D-8', /(?:뼈아[프픈팠]|뜨끔|부끄럽|(?:제일|가장|많이)\s*아팠)/g),
|
|
154
|
+
// 방향, 결론, 판단, 논의가 스스로 움직이는 자리를 본다. 사이에 토큰 하나까지는
|
|
155
|
+
// 건너뛰되 품사는 안 가린다. 조사를 필수로 둬야 부사격 "그 방향으로 갔어요"가 빠진다 —
|
|
156
|
+
// 그건 룰북이 처방으로 내놓은 형태라 걸리면 처방을 다시 고치라고 시키는 꼴이 된다
|
|
157
|
+
matcher('D-9', /(?:방향|결론|판단|논의)[이가은는도]\s*(?:\S+\s+)?(?:갔|가고|간다)/g),
|
|
102
158
|
// '잠금 파일'처럼 명사로 굳은 자리는 빼고 동사형만 본다. 테스트나 기준을
|
|
103
159
|
// 자물쇠에 빗대는 자리가 대상이다 — "테스트로 잠갔다" → "관련 테스트가 있다"
|
|
104
160
|
matcher('F-7', /(?:증류|배선|결정화|평탄화|오케스트레이션|파이프라인화|잠[그근갔글가긴겨겼기](?![가-힣]*파일))/g),
|
|
161
|
+
// 목적어가 추상명사인 자리만 본다 — "온도를 재봤는데"는 멀쩡한 물리적 용법이다.
|
|
162
|
+
// 어미를 통째로 확인해야 "재보정", "재보고서"처럼 접두사 재-에 보-가 붙은 말이 빠진다.
|
|
163
|
+
// 문자 클래스로 "보" 한 글자만 보면 그 둘이 그대로 걸린다.
|
|
164
|
+
// 다만 "재보고 있-"은 진행형과 형태가 같아 열어 뒀다
|
|
165
|
+
matcher('F-8', /(?:근거|의견|판단|영향|의미|가치|리스크)(?:를|을)\s*(?:재(?=봤|봐|보니|보면|본다|보았|보고\s*있|본(?![가-힣]))|달아(?=[봤봐본보았뒀둔두])|달았)/g),
|
|
105
166
|
matcher('G-2', /(?:로\s*보인다|인\s*듯하다|로\s*판단된다|라고\s*여겨진다|로\s*여겨진다)/g),
|
|
106
167
|
matcher('I-1', /(?:인\s*것이다|한\s*것이다|는\s*것이다|일\s*것이다)/g),
|
|
107
168
|
matcher('I-3', /(?:다는\s*뜻이다|다는\s*의미다|다는\s*것이다)/g),
|
|
108
|
-
|
|
169
|
+
// "그"만 공백을 요구한다. 안 그러면 "그건"("것은"의 준말)이 걸리는데,
|
|
170
|
+
// author-voice.md 가 그건 그대로 두라고 예외로 적어둔 자리다
|
|
171
|
+
matcher('I-5', new RegExp(`(?:해당|이번)\\s*건${NOT_HANGUL}|그\\s+건${NOT_HANGUL}|[가-힣]\\s건(?:은|이|을|에|도)${NOT_HANGUL}`, 'g')),
|
|
172
|
+
matcher('I-6', I6),
|
|
173
|
+
// 리뷰 코멘트를 가리키는 호칭만 본다 — 내 것은 "남겼던 의견", 상대 것은 "짚어주신 부분".
|
|
174
|
+
// 어떤 꼴을 여는지는 정규식과 코퍼스의 I-7 항목이 갖고 있다.
|
|
175
|
+
//
|
|
176
|
+
// 활용형을 지적하-까지 그냥 열면 "원칙 위반을 지적하고" 같은 일반 동사가 걸린다.
|
|
177
|
+
// 그래서 뒤에 오는 명사로 묶는데, 그것만으로는 3인칭 서술을 못 가른다 —
|
|
178
|
+
// 그래서 주어 없는 갈래에도 lookbehind 를 붙였다. 앞이 한글 + 이, 가, 은, 는이면
|
|
179
|
+
// 3인칭 주어로 보고 뺀다 — "논문이 지적했던 부분", "논문은 지적했던 부분"이 빠지고
|
|
180
|
+
// 주어를 생략한 "지적했던 부분"은 남는다.
|
|
181
|
+
//
|
|
182
|
+
// 그 lookbehind 는 줄바꿈 하나까지만 넘는다. 개행에는 문단 경계와 그냥 줄을 접은 자리가 섞여
|
|
183
|
+
// 있어서다 — 이 레포 마크다운은 백 자 언저리에서 줄을 접으므로 3인칭 주어와 서술어가
|
|
184
|
+
// 다른 줄에 놓인다. 그렇다고 개행을 무제한 넘기면 앞 문단이 조사로 끝났을 때
|
|
185
|
+
// 다음 문단 첫 문장이 통째로 빠진다. 하나만 넘겨 양쪽을 다 막는다.
|
|
186
|
+
// 윈도우 줄바꿈은 두 글자라 \r 도 함께 넘긴다.
|
|
187
|
+
//
|
|
188
|
+
// 여기까지가 형태로 가를 수 있는 끝이다. 앞 줄이 조사로 끝나고 빈 줄 없이 다음 줄에서
|
|
189
|
+
// 새 문장이 시작하면 줄을 접은 자리와 글자열이 똑같아 못 가른다. 주제격 "는" 뒤에
|
|
190
|
+
// 주어를 생략한 서술이 오는 자리도 마찬가지다 — 3인칭 주어로 오인한다.
|
|
191
|
+
// 더 좁은 문자 클래스로 의미 경계를 흉내 내면 반대편이 열린다. 더 좁히지 않는다.
|
|
192
|
+
//
|
|
193
|
+
// 그 lookbehind 는 "우리가", "저는"처럼 1인칭 화자의 꼬리도 함께 막는다. 그건 잡아야
|
|
194
|
+
// 하는 자리라 화자 갈래를 앞에 둬서 거기서 먼저 걸리게 한다. 그래서 화자 목록에
|
|
195
|
+
// 1인칭 대명사와 주격, 주제격 조사의 조합 여덟을 다 적는다 — 하나라도 빠뜨리면
|
|
196
|
+
// lookbehind 가 그 자리를 3인칭으로 오인한다. 실제로 "가" 꼴만 적었을 때
|
|
197
|
+
// "저는 지적했던 부분"이 통째로 빠졌다.
|
|
198
|
+
//
|
|
199
|
+
// 두 갈래의 활용형이 다르다. 화자 갈래는 한, 했던, 했었던을 열고 주어 없는 갈래는
|
|
200
|
+
// 했던과 했었던만 연다. 주어가 없으면 "지적한"이 일반 동사와 구별이 안 돼서다.
|
|
201
|
+
//
|
|
202
|
+
// 화자 갈래는 왼쪽을 lookbehind 로 막는다. 안 막으면 "문제가", "주제가"의 꼬리가
|
|
203
|
+
// 화자로 읽힌다. 단독 "제"는 "실제", "규제"의 꼬리까지 먹어서 아예 뺐다.
|
|
204
|
+
//
|
|
205
|
+
// 화자와 "지적" 사이는 토큰 셋까지 허용한다 — "제가 리뷰에서 여러 번 지적한 부분"이
|
|
206
|
+
// 그만큼 벌어진다. 이 범위를 아래에서 허용 범위라 부른다.
|
|
207
|
+
//
|
|
208
|
+
// 그 토큰에서 마침표, 쉼표, 느낌표, 물음표, 콜론, 세미콜론, 괄호, 말줄임표를 뺀다.
|
|
209
|
+
// 전각 마침표와 쉼표도 함께 뺀다.
|
|
210
|
+
// 이, 가, 은, 는으로 끝나는 토큰도 뺀다. 노린 건 3인칭 주어다. 안 빼면 허용 범위가
|
|
211
|
+
// 절 경계를 넘어 "제가 보기에 그 논문이 지적한 부분"의 주어를 통째로 삼킨다.
|
|
212
|
+
// 토큰 길이도 스무 자로 묶는다. 안 묶으면 공백 없는 입력에서 앞의 부정 선읽기가
|
|
213
|
+
// 매 화자 위치마다 끝까지 훑어 길이의 제곱으로 늘어난다. "제가x"를 만 육천 번 이은
|
|
214
|
+
// 4만8천 자에서 2980ms 였다 — 재현하려면 공백이 하나도 없어야 한다.
|
|
215
|
+
// 그 입력은 detector-perf.test.ts 가 시간 상한과 함께 붙잡는다.
|
|
216
|
+
//
|
|
217
|
+
// 다만 형태소 분석이 없어 글자로 자른다. 그래서 주격 조사가 아닌 것도 함께 빠진다 —
|
|
218
|
+
// 부사 "많이", "깊이", 관형사 "이", 부사어 "처음에는"이 사이에 오면 그 문장은 놓친다.
|
|
219
|
+
// 반대로 조사 없이 오는 3인칭 주어("제가 보기에 논문 저자 지적한 부분")는 통과한다.
|
|
220
|
+
// 이 둘은 형태가 같아서 정규식으로는 못 가른다. 더 좁히면 반대편이 열려서 멈춘다.
|
|
221
|
+
// 이 한계들은 코퍼스가 "한계" 라벨로 고정한다. 허용 범위를 건드리면 거기서 깨진다.
|
|
222
|
+
//
|
|
223
|
+
// 이 룰이 산출물 문장에만 걸린다는 근거는
|
|
224
|
+
// author-voice.md §「리뷰 코멘트를 "지적"이라고 부르지 않는다 (ai-tell I-7)」의
|
|
225
|
+
// 마지막 문단에 있다. 정규식은 그 구분을 못 하니
|
|
226
|
+
// 룰 문서에서 나는 hit 은 인용 예외로 걸러 읽는다
|
|
227
|
+
matcher('I-7', new RegExp(`지적\\s*(?:을|이|은|도|만|에|의|과|와)${NOT_HANGUL}|지적(?:해\\s*주|하신|받|당)|지적\\s*\\d+\\s*건|지적\\s*사항|지적들|(?<![가-힣])(?:제가|내가|우리가|저희가|저는|나는|우리는|저희는)\\s*(?:(?![^\\s.,、!?。:;()…]{0,20}[이가은는]\\s)[^\\s.,、!?。:;()…]{1,20}\\s+){0,3}지적(?:한|했던|했었던)\\s*(?:부분|점|내용|사항|코멘트|의견|건)|(?<![가-힣][이가은는][ \\t]*\\r?\\n?[ \\t]*)지적했(?:었)?던\\s*(?:부분|내용|코멘트|의견)`, 'g')),
|
|
228
|
+
// 관형형 어간을 열거해야 명사+보조사("설계는 수준이")와 등급을 말하는 동사 관형형
|
|
229
|
+
// ("요구되는 수준")이 빠진다. [가-힣]는 으로 열면 둘 다 걸린다.
|
|
230
|
+
// 보 는 단독으로 두면 "정보는", "확보는"의 끝 음절을 먹어서 복합 어간으로만 적는다.
|
|
231
|
+
// 하 는 같은 구멍이 남지만("부하는 수준이") 그걸 빼면 "확인하는 수준"을 통째로 놓친다.
|
|
232
|
+
// 뒤따르는 조사는 안 본다 — 어간 열거가 이미 등급 자리를 걸러서, 조사까지 막으면
|
|
233
|
+
// "다듬는 수준으로 끝냈어요" 처럼 잡아야 할 자리만 빠진다
|
|
234
|
+
matcher('I-8', /(?:하|다듬|손보|고치|살펴보|알아보|들여다보|넘기|훑)는\s*수준/g),
|
|
109
235
|
sentenceInitial('H-1', ['또한', '따라서', '즉', '나아가', '아울러', '게다가', '더욱이']),
|
|
110
236
|
sentenceInitial('H-3', ['이는', '이\\s*점에서', '이\\s*관점에서', '이\\s*말은']),
|
|
111
237
|
];
|
|
112
238
|
export const DETECTABLE_RULE_IDS = DETECTORS.map((d) => d.ruleId);
|
|
113
239
|
export function detect(text, ruleIds) {
|
|
240
|
+
return detectOn(proseOnly(text), ruleIds);
|
|
241
|
+
}
|
|
242
|
+
function detectOn(prose, ruleIds) {
|
|
114
243
|
const wanted = ruleIds ? new Set(ruleIds) : null;
|
|
115
244
|
const results = [];
|
|
116
|
-
// 탐지기 24개가 각자 부르면 줄 분할과 인용 제거가 스물네 번 반복된다. 한 번만 한다
|
|
117
|
-
const prose = proseOnly(text);
|
|
118
245
|
for (const detector of DETECTORS) {
|
|
119
246
|
if (wanted && !wanted.has(detector.ruleId))
|
|
120
247
|
continue;
|
|
@@ -124,11 +251,58 @@ export function detect(text, ruleIds) {
|
|
|
124
251
|
results.push({
|
|
125
252
|
ruleId: detector.ruleId,
|
|
126
253
|
count: hits.length,
|
|
127
|
-
samples: [...new Set(hits)].slice(0, SAMPLE_CAP),
|
|
254
|
+
samples: [...new Set(hits)].map(clampSample).slice(0, SAMPLE_CAP),
|
|
128
255
|
});
|
|
129
256
|
}
|
|
130
257
|
return results;
|
|
131
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* 후보 넷을 돌려보고 둘만 남겼다.
|
|
261
|
+
*
|
|
262
|
+
* 백틱 안에 조사가 들어간 자리("`c:로`")는 레포에서 8건이 걸렸는데 그중 여섯이
|
|
263
|
+
* `파일:라인`, `human:이름` 같은 형식 표기였다. 영문 일반 뒤 조사("main 에")는 인라인
|
|
264
|
+
* 코드를 지운 자국과 작성자가 인용한 코드 예시를 통째로 먹었다.
|
|
265
|
+
*
|
|
266
|
+
* 공백을 같은 줄로 한정하는 건 \s+ 가 줄바꿈을 건너뛰어 다음 줄 첫 조사에 붙기 때문이다.
|
|
267
|
+
* 왼쪽 경계를 후행 부정으로 막는 건 성능이다 — 그게 없으면 영문 러닝 안 모든 위치에서
|
|
268
|
+
* 매칭을 다시 시작해 길이의 제곱으로 늘어난다. 6만 자에서 5.9초가 0.5밀리초가 됐다.
|
|
269
|
+
*/
|
|
270
|
+
const SPACING = [
|
|
271
|
+
{
|
|
272
|
+
label: '식별자 뒤 조사를 띄어 썼다',
|
|
273
|
+
fix: '붙여 쓴다 (`6564d04 에서` → `6564d04에서`)',
|
|
274
|
+
// 16진수로만 이뤄진 영단어를 거르려고 숫자를 최소 하나 요구한다
|
|
275
|
+
re: /\b(?=[0-9a-f]*[0-9])[0-9a-f]{7,40}[ \t]+(?:에서|에|로|은|는|이|가|을|를|와|과|의)(?![가-힣])/g,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
label: '외래어와 하다를 띄어 썼다',
|
|
279
|
+
fix: '붙여 쓰거나 우리말로 (`Approve 합니다` → `Approve합니다` / `승인합니다`)',
|
|
280
|
+
re: /(?<![A-Za-z])[A-Za-z]{2,}[ \t]+(?:합니다|했습니다|해요|할게요|하겠습니다|한다|하면|하고)(?![가-힣])/g,
|
|
281
|
+
},
|
|
282
|
+
];
|
|
283
|
+
/**
|
|
284
|
+
* 어투가 아니라 맞춤법인 자리를 센다. 등급과 따로 가는 값이다.
|
|
285
|
+
*
|
|
286
|
+
* 리뷰 코멘트는 커밋 해시나 브랜치명을 문장에 그대로 섞기 때문에 조사 처리가 반복해서
|
|
287
|
+
* 어긋난다 (author-voice.md §「기계적 점검 (어투는 아니지만 자주 틀리는 것)」).
|
|
288
|
+
* S1 총계에 섞으면 윤문 등급이 맞춤법 때문에 떨어지므로 ScanReport가 이 값을 따로 들고 간다.
|
|
289
|
+
*/
|
|
290
|
+
export function spacingIssues(text) {
|
|
291
|
+
return spacingOn(proseOnly(text));
|
|
292
|
+
}
|
|
293
|
+
function spacingOn(prose) {
|
|
294
|
+
const out = [];
|
|
295
|
+
for (const { label, fix, re } of SPACING) {
|
|
296
|
+
const hits = [...prose.matchAll(re)].map((m) => m[0].trim());
|
|
297
|
+
if (hits.length === 0)
|
|
298
|
+
continue;
|
|
299
|
+
// 샘플은 사용자 파일에서 잘라낸 원문이고 보고문은 모델이 지시로 읽는다.
|
|
300
|
+
// 개행과 백틱을 눌러 두면 잘라낸 조각이 보고문의 구조를 흉내 내지 못한다
|
|
301
|
+
const samples = [...new Set(hits)].map(clampSample).slice(0, SAMPLE_CAP);
|
|
302
|
+
out.push({ label, fix, count: hits.length, samples });
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
132
306
|
export function countByRule(text, ruleIds) {
|
|
133
307
|
return new Map(detect(text, ruleIds).map((d) => [d.ruleId, d.count]));
|
|
134
308
|
}
|
|
@@ -186,4 +360,15 @@ export function structureStats(text) {
|
|
|
186
360
|
links: (text.match(/\[[^\]]+\]\([^)]+\)/g) ?? []).length,
|
|
187
361
|
};
|
|
188
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* 어투와 맞춤법을 한 산문 위에서 함께 본다.
|
|
365
|
+
*
|
|
366
|
+
* detect 와 spacingIssues 를 따로 부르면 줄 분할과 인용 제거가 두 번 돈다. 비용은 작지만
|
|
367
|
+
* (500KB 입력에서 12%) 진짜 문제는 나중에 한쪽만 산문 기준이 바뀌면 어투와 맞춤법이 서로
|
|
368
|
+
* 다른 텍스트를 보게 되는 것이다. 산문 정의를 여기 한 군데로 모은다.
|
|
369
|
+
*/
|
|
370
|
+
export function scanProse(text, ruleIds) {
|
|
371
|
+
const prose = proseOnly(text);
|
|
372
|
+
return { detections: detectOn(prose, ruleIds), spacing: spacingOn(prose) };
|
|
373
|
+
}
|
|
189
374
|
//# sourceMappingURL=detectors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectors.js","sourceRoot":"","sources":["../../../src/humanize/detectors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"detectors.js","sourceRoot":"","sources":["../../../src/humanize/detectors.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { parseRuleBook, parseRegister, ruleLabel, s1Ids, type RuleScanOptions, citedRuleIds, expandIdRanges, QUICK_RULES_PATH, type Register, type Rule, type RuleBook, type Severity, } from './rules.js';
|
|
2
|
-
export { detect, countByRule, protectedTokens, missingProtectedTokens, structureStats, splitSentences, reportRegisterStats, DETECTABLE_RULE_IDS, type Detection, type ReportRegisterStats, type StructureStats, } from './detectors.js';
|
|
2
|
+
export { detect, countByRule, protectedTokens, missingProtectedTokens, structureStats, splitSentences, reportRegisterStats, spacingIssues, scanProse, DETECTABLE_RULE_IDS, type Detection, type ReportRegisterStats, type SpacingIssue, type StructureStats, } from './detectors.js';
|
|
3
3
|
export { changeRate, type ChangeRateOptions } from './change-rate.js';
|
|
4
4
|
export { runCheck, prescan, decide, formatReport, EXIT_CODE, THRESHOLD, MAX_ATTEMPTS, type CheckReport, type PrescanReport, type Decision, type NextAction, type RunCheckOptions, type RunCheckWithPrescan, type AxisResult, type Verdict, } from './check.js';
|
|
5
5
|
export { scan, formatScan, type ScanReport, type ScanHit, type ScanOptions } from './scan.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/humanize/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,KAAK,EACL,KAAK,eAAe,EACpB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/humanize/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,KAAK,EACL,KAAK,eAAe,EACpB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { parseRuleBook, parseRegister, ruleLabel, s1Ids, citedRuleIds, expandIdRanges, QUICK_RULES_PATH, } from './rules.js';
|
|
2
|
-
export { detect, countByRule, protectedTokens, missingProtectedTokens, structureStats, splitSentences, reportRegisterStats, DETECTABLE_RULE_IDS, } from './detectors.js';
|
|
2
|
+
export { detect, countByRule, protectedTokens, missingProtectedTokens, structureStats, splitSentences, reportRegisterStats, spacingIssues, scanProse, DETECTABLE_RULE_IDS, } from './detectors.js';
|
|
3
3
|
export { changeRate } from './change-rate.js';
|
|
4
4
|
export { runCheck, prescan, decide, formatReport, EXIT_CODE, THRESHOLD, MAX_ATTEMPTS, } from './check.js';
|
|
5
5
|
export { scan, formatScan } from './scan.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/humanize/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,KAAK,EAEL,YAAY,EACZ,cAAc,EACd,gBAAgB,GAKjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/humanize/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,SAAS,EACT,KAAK,EAEL,YAAY,EACZ,cAAc,EACd,gBAAgB,GAKjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,mBAAmB,GAKpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,UAAU,EAA0B,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,GASb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAmD,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** 스캔 대상 상한. 사람이 쓴 원고 한 벌은 이 근처에도 안 온다 */
|
|
2
|
+
export declare const MAX_INPUT_BYTES = 2000000;
|
|
3
|
+
export interface ReadInputFailure {
|
|
4
|
+
/** 사람에게 보일 한 줄 */
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 검사 대상 파일을 fd 하나로 열어 종류와 크기를 확인하고 읽는다.
|
|
9
|
+
*
|
|
10
|
+
* statSync 로 재고 readFileSync 로 다시 열면 그 사이에 경로가 다른 대상을 가리킬 수 있어서
|
|
11
|
+
* 종류 검사도 상한도 실제로 읽은 것에 안 걸린다. 크기를 0 으로 보고하는 특수 파일도 같은
|
|
12
|
+
* 자리에서 막힌다.
|
|
13
|
+
*
|
|
14
|
+
* humanize-scan 과 humanize-check 가 같은 탐지기를 돌리므로 읽는 경계도 같아야 한다.
|
|
15
|
+
* 한쪽만 막으면 다른 쪽으로 상한을 우회해 같은 정규식에 임의 크기를 먹일 수 있다.
|
|
16
|
+
*
|
|
17
|
+
* 실패는 던지지 않고 돌려준다 — 종료 코드는 부르는 명령이 정한다.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readInput(path: string, label?: string): string | ReadInputFailure;
|
|
20
|
+
export declare function isReadFailure(result: string | ReadInputFailure): result is ReadInputFailure;
|
|
21
|
+
//# sourceMappingURL=read-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-input.d.ts","sourceRoot":"","sources":["../../../src/humanize/read-input.ts"],"names":[],"mappings":"AAGA,0CAA0C;AAC1C,eAAO,MAAM,eAAe,UAAY,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,GAAG,gBAAgB,CA8B7E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,MAAM,IAAI,gBAAgB,CAE3F"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
/** 스캔 대상 상한. 사람이 쓴 원고 한 벌은 이 근처에도 안 온다 */
|
|
4
|
+
export const MAX_INPUT_BYTES = 2_000_000;
|
|
5
|
+
/**
|
|
6
|
+
* 검사 대상 파일을 fd 하나로 열어 종류와 크기를 확인하고 읽는다.
|
|
7
|
+
*
|
|
8
|
+
* statSync 로 재고 readFileSync 로 다시 열면 그 사이에 경로가 다른 대상을 가리킬 수 있어서
|
|
9
|
+
* 종류 검사도 상한도 실제로 읽은 것에 안 걸린다. 크기를 0 으로 보고하는 특수 파일도 같은
|
|
10
|
+
* 자리에서 막힌다.
|
|
11
|
+
*
|
|
12
|
+
* humanize-scan 과 humanize-check 가 같은 탐지기를 돌리므로 읽는 경계도 같아야 한다.
|
|
13
|
+
* 한쪽만 막으면 다른 쪽으로 상한을 우회해 같은 정규식에 임의 크기를 먹일 수 있다.
|
|
14
|
+
*
|
|
15
|
+
* 실패는 던지지 않고 돌려준다 — 종료 코드는 부르는 명령이 정한다.
|
|
16
|
+
*/
|
|
17
|
+
export function readInput(path, label = '') {
|
|
18
|
+
const full = resolve(process.cwd(), path);
|
|
19
|
+
const prefix = label ? `${label} ` : '';
|
|
20
|
+
let fd;
|
|
21
|
+
try {
|
|
22
|
+
fd = openSync(full, 'r');
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const code = error.code;
|
|
26
|
+
// 권한이나 링크 순환을 파일 부재로 뭉개면 다음 사람이 엉뚱한 데를 고친다
|
|
27
|
+
return {
|
|
28
|
+
message: code === 'ENOENT'
|
|
29
|
+
? `${prefix}파일이 없습니다: ${full}`
|
|
30
|
+
: `${prefix}읽을 수 없습니다: ${full} (${code})`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const stat = fstatSync(fd);
|
|
35
|
+
if (!stat.isFile())
|
|
36
|
+
return { message: `${prefix}파일이 아닙니다: ${full}` };
|
|
37
|
+
if (stat.size > MAX_INPUT_BYTES) {
|
|
38
|
+
return { message: `${prefix}파일이 너무 큽니다: ${stat.size}B (상한 ${MAX_INPUT_BYTES}B)` };
|
|
39
|
+
}
|
|
40
|
+
const buffer = Buffer.allocUnsafe(stat.size);
|
|
41
|
+
const read = readSync(fd, buffer, 0, stat.size, 0);
|
|
42
|
+
return buffer.subarray(0, read).toString('utf-8');
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
closeSync(fd);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function isReadFailure(result) {
|
|
49
|
+
return typeof result !== 'string';
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=read-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-input.js","sourceRoot":"","sources":["../../../src/humanize/read-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,0CAA0C;AAC1C,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC;AAOzC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAK,GAAG,EAAE;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAExC,IAAI,EAAU,CAAC;IACf,IAAI,CAAC;QACH,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;QACnD,2CAA2C;QAC3C,OAAO;YACL,OAAO,EACL,IAAI,KAAK,QAAQ;gBACf,CAAC,CAAC,GAAG,MAAM,aAAa,IAAI,EAAE;gBAC9B,CAAC,CAAC,GAAG,MAAM,cAAc,IAAI,KAAK,IAAI,GAAG;SAC9C,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,aAAa,IAAI,EAAE,EAAE,CAAC;QACrE,IAAI,IAAI,CAAC,IAAI,GAAG,eAAe,EAAE,CAAC;YAChC,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,IAAI,CAAC,IAAI,SAAS,eAAe,IAAI,EAAE,CAAC;QACpF,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpD,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAiC;IAC7D,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC;AACpC,CAAC"}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 윤문 전에 원문을 훑어 "이번에 볼 룰"만 추린다.
|
|
3
|
+
*
|
|
4
|
+
* 룰북은 예순 개를 넘겼다. 매번 전부 펼치면 모델이 나눠 쓰는 주의가 룰마다 얇아진다.
|
|
5
|
+
* 실제로 걸린 서너 개는 안 걸린 쉰 개에 묻힌다. 스캔은 그 반대로 간다 —
|
|
6
|
+
* 걸린 룰만 처방과 함께 내놓는다. 나머지는 아예 말하지 않는다.
|
|
7
|
+
*
|
|
8
|
+
* 탐지기가 없는 S1은 목록으로만 넘긴다. 코드가 못 가리는 자리를 가린다고 하면
|
|
9
|
+
* 그게 더 나쁜 거짓말이다.
|
|
10
|
+
*/
|
|
11
|
+
import { type SpacingIssue } from './detectors.js';
|
|
1
12
|
import { type Register, type RuleScanOptions } from './rules.js';
|
|
2
13
|
export interface ScanHit {
|
|
3
14
|
ruleId: string;
|
|
@@ -14,6 +25,8 @@ export interface ScanReport {
|
|
|
14
25
|
hits: ScanHit[];
|
|
15
26
|
/** 탐지기가 없어 모델이 직접 봐야 하는 S1 룰 ID */
|
|
16
27
|
unverifiable: string[];
|
|
28
|
+
/** 어투가 아니라 맞춤법인 자리. s1Total 과 worthHumanizing 에는 안 섞는다 */
|
|
29
|
+
spacing: SpacingIssue[];
|
|
17
30
|
/** 걸리는 게 없으면 윤문하지 않는다 */
|
|
18
31
|
worthHumanizing: boolean;
|
|
19
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/humanize/scan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/humanize/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAmC,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAElG,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,CAAC;IACnB,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,mCAAmC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,0DAA0D;IAC1D,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,yBAAyB;IACzB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,uEAAuE;AACvE,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,UAAU,CA4B5E;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAiDrD"}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 윤문 전에 원문을 훑어 "이번에 볼 룰"만 추린다.
|
|
3
3
|
*
|
|
4
|
-
* 룰북은
|
|
4
|
+
* 룰북은 예순 개를 넘겼다. 매번 전부 펼치면 모델이 나눠 쓰는 주의가 룰마다 얇아진다.
|
|
5
5
|
* 실제로 걸린 서너 개는 안 걸린 쉰 개에 묻힌다. 스캔은 그 반대로 간다 —
|
|
6
6
|
* 걸린 룰만 처방과 함께 내놓는다. 나머지는 아예 말하지 않는다.
|
|
7
7
|
*
|
|
8
8
|
* 탐지기가 없는 S1은 목록으로만 넘긴다. 코드가 못 가리는 자리를 가린다고 하면
|
|
9
9
|
* 그게 더 나쁜 거짓말이다.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { scanProse, DETECTABLE_RULE_IDS } from './detectors.js';
|
|
12
12
|
import { parseRuleBook, ruleLabel, s1Ids } from './rules.js';
|
|
13
13
|
export function scan(text, options = {}) {
|
|
14
14
|
const register = options.register ?? 'doc';
|
|
15
15
|
const book = options.book ?? parseRuleBook();
|
|
16
16
|
const targets = s1Ids(book, register);
|
|
17
17
|
const detectable = new Set(DETECTABLE_RULE_IDS);
|
|
18
|
-
const
|
|
18
|
+
const { detections, spacing } = scanProse(text, targets);
|
|
19
|
+
const hits = detections
|
|
19
20
|
.map((found) => ({
|
|
20
21
|
ruleId: found.ruleId,
|
|
21
22
|
label: ruleLabel(book, found.ruleId),
|
|
@@ -30,10 +31,17 @@ export function scan(text, options = {}) {
|
|
|
30
31
|
s1Total,
|
|
31
32
|
hits,
|
|
32
33
|
unverifiable: targets.filter((id) => !detectable.has(id)),
|
|
34
|
+
spacing,
|
|
33
35
|
worthHumanizing: s1Total > 0,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
export function formatScan(report) {
|
|
39
|
+
const spacing = report.spacing.flatMap((issue) => [
|
|
40
|
+
`- ${issue.label} ${issue.count}건`,
|
|
41
|
+
...issue.samples.map((sample) => ` "${sample}"`),
|
|
42
|
+
` 처방: ${issue.fix}`,
|
|
43
|
+
]);
|
|
44
|
+
const spacingBlock = spacing.length > 0 ? ['', '맞춤법 (등급과 무관하게 그냥 고친다)', ...spacing] : [];
|
|
37
45
|
if (!report.worthHumanizing) {
|
|
38
46
|
// 직접 확인할 룰을 먼저 세운다. "윤문하지 않는다"를 앞에 두면 그 한 줄만 읽고
|
|
39
47
|
// 비탐지 룰 확인을 건너뛰게 된다 — 탐지기가 0건이라고 글이 깨끗한 건 아니다.
|
|
@@ -42,8 +50,11 @@ export function formatScan(report) {
|
|
|
42
50
|
'',
|
|
43
51
|
'아래 룰은 탐지기가 못 가린다. 직접 읽어서 확인한다.',
|
|
44
52
|
` ${report.unverifiable.join(' ')}`,
|
|
53
|
+
...spacingBlock,
|
|
45
54
|
'',
|
|
46
|
-
|
|
55
|
+
report.spacing.length > 0
|
|
56
|
+
? '어투는 그대로 두고 위 맞춤법만 고쳐서 낸다.'
|
|
57
|
+
: '여기서도 걸리는 게 없으면 윤문하지 않고 원문을 그대로 낸다.',
|
|
47
58
|
].join('\n');
|
|
48
59
|
}
|
|
49
60
|
const lines = [
|
|
@@ -59,7 +70,7 @@ export function formatScan(report) {
|
|
|
59
70
|
if (hit.prescription)
|
|
60
71
|
lines.push(` 처방: ${hit.prescription}`);
|
|
61
72
|
}
|
|
62
|
-
lines.push('', '탐지기가 못 가리는 S1 (직접 확인)', ` ${report.unverifiable.join(' ')}`, '', '위 목록 밖의 룰은 이번 텍스트에서 안 걸렸다. 찾아 나서지 않는다.');
|
|
73
|
+
lines.push('', '탐지기가 못 가리는 S1 (직접 확인)', ` ${report.unverifiable.join(' ')}`, ...spacingBlock, '', '위 목록 밖의 룰은 이번 텍스트에서 안 걸렸다. 찾아 나서지 않는다.');
|
|
63
74
|
return lines.join('\n');
|
|
64
75
|
}
|
|
65
76
|
//# sourceMappingURL=scan.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/humanize/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/humanize/scan.ts"],"names":[],"mappings":"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"}
|
package/dist/src/mcp/schemas.js
CHANGED
|
@@ -431,7 +431,7 @@ export const executeInputSchema = z.object({
|
|
|
431
431
|
prReviewer: z
|
|
432
432
|
.string()
|
|
433
433
|
.optional()
|
|
434
|
-
.describe('review_publish가 남길 판정의 리뷰어 이름. 생략하면 GESTALT_ACTOR 환경변수, 그것도 없으면 gestalt:review를 쓴다. 개별 코멘트 작성자는 이 값이 아니라 각
|
|
434
|
+
.describe('review_publish가 남길 판정의 리뷰어 이름. 생략하면 GESTALT_ACTOR 환경변수, 그것도 없으면 gestalt:review를 쓴다. 개별 코멘트 작성자는 이 값이 아니라 각 의견의 reportedBy다.'),
|
|
435
435
|
});
|
|
436
436
|
export const executeToolSchema = executeInputSchema.shape;
|
|
437
437
|
// ─── Create Agent Tool ──────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gestalt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Gestalt psychology-driven AI development harness. Transforms scattered requirements into structured, validated specifications through interactive interviews.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tienne"
|
|
@@ -31,7 +31,7 @@ pnpm verify:rules
|
|
|
31
31
|
본문에 쓰면 산출물로 샌다. 금지어를 넣었으면 같은 문서를 grep한다.
|
|
32
32
|
- 어투 규칙은 **S1으로 올려야 실제로 강제된다.** S2는 모델이 우선순위를 알아서 정하면서 새어나간다.
|
|
33
33
|
- 심각도를 바꿀 때는 근거를 남긴다. A-2, I-1, C-8은 대조 코퍼스 측정으로 조정했고 그 근거가
|
|
34
|
-
룰북
|
|
34
|
+
[룰북 측정 근거](ai-tell-quick-rules.md#측정-근거)에 있다. 감으로 올리고 내리면 다음 사람이 되돌린다.
|
|
35
35
|
- 경로를 참조하는 자리가 여러 곳이다. 파일을 옮기거나 이름을 바꾸면 `plugin/` 전체에서
|
|
36
36
|
상대경로 참조를 다시 확인한다 (에이전트는 `../_shared/references/`, 에이전트의 `references/`
|
|
37
37
|
하위 문서는 `../../_shared/references/`, 스킬은 `../../role-agents/_shared/references/`).
|