@wooojin/forgen 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +20 -0
- package/CHANGELOG.md +353 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +21 -0
- package/README.ja.md +469 -0
- package/README.ko.md +469 -0
- package/README.md +483 -0
- package/README.zh.md +469 -0
- package/agents/analyst.md +98 -0
- package/agents/architect.md +62 -0
- package/agents/code-reviewer.md +120 -0
- package/agents/code-simplifier.md +197 -0
- package/agents/critic.md +70 -0
- package/agents/debugger.md +117 -0
- package/agents/designer.md +131 -0
- package/agents/executor.md +54 -0
- package/agents/explore.md +145 -0
- package/agents/git-master.md +212 -0
- package/agents/performance-reviewer.md +172 -0
- package/agents/planner.md +29 -0
- package/agents/qa-tester.md +158 -0
- package/agents/refactoring-expert.md +168 -0
- package/agents/scientist.md +144 -0
- package/agents/security-reviewer.md +137 -0
- package/agents/test-engineer.md +153 -0
- package/agents/verifier.md +133 -0
- package/agents/writer.md +184 -0
- package/commands/api-design.md +268 -0
- package/commands/architecture-decision.md +314 -0
- package/commands/ci-cd.md +270 -0
- package/commands/code-review.md +233 -0
- package/commands/compound.md +117 -0
- package/commands/database.md +263 -0
- package/commands/debug-detective.md +99 -0
- package/commands/docker.md +274 -0
- package/commands/documentation.md +276 -0
- package/commands/ecomode.md +51 -0
- package/commands/frontend.md +271 -0
- package/commands/git-master.md +90 -0
- package/commands/incident-response.md +292 -0
- package/commands/migrate.md +101 -0
- package/commands/performance.md +288 -0
- package/commands/refactor.md +105 -0
- package/commands/security-review.md +288 -0
- package/commands/tdd.md +183 -0
- package/commands/testing-strategy.md +265 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +295 -0
- package/dist/core/auto-compound-runner.d.ts +12 -0
- package/dist/core/auto-compound-runner.js +460 -0
- package/dist/core/config-hooks.d.ts +10 -0
- package/dist/core/config-hooks.js +112 -0
- package/dist/core/config-injector.d.ts +50 -0
- package/dist/core/config-injector.js +455 -0
- package/dist/core/doctor.d.ts +1 -0
- package/dist/core/doctor.js +163 -0
- package/dist/core/errors.d.ts +81 -0
- package/dist/core/errors.js +133 -0
- package/dist/core/global-config.d.ts +43 -0
- package/dist/core/global-config.js +25 -0
- package/dist/core/harness.d.ts +24 -0
- package/dist/core/harness.js +621 -0
- package/dist/core/init.d.ts +7 -0
- package/dist/core/init.js +37 -0
- package/dist/core/inspect-cli.d.ts +7 -0
- package/dist/core/inspect-cli.js +47 -0
- package/dist/core/legacy-detector.d.ts +33 -0
- package/dist/core/legacy-detector.js +66 -0
- package/dist/core/logger.d.ts +34 -0
- package/dist/core/logger.js +121 -0
- package/dist/core/mcp-config.d.ts +44 -0
- package/dist/core/mcp-config.js +177 -0
- package/dist/core/notepad.d.ts +31 -0
- package/dist/core/notepad.js +88 -0
- package/dist/core/paths.d.ts +85 -0
- package/dist/core/paths.js +101 -0
- package/dist/core/plugin-detector.d.ts +44 -0
- package/dist/core/plugin-detector.js +226 -0
- package/dist/core/runtime-detector.d.ts +8 -0
- package/dist/core/runtime-detector.js +49 -0
- package/dist/core/scope-resolver.d.ts +8 -0
- package/dist/core/scope-resolver.js +45 -0
- package/dist/core/session-logger.d.ts +6 -0
- package/dist/core/session-logger.js +111 -0
- package/dist/core/session-store.d.ts +28 -0
- package/dist/core/session-store.js +218 -0
- package/dist/core/settings-lock.d.ts +18 -0
- package/dist/core/settings-lock.js +125 -0
- package/dist/core/spawn.d.ts +3 -0
- package/dist/core/spawn.js +135 -0
- package/dist/core/types.d.ts +108 -0
- package/dist/core/types.js +1 -0
- package/dist/core/uninstall.d.ts +4 -0
- package/dist/core/uninstall.js +307 -0
- package/dist/core/v1-bootstrap.d.ts +26 -0
- package/dist/core/v1-bootstrap.js +155 -0
- package/dist/engine/compound-cli.d.ts +24 -0
- package/dist/engine/compound-cli.js +250 -0
- package/dist/engine/compound-extractor.d.ts +68 -0
- package/dist/engine/compound-extractor.js +860 -0
- package/dist/engine/compound-lifecycle.d.ts +32 -0
- package/dist/engine/compound-lifecycle.js +305 -0
- package/dist/engine/compound-loop.d.ts +32 -0
- package/dist/engine/compound-loop.js +511 -0
- package/dist/engine/match-eval-log.d.ts +139 -0
- package/dist/engine/match-eval-log.js +270 -0
- package/dist/engine/phrase-blocklist.d.ts +119 -0
- package/dist/engine/phrase-blocklist.js +208 -0
- package/dist/engine/skill-promoter.d.ts +20 -0
- package/dist/engine/skill-promoter.js +115 -0
- package/dist/engine/solution-format.d.ts +160 -0
- package/dist/engine/solution-format.js +432 -0
- package/dist/engine/solution-index.d.ts +13 -0
- package/dist/engine/solution-index.js +252 -0
- package/dist/engine/solution-matcher.d.ts +364 -0
- package/dist/engine/solution-matcher.js +656 -0
- package/dist/engine/solution-writer.d.ts +76 -0
- package/dist/engine/solution-writer.js +157 -0
- package/dist/engine/term-matcher.d.ts +81 -0
- package/dist/engine/term-matcher.js +268 -0
- package/dist/engine/term-normalizer.d.ts +116 -0
- package/dist/engine/term-normalizer.js +171 -0
- package/dist/fgx.d.ts +6 -0
- package/dist/fgx.js +42 -0
- package/dist/forge/cli.d.ts +11 -0
- package/dist/forge/cli.js +100 -0
- package/dist/forge/evidence-processor.d.ts +21 -0
- package/dist/forge/evidence-processor.js +87 -0
- package/dist/forge/mismatch-detector.d.ts +44 -0
- package/dist/forge/mismatch-detector.js +83 -0
- package/dist/forge/onboarding-cli.d.ts +6 -0
- package/dist/forge/onboarding-cli.js +89 -0
- package/dist/forge/onboarding.d.ts +25 -0
- package/dist/forge/onboarding.js +122 -0
- package/dist/hooks/compound-reflection.d.ts +45 -0
- package/dist/hooks/compound-reflection.js +82 -0
- package/dist/hooks/context-guard.d.ts +24 -0
- package/dist/hooks/context-guard.js +156 -0
- package/dist/hooks/dangerous-patterns.json +18 -0
- package/dist/hooks/db-guard.d.ts +17 -0
- package/dist/hooks/db-guard.js +105 -0
- package/dist/hooks/hook-config.d.ts +29 -0
- package/dist/hooks/hook-config.js +92 -0
- package/dist/hooks/hook-registry.d.ts +43 -0
- package/dist/hooks/hook-registry.js +31 -0
- package/dist/hooks/hooks-generator.d.ts +49 -0
- package/dist/hooks/hooks-generator.js +99 -0
- package/dist/hooks/intent-classifier.d.ts +12 -0
- package/dist/hooks/intent-classifier.js +62 -0
- package/dist/hooks/keyword-detector.d.ts +25 -0
- package/dist/hooks/keyword-detector.js +389 -0
- package/dist/hooks/notepad-injector.d.ts +18 -0
- package/dist/hooks/notepad-injector.js +51 -0
- package/dist/hooks/permission-handler.d.ts +14 -0
- package/dist/hooks/permission-handler.js +114 -0
- package/dist/hooks/post-tool-failure.d.ts +11 -0
- package/dist/hooks/post-tool-failure.js +118 -0
- package/dist/hooks/post-tool-handlers.d.ts +17 -0
- package/dist/hooks/post-tool-handlers.js +115 -0
- package/dist/hooks/post-tool-use.d.ts +29 -0
- package/dist/hooks/post-tool-use.js +151 -0
- package/dist/hooks/pre-compact.d.ts +10 -0
- package/dist/hooks/pre-compact.js +165 -0
- package/dist/hooks/pre-tool-use.d.ts +31 -0
- package/dist/hooks/pre-tool-use.js +325 -0
- package/dist/hooks/prompt-injection-filter.d.ts +56 -0
- package/dist/hooks/prompt-injection-filter.js +287 -0
- package/dist/hooks/rate-limiter.d.ts +21 -0
- package/dist/hooks/rate-limiter.js +86 -0
- package/dist/hooks/secret-filter.d.ts +14 -0
- package/dist/hooks/secret-filter.js +65 -0
- package/dist/hooks/session-recovery.d.ts +27 -0
- package/dist/hooks/session-recovery.js +406 -0
- package/dist/hooks/shared/atomic-write.d.ts +41 -0
- package/dist/hooks/shared/atomic-write.js +148 -0
- package/dist/hooks/shared/context-budget.d.ts +37 -0
- package/dist/hooks/shared/context-budget.js +45 -0
- package/dist/hooks/shared/file-lock.d.ts +56 -0
- package/dist/hooks/shared/file-lock.js +253 -0
- package/dist/hooks/shared/hook-response.d.ts +33 -0
- package/dist/hooks/shared/hook-response.js +62 -0
- package/dist/hooks/shared/injection-caps.d.ts +39 -0
- package/dist/hooks/shared/injection-caps.js +52 -0
- package/dist/hooks/shared/plugin-signal.d.ts +23 -0
- package/dist/hooks/shared/plugin-signal.js +104 -0
- package/dist/hooks/shared/read-stdin.d.ts +8 -0
- package/dist/hooks/shared/read-stdin.js +63 -0
- package/dist/hooks/shared/sanitize-id.d.ts +7 -0
- package/dist/hooks/shared/sanitize-id.js +9 -0
- package/dist/hooks/shared/sanitize.d.ts +7 -0
- package/dist/hooks/shared/sanitize.js +22 -0
- package/dist/hooks/skill-injector.d.ts +38 -0
- package/dist/hooks/skill-injector.js +285 -0
- package/dist/hooks/slop-detector.d.ts +18 -0
- package/dist/hooks/slop-detector.js +93 -0
- package/dist/hooks/solution-injector.d.ts +58 -0
- package/dist/hooks/solution-injector.js +436 -0
- package/dist/hooks/subagent-tracker.d.ts +10 -0
- package/dist/hooks/subagent-tracker.js +90 -0
- package/dist/i18n/index.d.ts +43 -0
- package/dist/i18n/index.js +224 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.js +14 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +40 -0
- package/dist/mcp/solution-reader.d.ts +90 -0
- package/dist/mcp/solution-reader.js +273 -0
- package/dist/mcp/tools.d.ts +16 -0
- package/dist/mcp/tools.js +302 -0
- package/dist/preset/facet-catalog.d.ts +17 -0
- package/dist/preset/facet-catalog.js +46 -0
- package/dist/preset/preset-manager.d.ts +31 -0
- package/dist/preset/preset-manager.js +111 -0
- package/dist/renderer/inspect-renderer.d.ts +11 -0
- package/dist/renderer/inspect-renderer.js +123 -0
- package/dist/renderer/rule-renderer.d.ts +18 -0
- package/dist/renderer/rule-renderer.js +159 -0
- package/dist/store/evidence-store.d.ts +23 -0
- package/dist/store/evidence-store.js +58 -0
- package/dist/store/profile-store.d.ts +12 -0
- package/dist/store/profile-store.js +53 -0
- package/dist/store/recommendation-store.d.ts +22 -0
- package/dist/store/recommendation-store.js +64 -0
- package/dist/store/rule-store.d.ts +22 -0
- package/dist/store/rule-store.js +62 -0
- package/dist/store/session-state-store.d.ts +11 -0
- package/dist/store/session-state-store.js +44 -0
- package/dist/store/types.d.ts +159 -0
- package/dist/store/types.js +7 -0
- package/hooks/hook-registry.json +21 -0
- package/hooks/hooks.json +185 -0
- package/package.json +89 -0
- package/plugin.json +20 -0
- package/scripts/postinstall.js +826 -0
- package/skills/api-design/SKILL.md +262 -0
- package/skills/architecture-decision/SKILL.md +309 -0
- package/skills/ci-cd/SKILL.md +264 -0
- package/skills/code-review/SKILL.md +228 -0
- package/skills/compound/SKILL.md +101 -0
- package/skills/database/SKILL.md +257 -0
- package/skills/debug-detective/SKILL.md +95 -0
- package/skills/docker/SKILL.md +268 -0
- package/skills/documentation/SKILL.md +270 -0
- package/skills/ecomode/SKILL.md +46 -0
- package/skills/frontend/SKILL.md +265 -0
- package/skills/git-master/SKILL.md +86 -0
- package/skills/incident-response/SKILL.md +286 -0
- package/skills/migrate/SKILL.md +96 -0
- package/skills/performance/SKILL.md +282 -0
- package/skills/refactor/SKILL.md +100 -0
- package/skills/security-review/SKILL.md +282 -0
- package/skills/tdd/SKILL.md +178 -0
- package/skills/testing-strategy/SKILL.md +260 -0
- package/starter-pack/solutions/starter-api-error-responses.md +37 -0
- package/starter-pack/solutions/starter-async-patterns.md +40 -0
- package/starter-pack/solutions/starter-caching-strategy.md +40 -0
- package/starter-pack/solutions/starter-code-review-checklist.md +39 -0
- package/starter-pack/solutions/starter-debugging-systematic.md +40 -0
- package/starter-pack/solutions/starter-dependency-injection.md +40 -0
- package/starter-pack/solutions/starter-error-handling-patterns.md +38 -0
- package/starter-pack/solutions/starter-git-atomic-commits.md +36 -0
- package/starter-pack/solutions/starter-input-validation.md +40 -0
- package/starter-pack/solutions/starter-n-plus-one-queries.md +37 -0
- package/starter-pack/solutions/starter-refactor-safely.md +38 -0
- package/starter-pack/solutions/starter-secret-management.md +37 -0
- package/starter-pack/solutions/starter-separation-of-concerns.md +36 -0
- package/starter-pack/solutions/starter-tdd-red-green-refactor.md +40 -0
- package/starter-pack/solutions/starter-typescript-strict-types.md +39 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen v1 — Inspect CLI
|
|
3
|
+
*
|
|
4
|
+
* forgen inspect profile|rules|evidence|session
|
|
5
|
+
* Authoritative: docs/plans/2026-04-03-forgen-rule-renderer-spec.md §6
|
|
6
|
+
*/
|
|
7
|
+
import { loadProfile } from '../store/profile-store.js';
|
|
8
|
+
import { loadAllRules } from '../store/rule-store.js';
|
|
9
|
+
import { loadRecentEvidence } from '../store/evidence-store.js';
|
|
10
|
+
import { loadRecentSessions } from '../store/session-state-store.js';
|
|
11
|
+
import * as inspect from '../renderer/inspect-renderer.js';
|
|
12
|
+
export async function handleInspect(args) {
|
|
13
|
+
const sub = args[0];
|
|
14
|
+
if (sub === 'profile') {
|
|
15
|
+
const profile = loadProfile();
|
|
16
|
+
if (!profile) {
|
|
17
|
+
console.log('\n No v1 profile found. Run onboarding first.\n');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log('\n' + inspect.renderProfile(profile) + '\n');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (sub === 'rules') {
|
|
24
|
+
const rules = loadAllRules();
|
|
25
|
+
console.log('\n' + inspect.renderRules(rules) + '\n');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (sub === 'evidence') {
|
|
29
|
+
const evidence = loadRecentEvidence(20);
|
|
30
|
+
console.log('\n' + inspect.renderEvidence(evidence) + '\n');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (sub === 'session') {
|
|
34
|
+
const sessions = loadRecentSessions(1);
|
|
35
|
+
if (sessions.length === 0) {
|
|
36
|
+
console.log('\n No session state found.\n');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
console.log('\n' + inspect.renderSession(sessions[0]) + '\n');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(` Usage:
|
|
43
|
+
forgen inspect profile — 현재 profile 상태
|
|
44
|
+
forgen inspect rules — active/suppressed 규칙 목록
|
|
45
|
+
forgen inspect evidence — 최근 evidence 목록
|
|
46
|
+
forgen inspect session — 현재/최근 세션 상태`);
|
|
47
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen v1 — Legacy Detector
|
|
3
|
+
*
|
|
4
|
+
* ~/.compound/ 5D profile 감지, backup, cutover 분기.
|
|
5
|
+
* Authoritative spec:
|
|
6
|
+
* docs/plans/2026-04-03-forgen-lifecycle-design.md §12
|
|
7
|
+
* docs/plans/2026-04-03-forgen-data-model-storage-spec.md §11
|
|
8
|
+
*
|
|
9
|
+
* cutover 순서:
|
|
10
|
+
* 1. ~/.compound/me/forge-profile.json 존재 여부 확인
|
|
11
|
+
* 2. 5D schema인지 검사
|
|
12
|
+
* 3. legacy면 backup (forge-profile.legacy-<ts>.json)
|
|
13
|
+
* 4. 새 profile은 ~/.forgen/me/forge-profile.json에 생성 → fresh onboarding
|
|
14
|
+
*/
|
|
15
|
+
export interface LegacyCheckResult {
|
|
16
|
+
found: boolean;
|
|
17
|
+
isLegacy: boolean;
|
|
18
|
+
backupPath: string | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ~/.compound/me/forge-profile.json이 5D legacy인지 확인.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkLegacyProfile(): LegacyCheckResult;
|
|
24
|
+
/**
|
|
25
|
+
* legacy profile을 backup.
|
|
26
|
+
* ~/.compound/me/forge-profile.legacy-<timestamp>.json으로 복사.
|
|
27
|
+
*/
|
|
28
|
+
export declare function backupLegacyProfile(): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* legacy cutover 전체 실행.
|
|
31
|
+
* 반환: backup 경로 (legacy가 아니면 null)
|
|
32
|
+
*/
|
|
33
|
+
export declare function runLegacyCutover(): string | null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen v1 — Legacy Detector
|
|
3
|
+
*
|
|
4
|
+
* ~/.compound/ 5D profile 감지, backup, cutover 분기.
|
|
5
|
+
* Authoritative spec:
|
|
6
|
+
* docs/plans/2026-04-03-forgen-lifecycle-design.md §12
|
|
7
|
+
* docs/plans/2026-04-03-forgen-data-model-storage-spec.md §11
|
|
8
|
+
*
|
|
9
|
+
* cutover 순서:
|
|
10
|
+
* 1. ~/.compound/me/forge-profile.json 존재 여부 확인
|
|
11
|
+
* 2. 5D schema인지 검사
|
|
12
|
+
* 3. legacy면 backup (forge-profile.legacy-<ts>.json)
|
|
13
|
+
* 4. 새 profile은 ~/.forgen/me/forge-profile.json에 생성 → fresh onboarding
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as path from 'node:path';
|
|
17
|
+
import { FORGE_PROFILE } from './paths.js';
|
|
18
|
+
import { safeReadJSON } from '../hooks/shared/atomic-write.js';
|
|
19
|
+
// 5D schema의 핵심 필드
|
|
20
|
+
const LEGACY_FIELDS = ['riskTolerance', 'autonomyPreference', 'qualityFocus', 'abstractionLevel', 'communicationStyle'];
|
|
21
|
+
/**
|
|
22
|
+
* ~/.compound/me/forge-profile.json이 5D legacy인지 확인.
|
|
23
|
+
*/
|
|
24
|
+
export function checkLegacyProfile() {
|
|
25
|
+
if (!fs.existsSync(FORGE_PROFILE)) {
|
|
26
|
+
return { found: false, isLegacy: false, backupPath: null };
|
|
27
|
+
}
|
|
28
|
+
const data = safeReadJSON(FORGE_PROFILE, {});
|
|
29
|
+
// model_version이 2.x면 이미 v1 마이그레이션 완료
|
|
30
|
+
if (typeof data.model_version === 'string' && data.model_version.startsWith('2.')) {
|
|
31
|
+
return { found: true, isLegacy: false, backupPath: null };
|
|
32
|
+
}
|
|
33
|
+
// 5D 필드 존재 여부로 legacy 판정
|
|
34
|
+
const dimensions = data.dimensions;
|
|
35
|
+
if (dimensions && LEGACY_FIELDS.some(f => f in dimensions)) {
|
|
36
|
+
return { found: true, isLegacy: true, backupPath: null };
|
|
37
|
+
}
|
|
38
|
+
// dimensions가 없더라도 model_version이 없으면 legacy로 간주
|
|
39
|
+
if (!data.model_version) {
|
|
40
|
+
return { found: true, isLegacy: true, backupPath: null };
|
|
41
|
+
}
|
|
42
|
+
return { found: true, isLegacy: false, backupPath: null };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* legacy profile을 backup.
|
|
46
|
+
* ~/.compound/me/forge-profile.legacy-<timestamp>.json으로 복사.
|
|
47
|
+
*/
|
|
48
|
+
export function backupLegacyProfile() {
|
|
49
|
+
if (!fs.existsSync(FORGE_PROFILE))
|
|
50
|
+
return null;
|
|
51
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
52
|
+
const backupPath = path.join(path.dirname(FORGE_PROFILE), `forge-profile.legacy-${timestamp}.json`);
|
|
53
|
+
fs.copyFileSync(FORGE_PROFILE, backupPath);
|
|
54
|
+
return backupPath;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* legacy cutover 전체 실행.
|
|
58
|
+
* 반환: backup 경로 (legacy가 아니면 null)
|
|
59
|
+
*/
|
|
60
|
+
export function runLegacyCutover() {
|
|
61
|
+
const check = checkLegacyProfile();
|
|
62
|
+
if (!check.found || !check.isLegacy)
|
|
63
|
+
return null;
|
|
64
|
+
const backupPath = backupLegacyProfile();
|
|
65
|
+
return backupPath;
|
|
66
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Unified Logger
|
|
3
|
+
*
|
|
4
|
+
* 환경변수:
|
|
5
|
+
* FORGEN_LOG_LEVEL — debug | info | warn | error (기본: info)
|
|
6
|
+
* FORGEN_DEBUG — 활성화할 namespace 목록 (쉼표 구분, 예: 'provider,hook')
|
|
7
|
+
* '*' 이면 전체 네임스페이스 활성화
|
|
8
|
+
* COMPOUND_DEBUG=1 — 레거시 호환: 전체 debug 출력 활성화 (FORGEN_DEBUG='*' 와 동일)
|
|
9
|
+
*
|
|
10
|
+
* 출력 형식: [forgen:namespace] message
|
|
11
|
+
* 출력 스트림: stderr (CLI 사용자 출력과 분리)
|
|
12
|
+
*
|
|
13
|
+
* ADR: debugLog()를 직접 대체하지 않고 내부 구현으로 유지.
|
|
14
|
+
* 기존 호출자는 현행 동작을 유지하면서 점진적으로 createLogger()로 마이그레이션.
|
|
15
|
+
*/
|
|
16
|
+
export declare enum LogLevel {
|
|
17
|
+
debug = 0,
|
|
18
|
+
info = 1,
|
|
19
|
+
warn = 2,
|
|
20
|
+
error = 3
|
|
21
|
+
}
|
|
22
|
+
export declare class Logger {
|
|
23
|
+
readonly namespace: string;
|
|
24
|
+
constructor(namespace: string);
|
|
25
|
+
private shouldOutput;
|
|
26
|
+
private write;
|
|
27
|
+
debug(msg: string, error?: unknown): void;
|
|
28
|
+
info(msg: string, error?: unknown): void;
|
|
29
|
+
warn(msg: string, error?: unknown): void;
|
|
30
|
+
error(msg: string, error?: unknown): void;
|
|
31
|
+
}
|
|
32
|
+
/** namespace 기반 Logger 인스턴스 팩토리 */
|
|
33
|
+
export declare function createLogger(namespace: string): Logger;
|
|
34
|
+
export declare function debugLog(context: string, msg: string, error?: unknown): void;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Unified Logger
|
|
3
|
+
*
|
|
4
|
+
* 환경변수:
|
|
5
|
+
* FORGEN_LOG_LEVEL — debug | info | warn | error (기본: info)
|
|
6
|
+
* FORGEN_DEBUG — 활성화할 namespace 목록 (쉼표 구분, 예: 'provider,hook')
|
|
7
|
+
* '*' 이면 전체 네임스페이스 활성화
|
|
8
|
+
* COMPOUND_DEBUG=1 — 레거시 호환: 전체 debug 출력 활성화 (FORGEN_DEBUG='*' 와 동일)
|
|
9
|
+
*
|
|
10
|
+
* 출력 형식: [forgen:namespace] message
|
|
11
|
+
* 출력 스트림: stderr (CLI 사용자 출력과 분리)
|
|
12
|
+
*
|
|
13
|
+
* ADR: debugLog()를 직접 대체하지 않고 내부 구현으로 유지.
|
|
14
|
+
* 기존 호출자는 현행 동작을 유지하면서 점진적으로 createLogger()로 마이그레이션.
|
|
15
|
+
*/
|
|
16
|
+
export var LogLevel;
|
|
17
|
+
(function (LogLevel) {
|
|
18
|
+
LogLevel[LogLevel["debug"] = 0] = "debug";
|
|
19
|
+
LogLevel[LogLevel["info"] = 1] = "info";
|
|
20
|
+
LogLevel[LogLevel["warn"] = 2] = "warn";
|
|
21
|
+
LogLevel[LogLevel["error"] = 3] = "error";
|
|
22
|
+
})(LogLevel || (LogLevel = {}));
|
|
23
|
+
const LEVEL_LABELS = {
|
|
24
|
+
[LogLevel.debug]: 'debug',
|
|
25
|
+
[LogLevel.info]: 'info',
|
|
26
|
+
[LogLevel.warn]: 'warn',
|
|
27
|
+
[LogLevel.error]: 'error',
|
|
28
|
+
};
|
|
29
|
+
function parseLogLevel(raw) {
|
|
30
|
+
switch (raw?.toLowerCase()) {
|
|
31
|
+
case 'debug': return LogLevel.debug;
|
|
32
|
+
case 'warn': return LogLevel.warn;
|
|
33
|
+
case 'error': return LogLevel.error;
|
|
34
|
+
case 'info': return LogLevel.info;
|
|
35
|
+
default:
|
|
36
|
+
// COMPOUND_DEBUG=1이 설정되어 있고 FORGEN_LOG_LEVEL이 명시되지 않으면 debug 레벨로 동작
|
|
37
|
+
if (process.env.FORGEN_DEBUG === '1' || process.env.COMPOUND_DEBUG === '1')
|
|
38
|
+
return LogLevel.debug;
|
|
39
|
+
return LogLevel.info;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function resolveEnabledNamespaces() {
|
|
43
|
+
// 레거시 COMPOUND_DEBUG=1 → 전체 활성화
|
|
44
|
+
if (process.env.FORGEN_DEBUG === '1' || process.env.COMPOUND_DEBUG === '1')
|
|
45
|
+
return '*';
|
|
46
|
+
const raw = process.env.FORGEN_DEBUG;
|
|
47
|
+
if (!raw)
|
|
48
|
+
return null;
|
|
49
|
+
if (raw === '*')
|
|
50
|
+
return '*';
|
|
51
|
+
return new Set(raw.split(',').map(s => s.trim()).filter(Boolean));
|
|
52
|
+
}
|
|
53
|
+
export class Logger {
|
|
54
|
+
namespace;
|
|
55
|
+
constructor(namespace) {
|
|
56
|
+
this.namespace = namespace;
|
|
57
|
+
}
|
|
58
|
+
shouldOutput(level) {
|
|
59
|
+
const configuredLevel = parseLogLevel(process.env.FORGEN_LOG_LEVEL);
|
|
60
|
+
// info 이상은 namespace 필터 무관하게 레벨만 체크
|
|
61
|
+
if (level >= LogLevel.info) {
|
|
62
|
+
return level >= configuredLevel;
|
|
63
|
+
}
|
|
64
|
+
// debug 레벨: namespace 필터 + 레벨 체크 모두 통과해야 함
|
|
65
|
+
if (level < configuredLevel)
|
|
66
|
+
return false;
|
|
67
|
+
const enabled = resolveEnabledNamespaces();
|
|
68
|
+
if (enabled === null)
|
|
69
|
+
return false;
|
|
70
|
+
if (enabled === '*')
|
|
71
|
+
return true;
|
|
72
|
+
return enabled.has(this.namespace);
|
|
73
|
+
}
|
|
74
|
+
write(level, msg, error) {
|
|
75
|
+
if (!this.shouldOutput(level))
|
|
76
|
+
return;
|
|
77
|
+
const label = LEVEL_LABELS[level];
|
|
78
|
+
const errPart = error !== undefined
|
|
79
|
+
? `: ${error instanceof Error ? error.message : String(error)}`
|
|
80
|
+
: '';
|
|
81
|
+
const line = `[forgen:${this.namespace}] [${label}] ${msg}${errPart}`;
|
|
82
|
+
// warn/error → console.error, debug/info → console.error (stderr, CLI 출력과 분리)
|
|
83
|
+
console.error(line);
|
|
84
|
+
}
|
|
85
|
+
debug(msg, error) { this.write(LogLevel.debug, msg, error); }
|
|
86
|
+
info(msg, error) { this.write(LogLevel.info, msg, error); }
|
|
87
|
+
warn(msg, error) { this.write(LogLevel.warn, msg, error); }
|
|
88
|
+
error(msg, error) { this.write(LogLevel.error, msg, error); }
|
|
89
|
+
}
|
|
90
|
+
/** namespace 기반 Logger 인스턴스 팩토리 */
|
|
91
|
+
export function createLogger(namespace) {
|
|
92
|
+
return new Logger(namespace);
|
|
93
|
+
}
|
|
94
|
+
// ── 레거시 호환 API ──────────────────────────────────────────────────────────
|
|
95
|
+
//
|
|
96
|
+
// 기존 debugLog(context, msg, error?) 호출자는 변경 없이 동작합니다.
|
|
97
|
+
// 내부적으로 createLogger를 사용하여 동일한 환경변수 제어를 따릅니다.
|
|
98
|
+
// 레거시 형식 출력: [CH:context] message (기존 형식 유지로 로그 파서 호환)
|
|
99
|
+
export function debugLog(context, msg, error) {
|
|
100
|
+
// 레거시 호환: COMPOUND_DEBUG=1이면 레벨 체크 없이 무조건 출력
|
|
101
|
+
// FORGEN_LOG_LEVEL이 명시적으로 설정된 경우에만 레벨 우선순위를 따름
|
|
102
|
+
const isLegacyForced = process.env.FORGEN_DEBUG === '1' || process.env.COMPOUND_DEBUG === '1';
|
|
103
|
+
if (!isLegacyForced) {
|
|
104
|
+
const configuredLevel = parseLogLevel(process.env.FORGEN_LOG_LEVEL);
|
|
105
|
+
if (LogLevel.debug < configuredLevel)
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
else if (process.env.FORGEN_LOG_LEVEL) {
|
|
109
|
+
// COMPOUND_DEBUG=1이더라도 명시적 LOG_LEVEL 설정은 존중
|
|
110
|
+
const configuredLevel = parseLogLevel(process.env.FORGEN_LOG_LEVEL);
|
|
111
|
+
if (LogLevel.debug < configuredLevel)
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const enabled = resolveEnabledNamespaces();
|
|
115
|
+
if (enabled === null && !isLegacyForced)
|
|
116
|
+
return;
|
|
117
|
+
if (enabled !== null && enabled !== '*' && !enabled.has(context))
|
|
118
|
+
return;
|
|
119
|
+
const errMsg = error instanceof Error ? error.message : String(error ?? '');
|
|
120
|
+
console.error(`[CH:${context}] ${msg}${errMsg ? `: ${errMsg}` : ''}`);
|
|
121
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-config.ts — MCP 서버 설정 생성기
|
|
3
|
+
*
|
|
4
|
+
* Claude Code settings.json의 mcpServers 섹션을 관리한다.
|
|
5
|
+
* - 기본 MCP 서버 템플릿 제공 (filesystem, fetch, context7, playwright)
|
|
6
|
+
* - 설정 생성 및 주입 (기존 설정과 병합, 덮어쓰기 없음)
|
|
7
|
+
* - CLI 핸들러: forgen mcp list/add/remove/templates
|
|
8
|
+
*/
|
|
9
|
+
/** MCP 서버 단일 설정 */
|
|
10
|
+
export interface McpServerConfig {
|
|
11
|
+
command: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
env?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 기본 MCP 서버 템플릿 목록 반환
|
|
17
|
+
* 반환값: 서버 이름 → 설정 객체 맵
|
|
18
|
+
*/
|
|
19
|
+
export declare function getDefaultMcpTemplates(): Record<string, McpServerConfig>;
|
|
20
|
+
/**
|
|
21
|
+
* 선택한 서버 이름 목록을 받아 McpServerConfig 맵 생성
|
|
22
|
+
* 알 수 없는 서버 이름은 경고 출력 후 무시
|
|
23
|
+
*/
|
|
24
|
+
export declare function generateMcpConfig(servers: string[]): Record<string, McpServerConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* ~/.claude/settings.json의 mcpServers에 서버 설정 주입
|
|
27
|
+
* 기존 mcpServers와 병합 (같은 이름의 서버가 있으면 덮어씀)
|
|
28
|
+
*/
|
|
29
|
+
export declare function injectMcpServers(servers: Record<string, McpServerConfig>): void;
|
|
30
|
+
/**
|
|
31
|
+
* 현재 ~/.claude/settings.json에 등록된 MCP 서버 목록 반환
|
|
32
|
+
* settings.json이 없거나 mcpServers가 없으면 빈 객체 반환
|
|
33
|
+
*/
|
|
34
|
+
export declare function listInstalledMcpServers(): Record<string, McpServerConfig>;
|
|
35
|
+
/**
|
|
36
|
+
* MCP CLI 핸들러
|
|
37
|
+
*
|
|
38
|
+
* 사용법:
|
|
39
|
+
* forgen mcp list — 설치된 MCP 서버 목록
|
|
40
|
+
* forgen mcp templates — 사용 가능한 기본 템플릿 목록
|
|
41
|
+
* forgen mcp add <name> [<name>...] — MCP 서버 추가 (템플릿 기준)
|
|
42
|
+
* forgen mcp remove <name> — MCP 서버 제거
|
|
43
|
+
*/
|
|
44
|
+
export declare function handleMcp(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-config.ts — MCP 서버 설정 생성기
|
|
3
|
+
*
|
|
4
|
+
* Claude Code settings.json의 mcpServers 섹션을 관리한다.
|
|
5
|
+
* - 기본 MCP 서버 템플릿 제공 (filesystem, fetch, context7, playwright)
|
|
6
|
+
* - 설정 생성 및 주입 (기존 설정과 병합, 덮어쓰기 없음)
|
|
7
|
+
* - CLI 핸들러: forgen mcp list/add/remove/templates
|
|
8
|
+
*/
|
|
9
|
+
import { readSettings, writeSettings } from './settings-lock.js';
|
|
10
|
+
/** 기본 MCP 서버 템플릿 맵 */
|
|
11
|
+
const DEFAULT_MCP_TEMPLATES = {
|
|
12
|
+
filesystem: {
|
|
13
|
+
command: 'npx',
|
|
14
|
+
args: ['-y', '@anthropic-ai/mcp-server-filesystem', '--allow-dir', '.'],
|
|
15
|
+
},
|
|
16
|
+
fetch: {
|
|
17
|
+
command: 'npx',
|
|
18
|
+
args: ['-y', '@anthropic-ai/mcp-server-fetch'],
|
|
19
|
+
},
|
|
20
|
+
context7: {
|
|
21
|
+
command: 'npx',
|
|
22
|
+
args: ['-y', '@upstash/context7-mcp'],
|
|
23
|
+
},
|
|
24
|
+
playwright: {
|
|
25
|
+
command: 'npx',
|
|
26
|
+
args: ['-y', '@anthropic-ai/mcp-server-playwright'],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 기본 MCP 서버 템플릿 목록 반환
|
|
31
|
+
* 반환값: 서버 이름 → 설정 객체 맵
|
|
32
|
+
*/
|
|
33
|
+
export function getDefaultMcpTemplates() {
|
|
34
|
+
const result = {};
|
|
35
|
+
for (const [k, v] of Object.entries(DEFAULT_MCP_TEMPLATES)) {
|
|
36
|
+
result[k] = { ...v, args: [...v.args], ...(v.env ? { env: { ...v.env } } : {}) };
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 선택한 서버 이름 목록을 받아 McpServerConfig 맵 생성
|
|
42
|
+
* 알 수 없는 서버 이름은 경고 출력 후 무시
|
|
43
|
+
*/
|
|
44
|
+
export function generateMcpConfig(servers) {
|
|
45
|
+
const templates = getDefaultMcpTemplates();
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const name of servers) {
|
|
48
|
+
if (templates[name]) {
|
|
49
|
+
result[name] = { ...templates[name], args: [...templates[name].args] };
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.warn(`[mcp-config] Unknown MCP server name: "${name}" — skipping.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ~/.claude/settings.json의 mcpServers에 서버 설정 주입
|
|
59
|
+
* 기존 mcpServers와 병합 (같은 이름의 서버가 있으면 덮어씀)
|
|
60
|
+
*/
|
|
61
|
+
export function injectMcpServers(servers) {
|
|
62
|
+
const settings = readSettings(); // 파싱 실패 시 throw
|
|
63
|
+
const existing = settings.mcpServers ?? {};
|
|
64
|
+
settings.mcpServers = { ...existing, ...servers };
|
|
65
|
+
writeSettings(settings); // lock + backup + atomic write
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 현재 ~/.claude/settings.json에 등록된 MCP 서버 목록 반환
|
|
69
|
+
* settings.json이 없거나 mcpServers가 없으면 빈 객체 반환
|
|
70
|
+
*/
|
|
71
|
+
export function listInstalledMcpServers() {
|
|
72
|
+
try {
|
|
73
|
+
const settings = readSettings();
|
|
74
|
+
return settings.mcpServers ?? {};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* MCP CLI 핸들러
|
|
82
|
+
*
|
|
83
|
+
* 사용법:
|
|
84
|
+
* forgen mcp list — 설치된 MCP 서버 목록
|
|
85
|
+
* forgen mcp templates — 사용 가능한 기본 템플릿 목록
|
|
86
|
+
* forgen mcp add <name> [<name>...] — MCP 서버 추가 (템플릿 기준)
|
|
87
|
+
* forgen mcp remove <name> — MCP 서버 제거
|
|
88
|
+
*/
|
|
89
|
+
export async function handleMcp(args) {
|
|
90
|
+
const sub = args[0];
|
|
91
|
+
if (!sub || sub === 'list') {
|
|
92
|
+
const installed = listInstalledMcpServers();
|
|
93
|
+
const names = Object.keys(installed);
|
|
94
|
+
console.log('\n Forgen — MCP Servers\n');
|
|
95
|
+
if (names.length === 0) {
|
|
96
|
+
console.log(' No MCP servers installed.');
|
|
97
|
+
console.log(' Use `forgen mcp add <name>` to add one.\n');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(` Installed servers (${names.length}):\n`);
|
|
101
|
+
for (const name of names) {
|
|
102
|
+
const cfg = installed[name];
|
|
103
|
+
console.log(` ${name}`);
|
|
104
|
+
console.log(` command: ${cfg.command} ${cfg.args.join(' ')}`);
|
|
105
|
+
if (cfg.env && Object.keys(cfg.env).length > 0) {
|
|
106
|
+
console.log(` env: ${JSON.stringify(cfg.env)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.log('');
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (sub === 'templates') {
|
|
114
|
+
const templates = getDefaultMcpTemplates();
|
|
115
|
+
console.log('\n Forgen — MCP Templates\n');
|
|
116
|
+
console.log(` Available default templates (${Object.keys(templates).length}):\n`);
|
|
117
|
+
for (const [name, cfg] of Object.entries(templates)) {
|
|
118
|
+
console.log(` ${name}`);
|
|
119
|
+
console.log(` command: ${cfg.command} ${cfg.args.join(' ')}`);
|
|
120
|
+
}
|
|
121
|
+
console.log('');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (sub === 'add') {
|
|
125
|
+
const serverNames = args.slice(1);
|
|
126
|
+
if (serverNames.length === 0) {
|
|
127
|
+
console.error(' Error: Please specify a server name to add.');
|
|
128
|
+
console.error(' Usage: forgen mcp add <name> [<name>...]');
|
|
129
|
+
console.error(' Available templates: forgen mcp templates');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
const configs = generateMcpConfig(serverNames);
|
|
133
|
+
const addedNames = Object.keys(configs);
|
|
134
|
+
if (addedNames.length === 0) {
|
|
135
|
+
console.error(' Error: No valid server names provided.');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
injectMcpServers(configs);
|
|
139
|
+
console.log('\n Forgen — MCP Add\n');
|
|
140
|
+
for (const name of addedNames) {
|
|
141
|
+
console.log(` ✓ Added: ${name}`);
|
|
142
|
+
}
|
|
143
|
+
console.log('');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (sub === 'remove') {
|
|
147
|
+
const serverName = args[1];
|
|
148
|
+
if (!serverName) {
|
|
149
|
+
console.error(' Error: Please specify a server name to remove.');
|
|
150
|
+
console.error(' Usage: forgen mcp remove <name>');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
let settings;
|
|
154
|
+
try {
|
|
155
|
+
settings = readSettings();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
console.error(' Error: Failed to parse settings.json');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const mcpServers = settings.mcpServers ?? {};
|
|
163
|
+
if (!mcpServers[serverName]) {
|
|
164
|
+
console.log(` "${serverName}" is not installed.`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
delete mcpServers[serverName];
|
|
168
|
+
settings.mcpServers = mcpServers;
|
|
169
|
+
writeSettings(settings); // lock + backup + atomic write
|
|
170
|
+
console.log(`\n ✓ Removed: ${serverName}\n`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// 알 수 없는 서브커맨드
|
|
174
|
+
console.error(` Error: Unknown mcp subcommand: "${sub}"`);
|
|
175
|
+
console.error(' Usage: forgen mcp [list|templates|add|remove]');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Notepad 코어 모듈
|
|
3
|
+
*
|
|
4
|
+
* .compound/notepad.md 파일을 관리합니다.
|
|
5
|
+
* compaction(컨텍스트 압축)에서 살아남는 영구 컨텍스트 노트 저장소로,
|
|
6
|
+
* 세션이 바뀌어도 유지되어야 하는 중요 메모를 보관합니다.
|
|
7
|
+
*
|
|
8
|
+
* 사용 위치: {프로젝트루트}/.compound/notepad.md
|
|
9
|
+
*/
|
|
10
|
+
/** {repo}/.compound/notepad.md 경로 반환 */
|
|
11
|
+
export declare function getNotepadPath(cwd: string): string;
|
|
12
|
+
/** notepad.md 내용 읽기. 파일이 없으면 빈 문자열 반환. */
|
|
13
|
+
export declare function readNotepad(cwd: string): string;
|
|
14
|
+
/** notepad.md 전체를 content로 덮어쓰기. 부모 디렉토리가 없으면 자동 생성. */
|
|
15
|
+
export declare function writeNotepad(cwd: string, content: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* notepad.md 끝에 타임스탬프와 함께 항목 추가.
|
|
18
|
+
* 형식: `\n## [YYYY-MM-DD HH:mm]\n{entry}\n`
|
|
19
|
+
*/
|
|
20
|
+
export declare function appendToNotepad(cwd: string, entry: string): void;
|
|
21
|
+
/** notepad.md 내용 초기화 (빈 파일로 덮어쓰기). */
|
|
22
|
+
export declare function clearNotepad(cwd: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* CLI 핸들러: `forgen notepad <show|add|edit|clear>`
|
|
25
|
+
*
|
|
26
|
+
* - show: 현재 notepad 내용 출력
|
|
27
|
+
* - add "내용": 타임스탬프와 함께 항목 추가
|
|
28
|
+
* - edit: notepad.md 파일 경로 출력 (에디터에서 직접 편집용)
|
|
29
|
+
* - clear: notepad 초기화
|
|
30
|
+
*/
|
|
31
|
+
export declare function handleNotepad(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forgen — Notepad 코어 모듈
|
|
3
|
+
*
|
|
4
|
+
* .compound/notepad.md 파일을 관리합니다.
|
|
5
|
+
* compaction(컨텍스트 압축)에서 살아남는 영구 컨텍스트 노트 저장소로,
|
|
6
|
+
* 세션이 바뀌어도 유지되어야 하는 중요 메모를 보관합니다.
|
|
7
|
+
*
|
|
8
|
+
* 사용 위치: {프로젝트루트}/.compound/notepad.md
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { projectDir } from './paths.js';
|
|
13
|
+
/** {repo}/.compound/notepad.md 경로 반환 */
|
|
14
|
+
export function getNotepadPath(cwd) {
|
|
15
|
+
return path.join(projectDir(cwd), 'notepad.md');
|
|
16
|
+
}
|
|
17
|
+
/** notepad.md 내용 읽기. 파일이 없으면 빈 문자열 반환. */
|
|
18
|
+
export function readNotepad(cwd) {
|
|
19
|
+
const filePath = getNotepadPath(cwd);
|
|
20
|
+
if (!fs.existsSync(filePath))
|
|
21
|
+
return '';
|
|
22
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
23
|
+
}
|
|
24
|
+
/** notepad.md 전체를 content로 덮어쓰기. 부모 디렉토리가 없으면 자동 생성. */
|
|
25
|
+
export function writeNotepad(cwd, content) {
|
|
26
|
+
const filePath = getNotepadPath(cwd);
|
|
27
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
28
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* notepad.md 끝에 타임스탬프와 함께 항목 추가.
|
|
32
|
+
* 형식: `\n## [YYYY-MM-DD HH:mm]\n{entry}\n`
|
|
33
|
+
*/
|
|
34
|
+
export function appendToNotepad(cwd, entry) {
|
|
35
|
+
const now = new Date();
|
|
36
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
37
|
+
const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
38
|
+
const block = `\n## [${timestamp}]\n${entry}\n`;
|
|
39
|
+
const filePath = getNotepadPath(cwd);
|
|
40
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
41
|
+
fs.appendFileSync(filePath, block, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
/** notepad.md 내용 초기화 (빈 파일로 덮어쓰기). */
|
|
44
|
+
export function clearNotepad(cwd) {
|
|
45
|
+
writeNotepad(cwd, '');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* CLI 핸들러: `forgen notepad <show|add|edit|clear>`
|
|
49
|
+
*
|
|
50
|
+
* - show: 현재 notepad 내용 출력
|
|
51
|
+
* - add "내용": 타임스탬프와 함께 항목 추가
|
|
52
|
+
* - edit: notepad.md 파일 경로 출력 (에디터에서 직접 편집용)
|
|
53
|
+
* - clear: notepad 초기화
|
|
54
|
+
*/
|
|
55
|
+
export async function handleNotepad(args) {
|
|
56
|
+
const subcommand = args[0] ?? 'show';
|
|
57
|
+
const cwd = process.env.FORGEN_CWD ?? process.env.COMPOUND_CWD ?? process.cwd();
|
|
58
|
+
if (subcommand === 'show') {
|
|
59
|
+
const content = readNotepad(cwd);
|
|
60
|
+
if (!content.trim()) {
|
|
61
|
+
console.log(' (notepad is empty)');
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
console.log(content);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (subcommand === 'add') {
|
|
68
|
+
const entry = args.slice(1).join(' ');
|
|
69
|
+
if (!entry) {
|
|
70
|
+
console.error(' Usage: forgen notepad add "content to add"');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
appendToNotepad(cwd, entry);
|
|
74
|
+
console.log(' Added to notepad.');
|
|
75
|
+
}
|
|
76
|
+
else if (subcommand === 'edit') {
|
|
77
|
+
const filePath = getNotepadPath(cwd);
|
|
78
|
+
console.log(` Notepad file path: ${filePath}`);
|
|
79
|
+
console.log(` Open with your editor: $EDITOR ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
else if (subcommand === 'clear') {
|
|
82
|
+
clearNotepad(cwd);
|
|
83
|
+
console.log(' Notepad cleared.');
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(' Usage: forgen notepad <show|add|edit|clear>');
|
|
87
|
+
}
|
|
88
|
+
}
|