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.
Files changed (75) hide show
  1. package/bin/triflux.mjs +268 -5
  2. package/hooks/hook-adaptive-collector.mjs +86 -0
  3. package/hooks/hook-registry.json +4 -4
  4. package/hooks/keyword-rules.json +189 -1
  5. package/hooks/safety-guard.mjs +16 -0
  6. package/hub/account-broker.mjs +242 -0
  7. package/hub/adaptive-diagnostic.mjs +319 -0
  8. package/hub/adaptive-inject.mjs +186 -0
  9. package/hub/adaptive-memory.mjs +322 -0
  10. package/hub/adaptive.mjs +143 -0
  11. package/hub/cli-adapter-base.mjs +192 -0
  12. package/hub/codex-adapter.mjs +191 -0
  13. package/hub/codex-preflight.mjs +147 -0
  14. package/hub/gemini-adapter.mjs +180 -0
  15. package/hub/lib/cache-guard.mjs +114 -0
  16. package/hub/lib/known-errors.json +72 -0
  17. package/hub/lib/memory-store.mjs +748 -0
  18. package/hub/lib/ssh-command.mjs +150 -0
  19. package/hub/lib/uuidv7.mjs +44 -0
  20. package/hub/middleware/request-logger.mjs +80 -0
  21. package/hub/pipe.mjs +1 -1
  22. package/hub/platform.mjs +58 -19
  23. package/hub/reflexion.mjs +303 -38
  24. package/hub/router.mjs +1 -1
  25. package/hub/schema.sql +2 -0
  26. package/hub/server.mjs +1218 -1112
  27. package/hub/session-fingerprint.mjs +352 -0
  28. package/hub/store-adapter.mjs +88 -584
  29. package/hub/store.mjs +857 -820
  30. package/hub/team/backend.mjs +3 -5
  31. package/hub/team/cli/services/hub-client.mjs +38 -19
  32. package/hub/team/cli/services/native-control.mjs +4 -5
  33. package/hub/team/conductor.mjs +662 -0
  34. package/hub/team/event-log.mjs +76 -0
  35. package/hub/team/headless.mjs +89 -30
  36. package/hub/team/health-probe.mjs +272 -0
  37. package/hub/team/launcher-template.mjs +94 -0
  38. package/hub/team/lead-control.mjs +104 -0
  39. package/hub/team/notify.mjs +293 -0
  40. package/hub/team/pane.mjs +4 -5
  41. package/hub/team/process-cleanup.mjs +342 -0
  42. package/hub/team/remote-probe.mjs +276 -0
  43. package/hub/team/remote-watcher.mjs +478 -0
  44. package/hub/team/session-sync.mjs +169 -0
  45. package/hub/team/tui-remote-adapter.mjs +393 -0
  46. package/hub/team/tui.mjs +206 -2
  47. package/hub/team-bridge.mjs +25 -0
  48. package/hub/tools.mjs +1 -1
  49. package/hud/constants.mjs +7 -0
  50. package/hud/context-monitor.mjs +397 -0
  51. package/hud/hud-qos-status.mjs +8 -4
  52. package/hud/providers/claude.mjs +5 -0
  53. package/hud/renderers.mjs +13 -9
  54. package/package.json +15 -5
  55. package/scripts/__tests__/gen-skill-docs.test.mjs +87 -0
  56. package/scripts/__tests__/skill-template.test.mjs +104 -0
  57. package/scripts/cache-warmup.mjs +3 -3
  58. package/scripts/claudemd-sync.mjs +103 -0
  59. package/scripts/gen-skill-docs.mjs +110 -0
  60. package/scripts/lib/claudemd-manager.mjs +325 -0
  61. package/scripts/lib/claudemd-scanner.mjs +218 -0
  62. package/scripts/lib/env-probe.mjs +95 -14
  63. package/scripts/lib/handoff.mjs +171 -0
  64. package/scripts/lib/skill-template.mjs +222 -0
  65. package/scripts/notion-read.mjs +5 -3
  66. package/scripts/pack.mjs +205 -0
  67. package/scripts/preflight-cache.mjs +6 -5
  68. package/scripts/remote-spawn.mjs +5 -5
  69. package/scripts/setup.mjs +317 -54
  70. package/scripts/templates/claudemd-tfx-section.md +54 -0
  71. package/scripts/test-lock.mjs +71 -0
  72. package/skills/_templates/base.md +9 -0
  73. package/skills/_templates/deep.md +6 -0
  74. package/skills/tfx-codex-swarm/SKILL.md +55 -11
  75. /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 cmdUpdate() {
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
+ }
@@ -153,9 +153,9 @@
153
153
  "id": "ext-session-vault-start",
154
154
  "source": "session-vault",
155
155
  "matcher": "*",
156
- "command": "${HOME}/Desktop/Projects/cli/session-vault/scripts/start_hook.sh",
156
+ "command": "bash \"${HOME}/Desktop/Projects/tools/session-vault/scripts/start_hook.sh\"",
157
157
  "priority": 100,
158
- "enabled": false,
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/cli/session-vault/scripts/export_hook.sh",
180
+ "command": "bash \"${HOME}/Desktop/Projects/tools/session-vault/scripts/export_hook.sh\"",
181
181
  "priority": 100,
182
- "enabled": false,
182
+ "enabled": true,
183
183
  "timeout": 30,
184
184
  "blocking": false,
185
185
  "description": "세션 트랜스크립트 내보내기"
@@ -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": [