triflux 10.50.0 → 10.50.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.
Files changed (118) hide show
  1. package/CLAUDE.md +5 -5
  2. package/README.ko.md +5 -6
  3. package/README.md +5 -6
  4. package/bin/tfx-live.mjs +0 -47
  5. package/bin/triflux.mjs +3 -72
  6. package/cto/collect.mjs +16 -174
  7. package/cto/current.schema.json +4 -16
  8. package/cto/events.mjs +13 -294
  9. package/cto/hygiene.mjs +26 -346
  10. package/cto/index.mjs +5 -25
  11. package/cto/lake-root.mjs +114 -16
  12. package/cto/status.mjs +31 -78
  13. package/hub/lib/cto-env.mjs +0 -23
  14. package/hub/paths.mjs +0 -6
  15. package/hub/pipeline/index.mjs +0 -190
  16. package/hub/pipeline/transitions.mjs +0 -10
  17. package/hub/reflexion.mjs +1 -84
  18. package/hub/store-adapter.mjs +1 -1
  19. package/hub/team/conductor.mjs +1 -1
  20. package/hub/team/execution-mode.mjs +1 -1
  21. package/hub/team/headless.mjs +1 -1
  22. package/hub/team/psmux.mjs +16 -402
  23. package/hub/team/session.mjs +1 -28
  24. package/hub/team/terminal-opener.mjs +11 -35
  25. package/hud/cli-policy.mjs +1 -5
  26. package/hud/colors.mjs +0 -13
  27. package/hud/constants.mjs +4 -135
  28. package/hud/context-monitor.mjs +3 -64
  29. package/hud/hud-qos-status.mjs +43 -225
  30. package/hud/providers/claude.mjs +4 -16
  31. package/hud/providers/gemini.mjs +7 -698
  32. package/hud/renderers.mjs +40 -294
  33. package/hud/terminal.mjs +1 -63
  34. package/hud/utils.mjs +0 -24
  35. package/package.json +2 -4
  36. package/scripts/__tests__/release-governance.test.mjs +0 -1
  37. package/scripts/lib/gemini-profiles.mjs +1 -1
  38. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  39. package/scripts/lib/psmux-info.mjs +18 -49
  40. package/scripts/lib/skill-template.mjs +0 -26
  41. package/scripts/pack.mjs +2 -21
  42. package/scripts/release/bump-version.mjs +8 -31
  43. package/scripts/release/lib.mjs +6 -13
  44. package/scripts/release/prepare.mjs +1 -5
  45. package/scripts/setup.mjs +26 -30
  46. package/scripts/tfx-route-post.mjs +0 -1
  47. package/scripts/tfx-route.sh +0 -9
  48. package/skills/tfx-auto/SKILL.md +1 -3
  49. package/skills/tfx-ship/SKILL.md +1 -1
  50. package/bin/tfx-profile.mjs +0 -7
  51. package/cto/dashboard.mjs +0 -474
  52. package/cto/hygiene-actions.mjs +0 -355
  53. package/cto/hygiene-notify.mjs +0 -170
  54. package/cto/steward.mjs +0 -362
  55. package/hub/codex-compat.mjs +0 -12
  56. package/hub/fullcycle.mjs +0 -113
  57. package/hub/gemini-adapter.mjs +0 -174
  58. package/hub/intent.mjs +0 -321
  59. package/hub/lib/mcp-response-cache.mjs +0 -239
  60. package/hub/lib/path-utils.mjs +0 -165
  61. package/hub/lib/timeline-adapter.mjs +0 -190
  62. package/hub/log-retention.mjs +0 -726
  63. package/hub/pipeline/gates/confidence.mjs +0 -75
  64. package/hub/pipeline/gates/consensus.mjs +0 -106
  65. package/hub/pipeline/gates/index.mjs +0 -9
  66. package/hub/pipeline/gates/selfcheck.mjs +0 -120
  67. package/hub/quality/deslop.mjs +0 -274
  68. package/hub/research.mjs +0 -155
  69. package/hub/role-control-events.mjs +0 -113
  70. package/hub/routing/complexity.mjs +0 -269
  71. package/hub/routing/index.mjs +0 -122
  72. package/hub/routing/q-learning.mjs +0 -387
  73. package/hub/token-mode.mjs +0 -228
  74. package/hub/workers/gemini-worker.mjs +0 -217
  75. package/hud/mission-board.mjs +0 -56
  76. package/hud/providers/cto.mjs +0 -76
  77. package/references/cli-parameter-reference.md +0 -240
  78. package/scripts/__tests__/gen-skill-manifest.test.mjs +0 -81
  79. package/scripts/__tests__/skill-template.test.mjs +0 -293
  80. package/scripts/__tests__/smoke.test.mjs +0 -26
  81. package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +0 -223
  82. package/scripts/__tests__/tfx-route-node-entry.test.mjs +0 -449
  83. package/scripts/__tests__/tfx-route-phase1-modules.test.mjs +0 -268
  84. package/scripts/cache-buildup.mjs +0 -30
  85. package/scripts/cli-route.sh +0 -3
  86. package/scripts/gen-skill-manifest.mjs +0 -87
  87. package/scripts/lib/agent-json.mjs +0 -27
  88. package/scripts/lib/async.mjs +0 -174
  89. package/scripts/lib/cli-agy.mjs +0 -85
  90. package/scripts/lib/cli-codex.mjs +0 -62
  91. package/scripts/lib/cli-gemini.mjs +0 -37
  92. package/scripts/lib/env.mjs +0 -14
  93. package/scripts/lib/hub.mjs +0 -48
  94. package/scripts/lib/pid.mjs +0 -63
  95. package/scripts/lib/quota.mjs +0 -47
  96. package/scripts/lib/skill-state.mjs +0 -220
  97. package/scripts/lib/team.mjs +0 -75
  98. package/scripts/lib/timeout.mjs +0 -66
  99. package/scripts/lib/tmp.mjs +0 -26
  100. package/scripts/lib/toml.mjs +0 -132
  101. package/scripts/preinstall.mjs +0 -128
  102. package/scripts/session-spawn-helper.mjs +0 -261
  103. package/scripts/test-tfx-route-no-claude-native.mjs +0 -63
  104. package/scripts/tfx-batch-stats.mjs +0 -117
  105. package/scripts/tfx-route.mjs +0 -314
  106. package/scripts/token-snapshot.mjs +0 -696
  107. package/skills/tfx-auto/skill.json +0 -5
  108. package/skills/tfx-doctor/skill.json +0 -5
  109. package/skills/tfx-harness/skill.json +0 -5
  110. package/skills/tfx-live/skill.json +0 -5
  111. package/skills/tfx-remote/skill.json +0 -5
  112. package/skills/tfx-research/skill.json +0 -6
  113. package/skills/tfx-review/skill.json +0 -6
  114. package/skills/tfx-setup/skill.json +0 -5
  115. package/skills/tfx-ship/skill.json +0 -5
  116. package/skills/tfx-wt/skill.json +0 -5
  117. package/tui/codex-profile.mjs +0 -421
  118. package/tui/gemini-profile.mjs +0 -286
