@triflux/core 10.0.1 → 10.0.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.
@@ -19,7 +19,7 @@
19
19
  // CLAUDE_PLUGIN_ROOT — ${PLUGIN_ROOT} 치환용
20
20
  // HOME / USERPROFILE — ${HOME} 치환용
21
21
 
22
- import { readFileSync, existsSync } from "node:fs";
22
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
23
23
  import { join, dirname } from "node:path";
24
24
  import { fileURLToPath } from "node:url";
25
25
  import { execFileSync, execFile } from "node:child_process";
@@ -210,8 +210,55 @@ function mergeOutputs(accumulated, newOutput) {
210
210
  }
211
211
  }
212
212
 
213
+ // ── 라우팅 가중치 기록 ────────────────────────────────────────
214
+ function recordRouteOutcome(slug, mode, outcome) {
215
+ const home = process.env.HOME || process.env.USERPROFILE || "";
216
+ const projDir = join(home, ".gstack", "projects", slug);
217
+ const weightsPath = join(projDir, "routing-weights.json");
218
+
219
+ mkdirSync(projDir, { recursive: true });
220
+
221
+ const weights = existsSync(weightsPath)
222
+ ? JSON.parse(readFileSync(weightsPath, "utf8"))
223
+ : { updated_at: null, total_routes: 0, overrides: 0, weights: { mode_bias: {}, profile_bias: {}, depth_bias: {} } };
224
+
225
+ weights.total_routes++;
226
+ weights.updated_at = new Date().toISOString();
227
+
228
+ const bias = weights.weights.mode_bias;
229
+ const current = bias[mode] || 0;
230
+
231
+ if (outcome === "override") {
232
+ bias[mode] = Math.max(0, current - 0.1);
233
+ weights.overrides++;
234
+ } else if (outcome === "completion") {
235
+ bias[mode] = Math.min(1, current + 0.05);
236
+ } else if (outcome === "abort") {
237
+ bias[mode] = Math.max(0, current - 0.1);
238
+ }
239
+
240
+ const total = Object.values(bias).reduce((s, v) => s + v, 0);
241
+ if (total > 0) {
242
+ for (const key of Object.keys(bias)) {
243
+ bias[key] = +(bias[key] / total).toFixed(3);
244
+ }
245
+ }
246
+
247
+ writeFileSync(weightsPath, JSON.stringify(weights, null, 2), "utf8");
248
+ }
249
+
213
250
  // ── 메인 ────────────────────────────────────────────────────
214
251
  async function main() {
252
+ // CLI: --record-route <slug> <mode> <outcome>
253
+ const rrIdx = process.argv.indexOf("--record-route");
254
+ if (rrIdx !== -1) {
255
+ const slug = process.argv[rrIdx + 1];
256
+ const mode = process.argv[rrIdx + 2];
257
+ const outcome = process.argv[rrIdx + 3];
258
+ if (slug && mode && outcome) recordRouteOutcome(slug, mode, outcome);
259
+ process.exit(0);
260
+ }
261
+
215
262
  const stdinRaw = readStdin();
216
263
  const registry = loadRegistry();
217
264
 
@@ -13,8 +13,7 @@
13
13
  "priority": 0,
14
14
  "supersedes": [
15
15
  "tfx-multi",
16
- "tfx-auto",
17
- "tfx-auto-codex",
16
+ "tfx-unified",
18
17
  "tfx-codex",
19
18
  "tfx-gemini"
20
19
  ],
@@ -71,34 +70,21 @@
71
70
  "mcp_route": null
72
71
  },
