@wooojin/forgen 0.4.10 → 0.4.12
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-plugin/plugin.json +1 -1
- package/CHANGELOG.md +62 -0
- package/README.md +33 -1
- package/assets/claude/agents/forgen-verify.md +65 -0
- package/assets/claude/workflows/compound-extract.js +136 -0
- package/assets/claude/workflows/evidence-gate-audit.js +107 -0
- package/assets/shared/hook-registry.json +1 -0
- package/dist/checks/_shared/meta-guard-dispatch.d.ts +38 -0
- package/dist/checks/_shared/meta-guard-dispatch.js +80 -0
- package/dist/checks/_shared/text-sanitizer.js +15 -0
- package/dist/cli.js +57 -2
- package/dist/core/changelog-cli.d.ts +7 -0
- package/dist/core/changelog-cli.js +100 -0
- package/dist/core/doctor.d.ts +3 -0
- package/dist/core/doctor.js +38 -0
- package/dist/core/effort-advisory.d.ts +23 -0
- package/dist/core/effort-advisory.js +29 -0
- package/dist/core/explain-cli.d.ts +6 -0
- package/dist/core/explain-cli.js +99 -0
- package/dist/core/health-cli.d.ts +23 -0
- package/dist/core/health-cli.js +86 -0
- package/dist/core/probe-workflow-cli.d.ts +72 -0
- package/dist/core/probe-workflow-cli.js +282 -0
- package/dist/core/spawn.d.ts +13 -0
- package/dist/core/spawn.js +36 -8
- package/dist/core/stats-cli.d.ts +22 -9
- package/dist/core/stats-cli.js +149 -0
- package/dist/core/watch-cli.d.ts +7 -0
- package/dist/core/watch-cli.js +185 -0
- package/dist/core/workflows-cli.d.ts +26 -0
- package/dist/core/workflows-cli.js +120 -0
- package/dist/engine/compound-export.d.ts +12 -0
- package/dist/engine/compound-export.js +136 -14
- package/dist/engine/compound-extractor.d.ts +12 -43
- package/dist/engine/compound-extractor.js +27 -756
- package/dist/engine/extraction-diff.d.ts +11 -0
- package/dist/engine/extraction-diff.js +105 -0
- package/dist/engine/extraction-gates.d.ts +37 -0
- package/dist/engine/extraction-gates.js +100 -0
- package/dist/engine/extraction-git.d.ts +20 -0
- package/dist/engine/extraction-git.js +75 -0
- package/dist/engine/extraction-persistence.d.ts +27 -0
- package/dist/engine/extraction-persistence.js +140 -0
- package/dist/engine/extraction-session.d.ts +26 -0
- package/dist/engine/extraction-session.js +230 -0
- package/dist/engine/lifecycle/types.d.ts +1 -1
- package/dist/engine/meta-learning/matcher-weight-loader.d.ts +16 -0
- package/dist/engine/meta-learning/matcher-weight-loader.js +45 -0
- package/dist/engine/precision-guards.d.ts +14 -0
- package/dist/engine/precision-guards.js +39 -0
- package/dist/engine/ranking-pipeline.d.ts +45 -0
- package/dist/engine/ranking-pipeline.js +66 -0
- package/dist/engine/relevance-scorer.d.ts +43 -0
- package/dist/engine/relevance-scorer.js +81 -0
- package/dist/engine/scoring-algorithms.d.ts +31 -0
- package/dist/engine/scoring-algorithms.js +109 -0
- package/dist/engine/solution-matcher-eval.d.ts +97 -0
- package/dist/engine/solution-matcher-eval.js +122 -0
- package/dist/engine/solution-matcher.d.ts +21 -380
- package/dist/engine/solution-matcher.js +27 -828
- package/dist/fgx.js +1 -1
- package/dist/hooks/notepad-injector.js +7 -0
- package/dist/hooks/post-tool-use.js +8 -1
- package/dist/hooks/secret-filter.d.ts +1 -0
- package/dist/hooks/secret-filter.js +17 -7
- package/dist/hooks/shared/preflight-check.d.ts +15 -0
- package/dist/hooks/shared/preflight-check.js +51 -0
- package/dist/hooks/stop-guard.js +19 -60
- package/dist/hooks/subagent-stop-guard.d.ts +23 -0
- package/dist/hooks/subagent-stop-guard.js +158 -0
- package/dist/hooks/subagent-tracker.d.ts +36 -3
- package/dist/hooks/subagent-tracker.js +86 -39
- package/hooks/hooks.json +6 -1
- package/package.json +7 -7
- package/plugin.json +1 -1
- package/scripts/postinstall.js +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.12] — 2026-06-01 — Hotfix: 훅 stdout 누출 + 세션 종료 블로킹
|
|
11
|
+
|
|
12
|
+
핫픽스 두 건.
|
|
13
|
+
|
|
14
|
+
- **fix(hooks)**: `secret-filter` 에 ESM main-guard 누락 → `context-guard` 가 `redactSecrets`
|
|
15
|
+
를 import 할 때 `secret-filter.main()` 이 import 부작용으로 실행되어 유령 `{"continue":true}`
|
|
16
|
+
를 1줄 추가 emit. 그 결과 context-guard 의 stdout 이 JSON 2줄이 되어 Claude Code 파싱
|
|
17
|
+
실패 → raw `{"continue":true}` 가 매 프롬프트/세션 종료마다 사용자 터미널에 노출되었다.
|
|
18
|
+
표준 main-guard 추가로 수정. (등록 훅 11개 중 context-guard 만 영향)
|
|
19
|
+
- **perf(compound)**: 세션 종료 시 `runAutoCompound` 가 execFileSync 로 동기 실행되어 최대
|
|
20
|
+
~210초(haiku LLM 3회 순차) 블록되었다. 같은 작업을 Stop 훅·session-recovery 가 이미
|
|
21
|
+
detached 로 spawn 하므로(dedup 마커 공유), 동기 경로를 detached + unref 로 전환해 세션
|
|
22
|
+
종료를 막지 않게 했다. 결과는 다음 세션 시작 시 surface.
|
|
23
|
+
- **test**: `hook-single-line-output` (훅당 stdout 1줄 + secret-filter import 무부작용),
|
|
24
|
+
`auto-compound-detached` (detached/unref/dedup 분기) 회귀 테스트 추가.
|
|
25
|
+
|
|
26
|
+
## [0.4.11] — 2026-05-29 — Opus 4.8 + Dynamic Workflows 대응
|
|
27
|
+
|
|
28
|
+
테마: Claude Opus 4.8(2026-05-28 GA)의 **dynamic workflows**(최대 1,000 서브에이전트,
|
|
29
|
+
대화 밖 격리 런타임)·effort(high/xhigh/ultracode) 도입에 forgen 을 정합화. 설계는
|
|
30
|
+
[ADR-009](docs/adr/ADR-009-opus-4-8-dynamic-workflows.md). 핵심 긴장: forgen 검증은
|
|
31
|
+
메인 Stop hook 에 묶여 있는데 워크플로우는 작업을 대화 밖으로 옮긴다 → probe 로
|
|
32
|
+
"워크플로우 내부 에이전트도 forgen 훅을 발화"함을 실측 확인 후 검증을 확장.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- **probe (§1)**: `forgen probe-workflow arm|report|status` — dynamic-workflow 서브에이전트가
|
|
36
|
+
forgen 훅(SubagentStart/Stop·Pre/PostToolUse)을 발화하는지 실측. 결과를
|
|
37
|
+
`~/.forgen/state/probe-workflow-result.json` 에 박제. 실측 verdict = `workflow-hooks-fire`.
|
|
38
|
+
- **SubagentStop 검증 (§2)**: 신규 `subagent-stop-guard` 훅 — 워크플로우/Task 서브에이전트의
|
|
39
|
+
마지막 응답에 메타 가드(TEST-1/2/3 + DANGEROUS)를 적용, `decision:block` 으로 재개.
|
|
40
|
+
`(sessionId,agentId)` block-count 키(동시 서브에이전트 stuck-loop 충돌 방지),
|
|
41
|
+
per-agent recentTools(`post-tool-use` 가 agent_id 있을 때 분리 → 메인 세션 TEST-2 오염 방지).
|
|
42
|
+
- **워크플로우 템플릿 (§3)**: `forgen workflows install [--project] | list`. forgen 철학을
|
|
43
|
+
인코딩한 canonical 템플릿 동봉 — `evidence-gate-audit`(no-mock 증거 게이팅 감사),
|
|
44
|
+
`compound-extract`(구현 기록이 아닌 판단 기준 추출). verify 스테이지용 `forgen-verify`
|
|
45
|
+
에이전트 추가(플러그인 agents 키로 자동 배포; built-in agents 13→14).
|
|
46
|
+
- **compound↔workflow 양방향 배선**: 두 템플릿이 fan-out 전 forgen-compound MCP
|
|
47
|
+
`compound-search` 로 과거 패턴을 recall(읽기, 안전)하고, `compound-extract` 는
|
|
48
|
+
`args.persist=true` 일 때만 keep 된 후보를 `forgen compound --solution` 으로 적재
|
|
49
|
+
(기본은 review-gated — 품질 게이트 우회 금지). 라이브 확인: 워크플로우 에이전트가
|
|
50
|
+
compound-search MCP 도달·3건 회수.
|
|
51
|
+
- **effort 권고 (§5)**: `forgen doctor [Effort]` 섹션 — long-running(forge-loop) 컨텍스트면
|
|
52
|
+
xhigh/ultracode 권고. nudge-only (forgen 은 effort 를 프로그램적으로 설정 불가).
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- **동시성 임계값 (§4)**: `MAX_CONCURRENT_AGENTS` 10 고정 → `FORGEN_MAX_CONCURRENT_AGENTS`
|
|
56
|
+
env(기본 16) + `workflow-subagent` 면제. workflow/team/swarm 실행마다 뜨던 거짓 경고 제거.
|
|
57
|
+
- **meta-guard 디스패처 추출 (§2a)**: stop-guard 인라인 가드 로직을
|
|
58
|
+
`checks/_shared/meta-guard-dispatch.runMetaGuards` 로 추출 (Stop/SubagentStop 공유, 동작 불변).
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- **subagent-tracker 동시쓰기 레이스 (§A)**: `load→push→save` 가 파일 락 없는 RMW 였어
|
|
62
|
+
동시 SubagentStart(워크플로우 fanout) 간 lost-update 로 일부 에이전트 누락(probe 에서
|
|
63
|
+
3개 중 1개 손실 관찰). `recordAgentEvent` 추출 + `withFileLock` 으로 보호, 락 안 fresh re-read.
|
|
64
|
+
|
|
65
|
+
### Notes
|
|
66
|
+
- **opus-4.8 재캘리브레이션 PENDING**: v0.4.5 δ>0 측정은 sonnet/codex 드라이버 기준.
|
|
67
|
+
opus-4.8 재측정 전까지 효과 주장을 하지 않음 — 절차/블로커는
|
|
68
|
+
[docs/release/v0.4.11-calibration-pending.md](docs/release/v0.4.11-calibration-pending.md).
|
|
69
|
+
- 라이브 검증: forgen-verify 에이전트가 실 워크플로우에서 해소·실행(grep 증거 → confirmed),
|
|
70
|
+
subagent-stop-guard 가 워크플로우 서브에이전트에 발화(40ms, false-block 없음) 확인.
|
|
71
|
+
|
|
10
72
|
## [0.4.8] — 2026-05-15 — Codex 동등화 마무리 + 잔재 청소
|
|
11
73
|
|
|
12
74
|
테마: v0.4.6 (Unattended Resilience) 이후 남아 있던 **Codex 동등화 마무리**
|
package/README.md
CHANGED
|
@@ -222,8 +222,39 @@ forgen config default-host codex # set persistent default
|
|
|
222
222
|
- **Codex CLI** — install per [Codex docs](https://github.com/openai/codex)
|
|
223
223
|
- Or both — `forgen install both` registers symmetric hooks/MCP for each
|
|
224
224
|
|
|
225
|
+
```bash
|
|
226
|
+
# Verify your setup is healthy after install:
|
|
227
|
+
forgen doctor --quick
|
|
228
|
+
```
|
|
229
|
+
|
|
225
230
|
> **Vendor dependency:** Forgen wraps Claude Code and Codex CLI symmetrically (Claude is the behavior reference; Codex extends with equivalence). Upstream API/CLI changes may affect behavior. Tested with Claude Code 1.0.x / 2.1.x and Codex 0.x.
|
|
226
231
|
|
|
232
|
+
> **Upgrading from v0.4.x?** Run `forgen install claude` (or `codex` / `both`) after upgrading — v0.4.3+ requires explicit host registration. Then `forgen doctor --quick` to verify.
|
|
233
|
+
|
|
234
|
+
### Try your first block
|
|
235
|
+
|
|
236
|
+
After setup, trigger forgen's stop-guard in under 60 seconds:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
forgen # launches Claude Code with forgen hooks active
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Then paste this prompt into the Claude session:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
Refactor the main entry point to use async/await. When done, tell me your confidence level out of 100.
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Claude will likely reply with a confidence score (e.g., "95/100") **without running tests**. forgen's **TEST-2 self-score inflation** guard will block the turn:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
[forgen:stop-guard/builtin:self-score-inflation]
|
|
252
|
+
자가 점수 상승 선언 1건 (95/100). 측정 도구 호출 0회 — 숫자를 뒷받침할
|
|
253
|
+
실행/확인 증거 없음. 테스트/빌드/curl 실행 결과를 턴에 포함해 재응답.
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Claude reads the block reason, retracts its claim, runs the actual test, and resubmits with evidence. **That's your first block.**
|
|
257
|
+
|
|
227
258
|
### Isolated / CI / Docker usage
|
|
228
259
|
|
|
229
260
|
Forgen's home directory is `~/.forgen` by default, but can be overridden per-process:
|
|
@@ -420,7 +451,7 @@ Curated, compound-native skills. Each integrates with your accumulated knowledge
|
|
|
420
451
|
| `architecture-decision` | "adr" | Weighted trade-off matrix, ADR lifecycle, reversibility classification |
|
|
421
452
|
| `docker` | "docker", "컨테이너" | Multi-stage builds, security hardening, 10 failure modes
|
|
422
453
|
|
|
423
|
-
###
|
|
454
|
+
### 14 built-in agents
|
|
424
455
|
|
|
425
456
|
Sub-agents with physically separated tool access, `Failure_Modes_To_Avoid` sections, and Good/Bad examples. Invoked via `Agent(subagent_type: "ch-<name>")`. The `ch-` prefix avoids collisions with OMC / built-in Claude Code agents.
|
|
426
457
|
|
|
@@ -451,6 +482,7 @@ Sub-agents with physically separated tool access, `Failure_Modes_To_Avoid` secti
|
|
|
451
482
|
| `ch-designer` | Sonnet | UI/UX — component architecture, accessibility, responsive design |
|
|
452
483
|
| `ch-git-master` | Sonnet | Git workflows — atomic commits, rebasing, history management (Bash limited to git) |
|
|
453
484
|
| `ch-verifier` | Sonnet | Completion verifier — evidence collection, test adequacy, manual test scenarios (compound-aware) |
|
|
485
|
+
| `forgen-verify` | Sonnet | Workflow verify-stage agent — adversarially confirms a finding with REAL execution evidence (no `ch-` prefix; invoked by dynamic-workflow templates via `agentType`) |
|
|
454
486
|
|
|
455
487
|
> Absorbed in this redesign: `security-reviewer` / `performance-reviewer` → `ch-code-reviewer`, `refactoring-expert` / `code-simplifier` → `ch-executor`, `qa-tester` → `ch-verifier`, `scientist` / `writer` removed.
|
|
456
488
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forgen-verify
|
|
3
|
+
description: Workflow verify-stage agent — adversarially confirms a claim/finding with REAL execution evidence (no mock), returns a structured verdict
|
|
4
|
+
model: sonnet
|
|
5
|
+
maxTurns: 12
|
|
6
|
+
color: red
|
|
7
|
+
tools:
|
|
8
|
+
- Read
|
|
9
|
+
- Bash
|
|
10
|
+
- Glob
|
|
11
|
+
- Grep
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!-- forgen-managed -->
|
|
15
|
+
|
|
16
|
+
<Agent_Prompt>
|
|
17
|
+
|
|
18
|
+
# forgen-verify — 워크플로우 검증 스테이지 에이전트
|
|
19
|
+
|
|
20
|
+
"완료했다고 말하는 것과 완료를 증명하는 것은 다르다."
|
|
21
|
+
|
|
22
|
+
당신은 dynamic-workflow 의 verify 스테이지에서 호출됩니다. 상위 스크립트가 넘긴
|
|
23
|
+
**하나의 주장(claim)/발견(finding)** 을 받아, 그것이 실제로 참인지 **반박을 시도**한
|
|
24
|
+
뒤 구조화된 판정을 반환합니다. 당신의 출력은 사람용 메시지가 아니라 스크립트가
|
|
25
|
+
소비하는 **데이터**입니다 (schema 가 주어지면 그 형식으로 반환).
|
|
26
|
+
|
|
27
|
+
## forgen 검증 원칙 (이 에이전트의 정체성)
|
|
28
|
+
|
|
29
|
+
1. **실행 증거만 유효 (no-mock)**: "테스트가 통과할 것이다", "동작할 것이다" 류의
|
|
30
|
+
추정은 증거가 아니다. 실제로 `Bash` 로 빌드/테스트/재현을 **지금 실행**한 결과만
|
|
31
|
+
인정한다. mock/stub 기반 통과는 증거로 치지 않는다.
|
|
32
|
+
2. **반박 우선 (adversarial)**: 주장을 확증하려 하지 말고 **깨뜨리려고** 시도하라.
|
|
33
|
+
재현이 안 되거나 증거가 약하면 기본값은 `refuted`/`unverified` 다.
|
|
34
|
+
3. **자가 점수 금지**: "신뢰도 95%" 같은 숫자를 측정 없이 붙이지 않는다.
|
|
35
|
+
|
|
36
|
+
## 검증 프로토콜
|
|
37
|
+
|
|
38
|
+
1. 주장을 1문장으로 재진술 (무엇이 참이라 주장되는가).
|
|
39
|
+
2. 검증 방법 결정: 재현 명령 / 대상 코드 위치 / 기대 대비 실제.
|
|
40
|
+
3. **실제 실행** (`Bash`/`Read`/`Grep`). 출력을 증거로 인용.
|
|
41
|
+
4. 판정: 증거가 주장을 뒷받침하면 `confirmed`, 반증되면 `refuted`,
|
|
42
|
+
실행 불가/불충분하면 `unverified` (확신 없으면 confirmed 로 올리지 말 것).
|
|
43
|
+
|
|
44
|
+
## 출력 형식 (schema 없을 때 기본)
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
verdict: confirmed | refuted | unverified
|
|
48
|
+
evidence: <실제 실행한 명령 + 핵심 출력 1-3줄>
|
|
49
|
+
reason: <1-2문장 근거>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
<Failure_Modes_To_Avoid>
|
|
53
|
+
- ❌ 코드만 읽고 "맞는 것 같다" → 실행 증거 없으면 unverified
|
|
54
|
+
- ❌ 이전/추정 결과 인용 → 지금 실행한 출력만
|
|
55
|
+
- ❌ 애매하면 confirmed 로 처리 → 애매하면 unverified (보수적)
|
|
56
|
+
- ❌ 사람용 산문으로 장황하게 → 스크립트가 파싱할 데이터로 간결히
|
|
57
|
+
</Failure_Modes_To_Avoid>
|
|
58
|
+
|
|
59
|
+
<Success_Criteria>
|
|
60
|
+
- 판정에 **실제 실행 출력**이 증거로 붙어 있다
|
|
61
|
+
- confirmed 는 증거가 명백할 때만
|
|
62
|
+
- 출력이 간결하고 (schema 있으면) 그 형식을 정확히 따른다
|
|
63
|
+
</Success_Criteria>
|
|
64
|
+
|
|
65
|
+
</Agent_Prompt>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen template — compound-extract
|
|
3
|
+
*
|
|
4
|
+
* 변경분(diff)/세션 작업에서 **재사용 가능한 판단 기준**을 추출한다. forgen 의
|
|
5
|
+
* compound 원칙을 인코딩: "무엇을 만들었는가"(구현 기록)가 아니라 "이런 상황에서는
|
|
6
|
+
* 이렇게 한다"(적용 조건 + 판단 근거 + 주의사항)로 프레이밍하고, 코드를 읽으면 알 수
|
|
7
|
+
* 있는 것은 버린다. 여러 후보를 뽑은 뒤 중복을 병합하고, 각 후보가 정말 일반화
|
|
8
|
+
* 가능한지(코드-자명하지 않은지) 비판 에이전트로 거른다.
|
|
9
|
+
*
|
|
10
|
+
* compound 연동(ADR-009 §3, 양방향):
|
|
11
|
+
* - Recall: 추출 전 forgen-compound MCP `compound-search` 로 기존 패턴을 회수해
|
|
12
|
+
* critic 이 중복(Q5)을 drop 하도록 한다.
|
|
13
|
+
* - Ingest: `args.persist === true` 일 때만 keep 된 후보를 `forgen compound
|
|
14
|
+
* --solution` 으로 store 에 적재. 기본값은 적재하지 않고 review 용으로 반환한다
|
|
15
|
+
* (forgen 의 human-review 규율 존중 — 품질 게이트 우회 금지).
|
|
16
|
+
*
|
|
17
|
+
* 사용:
|
|
18
|
+
* /compound-extract (대상=git diff HEAD~1, 적재 안 함)
|
|
19
|
+
* args 로 { range, persist } 전달 가능 (persist:true → 자동 적재).
|
|
20
|
+
*
|
|
21
|
+
* 저장 위치: ~/.claude/workflows/ (forgen workflows install 로 복사됨).
|
|
22
|
+
*/
|
|
23
|
+
export const meta = {
|
|
24
|
+
name: 'compound-extract',
|
|
25
|
+
description: 'forgen compound extraction — recall existing patterns, mine reusable JUDGMENT CRITERIA from a diff, dedupe via critic, and optionally persist (args.persist) to the compound store',
|
|
26
|
+
phases: [
|
|
27
|
+
{ title: 'Recall', detail: 'compound-search existing patterns to dedupe against' },
|
|
28
|
+
{ title: 'Mine', detail: 'extract candidate patterns from the diff' },
|
|
29
|
+
{ title: 'Filter', detail: 'critic drops code-self-evident / duplicate / non-generalizable candidates' },
|
|
30
|
+
{ title: 'Ingest', detail: 'persist kept candidates (only when args.persist)' },
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const range = (args && args.range) || 'HEAD~1'
|
|
35
|
+
const persist = !!(args && args.persist)
|
|
36
|
+
|
|
37
|
+
const CANDIDATES_SCHEMA = {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
candidates: {
|
|
41
|
+
type: 'array',
|
|
42
|
+
items: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
title: { type: 'string', description: 'situation-framed, NO concrete component/function names' },
|
|
46
|
+
condition: { type: 'string', description: 'when this applies' },
|
|
47
|
+
rationale: { type: 'string', description: 'why — the judgment basis' },
|
|
48
|
+
caution: { type: 'string', description: 'pitfalls / when NOT to apply' },
|
|
49
|
+
},
|
|
50
|
+
required: ['title', 'condition', 'rationale'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['candidates'],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const KEEP_SCHEMA = {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
keep: { type: 'boolean' },
|
|
61
|
+
reason: { type: 'string', description: 'why kept or dropped (code-self-evident? not generalizable?)' },
|
|
62
|
+
refinedTitle: { type: 'string' },
|
|
63
|
+
},
|
|
64
|
+
required: ['keep', 'reason'],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 0) Recall — 기존 compound 패턴 회수 (dedup 근거). MCP 없으면 빈 컨텍스트.
|
|
68
|
+
const existing = await agent(
|
|
69
|
+
`Use the forgen-compound MCP tool \`compound-search\` (via ToolSearch if needed) with a query derived ` +
|
|
70
|
+
`from \`git diff ${range} --stat\` (key topics/domains). Return a concise list of EXISTING compound ` +
|
|
71
|
+
`solution titles + one-line gist each. If unavailable or none, reply exactly: (no existing compound knowledge).`,
|
|
72
|
+
{ label: 'recall:existing', phase: 'Recall' },
|
|
73
|
+
)
|
|
74
|
+
const existingContext = existing && !/no existing compound knowledge/i.test(existing)
|
|
75
|
+
? `\n\n이미 store 에 있는 패턴(중복이면 drop):\n${existing}`
|
|
76
|
+
: ''
|
|
77
|
+
|
|
78
|
+
// 1) Mine — 여러 각도에서 후보 추출 (diff 를 직접 읽고).
|
|
79
|
+
const angles = [
|
|
80
|
+
'debugging/디버깅에서 얻은 교훈 (재발 방지 판단 기준)',
|
|
81
|
+
'설계/구조 결정의 근거 (왜 이 접근을 택했는가)',
|
|
82
|
+
'함정/주의사항 (이 상황에서 흔히 틀리는 것)',
|
|
83
|
+
]
|
|
84
|
+
const mined = await parallel(angles.map((angle) => () =>
|
|
85
|
+
agent(
|
|
86
|
+
`\`git diff ${range}\` 를 읽고, "${angle}" 관점에서 재사용 가능한 판단 기준을 추출하라.\n` +
|
|
87
|
+
`중요: 제목에 구체 컴포넌트/함수명 금지. "무엇을 만들었나"가 아니라 "이런 상황엔 이렇게 한다"로.\n` +
|
|
88
|
+
`코드를 읽으면 바로 알 수 있는 사실은 후보에서 제외.`,
|
|
89
|
+
{ label: `mine`, phase: 'Mine', schema: CANDIDATES_SCHEMA },
|
|
90
|
+
)))
|
|
91
|
+
|
|
92
|
+
const candidates = mined.filter(Boolean).flatMap((m) => m.candidates || [])
|
|
93
|
+
|
|
94
|
+
// 2) Filter — 각 후보를 비판 에이전트가 평가: 코드-자명하거나 일반화 불가면 drop.
|
|
95
|
+
const judged = await parallel(candidates.map((c) => () =>
|
|
96
|
+
agent(
|
|
97
|
+
`이 compound 후보가 저장할 가치가 있는가? 기준: (a) 코드를 읽으면 알 수 있는 건 ` +
|
|
98
|
+
`drop, (b) 한 번 쓰고 말 1회성도 drop, (c) 이미 store 에 있는 것과 중복이면 drop, ` +
|
|
99
|
+
`(d) 적용 조건+판단 근거가 일반화 가능하고 신규면 keep.\n\n` +
|
|
100
|
+
`제목: ${c.title}\n조건: ${c.condition}\n근거: ${c.rationale}\n주의: ${c.caution || '(없음)'}${existingContext}`,
|
|
101
|
+
{ label: `filter`, phase: 'Filter', agentType: 'ch-critic', schema: KEEP_SCHEMA },
|
|
102
|
+
).then((j) => ({ ...c, ...j }))))
|
|
103
|
+
|
|
104
|
+
const kept = judged.filter(Boolean).filter((c) => c.keep).map((c) => ({
|
|
105
|
+
title: c.refinedTitle || c.title,
|
|
106
|
+
condition: c.condition,
|
|
107
|
+
rationale: c.rationale,
|
|
108
|
+
caution: c.caution,
|
|
109
|
+
}))
|
|
110
|
+
log(`compound-extract: ${candidates.length} candidates → ${kept.length} kept (dropped ${candidates.length - kept.length} as code-self-evident / duplicate / one-off)`)
|
|
111
|
+
|
|
112
|
+
// 3) Ingest (ADR-009 §3, opt-in) — keep 된 후보를 store 에 적재. args.persist 일 때만.
|
|
113
|
+
// 품질 게이트(Filter critic)를 이미 통과한 것만 들어오므로 우회가 아니다.
|
|
114
|
+
let persisted = 0
|
|
115
|
+
if (persist && kept.length) {
|
|
116
|
+
const results = await parallel(kept.map((c) => () =>
|
|
117
|
+
agent(
|
|
118
|
+
`Persist this compound solution to forgen's store by running EXACTLY this shell command via Bash ` +
|
|
119
|
+
`(escape quotes safely): forgen compound --solution "<title>" "<content>". \n` +
|
|
120
|
+
`title: ${c.title}\n` +
|
|
121
|
+
`content (조건/근거/주의를 한 본문으로): 조건: ${c.condition} | 근거: ${c.rationale} | 주의: ${c.caution || '없음'}\n` +
|
|
122
|
+
`Run it, then reply "saved" if exit 0, else reply the error.`,
|
|
123
|
+
{ label: `ingest:${c.title.slice(0, 24)}`, phase: 'Ingest' },
|
|
124
|
+
).then((r) => (/saved/i.test(r || '') ? 1 : 0))))
|
|
125
|
+
persisted = results.reduce((a, b) => a + (b || 0), 0)
|
|
126
|
+
log(`compound-extract: persisted ${persisted}/${kept.length} via \`forgen compound --solution\``)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
range,
|
|
131
|
+
kept,
|
|
132
|
+
persisted: persist ? persisted : 0,
|
|
133
|
+
note: persist
|
|
134
|
+
? `Persisted ${persisted}/${kept.length} to the compound store. Verify with \`forgen compound list\`.`
|
|
135
|
+
: 'Not persisted (default). Re-run with args { persist: true } to auto-save, or review with `forgen compound`.',
|
|
136
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen template — evidence-gate-audit
|
|
3
|
+
*
|
|
4
|
+
* forgen 철학을 dynamic-workflow 로 인코딩: 대상을 여러 각도로 감사(find)하고,
|
|
5
|
+
* 각 발견을 forgen-verify 에이전트로 **실제 실행 증거** 기반 반박 검증한 뒤,
|
|
6
|
+
* confirmed 만 보고한다. "찾았다"가 아니라 "증명됐다"만 남긴다.
|
|
7
|
+
*
|
|
8
|
+
* compound 연동(ADR-009 §3): fan-out 전 forgen-compound MCP 로 과거 솔루션/패턴을
|
|
9
|
+
* recall 하여 finder 들이 누적 지식을 활용하도록 한다 (recall→fanout). 읽기 전용이라
|
|
10
|
+
* 안전 — store 에 쓰지 않는다.
|
|
11
|
+
*
|
|
12
|
+
* 사용:
|
|
13
|
+
* /evidence-gate-audit (대상=src/, 기본 차원)
|
|
14
|
+
* args 로 { target, dimensions } 전달 가능.
|
|
15
|
+
*
|
|
16
|
+
* 저장 위치: ~/.claude/workflows/ (forgen workflows install 로 복사됨).
|
|
17
|
+
*/
|
|
18
|
+
export const meta = {
|
|
19
|
+
name: 'evidence-gate-audit',
|
|
20
|
+
description: 'forgen evidence-gated audit — recall prior compound knowledge, find issues across dimensions, then confirm each with REAL execution evidence (forgen-verify), report only what is proven',
|
|
21
|
+
phases: [
|
|
22
|
+
{ title: 'Recall', detail: 'compound-search prior patterns for the target' },
|
|
23
|
+
{ title: 'Find', detail: 'fan out finders across dimensions' },
|
|
24
|
+
{ title: 'Verify', detail: 'forgen-verify confirms each finding with real execution' },
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const target = (args && args.target) || 'src/'
|
|
29
|
+
const DIMENSIONS = (args && args.dimensions) || [
|
|
30
|
+
{ key: 'correctness', prompt: `Audit ${target} for correctness bugs (logic errors, wrong conditions, off-by-one, unhandled nulls).` },
|
|
31
|
+
{ key: 'error-paths', prompt: `Audit ${target} for missing/empty error handling and swallowed exceptions.` },
|
|
32
|
+
{ key: 'concurrency', prompt: `Audit ${target} for race conditions and unguarded shared-state read-modify-write.` },
|
|
33
|
+
{ key: 'security', prompt: `Audit ${target} for injection, path traversal, secret leakage, and unsafe input handling.` },
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const FINDINGS_SCHEMA = {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
findings: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
items: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
title: { type: 'string' },
|
|
45
|
+
file: { type: 'string' },
|
|
46
|
+
line: { type: 'number' },
|
|
47
|
+
claim: { type: 'string', description: 'the concrete, testable claim that this is a real issue' },
|
|
48
|
+
repro: { type: 'string', description: 'how to reproduce/verify it (command or steps)' },
|
|
49
|
+
},
|
|
50
|
+
required: ['title', 'file', 'claim'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['findings'],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const VERDICT_SCHEMA = {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
verdict: { type: 'string', enum: ['confirmed', 'refuted', 'unverified'] },
|
|
61
|
+
evidence: { type: 'string', description: 'actual executed command + key output' },
|
|
62
|
+
reason: { type: 'string' },
|
|
63
|
+
},
|
|
64
|
+
required: ['verdict', 'reason'],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Recall (ADR-009 §3): fan-out 전 누적 compound 지식을 회수해 finder 들에 주입.
|
|
68
|
+
// forgen-compound MCP 가 연결돼 있으면 compound-search 로, 없으면 빈 컨텍스트로 진행.
|
|
69
|
+
const recall = await agent(
|
|
70
|
+
`Use the forgen-compound MCP tool \`compound-search\` with query "${target} audit bug security race" ` +
|
|
71
|
+
`(call it via ToolSearch if needed) to recall accumulated patterns/solutions relevant to auditing ${target}. ` +
|
|
72
|
+
`Return a concise bullet summary of prior knowledge that should inform an audit (gotchas, known pitfalls, ` +
|
|
73
|
+
`judgment criteria). If the tool is unavailable or returns nothing, reply exactly: (no prior compound knowledge).`,
|
|
74
|
+
{ label: 'recall:compound', phase: 'Recall' },
|
|
75
|
+
)
|
|
76
|
+
const recallContext = recall && !/no prior compound knowledge/i.test(recall)
|
|
77
|
+
? `\n\nPrior compound knowledge (apply where relevant):\n${recall}`
|
|
78
|
+
: ''
|
|
79
|
+
|
|
80
|
+
// pipeline: 각 차원의 find 가 끝나는 즉시 그 발견들을 forgen-verify 로 검증.
|
|
81
|
+
const results = await pipeline(
|
|
82
|
+
DIMENSIONS,
|
|
83
|
+
(d) => agent(`${d.prompt}\nReturn each issue as a concrete, testable claim with a repro.${recallContext}`, {
|
|
84
|
+
label: `find:${d.key}`, phase: 'Find', schema: FINDINGS_SCHEMA,
|
|
85
|
+
}),
|
|
86
|
+
(found, d) => parallel((found?.findings || []).map((f) => () =>
|
|
87
|
+
agent(
|
|
88
|
+
`Adversarially verify this audit finding with REAL execution evidence (no mock). ` +
|
|
89
|
+
`If you cannot reproduce it, return unverified. Default to refuted when uncertain.\n\n` +
|
|
90
|
+
`Claim: ${f.claim}\nLocation: ${f.file}${f.line ? ':' + f.line : ''}\nRepro: ${f.repro || '(none given — derive one)'}`,
|
|
91
|
+
{ label: `verify:${d.key}:${f.file}`, phase: 'Verify', agentType: 'forgen-verify', schema: VERDICT_SCHEMA },
|
|
92
|
+
).then((v) => ({ ...f, dimension: d.key, ...v })),
|
|
93
|
+
)),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const all = results.flat().filter(Boolean)
|
|
97
|
+
const confirmed = all.filter((f) => f.verdict === 'confirmed')
|
|
98
|
+
const unverified = all.filter((f) => f.verdict === 'unverified')
|
|
99
|
+
|
|
100
|
+
log(`evidence-gate-audit: ${all.length} findings → ${confirmed.length} confirmed, ${unverified.length} unverified (dropped: refuted)`)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
target,
|
|
104
|
+
confirmed,
|
|
105
|
+
unverified, // surfaced separately — NOT silently dropped (no-silent-caps)
|
|
106
|
+
summary: `${confirmed.length} proven issues in ${target}. ${unverified.length} could not be verified.`,
|
|
107
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
{ "name": "permission-handler", "tier": "workflow", "event": "PermissionRequest", "matcher": "*", "script": "hooks/permission-handler.js", "timeout": 2, "compoundCritical": false },
|
|
17
17
|
{ "name": "subagent-tracker-start", "tier": "workflow", "event": "SubagentStart", "matcher": "*", "script": "hooks/subagent-tracker.js start", "timeout": 2, "compoundCritical": false },
|
|
18
18
|
{ "name": "subagent-tracker-stop", "tier": "workflow", "event": "SubagentStop", "matcher": "*", "script": "hooks/subagent-tracker.js stop", "timeout": 2, "compoundCritical": false },
|
|
19
|
+
{ "name": "subagent-stop-guard", "tier": "workflow", "event": "SubagentStop", "matcher": "*", "script": "hooks/subagent-stop-guard.js", "timeout": 10, "compoundCritical": false },
|
|
19
20
|
{ "name": "post-tool-failure", "tier": "workflow", "event": "PostToolUseFailure", "matcher": "*", "script": "hooks/post-tool-failure.js", "timeout": 3, "compoundCritical": false },
|
|
20
21
|
{ "name": "solution-injector", "tier": "compound-core", "event": "UserPromptSubmit", "matcher": "*", "script": "hooks/solution-injector.js", "timeout": 5, "compoundCritical": true },
|
|
21
22
|
{ "name": "skill-injector", "tier": "compound-core", "event": "UserPromptSubmit", "matcher": "*", "script": "hooks/skill-injector.js", "timeout": 5, "compoundCritical": true },
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta-guard dispatcher — TEST-1/2/3 + DANGEROUS 빌트인 가드의 단일 평가 지점.
|
|
3
|
+
*
|
|
4
|
+
* ADR-009 §2a: 기존 stop-guard.main() 안에 인라인돼 있던 checks[] + for-loop 를
|
|
5
|
+
* 순수 함수로 추출한다. 목적은 동일 로직을 `Stop`(메인 응답) 과 `SubagentStop`
|
|
6
|
+
* (워크플로우/Task subagent 응답) 두 hook 에서 공유하기 위함이다 (probe 실측:
|
|
7
|
+
* 워크플로우 내부 에이전트도 forgen 훅을 발화 → subagent 산출물도 검증 대상).
|
|
8
|
+
*
|
|
9
|
+
* 순수성: IO/부수효과 없음. recordViolation·blockStop·override 분기는 호출자가
|
|
10
|
+
* 담당한다 (hook 별로 다름). 평가 순서·sanitize 적용·dangerous 의 raw 입력 사용은
|
|
11
|
+
* 추출 전 stop-guard 동작과 정확히 동일하게 보존한다 (회귀 테스트로 박제).
|
|
12
|
+
*/
|
|
13
|
+
export interface MetaGuardContext {
|
|
14
|
+
/** 평가 대상 응답 원문 (dangerous-pattern 은 코드펜스 보존 위해 raw 사용). */
|
|
15
|
+
lastMessage: string;
|
|
16
|
+
/** 최근 tool 이름 윈도우 (TEST-1/2 의 "측정 도구 호출 수" 계산). */
|
|
17
|
+
recentTools: string[];
|
|
18
|
+
/** TEST-1 fact-vs-agreement 최소 측정 횟수 (기본 1). */
|
|
19
|
+
minMeasurements?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface MetaGuardResult {
|
|
22
|
+
/** 짧은 식별자 (builtin:<shortId> 형태의 rule_id 와 reason prefix 에 사용). */
|
|
23
|
+
shortId: string;
|
|
24
|
+
/** 사람-읽기 rule slug (systemMessage 보조). */
|
|
25
|
+
ruleSlug: string;
|
|
26
|
+
/** block = 세션 재개 강제, correction = 기록만 (alert-level). */
|
|
27
|
+
kind: 'block' | 'correction';
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 트리거된 가드를 평가 순서대로 반환한다. **첫 block 까지** 평가 후 중단한다
|
|
32
|
+
* (추출 전 for-loop 가 첫 block 에서 return 하던 laziness 보존 — block 이후 가드는
|
|
33
|
+
* 어차피 기록되지 않으므로 평가하지 않는다).
|
|
34
|
+
*
|
|
35
|
+
* 평가 순서: DANGEROUS-RESPONSE(즉시 차단·안전 우선) → TEST-2(self-score, 강한 신호)
|
|
36
|
+
* → TEST-3(conclusion/verification 비율) → TEST-1(fact-vs-agreement, alert-only).
|
|
37
|
+
*/
|
|
38
|
+
export declare function runMetaGuards(ctx: MetaGuardContext): MetaGuardResult[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta-guard dispatcher — TEST-1/2/3 + DANGEROUS 빌트인 가드의 단일 평가 지점.
|
|
3
|
+
*
|
|
4
|
+
* ADR-009 §2a: 기존 stop-guard.main() 안에 인라인돼 있던 checks[] + for-loop 를
|
|
5
|
+
* 순수 함수로 추출한다. 목적은 동일 로직을 `Stop`(메인 응답) 과 `SubagentStop`
|
|
6
|
+
* (워크플로우/Task subagent 응답) 두 hook 에서 공유하기 위함이다 (probe 실측:
|
|
7
|
+
* 워크플로우 내부 에이전트도 forgen 훅을 발화 → subagent 산출물도 검증 대상).
|
|
8
|
+
*
|
|
9
|
+
* 순수성: IO/부수효과 없음. recordViolation·blockStop·override 분기는 호출자가
|
|
10
|
+
* 담당한다 (hook 별로 다름). 평가 순서·sanitize 적용·dangerous 의 raw 입력 사용은
|
|
11
|
+
* 추출 전 stop-guard 동작과 정확히 동일하게 보존한다 (회귀 테스트로 박제).
|
|
12
|
+
*/
|
|
13
|
+
import { checkConclusionVerificationRatio } from '../conclusion-verification-ratio.js';
|
|
14
|
+
import { checkSelfScoreInflation } from '../self-score-deflation.js';
|
|
15
|
+
import { checkFactVsAgreement } from '../fact-vs-agreement.js';
|
|
16
|
+
import { checkDangerousResponsePattern } from '../dangerous-response-pattern.js';
|
|
17
|
+
import { sanitizeForGuard } from './text-sanitizer.js';
|
|
18
|
+
/**
|
|
19
|
+
* 트리거된 가드를 평가 순서대로 반환한다. **첫 block 까지** 평가 후 중단한다
|
|
20
|
+
* (추출 전 for-loop 가 첫 block 에서 return 하던 laziness 보존 — block 이후 가드는
|
|
21
|
+
* 어차피 기록되지 않으므로 평가하지 않는다).
|
|
22
|
+
*
|
|
23
|
+
* 평가 순서: DANGEROUS-RESPONSE(즉시 차단·안전 우선) → TEST-2(self-score, 강한 신호)
|
|
24
|
+
* → TEST-3(conclusion/verification 비율) → TEST-1(fact-vs-agreement, alert-only).
|
|
25
|
+
*/
|
|
26
|
+
export function runMetaGuards(ctx) {
|
|
27
|
+
const sanitized = sanitizeForGuard(ctx.lastMessage);
|
|
28
|
+
const recentTools = ctx.recentTools;
|
|
29
|
+
const minMeasurements = ctx.minMeasurements ?? 1;
|
|
30
|
+
const checks = [
|
|
31
|
+
{
|
|
32
|
+
shortId: 'dangerous-response-pattern',
|
|
33
|
+
ruleSlug: 'rule:DANGEROUS-RESPONSE — destructive command suggestion',
|
|
34
|
+
kind: 'block',
|
|
35
|
+
// 주의: sanitizer 가 백틱/코드블록을 제거하므로 raw lastMessage 를 전달.
|
|
36
|
+
// 위험 명령은 코드 fence 안에 있어도 동등하게 위험함.
|
|
37
|
+
run: () => {
|
|
38
|
+
const r = checkDangerousResponsePattern({ text: ctx.lastMessage });
|
|
39
|
+
return { triggered: r.block, reason: r.reason };
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
shortId: 'self-score-inflation',
|
|
44
|
+
ruleSlug: 'rule:TEST-2 — self-score inflation',
|
|
45
|
+
kind: 'block',
|
|
46
|
+
run: () => {
|
|
47
|
+
const r = checkSelfScoreInflation({ text: sanitized, recentTools });
|
|
48
|
+
return { triggered: r.block, reason: r.reason };
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
shortId: 'conclusion-ratio',
|
|
53
|
+
ruleSlug: 'rule:TEST-3 — conclusion/verification ratio',
|
|
54
|
+
kind: 'block',
|
|
55
|
+
run: () => {
|
|
56
|
+
const r = checkConclusionVerificationRatio({ text: sanitized });
|
|
57
|
+
return { triggered: r.block, reason: r.reason };
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
shortId: 'fact-vs-agreement',
|
|
62
|
+
ruleSlug: 'rule:TEST-1 — fact vs agreement',
|
|
63
|
+
kind: 'correction', // alert-level only per fact-vs-agreement.ts design
|
|
64
|
+
run: () => {
|
|
65
|
+
const r = checkFactVsAgreement({ text: sanitized, recentTools, minMeasurements });
|
|
66
|
+
return { triggered: r.alert, reason: r.reason };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
const results = [];
|
|
71
|
+
for (const c of checks) {
|
|
72
|
+
const out = c.run();
|
|
73
|
+
if (!out.triggered)
|
|
74
|
+
continue;
|
|
75
|
+
results.push({ shortId: c.shortId, ruleSlug: c.ruleSlug, kind: c.kind, reason: out.reason });
|
|
76
|
+
if (c.kind === 'block')
|
|
77
|
+
break; // 첫 block 에서 중단 (이후 가드는 기록되지 않음)
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
@@ -56,5 +56,20 @@ export function sanitizeForGuard(raw) {
|
|
|
56
56
|
// 긴 인용은 사용자 발언/실제 사실 인용이므로 가드 판정 대상에 남김.
|
|
57
57
|
const shortQuoteRe = new RegExp(`"[^"\\n]{0,${SHORT_QUOTE_MAX}}"`, 'g');
|
|
58
58
|
s = s.replace(shortQuoteRe, '');
|
|
59
|
+
// 5) ADR-009 §7 후속: 곡선따옴표(curly)·한글 인용부호로 감싼 짧은 referential
|
|
60
|
+
// 토큰도 제거. 메타 대화에서 트리거 어휘를 「'mock'」, "verified" 처럼
|
|
61
|
+
// 인용만 해도 가드가 self-match 하던 거짓양성(이 세션 다수 관찰)을 줄인다.
|
|
62
|
+
// SHORT_QUOTE_MAX 이하만 — 긴 인용(사용자 발언/사실)은 보존.
|
|
63
|
+
const QUOTE_PAIRS = [
|
|
64
|
+
['‘', '’'], // ' ' single curly
|
|
65
|
+
['“', '”'], // " " double curly
|
|
66
|
+
['「', '」'], // 「 」 fullwidth
|
|
67
|
+
['「', '」'], // 「 」 halfwidth
|
|
68
|
+
['『', '』'], // 『 』
|
|
69
|
+
];
|
|
70
|
+
for (const [open, close] of QUOTE_PAIRS) {
|
|
71
|
+
const re = new RegExp(`${open}[^${close}\\n]{0,${SHORT_QUOTE_MAX}}${close}`, 'g');
|
|
72
|
+
s = s.replace(re, '');
|
|
73
|
+
}
|
|
59
74
|
return s;
|
|
60
75
|
}
|