package/CLAUDE.md CHANGED
@@ -28,14 +28,14 @@ macOS/Linux는 플랫폼 보호기가 아무 작업도 하지 않게 처리하
28
28
 
29
29
  위 `<psmux-wt>` 룰셋은 Windows 전용이다. macOS/Linux 환경에서 triflux가 터미널/세션을 다루는 방식.
30
30
 
31
- ### terminal-opener.mjs 3단계 대체 경로(`hub/team/terminal-opener.mjs:124~149`)
31
+ ### terminal-opener.mjs 3단계 대체 경로(`hub/team/terminal-opener.mjs` 의 `openCommand`)
32
32
 
33
33
  `openCommand()` 가 순차 평가하는 분기:
34
34
 
35
35
  | 우선순위 | 조건 | 동작 | API |
36
36
  |---------|------|------|-----|
37
37
  | 1 | `platform === "win32"` | `wt-manager.createTab` | `hub/team/wt-manager.mjs` |
38
- | 2 | `isTmuxLikeMux(mux, platform)` (`detectMultiplexer()` → `getMultiplexerType()`/`hasMultiplexer()`/`hasTmux()` + 리터럴 psmux 검사) | `tmux new-window -n <title> <command>` | 셸 직접 호출 |
38
+ | 2 | `isTmuxLikeMux(mux)` (`detectMultiplexer()` → `getMultiplexerType()`/`hasMultiplexer()`/`hasTmux()`) | `tmux new-window -n <title> <command>` | 셸 직접 호출 |
39
39
  | 3 | `platform === "darwin"` (대체 경로) | `open -a Terminal` | macOS `open` 명령 |
40
40
  | — | Linux(멀티플렉서 없음) | 미지원(`false` 반환) | — |
41
41
 
@@ -43,9 +43,9 @@ macOS/Linux는 플랫폼 보호기가 아무 작업도 하지 않게 처리하
43
43
 
44
44
  | 후보 | 필요성 | 이유 |
45
45
  |------|--------|------|
