@wooojin/forgen 0.4.10 → 0.4.11
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 +46 -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/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/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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen workflows — dynamic-workflow 템플릿 설치/조회 (ADR-009 §3).
|
|
3
|
+
*
|
|
4
|
+
* dynamic workflows 는 플러그인 매니페스트로 번들할 수 없고(research preview,
|
|
5
|
+
* `.claude/workflows/` 에만 저장됨), forgen 은 철학을 인코딩한 canonical 템플릿을
|
|
6
|
+
* `assets/claude/workflows/*.js` 로 동봉한다. 이 명령이 그것을 사용자의
|
|
7
|
+
* `.claude/workflows/` 로 복사한다 → `/<name>` 으로 실행 가능.
|
|
8
|
+
*
|
|
9
|
+
* forgen-verify 에이전트(verify 스테이지용)는 플러그인 `agents` 키로 자동 배포되므로
|
|
10
|
+
* 별도 설치가 필요 없다.
|
|
11
|
+
*
|
|
12
|
+
* forgen workflows install ~/.claude/workflows/ (개인, 모든 프로젝트)
|
|
13
|
+
* forgen workflows install --project <cwd>/.claude/workflows/ (저장소 공유)
|
|
14
|
+
* forgen workflows list 동봉 템플릿 + 설치 여부
|
|
15
|
+
*/
|
|
16
|
+
/** forgen pkg root 의 assets/claude/workflows 디렉토리. invoke-agent 와 동일 walk-up. */
|
|
17
|
+
export declare function findTemplatesDir(): string;
|
|
18
|
+
/** 템플릿 파일명(.js) 목록. 실패 시 []. */
|
|
19
|
+
export declare function listTemplates(templatesDir: string): string[];
|
|
20
|
+
export interface InstallResult {
|
|
21
|
+
installed: string[];
|
|
22
|
+
targetDir: string;
|
|
23
|
+
}
|
|
24
|
+
/** 템플릿을 targetDir 로 복사 (DI — 테스트 주입용). 기존 파일은 덮어쓴다. */
|
|
25
|
+
export declare function installWorkflows(templatesDir: string, targetDir: string): InstallResult;
|
|
26
|
+
export declare function handleWorkflows(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forgen workflows — dynamic-workflow 템플릿 설치/조회 (ADR-009 §3).
|
|
3
|
+
*
|
|
4
|
+
* dynamic workflows 는 플러그인 매니페스트로 번들할 수 없고(research preview,
|
|
5
|
+
* `.claude/workflows/` 에만 저장됨), forgen 은 철학을 인코딩한 canonical 템플릿을
|
|
6
|
+
* `assets/claude/workflows/*.js` 로 동봉한다. 이 명령이 그것을 사용자의
|
|
7
|
+
* `.claude/workflows/` 로 복사한다 → `/<name>` 으로 실행 가능.
|
|
8
|
+
*
|
|
9
|
+
* forgen-verify 에이전트(verify 스테이지용)는 플러그인 `agents` 키로 자동 배포되므로
|
|
10
|
+
* 별도 설치가 필요 없다.
|
|
11
|
+
*
|
|
12
|
+
* forgen workflows install ~/.claude/workflows/ (개인, 모든 프로젝트)
|
|
13
|
+
* forgen workflows install --project <cwd>/.claude/workflows/ (저장소 공유)
|
|
14
|
+
* forgen workflows list 동봉 템플릿 + 설치 여부
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as os from 'node:os';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
const isTTY = process.stdout.isTTY;
|
|
21
|
+
const C = {
|
|
22
|
+
reset: isTTY ? '\x1b[0m' : '',
|
|
23
|
+
bold: isTTY ? '\x1b[1m' : '',
|
|
24
|
+
dim: isTTY ? '\x1b[2m' : '',
|
|
25
|
+
green: isTTY ? '\x1b[32m' : '',
|
|
26
|
+
cyan: isTTY ? '\x1b[36m' : '',
|
|
27
|
+
};
|
|
28
|
+
/** forgen pkg root 의 assets/claude/workflows 디렉토리. invoke-agent 와 동일 walk-up. */
|
|
29
|
+
export function findTemplatesDir() {
|
|
30
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
32
|
+
const pkgJson = path.join(dir, 'package.json');
|
|
33
|
+
if (fs.existsSync(pkgJson)) {
|
|
34
|
+
try {
|
|
35
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf-8'));
|
|
36
|
+
if (pkg.name === '@wooojin/forgen') {
|
|
37
|
+
const candidate = path.join(dir, 'assets', 'claude', 'workflows');
|
|
38
|
+
if (fs.existsSync(candidate))
|
|
39
|
+
return candidate;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { /* fallthrough */ }
|
|
43
|
+
}
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
if (parent === dir)
|
|
46
|
+
break;
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
throw new Error('forgen workflows: pkg root + assets/claude/workflows/ not found');
|
|
50
|
+
}
|
|
51
|
+
/** 템플릿 파일명(.js) 목록. 실패 시 []. */
|
|
52
|
+
export function listTemplates(templatesDir) {
|
|
53
|
+
try {
|
|
54
|
+
return fs.readdirSync(templatesDir).filter((f) => f.endsWith('.js')).sort();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** 템플릿을 targetDir 로 복사 (DI — 테스트 주입용). 기존 파일은 덮어쓴다. */
|
|
61
|
+
export function installWorkflows(templatesDir, targetDir) {
|
|
62
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
63
|
+
const installed = [];
|
|
64
|
+
for (const file of listTemplates(templatesDir)) {
|
|
65
|
+
fs.copyFileSync(path.join(templatesDir, file), path.join(targetDir, file));
|
|
66
|
+
installed.push(file);
|
|
67
|
+
}
|
|
68
|
+
return { installed, targetDir };
|
|
69
|
+
}
|
|
70
|
+
function resolveTargetBase(projectScope) {
|
|
71
|
+
return projectScope ? process.cwd() : os.homedir();
|
|
72
|
+
}
|
|
73
|
+
export async function handleWorkflows(args) {
|
|
74
|
+
const sub = args[0] ?? 'list';
|
|
75
|
+
const projectScope = args.includes('--project');
|
|
76
|
+
let templatesDir;
|
|
77
|
+
try {
|
|
78
|
+
templatesDir = findTemplatesDir();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
console.log(`\n ${C.dim}워크플로우 템플릿을 찾을 수 없습니다 (forgen 설치 확인).${C.reset}\n`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (sub === 'install') {
|
|
86
|
+
const targetDir = path.join(resolveTargetBase(projectScope), '.claude', 'workflows');
|
|
87
|
+
const { installed } = installWorkflows(templatesDir, targetDir);
|
|
88
|
+
if (installed.length === 0) {
|
|
89
|
+
console.log(`\n ${C.dim}동봉된 템플릿이 없습니다.${C.reset}\n`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
console.log(`\n ${C.green}✓${C.reset} ${installed.length} workflow templates → ${C.cyan}${targetDir}${C.reset}`);
|
|
93
|
+
for (const f of installed)
|
|
94
|
+
console.log(` /${f.replace(/\.js$/, '')}`);
|
|
95
|
+
console.log(`\n ${C.dim}Claude Code 에서 /<name> 으로 실행. verify 스테이지는 forgen-verify 에이전트 사용.${C.reset}\n`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (sub === 'list') {
|
|
99
|
+
const templates = listTemplates(templatesDir);
|
|
100
|
+
const homeDir = path.join(os.homedir(), '.claude', 'workflows');
|
|
101
|
+
const projDir = path.join(process.cwd(), '.claude', 'workflows');
|
|
102
|
+
console.log(`\n ${C.bold}forgen workflow templates${C.reset}\n`);
|
|
103
|
+
for (const f of templates) {
|
|
104
|
+
const name = f.replace(/\.js$/, '');
|
|
105
|
+
const inHome = fs.existsSync(path.join(homeDir, f));
|
|
106
|
+
const inProj = fs.existsSync(path.join(projDir, f));
|
|
107
|
+
const where = [inHome && 'user', inProj && 'project'].filter(Boolean).join('+') || 'not installed';
|
|
108
|
+
console.log(` ${inHome || inProj ? C.green + '✓' + C.reset : C.dim + '·' + C.reset} /${name} ${C.dim}(${where})${C.reset}`);
|
|
109
|
+
}
|
|
110
|
+
console.log(`\n ${C.dim}install: forgen workflows install [--project]${C.reset}\n`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log(`
|
|
114
|
+
${C.bold}forgen workflows${C.reset} — dynamic-workflow 템플릿 (ADR-009 §3)
|
|
115
|
+
|
|
116
|
+
Usage:
|
|
117
|
+
forgen workflows install [--project] 템플릿을 .claude/workflows/ 로 복사
|
|
118
|
+
forgen workflows list 동봉 템플릿 + 설치 여부
|
|
119
|
+
`);
|
|
120
|
+
}
|
|
@@ -50,6 +50,18 @@ export declare function exportKnowledge(outputPath?: string): ExportResult;
|
|
|
50
50
|
* added.
|
|
51
51
|
*/
|
|
52
52
|
export declare function importKnowledge(archivePath: string): ImportResult;
|
|
53
|
+
/**
|
|
54
|
+
* Selective export — filter to specific categories.
|
|
55
|
+
* `--only solutions,rules` exports only those two directories.
|
|
56
|
+
*/
|
|
57
|
+
export declare function exportKnowledgeSelective(categories: string[], outputPath?: string): ExportResult;
|
|
58
|
+
/**
|
|
59
|
+
* Import with merge mode — updates existing files instead of skipping them.
|
|
60
|
+
* Newer files in the archive overwrite older local files.
|
|
61
|
+
*/
|
|
62
|
+
export declare function importKnowledgeMerge(archivePath: string): ImportResult & {
|
|
63
|
+
merged: number;
|
|
64
|
+
};
|
|
53
65
|
/** CLI handler: forgen compound export */
|
|
54
66
|
export declare function handleExport(args: string[]): Promise<void>;
|
|
55
67
|
/** CLI handler: forgen compound import */
|
|
@@ -152,14 +152,116 @@ export function importKnowledge(archivePath) {
|
|
|
152
152
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Selective export — filter to specific categories.
|
|
157
|
+
* `--only solutions,rules` exports only those two directories.
|
|
158
|
+
*/
|
|
159
|
+
export function exportKnowledgeSelective(categories, outputPath) {
|
|
160
|
+
const date = new Date().toISOString().split('T')[0];
|
|
161
|
+
const resolved = outputPath ?? path.join(process.cwd(), `forgen-knowledge-${date}.tar.gz`);
|
|
162
|
+
const validCategories = categories.filter(c => KNOWLEDGE_DIRS.includes(c));
|
|
163
|
+
if (validCategories.length === 0) {
|
|
164
|
+
throw new Error(`No valid categories. Choose from: ${KNOWLEDGE_DIRS.join(', ')}`);
|
|
165
|
+
}
|
|
166
|
+
const counts = {};
|
|
167
|
+
const existingDirs = [];
|
|
168
|
+
for (const name of validCategories) {
|
|
169
|
+
const dir = path.join(ME_DIR, name);
|
|
170
|
+
const count = countFiles(dir);
|
|
171
|
+
counts[name] = count;
|
|
172
|
+
if (fs.existsSync(dir)) {
|
|
173
|
+
existingDirs.push(name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const totalFiles = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
177
|
+
if (existingDirs.length === 0) {
|
|
178
|
+
throw new Error('No knowledge directories found to export.');
|
|
179
|
+
}
|
|
180
|
+
const outDir = path.dirname(resolved);
|
|
181
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
182
|
+
execFileSync('tar', ['czf', resolved, ...existingDirs], {
|
|
183
|
+
cwd: ME_DIR,
|
|
184
|
+
timeout: 30000,
|
|
185
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
186
|
+
});
|
|
187
|
+
return { outputPath: resolved, counts, totalFiles };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Import with merge mode — updates existing files instead of skipping them.
|
|
191
|
+
* Newer files in the archive overwrite older local files.
|
|
192
|
+
*/
|
|
193
|
+
export function importKnowledgeMerge(archivePath) {
|
|
194
|
+
if (!fs.existsSync(archivePath)) {
|
|
195
|
+
throw new Error(`Archive not found: ${archivePath}`);
|
|
196
|
+
}
|
|
197
|
+
const listOutput = execFileSync('tar', ['tzf', archivePath], {
|
|
198
|
+
timeout: 30000,
|
|
199
|
+
encoding: 'utf-8',
|
|
200
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
201
|
+
});
|
|
202
|
+
const archiveFiles = listOutput
|
|
203
|
+
.split('\n')
|
|
204
|
+
.map(f => f.trim())
|
|
205
|
+
.filter(f => f && !f.endsWith('/'));
|
|
206
|
+
const tmpDir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'forgen-merge-'));
|
|
207
|
+
try {
|
|
208
|
+
execFileSync('tar', ['xzf', archivePath, '-C', tmpDir], {
|
|
209
|
+
timeout: 30000,
|
|
210
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
211
|
+
});
|
|
212
|
+
const result = { imported: 0, skipped: 0, merged: 0, details: [] };
|
|
213
|
+
const meDirCanon = path.resolve(ME_DIR) + path.sep;
|
|
214
|
+
const tmpDirCanon = path.resolve(tmpDir) + path.sep;
|
|
215
|
+
for (const relFile of archiveFiles) {
|
|
216
|
+
const srcPath = path.resolve(path.join(tmpDir, relFile));
|
|
217
|
+
const destPath = path.resolve(path.join(ME_DIR, relFile));
|
|
218
|
+
if (!isPathInside(meDirCanon, destPath) || !isPathInside(tmpDirCanon, srcPath)) {
|
|
219
|
+
result.skipped++;
|
|
220
|
+
result.details.push({ file: relFile, action: 'skipped' });
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (fs.existsSync(destPath)) {
|
|
224
|
+
const srcMtime = fs.statSync(srcPath).mtimeMs;
|
|
225
|
+
const destMtime = fs.statSync(destPath).mtimeMs;
|
|
226
|
+
if (srcMtime > destMtime) {
|
|
227
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
228
|
+
fs.copyFileSync(srcPath, destPath);
|
|
229
|
+
result.merged++;
|
|
230
|
+
result.details.push({ file: relFile, action: 'imported' });
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
result.skipped++;
|
|
234
|
+
result.details.push({ file: relFile, action: 'skipped' });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
239
|
+
fs.copyFileSync(srcPath, destPath);
|
|
240
|
+
result.imported++;
|
|
241
|
+
result.details.push({ file: relFile, action: 'imported' });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
155
250
|
/** CLI handler: forgen compound export */
|
|
156
251
|
export async function handleExport(args) {
|
|
157
252
|
const outputIdx = args.indexOf('--output');
|
|
158
253
|
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : undefined;
|
|
254
|
+
const onlyIdx = args.indexOf('--only');
|
|
255
|
+
const onlyCategories = onlyIdx !== -1 ? (args[onlyIdx + 1] ?? '').split(',').filter(Boolean) : [];
|
|
159
256
|
try {
|
|
160
|
-
const result =
|
|
257
|
+
const result = onlyCategories.length > 0
|
|
258
|
+
? exportKnowledgeSelective(onlyCategories, outputPath)
|
|
259
|
+
: exportKnowledge(outputPath);
|
|
161
260
|
console.log('\n Compound Knowledge Export\n');
|
|
162
261
|
console.log(` Output: ${result.outputPath}`);
|
|
262
|
+
if (onlyCategories.length > 0) {
|
|
263
|
+
console.log(` Filter: ${onlyCategories.join(', ')}`);
|
|
264
|
+
}
|
|
163
265
|
console.log();
|
|
164
266
|
for (const [category, count] of Object.entries(result.counts)) {
|
|
165
267
|
console.log(` ${category}: ${count} files`);
|
|
@@ -173,23 +275,43 @@ export async function handleExport(args) {
|
|
|
173
275
|
}
|
|
174
276
|
/** CLI handler: forgen compound import */
|
|
175
277
|
export async function handleImport(args) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
278
|
+
const filteredArgs = args.filter(a => !a.startsWith('--'));
|
|
279
|
+
const archivePath = filteredArgs[0];
|
|
280
|
+
const mergeMode = args.includes('--merge');
|
|
281
|
+
if (!archivePath) {
|
|
282
|
+
console.log(' Usage: forgen compound import <path-to-archive> [--merge]\n');
|
|
283
|
+
console.log(' --merge Update existing files if archive version is newer');
|
|
179
284
|
return;
|
|
180
285
|
}
|
|
181
286
|
try {
|
|
182
287
|
const resolved = path.resolve(archivePath);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
console.log();
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
288
|
+
if (mergeMode) {
|
|
289
|
+
const result = importKnowledgeMerge(resolved);
|
|
290
|
+
console.log('\n Compound Knowledge Import (merge mode)\n');
|
|
291
|
+
console.log(` Archive: ${resolved}`);
|
|
292
|
+
console.log(` New: ${result.imported} files`);
|
|
293
|
+
console.log(` Merged: ${result.merged} files (newer version overwrote local)`);
|
|
294
|
+
console.log(` Skipped: ${result.skipped} files (local is same or newer)`);
|
|
295
|
+
if (result.details.length > 0 && result.details.length <= 20) {
|
|
296
|
+
console.log();
|
|
297
|
+
for (const d of result.details) {
|
|
298
|
+
const icon = d.action === 'imported' ? '+' : '-';
|
|
299
|
+
console.log(` ${icon} ${d.file}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
const result = importKnowledge(resolved);
|
|
305
|
+
console.log('\n Compound Knowledge Import\n');
|
|
306
|
+
console.log(` Archive: ${resolved}`);
|
|
307
|
+
console.log(` Imported: ${result.imported} new files`);
|
|
308
|
+
console.log(` Skipped: ${result.skipped} existing files`);
|
|
309
|
+
if (result.details.length > 0 && result.details.length <= 20) {
|
|
310
|
+
console.log();
|
|
311
|
+
for (const d of result.details) {
|
|
312
|
+
const icon = d.action === 'imported' ? '+' : '-';
|
|
313
|
+
console.log(` ${icon} ${d.file}`);
|
|
314
|
+
}
|
|
193
315
|
}
|
|
194
316
|
}
|
|
195
317
|
console.log();
|
|
@@ -1,54 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Forgen — Compound Knowledge Extractor
|
|
2
|
+
* Forgen — Compound Knowledge Extractor (facade)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Orchestrates extraction pipeline: git analysis → quality gates →
|
|
5
|
+
* pattern extraction → persistence. Re-exports from decomposed modules.
|
|
6
6
|
*
|
|
7
|
-
* Module
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Lines 275-395: extractFromSessionContext — prompt/write history analysis (actions, hotspots, tech)
|
|
14
|
-
* - Lines 396-475: saveExtractedSolution, updateReExtractedCounter — solution persistence
|
|
15
|
-
* - Lines 477-555: runExtraction — main entry point orchestrating gates + extraction + state
|
|
16
|
-
* - Lines 557-634: processExtractionResults, isExtractionPaused, pauseExtraction, resumeExtraction
|
|
7
|
+
* Module layout (post-decomposition):
|
|
8
|
+
* extraction-git.ts — getNewCommits, getCommitMessages, getGitDiff, getDiffStats
|
|
9
|
+
* extraction-gates.ts — gate0-gate3, gateTrivial, evaluateExtractedSolution, ExtractedSolution
|
|
10
|
+
* extraction-diff.ts — extractFromDiff, findCommonPrefix
|
|
11
|
+
* extraction-session.ts — extractFromSessionContext, loadClaudeProjectSessionContext
|
|
12
|
+
* extraction-persistence.ts — saveExtractedSolution, updateReExtractedCounter, LastExtraction
|
|
17
13
|
*/
|
|
18
|
-
import type {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type: SolutionType;
|
|
22
|
-
tags: string[];
|
|
23
|
-
identifiers: string[];
|
|
24
|
-
context: string;
|
|
25
|
-
content: string;
|
|
26
|
-
}
|
|
27
|
-
interface WriteContextEntry {
|
|
28
|
-
filePath: string;
|
|
29
|
-
contentSnippet: string;
|
|
30
|
-
fileExtension: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Load Claude session prompts + writes correlated to `cwd`.
|
|
34
|
-
*
|
|
35
|
-
* Exported primarily for test assertions (the `claude-session-context`
|
|
36
|
-
* tests need to verify that correlation picks the right project's
|
|
37
|
-
* sessions and ignores unrelated ones). Before C4 the tests could
|
|
38
|
-
* observe this indirectly via the now-removed `recurring-task-pattern`
|
|
39
|
-
* extractor; now they check this loader directly. Not intended for
|
|
40
|
-
* production callers outside the extractor pipeline.
|
|
41
|
-
*/
|
|
42
|
-
export declare function loadClaudeProjectSessionContext(cwd: string, lastExtractedAt: string): {
|
|
43
|
-
prompts: string[];
|
|
44
|
-
writes: WriteContextEntry[];
|
|
45
|
-
};
|
|
14
|
+
import type { ExtractedSolution } from './extraction-gates.js';
|
|
15
|
+
export { loadClaudeProjectSessionContext } from './extraction-session.js';
|
|
16
|
+
export type { ExtractedSolution } from './extraction-gates.js';
|
|
46
17
|
export declare function previewExtraction(cwd: string): Promise<{
|
|
47
18
|
preview: ExtractedSolution[];
|
|
48
19
|
skipped: string[];
|
|
49
20
|
reason?: string;
|
|
50
21
|
}>;
|
|
51
|
-
/** Main extraction function — called from SessionStart or CLI */
|
|
52
22
|
export declare function runExtraction(cwd: string, sessionId: string): Promise<{
|
|
53
23
|
extracted: string[];
|
|
54
24
|
skipped: string[];
|
|
@@ -65,4 +35,3 @@ export declare function isExtractionPaused(): boolean;
|
|
|
65
35
|
export declare function pauseExtraction(): void;
|
|
66
36
|
/** Resume auto-extraction */
|
|
67
37
|
export declare function resumeExtraction(): void;
|
|
68
|
-
export {};
|