triflux 9.8.3 → 9.8.6
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/bin/triflux.mjs +268 -5
- package/hooks/hook-adaptive-collector.mjs +86 -0
- package/hooks/hook-registry.json +4 -4
- package/hooks/keyword-rules.json +189 -1
- package/hooks/safety-guard.mjs +16 -0
- package/hub/account-broker.mjs +242 -0
- package/hub/adaptive-diagnostic.mjs +319 -0
- package/hub/adaptive-inject.mjs +186 -0
- package/hub/adaptive-memory.mjs +322 -0
- package/hub/adaptive.mjs +143 -0
- package/hub/cli-adapter-base.mjs +192 -0
- package/hub/codex-adapter.mjs +191 -0
- package/hub/codex-preflight.mjs +147 -0
- package/hub/gemini-adapter.mjs +180 -0
- package/hub/lib/cache-guard.mjs +114 -0
- package/hub/lib/known-errors.json +72 -0
- package/hub/lib/memory-store.mjs +748 -0
- package/hub/lib/ssh-command.mjs +150 -0
- package/hub/lib/uuidv7.mjs +44 -0
- package/hub/middleware/request-logger.mjs +80 -0
- package/hub/pipe.mjs +1 -1
- package/hub/platform.mjs +58 -19
- package/hub/reflexion.mjs +303 -38
- package/hub/router.mjs +1 -1
- package/hub/schema.sql +2 -0
- package/hub/server.mjs +1218 -1112
- package/hub/session-fingerprint.mjs +352 -0
- package/hub/store-adapter.mjs +88 -584
- package/hub/store.mjs +857 -820
- package/hub/team/backend.mjs +3 -5
- package/hub/team/cli/services/hub-client.mjs +38 -19
- package/hub/team/cli/services/native-control.mjs +4 -5
- package/hub/team/conductor.mjs +662 -0
- package/hub/team/event-log.mjs +76 -0
- package/hub/team/headless.mjs +89 -30
- package/hub/team/health-probe.mjs +272 -0
- package/hub/team/launcher-template.mjs +94 -0
- package/hub/team/lead-control.mjs +104 -0
- package/hub/team/notify.mjs +293 -0
- package/hub/team/pane.mjs +4 -5
- package/hub/team/process-cleanup.mjs +342 -0
- package/hub/team/remote-probe.mjs +276 -0
- package/hub/team/remote-watcher.mjs +478 -0
- package/hub/team/session-sync.mjs +169 -0
- package/hub/team/tui-remote-adapter.mjs +393 -0
- package/hub/team/tui.mjs +206 -2
- package/hub/team-bridge.mjs +25 -0
- package/hub/tools.mjs +1 -1
- package/hud/constants.mjs +7 -0
- package/hud/context-monitor.mjs +397 -0
- package/hud/hud-qos-status.mjs +8 -4
- package/hud/providers/claude.mjs +5 -0
- package/hud/renderers.mjs +13 -9
- package/package.json +15 -5
- package/scripts/__tests__/gen-skill-docs.test.mjs +87 -0
- package/scripts/__tests__/skill-template.test.mjs +104 -0
- package/scripts/cache-warmup.mjs +3 -3
- package/scripts/claudemd-sync.mjs +103 -0
- package/scripts/gen-skill-docs.mjs +110 -0
- package/scripts/lib/claudemd-manager.mjs +325 -0
- package/scripts/lib/claudemd-scanner.mjs +218 -0
- package/scripts/lib/env-probe.mjs +95 -14
- package/scripts/lib/handoff.mjs +171 -0
- package/scripts/lib/skill-template.mjs +222 -0
- package/scripts/notion-read.mjs +5 -3
- package/scripts/pack.mjs +205 -0
- package/scripts/preflight-cache.mjs +6 -5
- package/scripts/remote-spawn.mjs +5 -5
- package/scripts/setup.mjs +317 -54
- package/scripts/templates/claudemd-tfx-section.md +54 -0
- package/scripts/test-lock.mjs +71 -0
- package/skills/_templates/base.md +9 -0
- package/skills/_templates/deep.md +6 -0
- package/skills/tfx-codex-swarm/SKILL.md +55 -11
- /package/hub/{team/codex-compat.mjs → codex-compat.mjs} +0 -0
package/bin/triflux.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// triflux CLI — setup, doctor, version
|
|
3
3
|
import { copyFileSync, existsSync, readFileSync, readSync, writeFileSync, mkdirSync, chmodSync, readdirSync, unlinkSync, statSync, openSync, closeSync } from "fs";
|
|
4
|
-
import { join, dirname, basename } from "path";
|
|
4
|
+
import { join, dirname, basename, resolve } from "path";
|
|
5
5
|
import { homedir, tmpdir } from "os";
|
|
6
6
|
import { execSync, execFileSync, spawn } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
@@ -11,6 +11,7 @@ import { detectMultiplexer, getSessionAttachedCount, killSession, listSessions,
|
|
|
11
11
|
import { forceCleanupTeam } from "../hub/team/nativeProxy.mjs";
|
|
12
12
|
import { cleanupStaleOmcTeams, inspectStaleOmcTeams } from "../hub/team/staleState.mjs";
|
|
13
13
|
import { getPipelineStateDbPath } from "../hub/pipeline/state.mjs";
|
|
14
|
+
import { serializeHandoff } from "../scripts/lib/handoff.mjs";
|
|
14
15
|
import { ensureGeminiProfiles } from "../scripts/lib/gemini-profiles.mjs";
|
|
15
16
|
import { probePsmuxSupport, formatPsmuxInstallGuidance, formatPsmuxUpdateGuidance } from "../scripts/lib/psmux-info.mjs";
|
|
16
17
|
import {
|
|
@@ -28,7 +29,13 @@ import {
|
|
|
28
29
|
extractManagedHookFilename, getManagedRegistryHooks, ensureHooksInSettings,
|
|
29
30
|
ensureCodexHubServerConfig,
|
|
30
31
|
} from "../scripts/setup.mjs";
|
|
32
|
+
import {
|
|
33
|
+
ensureGlobalClaudeRoutingSection,
|
|
34
|
+
ensureTfxSection,
|
|
35
|
+
getLatestRoutingTable,
|
|
36
|
+
} from "../scripts/claudemd-sync.mjs";
|
|
31
37
|
import { cleanupTmpFiles } from "../scripts/tmp-cleanup.mjs";
|
|
38
|
+
import { checkNetworkAvailability, validateRuntimeCachePaths } from "../hub/lib/cache-guard.mjs";
|
|
32
39
|
|
|
33
40
|
const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
34
41
|
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
@@ -98,6 +105,17 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
98
105
|
{ name: "--json", type: "boolean", description: "버전 정보를 JSON으로 출력" },
|
|
99
106
|
],
|
|
100
107
|
},
|
|
108
|
+
handoff: {
|
|
109
|
+
usage: "tfx handoff [--target local|remote] [--decision <text>] [--decision-file <path>] [--output <path>] [--json]",
|
|
110
|
+
description: "현재 작업 컨텍스트를 세션 핸드오프 프롬프트로 직렬화",
|
|
111
|
+
options: [
|
|
112
|
+
{ name: "--target", type: "string", description: "주입 대상 (local|remote, 기본값 remote)" },
|
|
113
|
+
{ name: "--decision", type: "string", description: "핸드오프 결정사항 (반복 지정 가능)" },
|
|
114
|
+
{ name: "--decision-file", type: "string", description: "결정사항 파일 (라인/불릿 단위)" },
|
|
115
|
+
{ name: "--output", type: "string", description: "생성한 핸드오프 프롬프트 저장 경로" },
|
|
116
|
+
{ name: "--json", type: "boolean", description: "핸드오프 결과를 JSON으로 출력" },
|
|
117
|
+
],
|
|
118
|
+
},
|
|
101
119
|
list: {
|
|
102
120
|
usage: "tfx list [--json]",
|
|
103
121
|
description: "패키지 스킬과 사용자 스킬 목록 표시",
|
|
@@ -522,6 +540,26 @@ function describeSyncAction(src, dst, label) {
|
|
|
522
540
|
};
|
|
523
541
|
}
|
|
524
542
|
|
|
543
|
+
function syncClaudeRoutingSectionsForCli() {
|
|
544
|
+
try {
|
|
545
|
+
const routingTable = getLatestRoutingTable();
|
|
546
|
+
return [
|
|
547
|
+
ensureTfxSection(join(PKG_ROOT, "CLAUDE.md"), routingTable),
|
|
548
|
+
ensureGlobalClaudeRoutingSection(CLAUDE_DIR),
|
|
549
|
+
];
|
|
550
|
+
} catch (error) {
|
|
551
|
+
const reason = error instanceof Error ? error.message : "routing_sync_failed";
|
|
552
|
+
return [{ action: "unchanged", path: join(PKG_ROOT, "CLAUDE.md"), skipped: true, reason }];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function getClaudeRoutingSyncSummary(results) {
|
|
557
|
+
return results.reduce((summary, result) => ({
|
|
558
|
+
changed: summary.changed + (result.action === "created" || result.action === "updated" ? 1 : 0),
|
|
559
|
+
skipped: summary.skipped + (result.skipped ? 1 : 0),
|
|
560
|
+
}), { changed: 0, skipped: 0 });
|
|
561
|
+
}
|
|
562
|
+
|
|
525
563
|
// ── 크로스 셸 진단 ──
|
|
526
564
|
|
|
527
565
|
function checkCliCrossShell(cmd, installHint) {
|
|
@@ -691,7 +729,7 @@ function buildSetupDryRunPlan() {
|
|
|
691
729
|
}
|
|
692
730
|
|
|
693
731
|
function cmdSetup(options = {}) {
|
|
694
|
-
const { dryRun = false, overrideVersion } = options;
|
|
732
|
+
const { dryRun = false, overrideVersion, skipClaudeMdSync = false } = options;
|
|
695
733
|
if (dryRun) {
|
|
696
734
|
printJson(buildSetupDryRunPlan());
|
|
697
735
|
return;
|
|
@@ -781,6 +819,21 @@ function cmdSetup(options = {}) {
|
|
|
781
819
|
// ── 결과 추적 ──
|
|
782
820
|
const summary = [];
|
|
783
821
|
|
|
822
|
+
if (!skipClaudeMdSync) {
|
|
823
|
+
const claudeRoutingResults = syncClaudeRoutingSectionsForCli();
|
|
824
|
+
const claudeRoutingSummary = getClaudeRoutingSyncSummary(claudeRoutingResults);
|
|
825
|
+
if (claudeRoutingSummary.changed > 0) {
|
|
826
|
+
ok(`CLAUDE.md 라우팅: ${claudeRoutingSummary.changed}개 파일 반영`);
|
|
827
|
+
summary.push({ item: "CLAUDE.md 라우팅", status: "✅", detail: `${claudeRoutingSummary.changed}개 파일 반영` });
|
|
828
|
+
} else if (claudeRoutingSummary.skipped > 0) {
|
|
829
|
+
ok("CLAUDE.md 라우팅: 대상 파일 없음 (건너뜀)");
|
|
830
|
+
summary.push({ item: "CLAUDE.md 라우팅", status: "⏭️", detail: "대상 파일 없음" });
|
|
831
|
+
} else {
|
|
832
|
+
ok("CLAUDE.md 라우팅: 최신 상태");
|
|
833
|
+
summary.push({ item: "CLAUDE.md 라우팅", status: "✅", detail: "최신 상태" });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
784
837
|
const codexProfileResult = ensureCodexProfiles();
|
|
785
838
|
if (!codexProfileResult.ok) {
|
|
786
839
|
warn(`Codex profiles 설정 실패: ${codexProfileResult.message}`);
|
|
@@ -2434,7 +2487,56 @@ async function cmdDoctor(options = {}) {
|
|
|
2434
2487
|
});
|
|
2435
2488
|
}
|
|
2436
2489
|
|
|
2437
|
-
function
|
|
2490
|
+
function normalizeRemoteReachabilityUrl(remoteUrl) {
|
|
2491
|
+
if (!remoteUrl) return null;
|
|
2492
|
+
if (/^https?:\/\//iu.test(remoteUrl)) {
|
|
2493
|
+
try {
|
|
2494
|
+
return new URL(remoteUrl).origin;
|
|
2495
|
+
} catch {
|
|
2496
|
+
return null;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
const scpMatch = /^git@([^:]+):/iu.exec(remoteUrl);
|
|
2500
|
+
if (scpMatch) return `https://${scpMatch[1]}`;
|
|
2501
|
+
if (/^ssh:\/\//iu.test(remoteUrl)) {
|
|
2502
|
+
try {
|
|
2503
|
+
return `https://${new URL(remoteUrl).hostname}`;
|
|
2504
|
+
} catch {
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
return null;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
function resolveGitUpdateUrl(repoDir) {
|
|
2512
|
+
try {
|
|
2513
|
+
const remoteUrl = execSync("git remote get-url origin", {
|
|
2514
|
+
encoding: "utf8",
|
|
2515
|
+
timeout: 10_000,
|
|
2516
|
+
cwd: repoDir,
|
|
2517
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
2518
|
+
windowsHide: true,
|
|
2519
|
+
}).trim();
|
|
2520
|
+
return normalizeRemoteReachabilityUrl(remoteUrl);
|
|
2521
|
+
} catch {
|
|
2522
|
+
return null;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
|
|
2526
|
+
function resolveUpdateTargets({ installMode, pluginPath }) {
|
|
2527
|
+
const repoDir = installMode === "plugin" ? (pluginPath || PKG_ROOT) : PKG_ROOT;
|
|
2528
|
+
const gitUrl = resolveGitUpdateUrl(repoDir);
|
|
2529
|
+
|
|
2530
|
+
if (installMode === "npm-global" || installMode === "npm-local") {
|
|
2531
|
+
return ["https://registry.npmjs.org/triflux"];
|
|
2532
|
+
}
|
|
2533
|
+
if (installMode === "plugin" || installMode === "git-local") {
|
|
2534
|
+
return gitUrl ? [gitUrl] : ["https://github.com"];
|
|
2535
|
+
}
|
|
2536
|
+
return [];
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
async function cmdUpdate() {
|
|
2438
2540
|
const isDev = isDevUpdateRequested(NORMALIZED_ARGS);
|
|
2439
2541
|
const tagLabel = isDev ? ` ${YELLOW}--dev${RESET}` : "";
|
|
2440
2542
|
console.log(`\n${BOLD}triflux update${RESET}${tagLabel}\n`);
|
|
@@ -2490,6 +2592,27 @@ function cmdUpdate() {
|
|
|
2490
2592
|
|
|
2491
2593
|
info(`검색: ${installMode === "plugin" ? "플러그인" : installMode === "npm-global" ? "npm global" : installMode === "npm-local" ? "npm local" : installMode === "git-local" ? "git 로컬 저장소" : "알 수 없음"} 설치 감지`);
|
|
2492
2594
|
|
|
2595
|
+
const networkTargets = resolveUpdateTargets({ installMode, pluginPath });
|
|
2596
|
+
if (networkTargets.length > 0) {
|
|
2597
|
+
const networkStatus = await checkNetworkAvailability(networkTargets);
|
|
2598
|
+
if (!networkStatus.online) {
|
|
2599
|
+
fail(`네트워크 확인 실패: ${networkStatus.unreachable.join(", ")}`);
|
|
2600
|
+
info("네트워크 연결을 확인한 뒤 다시 시도하세요.");
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
ok(`네트워크 확인 완료 (${networkStatus.reachable.join(", ")})`);
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
const cacheValidation = validateRuntimeCachePaths(join(CLAUDE_DIR, "cache"));
|
|
2607
|
+
if (!cacheValidation.ok) {
|
|
2608
|
+
warn(`런타임 캐시 검증 이슈 ${cacheValidation.issues.length}건 발견`);
|
|
2609
|
+
for (const issue of cacheValidation.issues) {
|
|
2610
|
+
info(`${issue.file}: ${issue.error}`);
|
|
2611
|
+
}
|
|
2612
|
+
} else {
|
|
2613
|
+
ok("런타임 캐시 검증 완료");
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2493
2616
|
// 2. 설치 방식에 따라 업데이트
|
|
2494
2617
|
const oldVer = PKG.version;
|
|
2495
2618
|
let updated = false;
|
|
@@ -2654,9 +2777,23 @@ function cmdUpdate() {
|
|
|
2654
2777
|
}
|
|
2655
2778
|
}
|
|
2656
2779
|
|
|
2780
|
+
// ── Post-update: CLAUDE.md 라우팅 동기화 ──
|
|
2781
|
+
console.log(`\n${CYAN}── CLAUDE.md 라우팅 동기화 ──${RESET}`);
|
|
2782
|
+
{
|
|
2783
|
+
const claudeRoutingResults = syncClaudeRoutingSectionsForCli();
|
|
2784
|
+
const claudeRoutingSummary = getClaudeRoutingSyncSummary(claudeRoutingResults);
|
|
2785
|
+
if (claudeRoutingSummary.changed > 0) {
|
|
2786
|
+
ok(`CLAUDE.md 라우팅 ${claudeRoutingSummary.changed}개 파일 반영`);
|
|
2787
|
+
} else if (claudeRoutingSummary.skipped > 0) {
|
|
2788
|
+
ok("CLAUDE.md 라우팅 대상 파일 없음 (건너뜀)");
|
|
2789
|
+
} else {
|
|
2790
|
+
ok("CLAUDE.md 라우팅 최신 상태");
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2657
2794
|
// ── Post-update: 설정 동기화 ──
|
|
2658
2795
|
console.log(`\n${CYAN}── 설정 동기화 ──${RESET}`);
|
|
2659
|
-
cmdSetup({ fromUpdate: true, overrideVersion: newVer });
|
|
2796
|
+
cmdSetup({ fromUpdate: true, overrideVersion: newVer, skipClaudeMdSync: true });
|
|
2660
2797
|
|
|
2661
2798
|
// ── Post-update: 훅 오케스트레이터 적용 ──
|
|
2662
2799
|
{
|
|
@@ -2783,6 +2920,128 @@ function cmdVersion(options = {}) {
|
|
|
2783
2920
|
console.log("");
|
|
2784
2921
|
}
|
|
2785
2922
|
|
|
2923
|
+
function cmdHandoff(args = [], options = {}) {
|
|
2924
|
+
const { json = false } = options;
|
|
2925
|
+
const parsed = {
|
|
2926
|
+
target: "remote",
|
|
2927
|
+
decisions: [],
|
|
2928
|
+
decisionFile: null,
|
|
2929
|
+
output: null,
|
|
2930
|
+
cwd: process.cwd(),
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2934
|
+
const arg = args[index];
|
|
2935
|
+
const next = args[index + 1];
|
|
2936
|
+
|
|
2937
|
+
if (arg === "--target") {
|
|
2938
|
+
if (!next || next.startsWith("-")) {
|
|
2939
|
+
throw createCliError("--target 값이 필요합니다 (local|remote)", {
|
|
2940
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2941
|
+
reason: "argError",
|
|
2942
|
+
fix: "tfx handoff --target remote",
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2945
|
+
if (!["local", "remote"].includes(next)) {
|
|
2946
|
+
throw createCliError(`지원하지 않는 --target 값: ${next}`, {
|
|
2947
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2948
|
+
reason: "argError",
|
|
2949
|
+
fix: "tfx handoff --target local|remote",
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
parsed.target = next;
|
|
2953
|
+
index += 1;
|
|
2954
|
+
continue;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
if (arg === "--decision") {
|
|
2958
|
+
if (!next || next.startsWith("-")) {
|
|
2959
|
+
throw createCliError("--decision 값이 필요합니다", {
|
|
2960
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2961
|
+
reason: "argError",
|
|
2962
|
+
fix: "tfx handoff --decision \"결정사항\"",
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
parsed.decisions.push(next);
|
|
2966
|
+
index += 1;
|
|
2967
|
+
continue;
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
if (arg === "--decision-file") {
|
|
2971
|
+
if (!next || next.startsWith("-")) {
|
|
2972
|
+
throw createCliError("--decision-file 경로가 필요합니다", {
|
|
2973
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2974
|
+
reason: "argError",
|
|
2975
|
+
fix: "tfx handoff --decision-file .omx/notepad.md",
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
parsed.decisionFile = resolve(next);
|
|
2979
|
+
index += 1;
|
|
2980
|
+
continue;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
if (arg === "--output" || arg === "--out") {
|
|
2984
|
+
if (!next || next.startsWith("-")) {
|
|
2985
|
+
throw createCliError(`${arg} 경로가 필요합니다`, {
|
|
2986
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2987
|
+
reason: "argError",
|
|
2988
|
+
fix: "tfx handoff --output .omx/handoff.md",
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
parsed.output = resolve(next);
|
|
2992
|
+
index += 1;
|
|
2993
|
+
continue;
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
if (arg === "--cwd") {
|
|
2997
|
+
if (!next || next.startsWith("-")) {
|
|
2998
|
+
throw createCliError("--cwd 경로가 필요합니다", {
|
|
2999
|
+
exitCode: EXIT_ARG_ERROR,
|
|
3000
|
+
reason: "argError",
|
|
3001
|
+
fix: "tfx handoff --cwd <project-path>",
|
|
3002
|
+
});
|
|
3003
|
+
}
|
|
3004
|
+
parsed.cwd = resolve(next);
|
|
3005
|
+
index += 1;
|
|
3006
|
+
continue;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
throw createCliError(`알 수 없는 handoff 옵션: ${arg}`, {
|
|
3010
|
+
exitCode: EXIT_ARG_ERROR,
|
|
3011
|
+
reason: "argError",
|
|
3012
|
+
fix: "tfx handoff --target remote --output .omx/handoff.md",
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
const result = serializeHandoff({
|
|
3017
|
+
target: parsed.target,
|
|
3018
|
+
decisions: parsed.decisions,
|
|
3019
|
+
decisionFile: parsed.decisionFile,
|
|
3020
|
+
cwd: parsed.cwd,
|
|
3021
|
+
});
|
|
3022
|
+
|
|
3023
|
+
if (parsed.output) {
|
|
3024
|
+
const outputDir = dirname(parsed.output);
|
|
3025
|
+
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
|
|
3026
|
+
writeFileSync(parsed.output, `${result.prompt}\n`, "utf8");
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
if (json) {
|
|
3030
|
+
printJson({
|
|
3031
|
+
handoff: {
|
|
3032
|
+
...result,
|
|
3033
|
+
...(parsed.output ? { output: parsed.output } : {}),
|
|
3034
|
+
},
|
|
3035
|
+
});
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
console.log(result.prompt);
|
|
3040
|
+
if (parsed.output) {
|
|
3041
|
+
console.log(`\n${DIM}saved:${RESET} ${parsed.output}`);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
|
|
2786
3045
|
function cmdSchema(args = []) {
|
|
2787
3046
|
const bundle = loadDelegatorSchemaBundle();
|
|
2788
3047
|
const selector = String(args[0] || "").trim();
|
|
@@ -3054,6 +3313,7 @@ ${updateNotice}
|
|
|
3054
3313
|
${WHITE_BRIGHT}tfx update${RESET} ${GRAY}최신 안정 버전으로 업데이트${RESET}
|
|
3055
3314
|
${DIM} --dev / dev${RESET} ${GRAY}dev 태그로 업데이트${RESET}
|
|
3056
3315
|
${WHITE_BRIGHT}tfx list${RESET} ${GRAY}설치된 스킬 목록${RESET}
|
|
3316
|
+
${WHITE_BRIGHT}tfx handoff${RESET} ${GRAY}현재 컨텍스트를 원격/로컬 핸드오프 프롬프트로 생성${RESET}
|
|
3057
3317
|
${WHITE_BRIGHT}tfx schema${RESET} ${GRAY}CLI/Hub schema JSON 출력${RESET}
|
|
3058
3318
|
${WHITE_BRIGHT}tfx hub${RESET} ${GRAY}MCP 메시지 버스 관리 (start/stop/status)${RESET}
|
|
3059
3319
|
${WHITE_BRIGHT}tfx tray${RESET} ${GRAY}Windows 시스템 트레이 실행${RESET}
|
|
@@ -3553,12 +3813,15 @@ async function main() {
|
|
|
3553
3813
|
cmdSchema(cmdArgs);
|
|
3554
3814
|
return;
|
|
3555
3815
|
case "update":
|
|
3556
|
-
cmdUpdate();
|
|
3816
|
+
await cmdUpdate();
|
|
3557
3817
|
return;
|
|
3558
3818
|
case "list":
|
|
3559
3819
|
case "ls":
|
|
3560
3820
|
cmdList({ json: JSON_OUTPUT });
|
|
3561
3821
|
return;
|
|
3822
|
+
case "handoff":
|
|
3823
|
+
cmdHandoff(cmdArgs, { json: JSON_OUTPUT });
|
|
3824
|
+
return;
|
|
3562
3825
|
case "hub":
|
|
3563
3826
|
await cmdHub(cmdArgs, { json: JSON_OUTPUT && (cmdArgs[0] || "status") === "status" });
|
|
3564
3827
|
return;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import { createAdaptiveEngine } from '../hub/adaptive.mjs';
|
|
8
|
+
|
|
9
|
+
let engine = null;
|
|
10
|
+
let createEngine = createAdaptiveEngine;
|
|
11
|
+
|
|
12
|
+
function readStdin() {
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(0, 'utf8');
|
|
15
|
+
} catch {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function inferProjectSlug(cwd = process.cwd()) {
|
|
21
|
+
const packagePath = join(cwd, 'package.json');
|
|
22
|
+
if (existsSync(packagePath)) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
25
|
+
if (typeof pkg.name === 'string' && pkg.name.trim()) return pkg.name.trim();
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
return basename(cwd) || 'default';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getEngine() {
|
|
32
|
+
if (engine) return engine;
|
|
33
|
+
engine = createEngine({
|
|
34
|
+
projectSlug: inferProjectSlug(),
|
|
35
|
+
repoRoot: process.cwd(),
|
|
36
|
+
});
|
|
37
|
+
engine.startSession?.();
|
|
38
|
+
return engine;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildErrorContext(event = {}) {
|
|
42
|
+
return {
|
|
43
|
+
exitCode: event.exitCode,
|
|
44
|
+
stderr: String(event.stderr || '').slice(0, 500),
|
|
45
|
+
tool: event.tool,
|
|
46
|
+
command: String(event.command || '').slice(0, 200),
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function hookAdaptiveCollector(event = {}) {
|
|
52
|
+
if (Number(event.exitCode) === 0) return null;
|
|
53
|
+
if (!event.tool || event.tool === 'Read') return null;
|
|
54
|
+
|
|
55
|
+
const result = getEngine().handleError(buildErrorContext(event));
|
|
56
|
+
if (result?.diagnosed) {
|
|
57
|
+
console.error(`[adaptive] 에러 패턴 감지: ${result.rule?.id || 'unknown'}`);
|
|
58
|
+
if (result.promoted) {
|
|
59
|
+
console.error(`[adaptive] 규칙 승격 → Tier ${result.rule?.tier ?? '?'}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function __setAdaptiveCollectorFactoryForTests(factory) {
|
|
66
|
+
createEngine = factory;
|
|
67
|
+
engine = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function __resetAdaptiveCollectorForTests() {
|
|
71
|
+
createEngine = createAdaptiveEngine;
|
|
72
|
+
engine = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
const raw = readStdin();
|
|
77
|
+
if (!raw.trim()) return;
|
|
78
|
+
try {
|
|
79
|
+
hookAdaptiveCollector(JSON.parse(raw));
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isEntrypoint = process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url;
|
|
84
|
+
if (isEntrypoint) {
|
|
85
|
+
main();
|
|
86
|
+
}
|
package/hooks/hook-registry.json
CHANGED
|
@@ -153,9 +153,9 @@
|
|
|
153
153
|
"id": "ext-session-vault-start",
|
|
154
154
|
"source": "session-vault",
|
|
155
155
|
"matcher": "*",
|
|
156
|
-
"command": "${HOME}/Desktop/Projects/
|
|
156
|
+
"command": "bash \"${HOME}/Desktop/Projects/tools/session-vault/scripts/start_hook.sh\"",
|
|
157
157
|
"priority": 100,
|
|
158
|
-
"enabled":
|
|
158
|
+
"enabled": true,
|
|
159
159
|
"timeout": 10,
|
|
160
160
|
"blocking": false,
|
|
161
161
|
"description": "세션 볼트 로깅 시작"
|
|
@@ -177,9 +177,9 @@
|
|
|
177
177
|
"id": "ext-session-vault-export",
|
|
178
178
|
"source": "session-vault",
|
|
179
179
|
"matcher": "*",
|
|
180
|
-
"command": "${HOME}/Desktop/Projects/
|
|
180
|
+
"command": "bash \"${HOME}/Desktop/Projects/tools/session-vault/scripts/export_hook.sh\"",
|
|
181
181
|
"priority": 100,
|
|
182
|
-
"enabled":
|
|
182
|
+
"enabled": true,
|
|
183
183
|
"timeout": 30,
|
|
184
184
|
"blocking": false,
|
|
185
185
|
"description": "세션 트랜스크립트 내보내기"
|
package/hooks/keyword-rules.json
CHANGED
|
@@ -37,6 +37,46 @@
|
|
|
37
37
|
"state": null,
|
|
38
38
|
"mcp_route": null
|
|
39
39
|
},
|
|
40
|
+
{
|
|
41
|
+
"id": "tfx-codex-swarm",
|
|
42
|
+
"patterns": [
|
|
43
|
+
{
|
|
44
|
+
"source": "\\btfx[\\s-]?codex[\\s-]?swarm\\b",
|
|
45
|
+
"flags": "i"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"source": "\\bcodex[\\s-]?swarm\\b",
|
|
49
|
+
"flags": "i"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"skill": "tfx-codex-swarm",
|
|
53
|
+
"priority": 1,
|
|
54
|
+
"supersedes": [
|
|
55
|
+
"tfx-codex"
|
|
56
|
+
],
|
|
57
|
+
"exclusive": false,
|
|
58
|
+
"state": null,
|
|
59
|
+
"mcp_route": null
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "tfx-remote-spawn",
|
|
63
|
+
"patterns": [
|
|
64
|
+
{
|
|
65
|
+
"source": "\\btfx[\\s-]?remote[\\s-]?spawn\\b",
|
|
66
|
+
"flags": "i"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"source": "\\bremote[\\s-]?spawn\\b",
|
|
70
|
+
"flags": "i"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"skill": "tfx-remote-spawn",
|
|
74
|
+
"priority": 1,
|
|
75
|
+
"supersedes": [],
|
|
76
|
+
"exclusive": false,
|
|
77
|
+
"state": null,
|
|
78
|
+
"mcp_route": null
|
|
79
|
+
},
|
|
40
80
|
{
|
|
41
81
|
"id": "tfx-auto-codex",
|
|
42
82
|
"patterns": [
|
|
@@ -74,7 +114,7 @@
|
|
|
74
114
|
"id": "tfx-codex",
|
|
75
115
|
"patterns": [
|
|
76
116
|
{
|
|
77
|
-
"source": "\\btfx[\\s-]?codex\\b",
|
|
117
|
+
"source": "\\btfx[\\s-]?codex\\b(?![\\s-]?swarm)",
|
|
78
118
|
"flags": "i"
|
|
79
119
|
}
|
|
80
120
|
],
|
|
@@ -373,6 +413,154 @@
|
|
|
373
413
|
"state": null,
|
|
374
414
|
"mcp_route": "codex"
|
|
375
415
|
},
|
|
416
|
+
{
|
|
417
|
+
"id": "gstack-checkpoint",
|
|
418
|
+
"patterns": [
|
|
419
|
+
{
|
|
420
|
+
"source": "(?:어디까지\\s*했|뭐\\s*하고\\s*있었|뭘\\s*해야\\s*하|까먹|기억이\\s*안|이어서\\s*하|중단\\s*지점)",
|
|
421
|
+
"flags": "i"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"source": "\\b(?:checkpoint|resume|pick\\s*up|where\\s*was\\s*I)\\b",
|
|
425
|
+
"flags": "i"
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
"skill": "checkpoint",
|
|
429
|
+
"priority": 5,
|
|
430
|
+
"supersedes": [],
|
|
431
|
+
"exclusive": false,
|
|
432
|
+
"state": null,
|
|
433
|
+
"mcp_route": null
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"id": "gstack-office-hours",
|
|
437
|
+
"patterns": [
|
|
438
|
+
{
|
|
439
|
+
"source": "(?:아이디어\\s*(?:있|정리|브레인)|뭘\\s*만들|제품\\s*구상)",
|
|
440
|
+
"flags": "i"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"source": "\\boffice[\\s-]?hours\\b",
|
|
444
|
+
"flags": "i"
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
"skill": "office-hours",
|
|
448
|
+
"priority": 5,
|
|
449
|
+
"supersedes": [],
|
|
450
|
+
"exclusive": false,
|
|
451
|
+
"state": null,
|
|
452
|
+
"mcp_route": null
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"id": "gstack-ship",
|
|
456
|
+
"patterns": [
|
|
457
|
+
{
|
|
458
|
+
"source": "(?:배포해|PR\\s*만들|릴리스\\s*해|머지하고\\s*배포)",
|
|
459
|
+
"flags": "i"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"source": "\\bship\\b(?!.*tfx)",
|
|
463
|
+
"flags": "i"
|
|
464
|
+
}
|
|
465
|
+
],
|
|
466
|
+
"skill": "ship",
|
|
467
|
+
"priority": 5,
|
|
468
|
+
"supersedes": [],
|
|
469
|
+
"exclusive": false,
|
|
470
|
+
"state": null,
|
|
471
|
+
"mcp_route": null
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"id": "gstack-investigate",
|
|
475
|
+
"patterns": [
|
|
476
|
+
{
|
|
477
|
+
"source": "(?:왜\\s*(?:안\\s*돼|터져|에러)|원인\\s*(?:찾아|분석)|root\\s*cause)",
|
|
478
|
+
"flags": "i"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"source": "\\binvestigate\\b",
|
|
482
|
+
"flags": "i"
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
"skill": "investigate",
|
|
486
|
+
"priority": 5,
|
|
487
|
+
"supersedes": [],
|
|
488
|
+
"exclusive": false,
|
|
489
|
+
"state": null,
|
|
490
|
+
"mcp_route": null
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
"id": "gstack-cso",
|
|
494
|
+
"patterns": [
|
|
495
|
+
{
|
|
496
|
+
"source": "(?:보안\\s*(?:감사|점검|리뷰|스캔)|\\bcso\\b|\\bOWASP\\b|\\bSTRIDE\\b)",
|
|
497
|
+
"flags": "i"
|
|
498
|
+
}
|
|
499
|
+
],
|
|
500
|
+
"skill": "cso",
|
|
501
|
+
"priority": 5,
|
|
502
|
+
"supersedes": [],
|
|
503
|
+
"exclusive": false,
|
|
504
|
+
"state": null,
|
|
505
|
+
"mcp_route": null
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"id": "gstack-qa-browser",
|
|
509
|
+
"patterns": [
|
|
510
|
+
{
|
|
511
|
+
"source": "(?:사이트|브라우저|웹)\\s*(?:QA|테스트|확인|점검)",
|
|
512
|
+
"flags": "i"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"source": "(?:클릭|접속).*(?:테스트|확인)",
|
|
516
|
+
"flags": "i"
|
|
517
|
+
}
|
|
518
|
+
],
|
|
519
|
+
"skill": "qa",
|
|
520
|
+
"priority": 5,
|
|
521
|
+
"supersedes": [],
|
|
522
|
+
"exclusive": false,
|
|
523
|
+
"state": null,
|
|
524
|
+
"mcp_route": null
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"id": "gstack-retro",
|
|
528
|
+
"patterns": [
|
|
529
|
+
{
|
|
530
|
+
"source": "(?:회고|이번\\s*주\\s*뭐\\s*했|주간\\s*리뷰|뭘\\s*했지)",
|
|
531
|
+
"flags": "i"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"source": "\\bretro\\b",
|
|
535
|
+
"flags": "i"
|
|
536
|
+
}
|
|
537
|
+
],
|
|
538
|
+
"skill": "retro",
|
|
539
|
+
"priority": 5,
|
|
540
|
+
"supersedes": [],
|
|
541
|
+
"exclusive": false,
|
|
542
|
+
"state": null,
|
|
543
|
+
"mcp_route": null
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
"id": "gstack-autoplan",
|
|
547
|
+
"patterns": [
|
|
548
|
+
{
|
|
549
|
+
"source": "(?:자동\\s*리뷰|리뷰\\s*파이프라인|전체\\s*리뷰)",
|
|
550
|
+
"flags": "i"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"source": "\\bautoplan\\b",
|
|
554
|
+
"flags": "i"
|
|
555
|
+
}
|
|
556
|
+
],
|
|
557
|
+
"skill": "autoplan",
|
|
558
|
+
"priority": 5,
|
|
559
|
+
"supersedes": [],
|
|
560
|
+
"exclusive": false,
|
|
561
|
+
"state": null,
|
|
562
|
+
"mcp_route": null
|
|
563
|
+
},
|
|
376
564
|
{
|
|
377
565
|
"id": "suppress-omc-team",
|
|
378
566
|
"patterns": [
|