@triflux/core 10.50.1 → 10.51.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.
Files changed (90) hide show
  1. package/hooks/agy-session-hook.mjs +11 -56
  2. package/hooks/codex-session-hook.mjs +11 -101
  3. package/hub/bridge.mjs +1 -79
  4. package/hub/cli-adapter-base.mjs +12 -90
  5. package/hub/codex-adapter.mjs +0 -1
  6. package/hub/codex-preflight.mjs +5 -28
  7. package/hub/index.mjs +3 -9
  8. package/hub/lib/cto-env.mjs +0 -23
  9. package/hub/lib/known-errors.json +2 -2
  10. package/hub/lib/process-utils.mjs +0 -123
  11. package/hub/lib/spawn-trace.mjs +3 -2
  12. package/hub/lib/timeout-defaults.mjs +0 -1
  13. package/hub/paths.mjs +0 -6
  14. package/hub/pipeline/index.mjs +0 -190
  15. package/hub/pipeline/transitions.mjs +0 -10
  16. package/hub/reflexion.mjs +1 -84
  17. package/hub/role-contract.mjs +0 -2
  18. package/hud/cli-policy.mjs +0 -26
  19. package/hud/colors.mjs +0 -13
  20. package/hud/constants.mjs +7 -153
  21. package/hud/context-monitor.mjs +3 -64
  22. package/hud/hud-qos-status.mjs +54 -264
  23. package/hud/providers/claude.mjs +4 -16
  24. package/hud/providers/gemini.mjs +83 -689
  25. package/hud/renderers.mjs +59 -367
  26. package/hud/terminal.mjs +1 -63
  27. package/hud/utils.mjs +2 -26
  28. package/package.json +1 -4
  29. package/scripts/lib/agent-route-policy.mjs +162 -14
  30. package/scripts/lib/claudemd-scanner.mjs +2 -199
  31. package/scripts/lib/cli-claude.mjs +0 -2
  32. package/scripts/lib/context.mjs +3 -19
  33. package/scripts/lib/env-probe.mjs +5 -64
  34. package/scripts/lib/gemini-profiles.mjs +2 -69
  35. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  36. package/scripts/lib/machine-profile.mjs +3 -17
  37. package/scripts/lib/mcp-filter.mjs +109 -53
  38. package/scripts/lib/mcp-health.mjs +2 -2
  39. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  40. package/scripts/lib/psmux-info.mjs +25 -104
  41. package/scripts/lib/session-presence.mjs +1 -1
  42. package/scripts/lib/skill-template.mjs +0 -271
  43. package/hub/codex-compat.mjs +0 -12
  44. package/hub/dynamic-routing-engine.mjs +0 -511
  45. package/hub/fullcycle.mjs +0 -113
  46. package/hub/gemini-adapter.mjs +0 -174
  47. package/hub/intent.mjs +0 -321
  48. package/hub/lib/mcp-response-cache.mjs +0 -239
  49. package/hub/lib/path-utils.mjs +0 -165
  50. package/hub/lib/risk-tier.mjs +0 -53
  51. package/hub/lib/ssh-command.mjs +0 -231
  52. package/hub/lib/staged-file-detect.mjs +0 -43
  53. package/hub/lib/tfx-route-args.mjs +0 -353
  54. package/hub/lib/timeline-adapter.mjs +0 -190
  55. package/hub/pipeline/gates/confidence.mjs +0 -75
  56. package/hub/pipeline/gates/consensus.mjs +0 -106
  57. package/hub/pipeline/gates/index.mjs +0 -9
  58. package/hub/pipeline/gates/selfcheck.mjs +0 -120
  59. package/hub/quality/deslop.mjs +0 -274
  60. package/hub/research.mjs +0 -155
  61. package/hub/role-control-events.mjs +0 -113
  62. package/hub/routing/complexity.mjs +0 -269
  63. package/hub/routing/index.mjs +0 -122
  64. package/hub/routing/q-learning.mjs +0 -387
  65. package/hub/routing-snapshot.mjs +0 -261
  66. package/hub/token-mode.mjs +0 -228
  67. package/hud/mission-board.mjs +0 -56
  68. package/hud/providers/cto.mjs +0 -76
  69. package/mesh/index.mjs +0 -69
  70. package/mesh/mesh-budget.mjs +0 -137
  71. package/mesh/mesh-heartbeat.mjs +0 -100
  72. package/mesh/mesh-protocol.mjs +0 -96
  73. package/mesh/mesh-queue.mjs +0 -163
  74. package/mesh/mesh-registry.mjs +0 -78
  75. package/mesh/mesh-router.mjs +0 -77
  76. package/scripts/lib/agent-json.mjs +0 -27
  77. package/scripts/lib/async.mjs +0 -174
  78. package/scripts/lib/cli-agy.mjs +0 -85
  79. package/scripts/lib/cli-codex.mjs +0 -62
  80. package/scripts/lib/cli-gemini.mjs +0 -37
  81. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  82. package/scripts/lib/env.mjs +0 -14
  83. package/scripts/lib/hub.mjs +0 -48
  84. package/scripts/lib/pid.mjs +0 -63
  85. package/scripts/lib/quota.mjs +0 -47
  86. package/scripts/lib/skill-state.mjs +0 -220
  87. package/scripts/lib/team.mjs +0 -75
  88. package/scripts/lib/timeout.mjs +0 -66
  89. package/scripts/lib/tmp.mjs +0 -26
  90. package/scripts/lib/toml.mjs +0 -132
