triflux 10.50.2 → 10.52.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 (263) hide show
  1. package/CLAUDE.md +57 -41
  2. package/README.ko.md +41 -45
  3. package/README.md +41 -45
  4. package/adapters/codex/skills/tfx-live/SKILL.md +25 -11
  5. package/bin/tfx-live.mjs +341 -104
  6. package/bin/triflux.mjs +147 -2624
  7. package/config/mcp-registry.json +3 -12
  8. package/cto/brief.mjs +0 -20
  9. package/cto/collect.mjs +1 -200
  10. package/cto/current.schema.json +2 -23
  11. package/cto/hygiene.mjs +2 -61
  12. package/cto/lake-root.mjs +0 -56
  13. package/cto/status.mjs +5 -187
  14. package/hooks/agy-session-hook.mjs +11 -56
  15. package/hooks/codex-session-hook.mjs +11 -101
  16. package/hub/bridge.mjs +65 -1250
  17. package/hub/cli-adapter-base.mjs +12 -90
  18. package/hub/codex-adapter.mjs +0 -1
  19. package/hub/codex-preflight.mjs +5 -28
  20. package/hub/lib/process-utils.mjs +1 -156
  21. package/hub/lib/spawn-trace.mjs +3 -2
  22. package/hub/lib/timeout-defaults.mjs +0 -1
  23. package/hub/team/agent-map.json +0 -1
  24. package/hub/team/agents-row-attach.py +120 -0
  25. package/hub/team/agents-row.mjs +200 -0
  26. package/hub/team/ansi.mjs +1 -35
  27. package/hub/team/backend.mjs +9 -145
  28. package/hub/team/claude-daemon-control.mjs +1 -2
  29. package/hub/team/claude-session-projection.mjs +5 -73
  30. package/hub/team/cli/commands/attach.mjs +1 -17
  31. package/hub/team/cli/commands/control.mjs +12 -38
  32. package/hub/team/cli/commands/debug.mjs +2 -39
  33. package/hub/team/cli/commands/focus.mjs +5 -31
  34. package/hub/team/cli/commands/interrupt.mjs +6 -25
  35. package/hub/team/cli/commands/kill.mjs +1 -30
  36. package/hub/team/cli/commands/list.mjs +0 -16
  37. package/hub/team/cli/commands/send.mjs +6 -26
  38. package/hub/team/cli/commands/start/index.mjs +42 -133
  39. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  40. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  41. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  42. package/hub/team/cli/commands/status.mjs +2 -57
  43. package/hub/team/cli/commands/stop.mjs +14 -19
  44. package/hub/team/cli/help.mjs +4 -5
  45. package/hub/team/cli/manifest.mjs +5 -1
  46. package/hub/team/cli/render.mjs +0 -12
  47. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  48. package/hub/team/cli/services/state-store.mjs +5 -20
  49. package/hub/team/codex-queue.mjs +385 -0
  50. package/hub/team/handoff.mjs +3 -34
  51. package/hub/team/headless.mjs +108 -1296
  52. package/hub/team/orchestrator.mjs +4 -30
  53. package/hub/team/pane.mjs +6 -28
  54. package/hub/team/psmux.mjs +3 -34
  55. package/hub/team/remote-session.mjs +1 -2
  56. package/hub/team/session-context.mjs +1 -1
  57. package/hub/team/session.mjs +7 -91
  58. package/hub/team/tui-core.mjs +1 -1
  59. package/hub/team/tui-viewer.mjs +11 -58
  60. package/hub/team/tui.mjs +8 -306
  61. package/hub/team/uds-orchestrator.mjs +2 -580
  62. package/hub/team/wt-manager.mjs +2 -2
  63. package/hub/workers/factory.mjs +6 -84
  64. package/hub/workers/interface.mjs +1 -7
  65. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  66. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  67. package/hud/cli-policy.mjs +0 -22
  68. package/hud/constants.mjs +13 -26
  69. package/hud/hud-qos-status.mjs +42 -54
  70. package/hud/providers/gemini.mjs +125 -20
  71. package/hud/renderers.mjs +101 -122
  72. package/hud/utils.mjs +10 -6
  73. package/package.json +7 -12
  74. package/scripts/__tests__/mcp-guard-engine-http-headers.test.mjs +6 -6
  75. package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +1 -4
  76. package/scripts/__tests__/mcp-guard-engine-proactive-sync.test.mjs +16 -21
  77. package/scripts/__tests__/mcp-guard-engine-stdio-sync.test.mjs +1 -1
  78. package/scripts/__tests__/mcp-guard-engine-sync-http-headers.test.mjs +2 -2
  79. package/scripts/__tests__/mcp-guard-engine.test.mjs +56 -63
  80. package/scripts/__tests__/spawn-trace.test.mjs +16 -0
  81. package/scripts/check-codex-config-stable.mjs +4 -37
  82. package/scripts/claude-login-detect.mjs +0 -1
  83. package/scripts/claudemd-sync.mjs +1 -28
  84. package/scripts/lib/agent-route-policy.mjs +143 -62
  85. package/scripts/lib/cli-claude.mjs +0 -14
  86. package/scripts/lib/env-probe.mjs +2 -131
  87. package/scripts/lib/gemini-profiles.mjs +2 -69
  88. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  89. package/scripts/lib/machine-profile.mjs +3 -17
  90. package/scripts/lib/mcp-filter.mjs +109 -53
  91. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  92. package/scripts/lib/mcp-health.mjs +2 -2
  93. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  94. package/scripts/lib/psmux-info.mjs +11 -59
  95. package/scripts/lib/session-presence.mjs +2 -100
  96. package/scripts/lib/skill-template.mjs +0 -245
  97. package/scripts/pack.mjs +8 -83
  98. package/scripts/preflight-cache.mjs +5 -18
  99. package/scripts/release/check-packages-mirror.mjs +2 -8
  100. package/scripts/release/version-manifest.json +9 -1
  101. package/scripts/setup.mjs +100 -417
  102. package/scripts/test-lock.mjs +0 -1
  103. package/scripts/tfx-route-post.mjs +5 -69
  104. package/scripts/tfx-route-worker.mjs +4 -79
  105. package/scripts/tfx-route.sh +169 -1495
  106. package/scripts/wt-cli.mjs +1 -2
  107. package/skills/tfx-auto/SKILL.md +123 -741
  108. package/skills/tfx-auto/references/consensus-schema.md +161 -0
  109. package/skills/tfx-auto/references/tmux-observation.md +97 -0
  110. package/skills/tfx-doctor/SKILL.md +5 -6
  111. package/skills/tfx-lead/SKILL.md +195 -0
  112. package/skills/tfx-live/SKILL.md +25 -11
  113. package/skills/tfx-remote/SKILL.md +24 -87
  114. package/skills/tfx-research/SKILL.md +13 -20
  115. package/skills/tfx-review/SKILL.md +33 -35
  116. package/skills/tfx-setup/SKILL.md +1 -1
  117. package/skills/tfx-ship/SKILL.md +6 -27
  118. package/skills/tfx-wt/SKILL.md +34 -68
  119. package/bin/tfx-doctor-tui.mjs +0 -7
  120. package/bin/tfx-setup-tui.mjs +0 -7
  121. package/config/routing-policy.json +0 -124
  122. package/docs/assets/consensus-flow.svg +0 -125
  123. package/docs/assets/deep-vs-light.svg +0 -193
  124. package/docs/assets/demo-dark.gif +0 -0
  125. package/docs/assets/demo-light.gif +0 -0
  126. package/docs/assets/demo-psmux.gif +0 -0
  127. package/hub/adaptive-diagnostic.mjs +0 -350
  128. package/hub/adaptive-inject.mjs +0 -224
  129. package/hub/adaptive-memory.mjs +0 -277
  130. package/hub/adaptive.mjs +0 -172
  131. package/hub/assign-callbacks.mjs +0 -170
  132. package/hub/delegator/contracts.mjs +0 -38
  133. package/hub/delegator/index.mjs +0 -14
  134. package/hub/delegator/schema/delegator-tools.schema.json +0 -250
  135. package/hub/delegator/service.mjs +0 -336
  136. package/hub/delegator/tool-definitions.mjs +0 -35
  137. package/hub/dynamic-routing-engine.mjs +0 -511
  138. package/hub/hitl.mjs +0 -202
  139. package/hub/hub-lifecycle.mjs +0 -248
  140. package/hub/lib/known-errors.json +0 -72
  141. package/hub/lib/memory-store.mjs +0 -1001
  142. package/hub/lib/phase-manager.mjs +0 -493
  143. package/hub/lib/risk-tier.mjs +0 -53
  144. package/hub/lib/ssh-command.mjs +0 -231
  145. package/hub/lib/staged-file-detect.mjs +0 -43
  146. package/hub/lib/state-snapshot.mjs +0 -293
  147. package/hub/lib/tfx-route-args.mjs +0 -353
  148. package/hub/lib/trace-recorder.mjs +0 -153
  149. package/hub/lib/uuidv7.mjs +0 -45
  150. package/hub/memory-doctor.mjs +0 -640
  151. package/hub/middleware/quota-middleware.mjs +0 -70
  152. package/hub/middleware/request-logger.mjs +0 -191
  153. package/hub/paths.mjs +0 -24
  154. package/hub/pipe.mjs +0 -825
  155. package/hub/pipeline/index.mjs +0 -163
  156. package/hub/pipeline/state.mjs +0 -191
  157. package/hub/pipeline/transitions.mjs +0 -131
  158. package/hub/promote-penalties.mjs +0 -138
  159. package/hub/public/dashboard.html +0 -416
  160. package/hub/reflexion.mjs +0 -290
  161. package/hub/role-contract.mjs +0 -314
  162. package/hub/router.mjs +0 -2026
  163. package/hub/routing-snapshot.mjs +0 -261
  164. package/hub/schema.sql +0 -209
  165. package/hub/server.mjs +0 -3048
  166. package/hub/session-fingerprint.mjs +0 -418
  167. package/hub/state.mjs +0 -300
  168. package/hub/store-adapter.mjs +0 -182
  169. package/hub/store.mjs +0 -1736
  170. package/hub/team/build-worker-prompt.mjs +0 -92
  171. package/hub/team/check-mcp-hub.mjs +0 -49
  172. package/hub/team/claude-native-bridge.mjs +0 -987
  173. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  174. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  175. package/hub/team/cli/services/hub-client.mjs +0 -264
  176. package/hub/team/cli/services/native-control.mjs +0 -145
  177. package/hub/team/codex-review.mjs +0 -520
  178. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  179. package/hub/team/conductor-registry.mjs +0 -121
  180. package/hub/team/conductor.mjs +0 -1529
  181. package/hub/team/consensus-meta.mjs +0 -176
  182. package/hub/team/cto-auto-collect.mjs +0 -115
  183. package/hub/team/cto-pull.mjs +0 -31
  184. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  185. package/hub/team/dashboard.mjs +0 -322
  186. package/hub/team/event-log.mjs +0 -84
  187. package/hub/team/execution-mode.mjs +0 -324
  188. package/hub/team/extract-completion-payload.mjs +0 -110
  189. package/hub/team/headless-bridge-session.mjs +0 -17
  190. package/hub/team/health-probe.mjs +0 -435
  191. package/hub/team/interactive-native-launcher.mjs +0 -119
  192. package/hub/team/interactive-native-worker.mjs +0 -80
  193. package/hub/team/interactive-tui-transport.mjs +0 -479
  194. package/hub/team/launcher-template.mjs +0 -144
  195. package/hub/team/lead-control.mjs +0 -191
  196. package/hub/team/mcp-selector.mjs +0 -145
  197. package/hub/team/native-supervisor.mjs +0 -460
  198. package/hub/team/native.mjs +0 -727
  199. package/hub/team/recovery-store.mjs +0 -149
  200. package/hub/team/remote-probe.mjs +0 -292
  201. package/hub/team/remote-watcher.mjs +0 -506
  202. package/hub/team/runtime-strategy.mjs +0 -200
  203. package/hub/team/schemas/worker-completion.json +0 -39
  204. package/hub/team/sentinel-capture.mjs +0 -127
  205. package/hub/team/session-sync.mjs +0 -316
  206. package/hub/team/swarm-cli.mjs +0 -390
  207. package/hub/team/swarm-hypervisor.mjs +0 -2206
  208. package/hub/team/swarm-intent.mjs +0 -216
  209. package/hub/team/swarm-locks.mjs +0 -335
  210. package/hub/team/swarm-planner.mjs +0 -371
  211. package/hub/team/swarm-preflight.mjs +0 -366
  212. package/hub/team/swarm-reconciler.mjs +0 -242
  213. package/hub/team/synapse-cli.mjs +0 -458
  214. package/hub/team/synapse-http.mjs +0 -311
  215. package/hub/team/synapse-registry.mjs +0 -618
  216. package/hub/team/tui-remote-adapter.mjs +0 -377
  217. package/hub/team/tui-synapse.mjs +0 -223
  218. package/hub/team/worker-completion-validator.mjs +0 -79
  219. package/hub/team/worker-sandbox.mjs +0 -125
  220. package/hub/team/worktree-lifecycle.mjs +0 -639
  221. package/hub/team/wt-templates.json +0 -43
  222. package/hub/team-bridge.mjs +0 -27
  223. package/hub/tools.mjs +0 -900
  224. package/hub/workers/antigravity-route-worker.mjs +0 -179
  225. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  226. package/hub/workers/delegator-mcp.mjs +0 -1098
  227. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  228. package/mesh/index.mjs +0 -69
  229. package/mesh/mesh-budget.mjs +0 -137
  230. package/mesh/mesh-heartbeat.mjs +0 -100
  231. package/mesh/mesh-protocol.mjs +0 -96
  232. package/mesh/mesh-queue.mjs +0 -163
  233. package/mesh/mesh-registry.mjs +0 -78
  234. package/mesh/mesh-router.mjs +0 -77
  235. package/scripts/__tests__/skill-surface.test.mjs +0 -93
  236. package/scripts/cache-doctor.mjs +0 -156
  237. package/scripts/cache-warmup.mjs +0 -630
  238. package/scripts/completions/tfx.bash +0 -74
  239. package/scripts/completions/tfx.fish +0 -57
  240. package/scripts/completions/tfx.zsh +0 -98
  241. package/scripts/demo.mjs +0 -197
  242. package/scripts/doctor-dynamic-routing.mjs +0 -87
  243. package/scripts/hub-ensure.mjs +0 -842
  244. package/scripts/hub-watchdog.mjs +0 -90
  245. package/scripts/lib/claudemd-scanner.mjs +0 -223
  246. package/scripts/lib/context.mjs +0 -67
  247. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  248. package/scripts/lib/handoff.mjs +0 -187
  249. package/scripts/lib/logger.mjs +0 -122
  250. package/scripts/lib/stealth-fetch.mjs +0 -176
  251. package/scripts/notion-read.mjs +0 -686
  252. package/scripts/snapshot-codex-state.mjs +0 -37
  253. package/scripts/snapshot-gemini-state.mjs +0 -37
  254. package/scripts/sync-codex-auth.mjs +0 -71
  255. package/scripts/sync-hub-mcp-settings.mjs +0 -697
  256. package/skills/_templates/base.md +0 -4
  257. package/skills/_templates/deep.md +0 -6
  258. package/skills/shared/telemetry-segment.md +0 -6
  259. package/tui/core.mjs +0 -266
  260. package/tui/doctor.mjs +0 -375
  261. package/tui/monitor-data.mjs +0 -152
  262. package/tui/monitor.mjs +0 -318
  263. package/tui/setup.mjs +0 -506
