@wooojin/forgen 0.4.13 → 0.5.5
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 +261 -0
- package/README.ja.md +16 -12
- package/README.ko.md +12 -8
- package/README.md +90 -27
- package/README.zh.md +16 -12
- package/assets/claude/commands/forge-loop.md +7 -1
- package/assets/claude/commands/ship.md +18 -1
- package/assets/opencode/forgen.ts +52 -0
- package/assets/shared/hook-registry.json +210 -22
- package/dist/checks/_shared/meta-guard-dispatch.d.ts +7 -0
- package/dist/checks/_shared/meta-guard-dispatch.js +12 -2
- package/dist/checks/_shared/model-profile.d.ts +25 -0
- package/dist/checks/_shared/model-profile.js +63 -0
- package/dist/cli.js +99 -153
- package/dist/core/auto-compound-runner.d.ts +0 -11
- package/dist/core/auto-compound-runner.js +199 -92
- package/dist/core/changelog-cli.d.ts +4 -1
- package/dist/core/changelog-cli.js +8 -7
- package/dist/core/compound-consent.d.ts +15 -0
- package/dist/core/compound-consent.js +48 -0
- package/dist/core/compound-sweep-cli.d.ts +60 -0
- package/dist/core/compound-sweep-cli.js +374 -0
- package/dist/core/config-injector.d.ts +16 -1
- package/dist/core/config-injector.js +47 -28
- package/dist/core/dashboard-cli.js +40 -16
- package/dist/core/dashboard.d.ts +3 -4
- package/dist/core/dashboard.js +15 -34
- package/dist/core/dev-cli.d.ts +13 -0
- package/dist/core/dev-cli.js +70 -0
- package/dist/core/doctor.d.ts +16 -5
- package/dist/core/doctor.js +190 -176
- package/dist/core/drift-score.d.ts +2 -0
- package/dist/core/drift-score.js +9 -1
- package/dist/core/harness.js +115 -43
- package/dist/core/health-cli.d.ts +2 -0
- package/dist/core/health-cli.js +6 -1
- package/dist/core/host-detect.d.ts +3 -1
- package/dist/core/host-detect.js +26 -1
- package/dist/core/migrate-cli.js +15 -0
- package/dist/core/migrate-evidence-host.d.ts +2 -1
- package/dist/core/migrate-tenetx.d.ts +50 -0
- package/dist/core/migrate-tenetx.js +262 -0
- package/dist/core/probe-workflow-cli.d.ts +3 -3
- package/dist/core/probe-workflow-cli.js +13 -13
- package/dist/core/recall-cli.js +1 -1
- package/dist/core/regress-map-cli.js +1 -1
- package/dist/core/rendered-rules-manifest.d.ts +29 -0
- package/dist/core/rendered-rules-manifest.js +60 -0
- package/dist/core/session-store.js +14 -3
- package/dist/core/settings-injector.d.ts +3 -0
- package/dist/core/settings-injector.js +12 -16
- package/dist/core/spawn.d.ts +37 -1
- package/dist/core/spawn.js +116 -9
- package/dist/core/state-gc.js +1 -0
- package/dist/core/status-cli.d.ts +20 -0
- package/dist/core/status-cli.js +100 -0
- package/dist/core/statusline-cli.d.ts +7 -0
- package/dist/core/statusline-cli.js +63 -19
- package/dist/core/transcript-summary.d.ts +18 -0
- package/dist/core/transcript-summary.js +81 -0
- package/dist/core/trust-layer-intent.d.ts +21 -1
- package/dist/core/trust-layer-intent.js +7 -0
- package/dist/core/types.d.ts +3 -2
- package/dist/core/uninstall.js +12 -0
- package/dist/core/usage-telemetry.d.ts +7 -1
- package/dist/core/usage-telemetry.js +7 -9
- package/dist/core/v1-bootstrap.js +1 -1
- package/dist/core/watch-cli.js +1 -1
- package/dist/engine/compound-extractor.js +10 -0
- package/dist/engine/compound-loop.js +55 -7
- package/dist/engine/compound-share.d.ts +85 -0
- package/dist/engine/compound-share.js +606 -0
- package/dist/engine/correction-cluster-runner.d.ts +38 -0
- package/dist/engine/correction-cluster-runner.js +188 -0
- package/dist/engine/correction-clustering.d.ts +80 -0
- package/dist/engine/correction-clustering.js +167 -0
- package/dist/engine/enforce-classifier.d.ts +10 -1
- package/dist/engine/enforce-classifier.js +111 -22
- package/dist/engine/extraction-session.js +10 -3
- package/dist/engine/private-filter.d.ts +36 -0
- package/dist/engine/private-filter.js +100 -0
- package/dist/engine/ranking-pipeline.js +4 -2
- package/dist/engine/relevance-gate.d.ts +12 -0
- package/dist/engine/relevance-gate.js +12 -0
- package/dist/engine/roi-demotion.d.ts +79 -0
- package/dist/engine/roi-demotion.js +159 -0
- package/dist/engine/solution-format.d.ts +1 -0
- package/dist/engine/solution-format.js +26 -0
- package/dist/engine/solution-matcher.js +10 -1
- package/dist/fgx.js +7 -6
- package/dist/forge/cli.js +8 -2
- package/dist/hooks/compound-reflection.js +6 -1
- package/dist/hooks/context-guard.d.ts +16 -1
- package/dist/hooks/context-guard.js +99 -49
- package/dist/hooks/hook-config.js +5 -0
- package/dist/hooks/hook-registry.d.ts +6 -1
- package/dist/hooks/hooks-generator.js +3 -0
- package/dist/hooks/post-tool-use.js +2 -3
- package/dist/hooks/pre-compact.js +14 -0
- package/dist/hooks/pre-tool-use.js +5 -1
- package/dist/hooks/session-end.d.ts +34 -0
- package/dist/hooks/session-end.js +100 -0
- package/dist/hooks/session-recovery.js +18 -1
- package/dist/hooks/shared/hook-timing.js +6 -1
- package/dist/hooks/shared/stop-triggers.d.ts +29 -2
- package/dist/hooks/shared/stop-triggers.js +35 -2
- package/dist/hooks/solution-injector.d.ts +28 -0
- package/dist/hooks/solution-injector.js +126 -28
- package/dist/hooks/stop-guard.js +5 -2
- package/dist/hooks/subagent-stop-guard.js +4 -1
- package/dist/host/capabilities-claude.js +1 -0
- package/dist/host/capabilities-codex.js +1 -0
- package/dist/host/capabilities-opencode.d.ts +26 -0
- package/dist/host/capabilities-opencode.js +78 -0
- package/dist/host/capabilities-registry.d.ts +7 -0
- package/dist/host/capabilities-registry.js +14 -0
- package/dist/host/codex-rules-context.d.ts +25 -0
- package/dist/host/codex-rules-context.js +62 -0
- package/dist/host/exec-host.d.ts +14 -3
- package/dist/host/exec-host.js +15 -2
- package/dist/host/host-binding.d.ts +27 -0
- package/dist/host/host-binding.js +11 -0
- package/dist/host/host-runtime.js +18 -0
- package/dist/host/install-codex.d.ts +66 -0
- package/dist/host/install-codex.js +293 -25
- package/dist/host/install-opencode.d.ts +38 -0
- package/dist/host/install-opencode.js +148 -0
- package/dist/host/install-orchestrator.d.ts +4 -1
- package/dist/host/install-orchestrator.js +30 -1
- package/dist/host/invoke-agent.d.ts +3 -2
- package/dist/host/invoke-agent.js +1 -0
- package/dist/host/opencode/context-cli.d.ts +15 -0
- package/dist/host/opencode/context-cli.js +25 -0
- package/dist/host/opencode/guard-cli.d.ts +13 -0
- package/dist/host/opencode/guard-cli.js +39 -0
- package/dist/host/opencode/plugin/forgen.d.ts +31 -0
- package/dist/host/opencode/plugin/forgen.js +60 -0
- package/dist/host/opencode/translate.d.ts +49 -0
- package/dist/host/opencode/translate.js +96 -0
- package/dist/host/parity-harness.d.ts +10 -2
- package/dist/host/parity-harness.js +7 -3
- package/dist/host/projection.d.ts +37 -3
- package/dist/host/projection.js +104 -42
- package/dist/mcp/tools.js +17 -4
- package/dist/store/evidence-store.d.ts +2 -6
- package/dist/store/evidence-store.js +164 -46
- package/dist/store/host-mismatch.d.ts +2 -1
- package/dist/store/host-mismatch.js +8 -8
- package/dist/store/profile-store.d.ts +3 -2
- package/dist/store/rule-store.d.ts +41 -0
- package/dist/store/rule-store.js +77 -2
- package/dist/store/types.d.ts +9 -2
- package/hooks/hooks.json +13 -1
- package/package.json +3 -2
- package/plugin.json +2 -2
- package/skills/architecture-decision/SKILL.md +18 -0
- package/skills/calibrate/SKILL.md +18 -0
- package/skills/code-review/SKILL.md +17 -0
- package/skills/compound/SKILL.md +17 -0
- package/skills/deep-interview/SKILL.md +7 -0
- package/skills/docker/SKILL.md +18 -0
- package/skills/forge-loop/SKILL.md +30 -1
- package/skills/learn/SKILL.md +15 -0
- package/skills/retro/SKILL.md +16 -0
- package/skills/ship/SKILL.md +36 -1
|
@@ -10,4 +10,11 @@
|
|
|
10
10
|
* Line 4: (TODO: tool counts — 추적 인프라 없음)
|
|
11
11
|
* Line 5: (TODO: active task — 추적 인프라 없음)
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* W1-2 (feature-audit 2026-07-21): forgen 가치 카운터 라인 — invisible 가치 가시화.
|
|
15
|
+
* native 플랜/토큰 usage(→ /usage, W2에서 물러남)가 아니라 forgen *자체 활동량*을
|
|
16
|
+
* 노출한다: recall 주입·surfaced·교정 캡처·ROI 강등·차단. 전부 computeStats()가 이미
|
|
17
|
+
* 실측 중인 카운터라 조작 위험 0. computeStats 실패 시 라인 생략(fail-open).
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildValueLine(): string | null;
|
|
13
20
|
export declare function handleStatusline(): Promise<void>;
|
|
@@ -15,9 +15,10 @@ import * as path from 'node:path';
|
|
|
15
15
|
import * as os from 'node:os';
|
|
16
16
|
import { execSync } from 'node:child_process';
|
|
17
17
|
import { loadActiveRules } from '../store/rule-store.js';
|
|
18
|
-
import { getUsageStats } from './usage-telemetry.js';
|
|
19
18
|
import { STATE_DIR } from './paths.js';
|
|
20
19
|
import { classifySolutions } from './lifecycle-classifier.js';
|
|
20
|
+
import { computeStats } from './stats-cli.js';
|
|
21
|
+
import { loadRoiDemotions } from '../engine/roi-demotion.js';
|
|
21
22
|
// 0.4.6 perf #13 — statusline 출력을 5초 캐싱.
|
|
22
23
|
// claude statusLine 은 짧은 간격으로 재호출되는데 매번 git/find/rule-store 를
|
|
23
24
|
// 실행하면 ~100ms 누적. CACHE_TTL_MS 동안 동일 출력 재사용.
|
|
@@ -148,18 +149,48 @@ function buildLifecycleLine() {
|
|
|
148
149
|
return null;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* ADR-010 W2-2: 사용량 세그먼트("📊 N/5h · N/wk") 제거 — native /usage 가
|
|
154
|
+
* plan limit 를 정확히 분해한다. 이관 사실을 딱 1회만 공지 (state flag).
|
|
155
|
+
*/
|
|
152
156
|
function buildUsageLine() {
|
|
153
157
|
try {
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
const noticeFlag = path.join(STATE_DIR, 'usage-notice-shown');
|
|
159
|
+
if (!fs.existsSync(noticeFlag)) {
|
|
160
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
161
|
+
fs.writeFileSync(noticeFlag, new Date().toISOString());
|
|
162
|
+
return `${DIM}ℹ 사용량 표시는 native /usage 로 이동했습니다 (이 안내는 1회만 표시)${RESET}`;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* W1-2 (feature-audit 2026-07-21): forgen 가치 카운터 라인 — invisible 가치 가시화.
|
|
172
|
+
* native 플랜/토큰 usage(→ /usage, W2에서 물러남)가 아니라 forgen *자체 활동량*을
|
|
173
|
+
* 노출한다: recall 주입·surfaced·교정 캡처·ROI 강등·차단. 전부 computeStats()가 이미
|
|
174
|
+
* 실측 중인 카운터라 조작 위험 0. computeStats 실패 시 라인 생략(fail-open).
|
|
175
|
+
*/
|
|
176
|
+
export function buildValueLine() {
|
|
177
|
+
try {
|
|
178
|
+
let roiDemoted = 0;
|
|
179
|
+
try {
|
|
180
|
+
roiDemoted = Object.keys(loadRoiDemotions()).length;
|
|
181
|
+
}
|
|
182
|
+
catch { /* roi store 없음 → 0 */ }
|
|
183
|
+
const s = computeStats();
|
|
184
|
+
const a = s.assistToday;
|
|
185
|
+
const parts = [
|
|
186
|
+
`${YELLOW}✦${RESET}`,
|
|
187
|
+
`${DIM}recall${RESET} ${a.recallHits}`,
|
|
188
|
+
`${DIM}surfaced${RESET} ${a.surfaced}`,
|
|
189
|
+
`${DIM}교정${RESET} +${s.corrections7d}${DIM}(7d)${RESET}`,
|
|
190
|
+
`${DIM}ROI↓${RESET} ${roiDemoted}`,
|
|
191
|
+
`${DIM}차단${RESET} ${s.blocks7d}${DIM}(7d)${RESET}`,
|
|
192
|
+
];
|
|
193
|
+
return parts.join(` ${DIM}·${RESET} `);
|
|
163
194
|
}
|
|
164
195
|
catch {
|
|
165
196
|
return null;
|
|
@@ -204,7 +235,19 @@ function writeCache(content) {
|
|
|
204
235
|
catch { /* fail-open */ }
|
|
205
236
|
}
|
|
206
237
|
export async function handleStatusline() {
|
|
207
|
-
//
|
|
238
|
+
// W4-3 (ADR-010): hook stdin 엔 모델 필드가 없으므로 statusline 이 세션별
|
|
239
|
+
// 모델을 캐시 → Stop/SubagentStop 가드가 per-model 프로필 조회에 사용.
|
|
240
|
+
// 리뷰 SEV-2: 이 기록은 반드시 5초 표시-캐시 early-return **앞**에 있어야
|
|
241
|
+
// 한다 — /model 로 세션 중 모델을 바꾸면 cache-hit 렌더가 payload 를 아예
|
|
242
|
+
// 안 읽어 stale 모델(잘못된 가드 모드)이 유지되는 창이 생긴다.
|
|
243
|
+
// 필드 부재(구버전 CC 등) 시 기록 안 함 = 가드는 'unknown' → block 유지.
|
|
244
|
+
const payload = readStdinJson();
|
|
245
|
+
if (payload.session_id && payload.model?.id) {
|
|
246
|
+
const { cacheSessionModel } = await import('../checks/_shared/model-profile.js');
|
|
247
|
+
const { sanitizeId } = await import('../hooks/shared/sanitize-id.js');
|
|
248
|
+
cacheSessionModel(sanitizeId(payload.session_id), payload.model.id);
|
|
249
|
+
}
|
|
250
|
+
// 캐시 hit 시 표시만 캐시에서 (5초 윈도우 내 동일 출력 가정).
|
|
208
251
|
// 라인 단위 cache → console.log 라인별 (테스트 호환).
|
|
209
252
|
const cached = readCacheIfFresh();
|
|
210
253
|
if (cached !== null) {
|
|
@@ -212,27 +255,28 @@ export async function handleStatusline() {
|
|
|
212
255
|
console.log(line);
|
|
213
256
|
return;
|
|
214
257
|
}
|
|
215
|
-
const payload = readStdinJson();
|
|
216
258
|
const cwd = payload.workspace?.current_dir ?? process.cwd();
|
|
217
259
|
const claudeDir = path.join(os.homedir(), '.claude');
|
|
218
260
|
const line1 = buildLine1(payload, cwd);
|
|
219
261
|
const line3 = buildLine3(claudeDir, cwd);
|
|
220
262
|
const usageLine = buildUsageLine();
|
|
263
|
+
const valueLine = buildValueLine(); // W1-2: forgen 가치 카운터
|
|
221
264
|
const lifecycleLine = buildLifecycleLine();
|
|
222
|
-
// Line 2 (context/usage): stdin JSON spec 미확인으로 생략 — TODO
|
|
223
|
-
// Line 4 (tool counts): 추적 인프라 없음 — TODO
|
|
224
|
-
// Line 5 (active task): 추적 인프라 없음 — TODO
|
|
225
265
|
console.log(line1);
|
|
226
266
|
console.log(line3);
|
|
227
267
|
if (usageLine)
|
|
228
268
|
console.log(usageLine);
|
|
269
|
+
if (valueLine)
|
|
270
|
+
console.log(valueLine);
|
|
229
271
|
if (lifecycleLine)
|
|
230
272
|
console.log(lifecycleLine);
|
|
273
|
+
// W2-2: 1회 공지(usageLine)는 캐시에 넣지 않는다 — 캐시 재생 시
|
|
274
|
+
// "1회만" 약속이 5초 창 동안 반복 위반되는 실측 버그 방지.
|
|
231
275
|
const cacheLines = [line1, line3];
|
|
232
|
-
if (
|
|
233
|
-
cacheLines.push(
|
|
276
|
+
if (valueLine)
|
|
277
|
+
cacheLines.push(valueLine);
|
|
234
278
|
if (lifecycleLine)
|
|
235
279
|
cacheLines.push(lifecycleLine);
|
|
236
|
-
const cacheBody = cacheLines.join('\n')
|
|
280
|
+
const cacheBody = `${cacheLines.join('\n')}\n`;
|
|
237
281
|
writeCache(cacheBody);
|
|
238
282
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript → 요약 텍스트 (순수, IO 없음 — 테스트 가능).
|
|
3
|
+
*
|
|
4
|
+
* auto-compound-runner 의 extractSummary 에서 분리(2026-08-04). 분리 이유: 실 Claude
|
|
5
|
+
* transcript 스키마 버그(text 가 `entry.message.content` 에 있는데 top-level `entry.content`
|
|
6
|
+
* 만 읽어 전 user/assistant 턴 누락 → auto-compound 가 실데이터에서 dead)를 회귀 테스트로
|
|
7
|
+
* 고정하기 위함. 러너 본문은 process.argv 실행부와 묶여 있어 직접 unit-test 불가였다.
|
|
8
|
+
*
|
|
9
|
+
* 스키마:
|
|
10
|
+
* Claude: {type:'user'|'assistant', message:{role, content: string | [{type:'text',text}]}}
|
|
11
|
+
* (일부 queue-operation 등은 top-level `content` 에 저장 → fallback)
|
|
12
|
+
* Codex: {type:'response_item', payload:{role, content:[{type:'input_text', text}]}}
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractText(c: unknown): string;
|
|
15
|
+
/** Codex content array → flat string. content: [{type:'input_text', text}] */
|
|
16
|
+
export declare function extractCodexText(content: unknown): string;
|
|
17
|
+
/** JSONL 원문 → `[User]/[Assistant]` 요약. maxChars 초과 시 조기 종료. */
|
|
18
|
+
export declare function summarizeTranscript(rawText: string, maxChars?: number): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript → 요약 텍스트 (순수, IO 없음 — 테스트 가능).
|
|
3
|
+
*
|
|
4
|
+
* auto-compound-runner 의 extractSummary 에서 분리(2026-08-04). 분리 이유: 실 Claude
|
|
5
|
+
* transcript 스키마 버그(text 가 `entry.message.content` 에 있는데 top-level `entry.content`
|
|
6
|
+
* 만 읽어 전 user/assistant 턴 누락 → auto-compound 가 실데이터에서 dead)를 회귀 테스트로
|
|
7
|
+
* 고정하기 위함. 러너 본문은 process.argv 실행부와 묶여 있어 직접 unit-test 불가였다.
|
|
8
|
+
*
|
|
9
|
+
* 스키마:
|
|
10
|
+
* Claude: {type:'user'|'assistant', message:{role, content: string | [{type:'text',text}]}}
|
|
11
|
+
* (일부 queue-operation 등은 top-level `content` 에 저장 → fallback)
|
|
12
|
+
* Codex: {type:'response_item', payload:{role, content:[{type:'input_text', text}]}}
|
|
13
|
+
*/
|
|
14
|
+
export function extractText(c) {
|
|
15
|
+
if (typeof c === 'string')
|
|
16
|
+
return c;
|
|
17
|
+
if (Array.isArray(c)) {
|
|
18
|
+
return c
|
|
19
|
+
.filter((x) => typeof x === 'object' && x !== null && x.type === 'text')
|
|
20
|
+
.map((x) => (typeof x.text === 'string' ? x.text : ''))
|
|
21
|
+
.join('\n');
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
/** Codex content array → flat string. content: [{type:'input_text', text}] */
|
|
26
|
+
export function extractCodexText(content) {
|
|
27
|
+
if (!Array.isArray(content))
|
|
28
|
+
return '';
|
|
29
|
+
const parts = [];
|
|
30
|
+
for (const item of content) {
|
|
31
|
+
if (item && typeof item === 'object' && 'text' in item && typeof item.text === 'string') {
|
|
32
|
+
parts.push(item.text);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return parts.join('\n');
|
|
36
|
+
}
|
|
37
|
+
/** JSONL 원문 → `[User]/[Assistant]` 요약. maxChars 초과 시 조기 종료. */
|
|
38
|
+
export function summarizeTranscript(rawText, maxChars = 8000) {
|
|
39
|
+
const lines = rawText.split('\n').filter(Boolean);
|
|
40
|
+
const messages = [];
|
|
41
|
+
let totalChars = 0;
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
try {
|
|
44
|
+
const entry = JSON.parse(line);
|
|
45
|
+
// Claude schema — text 는 message.content 우선(실버그 수정), top-level 은 fallback.
|
|
46
|
+
if (entry.type === 'user' || entry.type === 'queue-operation') {
|
|
47
|
+
const text = extractText(entry.message?.content ?? entry.content);
|
|
48
|
+
if (text) {
|
|
49
|
+
messages.push(`[User] ${text.slice(0, 500)}`);
|
|
50
|
+
totalChars += text.length;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (entry.type === 'assistant') {
|
|
54
|
+
const text = extractText(entry.message?.content ?? entry.content);
|
|
55
|
+
if (text) {
|
|
56
|
+
messages.push(`[Assistant] ${text.slice(0, 500)}`);
|
|
57
|
+
totalChars += text.length;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Codex schema
|
|
61
|
+
else if (entry.type === 'response_item' && entry.payload?.role === 'user') {
|
|
62
|
+
const text = extractCodexText(entry.payload.content);
|
|
63
|
+
if (text) {
|
|
64
|
+
messages.push(`[User] ${text.slice(0, 500)}`);
|
|
65
|
+
totalChars += text.length;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (entry.type === 'response_item' && entry.payload?.role === 'assistant') {
|
|
69
|
+
const text = extractCodexText(entry.payload.content);
|
|
70
|
+
if (text) {
|
|
71
|
+
messages.push(`[Assistant] ${text.slice(0, 500)}`);
|
|
72
|
+
totalChars += text.length;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* skip malformed line */ }
|
|
77
|
+
if (totalChars > maxChars)
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
return messages.join('\n\n');
|
|
81
|
+
}
|
|
@@ -19,9 +19,29 @@ export interface CapabilityDeclaration {
|
|
|
19
19
|
/** source-of-truth (spec 또는 외부 docs/source 인용). */
|
|
20
20
|
readonly source?: string;
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* 지원 host 의 정준 런타임 목록(단일 소스). `HostId` 는 여기서 파생되므로 새 host 추가 시
|
|
24
|
+
* 이 배열만 넓히면 타입·런타임이 함께 확장된다. 자유형 `host === 'claude'` 이진 비교 대신
|
|
25
|
+
* `(HOST_IDS as readonly string[]).includes(host)` 로 "유효 host 인지" 를 판정하라
|
|
26
|
+
* (W3-3 리뷰 SEV-3 #5: Record<HostId> 는 Record 리터럴만 강제, 자유비교는 미포착).
|
|
27
|
+
*/
|
|
28
|
+
export declare const HOST_IDS: readonly ["claude", "codex", "opencode"];
|
|
29
|
+
export type HostId = (typeof HOST_IDS)[number];
|
|
30
|
+
/**
|
|
31
|
+
* 능력 선언의 검증 수준 (W3-3 리뷰 SEV-3 #1).
|
|
32
|
+
* - 'runtime': forgen 이 이 host 에서 실제로 강제/실행함(레퍼런스 host — claude).
|
|
33
|
+
* - 'source' : host 의 hook schema 소스로 검증했고 forgen 배선 완료(codex).
|
|
34
|
+
* - 'docs' : host 문서 기반 선언이나 **forgen 배선 미완**(opencode P1 — plugin 슬림 전).
|
|
35
|
+
*
|
|
36
|
+
* status='supported' 의 의미가 host 마다 다른 문제를 구조화한다: 'runtime'/'source' 의
|
|
37
|
+
* supported 는 "forgen 이 강제함", 'docs' 의 supported 는 "플랫폼이 가능하나 forgen 미배선".
|
|
38
|
+
* 프로그램 소비자는 verificationLevel 로 둘을 게이트해야 한다(intentEnforced 참조).
|
|
39
|
+
*/
|
|
40
|
+
export type CapabilityVerificationLevel = 'runtime' | 'source' | 'docs';
|
|
23
41
|
export interface HostCapabilities {
|
|
24
42
|
readonly hostId: HostId;
|
|
43
|
+
/** 이 host 선언 전체의 검증 수준. 'docs' 면 아래 status 는 "플랫폼-가능"이지 "forgen-배선"이 아니다. */
|
|
44
|
+
readonly verificationLevel: CapabilityVerificationLevel;
|
|
25
45
|
/**
|
|
26
46
|
* 모든 TrustLayerIntent 에 대한 선언. `Record<TrustLayerIntent, _>` 타입이
|
|
27
47
|
* 컴파일 타임에 누락을 차단한다.
|
|
@@ -16,6 +16,13 @@ export const TRUST_LAYER_INTENTS = [
|
|
|
16
16
|
'forge-loop-state-inject',
|
|
17
17
|
'self-evidence-record',
|
|
18
18
|
];
|
|
19
|
+
/**
|
|
20
|
+
* 지원 host 의 정준 런타임 목록(단일 소스). `HostId` 는 여기서 파생되므로 새 host 추가 시
|
|
21
|
+
* 이 배열만 넓히면 타입·런타임이 함께 확장된다. 자유형 `host === 'claude'` 이진 비교 대신
|
|
22
|
+
* `(HOST_IDS as readonly string[]).includes(host)` 로 "유효 host 인지" 를 판정하라
|
|
23
|
+
* (W3-3 리뷰 SEV-3 #5: Record<HostId> 는 Record 리터럴만 강제, 자유비교는 미포착).
|
|
24
|
+
*/
|
|
25
|
+
export const HOST_IDS = ['claude', 'codex', 'opencode'];
|
|
19
26
|
/**
|
|
20
27
|
* 런타임 assertion — host adapter 가 새 의도 추가를 누락한 경우 fail.
|
|
21
28
|
* 컴파일 타임 가드를 우회하는 동적 생성 코드를 위한 안전망.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HostId } from './trust-layer-intent.js';
|
|
1
2
|
export interface Principle {
|
|
2
3
|
belief: string;
|
|
3
4
|
/**
|
|
@@ -106,8 +107,8 @@ export interface HarnessContext {
|
|
|
106
107
|
/** 모델 라우팅 프리셋 (default, cost-saving, max-quality) */
|
|
107
108
|
routingPreset?: string;
|
|
108
109
|
}
|
|
109
|
-
/** 런타임 Host */
|
|
110
|
-
export type RuntimeHost =
|
|
110
|
+
/** 런타임 Host — 정식 정의는 `trust-layer-intent.ts` 의 HostId (Multi-Harness Adapter Plan P0). */
|
|
111
|
+
export type RuntimeHost = HostId;
|
|
111
112
|
/** 런칭 컨텍스트 — CLI에서 runtime/args 결정을 모델화 */
|
|
112
113
|
export interface LaunchContext {
|
|
113
114
|
runtime: RuntimeHost;
|
package/dist/core/uninstall.js
CHANGED
|
@@ -236,6 +236,10 @@ function cleanCompoundRules(cwd) {
|
|
|
236
236
|
// v4.1+ consolidated
|
|
237
237
|
'project-context.md',
|
|
238
238
|
'routing.md',
|
|
239
|
+
// v0.5.0+ project-scoped (ADR-010 W1-3)
|
|
240
|
+
'v1-rules.md',
|
|
241
|
+
'forge-behavioral.md',
|
|
242
|
+
'user-profile.md',
|
|
239
243
|
// legacy (v4.0 and earlier)
|
|
240
244
|
'security.md',
|
|
241
245
|
'golden-principles.md',
|
|
@@ -257,6 +261,14 @@ function cleanCompoundRules(cwd) {
|
|
|
257
261
|
fs.unlinkSync(legacyPath);
|
|
258
262
|
removedCount++;
|
|
259
263
|
}
|
|
264
|
+
// 레거시 글로벌 사이드채널 (ADR-010 W1-3 이전 버전이 ~/.claude/rules/ 에 씀).
|
|
265
|
+
// 새 세션이 열리기 전에 uninstall 하는 경우 harness 마이그레이션이 못 지우므로
|
|
266
|
+
// 여기서도 회수한다 — 방치 시 언인스톨 후에도 전 세션에 주입된다.
|
|
267
|
+
const legacyGlobalBehavioral = path.join(os.homedir(), '.claude', 'rules', 'forge-behavioral.md');
|
|
268
|
+
if (fs.existsSync(legacyGlobalBehavioral)) {
|
|
269
|
+
fs.unlinkSync(legacyGlobalBehavioral);
|
|
270
|
+
removedCount++;
|
|
271
|
+
}
|
|
260
272
|
if (removedCount > 0) {
|
|
261
273
|
console.log(` ✓ Removed ${removedCount} rule file(s)`);
|
|
262
274
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* 0.4.6 신설. limit prediction 은 의도적으로 제외 — Anthropic 의 실제 limit 가
|
|
14
14
|
* 계정/플랜별 가변이라 hard-code 부정확. raw count 만 노출하고 사용자가 판단.
|
|
15
15
|
*/
|
|
16
|
+
import type { HostId } from './trust-layer-intent.js';
|
|
16
17
|
export interface UsageStats {
|
|
17
18
|
hour5: {
|
|
18
19
|
claude: number;
|
|
@@ -25,7 +26,12 @@ export interface UsageStats {
|
|
|
25
26
|
total: number;
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated ADR-010 W2-2 (2026-07-16): native `/usage` 가 plan limit 를
|
|
31
|
+
* skill/subagent/plugin/MCP 별로 정확히 분해하므로 forgen 의 raw count 는
|
|
32
|
+
* 중복 표면. 기록 중단(no-op shim — API 만 유지), v0.6.0 에서 모듈 삭제 예정.
|
|
33
|
+
*/
|
|
34
|
+
export declare function recordToolCall(_runtime?: HostId): void;
|
|
29
35
|
/**
|
|
30
36
|
* sliding window count. fail-open: 파일 미존재/parse 실패는 0 반환.
|
|
31
37
|
*
|
|
@@ -22,15 +22,13 @@ const TELEMETRY_PATH = path.join(STATE_DIR, 'usage-telemetry.jsonl');
|
|
|
22
22
|
const HOUR5_MS = 5 * 60 * 60 * 1000;
|
|
23
23
|
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
24
|
const PRUNE_THRESHOLD = 10_000; // 10K lines 누적 시 prune
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
log.debug('telemetry append 실패', e);
|
|
33
|
-
}
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated ADR-010 W2-2 (2026-07-16): native `/usage` 가 plan limit 를
|
|
27
|
+
* skill/subagent/plugin/MCP 별로 정확히 분해하므로 forgen 의 raw count 는
|
|
28
|
+
* 중복 표면. 기록 중단(no-op shim — API 만 유지), v0.6.0 에서 모듈 삭제 예정.
|
|
29
|
+
*/
|
|
30
|
+
export function recordToolCall(_runtime = 'claude') {
|
|
31
|
+
// no-op — 사용량 추적은 native /usage 로 이관됨.
|
|
34
32
|
}
|
|
35
33
|
/**
|
|
36
34
|
* sliding window count. fail-open: 파일 미존재/parse 실패는 0 반환.
|
|
@@ -129,7 +129,7 @@ export function bootstrapV1Session() {
|
|
|
129
129
|
mismatchResult = detectMismatch(allSignals);
|
|
130
130
|
// mismatch 감지 시 재추천 생성
|
|
131
131
|
if (mismatchResult.quality_mismatch || mismatchResult.autonomy_mismatch) {
|
|
132
|
-
session.warnings.push(`Pack mismatch 감지: quality=${mismatchResult.quality_score}, autonomy=${mismatchResult.autonomy_score}. forgen forge --reset soft 로 재설정하거나 forgen onboarding 으로 재추천을 받으세요.`);
|
|
132
|
+
session.warnings.push(`Pack mismatch 감지: quality=${mismatchResult.quality_score}, autonomy=${mismatchResult.autonomy_score}. forgen forge --reset soft 로 재설정하거나 forgen forge --onboarding 으로 재추천을 받으세요.`);
|
|
133
133
|
const rec = createRecommendation({
|
|
134
134
|
source: 'mismatch_recommendation',
|
|
135
135
|
quality_pack: mismatchResult.quality_mismatch
|
package/dist/core/watch-cli.js
CHANGED
|
@@ -136,7 +136,7 @@ export async function handleWatch() {
|
|
|
136
136
|
format: formatMatchEval,
|
|
137
137
|
},
|
|
138
138
|
];
|
|
139
|
-
console.log(`\n ${C.cyan}forgen
|
|
139
|
+
console.log(`\n ${C.cyan}forgen status --live${C.reset} — real-time event stream`);
|
|
140
140
|
console.log(` ${C.dim}Watching: hook-timing, violations, match-eval-log${C.reset}`);
|
|
141
141
|
console.log(` ${C.dim}Press Ctrl+C to stop${C.reset}\n`);
|
|
142
142
|
// Show recent events (last 10 per source)
|
|
@@ -21,6 +21,7 @@ import { getNewCommits, getCommitMessages, getGitDiff, getDiffStats } from './ex
|
|
|
21
21
|
import { gate0, evaluateExtractedSolution } from './extraction-gates.js';
|
|
22
22
|
import { extractFromDiff } from './extraction-diff.js';
|
|
23
23
|
import { extractFromSessionContext } from './extraction-session.js';
|
|
24
|
+
import { stripPrivate, isFullyPrivate } from './private-filter.js';
|
|
24
25
|
import { loadLastExtraction, saveLastExtraction, saveExtractedSolution, updateReExtractedCounter, emitCompoundExtractActedOn, } from './extraction-persistence.js';
|
|
25
26
|
const log = createLogger('compound-extractor');
|
|
26
27
|
const MAX_EXTRACTIONS_PER_DAY = 5;
|
|
@@ -172,6 +173,15 @@ export function processExtractionResults(rawJson, sessionId) {
|
|
|
172
173
|
return { saved, skipped };
|
|
173
174
|
}
|
|
174
175
|
for (const sol of solutions.slice(0, 3)) {
|
|
176
|
+
// W2-5 (private 태그): <private> 범위를 솔루션 content/context 에서 제거 후 저장.
|
|
177
|
+
// 통째로 private 이면 저장 skip (학습 코퍼스에 남기지 않음).
|
|
178
|
+
if (isFullyPrivate(sol.content) || isFullyPrivate(sol.context ?? '')) {
|
|
179
|
+
skipped.push(`${sol.name}: <private> (제외)`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
sol.content = stripPrivate(sol.content).cleaned;
|
|
183
|
+
if (sol.context)
|
|
184
|
+
sol.context = stripPrivate(sol.context).cleaned;
|
|
175
185
|
const evaluation = evaluateExtractedSolution(sol);
|
|
176
186
|
if (evaluation.action === 'skip' || evaluation.action === 'duplicate') {
|
|
177
187
|
skipped.push(evaluation.message ?? `${sol.name}: skipped`);
|
|
@@ -190,11 +190,17 @@ export async function handleCompound(args) {
|
|
|
190
190
|
forgen compound --lifecycle Run promotion/demotion/circuit-breaker check
|
|
191
191
|
forgen compound --verify <name> Manually promote solution to verified
|
|
192
192
|
|
|
193
|
-
Export/Import:
|
|
193
|
+
Export/Import (whole store, tar.gz):
|
|
194
194
|
forgen compound export [--output path]
|
|
195
195
|
Export knowledge to tar.gz archive
|
|
196
196
|
forgen compound import <path> Import knowledge from archive (skip existing)
|
|
197
197
|
|
|
198
|
+
Export/Import (per-pattern, JSON — with confidence + provenance):
|
|
199
|
+
forgen compound export <name...> [--out file]
|
|
200
|
+
Export named solutions/rules as a portable bundle
|
|
201
|
+
forgen compound import <bundle.json> [--dry-run]
|
|
202
|
+
Import a bundle (probation trust, never overwrites)
|
|
203
|
+
|
|
198
204
|
Auto-extraction:
|
|
199
205
|
forgen compound --pause-auto Pause auto-extraction
|
|
200
206
|
forgen compound --resume-auto Resume auto-extraction
|
|
@@ -204,16 +210,50 @@ export async function handleCompound(args) {
|
|
|
204
210
|
`);
|
|
205
211
|
return;
|
|
206
212
|
}
|
|
213
|
+
// --- manual add (defense-in-depth, 2026-08-20) ---
|
|
214
|
+
// manual add(--solution/--rule/--convention/--pattern)를 아래 서브커맨드 dispatch 보다
|
|
215
|
+
// 먼저 처리한다. dispatch 는 args.includes('remove'|'clean-stale'|...) 로 전체 인자를
|
|
216
|
+
// 스캔하므로, add 의 위치인자(title/content)에 그런 토큰이 섞이면 삭제·정리 분기를
|
|
217
|
+
// 탈취할 수 있다(argument confusion — auto-compound 러너가 untrusted 모델 출력을 넘기던
|
|
218
|
+
// 경로에서 실증). type flag 는 add 전용이라 우선 처리해도 legit 동작 불변이며, 이렇게
|
|
219
|
+
// 하면 title/content 가 dispatch 스캔에 아예 닿지 않는다. (러너측 대시-선두 가드에 더한 2중 방어)
|
|
220
|
+
if (['--solution', '--rule', '--convention', '--pattern'].some(f => args.includes(f))) {
|
|
221
|
+
await handleManualAdd(args, cwd, scope);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
207
224
|
// --- export command ---
|
|
225
|
+
// 통짜 tar.gz export(플래그만, positional 없음) vs 이름 지정 패턴 export를
|
|
226
|
+
// positional 인자 유무로 구분한다. `compound export foo` = 패턴 export,
|
|
227
|
+
// `compound export --output x` = 기존 whole-store export.
|
|
208
228
|
if (args[0] === 'export') {
|
|
209
|
-
const
|
|
210
|
-
|
|
229
|
+
const rest = args.slice(1);
|
|
230
|
+
const hasPatternNames = rest.length > 0 && !rest[0].startsWith('--');
|
|
231
|
+
if (hasPatternNames) {
|
|
232
|
+
const { handleShareExport } = await import('./compound-share.js');
|
|
233
|
+
await handleShareExport(rest);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
const { handleExport } = await import('./compound-export.js');
|
|
237
|
+
await handleExport(rest);
|
|
238
|
+
}
|
|
211
239
|
return;
|
|
212
240
|
}
|
|
213
241
|
// --- import command ---
|
|
242
|
+
// 대상 파일이 JSON 패턴 번들인지 tar.gz 아카이브인지 확장자/매직바이트로
|
|
243
|
+
// sniff해서 라우팅한다 (looksLikeShareBundle). 기존 tar.gz import 동작은
|
|
244
|
+
// 그대로 유지.
|
|
214
245
|
if (args[0] === 'import') {
|
|
215
|
-
const
|
|
216
|
-
|
|
246
|
+
const rest = args.slice(1);
|
|
247
|
+
const filePath = rest.find(a => !a.startsWith('--'));
|
|
248
|
+
const { looksLikeShareBundle } = await import('./compound-share.js');
|
|
249
|
+
if (filePath && looksLikeShareBundle(path.resolve(filePath))) {
|
|
250
|
+
const { handleShareImport } = await import('./compound-share.js');
|
|
251
|
+
await handleShareImport(rest);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
const { handleImport } = await import('./compound-export.js');
|
|
255
|
+
await handleImport(rest);
|
|
256
|
+
}
|
|
217
257
|
return;
|
|
218
258
|
}
|
|
219
259
|
// --pause-auto / --resume-auto
|
|
@@ -399,17 +439,25 @@ export async function handleCompound(args) {
|
|
|
399
439
|
'--lifecycle', '--verify', '--save', '--interactive',
|
|
400
440
|
'list', 'inspect', 'remove', 'rollback', 'retag', 'lifecycle',
|
|
401
441
|
'--list', '--inspect', '--remove', '--rollback', '--retag', '--since', 'interactive',
|
|
402
|
-
'export', 'import', '--output',
|
|
442
|
+
'export', 'import', '--output', '--out', '--dry-run',
|
|
403
443
|
];
|
|
404
444
|
const hasTypeFlag = knownFlags.some(f => args.includes(f));
|
|
405
445
|
if (!hasTypeFlag) {
|
|
406
446
|
console.log(' Unknown compound arguments. Run `forgen compound --help` for usage.\n');
|
|
407
447
|
return;
|
|
408
448
|
}
|
|
449
|
+
await handleManualAdd(args, cwd, scope);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* 수동 인사이트 추가 (--solution/--rule/--convention/--pattern).
|
|
453
|
+
* handleCompound 에서 두 경로로 호출된다:
|
|
454
|
+
* (1) hoisted early-dispatch — type flag 가 있으면 서브커맨드 스캔 전에 즉시(arg-confusion 방어).
|
|
455
|
+
* (2) fallthrough — type flag 없이 다른 known flag(예: --to)만 있는 legacy 경로(동작 보존).
|
|
456
|
+
*/
|
|
457
|
+
async function handleManualAdd(args, cwd, scope) {
|
|
409
458
|
console.log('\n Compound Loop — Accumulating insights\n');
|
|
410
459
|
console.log(` Scope: ${scope.summary}`);
|
|
411
460
|
console.log();
|
|
412
|
-
// 수동 인사이트 추가
|
|
413
461
|
const type = args.includes('--solution') ? 'solution'
|
|
414
462
|
: args.includes('--rule') ? 'rule'
|
|
415
463
|
: args.includes('--convention') ? 'convention'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Compound Pattern Share (패턴별 export/import)
|
|
3
|
+
*
|
|
4
|
+
* `compound-export.ts`의 `export`/`import`는 `~/.forgen/me/` 전체를 tar.gz로
|
|
5
|
+
* 통짜 백업/이관한다. 이 모듈은 그와 달리 **이름 지정된 패턴 단위**로 신뢰도
|
|
6
|
+
* (confidence/status/evidence)와 provenance를 함께 담은 JSON 번들을 만들고,
|
|
7
|
+
* 받는 쪽에서 안전하게 병합한다 (ECC `/instinct-import/export` 대응, OSS gap #1).
|
|
8
|
+
*
|
|
9
|
+
* 핵심 설계:
|
|
10
|
+
* - 번들은 스키마 버전 고정 JSON. 최상위/패턴 필드 모두 화이트리스트 검증 —
|
|
11
|
+
* 예상 못한 필드가 있으면 통째로 reject (실행 가능한 콘텐츠가 섞여 들어올
|
|
12
|
+
* 여지 자체를 차단).
|
|
13
|
+
* - 패턴마다 contentHash(sha256)를 동봉 — import 시 재계산해 일치하지 않으면
|
|
14
|
+
* reject (변조/손상 탐지).
|
|
15
|
+
* - 이름 충돌 시: 로컬 콘텐츠 해시가 같으면 "동일 패턴 재발견"으로 간주해
|
|
16
|
+
* `reExtracted` 카운터만 증가(기존 신뢰도는 건드리지 않음 — 이미 solution-writer.ts
|
|
17
|
+
* 의 dual-path 금지 불변식과 일치). 다르면 절대 덮어쓰지 않고 suffix된
|
|
18
|
+
* 이름으로 새로 생성.
|
|
19
|
+
* - 신규 생성되는 패턴은 항상 probation: status='experiment', confidence는
|
|
20
|
+
* 신규 솔루션 표준 베이스라인(statusConfidence('experiment')=0.3, 참고:
|
|
21
|
+
* extraction-persistence.ts saveExtractedSolution)을 상한으로 export 시점
|
|
22
|
+
* confidence의 절반만 반영. evidence는 전부 0으로 리셋 — exporter의 로컬
|
|
23
|
+
* 사용 이력을 이 머신이 검증 없이 물려받지 않는다. 이후 승급은 기존
|
|
24
|
+
* compound-lifecycle.ts의 단일 경로(runLifecycleCheck)로만 진행된다.
|
|
25
|
+
*/
|
|
26
|
+
import { type SolutionFrontmatter } from './solution-format.js';
|
|
27
|
+
export declare const SHARE_BUNDLE_SCHEMA_VERSION: 1;
|
|
28
|
+
export interface ShareBundlePatternV1 {
|
|
29
|
+
name: string;
|
|
30
|
+
category: 'solution' | 'rule';
|
|
31
|
+
frontmatter: SolutionFrontmatter;
|
|
32
|
+
context: string;
|
|
33
|
+
content: string;
|
|
34
|
+
/** sha256(type + sorted tags/identifiers + context + content) — confidence/evidence/timestamp 제외한 콘텐츠 지문. */
|
|
35
|
+
contentHash: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ShareBundleV1 {
|
|
38
|
+
schemaVersion: 1;
|
|
39
|
+
exportedAt: string;
|
|
40
|
+
/** sha256(hostname:username) 앞 16자 — 원본 식별용, PII 비가역. */
|
|
41
|
+
originHash: string;
|
|
42
|
+
patterns: ShareBundlePatternV1[];
|
|
43
|
+
}
|
|
44
|
+
export interface BuildBundleResult {
|
|
45
|
+
bundle: ShareBundleV1;
|
|
46
|
+
notFound: string[];
|
|
47
|
+
rejectedSecrets: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 이름 목록으로 패턴 번들 생성.
|
|
51
|
+
*
|
|
52
|
+
* 시크릿 감지된 패턴은 조용히 스킵하지 않고 `rejectedSecrets`로 보고하되,
|
|
53
|
+
* 번들 자체는 나머지 clean한 패턴으로 계속 진행한다 (부분 실패 허용).
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildShareBundle(names: string[]): BuildBundleResult;
|
|
56
|
+
export interface ShareBundleValidation {
|
|
57
|
+
ok: boolean;
|
|
58
|
+
bundle: ShareBundleV1 | null;
|
|
59
|
+
errors: string[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 번들 검증: 크기 캡 → 최상위 필드 화이트리스트 → 패턴별 필드 화이트리스트 →
|
|
63
|
+
* frontmatter 정합성 → contentHash 재계산 일치. 하나라도 실패하면 번들 전체를
|
|
64
|
+
* reject한다 (부분 신뢰 없음 — 손상/변조된 번들은 통째로 버린다).
|
|
65
|
+
*/
|
|
66
|
+
export declare function validateShareBundle(raw: unknown, rawSize: number): ShareBundleValidation;
|
|
67
|
+
export interface ShareImportAction {
|
|
68
|
+
sourceName: string;
|
|
69
|
+
category: 'solution' | 'rule';
|
|
70
|
+
action: 'merge-reextract' | 'create' | 'create-suffixed';
|
|
71
|
+
targetName: string;
|
|
72
|
+
detail: string;
|
|
73
|
+
}
|
|
74
|
+
export interface ShareImportSummary {
|
|
75
|
+
dryRun: boolean;
|
|
76
|
+
actions: ShareImportAction[];
|
|
77
|
+
}
|
|
78
|
+
/** dry-run과 실제 실행이 공유하는 계획 수립 — 파일시스템에 아무것도 쓰지 않는다. */
|
|
79
|
+
export declare function planShareImport(bundle: ShareBundleV1): ShareImportAction[];
|
|
80
|
+
export declare function executeShareImport(bundle: ShareBundleV1, opts?: {
|
|
81
|
+
dryRun?: boolean;
|
|
82
|
+
}): ShareImportSummary;
|
|
83
|
+
export declare function looksLikeShareBundle(filePath: string): boolean;
|
|
84
|
+
export declare function handleShareExport(args: string[]): Promise<void>;
|
|
85
|
+
export declare function handleShareImport(args: string[]): Promise<void>;
|