triflux 10.0.0-alpha.1 → 10.0.0-alpha.2

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 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,14 +11,17 @@ 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 {
17
18
  addRegistryServer,
19
+ createDefaultRegistry,
18
20
  inspectRegistry,
19
21
  inspectRegistryStatus,
20
22
  removeRegistryServer,
21
23
  removeServerFromTargets,
24
+ saveRegistry,
22
25
  syncRegistryTargets,
23
26
  } from "../scripts/lib/mcp-guard-engine.mjs";
24
27
  import {
@@ -28,7 +31,13 @@ import {
28
31
  extractManagedHookFilename, getManagedRegistryHooks, ensureHooksInSettings,
29
32
  ensureCodexHubServerConfig,
30
33
  } from "../scripts/setup.mjs";
34
+ import {
35
+ ensureGlobalClaudeRoutingSection,
36
+ ensureTfxSection,
37
+ getLatestRoutingTable,
38
+ } from "../scripts/claudemd-sync.mjs";
31
39
  import { cleanupTmpFiles } from "../scripts/tmp-cleanup.mjs";
40
+ import { checkNetworkAvailability, validateRuntimeCachePaths } from "../hub/lib/cache-guard.mjs";
32
41
 
33
42
  const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
34
43
  const CLAUDE_DIR = join(homedir(), ".claude");
@@ -98,6 +107,17 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
98
107
  { name: "--json", type: "boolean", description: "버전 정보를 JSON으로 출력" },
99
108
  ],
100
109
  },
110
+ handoff: {
111
+ usage: "tfx handoff [--target local|remote] [--decision <text>] [--decision-file <path>] [--output <path>] [--json]",
112
+ description: "현재 작업 컨텍스트를 세션 핸드오프 프롬프트로 직렬화",
113
+ options: [
114
+ { name: "--target", type: "string", description: "주입 대상 (local|remote, 기본값 remote)" },
115
+ { name: "--decision", type: "string", description: "핸드오프 결정사항 (반복 지정 가능)" },
116
+ { name: "--decision-file", type: "string", description: "결정사항 파일 (라인/불릿 단위)" },
117
+ { name: "--output", type: "string", description: "생성한 핸드오프 프롬프트 저장 경로" },
118
+ { name: "--json", type: "boolean", description: "핸드오프 결과를 JSON으로 출력" },
119
+ ],
120
+ },
101
121
  list: {
102
122
  usage: "tfx list [--json]",
103
123
  description: "패키지 스킬과 사용자 스킬 목록 표시",
@@ -522,6 +542,26 @@ function describeSyncAction(src, dst, label) {
522
542
  };
523
543
  }
524
544
 
545
+ function syncClaudeRoutingSectionsForCli() {
546
+ try {
547
+ const routingTable = getLatestRoutingTable();
548
+ return [
549
+ ensureTfxSection(join(PKG_ROOT, "CLAUDE.md"), routingTable),
550
+ ensureGlobalClaudeRoutingSection(CLAUDE_DIR),
551
+ ];
552
+ } catch (error) {
553
+ const reason = error instanceof Error ? error.message : "routing_sync_failed";
554
+ return [{ action: "unchanged", path: join(PKG_ROOT, "CLAUDE.md"), skipped: true, reason }];
555
+ }
556
+ }
557
+
558
+ function getClaudeRoutingSyncSummary(results) {
559
+ return results.reduce((summary, result) => ({
560
+ changed: summary.changed + (result.action === "created" || result.action === "updated" ? 1 : 0),
561
+ skipped: summary.skipped + (result.skipped ? 1 : 0),
562
+ }), { changed: 0, skipped: 0 });
563
+ }
564
+
525
565
  // ── 크로스 셸 진단 ──
526
566
 