@@ -1,493 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import {
3
- mkdir,
4
- readdir,
5
- readFile,
6
- rename,
7
- stat,
8
- unlink,
9
- writeFile,
10
- } from "node:fs/promises";
11
- import os from "node:os";
12
- import { dirname, join } from "node:path";
13
-
14
- /**
15
- * @typedef {"Research" | "Strategy" | "Execution" | "Validation"} WorkPhase
16
- */
17
-
18
- /**
19
- * @typedef {"active" | "complete" | "failed"} PhaseStatus
20
- */
21
-
22
- /**
23
- * @typedef {WorkPhase | "complete"} LegacyPhase
24
- */
25
-
26
- /**
27
- * @typedef {object} PhaseRecord
28
- * @property {WorkPhase | "complete"} phase
29
- * @property {PhaseStatus} phaseStatus
30
- * @property {WorkPhase | null} lastPhase
31
- */
32
-
33
- export const PHASE_ENUM = Object.freeze([
34
- "Research",
35
- "Strategy",
36
- "Execution",
37
- "Validation",
38
- ]);
39
-
40
- export const PHASE_STATUS = Object.freeze(["active", "complete", "failed"]);
41
-
42
- const PHASE_SET = new Set(PHASE_ENUM);
43
- const PHASE_STATUS_SET = new Set(PHASE_STATUS);
44
-
45
- /**
46
- * @returns {string}
47
- */
48
- function getHomeDir() {
49
- return process.env.HOME || process.env.USERPROFILE || os.homedir();
50
- }
51
-
52
- /**
53
- * @param {unknown} value
54
- * @returns {value is WorkPhase}
55
- */
56
- function isWorkPhase(value) {
57
- return PHASE_SET.has(String(value));
58
- }
59
-
60
- /**
61
- * @param {unknown} value
62
- * @returns {value is PhaseStatus}
63
- */
64
- function isPhaseStatus(value) {
65
- return PHASE_STATUS_SET.has(String(value));
66
- }
67
-
68
- /**
69
- * @param {unknown} value
70
- * @returns {WorkPhase | null}
71
- */
72
- function coerceWorkPhase(value) {
73
- const phase = coerceLegacyPhase(value);
74
- return phase === "complete" ? null : phase;
75
- }
76
-
77
- /**
78
- * @param {string} runId
79
- * @returns {{fullcycleStatePath: string, phaseStatePath: string}}
80
- */
81
- function getRunPaths(runId) {
82
- return {
83
- fullcycleStatePath: join(
84
- process.cwd(),
85
- ".tfx",
86
- "fullcycle",
87
- runId,
88
- "state.json",
89
- ),
90
- phaseStatePath: join(process.cwd(), ".tfx", "phases", `${runId}.json`),
91
- };
92
- }
93
-
94
- /**
95
- * @param {string} runId
96
- * @returns {string | null}
97
- */
98
- function resolveReadPath(runId) {
99
- const { fullcycleStatePath, phaseStatePath } = getRunPaths(runId);
100
- if (existsSync(fullcycleStatePath)) return fullcycleStatePath;
101
- if (existsSync(phaseStatePath)) return phaseStatePath;
102
- return null;
103
- }
104
-
105
- /**
106
- * @param {string} runId
107
- * @returns {string}
108
- */
109
- function resolveWritePath(runId) {
110
- const { fullcycleStatePath, phaseStatePath } = getRunPaths(runId);
111
- const fullcycleDir = dirname(fullcycleStatePath);
112
-
113
- if (
114
- existsSync(fullcycleStatePath) ||
115
- existsSync(fullcycleDir) ||
116
- !existsSync(phaseStatePath)
117
- ) {
118
- return fullcycleStatePath;
119
- }
120
-
121
- return phaseStatePath;
122
- }
123
-
124
- /**
125
- * @param {string} tempPath
126
- * @param {string} targetPath
127
- * @returns {Promise<void>}
128
- */
129
- async function safeReplaceFile(tempPath, targetPath) {
130
- try {
131
- await rename(tempPath, targetPath);
132
- } catch (error) {
133
- if (!["EEXIST", "EPERM", "EACCES"].includes(error?.code)) {
134
- try {
135
- await unlink(tempPath);
136
- } catch {}
137
- throw error;
138
- }
139
-
140
- try {
141
- await unlink(targetPath);
142
- } catch {}
143
-
144
- await rename(tempPath, targetPath);
145
- }
146
- }
147
-
148
- /**
149
- * @param {string} targetPath
150
- * @param {Record<string, unknown>} payload
151
- * @returns {Promise<void>}
152
- */
153
- async function writeJsonAtomic(targetPath, payload) {
154
- const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
155
- await mkdir(dirname(targetPath), { recursive: true });
156
- await writeFile(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
157
- encoding: "utf8",
158
- mode: 0o600,
159
- });
160
- await safeReplaceFile(tempPath, targetPath);
161
- }
162
-
163
- /**
164
- * @param {string} targetPath
165
- * @param {string} content
166
- * @returns {Promise<void>}
167
- */
168
- async function writeTextAtomic(targetPath, content) {
169
- const tempPath = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
170
- await mkdir(dirname(targetPath), { recursive: true });
171
- await writeFile(tempPath, content, {
172
- encoding: "utf8",
173
- mode: 0o600,
174
- });
175
- await safeReplaceFile(tempPath, targetPath);
176
- }
177
-
178
- /**
179
- * @param {string} filePath
180
- * @returns {Promise<Record<string, unknown> | null>}
181
- */
182
- async function readJsonFile(filePath) {
183
- try {
184
- const raw = await readFile(filePath, "utf8");
185
- return JSON.parse(raw);
186
- } catch (error) {
187
- if (error?.code === "ENOENT") {
188
- return null;
189
- }
190
- throw error;
191
- }
192
- }
193
-
194
- /**
195
- * @param {Record<string, unknown>} data
196
- * @returns {PhaseRecord | null}
197
- */
198
- function phaseRecordFromData(data) {
199
- const explicitPhase = isWorkPhase(data.phase) ? data.phase : null;
200
- const explicitStatus = isPhaseStatus(data.phase_status)
201
- ? data.phase_status
202
- : null;
203
- const explicitLastPhase = isWorkPhase(data.last_phase)
204
- ? data.last_phase
205
- : null;
206
- const legacyLastPhase =
207
- explicitLastPhase || coerceWorkPhase(data.last_successful_phase);
208
-
209
- if (explicitPhase) {
210
- return {
211
- phase: explicitPhase,
212
- phaseStatus: explicitStatus || "active",
213
- lastPhase: legacyLastPhase,
214
- };
215
- }
216
-
217
- const legacyPhase = coerceLegacyPhase(data.current_phase);
218
- if (!legacyPhase) {
219
- return null;
220
- }
221
-
222
- if (legacyPhase === "complete") {
223
- return {
224
- phase: legacyLastPhase || "complete",
225
- phaseStatus: explicitStatus || "complete",
226
- lastPhase: legacyLastPhase,
227
- };
228
- }
229
-
230
- return {
231
- phase: legacyPhase,
232
- phaseStatus: explicitStatus || "active",
233
- lastPhase: legacyLastPhase,
234
- };
235
- }
236
-
237
- /**
238
- * @param {WorkPhase | "complete"} phase
239
- * @returns {string}
240
- */
241
- function toLegacyPhaseName(phase) {
242
- return phase === "complete" ? "complete" : phase.toLowerCase();
243
- }
244
-
245
- /**
246
- * @param {Record<string, unknown>} data
247
- * @param {PhaseRecord} record
248
- * @returns {Record<string, unknown>}
249
- */
250
- function applyPhaseRecord(data, record) {
251
- const next = {
252
- ...data,
253
- phase_status: record.phaseStatus,
254
- current_phase: toLegacyPhaseName(record.phase),
255
- };
256
-
257
- if (record.phase !== "complete") {
258
- next.phase = record.phase;
259
- }
260
-
261
- if (record.lastPhase) {
262
- next.last_phase = record.lastPhase;
263
- next.last_successful_phase = record.lastPhase.toLowerCase();
264
- }
265
-
266
- if (record.phaseStatus === "complete" && record.phase !== "complete") {
267
- next.last_phase = record.phase;
268
- next.last_successful_phase = record.phase.toLowerCase();
269
- }
270
-
271
- return next;
272
- }
273
-
274
- /**
275
- * @param {string} content
276
- * @returns {{lines: string[], bodyLines: string[], eol: string} | null}
277
- */
278
- function parseFrontmatter(content) {
279
- const eol = content.includes("\r\n") ? "\r\n" : "\n";
280
- const lines = content.split(/\r?\n/);
281
-
282
- if (lines[0] !== "---") {
283
- return null;
284
- }
285
-
286
- const closingIndex = lines.indexOf("---", 1);
287
- if (closingIndex === -1) {
288
- return null;
289
- }
290
-
291
- return {
292
- lines: lines.slice(1, closingIndex),
293
- bodyLines: lines.slice(closingIndex + 1),
294
- eol,
295
- };
296
- }
297
-
298
- /**
299
- * @param {string[]} lines
300
- * @param {Record<string, string>} fields
301
- * @returns {string[]}
302
- */
303
- function upsertFrontmatterLines(lines, fields) {
304
- const nextLines = [...lines];
305
-
306
- for (const [key, value] of Object.entries(fields)) {
307
- const keyPrefix = `${key}:`;
308
- const index = nextLines.findIndex((line) => line.startsWith(keyPrefix));
309
-
310
- if (index >= 0) {
311
- nextLines[index] = `${key}: ${value}`;
312
- continue;
313
- }
314
-
315
- nextLines.push(`${key}: ${value}`);
316
- }
317
-
318
- return nextLines;
319
- }
320
-
321
- /**
322
- * @param {string} content
323
- * @param {Record<string, string>} fields
324
- * @returns {string | null}
325
- */
326
- function injectFrontmatterFields(content, fields) {
327
- const parsed = parseFrontmatter(content);
328
- if (!parsed) {
329
- return null;
330
- }
331
-
332
- const updatedLines = upsertFrontmatterLines(parsed.lines, fields);
333
- return ["---", ...updatedLines, "---", ...parsed.bodyLines].join(parsed.eol);
334
- }
335
-
336
- /**
337
- * @param {string} runId
338
- * @returns {Promise<PhaseRecord | null>}
339
- */
340
- export async function readPhase(runId) {
341
- const statePath = resolveReadPath(runId);
342
- if (!statePath) {
343
- return null;
344
- }
345
-
346
- const data = await readJsonFile(statePath);
347
- if (!data) {
348
- return null;
349
- }
350
-
351
- const record = phaseRecordFromData(data);
352
- if (!record) {
353
- return null;
354
- }
355
-
356
- if (!isWorkPhase(data.phase)) {
357
- await writeJsonAtomic(statePath, applyPhaseRecord(data, record));
358
- }
359
-
360
- return record;
361
- }
362
-
363
- /**
364
- * @param {string} runId
365
- * @param {WorkPhase} phase
366
- * @param {PhaseStatus} [status="active"]
367
- * @returns {Promise<void>}
368
- */
369
- export async function writePhase(runId, phase, status = "active") {
370
- if (!isWorkPhase(phase)) {
371
- throw new Error(`Invalid phase: ${phase}`);
372
- }
373
-
374
- if (!isPhaseStatus(status)) {
375
- throw new Error(`Invalid phase status: ${status}`);
376
- }
377
-
378
- const statePath = resolveWritePath(runId);
379
- const current = (await readJsonFile(statePath)) || {};
380
- const next = {
381
- ...current,
382
- run_id: current.run_id || runId,
383
- phase,
384
- phase_status: status,
385
- current_phase: phase.toLowerCase(),
386
- };
387
-
388
- if (status === "complete") {
389
- next.last_phase = phase;
390
- next.last_successful_phase = phase.toLowerCase();
391
- }
392
-
393
- await writeJsonAtomic(statePath, next);
394
- }
395
-
396
- /**
397
- * @param {string} runId
398
- * @param {string} slug
399
- * @returns {Promise<void>}
400
- */
401
- export async function syncToGstack(runId, slug) {
402
- const gstackRoot = join(getHomeDir(), ".gstack");
403
- if (!existsSync(gstackRoot)) {
404
- return;
405
- }
406
-
407
- const record = await readPhase(runId);
408
- if (!record) {
409
- return;
410
- }
411
-
412
- const phase = record.phase === "complete" ? record.lastPhase : record.phase;
413
- if (!phase) {
414
- return;
415
- }
416
-
417
- const checkpointsDir = join(gstackRoot, "projects", slug, "checkpoints");
418
- if (!existsSync(checkpointsDir)) {
419
- return;
420
- }
421
-
422
- const entries = await readdir(checkpointsDir, { withFileTypes: true });
423
- const markdownFiles = entries
424
- .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
425
- .map((entry) => join(checkpointsDir, entry.name));
426
-
427
- if (markdownFiles.length === 0) {
428
- return;
429
- }
430
-
431
- const latestFile = (
432
- await Promise.all(
433
- markdownFiles.map(async (filePath) => ({
434
- filePath,
435
- stats: await stat(filePath),
436
- })),
437
- )
438
- ).sort((left, right) => right.stats.mtimeMs - left.stats.mtimeMs)[0]
439
- ?.filePath;
440
-
441
- if (!latestFile) {
442
- return;
443
- }
444
-
445
- const currentContent = await readFile(latestFile, "utf8");
446
- const nextContent = injectFrontmatterFields(currentContent, {
447
- phase,
448
- triflux_run_id: runId,
449
- });
450
-
451
- if (!nextContent || nextContent === currentContent) {
452
- return;
453
- }
454
-
455
- await writeTextAtomic(latestFile, nextContent);
456
- }
457
-
458
- /**
459
- * @param {unknown} text
460
- * @returns {LegacyPhase | null}
461
- */
462
- export function coerceLegacyPhase(text) {
463
- if (text == null) {
464
- return null;
465
- }
466
-
467
- const value = String(text).trim();
468
- if (!value) {
469
- return null;
470
- }
471
-
472
- if (/^complete$/i.test(value)) {
473
- return "complete";
474
- }
475
-
476
- if (/interview|research|phase1/i.test(value)) {
477
- return "Research";
478
- }
479
-
480
- if (/plan|strategy|phase2/i.test(value)) {
481
- return "Strategy";
482
- }
483
-
484
- if (/exec|ship|phase3|phase4/i.test(value)) {
485
- return "Execution";
486
- }
487
-
488
- if (/valid|qa|review|phase5/i.test(value)) {
489
- return "Validation";
490
- }
491
-
492
- return null;
493
- }
@@ -1,53 +0,0 @@
1
- export const HIGH_PATTERNS = Object.freeze([
2
- /^hub\//,
3
- /^scripts\//,
4
- /^\.claude\/rules\//,
5
- /^bin\//,
6
- /^\.github\//,
7
- ]);
8
-
9
- export const MEDIUM_PATTERNS = Object.freeze([
10
- /package\.json$/,
11
- /\.ya?ml$/,
12
- /\.toml$/,
13
- /^config\//,
14
- /^hooks\//,
15
- ]);
16
-
17
- export const LOW_PATTERNS = Object.freeze([/\.md$/, /\.txt$/, /\.test\./]);
18
-
19
- export function classifyRiskTier({ changedFiles }) {
20
- const files = Array.isArray(changedFiles)
21
- ? changedFiles.filter((file) => typeof file === "string" && file.length > 0)
22
- : [];
23
-
24
- if (files.length === 0) {
25
- return "low";
26
- }
27
-
28
- if (matchesAny(files, HIGH_PATTERNS)) {
29
- return "high";
30
- }
31
-
32
- if (files.length > 1) {
33
- return "medium";
34
- }
35
-
36
- if (matchesAny(files, MEDIUM_PATTERNS)) {
37
- return "medium";
38
- }
39
-
40
- if (matchesPattern(files[0], LOW_PATTERNS)) {
41
- return "low";
42
- }
43
-
44
- return "medium";
45
- }
46
-
47
- function matchesAny(files, patterns) {
48
- return files.some((file) => matchesPattern(file, patterns));
49
- }
50
-
51
- function matchesPattern(file, patterns) {
52
- return patterns.some((pattern) => pattern.test(file));
53
- }