46
- | iTerm2 관리자 | **불필요** | `hub/lib/env-detect.mjs:96`이 `TERM_PROGRAM === "iTerm.app"`을 감지하지만 별도 GUI 패인 조작은 tmux/psmux로 처리한다. 새 창은 `open -a Terminal` 대체 경로로 충분하다. |
47
- | tmux 관리자 | **불필요** | psmux 자체가 tmux 포크다. `terminal-opener.mjs`가 `tmux new-window`로 직접 호출한다. |
48
- | psmux 관리자 | **이미 있음** | `hub/team/psmux.mjs`가 `IS_WINDOWS`/`IS_MAC`으로 플랫폼을 분기한다. |
46
+ | iTerm2 관리자 | **불필요** | `hub/lib/env-detect.mjs:96`이 `TERM_PROGRAM === "iTerm.app"`을 감지하지만 별도 GUI 패인 조작은 tmux로 처리한다. 새 창은 `open -a Terminal` 대체 경로로 충분하다. |
47
+ | tmux 관리자 | **불필요** | `terminal-opener.mjs`가 `tmux new-window`로 직접 호출한다. |
48
+ | psmux 관리자 | **이미 있음** | `hub/team/psmux.mjs`가 Windows에서는 psmux, macOS/Linux에서는 tmux를 사용한다. |
49
49
 
50
50
  참고 사례: OMC(`oh-my-claudecode`)도 운영체제별 관리자를 만들지 않고 Tmux 관리자·워크트리 관리자·Claude 실행기 세 구성 요소로 정리한다. 이 저장소 결정의 외부 근거가 아니라 비교 참고로만 본다.
51
51
 
package/README.ko.md CHANGED
@@ -131,12 +131,11 @@ Codex는 이름 붙은 프로필로 실행한다. 모델 ID는 `~/.codex/<프로
131
131
  | `tfx hub` | 로컬 Hub: `start`, `stop`, `status`, `ensure` |
132
132
  | `tfx mcp` | 관리형 MCP 레지스트리: `list`, `sync`, `add`, `remove` |
133
133
  | `tfx handoff` | 현재 맥락을 다른 세션이나 호스트로 넘길 프롬프트로 묶음 |
134
- | `tfx cto` | 저장소 단위 권위 콘솔: `collect`, `status`, `dashboard`, `hygiene`, `steward`, `event` |
134
+ | `tfx cto` | 저장소 단위 권위 콘솔: `collect`, `status`, `hygiene`(dry-run) |
135
135
  | `tfx review` / `tfx codex-team` | Codex git diff 리뷰 / Codex 주도 팀 모드 |
136
136
  | `tfx stealth-fetch <url>` | cloakbrowser로 URL 하나를 가져옴(JSON을 stdout으로) |
137
137
  | `tfx notion-read`, `tfx why`, `tfx schema`, `tfx list`, `tfx monitor`, `tfx update`, `tfx version` | Notion → Markdown, 커밋 intent 트레일러, CLI 스키마, 설치된 스킬, TUI 모니터, 업데이트, 버전 |
138
138
  | `tfx-live` | 라이브 세션 브리지(`/tfx-live` 스킬과 같은 명령) |
139
- | `tfx-profile` | 대화형 Codex 프로필 관리자 |
140
139
 
141
140
  정확한 인자는 `tfx <명령> --help`나 `tfx schema <명령>`으로 확인한다.
142
141
 
@@ -168,10 +167,10 @@ Claude Code Bash 도구의 600초 제한에 걸리지 않는다. 이후 `--job-s
168
167
  다른 경로로 넘어가지 않고 실패한다. 자세한 내용은
169
168
  [`.claude/rules/tfx-machine-profile.md`](.claude/rules/tfx-machine-profile.md).
170
169
 
171
- **CTO lake.** `tfx cto`는 저장소에서 일어난 일을 `.triflux/lake/`에 덧붙이기 전용으로 남긴다. 자동
172
- 동작은 기본으로 꺼져 있고 `TFX_CTO_AUTO_COLLECT=1`이나 `TFX_CTO_NORTH_STAR=1`로
173
- 켠다([ADR-0018](docs/adr/0018-cto-auto-behaviors-opt-in.md)). 예전 트레이 UI는 제거됐다
174
- ([ADR-0022](docs/adr/0022-remove-cto-tray.md)).
170
+ **CTO lake.** `tfx cto collect`로 `.triflux/lake/`의 저장소 스냅샷을 갱신하고,
171
+ `tfx cto status`로 생성 시각과 경과 시간을 확인한다. `tfx cto hygiene --dry-run`는 dry-run 결과를
172
+ 보고한다. 트레이와 쓰이지 않는 CTO 운영 명령은 제거했다
173
+ ([ADR-0024](docs/adr/0024-cto-explicit-queries-only.md)). 자동 수집은 기본으로 꺼져 있고 `TFX_CTO_AUTO_COLLECT=1` 로 켠다([ADR-0018](docs/adr/0018-cto-auto-behaviors-opt-in.md)).
175
174
 
