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
package/hub/team/nativeProxy.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// hub/team/nativeProxy.mjs
|
|
2
2
|
// Claude Native Teams 파일을 Hub tool/REST에서 안전하게 읽고 쓰기 위한 유틸.
|
|
3
3
|
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
import {
|
|
5
6
|
existsSync,
|
|
6
7
|
mkdirSync,
|
|
@@ -8,24 +9,23 @@ import {
|
|
|
8
9
|
rmSync,
|
|
9
10
|
unlinkSync,
|
|
10
11
|
writeFileSync,
|
|
11
|
-
} from
|
|
12
|
+
} from "node:fs";
|
|
12
13
|
import {
|
|
13
14
|
open as openFile,
|
|
14
15
|
readdir,
|
|
15
16
|
readFile,
|
|
16
17
|
stat,
|
|
17
18
|
unlink as unlinkFile,
|
|
18
|
-
} from
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { IS_WINDOWS } from '../platform.mjs';
|
|
19
|
+
} from "node:fs/promises";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { basename, dirname, join } from "node:path";
|
|
22
|
+
import { isPidAlive } from "../lib/process-utils.mjs";
|
|
23
|
+
import { IS_WINDOWS } from "../platform.mjs";
|
|
24
24
|
|
|
25
25
|
const TEAM_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
26
|
-
const CLAUDE_HOME = join(homedir(),
|
|
27
|
-
const TEAMS_ROOT = join(CLAUDE_HOME,
|
|
28
|
-
const TASKS_ROOT = join(CLAUDE_HOME,
|
|
26
|
+
const CLAUDE_HOME = join(homedir(), ".claude");
|
|
27
|
+
const TEAMS_ROOT = join(CLAUDE_HOME, "teams");
|
|
28
|
+
const TASKS_ROOT = join(CLAUDE_HOME, "tasks");
|
|
29
29
|
const LOCK_STALE_MS = 30000;
|
|
30
30
|
|
|
31
31
|
// ── 인메모리 캐시 (디렉토리 mtime 기반 무효화) ──
|
|
@@ -42,14 +42,14 @@ function err(code, message, extra = {}) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function validateTeamName(teamName) {
|
|
45
|
-
if (!TEAM_NAME_RE.test(String(teamName ||
|
|
46
|
-
throw new Error(
|
|
45
|
+
if (!TEAM_NAME_RE.test(String(teamName || ""))) {
|
|
46
|
+
throw new Error("INVALID_TEAM_NAME");
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
async function readJsonSafe(path) {
|
|
51
51
|
try {
|
|
52
|
-
return JSON.parse(await readFile(path,
|
|
52
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
53
53
|
} catch {
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
@@ -58,16 +58,20 @@ async function readJsonSafe(path) {
|
|
|
58
58
|
function atomicWriteJson(path, value) {
|
|
59
59
|
mkdirSync(dirname(path), { recursive: true });
|
|
60
60
|
const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
61
|
-
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`,
|
|
61
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
62
62
|
try {
|
|
63
63
|
renameSync(tmp, path);
|
|
64
64
|
} catch (e) {
|
|
65
65
|
// Windows NTFS: 대상 파일 존재 시 rename 실패 가능 → 삭제 후 재시도
|
|
66
|
-
if (IS_WINDOWS && (e.code ===
|
|
67
|
-
try {
|
|
66
|
+
if (IS_WINDOWS && (e.code === "EPERM" || e.code === "EEXIST")) {
|
|
67
|
+
try {
|
|
68
|
+
unlinkSync(path);
|
|
69
|
+
} catch {}
|
|
68
70
|
renameSync(tmp, path);
|
|
69
71
|
} else {
|
|
70
|
-
try {
|
|
72
|
+
try {
|
|
73
|
+
unlinkSync(tmp);
|
|
74
|
+
} catch {}
|
|
71
75
|
throw e;
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -87,29 +91,36 @@ async function readLockInfo(lockPath) {
|
|
|
87
91
|
|
|
88
92
|
let parsed = null;
|
|
89
93
|
try {
|
|
90
|
-
parsed = JSON.parse(await readFile(lockPath,
|
|
94
|
+
parsed = JSON.parse(await readFile(lockPath, "utf8"));
|
|
91
95
|
} catch {}
|
|
92
96
|
|
|
93
97
|
const now = Date.now();
|
|
94
98
|
const createdAtMs = Number(
|
|
95
|
-
parsed?.created_at_ms
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
parsed?.created_at_ms ??
|
|
100
|
+
parsed?.timestamp_ms ??
|
|
101
|
+
parsed?.timestamp ??
|
|
102
|
+
lockStat.mtimeMs,
|
|
99
103
|
);
|
|
100
104
|
const pid = Number(parsed?.pid);
|
|
101
105
|
|
|
102
106
|
return {
|
|
103
|
-
token: typeof parsed?.token ===
|
|
107
|
+
token: typeof parsed?.token === "string" ? parsed.token : null,
|
|
104
108
|
pid: Number.isInteger(pid) && pid > 0 ? pid : null,
|
|
105
|
-
created_at_ms: Number.isFinite(createdAtMs)
|
|
109
|
+
created_at_ms: Number.isFinite(createdAtMs)
|
|
110
|
+
? createdAtMs
|
|
111
|
+
: lockStat.mtimeMs,
|
|
106
112
|
mtime_ms: lockStat.mtimeMs,
|
|
107
|
-
age_ms: Math.max(
|
|
113
|
+
age_ms: Math.max(
|
|
114
|
+
0,
|
|
115
|
+
now - (Number.isFinite(createdAtMs) ? createdAtMs : lockStat.mtimeMs),
|
|
116
|
+
),
|
|
108
117
|
};
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
async function releaseFileLock(lockPath, token, handle) {
|
|
112
|
-
try {
|
|
121
|
+
try {
|
|
122
|
+
await handle?.close();
|
|
123
|
+
} catch {}
|
|
113
124
|
|
|
114
125
|
try {
|
|
115
126
|
const current = await readLockInfo(lockPath);
|
|
@@ -119,7 +130,13 @@ async function releaseFileLock(lockPath, token, handle) {
|
|
|
119
130
|
} catch {}
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
async function withFileLock(
|
|
133
|
+
async function withFileLock(
|
|
134
|
+
lockPath,
|
|
135
|
+
fn,
|
|
136
|
+
retries = 20,
|
|
137
|
+
delayMs = 25,
|
|
138
|
+
staleMs = LOCK_STALE_MS,
|
|
139
|
+
) {
|
|
123
140
|
mkdirSync(dirname(lockPath), { recursive: true });
|
|
124
141
|
const lockOwner = {
|
|
125
142
|
pid: process.pid,
|
|
@@ -132,9 +149,9 @@ async function withFileLock(lockPath, fn, retries = 20, delayMs = 25, staleMs =
|
|
|
132
149
|
|
|
133
150
|
for (let i = 0; i < retries; i += 1) {
|
|
134
151
|
try {
|
|
135
|
-
handle = await openFile(lockPath,
|
|
152
|
+
handle = await openFile(lockPath, "wx");
|
|
136
153
|
try {
|
|
137
|
-
await handle.writeFile(`${JSON.stringify(lockOwner)}\n`,
|
|
154
|
+
await handle.writeFile(`${JSON.stringify(lockOwner)}\n`, "utf8");
|
|
138
155
|
} catch (writeError) {
|
|
139
156
|
await releaseFileLock(lockPath, lockOwner.token, handle);
|
|
140
157
|
throw writeError;
|
|
@@ -142,7 +159,7 @@ async function withFileLock(lockPath, fn, retries = 20, delayMs = 25, staleMs =
|
|
|
142
159
|
break;
|
|
143
160
|
} catch (e) {
|
|
144
161
|
lastError = e;
|
|
145
|
-
if (e?.code !==
|
|
162
|
+
if (e?.code !== "EEXIST") throw e;
|
|
146
163
|
|
|
147
164
|
const current = await readLockInfo(lockPath);
|
|
148
165
|
const staleByAge = !current || current.age_ms > staleMs;
|
|
@@ -152,7 +169,7 @@ async function withFileLock(lockPath, fn, retries = 20, delayMs = 25, staleMs =
|
|
|
152
169
|
await unlinkFile(lockPath);
|
|
153
170
|
continue;
|
|
154
171
|
} catch (unlinkError) {
|
|
155
|
-
if (unlinkError?.code ===
|
|
172
|
+
if (unlinkError?.code === "ENOENT") continue;
|
|
156
173
|
lastError = unlinkError;
|
|
157
174
|
}
|
|
158
175
|
}
|
|
@@ -174,19 +191,21 @@ async function withFileLock(lockPath, fn, retries = 20, delayMs = 25, staleMs =
|
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
function getLeadSessionId(config) {
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
||
|
|
194
|
+
return (
|
|
195
|
+
config?.leadSessionId ||
|
|
196
|
+
config?.lead_session_id ||
|
|
197
|
+
config?.lead?.lead_session_id ||
|
|
198
|
+
config?.lead?.sessionId ||
|
|
199
|
+
null
|
|
200
|
+
);
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
export async function resolveTeamPaths(teamName) {
|
|
185
204
|
validateTeamName(teamName);
|
|
186
205
|
|
|
187
206
|
const teamDir = join(TEAMS_ROOT, teamName);
|
|
188
|
-
const configPath = join(teamDir,
|
|
189
|
-
const inboxesDir = join(teamDir,
|
|
207
|
+
const configPath = join(teamDir, "config.json");
|
|
208
|
+
const inboxesDir = join(teamDir, "inboxes");
|
|
190
209
|
const config = await readJsonSafe(configPath);
|
|
191
210
|
const leadSessionId = getLeadSessionId(config);
|
|
192
211
|
|
|
@@ -194,12 +213,12 @@ export async function resolveTeamPaths(teamName) {
|
|
|
194
213
|
const byLeadSession = leadSessionId ? join(TASKS_ROOT, leadSessionId) : null;
|
|
195
214
|
|
|
196
215
|
let tasksDir = byTeam;
|
|
197
|
-
let tasksDirResolution =
|
|
216
|
+
let tasksDirResolution = "not_found";
|
|
198
217
|
if (existsSync(byTeam)) {
|
|
199
|
-
tasksDirResolution =
|
|
218
|
+
tasksDirResolution = "team_name";
|
|
200
219
|
} else if (byLeadSession && existsSync(byLeadSession)) {
|
|
201
220
|
tasksDir = byLeadSession;
|
|
202
|
-
tasksDirResolution =
|
|
221
|
+
tasksDirResolution = "lead_session_id";
|
|
203
222
|
}
|
|
204
223
|
|
|
205
224
|
return {
|
|
@@ -222,7 +241,7 @@ export async function forceCleanupTeam(teamName) {
|
|
|
222
241
|
} catch {
|
|
223
242
|
paths = {
|
|
224
243
|
team_dir: join(TEAMS_ROOT, teamName),
|
|
225
|
-
config_path: join(TEAMS_ROOT, teamName,
|
|
244
|
+
config_path: join(TEAMS_ROOT, teamName, "config.json"),
|
|
226
245
|
tasks_dir: join(TASKS_ROOT, teamName),
|
|
227
246
|
lead_session_id: null,
|
|
228
247
|
config: null,
|
|
@@ -230,11 +249,14 @@ export async function forceCleanupTeam(teamName) {
|
|
|
230
249
|
}
|
|
231
250
|
|
|
232
251
|
try {
|
|
233
|
-
const config = paths.config || await readJsonSafe(paths.config_path);
|
|
252
|
+
const config = paths.config || (await readJsonSafe(paths.config_path));
|
|
234
253
|
if (config && Array.isArray(config.members)) {
|
|
235
254
|
atomicWriteJson(paths.config_path, {
|
|
236
255
|
...config,
|
|
237
|
-
members: config.members.map((member) => ({
|
|
256
|
+
members: config.members.map((member) => ({
|
|
257
|
+
...member,
|
|
258
|
+
isActive: false,
|
|
259
|
+
})),
|
|
238
260
|
});
|
|
239
261
|
}
|
|
240
262
|
} catch {}
|
|
@@ -261,7 +283,11 @@ async function collectTaskFiles(tasksDir) {
|
|
|
261
283
|
|
|
262
284
|
// 디렉토리 mtime 기반 캐시 — O(N) I/O를 반복 호출 시 O(1)로 축소
|
|
263
285
|
let dirMtime;
|
|
264
|
-
try {
|
|
286
|
+
try {
|
|
287
|
+
dirMtime = (await stat(tasksDir)).mtimeMs;
|
|
288
|
+
} catch {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
265
291
|
|
|
266
292
|
const cached = _dirCache.get(tasksDir);
|
|
267
293
|
if (cached && cached.mtimeMs === dirMtime) {
|
|
@@ -270,9 +296,9 @@ async function collectTaskFiles(tasksDir) {
|
|
|
270
296
|
|
|
271
297
|
const entries = await readdir(tasksDir);
|
|
272
298
|
const files = entries
|
|
273
|
-
.filter((name) => name.endsWith(
|
|
274
|
-
.filter((name) => !name.endsWith(
|
|
275
|
-
.filter((name) => name !==
|
|
299
|
+
.filter((name) => name.endsWith(".json"))
|
|
300
|
+
.filter((name) => !name.endsWith(".lock"))
|
|
301
|
+
.filter((name) => name !== ".highwatermark")
|
|
276
302
|
.map((name) => join(tasksDir, name));
|
|
277
303
|
|
|
278
304
|
_dirCache.set(tasksDir, { mtimeMs: dirMtime, files });
|
|
@@ -311,12 +337,12 @@ async function locateTaskFile(tasksDir, taskId) {
|
|
|
311
337
|
// 캐시된 collectTaskFiles로 풀 스캔
|
|
312
338
|
const files = await collectTaskFiles(tasksDir);
|
|
313
339
|
for (const file of files) {
|
|
314
|
-
if (basename(file,
|
|
340
|
+
if (basename(file, ".json") === taskId) {
|
|
315
341
|
_taskIdIndex.set(taskId, file);
|
|
316
342
|
return file;
|
|
317
343
|
}
|
|
318
344
|
const json = await readJsonSafe(file);
|
|
319
|
-
if (json && String(json.id ||
|
|
345
|
+
if (json && String(json.id || "") === taskId) {
|
|
320
346
|
_taskIdIndex.set(taskId, file);
|
|
321
347
|
return file;
|
|
322
348
|
}
|
|
@@ -325,7 +351,7 @@ async function locateTaskFile(tasksDir, taskId) {
|
|
|
325
351
|
}
|
|
326
352
|
|
|
327
353
|
function isObject(v) {
|
|
328
|
-
return !!v && typeof v ===
|
|
354
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
329
355
|
}
|
|
330
356
|
|
|
331
357
|
export async function teamInfo(args = {}) {
|
|
@@ -333,19 +359,22 @@ export async function teamInfo(args = {}) {
|
|
|
333
359
|
try {
|
|
334
360
|
validateTeamName(team_name);
|
|
335
361
|
} catch {
|
|
336
|
-
return err(
|
|
362
|
+
return err("INVALID_TEAM_NAME", "team_name 형식이 올바르지 않습니다");
|
|
337
363
|
}
|
|
338
364
|
|
|
339
365
|
const paths = await resolveTeamPaths(team_name);
|
|
340
366
|
if (!existsSync(paths.team_dir)) {
|
|
341
|
-
return err(
|
|
367
|
+
return err("TEAM_NOT_FOUND", `팀 디렉토리가 없습니다: ${paths.team_dir}`);
|
|
342
368
|
}
|
|
343
369
|
|
|
344
|
-
const members = Array.isArray(paths.config?.members)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
||
|
|
370
|
+
const members = Array.isArray(paths.config?.members)
|
|
371
|
+
? paths.config.members
|
|
372
|
+
: [];
|
|
373
|
+
const leadAgentId =
|
|
374
|
+
paths.config?.leadAgentId ||
|
|
375
|
+
paths.config?.lead_agent_id ||
|
|
376
|
+
members[0]?.agentId ||
|
|
377
|
+
null;
|
|
349
378
|
|
|
350
379
|
return {
|
|
351
380
|
ok: true,
|
|
@@ -359,14 +388,16 @@ export async function teamInfo(args = {}) {
|
|
|
359
388
|
lead_session_id: paths.lead_session_id,
|
|
360
389
|
},
|
|
361
390
|
...(include_members ? { members } : {}),
|
|
362
|
-
...(include_paths
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
391
|
+
...(include_paths
|
|
392
|
+
? {
|
|
393
|
+
paths: {
|
|
394
|
+
config_path: paths.config_path,
|
|
395
|
+
tasks_dir: paths.tasks_dir,
|
|
396
|
+
inboxes_dir: paths.inboxes_dir,
|
|
397
|
+
tasks_dir_resolution: paths.tasks_dir_resolution,
|
|
398
|
+
},
|
|
399
|
+
}
|
|
400
|
+
: {}),
|
|
370
401
|
},
|
|
371
402
|
};
|
|
372
403
|
}
|
|
@@ -383,19 +414,24 @@ export async function teamTaskList(args = {}) {
|
|
|
383
414
|
try {
|
|
384
415
|
validateTeamName(team_name);
|
|
385
416
|
} catch {
|
|
386
|
-
return err(
|
|
417
|
+
return err("INVALID_TEAM_NAME", "team_name 형식이 올바르지 않습니다");
|
|
387
418
|
}
|
|
388
419
|
|
|
389
420
|
const paths = await resolveTeamPaths(team_name);
|
|
390
|
-
if (paths.tasks_dir_resolution ===
|
|
391
|
-
return err(
|
|
421
|
+
if (paths.tasks_dir_resolution === "not_found") {
|
|
422
|
+
return err(
|
|
423
|
+
"TASKS_DIR_NOT_FOUND",
|
|
424
|
+
`task 디렉토리를 찾지 못했습니다: ${team_name}`,
|
|
425
|
+
);
|
|
392
426
|
}
|
|
393
427
|
|
|
394
428
|
const statusSet = new Set((statuses || []).map((s) => String(s)));
|
|
395
429
|
const maxCount = Math.max(1, Math.min(Number(limit) || 200, 1000));
|
|
396
430
|
let parseWarnings = 0;
|
|
397
431
|
const files = await collectTaskFiles(paths.tasks_dir);
|
|
398
|
-
const records = await Promise.all(
|
|
432
|
+
const records = await Promise.all(
|
|
433
|
+
files.map((file) => readTaskFileCached(file)),
|
|
434
|
+
);
|
|
399
435
|
|
|
400
436
|
const tasks = [];
|
|
401
437
|
for (const { file, mtimeMs: fileMtime, json } of records) {
|
|
@@ -405,8 +441,9 @@ export async function teamTaskList(args = {}) {
|
|
|
405
441
|
}
|
|
406
442
|
|
|
407
443
|
if (!include_internal && json?.metadata?._internal === true) continue;
|
|
408
|
-
if (owner && String(json.owner ||
|
|
409
|
-
if (statusSet.size > 0 && !statusSet.has(String(json.status ||
|
|
444
|
+
if (owner && String(json.owner || "") !== String(owner)) continue;
|
|
445
|
+
if (statusSet.size > 0 && !statusSet.has(String(json.status || "")))
|
|
446
|
+
continue;
|
|
410
447
|
|
|
411
448
|
tasks.push({
|
|
412
449
|
...json,
|
|
@@ -431,18 +468,26 @@ export async function teamTaskList(args = {}) {
|
|
|
431
468
|
}
|
|
432
469
|
|
|
433
470
|
// status 화이트리스트 (Claude Code API 호환)
|
|
434
|
-
const VALID_STATUSES = new Set([
|
|
471
|
+
const VALID_STATUSES = new Set([
|
|
472
|
+
"pending",
|
|
473
|
+
"in_progress",
|
|
474
|
+
"completed",
|
|
475
|
+
"deleted",
|
|
476
|
+
]);
|
|
435
477
|
|
|
436
478
|
export async function teamTaskUpdate(args = {}) {
|
|
437
479
|
// "failed" → "completed" + metadata.result 자동 매핑
|
|
438
|
-
if (String(args.status ||
|
|
480
|
+
if (String(args.status || "") === "failed") {
|
|
439
481
|
args = {
|
|
440
482
|
...args,
|
|
441
|
-
status:
|
|
442
|
-
metadata_patch: { ...(args.metadata_patch || {}), result:
|
|
483
|
+
status: "completed",
|
|
484
|
+
metadata_patch: { ...(args.metadata_patch || {}), result: "failed" },
|
|
443
485
|
};
|
|
444
486
|
} else if (args.status != null && !VALID_STATUSES.has(String(args.status))) {
|
|
445
|
-
return err(
|
|
487
|
+
return err(
|
|
488
|
+
"INVALID_STATUS",
|
|
489
|
+
`유효하지 않은 status: ${args.status}. 허용: ${[...VALID_STATUSES].join(", ")}`,
|
|
490
|
+
);
|
|
446
491
|
}
|
|
447
492
|
|
|
448
493
|
const {
|
|
@@ -464,21 +509,24 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
464
509
|
try {
|
|
465
510
|
validateTeamName(team_name);
|
|
466
511
|
} catch {
|
|
467
|
-
return err(
|
|
512
|
+
return err("INVALID_TEAM_NAME", "team_name 형식이 올바르지 않습니다");
|
|
468
513
|
}
|
|
469
514
|
|
|
470
|
-
if (!String(task_id ||
|
|
471
|
-
return err(
|
|
515
|
+
if (!String(task_id || "").trim()) {
|
|
516
|
+
return err("INVALID_TASK_ID", "task_id가 필요합니다");
|
|
472
517
|
}
|
|
473
518
|
|
|
474
519
|
const paths = await resolveTeamPaths(team_name);
|
|
475
|
-
if (paths.tasks_dir_resolution ===
|
|
476
|
-
return err(
|
|
520
|
+
if (paths.tasks_dir_resolution === "not_found") {
|
|
521
|
+
return err(
|
|
522
|
+
"TASKS_DIR_NOT_FOUND",
|
|
523
|
+
`task 디렉토리를 찾지 못했습니다: ${team_name}`,
|
|
524
|
+
);
|
|
477
525
|
}
|
|
478
526
|
|
|
479
527
|
const taskFile = await locateTaskFile(paths.tasks_dir, String(task_id));
|
|
480
528
|
if (!taskFile) {
|
|
481
|
-
return err(
|
|
529
|
+
return err("TASK_NOT_FOUND", `task를 찾지 못했습니다: ${task_id}`);
|
|
482
530
|
}
|
|
483
531
|
|
|
484
532
|
const lockFile = `${taskFile}.lock`;
|
|
@@ -487,14 +535,19 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
487
535
|
return await withFileLock(lockFile, async () => {
|
|
488
536
|
const before = await readJsonSafe(taskFile);
|
|
489
537
|
if (!before || !isObject(before)) {
|
|
490
|
-
return err(
|
|
538
|
+
return err("INVALID_TASK_FILE", `task 파일 파싱 실패: ${taskFile}`);
|
|
491
539
|
}
|
|
492
540
|
|
|
493
541
|
let beforeMtime = Date.now();
|
|
494
|
-
try {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
542
|
+
try {
|
|
543
|
+
beforeMtime = (await stat(taskFile)).mtimeMs;
|
|
544
|
+
} catch {}
|
|
545
|
+
|
|
546
|
+
if (
|
|
547
|
+
if_match_mtime_ms != null &&
|
|
548
|
+
Number(if_match_mtime_ms) !== Number(beforeMtime)
|
|
549
|
+
) {
|
|
550
|
+
return err("MTIME_CONFLICT", "if_match_mtime_ms가 일치하지 않습니다", {
|
|
498
551
|
task_file: taskFile,
|
|
499
552
|
mtime_ms: beforeMtime,
|
|
500
553
|
});
|
|
@@ -505,13 +558,16 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
505
558
|
let updated = false;
|
|
506
559
|
|
|
507
560
|
if (claim) {
|
|
508
|
-
const requestedOwner = String(owner || actor ||
|
|
509
|
-
const ownerNow = String(before.owner ||
|
|
510
|
-
const ownerCompatible =
|
|
511
|
-
|
|
561
|
+
const requestedOwner = String(owner || actor || "");
|
|
562
|
+
const ownerNow = String(before.owner || "");
|
|
563
|
+
const ownerCompatible =
|
|
564
|
+
ownerNow === "" ||
|
|
565
|
+
requestedOwner === "" ||
|
|
566
|
+
ownerNow === requestedOwner;
|
|
567
|
+
const statusPending = String(before.status || "") === "pending";
|
|
512
568
|
|
|
513
569
|
if (!statusPending || !ownerCompatible) {
|
|
514
|
-
return err(
|
|
570
|
+
return err("CLAIM_CONFLICT", "task claim 충돌", {
|
|
515
571
|
task_before: before,
|
|
516
572
|
task_file: taskFile,
|
|
517
573
|
mtime_ms: beforeMtime,
|
|
@@ -519,28 +575,34 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
519
575
|
}
|
|
520
576
|
|
|
521
577
|
if (requestedOwner) after.owner = requestedOwner;
|
|
522
|
-
after.status = status ||
|
|
578
|
+
after.status = status || "in_progress";
|
|
523
579
|
claimed = true;
|
|
524
580
|
updated = true;
|
|
525
581
|
}
|
|
526
582
|
|
|
527
|
-
if (owner != null && String(after.owner ||
|
|
583
|
+
if (owner != null && String(after.owner || "") !== String(owner)) {
|
|
528
584
|
after.owner = owner;
|
|
529
585
|
updated = true;
|
|
530
586
|
}
|
|
531
|
-
if (status != null && String(after.status ||
|
|
587
|
+
if (status != null && String(after.status || "") !== String(status)) {
|
|
532
588
|
after.status = status;
|
|
533
589
|
updated = true;
|
|
534
590
|
}
|
|
535
|
-
if (subject != null && String(after.subject ||
|
|
591
|
+
if (subject != null && String(after.subject || "") !== String(subject)) {
|
|
536
592
|
after.subject = subject;
|
|
537
593
|
updated = true;
|
|
538
594
|
}
|
|
539
|
-
if (
|
|
595
|
+
if (
|
|
596
|
+
description != null &&
|
|
597
|
+
String(after.description || "") !== String(description)
|
|
598
|
+
) {
|
|
540
599
|
after.description = description;
|
|
541
600
|
updated = true;
|
|
542
601
|
}
|
|
543
|
-
if (
|
|
602
|
+
if (
|
|
603
|
+
activeForm != null &&
|
|
604
|
+
String(after.activeForm || "") !== String(activeForm)
|
|
605
|
+
) {
|
|
544
606
|
after.activeForm = activeForm;
|
|
545
607
|
updated = true;
|
|
546
608
|
}
|
|
@@ -555,7 +617,9 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
555
617
|
}
|
|
556
618
|
|
|
557
619
|
if (Array.isArray(add_blocked_by) && add_blocked_by.length > 0) {
|
|
558
|
-
const current = Array.isArray(after.blockedBy)
|
|
620
|
+
const current = Array.isArray(after.blockedBy)
|
|
621
|
+
? [...after.blockedBy]
|
|
622
|
+
: [];
|
|
559
623
|
for (const item of add_blocked_by) {
|
|
560
624
|
if (!current.includes(item)) current.push(item);
|
|
561
625
|
}
|
|
@@ -577,7 +641,9 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
577
641
|
}
|
|
578
642
|
|
|
579
643
|
let afterMtime = beforeMtime;
|
|
580
|
-
try {
|
|
644
|
+
try {
|
|
645
|
+
afterMtime = (await stat(taskFile)).mtimeMs;
|
|
646
|
+
} catch {}
|
|
581
647
|
|
|
582
648
|
return {
|
|
583
649
|
ok: true,
|
|
@@ -592,36 +658,38 @@ export async function teamTaskUpdate(args = {}) {
|
|
|
592
658
|
};
|
|
593
659
|
});
|
|
594
660
|
} catch (e) {
|
|
595
|
-
return err(
|
|
661
|
+
return err("TASK_UPDATE_FAILED", e.message);
|
|
596
662
|
}
|
|
597
663
|
}
|
|
598
664
|
|
|
599
665
|
function sanitizeRecipientName(v) {
|
|
600
|
-
return String(v ||
|
|
666
|
+
return String(v || "team-lead").replace(/[\\/:*?"<>|]/g, "_");
|
|
601
667
|
}
|
|
602
668
|
|
|
603
669
|
export async function teamSendMessage(args = {}) {
|
|
604
670
|
const {
|
|
605
671
|
team_name,
|
|
606
672
|
from,
|
|
607
|
-
to =
|
|
673
|
+
to = "team-lead",
|
|
608
674
|
text,
|
|
609
675
|
summary,
|
|
610
|
-
color =
|
|
676
|
+
color = "blue",
|
|
611
677
|
} = args;
|
|
612
678
|
|
|
613
679
|
try {
|
|
614
680
|
validateTeamName(team_name);
|
|
615
681
|
} catch {
|
|
616
|
-
return err(
|
|
682
|
+
return err("INVALID_TEAM_NAME", "team_name 형식이 올바르지 않습니다");
|
|
617
683
|
}
|
|
618
684
|
|
|
619
|
-
if (!String(from ||
|
|
620
|
-
|
|
685
|
+
if (!String(from || "").trim())
|
|
686
|
+
return err("INVALID_FROM", "from이 필요합니다");
|
|
687
|
+
if (!String(text || "").trim())
|
|
688
|
+
return err("INVALID_TEXT", "text가 필요합니다");
|
|
621
689
|
|
|
622
690
|
const paths = await resolveTeamPaths(team_name);
|
|
623
691
|
if (!existsSync(paths.team_dir)) {
|
|
624
|
-
return err(
|
|
692
|
+
return err("TEAM_NOT_FOUND", `팀 디렉토리가 없습니다: ${paths.team_dir}`);
|
|
625
693
|
}
|
|
626
694
|
|
|
627
695
|
const recipient = sanitizeRecipientName(to);
|
|
@@ -640,7 +708,7 @@ export async function teamSendMessage(args = {}) {
|
|
|
640
708
|
text: String(text),
|
|
641
709
|
...(summary ? { summary: String(summary) } : {}),
|
|
642
710
|
timestamp: new Date().toISOString(),
|
|
643
|
-
color: String(color ||
|
|
711
|
+
color: String(color || "blue"),
|
|
644
712
|
read: false,
|
|
645
713
|
};
|
|
646
714
|
list.push(message);
|
|
@@ -650,8 +718,11 @@ export async function teamSendMessage(args = {}) {
|
|
|
650
718
|
if (list.length > MAX_INBOX) {
|
|
651
719
|
const ONE_HOUR_MS = 3600000;
|
|
652
720
|
const cutoff = Date.now() - ONE_HOUR_MS;
|
|
653
|
-
const pruned = list.filter(
|
|
654
|
-
|
|
721
|
+
const pruned = list.filter(
|
|
722
|
+
(m) =>
|
|
723
|
+
m?.read !== true ||
|
|
724
|
+
!m?.timestamp ||
|
|
725
|
+
new Date(m.timestamp).getTime() > cutoff,
|
|
655
726
|
);
|
|
656
727
|
list.length = 0;
|
|
657
728
|
list.push(...pruned);
|
|
@@ -676,7 +747,7 @@ export async function teamSendMessage(args = {}) {
|
|
|
676
747
|
},
|
|
677
748
|
};
|
|
678
749
|
} catch (e) {
|
|
679
|
-
return err(
|
|
750
|
+
return err("SEND_MESSAGE_FAILED", e.message);
|
|
680
751
|
}
|
|
681
752
|
}
|
|
682
753
|
|