triflux 10.4.0 → 10.7.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.
- package/.claude-plugin/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +22 -0
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/config/mcp-registry.json +29 -0
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +267 -256
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +96 -58
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +163 -126
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +202 -143
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +924 -791
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +127 -61
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +57 -37
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +95 -74
- package/hub/team/cli/commands/focus.mjs +80 -53
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +31 -24
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
- package/hub/team/cli/commands/start/start-mux.mjs +122 -73
- package/hub/team/cli/commands/start/start-wt.mjs +86 -69
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +52 -30
- package/hub/team/cli/services/attach-fallback.mjs +74 -54
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +37 -30
- package/hub/team/cli/services/native-control.mjs +143 -116
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +40 -30
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +120 -77
- package/hub/team/orchestrator.mjs +166 -161
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +675 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +411 -368
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +558 -507
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +21 -21
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +56 -21
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +139 -120
- package/scripts/keyword-detector.mjs +293 -272
- package/scripts/keyword-rules-expander.mjs +538 -521
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +355 -118
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +669 -554
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +61 -57
- package/scripts/tfx-batch-stats.mjs +117 -96
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/idle-notif-cooldown.json +1 -1
- package/skills/.omc/state/last-tool-error.json +1 -1
- package/skills/.omc/state/subagent-tracking.json +1 -1
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/{tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md → tfx-auto-codex/SKILL.md.tmpl} +33 -4
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/{tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md → tfx-gemini/SKILL.md.tmpl} +16 -7
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -16
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +457 -0
- package/tui/core.mjs +266 -0
- package/tui/doctor.mjs +375 -0
- package/tui/gemini-profile.mjs +299 -0
- package/tui/monitor-data.mjs +152 -0
- package/tui/monitor.mjs +339 -0
- package/tui/setup.mjs +533 -0
- package/CLAUDE.md +0 -193
- package/references/hosts.json +0 -33
- package/skills/tfx-workspace/async-tests/run-tests.sh +0 -203
- package/skills/tfx-workspace/evals/evals.json +0 -79
- package/skills/tfx-workspace/iteration-1/benchmark.json +0 -162
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +0 -9
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +0 -154
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +0 -9
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +0 -126
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +0 -9
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +0 -119
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +0 -9
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +0 -115
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +0 -10
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +0 -8
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +0 -86
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +0 -8
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +0 -81
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +0 -10
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +0 -316
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +0 -10
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +0 -352
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/review.html +0 -1325
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +0 -10
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +0 -97
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +0 -10
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +0 -94
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +0 -10
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +0 -209
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +0 -10
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +0 -193
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/benchmark.json +0 -62
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +0 -13
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +0 -11
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +0 -382
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +0 -11
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +0 -333
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/review.html +0 -1325
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +0 -217
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +0 -65
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +0 -94
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +0 -133
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +0 -426
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +0 -101
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* headless-guard.mjs — PreToolUse 훅 (상시 활성 auto-route)
|
|
4
5
|
*
|
|
@@ -24,11 +25,11 @@
|
|
|
24
25
|
* 성능: psmux 감지 결과를 5분간 캐시 ($TMPDIR/tfx-psmux-check.json)
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
28
28
|
import { execFileSync } from "node:child_process";
|
|
29
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
29
30
|
import { tmpdir } from "node:os";
|
|
30
31
|
import { join } from "node:path";
|
|
31
|
-
import {
|
|
32
|
+
import { deny, nudge } from "./lib/hook-utils.mjs";
|
|
32
33
|
import { probePsmuxSupport } from "./lib/psmux-info.mjs";
|
|
33
34
|
|
|
34
35
|
const CACHE_FILE = join(tmpdir(), "tfx-psmux-check.json");
|
|
@@ -37,8 +38,8 @@ const CACHE_TTL_MS = 5 * 60 * 1000; // 5분
|
|
|
37
38
|
// ── tfx-multi 상태 관리 (A+B) ──
|
|
38
39
|
const MULTI_STATE_FILE = join(tmpdir(), "tfx-multi-state.json");
|
|
39
40
|
const MULTI_EXPIRE_MS = 30 * 60 * 1000; // 30분 자동 만료
|
|
40
|
-
const GATE_THRESHOLD = 2;
|
|
41
|
-
const NUDGE_THRESHOLD = 4;
|
|
41
|
+
const GATE_THRESHOLD = 2; // A: dispatch 전 허용할 Agent 호출 수
|
|
42
|
+
const NUDGE_THRESHOLD = 4; // B: dispatch 후 nudge 트리거 횟수
|
|
42
43
|
|
|
43
44
|
function readMultiState() {
|
|
44
45
|
try {
|
|
@@ -47,17 +48,25 @@ function readMultiState() {
|
|
|
47
48
|
if (!state.active) return null;
|
|
48
49
|
// 자동 만료
|
|
49
50
|
if (Date.now() - state.activatedAt > MULTI_EXPIRE_MS) {
|
|
50
|
-
try {
|
|
51
|
+
try {
|
|
52
|
+
unlinkSync(MULTI_STATE_FILE);
|
|
53
|
+
} catch {
|
|
54
|
+
/* ignore */
|
|
55
|
+
}
|
|
51
56
|
return null;
|
|
52
57
|
}
|
|
53
58
|
return state;
|
|
54
|
-
} catch {
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
function writeMultiState(state) {
|
|
58
65
|
try {
|
|
59
66
|
writeFileSync(MULTI_STATE_FILE, JSON.stringify(state));
|
|
60
|
-
} catch {
|
|
67
|
+
} catch {
|
|
68
|
+
/* ignore */
|
|
69
|
+
}
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
function isPsmuxInstalled() {
|
|
@@ -69,15 +78,27 @@ function isPsmuxInstalled() {
|
|
|
69
78
|
if (age >= 0 && age < CACHE_TTL_MS) return cache.ok;
|
|
70
79
|
// age < 0 → 미래 ts (오염) → 캐시 무시하고 재검사
|
|
71
80
|
}
|
|
72
|
-
} catch {
|
|
81
|
+
} catch {
|
|
82
|
+
/* cache miss */
|
|
83
|
+
}
|
|
73
84
|
|
|
74
85
|
const probe = probePsmuxSupport({ execFileSyncFn: execFileSync });
|
|
75
86
|
const ok = probe.ok;
|
|
76
87
|
|
|
77
88
|
// 캐시 저장
|
|
78
89
|
try {
|
|
79
|
-
writeFileSync(
|
|
80
|
-
|
|
90
|
+
writeFileSync(
|
|
91
|
+
CACHE_FILE,
|
|
92
|
+
JSON.stringify({
|
|
93
|
+
ts: Date.now(),
|
|
94
|
+
ok,
|
|
95
|
+
version: probe.version,
|
|
96
|
+
missingCommands: probe.missingCommands,
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
} catch {
|
|
100
|
+
/* ignore */
|
|
101
|
+
}
|
|
81
102
|
|
|
82
103
|
return ok;
|
|
83
104
|
}
|
|
@@ -116,7 +137,7 @@ function parseRouteCommand(cmd) {
|
|
|
116
137
|
// v3: 원본 명령에서 추가 플래그 추출
|
|
117
138
|
const flags = {};
|
|
118
139
|
const afterPrompt = cmd.replace(/'.+?'/gs, "").replace(/".+?"/gs, "");
|
|
119
|
-
const timeoutMatch = afterPrompt.match(/(?:^|\s)(\d{2,4})(?:\s|$)/);
|
|
140
|
+
const timeoutMatch = afterPrompt.match(/(?:^|\s)(\d{2,4})(?:\s|$)/); // 4번째 인자 (timeout)
|
|
120
141
|
if (timeoutMatch) flags.timeout = parseInt(timeoutMatch[1], 10);
|
|
121
142
|
|
|
122
143
|
// 환경변수 기반 글로벌 플래그
|
|
@@ -127,18 +148,20 @@ function parseRouteCommand(cmd) {
|
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
function autoRoute(updatedCommand, reason) {
|
|
130
|
-
process.stdout.write(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
process.stdout.write(
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
hookSpecificOutput: {
|
|
154
|
+
hookEventName: "PreToolUse",
|
|
155
|
+
updatedInput: { command: updatedCommand },
|
|
156
|
+
additionalContext: reason,
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
137
160
|
process.exit(0);
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
const HEADLESS_FALLBACK_COMMAND =
|
|
141
|
-
|
|
164
|
+
"Bash(\"tfx multi --teammate-mode headless --assign 'codex:prompt:role' ...\")";
|
|
142
165
|
const DIRECT_CLI_BYPASS_HINT =
|
|
143
166
|
"로컬 디버깅이 목적이면 TFX_ALLOW_DIRECT_CLI=1로 일시 우회할 수 있습니다.";
|
|
144
167
|
|
|
@@ -153,7 +176,7 @@ async function main() {
|
|
|
153
176
|
for await (const chunk of process.stdin) raw += chunk;
|
|
154
177
|
|
|
155
178
|
if (!raw?.trim()) {
|
|
156
|
-
console.error(
|
|
179
|
+
console.error("[headless-guard] stdin이 비어있습니다 — 기본 허용");
|
|
157
180
|
process.exit(0);
|
|
158
181
|
}
|
|
159
182
|
|
|
@@ -192,8 +215,8 @@ async function main() {
|
|
|
192
215
|
if (/\b(codex\s+exec|gemini\s+(-p|--prompt))\b/i.test(cmd)) {
|
|
193
216
|
deny(
|
|
194
217
|
"[headless-guard] psmux send-keys/split-window에 codex/gemini 직접 호출이 포함되어 있습니다. " +
|
|
195
|
-
|
|
196
|
-
|
|
218
|
+
`승인된 경로: ${HEADLESS_FALLBACK_COMMAND}. ` +
|
|
219
|
+
DIRECT_CLI_BYPASS_HINT,
|
|
197
220
|
);
|
|
198
221
|
}
|
|
199
222
|
process.exit(0);
|
|
@@ -205,7 +228,7 @@ async function main() {
|
|
|
205
228
|
// #37 Bug4: gh/git 명령은 본문에 codex/gemini 문자열이 있어도 차단하지 않음
|
|
206
229
|
const SAFE_CMD_RE = /^\s*(?:[\w_]+=\S+\s+)*\s*(gh|git)\b/;
|
|
207
230
|
const cmdParts = cmd.split(/\s*(?:&&|\|\||\||;)\s*/);
|
|
208
|
-
let hasDirectCli = cmdParts.some(part => {
|
|
231
|
+
let hasDirectCli = cmdParts.some((part) => {
|
|
209
232
|
// gh/git 세그먼트는 건너뜀 (이슈 본문/커밋 메시지 내 codex/gemini 언급은 정상)
|
|
210
233
|
if (SAFE_CMD_RE.test(part)) return false;
|
|
211
234
|
// 1단계: env var prefix 제거 (FOO=bar ...)
|
|
@@ -214,24 +237,31 @@ async function main() {
|
|
|
214
237
|
.replace(/^\s*(?:[\w_]+=\S+\s+)*/, "")
|
|
215
238
|
.replace(/^\s*(?:env|command|nohup)\s+/, "")
|
|
216
239
|
.replace(/^\s*timeout\s+\d+\s+/, "")
|
|
217
|
-
.replace(
|
|
240
|
+
.replace(
|
|
241
|
+
/^\s*(?:bash|sh)\s+(?:-\w+\s+)*(?:"([^"]*)".*|'([^']*)'.*)/i,
|
|
242
|
+
"$1$2",
|
|
243
|
+
)
|
|
218
244
|
.replace(/^\s*(?:\/[\w./+-]+\/)(codex|gemini)\b/, " $1");
|
|
219
|
-
return
|
|
245
|
+
return (
|
|
246
|
+
/^\s*codex\b.*\bexec\b/i.test(stripped) ||
|
|
247
|
+
/^\s*gemini\s+(-p|--prompt)\b/i.test(stripped)
|
|
248
|
+
);
|
|
220
249
|
});
|
|
221
250
|
// 2차 휴리스틱: 1차 세그먼트 검사를 통과한 간접 실행 패턴 탐지
|
|
222
251
|
// full AST 파서 대신 현실적 위협 벡터만 커버 — eval, subshell, variable 확장
|
|
223
252
|
// 2차 휴리스틱: 간접 실행 패턴 탐지 (eval, subshell, variable 확장)
|
|
224
253
|
if (!hasDirectCli) {
|
|
225
|
-
const isAllSafeCmd = cmdParts.every(p => SAFE_CMD_RE.test(p));
|
|
254
|
+
const isAllSafeCmd = cmdParts.every((p) => SAFE_CMD_RE.test(p));
|
|
226
255
|
if (isAllSafeCmd) {
|
|
227
256
|
// gh/git 전용: $(codex exec ...) 직접 명령 치환만 차단
|
|
228
257
|
// $(cat <<'EOF'\n...codex exec text...\nEOF) 같은 heredoc 텍스트는 허용
|
|
229
|
-
hasDirectCli = /\$\(\s*(codex\s+exec|gemini\s+(-p|--prompt))\b/i.test(
|
|
258
|
+
hasDirectCli = /\$\(\s*(codex\s+exec|gemini\s+(-p|--prompt))\b/i.test(
|
|
259
|
+
cmd,
|
|
260
|
+
);
|
|
230
261
|
} else {
|
|
231
|
-
hasDirectCli =
|
|
262
|
+
hasDirectCli =
|
|
232
263
|
/\beval\b.*\b(codex\s+exec|gemini\s+(-p|--prompt))\b/i.test(cmd) ||
|
|
233
|
-
/\$[({].*\b(codex\s+exec|gemini\s+(-p|--prompt))\b/i.test(cmd)
|
|
234
|
-
);
|
|
264
|
+
/\$[({].*\b(codex\s+exec|gemini\s+(-p|--prompt))\b/i.test(cmd);
|
|
235
265
|
}
|
|
236
266
|
}
|
|
237
267
|
|
|
@@ -242,15 +272,17 @@ async function main() {
|
|
|
242
272
|
}
|
|
243
273
|
deny(
|
|
244
274
|
"[headless-guard] codex/gemini 직접 호출(파이프/복합 명령 포함)은 headless-guard에서 차단됩니다. " +
|
|
245
|
-
|
|
246
|
-
|
|
275
|
+
`승인된 경로: ${HEADLESS_FALLBACK_COMMAND}. ` +
|
|
276
|
+
DIRECT_CLI_BYPASS_HINT,
|
|
247
277
|
);
|
|
248
278
|
}
|
|
249
279
|
|
|
250
280
|
// tfx-route.sh 실행만 감지: 명령이 bash로 시작할 때만 (커밋 메시지/echo 등 무시)
|
|
251
281
|
if (/^\s*bash\s+.*tfx-route\.sh\s/.test(cmd)) {
|
|
252
282
|
// --async, --job-status, --job-result, --job-wait는 tfx-route.sh 내부 플래그 → 통과
|
|
253
|
-
if (
|
|
283
|
+
if (
|
|
284
|
+
/tfx-route\.sh\s+--(async|job-status|job-result|job-wait)\b/.test(cmd)
|
|
285
|
+
) {
|
|
254
286
|
process.exit(0);
|
|
255
287
|
}
|
|
256
288
|
|
|
@@ -261,7 +293,7 @@ async function main() {
|
|
|
261
293
|
if (!process.env.TFX_FORCE_HEADLESS) {
|
|
262
294
|
const isMultiWorker = /\s--(multi|parallel)\b/.test(cmd);
|
|
263
295
|
if (!isMultiWorker) {
|
|
264
|
-
process.exit(0);
|
|
296
|
+
process.exit(0); // 원본 tfx-route.sh 명령 그대로 통과
|
|
265
297
|
}
|
|
266
298
|
}
|
|
267
299
|
|
|
@@ -272,10 +304,11 @@ async function main() {
|
|
|
272
304
|
// v3: 플래그 빌더 — 하드코딩 제거, 원본 의도 보존
|
|
273
305
|
const parts = ["tfx multi --teammate-mode headless"];
|
|
274
306
|
if (!f.noAutoAttach) parts.push("--auto-attach");
|
|
275
|
-
if (!f.noAutoAttach) parts.push("--dashboard");
|
|
307
|
+
if (!f.noAutoAttach) parts.push("--dashboard"); // 워커 요약 스플릿이 기본
|
|
276
308
|
if (f.verbose) parts.push("--verbose");
|
|
277
309
|
parts.push(`--assign '${parsed.agent}:${safePrompt}:${parsed.agent}'`);
|
|
278
|
-
if (parsed.mcp && VALID_MCP.has(parsed.mcp))
|
|
310
|
+
if (parsed.mcp && VALID_MCP.has(parsed.mcp))
|
|
311
|
+
parts.push(`--mcp-profile ${parsed.mcp}`);
|
|
279
312
|
parts.push(`--timeout ${f.timeout || 600}`);
|
|
280
313
|
|
|
281
314
|
const builtCmd = parts.join(" ");
|
|
@@ -286,7 +319,7 @@ async function main() {
|
|
|
286
319
|
}
|
|
287
320
|
deny(
|
|
288
321
|
"[headless-guard] tfx-route.sh를 headless로 변환 실패. " +
|
|
289
|
-
|
|
322
|
+
"Bash(\"tfx multi --teammate-mode headless --assign 'cli:prompt:role' ...\") 형식을 사용하세요.",
|
|
290
323
|
);
|
|
291
324
|
}
|
|
292
325
|
}
|
|
@@ -307,24 +340,27 @@ async function main() {
|
|
|
307
340
|
if (multiState.nativeWorkCalls > GATE_THRESHOLD) {
|
|
308
341
|
deny(
|
|
309
342
|
`[headless-guard] tfx-multi gate: ${toolName} 호출 ${multiState.nativeWorkCalls}회 — headless dispatch 먼저 하세요.\n` +
|
|
310
|
-
|
|
343
|
+
"Bash(\"tfx multi --teammate-mode headless --auto-attach --dashboard --assign 'codex:프롬프트:역할' --timeout 600\")",
|
|
311
344
|
);
|
|
312
345
|
}
|
|
313
346
|
|
|
314
347
|
nudge(
|
|
315
348
|
`[headless-guard] tfx-multi 활성 (${multiState.nativeWorkCalls}/${GATE_THRESHOLD}). ` +
|
|
316
|
-
|
|
349
|
+
"headless dispatch 후 작업을 시작하세요.",
|
|
317
350
|
);
|
|
318
351
|
}
|
|
319
352
|
|
|
320
353
|
// H3 fix: Agent gate와 동일하게 NUDGE_THRESHOLD 기반 주기적 nudge
|
|
321
|
-
multiState.nativeWorkCallsSinceDispatch =
|
|
354
|
+
multiState.nativeWorkCallsSinceDispatch =
|
|
355
|
+
(multiState.nativeWorkCallsSinceDispatch || 0) + 1;
|
|
322
356
|
writeMultiState(multiState);
|
|
323
357
|
|
|
324
358
|
if (multiState.nativeWorkCallsSinceDispatch >= NUDGE_THRESHOLD) {
|
|
325
359
|
multiState.nativeWorkCallsSinceDispatch = 0;
|
|
326
360
|
writeMultiState(multiState);
|
|
327
|
-
nudge(
|
|
361
|
+
nudge(
|
|
362
|
+
"[headless-guard] nudge: headless 워커가 코드 수정 중. 직접 수정은 충돌 위험.",
|
|
363
|
+
);
|
|
328
364
|
}
|
|
329
365
|
process.exit(0); // threshold 미만이면 조용히 통과
|
|
330
366
|
}
|
|
@@ -333,7 +369,8 @@ async function main() {
|
|
|
333
369
|
if (toolName === "Agent") {
|
|
334
370
|
const subType = (toolInput.subagent_type || "").toLowerCase();
|
|
335
371
|
const NATIVE_TYPES = new Set(["explore", "plan", "general-purpose", ""]);
|
|
336
|
-
const isNative =
|
|
372
|
+
const isNative =
|
|
373
|
+
NATIVE_TYPES.has(subType) || subType.startsWith("oh-my-claudecode:");
|
|
337
374
|
|
|
338
375
|
// ── A+B: tfx-multi 상태 기반 처리 ──
|
|
339
376
|
const multiState = readMultiState();
|
|
@@ -346,17 +383,18 @@ async function main() {
|
|
|
346
383
|
if (multiState.nativeWorkCalls > GATE_THRESHOLD) {
|
|
347
384
|
deny(
|
|
348
385
|
`[headless-guard] tfx-multi gate: Agent(${subType || "default"}) 호출 ${multiState.nativeWorkCalls}회 — headless에 먼저 dispatch하세요.\n` +
|
|
349
|
-
|
|
386
|
+
"Bash(\"tfx multi --teammate-mode headless --auto-attach --dashboard --assign 'codex:프롬프트:역할' --timeout 600\")",
|
|
350
387
|
);
|
|
351
388
|
}
|
|
352
389
|
// 허용 범위 내 → 경고 + 통과
|
|
353
390
|
nudge(
|
|
354
391
|
`[headless-guard] tfx-multi 활성 (${multiState.nativeWorkCalls}/${GATE_THRESHOLD}). ` +
|
|
355
|
-
|
|
392
|
+
"headless dispatch 후 작업을 시작하세요.",
|
|
356
393
|
);
|
|
357
394
|
} else {
|
|
358
395
|
// ── B: nudge — dispatch 후, 네이티브 드리프트 감지 ──
|
|
359
|
-
multiState.nativeWorkCallsSinceDispatch =
|
|
396
|
+
multiState.nativeWorkCallsSinceDispatch =
|
|
397
|
+
(multiState.nativeWorkCallsSinceDispatch || 0) + 1;
|
|
360
398
|
writeMultiState(multiState);
|
|
361
399
|
|
|
362
400
|
if (multiState.nativeWorkCallsSinceDispatch >= NUDGE_THRESHOLD) {
|
|
@@ -364,7 +402,7 @@ async function main() {
|
|
|
364
402
|
writeMultiState(multiState);
|
|
365
403
|
nudge(
|
|
366
404
|
"[headless-guard] nudge: headless 워커가 실행 중입니다. " +
|
|
367
|
-
|
|
405
|
+
"결과를 기다리거나 추가 --assign으로 위임하세요.",
|
|
368
406
|
);
|
|
369
407
|
}
|
|
370
408
|
}
|
|
@@ -388,8 +426,8 @@ async function main() {
|
|
|
388
426
|
if (cliPatterns.some((p) => p.test(combined))) {
|
|
389
427
|
deny(
|
|
390
428
|
"[headless-guard] Codex/Gemini를 Agent()로 래핑하지 마세요. " +
|
|
391
|
-
|
|
392
|
-
|
|
429
|
+
`승인된 경로: ${HEADLESS_FALLBACK_COMMAND}. ` +
|
|
430
|
+
DIRECT_CLI_BYPASS_HINT,
|
|
393
431
|
);
|
|
394
432
|
}
|
|
395
433
|
}
|
package/scripts/hub-ensure.mjs
CHANGED
|
@@ -1,120 +1,139 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
// -
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const child = spawn(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// SessionStart 훅에서 호출되는 Hub 보장 스크립트.
|
|
4
|
+
// - /status 기반 헬스체크
|
|
5
|
+
// - 비정상 시 Hub를 detached로 기동
|
|
6
|
+
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
|
|
13
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
14
|
+
const PLUGIN_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
15
|
+
const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
|
|
16
|
+
|
|
17
|
+
function formatHostForUrl(host) {
|
|
18
|
+
return host.includes(":") ? `[${host}]` : host;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildHubBaseUrl(host, port) {
|
|
22
|
+
return `http://${formatHostForUrl(host)}:${port}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveHubTarget() {
|
|
26
|
+
const envPortRaw = Number(process.env.TFX_HUB_PORT || "");
|
|
27
|
+
const envPort =
|
|
28
|
+
Number.isFinite(envPortRaw) && envPortRaw > 0 ? envPortRaw : null;
|
|
29
|
+
const target = {
|
|
30
|
+
host: "127.0.0.1",
|
|
31
|
+
port: envPort || 27888,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (existsSync(HUB_PID_FILE)) {
|
|
35
|
+
try {
|
|
36
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
37
|
+
if (!envPort) {
|
|
38
|
+
const pidPort = Number(info?.port);
|
|
39
|
+
if (Number.isFinite(pidPort) && pidPort > 0) target.port = pidPort;
|
|
40
|
+
}
|
|
41
|
+
if (typeof info?.host === "string") {
|
|
42
|
+
const host = info.host.trim();
|
|
43
|
+
if (LOOPBACK_HOSTS.has(host)) target.host = host;
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// ignore parse errors and use env/default
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return target;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function isHubHealthy(host, port) {
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(`${buildHubBaseUrl(host, port)}/status`, {
|
|
56
|
+
signal: AbortSignal.timeout(1000),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) return false;
|
|
59
|
+
const data = await res.json();
|
|
60
|
+
return data?.hub?.state === "healthy";
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function startHubDetached(port) {
|
|
67
|
+
const serverPath = join(PLUGIN_ROOT, "hub", "server.mjs");
|
|
68
|
+
if (!existsSync(serverPath)) return false;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const env = { ...process.env, TFX_HUB_PORT: String(port) };
|
|
72
|
+
if (process.platform === "win32") {
|
|
73
|
+
const child = spawn(
|
|
74
|
+
"cmd.exe",
|
|
75
|
+
["/c", "start", "/b", "", process.execPath, serverPath],
|
|
76
|
+
{
|
|
77
|
+
env,
|
|
78
|
+
stdio: "ignore",
|
|
79
|
+
windowsHide: true,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
child.unref();
|
|
83
|
+
} else {
|
|
84
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
85
|
+
env,
|
|
86
|
+
detached: true,
|
|
87
|
+
stdio: "ignore",
|
|
88
|
+
});
|
|
89
|
+
child.unref();
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function waitForHubReady(host, port, maxWaitMs = 5000) {
|
|
98
|
+
const interval = 250;
|
|
99
|
+
const deadline = Date.now() + maxWaitMs;
|
|
100
|
+
while (Date.now() < deadline) {
|
|
101
|
+
if (await isHubHealthy(host, port)) return true;
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function run(stdinData) {
|
|
108
|
+
void stdinData;
|
|
109
|
+
|
|
110
|
+
const { host, port } = resolveHubTarget();
|
|
111
|
+
if (await isHubHealthy(host, port)) {
|
|
112
|
+
return { code: 0, stdout: "hub: ok", stderr: "" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const started = startHubDetached(port);
|
|
116
|
+
if (!started) {
|
|
117
|
+
return { code: 0, stdout: "", stderr: "[hub-ensure] hub 시작 실패" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const ready = await waitForHubReady(host, port, 3000);
|
|
121
|
+
return {
|
|
122
|
+
code: 0,
|
|
123
|
+
stdout: ready ? "hub: ok" : "hub: starting",
|
|
124
|
+
stderr: "",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const isMain =
|
|
129
|
+
process.argv[1] &&
|
|
130
|
+
import.meta.url.endsWith(
|
|
131
|
+
process.argv[1].replace(/\\/g, "/").split("/").pop(),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (isMain) {
|
|
135
|
+
const result = await run();
|
|
136
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
137
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
138
|
+
process.exit(result.code);
|
|
139
|
+
}
|