176
175
  **원격 호스트.** `/tfx-remote`와 `--remote <host>`는 `~/.config/triflux/hosts.json`
177
176
  (Windows는 `%APPDATA%\triflux\hosts.json`)에서 호스트를 읽는다. `/tfx-remote setup`으로 호스트를
package/README.md CHANGED
@@ -132,12 +132,11 @@ escalation chain: [`.claude/rules/tfx-escalation-chain.md`](.claude/rules/tfx-es
132
132
  | `tfx hub` | Local Hub: `start`, `stop`, `status`, `ensure` |
133
133
  | `tfx mcp` | Managed MCP registry: `list`, `sync`, `add`, `remove` |
134
134
  | `tfx handoff` | Serialize the current context for another session or host |
135
- | `tfx cto` | Repo-local authority console: `collect`, `status`, `dashboard`, `hygiene`, `steward`, `event` |
135
+ | `tfx cto` | Repo-local authority console: `collect`, `status`, `hygiene` (dry-run) |
136
136
  | `tfx review` / `tfx codex-team` | Codex git-diff review / Codex-led team mode |
137
137
  | `tfx stealth-fetch <url>` | Fetch one URL through cloakbrowser (JSON on stdout) |
138
138
  | `tfx notion-read`, `tfx why`, `tfx schema`, `tfx list`, `tfx monitor`, `tfx update`, `tfx version` | Notion → Markdown, commit intent trailers, CLI schemas, installed skills, TUI monitor, update, version |
139
139
  | `tfx-live` | Live session bridge (same as the `/tfx-live` skill) |
140
- | `tfx-profile` | Interactive Codex profile manager |
141
140
 
142
141
  `tfx <command> --help` and `tfx schema <command>` print the exact arguments.
143
142
 
@@ -169,10 +168,10 @@ the `claude agents` panel unless you pass `--no-native-bridge-ui`.
169
168
  removes a CLI from routing; if no allowed CLI is available the route fails instead of silently
170
169
  falling back. Details: [`.claude/rules/tfx-machine-profile.md`](.claude/rules/tfx-machine-profile.md).
171
170
 
172
- **CTO lake.** `tfx cto` keeps an append-only history of the repo in `.triflux/lake/`. Automatic
173
- behavior is off by default; opt in with `TFX_CTO_AUTO_COLLECT=1` or `TFX_CTO_NORTH_STAR=1`
174
- ([ADR-0018](docs/adr/0018-cto-auto-behaviors-opt-in.md)). The old tray UI was removed
175
- ([ADR-0022](docs/adr/0022-remove-cto-tray.md)).
171
+ **CTO lake.** Run `tfx cto collect` to refresh the repo snapshot in `.triflux/lake/`, then
172
+ `tfx cto status` to inspect it with its generation time and age. `tfx cto hygiene --dry-run` reports
173
+ dry-run findings. The tray and unused CTO operating commands were removed
174
+ ([ADR-0024](docs/adr/0024-cto-explicit-queries-only.md)). Automatic collection stays off unless `TFX_CTO_AUTO_COLLECT=1` is set ([ADR-0018](docs/adr/0018-cto-auto-behaviors-opt-in.md)).
176
175
 
177
176
  **Remote hosts.** `/tfx-remote` and `--remote <host>` read hosts from `~/.config/triflux/hosts.json`
178
177
  (Windows: `%APPDATA%\triflux\hosts.json`). Run `/tfx-remote setup` to add one, then
package/bin/tfx-live.mjs CHANGED
@@ -14,8 +14,6 @@ import { homedir, tmpdir } from "node:os";
14
14
  import { join as pathJoin, resolve as pathResolve } from "node:path";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { promisify } from "node:util";
17
- import { runHygiene } from "../cto/hygiene.mjs";
18
- import { notifyCtoHygieneOnce } from "../cto/hygiene-notify.mjs";
19
17
  import {
20
18
  codexThreadNames,
21
19
  readCodexSessionRecords,
@@ -26,7 +24,6 @@ import {
26
24
  findClaudeTranscript,
27
25
  readClaudeTranscript,
28
26
  } from "../hub/team/claude-transcript.mjs";
29
- import { createNotifier } from "../hub/team/notify.mjs";
30
27
  import {
31
28
  escapePwshSingleQuoted as escapeRemotePwshSingleQuoted,
32
29
  probeRemoteEnv as probeRemoteHostEnv,
@@ -100,8 +97,6 @@ function usage(command) {
100
97
  " tfx-live orchestrate --task TEXT [--mode peer|codex-led|claude-led] [--codex-transport exec|app-server-uds] [--codex-socket PATH|default] [--cwd DIR] [--timeout 120]",
101
98
  " Codex TUI may use a shared app-server daemon. UDS ask resumes the thread to receive answer events; sending to an active thread with --if-busy steer merges input into that turn.",
102
99
  " Runs the Claude(UDS)+Codex orchestration engine. --codex-transport app-server-uds drives a real `codex app-server` over WebSocket-over-UDS (experimental); default exec keeps the codex stdio one-shot path.",
103
- " tfx-live cto-hygiene-notify --root DIR --state-file PATH [--json]",
104
- " One-shot CTO hygiene dry-run notification: sends only when actionable hygiene state changes; no polling or apply/steward lock.",
105
100
  ];
106
101
  if (!command) return lines.join("\n");
107
102
  const selected = lines.filter(
@@ -4141,45 +4136,6 @@ function printJson(value) {
4141
4136
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
4142
4137
  }
4143
4138
 
4144
- async function ctoHygieneNotify(flags) {
4145
- const rootDir = flags.root ? pathResolve(flags.root) : process.cwd();
4146
- const stateFile = flags["state-file"]
4147
- ? pathResolve(flags["state-file"])
4148
- : pathJoin(rootDir, ".triflux", "cto-hygiene-notify-state.json");
4149
-
4150
- const projection = await runHygiene(["--dry-run", "--json"], {
4151
- rootDir,
4152
- dryRun: true,
4153
- json: false,
4154
- stdout: {
4155
- write() {
4156
- return true;
4157
- },
4158
- },
4159
- });
4160
-
4161
- const notifier = createNotifier({ stdout: process.stderr });
4162
- const result = await notifyCtoHygieneOnce(projection, {
4163
- notifier,
4164
- stateFile,
4165
- });
4166
- const payload = {
4167
- ok: true,
4168
- stateFile,
4169
- counts: projection.counts,
4170
- ...result,
4171
- };
4172
-
4173
- if (flags.json) {
4174
- printJson(payload);
4175
- return;
4176
- }
4177
-
4178
- process.stdout.write(
4179
- `cto hygiene notify: ${payload.reason} (${payload.hash})\n`,
4180
- );
4181
- }
4182
-
4183
4139
  async function main() {
4184
4140
  const { command, flags } = parseCli(process.argv.slice(2));
4185
4141
 
@@ -4214,8 +4170,6 @@ async function main() {
4214
4170
  await peer(flags);
4215
4171
  } else if (command === "orchestrate") {
4216
4172
  await orchestrate(flags);
4217
- } else if (command === "cto-hygiene-notify") {
4218
- await ctoHygieneNotify(flags);
4219
4173
  } else {
4220
4174
  throw new Error(`Unknown subcommand: ${command}\n${usage()}`);
4221
4175
  }
@@ -4230,7 +4184,6 @@ export {
4230
4184
  callRemoteLive,
4231
4185
  classifyPeerExitReason,
4232
4186
  createPeerSignalController,
4233
- ctoHygieneNotify,
4234
4187
  derivePeerStatus,
4235
4188
  discoverClaudeTmuxSessions,
4236
4189
  discoverCodexTmuxSessions,
package/bin/triflux.mjs CHANGED
@@ -476,62 +476,13 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
476
476
  },
477
477
  },
478
478
  cto: {
479
- usage: "tfx cto <collect|status|dashboard|hygiene|steward|event> [options]",
479
+ usage: "tfx cto <collect|status|hygiene> [options]",
480
480
  description: "repo-local authority layer console",
481
481
  subcommands: {
482
482
  collect: "refresh .triflux/lake/current.json from authority sources",
483
483
  status: "print the current authority summary",
484
- dashboard: "render the CTO console dashboard, optionally with --watch",
485
- hygiene: "project CTO hygiene counts and actionable dry-run rows",
486
- steward: {
487
- usage:
488
- "tfx cto steward [--watch] [--max-runs N] [--interval-ms N] [--apply|--dry-run] [--json] [--no-collect]",
489
- description:
490
- "periodically collect and invoke one-shot CTO hygiene without destructive cleanup",
491
- options: [
492
- {
493
- name: "--watch",
494
- type: "boolean",
495
- description:
496
- "run repeatedly until interrupted or --max-runs is reached",
497
- },
498
- {
499
- name: "--max-runs",
500
- type: "number",
501
- description:
502
- "maximum steward iterations; required for --watch --json",
503
- },
504
- {
505
- name: "--interval-ms",
506
- type: "number",
507
- description: "delay between watch iterations in milliseconds",
508
- },
509
- {
510
- name: "--apply",
511
- type: "boolean",
512
- description:
513
- "invoke hygiene apply mode (ledger acknowledgement only)",
514
- },
515
- {
516
- name: "--dry-run",
517
- type: "boolean",
518
- description: "invoke hygiene dry-run mode (default)",
519
- },
520
- {
521
- name: "--json",
522
- type: "boolean",
523
- description:
524
- "print one finite steward JSON summary object; with --watch requires --max-runs",
525
- },
526
- {
527
- name: "--no-collect",
528
- type: "boolean",
529
- description: "skip the collect pass before hygiene",
530
- },
531
- ],
532
- },
533
- event:
534
- "append wrapper lineage events: context-save, context-restore, pr-created, pr-merged-or-closed",
484
+ hygiene:
485
+ "project CTO hygiene counts and actionable dry-run rows (--dry-run)",
535
486
  },
536
487
  },
537
488
  multi: {
@@ -2583,26 +2534,6 @@ async function cmdDoctor(options = {}) {
2583
2534
  });
2584
2535
  warn("Codex 레이트 리밋 캐시 재생성 실패");
2585
2536
  }
2586
- try {
2587
- execFileSync(
2588
- process.execPath,
2589
- [hudScript, "--refresh-gemini-quota"],
2590
- { timeout: 15000, stdio: "ignore", windowsHide: true },
2591
- );
2592
- report.actions.push({
2593
- type: "rebuild",
2594
- name: "gemini-quota-cache",
2595
- status: "ok",
2596
- });
2597
- ok("Gemini 쿼터 캐시 재생성됨");
2598
- } catch {
2599
- report.actions.push({
2600
- type: "rebuild",
2601
- name: "gemini-quota-cache",
2602
- status: "failed",
2603
- });
2604
- warn("Gemini 쿼터 캐시 재생성 실패");
2605
- }
2606
2537
  }
2607
2538
  try {
2608
2539
  const { buildAll } = await import("../scripts/cache-warmup.mjs");
package/cto/collect.mjs CHANGED
@@ -1,23 +1,23 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import {
3
- appendFileSync,
4
- closeSync,
5
3
  existsSync,
6
4
  mkdirSync,
7
- openSync,
8
5
  readdirSync,
9
6
  readFileSync,
10
- renameSync,
11
7
  statSync,
12
- unlinkSync,
13
- writeFileSync,
14
8
  } from "node:fs";
15
9
  import { homedir } from "node:os";
16
- import { basename, dirname, join, relative } from "node:path";
10
+ import { join, relative } from "node:path";
17
11
  import { fileURLToPath } from "node:url";
18
12
 
19
13
  import { renderBrief } from "./brief.mjs";
20
- import { resolveLakeRootDir } from "./lake-root.mjs";
14
+ import { appendCtoEvent } from "./events.mjs";
15
+ import {
16
+ firstExisting,
17
+ readJsonLines,
18
+ resolveLakeRootDir,
19
+ writeAtomic,
20
+ } from "./lake-root.mjs";
21
21
 
22
22
  const SCHEMA_VERSION = "cto-lake.v1";
23
23
  const SOURCE_REGISTRY = [
@@ -69,24 +69,6 @@ const SOURCE_REGISTRY = [
69
69
  probe: ".omx/handoffs/*",
70
70
  enabled: true,
71
71
  },
72
- {
73
- id: "session_vault",
74
- kind: "optional-durable-ref",
75
- probe: ".triflux/session-vault.json, sessions_v2.db, or .session-vault",
76
- enabled: true,
77
- },
78
- {
79
- id: "agy",
80
- kind: "optional-durable-ref",
81
- probe: ".agy/state.json or .agy/refs.json",
82
- enabled: true,
83
- },
84
- {
85
- id: "gbrain",
86
- kind: "optional-durable-ref",
87
- probe: ".gbrain/refs.json or .gbrain/config.json",
88
- enabled: true,
89
- },
90
72
  ];
91
73
  const SOURCE_IDS = SOURCE_REGISTRY.map((source) => source.id);
92
74
 
@@ -104,10 +86,6 @@ function isoNow(opts) {
104
86
  return value.toISOString();
105
87
  }
106
88
 
107
- function sleep(ms) {
108
- return new Promise((resolve) => setTimeout(resolve, ms));
109
- }
110
-
111
89
  function sourceState(available, status, detail, collectedAt) {
112
90
  return {
113
91
  available,
@@ -130,25 +108,6 @@ function readJson(filePath) {
130
108
  return JSON.parse(readFileSync(filePath, "utf8"));
131
109
  }
132
110
 
133
- function readJsonLines(filePath, limit = 20) {
134
- if (!existsSync(filePath)) return [];
135
- const lines = readFileSync(filePath, "utf8")
136
- .split(/\r?\n/u)
137
- .map((line) => line.trim())
138
- .filter(Boolean);
139
- return lines.slice(-limit).flatMap((line) => {
140
- try {
141
- return [JSON.parse(line)];
142
- } catch {
143
- return [];
144
- }
145
- });
146
- }
147
-
148
- function findFirstExisting(candidates) {
149
- return candidates.find((candidate) => existsSync(candidate)) || null;
150
- }
151
-
152
111
  function listDirFiles(dirPath, rootDir, limit = 10) {
153
112
  if (!existsSync(dirPath)) return [];
154
113
  try {
@@ -237,7 +196,7 @@ function collectJsonArtifact(
237
196
  collectedAt,
238
197
  summarize = safeSummary,
239
198
  ) {
240
- const filePath = findFirstExisting(candidates);
199
+ const filePath = firstExisting(candidates);
241
200
  if (!filePath) {
242
201
  return missingSource(
243
202
  "no_shell_readable_artifact",
@@ -429,59 +388,6 @@ function collectHandoffs(rootDir, collectedAt) {
429
388
  );
430
389
  }
431
390
 
432
- function collectDurableRefs(
433
- id,
434
- candidates,
435
- rootDir,
436
- collectedAt,
437
- unavailableStatus = "no_shell_readable_artifact",
438
- ) {
439
- const filePath = findFirstExisting(candidates);
440
- if (!filePath) {
441
- return missingSource(
442
- unavailableStatus,
443
- `${id} exposes no shell-readable durable artifact`,
444
- collectedAt,
445
- );
446
- }
447
- try {
448
- const stat = statSync(filePath);
449
- if (stat.isDirectory()) {
450
- return sourceState(
451
- true,
452
- "ok",
453
- {
454
- path: relPath(rootDir, filePath),
455
- files: listDirFiles(filePath, rootDir, 10),
456
- },
457
- collectedAt,
458
- );
459
- }
460
- const parsed = basename(filePath).endsWith(".json")
461
- ? readJson(filePath)
462
- : readFileSync(filePath, "utf8");
463
- return sourceState(
464
- true,
465
- "ok",
466
- {
467
- path: relPath(rootDir, filePath),
468
- summary: safeSummary(parsed),
469
- },
470
- collectedAt,
471
- );
472
- } catch (error) {
473
- return sourceState(
474
- true,
475
- "read_error",
476
- {
477
- path: relPath(rootDir, filePath),
478
- error: error?.message || `${id} read failed`,
479
- },
480
- collectedAt,
481
- );
482
- }
483
- }
484
-
485
391
  function buildSummary(current) {
486
392
  const repo = current.repo;
487
393
  const omxGoals = current.sources.ultragoal_omx.detail?.active_goals || [];
@@ -633,48 +539,6 @@ function validateCurrent(current) {
633
539
  }
634
540
  }
635
541
 
636
- function writeAtomic(filePath, body) {
637
- mkdirSync(dirname(filePath), { recursive: true });
638
- const tmpPath = join(
639
- dirname(filePath),
640
- `.${basename(filePath)}.${process.pid}.${Date.now()}.tmp`,
641
- );
642
- writeFileSync(tmpPath, body, "utf8");
643
- renameSync(tmpPath, filePath);
644
- }
645
-
646
- async function appendLedgerEvent(lakeRoot, event, stderr) {
647
- mkdirSync(lakeRoot, { recursive: true });
648
- const ledgerPath = join(lakeRoot, "ledger.jsonl");
649
- const lockPath = join(lakeRoot, "ledger.jsonl.lock");
650
- let fd = null;
651
- for (let attempt = 0; attempt < 3; attempt += 1) {
652
- try {
653
- fd = openSync(lockPath, "wx", 0o600);
654
- break;
655
- } catch (error) {
656
- if (error?.code !== "EEXIST") throw error;
657
- if (attempt < 2) await sleep(100);
658
- }
659
- }
660
- if (fd === null) {
661
- stderr.write(
662
- "[tfx cto collect] warning: ledger lock timeout; skipped ledger append\n",
663
- );
664
- return false;
665
- }
666
-
667
- try {
668
- appendFileSync(ledgerPath, `${JSON.stringify(event)}\n`, "utf8");
669
- return true;
670
- } finally {
671
- closeSync(fd);
672
- try {
673
- unlinkSync(lockPath);
674
- } catch {}
675
- }
676
- }
677
-
678
542
  function collectSources(rootDir, collectedAt, execFileSyncFn, opts = {}) {
679
543
  const home = homedir();
680
544
  const hostArtifactCandidates =
@@ -748,34 +612,6 @@ function collectSources(rootDir, collectedAt, execFileSyncFn, opts = {}) {
748
612
  collectedAt,
749
613
  ),
750
614
  handoffs: collectHandoffs(rootDir, collectedAt),
751
- session_vault: collectDurableRefs(
752
- "session_vault",
753
- [
754
- join(rootDir, ".triflux", "session-vault.json"),
755
- join(rootDir, "sessions_v2.db"),
756
- join(rootDir, ".session-vault"),
757
- ],
758
- rootDir,
759
- collectedAt,
760
- "not_shell_collectable",
761
- ),
762
- agy: collectDurableRefs(
763
- "agy",
764
- [join(rootDir, ".agy", "state.json"), join(rootDir, ".agy", "refs.json")],
765
- rootDir,
766
- collectedAt,
767
- "not_shell_collectable",
768
- ),
769
- gbrain: collectDurableRefs(
770
- "gbrain",
771
- [
772
- join(rootDir, ".gbrain", "refs.json"),
773
- join(rootDir, ".gbrain", "config.json"),
774
- ],
775
- rootDir,
776
- collectedAt,
777
- "not_shell_collectable",
778
- ),
779
615
  };
780
616
 
781
617
  return { repo: git.repo, sources };
@@ -786,6 +622,12 @@ function hasFlag(args, flag) {
786
622
  }
787
623
 
788
624
  export async function runCollect(args = [], opts = {}) {
625
+ if (hasFlag(args, "--help")) {
626
+ (opts.stdout || process.stdout).write(
627
+ "Usage: tfx cto collect [--json]\nRefresh the CTO lake from durable authority sources.\n",
628
+ );
629
+ return;
630
+ }
789
631
  const rootDir = opts.rootDir || resolveLakeRootDir(process.cwd());
790
632
  const lakeRoot = opts.lakeRoot || join(rootDir, ".triflux", "lake");
791
633
  const stdout = opts.stdout || process.stdout;
@@ -822,7 +664,7 @@ export async function runCollect(args = [], opts = {}) {
822
664
  sources_json: "sources.json",
823
665
  },
824
666
  };
825
- const appended = await appendLedgerEvent(lakeRoot, event, stderr);
667
+ const { appended } = await appendCtoEvent(lakeRoot, event, { stderr });
826
668
  if (appended) current.ledger_tail = readLedgerTail(lakeRoot);
827
669
 
828
670
  validateCurrent(current);
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://triflux.local/schemas/cto/current.schema.json",
4
4
  "title": "Triflux CTO Lake Current Authority State",
5
- "description": "Contract for .triflux/lake/current.json. .triflux/lake is a triflux-owned, namespace-neutral cross-agent rollup for durable artifacts across Claude, Codex/OMX, OMC, AGY, gstack, gbrain, and Triflux state. It reads and aggregates .omx/ultragoal and .omc/ultragoal execution ledgers without replacing or duplicating their authority. ledger.jsonl and sources.json companion contracts are documented in $defs.",
5
+ "description": "Contract for .triflux/lake/current.json. .triflux/lake is a triflux-owned, namespace-neutral cross-agent rollup for durable artifacts across Claude, Codex/OMX, OMC, and Triflux state. It reads and aggregates .omx/ultragoal and .omc/ultragoal execution ledgers without replacing or duplicating their authority. ledger.jsonl and sources.json companion contracts are documented in $defs.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "sources": {
46
46
  "type": "object",
47
- "description": "Durable artifact source states only. Live Claude /goal state and AGY internal state are not shell-collectable; a source reports available:false when no shell-readable durable artifact exists.",
47
+ "description": "Durable artifact source states only. A source reports available:false when no shell-readable durable artifact exists.",
48
48
  "additionalProperties": false,
49
49
  "required": [
50
50
  "git",
@@ -54,10 +54,7 @@
54
54
  "tfx_synapse",
55
55
  "ultragoal_omx",
56
56
  "ultragoal_omc",
57
- "handoffs",
58
- "session_vault",
59
- "agy",
60
- "gbrain"
57
+ "handoffs"
61
58
  ],
62
59
  "properties": {
63
60
  "git": {
@@ -83,21 +80,12 @@
83
80
  },
84
81
  "handoffs": {
85
82
  "$ref": "#/$defs/sourceState"
86
- },
87
- "session_vault": {
88
- "$ref": "#/$defs/sourceState"
89
- },
90
- "agy": {
91
- "$ref": "#/$defs/sourceState"
92
- },
93
- "gbrain": {
94
- "$ref": "#/$defs/sourceState"
95
83
  }
96
84
  }
97
85
  },
98
86
  "summary": {
99
87
  "type": "object",
100
- "description": "Human- and dashboard-oriented synthesized authority summary derived from durable artifacts and live Triflux synapse session status. Phase 1 may add source-derived fields here without changing the top-level contract.",
88
+ "description": "Human-oriented synthesized authority summary derived from durable artifacts and live Triflux synapse session status. Phase 1 may add source-derived fields here without changing the top-level contract.",
101
89
  "additionalProperties": true
102
90
  },
103
91
  "ledger_tail": {