@@ -1,43 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
-
3
- export function detectChangedFiles({
4
- cwd = process.cwd(),
5
- includeStaged = true,
6
- includeUnstaged = true,
7
- includeUntracked = true,
8
- } = {}) {
9
- const staged = includeStaged
10
- ? runGit(cwd, ["diff", "--cached", "--name-only"])
11
- : [];
12
- const unstaged = includeUnstaged ? runGit(cwd, ["diff", "--name-only"]) : [];
13
- const untracked = includeUntracked
14
- ? runGit(cwd, ["ls-files", "--others", "--exclude-standard"])
15
- : [];
16
- const files = [...new Set([...staged, ...unstaged, ...untracked])];
17
-
18
- return {
19
- stagedCount: staged.length,
20
- unstagedCount: unstaged.length,
21
- untrackedCount: untracked.length,
22
- totalChanged: files.length,
23
- files,
24
- };
25
- }
26
-
27
- export function hasCodeChange(opts) {
28
- return detectChangedFiles(opts).totalChanged > 0;
29
- }
30
-
31
- function runGit(cwd, args) {
32
- try {
33
- return execFileSync("git", args, {
34
- cwd,
35
- encoding: "utf8",
36
- stdio: ["ignore", "pipe", "ignore"],
37
- })
38
- .split(/\r?\n/)
39
- .filter(Boolean);
40
- } catch {
41
- return [];
42
- }
43
- }
@@ -1,353 +0,0 @@
1
- // hub/lib/tfx-route-args.mjs
2
- // Phase 3 Step B — tfx-auto / tfx-route 플래그 파서.
3
- // 설계 문서: .triflux/plans/phase3-lead-codex-ralph-escalate.md
4
- //
5
- // 입력: ARGUMENTS 문자열 또는 토큰 배열.
6
- // 출력: {cli, mode, parallel, retry, isolation, remote, lead, noClaudeNative,
7
- // maxIterations, task, warnings}.
8
- //
9
- // 기존 플래그 (Phase 2 v10.9.33+):
10
- // --cli {auto|codex|antigravity|claude}
11
- // --mode {quick|deep|consensus|live}
12
- // --parallel {1|N|swarm}
13
- // --retry {0|1|ralph|auto-escalate} (Phase 3 에서 ralph/auto-escalate 신규)
14
- // --isolation {none|worktree}
15
- // --remote {none|<host>}
16
- // --rounds <N> (--mode live 전용, 기본 4)
17
- //
18
- // Phase 3 신규:
19
- // --lead {claude|codex} (tfx-auto-codex 의미 흡수)
20
- // --no-claude-native (Claude native sub-agent 경로 disable)
21
- // --max-iterations <N> (ralph / auto-escalate 상한, 0=unlimited)
22
-
23
- import { hasCodeChange } from "./staged-file-detect.mjs";
24
-
25
- export const DEFAULT_OPTIONS = Object.freeze({
26
- cli: "auto",
27
- mode: "quick",
28
- parallel: "1",
29
- retry: "1",
30
- isolation: "none",
31
- remote: "none",
32
- lead: "claude",
33
- noClaudeNative: false,
34
- maxIterations: 0,
35
- rounds: 4,
36
- });
37
-
38
- const VALID_VALUES = Object.freeze({
39
- cli: ["auto", "codex", "antigravity", "claude"],
40
- mode: ["quick", "deep", "consensus", "live"],
41
- retry: ["0", "1", "ralph", "auto-escalate"],
42
- isolation: ["none", "worktree"],
43
- lead: ["claude", "codex"],
44
- });
45
-
46
- const VALUE_FLAGS = new Set([
47
- "--cli",
48
- "--mode",
49
- "--parallel",
50
- "--retry",
51
- "--isolation",
52
- "--remote",
53
- "--lead",
54
- "--max-iterations",
55
- "--rounds",
56
- ]);
57
-
58
- const BOOL_FLAGS = new Set(["--no-claude-native"]);
59
-
60
- export function parseArgs(input) {
61
- const tokens = Array.isArray(input)
62
- ? input.slice()
63
- : tokenize(String(input || ""));
64
- const opts = { ...DEFAULT_OPTIONS };
65
- const warnings = [];
66
- const taskTokens = [];
67
- const providedFlags = new Set();
68
-
69
- let i = 0;
70
- while (i < tokens.length) {
71
- const raw = tokens[i];
72
-
73
- if (BOOL_FLAGS.has(raw)) {
74
- providedFlags.add(raw);
75
- applyBool(opts, raw);
76
- i += 1;
77
- continue;
78
- }
79
-
80
- // --flag=value 지원
81
- const eqIdx = raw.indexOf("=");
82
- let flag = raw;
83
- let value = null;
84
- if (eqIdx > 0 && raw.startsWith("--")) {
85
- flag = raw.slice(0, eqIdx);
86
- value = raw.slice(eqIdx + 1);
87
- }
88
-
89
- if (VALUE_FLAGS.has(flag)) {
90
- providedFlags.add(flag);
91
- if (value === null) {
92
- const next = tokens[i + 1];
93
- if (next === undefined || next.startsWith("--")) {
94
- warnings.push(`${flag} needs a value`);
95
- i += 1;
96
- continue;
97
- }
98
- value = next;
99
- i += 2;
100
- } else {
101
- i += 1;
102
- }
103
- applyValue(opts, flag, value, warnings);
104
- continue;
105
- }
106
-
107
- if (raw.startsWith("--")) {
108
- warnings.push(`unknown flag: ${raw}`);
109
- i += 1;
110
- continue;
111
- }
112
-
113
- taskTokens.push(raw);
114
- i += 1;
115
- }
116
-
117
- validate(opts, warnings, providedFlags);
118
-
119
- return {
120
- ...opts,
121
- task: taskTokens.join(" ").trim(),
122
- warnings,
123
- };
124
- }
125
-
126
- function tokenize(str) {
127
- const tokens = [];
128
- let cur = "";
129
- let quote = null;
130
- for (const ch of str) {
131
- if (quote) {
132
- if (ch === quote) {
133
- quote = null;
134
- } else {
135
- cur += ch;
136
- }
137
- continue;
138
- }
139
- if (ch === '"' || ch === "'") {
140
- quote = ch;
141
- continue;
142
- }
143
- if (/\s/.test(ch)) {
144
- if (cur) {
145
- tokens.push(cur);
146
- cur = "";
147
- }
148
- continue;
149
- }
150
- cur += ch;
151
- }
152
- if (cur) tokens.push(cur);
153
- return tokens;
154
- }
155
-
156
- function applyBool(opts, flag) {
157
- switch (flag) {
158
- case "--no-claude-native":
159
- opts.noClaudeNative = true;
160
- break;
161
- }
162
- }
163
-
164
- function applyValue(opts, flag, value, warnings) {
165
- switch (flag) {
166
- case "--cli":
167
- if (value === "gemini") {
168
- opts.cli = "antigravity";
169
- warnings.push(
170
- "--cli gemini is deprecated; using --cli antigravity instead",
171
- );
172
- } else {
173
- opts.cli = value;
174
- }
175
- break;
176
- case "--mode":
177
- opts.mode = value;
178
- break;
179
- case "--parallel":
180
- opts.parallel = value;
181
- break;
182
- case "--retry":
183
- opts.retry = value;
184
- break;
185
- case "--isolation":
186
- opts.isolation = value;
187
- break;
188
- case "--remote":
189
- opts.remote = value;
190
- break;
191
- case "--lead":
192
- opts.lead = value;
193
- break;
194
- case "--max-iterations": {
195
- const n = Number.parseInt(value, 10);
196
- if (Number.isNaN(n) || n < 0) {
197
- warnings.push(
198
- `invalid --max-iterations=${value}, expected non-negative integer (0=unlimited)`,
199
- );
200
- } else {
201
- opts.maxIterations = n;
202
- }
203
- break;
204
- }
205
- case "--rounds": {
206
- const n = Number(value);
207
- if (!Number.isInteger(n) || n <= 0) {
208
- warnings.push(`invalid --rounds=${value}, expected positive integer`);
209
- } else {
210
- opts.rounds = n;
211
- }
212
- break;
213
- }
214
- }
215
- }
216
-
217
- function validate(opts, warnings, providedFlags = new Set()) {
218
- for (const key of Object.keys(VALID_VALUES)) {
219
- if (!VALID_VALUES[key].includes(opts[key])) {
220
- warnings.push(
221
- `invalid --${key}=${opts[key]}, expected one of ${VALID_VALUES[key].join("|")}`,
222
- );
223
- }
224
- }
225
- if (
226
- opts.parallel !== "1" &&
227
- opts.parallel !== "swarm" &&
228
- !/^\d+$/.test(opts.parallel)
229
- ) {
230
- warnings.push(`invalid --parallel=${opts.parallel}, expected 1|N|swarm`);
231
- }
232
-
233
- if (opts.mode === "live") {
234
- const ignored = [
235
- ["--parallel", "parallel", DEFAULT_OPTIONS.parallel],
236
- ["--isolation", "isolation", DEFAULT_OPTIONS.isolation],
237
- ["--remote", "remote", DEFAULT_OPTIONS.remote],
238
- ];
239
- for (const [flag, key, fallback] of ignored) {
240
- if (providedFlags.has(flag)) {
241
- warnings.push(`${flag} ignored with --mode live`);
242
- opts[key] = fallback;
243
- }
244
- }
245
- if (providedFlags.has("--retry") && opts.retry === "ralph") {
246
- warnings.push("--retry ralph ignored with --mode live");
247
- opts.retry = DEFAULT_OPTIONS.retry;
248
- }
249
- } else if (providedFlags.has("--rounds")) {
250
- warnings.push("--rounds ignored unless --mode live");
251
- opts.rounds = DEFAULT_OPTIONS.rounds;
252
- }
253
-
254
- const parallelOne = opts.parallel === "1" || opts.parallel === 1;
255
- if (parallelOne && opts.isolation === "worktree") {
256
- warnings.push(
257
- "--isolation worktree requires --parallel >=2 or swarm; forcing isolation=none",
258
- );
259
- opts.isolation = "none";
260
- }
261
- if (opts.remote !== "none" && opts.parallel !== "swarm") {
262
- warnings.push(
263
- `--remote ${opts.remote} ignored (requires --parallel swarm)`,
264
- );
265
- }
266
- }
267
-
268
- export { VALID_VALUES };
269
-
270
- /**
271
- * Decide dispatch mode based on parsed args and current git state.
272
- *
273
- * Issue #281: auto router code changes should use isolated swarm dispatch for
274
- * multi-task work unless the user explicitly selected local parallel mode.
275
- *
276
- * @param {object} args
277
- * @param {object} [opts]
278
- * @param {string} [opts.cwd]
279
- * @param {Function} [opts.detector]
280
- * @returns {{
281
- * mode: "single" | "multi" | "swarm",
282
- * escalated: boolean,
283
- * warning: string | null,
284
- * reason: string,
285
- * }}
286
- */
287
- export function decideDispatchMode(
288
- args = {},
289
- { cwd = process.cwd(), detector = hasCodeChange } = {},
290
- ) {
291
- const tasksCount = countTasks(args);
292
- const parallel = normalizeParallel(args.parallel);
293
- const hasUserParallel = parallel !== null && parallel !== "1";
294
- const hasUserSwarm = parallel === "swarm" || args.isolation === "worktree";
295
- const codeChanged = Boolean(detector({ cwd }));
296
-
297
- if (hasUserSwarm) {
298
- return {
299
- mode: "swarm",
300
- escalated: false,
301
- warning: null,
302
- reason: "user-explicit-swarm",
303
- };
304
- }
305
-
306
- if (hasUserParallel && codeChanged) {
307
- return {
308
- mode: "multi",
309
- escalated: false,
310
- warning: `[tfx-auto] WARNING: 코드 변경 감지 (cwd race 위험). --parallel swarm --isolation worktree 권장. 사용자 명시 --parallel=${args.parallel} 존중하여 multi 로 진행.`,
311
- reason: "user-explicit-multi-with-code-change",
312
- };
313
- }
314
-
315
- if (tasksCount >= 2 && codeChanged) {
316
- return {
317
- mode: "swarm",
318
- escalated: true,
319
- warning: `[tfx-auto] 코드 변경 ${tasksCount}건 태스크 + dirty cwd 감지. swarm dispatch 자동 escalate (--parallel swarm --isolation worktree). Issue #281.`,
320
- reason: "auto-escalate-code-change",
321
- };
322
- }
323
-
324
- if (tasksCount >= 2 || hasUserParallel) {
325
- return {
326
- mode: "multi",
327
- escalated: false,
328
- warning: null,
329
- reason: "multi-no-code-change",
330
- };
331
- }
332
-
333
- return {
334
- mode: "single",
335
- escalated: false,
336
- warning: null,
337
- reason: "single-task",
338
- };
339
- }
340
-
341
- function countTasks(args) {
342
- if (Array.isArray(args.tasks)) return args.tasks.length;
343
- if (typeof args.tasks === "number") return args.tasks;
344
- if (typeof args.tasks === "string") return args.tasks.trim() ? 1 : 0;
345
- if (typeof args.task === "string") return args.task.trim() ? 1 : 0;
346
- return args.tasks ?? 0;
347
- }
348
-
349
- function normalizeParallel(value) {
350
- if (value == null) return null;
351
- if (value === "swarm") return value;
352
- return String(value);
353
- }
@@ -1,190 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { appendFile, mkdir, readFile } from "node:fs/promises";
4
- import os from "node:os";
5
- import path from "node:path";
6
- import { promisify } from "node:util";
7
-
8
- const execFileAsync = promisify(execFile);
9
-
10
- /**
11
- * @typedef {object} TimelineEvent
12
- * @property {string} skill
13
- * @property {string} event
14
- * @property {string} branch
15
- * @property {string} session
16
- * @property {string | undefined} [outcome]
17
- * @property {number | undefined} [durationS]
18
- * @property {string} ts
19
- */
20
-
21
- /**
22
- * @typedef {object} LogEventInput
23
- * @property {string} skill
24
- * @property {string} event
25
- * @property {string} branch
26
- * @property {string} session
27
- * @property {string | undefined} [outcome]
28
- * @property {number | undefined} [durationS]
29
- */
30
-
31
- /**
32
- * @typedef {object} LastSessionQuery
33
- * @property {string} branch
34
- * @property {string | undefined} [skill]
35
- */
36
-
37
- /**
38
- * @typedef {object} TimelineAdapter
39
- * @property {(event: LogEventInput) => Promise<void>} logEvent
40
- * @property {(n?: number) => Promise<TimelineEvent[]>} readRecent
41
- * @property {(query: LastSessionQuery) => Promise<TimelineEvent | null>} getLastSession
42
- */
43
-
44
- /**
45
- * @typedef {object} DetectedTimelineAdapter
46
- * @property {TimelineAdapter} adapter
47
- * @property {"gstack" | "null"} kind
48
- */
49
-
50
- /**
51
- * @param {string} filePath
52
- * @returns {Promise<TimelineEvent[]>}
53
- */
54
- async function readTimeline(filePath) {
55
- try {
56
- const content = await readFile(filePath, "utf8");
57
- /** @type {TimelineEvent[]} */
58
- const events = [];
59
-
60
- for (const line of content.split(/\r?\n/)) {
61
- if (!line.trim()) continue;
62
-
63
- try {
64
- events.push(JSON.parse(line));
65
- } catch {
66
- // Skip malformed lines to preserve remaining timeline history.
67
- }
68
- }
69
-
70
- return events;
71
- } catch (error) {
72
- if (
73
- error &&
74
- typeof error === "object" &&
75
- "code" in error &&
76
- error.code === "ENOENT"
77
- ) {
78
- return [];
79
- }
80
- throw error;
81
- }
82
- }
83
-
84
- /**
85
- * @param {string} filePath
86
- * @param {LogEventInput} event
87
- * @returns {Promise<void>}
88
- */
89
- async function appendTimeline(filePath, event) {
90
- await mkdir(path.dirname(filePath), { recursive: true });
91
- const entry = {
92
- ...event,
93
- ts: new Date().toISOString(),
94
- };
95
- await appendFile(filePath, `${JSON.stringify(entry)}\n`, "utf8");
96
- }
97
-
98
- /**
99
- * @param {() => Promise<string>} resolveFilePath
100
- * @returns {TimelineAdapter}
101
- */
102
- function createTimelineAdapter(resolveFilePath) {
103
- return {
104
- async logEvent(event) {
105
- const filePath = await resolveFilePath();
106
- await appendTimeline(filePath, event);
107
- },
108
-
109
- async readRecent(n = 10) {
110
- const filePath = await resolveFilePath();
111
- const events = await readTimeline(filePath);
112
- const limit = Number.isFinite(n) ? Math.trunc(n) : 10;
113
- if (limit <= 0) return [];
114
- return events.slice(-limit);
115
- },
116
-
117
- async getLastSession({ branch, skill }) {
118
- const filePath = await resolveFilePath();
119
- const events = await readTimeline(filePath);
120
-
121
- for (let index = events.length - 1; index >= 0; index -= 1) {
122
- const entry = events[index];
123
- if (!entry?.session) continue;
124
- if (entry.branch !== branch) continue;
125
- if (skill && entry.skill !== skill) continue;
126
- return entry;
127
- }
128
-
129
- return null;
130
- },
131
- };
132
- }
133
-
134
- /**
135
- * @returns {Promise<string>}
136
- */
137
- async function resolveRepoSlug() {
138
- const { stdout } = await execFileAsync(
139
- "git",
140
- ["rev-parse", "--show-toplevel"],
141
- {
142
- cwd: process.cwd(),
143
- encoding: "utf8",
144
- },
145
- );
146
- return path.basename(stdout.trim());
147
- }
148
-
149
- /**
150
- * @returns {Promise<string>}
151
- */
152
- async function resolveGstackTimelinePath() {
153
- const slug = await resolveRepoSlug();
154
- return path.join(os.homedir(), ".gstack", "projects", slug, "timeline.jsonl");
155
- }
156
-
157
- /**
158
- * @returns {Promise<string>}
159
- */
160
- async function resolveNullTimelinePath() {
161
- return path.join(process.cwd(), ".omc", "timeline.jsonl");
162
- }
163
-
164
- /** @type {TimelineAdapter} */
165
- export const gstackTimelineAdapter = createTimelineAdapter(
166
- resolveGstackTimelinePath,
167
- );
168
-
169
- /** @type {TimelineAdapter} */
170
- export const nullTimelineAdapter = createTimelineAdapter(
171
- resolveNullTimelinePath,
172
- );
173
-
174
- /**
175
- * @returns {DetectedTimelineAdapter}
176
- */
177
- export function detectAdapter() {
178
- const gstackRoot = path.join(os.homedir(), ".gstack");
179
- if (existsSync(gstackRoot)) {
180
- return {
181
- adapter: gstackTimelineAdapter,
182
- kind: "gstack",
183
- };
184
- }
185
-
186
- return {
187
- adapter: nullTimelineAdapter,
188
- kind: "null",
189
- };
190
- }
@@ -1,75 +0,0 @@
1
- // hub/pipeline/gates/confidence.mjs — Pre-Execution Confidence Gate
2
- //
3
- // plan → prd → [confidence] → exec
4
- // 5단계 확신도 검증: >=90% proceed / 70-89% alternative / <70% abort
5
-
6
- export const CRITERIA = [
7
- { id: "no_duplicate", label: "중복 구현 없는지?", weight: 0.25 },
8
- { id: "architecture", label: "아키텍처 준수?", weight: 0.25 },
9
- { id: "docs_verified", label: "공식 문서 확인?", weight: 0.2 },
10
- { id: "oss_reference", label: "OSS 레퍼런스?", weight: 0.15 },
11
- { id: "root_cause", label: "근본 원인 파악?", weight: 0.15 },
12
- ];
13
-
14
- /**
15
- * 확신도 검증 실행
16
- * @param {string|object} planArtifact - plan 단계에서 생성된 구현 계획
17
- * @param {object} context - { checks?, codebaseFiles?, existingTests? }
18
- * @param {object} [context.checks] - 각 기준별 점수 (boolean 또는 0-1 숫자)
19
- * @returns {{ score: number, breakdown: Array, decision: string, reasoning: string }}
20
- */
21
- export function runConfidenceCheck(planArtifact, context = {}) {
22
- if (!planArtifact) {
23
- return {
24
- score: 0,
25
- breakdown: CRITERIA.map((c) => ({
26
- id: c.id,
27
- label: c.label,
28
- weight: c.weight,
29
- score: 0,
30
- passed: false,
31
- })),
32
- decision: "abort",
33
- reasoning: "planArtifact가 제공되지 않았습니다.",
34
- };
35
- }
36
-
37
- const checks = context.checks || {};
38
-
39
- const breakdown = CRITERIA.map((c) => {
40
- const raw = checks[c.id];
41
- const score =
42
- typeof raw === "number" ? Math.max(0, Math.min(1, raw)) : raw ? 1 : 0;
43
- return {
44
- id: c.id,
45
- label: c.label,
46
- weight: c.weight,
47
- score,
48
- passed: score >= 0.7,
49
- };
50
- });
51
-
52
- const totalScore = Math.round(
53
- breakdown.reduce((sum, b) => sum + b.score * b.weight, 0) * 100,
54
- );
55
-
56
- let decision, reasoning;
57
- if (totalScore >= 90) {
58
- decision = "proceed";
59
- reasoning = `확신도 ${totalScore}%: 모든 기준 충족. 실행 진행.`;
60
- } else if (totalScore >= 70) {
61
- decision = "alternative";
62
- reasoning = `확신도 ${totalScore}%: 일부 기준 미달. 대안 검토 필요.`;
63
- } else {
64
- decision = "abort";
65
- reasoning = `확신도 ${totalScore}%: 기준 미달. 실행 중단.`;
66
- }
67
-
68
- return {
69
- score: totalScore,
70
- breakdown,
71
- decision,
72
- reasoning,
73
- needsReview: decision === "alternative",
74
- };
75
- }