73
72
  {
74
- "id": "tfx-auto-codex",
73
+ "id": "tfx-unified",
75
74
  "patterns": [
76
- {
77
- "source": "\\btfx[\\s-]?auto[\\s-]?codex\\b",
78
- "flags": "i"
79
- }
80
- ],
81
- "skill": "tfx-auto-codex",
82
- "priority": 1,
83
- "supersedes": [
84
- "tfx-auto",
85
- "tfx-codex"
86
- ],
87
- "exclusive": false,
88
- "state": null,
89
- "mcp_route": null
90
- },
91
- {
92
- "id": "tfx-auto",
93
- "patterns": [
94
- {
95
- "source": "\\btfx[\\s-]?auto\\b",
96
- "flags": "i"
97
- }
75
+ { "source": "\\btfx[\\s-]?auto\\b", "flags": "i" },
76
+ { "source": "\\btfx[\\s-]?auto[\\s-]?codex\\b", "flags": "i" },
77
+ { "source": "(?:만들어|고쳐|구현해|짜줘|수정해|바꿔)", "flags": "i" },
78
+ { "source": "(?:리뷰해|검토해|봐줘|괜찮아)", "flags": "i" },
79
+ { "source": "(?:테스트|검증|돌려봐|QA)", "flags": "i" },
80
+ { "source": "(?:분석해|계획|설계해)", "flags": "i" },
81
+ { "source": "(?:찾아봐|조사해|검색해)", "flags": "i" },
82
+ { "source": "(?:정리해|슬롭|클린업)", "flags": "i" },
83
+ { "source": "\\b(?:implement|build|fix|review|test|plan|analyze)\\b", "flags": "i" }
98
84
  ],
99
85
  "skill": "tfx-auto",
100
86
  "priority": 2,
101
- "supersedes": [],
87
+ "supersedes": ["tfx-auto-codex"],
102
88
  "exclusive": false,
103
89
  "state": null,
104
90
  "mcp_route": null
@@ -38,15 +38,16 @@ export function gte(minMinor) {
38
38
 
39
39
  /**
40
40
  * Codex CLI 기능별 분기 객체.
41
- * 117 = 0.117.0 (Rust 리라이트, exec 서브커맨드 도입)
41
+ * 실측 기반 임계값: 0.114.0에서 exec/skip-git-repo-check/color 확인됨.
42
+ * --output-last-message는 0.114.0에 없음 (0.117+ 추정).
42
43
  */
43
44
  export const FEATURES = {
44
- /** exec 서브커맨드 사용 가능 여부 */
45
- get execSubcommand() { return gte(117); },
46
- /** --output-last-message 플래그 지원 여부 */
45
+ /** exec 서브커맨드 사용 가능 여부 (0.110+ 이전부터 존재) */
46
+ get execSubcommand() { return gte(110); },
47
+ /** --output-last-message 플래그 지원 여부 (0.117+) */
47
48
  get outputLastMessage() { return gte(117); },
48
- /** --color never 플래그 지원 여부 */
49
- get colorNever() { return gte(117); },
49
+ /** --color <COLOR> 플래그 지원 여부 (exec와 동시 도입) */
50
+ get colorNever() { return gte(110); },
50
51
  /** 플러그인 시스템 지원 여부 (향후 확장용) */
51
52
  get pluginSystem() { return gte(120); },
52
53
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/core",
3
- "version": "10.0.1",
3
+ "version": "10.0.2",
4
4
  "description": "triflux core — CLI routing, pipeline, adapters. Zero native dependencies.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",
@@ -317,6 +317,30 @@ export function loadTemplatePartials(partialsDir) {
317
317
  return partials;
318
318
  }
319
319
 
320
+ export function loadSkillManifest(skillDir) {
321
+ const manifestPath = join(skillDir, "skill.json");
322
+ if (!existsSync(manifestPath)) return null;
323
+
324
+ const raw = readFileSync(manifestPath, "utf8");
325
+ return JSON.parse(raw);
326
+ }
327
+
328
+ export function parseFrontmatterWithManifest(source, skillDir) {
329
+ const manifest = skillDir ? loadSkillManifest(skillDir) : null;
330
+ const { data: yamlData, body } = parseFrontmatter(source);
331
+
332
+ if (!manifest) return { data: yamlData, body };
333
+
334
+ const merged = { ...yamlData };
335
+ if (manifest.name) merged.name = manifest.name;
336
+ if (manifest.description) merged.description = manifest.description;
337
+ if (manifest.triggers) merged.triggers = manifest.triggers;
338
+ if (manifest.argument_hint) merged["argument-hint"] = manifest.argument_hint;
339
+ if (manifest.internal != null) merged.internal = manifest.internal;
340
+
341
+ return { data: merged, body };
342
+ }
343
+
320
344
  export function renderSkillTemplate(template, context = {}, options = {}) {
321
345
  const { partials = {}, includes = {}, includeBaseDir = "" } = options;
322
346
  return renderWithContext(template, context, partials, {