527
567
  function checkCliCrossShell(cmd, installHint) {
@@ -691,7 +731,7 @@ function buildSetupDryRunPlan() {
691
731
  }
692
732
 
693
733
  function cmdSetup(options = {}) {
694
- const { dryRun = false, overrideVersion } = options;
734
+ const { dryRun = false, overrideVersion, skipClaudeMdSync = false } = options;
695
735
  if (dryRun) {
696
736
  printJson(buildSetupDryRunPlan());
697
737
  return;
@@ -781,6 +821,21 @@ function cmdSetup(options = {}) {
781
821
  // ── 결과 추적 ──
782
822
  const summary = [];
783
823
 
824
+ if (!skipClaudeMdSync) {
825
+ const claudeRoutingResults = syncClaudeRoutingSectionsForCli();
826
+ const claudeRoutingSummary = getClaudeRoutingSyncSummary(claudeRoutingResults);
827
+ if (claudeRoutingSummary.changed > 0) {
828
+ ok(`CLAUDE.md 라우팅: ${claudeRoutingSummary.changed}개 파일 반영`);
829
+ summary.push({ item: "CLAUDE.md 라우팅", status: "✅", detail: `${claudeRoutingSummary.changed}개 파일 반영` });
830
+ } else if (claudeRoutingSummary.skipped > 0) {
831
+ ok("CLAUDE.md 라우팅: 대상 파일 없음 (건너뜀)");
832
+ summary.push({ item: "CLAUDE.md 라우팅", status: "⏭️", detail: "대상 파일 없음" });
833
+ } else {
834
+ ok("CLAUDE.md 라우팅: 최신 상태");
835
+ summary.push({ item: "CLAUDE.md 라우팅", status: "✅", detail: "최신 상태" });
836
+ }
837
+ }
838
+
784
839
  const codexProfileResult = ensureCodexProfiles();
785
840
  if (!codexProfileResult.ok) {
786
841
  warn(`Codex profiles 설정 실패: ${codexProfileResult.message}`);
@@ -1128,13 +1183,10 @@ function buildMcpStatusRows(statusInfo) {
1128
1183
  }
1129
1184
 
1130
1185
  function ensureValidRegistryState() {
1131
- const registryState = inspectRegistry();
1186
+ let registryState = inspectRegistry();
1132
1187
  if (!registryState.exists) {
1133
- throw createCliError(`MCP registry missing: ${registryState.path}`, {
1134
- exitCode: EXIT_CONFIG_ERROR,
1135
- reason: "configError",
1136
- fix: "config/mcp-registry.json을 복원하거나 `tfx mcp add <name> --url <url>`로 다시 생성하세요.",
1137
- });
1188
+ saveRegistry(createDefaultRegistry());
1189
+ registryState = inspectRegistry();
1138
1190
  }
1139
1191
  if (!registryState.valid) {
1140
1192
  throw createCliError(`MCP registry invalid: ${registryState.errors.join("; ")}`, {
@@ -1335,9 +1387,13 @@ async function cmdDoctor(options = {}) {
1335
1387
  warn(`MCP registry 자동 동기화 실패: ${error.message}`);
1336
1388
  }
1337
1389
  } else if (registryStateForFix.exists) {
1338
- warn("MCP registry invalid — auto sync 건너뜀");
1390
+ saveRegistry(createDefaultRegistry());
1391
+ report.actions.push({ type: "mcp-registry-reset", status: "ok" });
1392
+ ok("MCP registry 손상 → 기본값으로 재생성됨");
1339
1393
  } else {
1340
- info("MCP registry 없음 — auto sync 건너뜀");
1394
+ saveRegistry(createDefaultRegistry());
1395
+ report.actions.push({ type: "mcp-registry-create", status: "ok" });
1396
+ ok("MCP registry 없음 → 기본값으로 자동 생성됨");
1341
1397
  }
1342
1398
  console.log(`\n ${LINE}`);
1343
1399
  info("수정 완료 — 아래 진단 결과를 확인하세요");
@@ -2157,28 +2213,27 @@ async function cmdDoctor(options = {}) {
2157
2213
  // ── MCP 중앙 레지스트리 ──
2158
2214
  section("MCP Registry");
2159
2215
  {
2160
- const registryState = inspectRegistry();
2216
+ let registryState = inspectRegistry();
2161
2217
  if (!registryState.exists) {
2218
+ saveRegistry(createDefaultRegistry());
2219
+ registryState = inspectRegistry();
2162
2220
  addDoctorCheck(report, {
2163
2221
  name: "mcp-registry",
2164
- status: "missing",
2222
+ status: "fixed",
2165
2223
  path: registryState.path,
2166
- fix: "config/mcp-registry.json을 복원하거나 `tfx mcp add <name> --url <url>`를 실행하세요.",
2224
+ action: "기본값으로 자동 생성됨",
2167
2225
  });
2168
- warn("mcp-registry.json 없음");
2169
- info(`path: ${registryState.path}`);
2170
- issues++;
2226
+ ok("mcp-registry.json 없음 → 기본값으로 자동 생성됨");
2171
2227
  } else if (!registryState.valid) {
2228
+ saveRegistry(createDefaultRegistry());
2229
+ registryState = inspectRegistry();
2172
2230
  addDoctorCheck(report, {
2173
2231
  name: "mcp-registry",
2174
- status: "invalid",
2232
+ status: "fixed",
2175
2233
  path: registryState.path,
2176
- errors: registryState.errors,
2177
- fix: "config/mcp-registry.json 구조를 수정하세요.",
2234
+ action: "손상 감지 → 기본값으로 재생성됨",
2178
2235
  });
2179
- fail("mcp-registry.json invalid");
2180
- for (const entry of registryState.errors) info(entry);
2181
- issues++;
2236
+ warn("mcp-registry.json 손상 → 기본값으로 재생성됨");
2182
2237
  } else {
2183
2238
  const statusInfo = inspectRegistryStatus(registryState.registry);
2184
2239
  const invalidConfigs = statusInfo.configs.filter((config) => config.parseError);
@@ -2434,7 +2489,56 @@ async function cmdDoctor(options = {}) {
2434
2489
  });
2435
2490
  }
2436
2491
 
2437
- function cmdUpdate() {
2492
+ function normalizeRemoteReachabilityUrl(remoteUrl) {
2493
+ if (!remoteUrl) return null;
2494
+ if (/^https?:\/\//iu.test(remoteUrl)) {
2495
+ try {
2496
+ return new URL(remoteUrl).origin;
2497
+ } catch {
2498
+ return null;
2499
+ }
2500
+ }
2501
+ const scpMatch = /^git@([^:]+):/iu.exec(remoteUrl);
2502
+ if (scpMatch) return `https://${scpMatch[1]}`;
2503
+ if (/^ssh:\/\//iu.test(remoteUrl)) {
2504
+ try {
2505
+ return `https://${new URL(remoteUrl).hostname}`;
2506
+ } catch {
2507
+ return null;
2508
+ }
2509
+ }
2510
+ return null;
2511
+ }
2512
+
2513
+ function resolveGitUpdateUrl(repoDir) {
2514
+ try {
2515
+ const remoteUrl = execSync("git remote get-url origin", {
2516
+ encoding: "utf8",
2517
+ timeout: 10_000,
2518
+ cwd: repoDir,
2519
+ stdio: ["pipe", "pipe", "ignore"],
2520
+ windowsHide: true,
2521
+ }).trim();
2522
+ return normalizeRemoteReachabilityUrl(remoteUrl);
2523
+ } catch {
2524
+ return null;
2525
+ }
2526
+ }
2527
+
2528
+ function resolveUpdateTargets({ installMode, pluginPath }) {
2529
+ const repoDir = installMode === "plugin" ? (pluginPath || PKG_ROOT) : PKG_ROOT;
2530
+ const gitUrl = resolveGitUpdateUrl(repoDir);
2531
+
2532
+ if (installMode === "npm-global" || installMode === "npm-local") {
2533
+ return ["https://registry.npmjs.org/triflux"];
2534
+ }
2535
+ if (installMode === "plugin" || installMode === "git-local") {
2536
+ return gitUrl ? [gitUrl] : ["https://github.com"];
2537
+ }
2538
+ return [];
2539
+ }
2540
+
2541
+ async function cmdUpdate() {
2438
2542
  const isDev = isDevUpdateRequested(NORMALIZED_ARGS);
2439
2543
  const tagLabel = isDev ? ` ${YELLOW}--dev${RESET}` : "";
2440
2544
  console.log(`\n${BOLD}triflux update${RESET}${tagLabel}\n`);
@@ -2490,6 +2594,27 @@ function cmdUpdate() {
2490
2594
 
2491
2595
  info(`검색: ${installMode === "plugin" ? "플러그인" : installMode === "npm-global" ? "npm global" : installMode === "npm-local" ? "npm local" : installMode === "git-local" ? "git 로컬 저장소" : "알 수 없음"} 설치 감지`);
2492
2596
 
2597
+ const networkTargets = resolveUpdateTargets({ installMode, pluginPath });
2598
+ if (networkTargets.length > 0) {
2599
+ const networkStatus = await checkNetworkAvailability(networkTargets);
2600
+ if (!networkStatus.online) {
2601
+ fail(`네트워크 확인 실패: ${networkStatus.unreachable.join(", ")}`);
2602
+ info("네트워크 연결을 확인한 뒤 다시 시도하세요.");
2603
+ return;
2604
+ }
2605
+ ok(`네트워크 확인 완료 (${networkStatus.reachable.join(", ")})`);
2606
+ }
2607
+
2608
+ const cacheValidation = validateRuntimeCachePaths(join(CLAUDE_DIR, "cache"));
2609
+ if (!cacheValidation.ok) {
2610
+ warn(`런타임 캐시 검증 이슈 ${cacheValidation.issues.length}건 발견`);
2611
+ for (const issue of cacheValidation.issues) {
2612
+ info(`${issue.file}: ${issue.error}`);
2613
+ }
2614
+ } else {
2615
+ ok("런타임 캐시 검증 완료");
2616
+ }
2617
+
2493
2618
  // 2. 설치 방식에 따라 업데이트
2494
2619
  const oldVer = PKG.version;
2495
2620
  let updated = false;
@@ -2654,9 +2779,23 @@ function cmdUpdate() {
2654
2779
  }
2655
2780
  }
2656
2781
 
2782
+ // ── Post-update: CLAUDE.md 라우팅 동기화 ──
2783
+ console.log(`\n${CYAN}── CLAUDE.md 라우팅 동기화 ──${RESET}`);
2784
+ {
2785
+ const claudeRoutingResults = syncClaudeRoutingSectionsForCli();
2786
+ const claudeRoutingSummary = getClaudeRoutingSyncSummary(claudeRoutingResults);
2787
+ if (claudeRoutingSummary.changed > 0) {
2788
+ ok(`CLAUDE.md 라우팅 ${claudeRoutingSummary.changed}개 파일 반영`);
2789
+ } else if (claudeRoutingSummary.skipped > 0) {
2790
+ ok("CLAUDE.md 라우팅 대상 파일 없음 (건너뜀)");
2791
+ } else {
2792
+ ok("CLAUDE.md 라우팅 최신 상태");
2793
+ }
2794
+ }
2795
+
2657
2796
  // ── Post-update: 설정 동기화 ──
2658
2797
  console.log(`\n${CYAN}── 설정 동기화 ──${RESET}`);
2659
- cmdSetup({ fromUpdate: true, overrideVersion: newVer });
2798
+ cmdSetup({ fromUpdate: true, overrideVersion: newVer, skipClaudeMdSync: true });
2660
2799
 
2661
2800
  // ── Post-update: 훅 오케스트레이터 적용 ──
2662
2801
  {
@@ -2783,6 +2922,128 @@ function cmdVersion(options = {}) {
2783
2922
  console.log("");
2784
2923
  }
2785
2924
 
2925
+ function cmdHandoff(args = [], options = {}) {
2926
+ const { json = false } = options;
2927
+ const parsed = {
2928
+ target: "remote",
2929
+ decisions: [],
2930
+ decisionFile: null,
2931
+ output: null,
2932
+ cwd: process.cwd(),
2933
+ };
2934
+
2935
+ for (let index = 0; index < args.length; index += 1) {
2936
+ const arg = args[index];
2937
+ const next = args[index + 1];
2938
+
2939
+ if (arg === "--target") {
2940
+ if (!next || next.startsWith("-")) {
2941
+ throw createCliError("--target 값이 필요합니다 (local|remote)", {
2942
+ exitCode: EXIT_ARG_ERROR,
2943
+ reason: "argError",
2944
+ fix: "tfx handoff --target remote",
2945
+ });
2946
+ }
2947
+ if (!["local", "remote"].includes(next)) {
2948
+ throw createCliError(`지원하지 않는 --target 값: ${next}`, {
2949
+ exitCode: EXIT_ARG_ERROR,
2950
+ reason: "argError",
2951
+ fix: "tfx handoff --target local|remote",
2952
+ });
2953
+ }
2954
+ parsed.target = next;
2955
+ index += 1;
2956
+ continue;
2957
+ }
2958
+
2959
+ if (arg === "--decision") {
2960
+ if (!next || next.startsWith("-")) {
2961
+ throw createCliError("--decision 값이 필요합니다", {
2962
+ exitCode: EXIT_ARG_ERROR,
2963
+ reason: "argError",
2964
+ fix: "tfx handoff --decision \"결정사항\"",
2965
+ });
2966
+ }
2967
+ parsed.decisions.push(next);
2968
+ index += 1;
2969
+ continue;
2970
+ }
2971
+
2972
+ if (arg === "--decision-file") {
2973
+ if (!next || next.startsWith("-")) {
2974
+ throw createCliError("--decision-file 경로가 필요합니다", {
2975
+ exitCode: EXIT_ARG_ERROR,
2976
+ reason: "argError",
2977
+ fix: "tfx handoff --decision-file .omx/notepad.md",
2978
+ });
2979
+ }
2980
+ parsed.decisionFile = resolve(next);
2981
+ index += 1;
2982
+ continue;
2983
+ }
2984
+
2985
+ if (arg === "--output" || arg === "--out") {
2986
+ if (!next || next.startsWith("-")) {
2987
+ throw createCliError(`${arg} 경로가 필요합니다`, {
2988
+ exitCode: EXIT_ARG_ERROR,
2989
+ reason: "argError",
2990
+ fix: "tfx handoff --output .omx/handoff.md",
2991
+ });
2992
+ }
2993
+ parsed.output = resolve(next);
2994
+ index += 1;
2995
+ continue;
2996
+ }
2997
+
2998
+ if (arg === "--cwd") {
2999
+ if (!next || next.startsWith("-")) {
3000
+ throw createCliError("--cwd 경로가 필요합니다", {
3001
+ exitCode: EXIT_ARG_ERROR,
3002
+ reason: "argError",
3003
+ fix: "tfx handoff --cwd <project-path>",
3004
+ });
3005
+ }
3006
+ parsed.cwd = resolve(next);
3007
+ index += 1;
3008
+ continue;
3009
+ }
3010
+
3011
+ throw createCliError(`알 수 없는 handoff 옵션: ${arg}`, {
3012
+ exitCode: EXIT_ARG_ERROR,
3013
+ reason: "argError",
3014
+ fix: "tfx handoff --target remote --output .omx/handoff.md",
3015
+ });
3016
+ }
3017
+
3018
+ const result = serializeHandoff({
3019
+ target: parsed.target,
3020
+ decisions: parsed.decisions,
3021
+ decisionFile: parsed.decisionFile,
3022
+ cwd: parsed.cwd,
3023
+ });
3024
+
3025
+ if (parsed.output) {
3026
+ const outputDir = dirname(parsed.output);
3027
+ if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
3028
+ writeFileSync(parsed.output, `${result.prompt}\n`, "utf8");
3029
+ }
3030
+
3031
+ if (json) {
3032
+ printJson({
3033
+ handoff: {
3034
+ ...result,
3035
+ ...(parsed.output ? { output: parsed.output } : {}),
3036
+ },
3037
+ });
3038
+ return;
3039
+ }
3040
+
3041
+ console.log(result.prompt);
3042
+ if (parsed.output) {
3043
+ console.log(`\n${DIM}saved:${RESET} ${parsed.output}`);
3044
+ }
3045
+ }
3046
+
2786
3047
  function cmdSchema(args = []) {
2787
3048
  const bundle = loadDelegatorSchemaBundle();
2788
3049
  const selector = String(args[0] || "").trim();
@@ -3054,6 +3315,7 @@ ${updateNotice}
3054
3315
  ${WHITE_BRIGHT}tfx update${RESET} ${GRAY}최신 안정 버전으로 업데이트${RESET}
3055
3316
  ${DIM} --dev / dev${RESET} ${GRAY}dev 태그로 업데이트${RESET}
3056
3317
  ${WHITE_BRIGHT}tfx list${RESET} ${GRAY}설치된 스킬 목록${RESET}
3318
+ ${WHITE_BRIGHT}tfx handoff${RESET} ${GRAY}현재 컨텍스트를 원격/로컬 핸드오프 프롬프트로 생성${RESET}
3057
3319
  ${WHITE_BRIGHT}tfx schema${RESET} ${GRAY}CLI/Hub schema JSON 출력${RESET}
3058
3320
  ${WHITE_BRIGHT}tfx hub${RESET} ${GRAY}MCP 메시지 버스 관리 (start/stop/status)${RESET}
3059
3321
  ${WHITE_BRIGHT}tfx tray${RESET} ${GRAY}Windows 시스템 트레이 실행${RESET}
@@ -3553,12 +3815,15 @@ async function main() {
3553
3815
  cmdSchema(cmdArgs);
3554
3816
  return;
3555
3817
  case "update":
3556
- cmdUpdate();
3818
+ await cmdUpdate();
3557
3819
  return;
3558
3820
  case "list":
3559
3821
  case "ls":
3560
3822
  cmdList({ json: JSON_OUTPUT });
3561
3823
  return;
3824
+ case "handoff":
3825
+ cmdHandoff(cmdArgs, { json: JSON_OUTPUT });
3826
+ return;
3562
3827
  case "hub":
3563
3828
  await cmdHub(cmdArgs, { json: JSON_OUTPUT && (cmdArgs[0] || "status") === "status" });
3564
3829
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.0.0-alpha.1",
3
+ "version": "10.0.0-alpha.2",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,9 @@
1
+ > **ARGUMENTS 처리**: 이 스킬이 `ARGUMENTS: <값>`과 함께 호출되면, 해당 값을 사용자 입력으로 취급하여
2
+ > 워크플로우의 첫 단계 입력으로 사용한다. ARGUMENTS가 비어있거나 없으면 기존 절차대로 사용자에게 입력을 요청한다.
3
+
4
+ > **Telemetry**
5
+ >
6
+ > - Skill: `{{SKILL_NAME}}`
7
+ > - Description: `{{SKILL_DESCRIPTION}}`
8
+ > - Session: 요청별 식별자를 유지해 단계별 실행 로그를 추적한다.
9
+ > - Errors: 실패 시 원인/복구/재시도 여부를 구조화해 기록한다.
@@ -0,0 +1,6 @@
1
+ ## Deep Consensus Protocol
2
+
3
+ 1. Planner / Architect / Critic 역할을 분리해 독립적으로 초안을 만든다.
4
+ 2. 교차 검토로 충돌 항목을 수집하고 합의 가능한 변경을 우선 반영한다.
5
+ 3. 남은 불일치는 근거(리스크/비용/검증 가능성)와 함께 명시한다.
6
+ 4. 합의된 결과만 최종 실행 입력으로 승격한다.
@@ -32,6 +32,14 @@ argument-hint: "<command|task> [args...]"
32
32
  > **ARGUMENTS 처리**: 이 스킬이 `ARGUMENTS: <값>`과 함께 호출되면, 해당 값을 사용자 입력으로 취급하여
33
33
  > 워크플로우의 첫 단계 입력으로 사용한다. ARGUMENTS가 비어있거나 없으면 기존 절차대로 사용자에게 입력을 요청한다.
34
34
 
35
+ > **Telemetry**
36
+ >
37
+ > - Skill: `tfx-auto`
38
+ > - Description: `통합 CLI 오케스트레이터. 커맨드 숏컷(단일) + 자동 분류/분해(병렬) + 수동 병렬. tfx-route.sh 기반. '코드 짜줘', '구현해줘', '만들어줘', '수정해줘', '고쳐줘', 'implement', 'build', 'fix' 같은 구현/수정 요청에 사용. CLI 라우팅이 필요한 모든 작업에 적극 활용.`
39
+ > - Session: 요청별 식별자를 유지해 단계별 실행 로그를 추적한다.
40
+ > - Errors: 실패 시 원인/복구/재시도 여부를 구조화해 기록한다.
41
+
42
+
35
43
 
36
44
  > **MANDATORY RULES**
37
45
  >