@telora/daemon 0.15.2 → 0.15.4
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/README.md +11 -11
- package/build-info.json +2 -2
- package/dist/assembly-engine.d.ts +1 -1
- package/dist/assembly-engine.d.ts.map +1 -1
- package/dist/assembly-resolvers.js +41 -41
- package/dist/assembly-resolvers.js.map +1 -1
- package/dist/branch-status.d.ts +4 -4
- package/dist/branch-status.d.ts.map +1 -1
- package/dist/branch-status.js +23 -23
- package/dist/branch-status.js.map +1 -1
- package/dist/cli/connect.js +1 -1
- package/dist/cli/connect.js.map +1 -1
- package/dist/completion-detector.d.ts +1 -1
- package/dist/completion-detector.js +1 -1
- package/dist/completion-handler.d.ts +1 -1
- package/dist/completion-handler.js +4 -4
- package/dist/completion-handler.js.map +1 -1
- package/dist/condition-evaluators.js +4 -4
- package/dist/condition-evaluators.js.map +1 -1
- package/dist/control-state.d.ts +6 -6
- package/dist/control-state.d.ts.map +1 -1
- package/dist/control-state.js +43 -43
- package/dist/control-state.js.map +1 -1
- package/dist/crash-recovery-scan.d.ts +1 -1
- package/dist/crash-recovery-scan.js +20 -20
- package/dist/crash-recovery-scan.js.map +1 -1
- package/dist/crash-recovery-types.d.ts +3 -3
- package/dist/crash-recovery-types.d.ts.map +1 -1
- package/dist/crash-recovery-types.js +2 -2
- package/dist/crash-recovery-types.js.map +1 -1
- package/dist/crash-recovery.d.ts +3 -3
- package/dist/crash-recovery.d.ts.map +1 -1
- package/dist/crash-recovery.js +21 -21
- package/dist/crash-recovery.js.map +1 -1
- package/dist/daemon-process.d.ts +1 -1
- package/dist/daemon-process.d.ts.map +1 -1
- package/dist/dag-validator.d.ts +6 -6
- package/dist/dag-validator.d.ts.map +1 -1
- package/dist/dag-validator.js +2 -2
- package/dist/dag-validator.js.map +1 -1
- package/dist/delivery-guards.d.ts +1 -1
- package/dist/delivery-guards.js +1 -1
- package/dist/delivery-lifecycle.d.ts +5 -5
- package/dist/delivery-lifecycle.d.ts.map +1 -1
- package/dist/delivery-lifecycle.js +12 -12
- package/dist/delivery-lifecycle.js.map +1 -1
- package/dist/delivery-merge.js +1 -1
- package/dist/delivery-merge.js.map +1 -1
- package/dist/dependency-resolver.d.ts +7 -7
- package/dist/dependency-resolver.d.ts.map +1 -1
- package/dist/dependency-resolver.js.map +1 -1
- package/dist/directive-executor.d.ts +32 -17
- package/dist/directive-executor.d.ts.map +1 -1
- package/dist/directive-executor.js +119 -78
- package/dist/directive-executor.js.map +1 -1
- package/dist/evaluation-context.d.ts +2 -2
- package/dist/evaluation-context.d.ts.map +1 -1
- package/dist/evaluation-context.js +4 -4
- package/dist/evaluation-context.js.map +1 -1
- package/dist/focus-completion-event.d.ts +97 -0
- package/dist/focus-completion-event.d.ts.map +1 -0
- package/dist/focus-completion-event.js +257 -0
- package/dist/focus-completion-event.js.map +1 -0
- package/dist/{strategy-completion.d.ts → focus-completion.d.ts} +11 -11
- package/dist/focus-completion.d.ts.map +1 -0
- package/dist/{strategy-completion.js → focus-completion.js} +68 -68
- package/dist/focus-completion.js.map +1 -0
- package/dist/{strategy-engine.d.ts → focus-engine.d.ts} +5 -5
- package/dist/focus-engine.d.ts.map +1 -0
- package/dist/{strategy-engine.js → focus-engine.js} +35 -35
- package/dist/focus-engine.js.map +1 -0
- package/dist/focus-executor.d.ts +55 -0
- package/dist/focus-executor.d.ts.map +1 -0
- package/dist/{strategy-executor.js → focus-executor.js} +167 -120
- package/dist/focus-executor.js.map +1 -0
- package/dist/focus-lifecycle.d.ts +61 -0
- package/dist/focus-lifecycle.d.ts.map +1 -0
- package/dist/{strategy-lifecycle.js → focus-lifecycle.js} +164 -164
- package/dist/focus-lifecycle.js.map +1 -0
- package/dist/{strategy-merge.d.ts → focus-merge.d.ts} +10 -10
- package/dist/focus-merge.d.ts.map +1 -0
- package/dist/{strategy-merge.js → focus-merge.js} +51 -51
- package/dist/focus-merge.js.map +1 -0
- package/dist/focus-phase.d.ts +22 -0
- package/dist/focus-phase.d.ts.map +1 -0
- package/dist/focus-phase.js +54 -0
- package/dist/focus-phase.js.map +1 -0
- package/dist/focus-prompt-builder.d.ts +35 -0
- package/dist/focus-prompt-builder.d.ts.map +1 -0
- package/dist/{strategy-prompt-builder.js → focus-prompt-builder.js} +55 -4
- package/dist/focus-prompt-builder.js.map +1 -0
- package/dist/focus-provisioning.d.ts +16 -0
- package/dist/focus-provisioning.d.ts.map +1 -0
- package/dist/focus-provisioning.js +119 -0
- package/dist/focus-provisioning.js.map +1 -0
- package/dist/{strategy-spawn-helpers.d.ts → focus-spawn-helpers.d.ts} +15 -15
- package/dist/focus-spawn-helpers.d.ts.map +1 -0
- package/dist/{strategy-spawn-helpers.js → focus-spawn-helpers.js} +28 -28
- package/dist/focus-spawn-helpers.js.map +1 -0
- package/dist/{strategy-team-lifecycle.d.ts → focus-team-lifecycle.d.ts} +13 -13
- package/dist/focus-team-lifecycle.d.ts.map +1 -0
- package/dist/{strategy-team-lifecycle.js → focus-team-lifecycle.js} +57 -57
- package/dist/focus-team-lifecycle.js.map +1 -0
- package/dist/focus-team-state.d.ts +24 -0
- package/dist/focus-team-state.d.ts.map +1 -0
- package/dist/{strategy-team-state.js → focus-team-state.js} +9 -9
- package/dist/focus-team-state.js.map +1 -0
- package/dist/focus-worktree-state.d.ts +47 -0
- package/dist/focus-worktree-state.d.ts.map +1 -0
- package/dist/{strategy-worktree-state.js → focus-worktree-state.js} +29 -29
- package/dist/focus-worktree-state.js.map +1 -0
- package/dist/git-merge.d.ts +5 -5
- package/dist/git-merge.d.ts.map +1 -1
- package/dist/git-merge.js +5 -5
- package/dist/git-merge.js.map +1 -1
- package/dist/git-state-detector.d.ts +1 -1
- package/dist/git-state-detector.js +9 -9
- package/dist/git-state-detector.js.map +1 -1
- package/dist/git-utils.d.ts +1 -1
- package/dist/git-utils.js +1 -1
- package/dist/git.d.ts +2 -2
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +3 -3
- package/dist/git.js.map +1 -1
- package/dist/heartbeat.d.ts +4 -4
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +7 -7
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/listener-auto-advance.d.ts +2 -2
- package/dist/listener-auto-advance.js +21 -21
- package/dist/listener-auto-advance.js.map +1 -1
- package/dist/listener.d.ts +12 -12
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +80 -75
- package/dist/listener.js.map +1 -1
- package/dist/loop-engine.d.ts +20 -20
- package/dist/loop-engine.d.ts.map +1 -1
- package/dist/loop-engine.js +82 -82
- package/dist/loop-engine.js.map +1 -1
- package/dist/loop-event-bus.d.ts +5 -5
- package/dist/loop-event-bus.d.ts.map +1 -1
- package/dist/loop-event-bus.js +3 -3
- package/dist/loop-llm-client.d.ts +1 -1
- package/dist/loop-llm-client.js +1 -1
- package/dist/loop-variance.d.ts +1 -1
- package/dist/loop-variance.js +1 -1
- package/dist/merge-detector.d.ts +6 -6
- package/dist/merge-detector.d.ts.map +1 -1
- package/dist/merge-detector.js +47 -47
- package/dist/merge-detector.js.map +1 -1
- package/dist/otlp-log-parser.js +2 -2
- package/dist/otlp-log-parser.js.map +1 -1
- package/dist/otlp-metric-parser.js +1 -1
- package/dist/otlp-metric-parser.js.map +1 -1
- package/dist/otlp-receiver.d.ts +1 -1
- package/dist/otlp-receiver.js +7 -7
- package/dist/otlp-receiver.js.map +1 -1
- package/dist/otlp-types.d.ts +1 -1
- package/dist/otlp-types.d.ts.map +1 -1
- package/dist/otlp-types.js +3 -3
- package/dist/otlp-types.js.map +1 -1
- package/dist/output-monitor.d.ts +6 -6
- package/dist/output-monitor.d.ts.map +1 -1
- package/dist/output-monitor.js +11 -11
- package/dist/output-monitor.js.map +1 -1
- package/dist/planning-prompt-builder.d.ts +11 -11
- package/dist/planning-prompt-builder.d.ts.map +1 -1
- package/dist/planning-prompt-builder.js +28 -28
- package/dist/planning-prompt-builder.js.map +1 -1
- package/dist/pm/mitigation-correlator.d.ts +7 -7
- package/dist/pm/mitigation-correlator.d.ts.map +1 -1
- package/dist/pm/mitigation-correlator.js +33 -33
- package/dist/pm/mitigation-correlator.js.map +1 -1
- package/dist/pm-engine.js +1 -1
- package/dist/pm-engine.js.map +1 -1
- package/dist/prompt-builder.d.ts +1 -1
- package/dist/prompt-builder.js +9 -9
- package/dist/prompt-builder.js.map +1 -1
- package/dist/prompts/starter-prompt.d.ts +2 -2
- package/dist/prompts/starter-prompt.d.ts.map +1 -1
- package/dist/prompts/starter-prompt.js +6 -6
- package/dist/qa-crash-recovery.d.ts +10 -10
- package/dist/qa-crash-recovery.d.ts.map +1 -1
- package/dist/qa-crash-recovery.js +32 -32
- package/dist/qa-crash-recovery.js.map +1 -1
- package/dist/qa-dev-server.d.ts +3 -3
- package/dist/qa-dev-server.d.ts.map +1 -1
- package/dist/qa-dev-server.js +3 -3
- package/dist/qa-dev-server.js.map +1 -1
- package/dist/qa-orchestrator.d.ts +18 -18
- package/dist/qa-orchestrator.d.ts.map +1 -1
- package/dist/qa-orchestrator.js +47 -47
- package/dist/qa-orchestrator.js.map +1 -1
- package/dist/qa-provisioner.d.ts +3 -3
- package/dist/qa-provisioner.d.ts.map +1 -1
- package/dist/qa-provisioner.js +14 -14
- package/dist/qa-provisioner.js.map +1 -1
- package/dist/qa-state.d.ts +10 -10
- package/dist/qa-state.d.ts.map +1 -1
- package/dist/qa-state.js +11 -11
- package/dist/qa-state.js.map +1 -1
- package/dist/queries/control-state.d.ts +5 -5
- package/dist/queries/control-state.d.ts.map +1 -1
- package/dist/queries/control-state.js +4 -4
- package/dist/queries/control-state.js.map +1 -1
- package/dist/queries/deliveries.d.ts +24 -23
- package/dist/queries/deliveries.d.ts.map +1 -1
- package/dist/queries/deliveries.js +30 -29
- package/dist/queries/deliveries.js.map +1 -1
- package/dist/queries/{strategies.d.ts → focuses.d.ts} +24 -21
- package/dist/queries/focuses.d.ts.map +1 -0
- package/dist/queries/{strategies.js → focuses.js} +39 -31
- package/dist/queries/focuses.js.map +1 -0
- package/dist/queries/index.d.ts +3 -3
- package/dist/queries/index.d.ts.map +1 -1
- package/dist/queries/index.js +2 -2
- package/dist/queries/index.js.map +1 -1
- package/dist/queries/qa.d.ts +16 -16
- package/dist/queries/qa.d.ts.map +1 -1
- package/dist/queries/qa.js +22 -22
- package/dist/queries/qa.js.map +1 -1
- package/dist/queries/schemas.d.ts +36 -30
- package/dist/queries/schemas.d.ts.map +1 -1
- package/dist/queries/schemas.js +28 -25
- package/dist/queries/schemas.js.map +1 -1
- package/dist/queries/sessions.d.ts +1 -1
- package/dist/queries/sessions.d.ts.map +1 -1
- package/dist/queries/sessions.js +2 -2
- package/dist/queries/sessions.js.map +1 -1
- package/dist/queries/shared.js +10 -10
- package/dist/queries/shared.js.map +1 -1
- package/dist/queries/workflows.d.ts +1 -1
- package/dist/queries/workflows.js +1 -1
- package/dist/queries/worktrees.d.ts +7 -7
- package/dist/queries/worktrees.d.ts.map +1 -1
- package/dist/queries/worktrees.js +7 -7
- package/dist/queries/worktrees.js.map +1 -1
- package/dist/review-defect-detector.d.ts +125 -0
- package/dist/review-defect-detector.d.ts.map +1 -0
- package/dist/review-defect-detector.js +289 -0
- package/dist/review-defect-detector.js.map +1 -0
- package/dist/session-lifecycle.d.ts +17 -17
- package/dist/session-lifecycle.d.ts.map +1 -1
- package/dist/session-lifecycle.js +82 -82
- package/dist/session-lifecycle.js.map +1 -1
- package/dist/spawn-cooldown.d.ts +8 -8
- package/dist/spawn-cooldown.d.ts.map +1 -1
- package/dist/spawn-cooldown.js +15 -15
- package/dist/spawn-cooldown.js.map +1 -1
- package/dist/spawn-environment.d.ts +2 -2
- package/dist/spawn-environment.d.ts.map +1 -1
- package/dist/spawn-environment.js +3 -3
- package/dist/spawn-environment.js.map +1 -1
- package/dist/spawner-lifecycle.d.ts +2 -2
- package/dist/spawner-lifecycle.d.ts.map +1 -1
- package/dist/spawner-lifecycle.js +3 -3
- package/dist/spawner-lifecycle.js.map +1 -1
- package/dist/spawner-resolution.d.ts +3 -3
- package/dist/spawner-resolution.d.ts.map +1 -1
- package/dist/spawner-resolution.js +4 -4
- package/dist/spawner-resolution.js.map +1 -1
- package/dist/spawner-stream-handlers.js +2 -2
- package/dist/spawner-stream-handlers.js.map +1 -1
- package/dist/spawner.d.ts +1 -1
- package/dist/spawner.d.ts.map +1 -1
- package/dist/spawner.js +6 -6
- package/dist/spawner.js.map +1 -1
- package/dist/stage-classifier.d.ts +1 -1
- package/dist/stage-classifier.js +1 -1
- package/dist/state-cascade.d.ts +25 -25
- package/dist/state-cascade.d.ts.map +1 -1
- package/dist/state-cascade.js +89 -89
- package/dist/state-cascade.js.map +1 -1
- package/dist/supabase.d.ts +4 -4
- package/dist/supabase.d.ts.map +1 -1
- package/dist/supabase.js +5 -5
- package/dist/supabase.js.map +1 -1
- package/dist/task-converter.d.ts +4 -4
- package/dist/task-converter.d.ts.map +1 -1
- package/dist/task-converter.js +1 -1
- package/dist/task-converter.js.map +1 -1
- package/dist/team-prompt-base.d.ts +49 -22
- package/dist/team-prompt-base.d.ts.map +1 -1
- package/dist/team-prompt-base.js +96 -32
- package/dist/team-prompt-base.js.map +1 -1
- package/dist/team-spawner.d.ts +9 -9
- package/dist/team-spawner.d.ts.map +1 -1
- package/dist/team-spawner.js +84 -82
- package/dist/team-spawner.js.map +1 -1
- package/dist/telemetry-writer.d.ts +2 -2
- package/dist/telemetry-writer.d.ts.map +1 -1
- package/dist/templates/claude-md.js +8 -8
- package/dist/templates/claude-settings.d.ts +1 -1
- package/dist/templates/claude-settings.js +2 -2
- package/dist/templates/claude-settings.js.map +1 -1
- package/dist/trigger-executor.d.ts +4 -4
- package/dist/trigger-executor.d.ts.map +1 -1
- package/dist/trigger-executor.js +11 -11
- package/dist/trigger-executor.js.map +1 -1
- package/dist/types/dag.d.ts +1 -1
- package/dist/types/delivery.d.ts +3 -3
- package/dist/types/delivery.d.ts.map +1 -1
- package/dist/types/{strategy.d.ts → focus.d.ts} +65 -31
- package/dist/types/focus.d.ts.map +1 -0
- package/dist/types/focus.js +5 -0
- package/dist/types/focus.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/merge.d.ts +1 -1
- package/dist/types/session.d.ts +2 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/unified-engine-lifecycle.js +11 -11
- package/dist/unified-engine-lifecycle.js.map +1 -1
- package/dist/unified-init.d.ts +1 -1
- package/dist/unified-init.js +9 -9
- package/dist/unified-init.js.map +1 -1
- package/dist/unified-shell-config.d.ts +4 -4
- package/dist/unified-shell-config.d.ts.map +1 -1
- package/dist/unified-shell-config.js +5 -5
- package/dist/unified-shell-config.js.map +1 -1
- package/dist/unified-shell-status.js +3 -3
- package/dist/unified-shell-status.js.map +1 -1
- package/dist/unified-shell.d.ts +1 -1
- package/dist/unified-shell.js +4 -4
- package/dist/unified-shell.js.map +1 -1
- package/dist/workflow-engine.d.ts +1 -1
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/{worktree-strategy.d.ts → worktree-focus.d.ts} +15 -15
- package/dist/worktree-focus.d.ts.map +1 -0
- package/dist/{worktree-strategy.js → worktree-focus.js} +37 -37
- package/dist/worktree-focus.js.map +1 -0
- package/dist/worktree.d.ts +10 -10
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +39 -39
- package/dist/worktree.js.map +1 -1
- package/package.json +3 -3
- package/dist/listener-review.d.ts +0 -37
- package/dist/listener-review.d.ts.map +0 -1
- package/dist/listener-review.js +0 -217
- package/dist/listener-review.js.map +0 -1
- package/dist/queries/strategies.d.ts.map +0 -1
- package/dist/queries/strategies.js.map +0 -1
- package/dist/review-spawner.d.ts +0 -32
- package/dist/review-spawner.d.ts.map +0 -1
- package/dist/review-spawner.js +0 -170
- package/dist/review-spawner.js.map +0 -1
- package/dist/strategy-completion-event.d.ts +0 -63
- package/dist/strategy-completion-event.d.ts.map +0 -1
- package/dist/strategy-completion-event.js +0 -138
- package/dist/strategy-completion-event.js.map +0 -1
- package/dist/strategy-completion.d.ts.map +0 -1
- package/dist/strategy-completion.js.map +0 -1
- package/dist/strategy-engine.d.ts.map +0 -1
- package/dist/strategy-engine.js.map +0 -1
- package/dist/strategy-executor.d.ts +0 -55
- package/dist/strategy-executor.d.ts.map +0 -1
- package/dist/strategy-executor.js.map +0 -1
- package/dist/strategy-lifecycle.d.ts +0 -61
- package/dist/strategy-lifecycle.d.ts.map +0 -1
- package/dist/strategy-lifecycle.js.map +0 -1
- package/dist/strategy-merge.d.ts.map +0 -1
- package/dist/strategy-merge.js.map +0 -1
- package/dist/strategy-prompt-builder.d.ts +0 -24
- package/dist/strategy-prompt-builder.d.ts.map +0 -1
- package/dist/strategy-prompt-builder.js.map +0 -1
- package/dist/strategy-provisioning.d.ts +0 -16
- package/dist/strategy-provisioning.d.ts.map +0 -1
- package/dist/strategy-provisioning.js +0 -119
- package/dist/strategy-provisioning.js.map +0 -1
- package/dist/strategy-spawn-helpers.d.ts.map +0 -1
- package/dist/strategy-spawn-helpers.js.map +0 -1
- package/dist/strategy-team-lifecycle.d.ts.map +0 -1
- package/dist/strategy-team-lifecycle.js.map +0 -1
- package/dist/strategy-team-state.d.ts +0 -24
- package/dist/strategy-team-state.d.ts.map +0 -1
- package/dist/strategy-team-state.js.map +0 -1
- package/dist/strategy-teardown.d.ts +0 -24
- package/dist/strategy-teardown.d.ts.map +0 -1
- package/dist/strategy-teardown.js +0 -158
- package/dist/strategy-teardown.js.map +0 -1
- package/dist/strategy-worktree-state.d.ts +0 -47
- package/dist/strategy-worktree-state.d.ts.map +0 -1
- package/dist/strategy-worktree-state.js.map +0 -1
- package/dist/team-prompt-variants.d.ts +0 -17
- package/dist/team-prompt-variants.d.ts.map +0 -1
- package/dist/team-prompt-variants.js +0 -79
- package/dist/team-prompt-variants.js.map +0 -1
- package/dist/types/strategy.d.ts.map +0 -1
- package/dist/types/strategy.js +0 -5
- package/dist/types/strategy.js.map +0 -1
- package/dist/worktree-merge.d.ts +0 -23
- package/dist/worktree-merge.d.ts.map +0 -1
- package/dist/worktree-merge.js +0 -57
- package/dist/worktree-merge.js.map +0 -1
- package/dist/worktree-strategy.d.ts.map +0 -1
- package/dist/worktree-strategy.js.map +0 -1
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Focus lifecycle management.
|
|
3
3
|
*
|
|
4
|
-
* Handles persistent worktree provisioning for active
|
|
5
|
-
* coordinated teardown when
|
|
6
|
-
* separate
|
|
4
|
+
* Handles persistent worktree provisioning for active focuses and
|
|
5
|
+
* coordinated teardown when focuses are deactivated. Replaces the
|
|
6
|
+
* separate detectDeactivatedFocuses() and checkQaForDeactivatedFocuses()
|
|
7
7
|
* flows with a single unified lifecycle.
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
10
10
|
import { join, resolve } from 'node:path';
|
|
11
11
|
import { installAuditPreCommitHook } from './audit-hooks.js';
|
|
12
|
-
import {
|
|
13
|
-
import { createWorktree, syncIntegrationWithMain, runGitSync,
|
|
14
|
-
import {
|
|
15
|
-
import { getActiveTeams } from './
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
12
|
+
import { getReadyFocuses, getActiveFocuses, reportGitState } from './supabase.js';
|
|
13
|
+
import { createWorktree, syncIntegrationWithMain, runGitSync, safeRemoveFocusWorktree, commitWipChanges, generateMcpConfigInWorktree, ensureIntegrationBranch, ensureMergeWorktree } from './git.js';
|
|
14
|
+
import { generateFocusBranchName, terminateTeam, waitForTeamExit } from './focus-executor.js';
|
|
15
|
+
import { getActiveTeams } from './focus-team-state.js';
|
|
16
|
+
import { recordFocusTeardown } from './spawn-cooldown.js';
|
|
17
|
+
import { getFocusWorktree, setFocusWorktree, removeFocusWorktree, getAllFocusWorktrees, hasFocusWorktree, } from './focus-worktree-state.js';
|
|
18
|
+
import { getFocusDeliveries } from './queries/focuses.js';
|
|
19
19
|
import { isStatusAgentActionable } from './stage-classifier.js';
|
|
20
|
-
import { escalateMergeConflict } from './
|
|
20
|
+
import { escalateMergeConflict } from './focus-merge.js';
|
|
21
21
|
import { getQaEnvironment, removeQaEnvironment } from './qa-state.js';
|
|
22
22
|
import { stopQaDevServer } from './qa-dev-server.js';
|
|
23
23
|
import { updateQaStatus } from './supabase.js';
|
|
24
24
|
// ── Persistent worktree provisioning ─────────────────────────────────
|
|
25
25
|
/**
|
|
26
|
-
* Ensure persistent worktrees exist for all active
|
|
26
|
+
* Ensure persistent worktrees exist for all active focuses.
|
|
27
27
|
*
|
|
28
|
-
* Called each poll cycle BEFORE
|
|
29
|
-
* worktrees for active
|
|
30
|
-
* Syncs integration with main once per cycle (not per
|
|
28
|
+
* Called each poll cycle BEFORE processReadyFocuses(). Creates
|
|
29
|
+
* worktrees for active focuses that don't have one yet.
|
|
30
|
+
* Syncs integration with main once per cycle (not per focus).
|
|
31
31
|
*/
|
|
32
|
-
export async function
|
|
33
|
-
let
|
|
32
|
+
export async function ensureFocusWorktrees(config, roles) {
|
|
33
|
+
let readyFocuses;
|
|
34
34
|
try {
|
|
35
|
-
|
|
35
|
+
readyFocuses = await getReadyFocuses(config.organizationId, config.productId);
|
|
36
36
|
}
|
|
37
37
|
catch (err) {
|
|
38
|
-
console.warn(`[
|
|
38
|
+
console.warn(`[focus-lifecycle] Failed to query ready focuses for worktree provisioning: ${err.message}`);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
// Find
|
|
42
|
-
const needsWorktree =
|
|
41
|
+
// Find focuses that need worktrees
|
|
42
|
+
const needsWorktree = readyFocuses.filter(s => !hasFocusWorktree(s.focus_id));
|
|
43
43
|
if (needsWorktree.length === 0)
|
|
44
44
|
return;
|
|
45
45
|
// Ensure git infrastructure exists (normally handled by initGit on startup,
|
|
@@ -49,29 +49,29 @@ export async function ensureStrategyWorktrees(config, roles) {
|
|
|
49
49
|
ensureMergeWorktree(config);
|
|
50
50
|
}
|
|
51
51
|
catch (err) {
|
|
52
|
-
console.warn(`[
|
|
52
|
+
console.warn(`[focus-lifecycle] Failed to ensure git infrastructure: ${err.message}`);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
// Sync integration with main once before creating any worktrees
|
|
56
56
|
try {
|
|
57
57
|
const syncResult = await syncIntegrationWithMain(config);
|
|
58
58
|
if (syncResult.success) {
|
|
59
|
-
console.log(`[
|
|
59
|
+
console.log(`[focus-lifecycle] Synced integration with main before worktree provisioning`);
|
|
60
60
|
}
|
|
61
61
|
else {
|
|
62
|
-
console.warn(`[
|
|
62
|
+
console.warn(`[focus-lifecycle] Could not sync integration with main: ${syncResult.error}`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
catch (err) {
|
|
66
|
-
console.warn(`[
|
|
66
|
+
console.warn(`[focus-lifecycle] Sync failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
67
67
|
}
|
|
68
|
-
for (const
|
|
69
|
-
const role = roles.get(
|
|
68
|
+
for (const focus of needsWorktree) {
|
|
69
|
+
const role = roles.get(focus.assigned_agent_role_id);
|
|
70
70
|
if (!role) {
|
|
71
|
-
console.warn(`[
|
|
71
|
+
console.warn(`[focus-lifecycle] Unknown agent role ${focus.assigned_agent_role_id} for focus "${focus.focus_name}", skipping worktree`);
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
|
-
const branchName =
|
|
74
|
+
const branchName = generateFocusBranchName(role, focus.focus_name, focus.focus_id);
|
|
75
75
|
const worktreePath = join(config.worktreeDir, branchName.replace(/\//g, '-'));
|
|
76
76
|
// Check if a valid worktree already exists on disk (e.g., after daemon restart
|
|
77
77
|
// or reactivation). Adopt it instead of destroying and rebuilding.
|
|
@@ -80,10 +80,10 @@ export async function ensureStrategyWorktrees(config, roles) {
|
|
|
80
80
|
const branchResult = runGitSync(['rev-parse', '--abbrev-ref', 'HEAD'], worktreePath);
|
|
81
81
|
if (gitDirResult.success && branchResult.success && branchResult.output === branchName) {
|
|
82
82
|
// Valid worktree on the expected branch -- adopt it
|
|
83
|
-
console.log(`[
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
console.log(`[focus-lifecycle] Adopting existing worktree for "${focus.focus_name}": ${worktreePath}`);
|
|
84
|
+
setFocusWorktree(focus.focus_id, {
|
|
85
|
+
focusId: focus.focus_id,
|
|
86
|
+
focusName: focus.focus_name,
|
|
87
87
|
worktreePath,
|
|
88
88
|
branchName,
|
|
89
89
|
createdAt: new Date(),
|
|
@@ -93,78 +93,78 @@ export async function ensureStrategyWorktrees(config, roles) {
|
|
|
93
93
|
const rebaseResult = runGitSync(['rebase', config.integrationBranch], worktreePath);
|
|
94
94
|
if (!rebaseResult.success) {
|
|
95
95
|
runGitSync(['rebase', '--abort'], worktreePath);
|
|
96
|
-
console.warn(`[
|
|
96
|
+
console.warn(`[focus-lifecycle] Could not rebase adopted worktree for "${focus.focus_name}" onto integration. Continuing with existing state.`);
|
|
97
97
|
}
|
|
98
98
|
else {
|
|
99
|
-
console.log(`[
|
|
99
|
+
console.log(`[focus-lifecycle] Rebased adopted worktree for "${focus.focus_name}" onto ${config.integrationBranch}`);
|
|
100
100
|
}
|
|
101
101
|
// Generate .mcp.json with absolute path to MCP server
|
|
102
102
|
generateMcpConfigInWorktree(worktreePath, config);
|
|
103
|
-
// Install audit pre-commit hook for read-only
|
|
104
|
-
if (
|
|
103
|
+
// Install audit pre-commit hook for read-only focuses
|
|
104
|
+
if (focus.read_only) {
|
|
105
105
|
installAuditPreCommitHook(worktreePath);
|
|
106
|
-
console.log(`[
|
|
106
|
+
console.log(`[focus-lifecycle] Installed audit pre-commit hook for "${focus.focus_name}"`);
|
|
107
107
|
}
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
110
110
|
// Directory exists but is not a valid worktree for this branch -- fall through to createWorktree
|
|
111
|
-
console.log(`[
|
|
111
|
+
console.log(`[focus-lifecycle] Existing directory at ${worktreePath} is not a valid worktree for ${branchName}, recreating`);
|
|
112
112
|
}
|
|
113
113
|
try {
|
|
114
114
|
const createdPath = await createWorktree(config, branchName);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
setFocusWorktree(focus.focus_id, {
|
|
116
|
+
focusId: focus.focus_id,
|
|
117
|
+
focusName: focus.focus_name,
|
|
118
118
|
worktreePath: createdPath,
|
|
119
119
|
branchName,
|
|
120
120
|
createdAt: new Date(),
|
|
121
121
|
hasQaDevServer: false,
|
|
122
122
|
});
|
|
123
|
-
console.log(`[
|
|
124
|
-
// Install audit pre-commit hook for read-only
|
|
125
|
-
if (
|
|
123
|
+
console.log(`[focus-lifecycle] Created persistent worktree for "${focus.focus_name}": ${createdPath}`);
|
|
124
|
+
// Install audit pre-commit hook for read-only focuses
|
|
125
|
+
if (focus.read_only) {
|
|
126
126
|
installAuditPreCommitHook(createdPath);
|
|
127
|
-
console.log(`[
|
|
127
|
+
console.log(`[focus-lifecycle] Installed audit pre-commit hook for "${focus.focus_name}"`);
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
catch (err) {
|
|
131
|
-
console.error(`[
|
|
132
|
-
// Continue with other
|
|
131
|
+
console.error(`[focus-lifecycle] Failed to create worktree for "${focus.focus_name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
132
|
+
// Continue with other focuses
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
// ── Coordinated teardown ─────────────────────────────────────────────
|
|
137
137
|
/**
|
|
138
|
-
* Tear down a single
|
|
138
|
+
* Tear down a single focus: stop QA, terminate team, remove worktree.
|
|
139
139
|
*
|
|
140
140
|
* Each step is independently try/caught so a failure in one
|
|
141
141
|
* does not prevent the others from running.
|
|
142
142
|
*/
|
|
143
|
-
export async function
|
|
144
|
-
console.log(`[
|
|
143
|
+
export async function teardownFocus(config, focusId, focusName) {
|
|
144
|
+
console.log(`[focus-lifecycle] Tearing down focus "${focusName}"`);
|
|
145
145
|
// Record teardown for spawn cooldown (prevents immediate respawn crash loops)
|
|
146
|
-
|
|
146
|
+
recordFocusTeardown(focusId);
|
|
147
147
|
// 1. Stop QA dev server if running
|
|
148
148
|
try {
|
|
149
|
-
const qaState = getQaEnvironment(
|
|
149
|
+
const qaState = getQaEnvironment(focusId);
|
|
150
150
|
if (qaState?.pid) {
|
|
151
|
-
console.log(`[
|
|
151
|
+
console.log(`[focus-lifecycle] Stopping QA dev server for "${focusName}" (PID: ${qaState.pid})`);
|
|
152
152
|
await stopQaDevServer(qaState.pid);
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
catch (err) {
|
|
156
|
-
console.warn(`[
|
|
156
|
+
console.warn(`[focus-lifecycle] Failed to stop QA dev server for "${focusName}": ${err.message}`);
|
|
157
157
|
}
|
|
158
158
|
// 2. Clear QA in-memory state
|
|
159
159
|
try {
|
|
160
|
-
removeQaEnvironment(
|
|
160
|
+
removeQaEnvironment(focusId);
|
|
161
161
|
}
|
|
162
162
|
catch (err) {
|
|
163
|
-
console.warn(`[
|
|
163
|
+
console.warn(`[focus-lifecycle] Failed to clear QA state for "${focusName}": ${err.message}`);
|
|
164
164
|
}
|
|
165
165
|
// 3. Clear QA DB state
|
|
166
166
|
try {
|
|
167
|
-
await updateQaStatus(
|
|
167
|
+
await updateQaStatus(focusId, {
|
|
168
168
|
qaStatus: null,
|
|
169
169
|
qaUrl: null,
|
|
170
170
|
qaPort: null,
|
|
@@ -174,204 +174,204 @@ export async function teardownStrategy(config, strategyId, strategyName) {
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
catch (err) {
|
|
177
|
-
console.warn(`[
|
|
177
|
+
console.warn(`[focus-lifecycle] Failed to clear QA DB state for "${focusName}": ${err.message}`);
|
|
178
178
|
}
|
|
179
179
|
// 4. Terminate agent team and wait for process exit
|
|
180
180
|
try {
|
|
181
181
|
const activeTeams = getActiveTeams();
|
|
182
|
-
const team = activeTeams.get(
|
|
182
|
+
const team = activeTeams.get(focusId);
|
|
183
183
|
if (team && team.phase !== 'shutting_down' && team.phase !== 'terminated') {
|
|
184
|
-
console.log(`[
|
|
185
|
-
terminateTeam(
|
|
184
|
+
console.log(`[focus-lifecycle] Terminating team for "${focusName}"`);
|
|
185
|
+
terminateTeam(focusId);
|
|
186
186
|
// Wait for process to actually exit (default 30s, then SIGKILL)
|
|
187
|
-
const exited = await waitForTeamExit(
|
|
187
|
+
const exited = await waitForTeamExit(focusId);
|
|
188
188
|
if (!exited) {
|
|
189
|
-
console.warn(`[
|
|
189
|
+
console.warn(`[focus-lifecycle] Team for "${focusName}" did not exit cleanly after SIGKILL`);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
catch (err) {
|
|
194
|
-
console.warn(`[
|
|
194
|
+
console.warn(`[focus-lifecycle] Failed to terminate team for "${focusName}": ${err.message}`);
|
|
195
195
|
}
|
|
196
196
|
// 4b. Commit any uncommitted WIP changes left by the agent
|
|
197
197
|
try {
|
|
198
|
-
const worktreeInfo =
|
|
198
|
+
const worktreeInfo = getFocusWorktree(focusId);
|
|
199
199
|
if (worktreeInfo) {
|
|
200
|
-
commitWipChanges(worktreeInfo.worktreePath,
|
|
200
|
+
commitWipChanges(worktreeInfo.worktreePath, focusName);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
catch (err) {
|
|
204
|
-
console.warn(`[
|
|
204
|
+
console.warn(`[focus-lifecycle] WIP commit failed for "${focusName}": ${err.message}`);
|
|
205
205
|
}
|
|
206
|
-
// 5. Remove
|
|
206
|
+
// 5. Remove focus worktree via canonical path (merge-before-remove + conditional state cleanup).
|
|
207
207
|
// If the team was killed mid-work (OOM, signal, crash) before handleTeamCompletion ran,
|
|
208
|
-
// there may be commits that never got a merge attempt.
|
|
208
|
+
// there may be commits that never got a merge attempt. safeRemoveFocusWorktree closes that gap.
|
|
209
209
|
try {
|
|
210
|
-
const worktreeInfo =
|
|
210
|
+
const worktreeInfo = getFocusWorktree(focusId);
|
|
211
211
|
if (worktreeInfo) {
|
|
212
|
-
console.log(`[
|
|
213
|
-
const removeResult = await
|
|
212
|
+
console.log(`[focus-lifecycle] Removing worktree for "${focusName}": ${worktreeInfo.worktreePath}`);
|
|
213
|
+
const removeResult = await safeRemoveFocusWorktree({
|
|
214
214
|
config,
|
|
215
215
|
worktreePath: worktreeInfo.worktreePath,
|
|
216
216
|
branchName: worktreeInfo.branchName,
|
|
217
|
-
deliveryName:
|
|
218
|
-
deliveryId:
|
|
219
|
-
|
|
217
|
+
deliveryName: focusName,
|
|
218
|
+
deliveryId: focusId,
|
|
219
|
+
focusId,
|
|
220
220
|
});
|
|
221
221
|
// Escalate if merge failed (worktree preserved with unmerged work)
|
|
222
222
|
if (removeResult.merged && removeResult.mergeError) {
|
|
223
223
|
escalateMergeConflict({
|
|
224
224
|
organizationId: config.organizationId,
|
|
225
225
|
sessionId: '',
|
|
226
|
-
deliveryId:
|
|
227
|
-
deliveryName:
|
|
226
|
+
deliveryId: focusId,
|
|
227
|
+
deliveryName: focusName,
|
|
228
228
|
branchName: worktreeInfo.branchName,
|
|
229
229
|
integrationBranch: config.integrationBranch,
|
|
230
230
|
mergeError: removeResult.mergeError,
|
|
231
|
-
}).catch(err => console.warn(`[
|
|
231
|
+
}).catch(err => console.warn(`[focus-lifecycle] escalateMergeConflict failed for "${focusName}": ${err.message}`));
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
catch (err) {
|
|
236
|
-
console.warn(`[
|
|
236
|
+
console.warn(`[focus-lifecycle] Failed to remove worktree for "${focusName}": ${err.message}`);
|
|
237
237
|
}
|
|
238
|
-
console.log(`[
|
|
238
|
+
console.log(`[focus-lifecycle] Teardown complete for "${focusName}"`);
|
|
239
239
|
}
|
|
240
240
|
/**
|
|
241
|
-
* Detect deactivated
|
|
241
|
+
* Detect deactivated focuses and tear them down.
|
|
242
242
|
*
|
|
243
|
-
* Replaces both
|
|
244
|
-
* and
|
|
243
|
+
* Replaces both detectDeactivatedFocuses() from focus-executor.ts
|
|
244
|
+
* and checkQaForDeactivatedFocuses() from qa-orchestrator.ts.
|
|
245
245
|
* A single pass handles both team termination and QA cleanup.
|
|
246
246
|
*/
|
|
247
|
-
export async function
|
|
248
|
-
const allWorktrees =
|
|
247
|
+
export async function checkForDeactivatedFocuses(config) {
|
|
248
|
+
const allWorktrees = getAllFocusWorktrees();
|
|
249
249
|
const activeTeams = getActiveTeams();
|
|
250
250
|
// Nothing to check if no worktrees and no teams
|
|
251
251
|
if (allWorktrees.size === 0 && activeTeams.size === 0)
|
|
252
252
|
return;
|
|
253
|
-
let
|
|
253
|
+
let activeFocusIds;
|
|
254
254
|
try {
|
|
255
|
-
// Use
|
|
255
|
+
// Use getActiveFocuses (not getReadyFocuses) -- a focus is "active" even
|
|
256
256
|
// when all deliveries are done. Worktrees must persist for QA and idle queues.
|
|
257
|
-
const
|
|
258
|
-
|
|
257
|
+
const activeFocuses = await getActiveFocuses(config.organizationId, config.productId);
|
|
258
|
+
activeFocusIds = new Set(activeFocuses.map(s => s.focus_id));
|
|
259
259
|
}
|
|
260
260
|
catch (err) {
|
|
261
261
|
// If the endpoint isn't deployed yet, skip deactivation checks entirely.
|
|
262
262
|
// Better to leave worktrees alive than to tear them down incorrectly.
|
|
263
|
-
console.warn(`[
|
|
263
|
+
console.warn(`[focus-lifecycle] Cannot determine active focuses (skipping deactivation check): ${err.message}`);
|
|
264
264
|
return;
|
|
265
265
|
}
|
|
266
|
-
// In multi-product setups, the global worktree/team maps contain
|
|
267
|
-
// from ALL products. Only check
|
|
268
|
-
// product — otherwise Product B's poll tears down Product A's
|
|
266
|
+
// In multi-product setups, the global worktree/team maps contain focuses
|
|
267
|
+
// from ALL products. Only check focuses we can confirm belong to THIS
|
|
268
|
+
// product — otherwise Product B's poll tears down Product A's focuses.
|
|
269
269
|
//
|
|
270
|
-
//
|
|
270
|
+
// Focus ownership is determined by:
|
|
271
271
|
// 1. Active team with matching productId (definitive)
|
|
272
|
-
// 2. Worktree for a
|
|
273
|
-
// (if it's active for this product, it's this product's
|
|
272
|
+
// 2. Worktree for a focus that appears in this product's active set
|
|
273
|
+
// (if it's active for this product, it's this product's focus)
|
|
274
274
|
//
|
|
275
275
|
// Orphan worktrees (no team, not in active set) have unknown product
|
|
276
276
|
// ownership and are left alone — startup recovery handles them.
|
|
277
|
-
const
|
|
277
|
+
const trackedFocusIds = new Set();
|
|
278
278
|
for (const [id, team] of activeTeams) {
|
|
279
279
|
if (team.productId === config.productId) {
|
|
280
|
-
|
|
280
|
+
trackedFocusIds.add(id);
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
-
for (const
|
|
284
|
-
if (
|
|
283
|
+
for (const focusId of trackedFocusIds) {
|
|
284
|
+
if (activeFocusIds.has(focusId))
|
|
285
285
|
continue;
|
|
286
|
-
const worktreeInfo =
|
|
287
|
-
const team = activeTeams.get(
|
|
288
|
-
const name = worktreeInfo?.
|
|
286
|
+
const worktreeInfo = getFocusWorktree(focusId);
|
|
287
|
+
const team = activeTeams.get(focusId);
|
|
288
|
+
const name = worktreeInfo?.focusName ?? team?.focusName ?? focusId;
|
|
289
289
|
// Skip teams already shutting down
|
|
290
290
|
if (team && (team.phase === 'shutting_down' || team.phase === 'terminated') && !worktreeInfo) {
|
|
291
291
|
continue;
|
|
292
292
|
}
|
|
293
293
|
const reason = worktreeInfo && !team
|
|
294
|
-
? 'worktree exists but
|
|
294
|
+
? 'worktree exists but focus no longer active'
|
|
295
295
|
: team && !worktreeInfo
|
|
296
|
-
? 'team running but
|
|
297
|
-
: '
|
|
298
|
-
console.log(`[
|
|
299
|
-
await
|
|
296
|
+
? 'team running but focus no longer active'
|
|
297
|
+
: 'focus no longer active';
|
|
298
|
+
console.log(`[focus-lifecycle] Focus "${name}" deactivated (${reason}) -- coordinated teardown`);
|
|
299
|
+
await teardownFocus(config, focusId, name);
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
// ── Startup recovery ─────────────────────────────────────────────
|
|
303
303
|
/**
|
|
304
|
-
* Rebuild the
|
|
304
|
+
* Rebuild the focus-worktree-state after a daemon restart.
|
|
305
305
|
*
|
|
306
306
|
* Two-phase approach:
|
|
307
307
|
* Phase 1 (DB-first): If hydrateFromDb() populated the Map, validate
|
|
308
|
-
* each entry against disk and enrich with
|
|
308
|
+
* each entry against disk and enrich with focus names. Removes
|
|
309
309
|
* stale entries (worktree deleted from disk) and cleans up entries
|
|
310
|
-
* for deactivated
|
|
310
|
+
* for deactivated focuses.
|
|
311
311
|
* Phase 2 (disk fallback): Scans for agent-* directories NOT already
|
|
312
312
|
* in the Map. Covers first-run after migration and manually created
|
|
313
|
-
* worktrees. Matches by ID prefix against active
|
|
313
|
+
* worktrees. Matches by ID prefix against active focuses.
|
|
314
314
|
*
|
|
315
315
|
* Also removes legacy qa-* directories from the pre-persistent worktree era.
|
|
316
316
|
*
|
|
317
317
|
* Must run AFTER hydrateFromDb() and crash recovery but BEFORE startListeners().
|
|
318
318
|
*/
|
|
319
|
-
export async function
|
|
319
|
+
export async function rebuildFocusWorktreeState(config) {
|
|
320
320
|
const { worktreeDir, repoPath, organizationId, productId } = config;
|
|
321
321
|
if (!existsSync(worktreeDir)) {
|
|
322
|
-
console.log('[
|
|
322
|
+
console.log('[focus-lifecycle] No worktree directory — nothing to rebuild');
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
|
-
// Query active
|
|
326
|
-
let
|
|
325
|
+
// Query active focuses for enrichment and deactivation detection
|
|
326
|
+
let activeFocuses;
|
|
327
327
|
try {
|
|
328
|
-
|
|
328
|
+
activeFocuses = await getActiveFocuses(organizationId, productId);
|
|
329
329
|
}
|
|
330
330
|
catch (err) {
|
|
331
|
-
console.warn(`[
|
|
331
|
+
console.warn(`[focus-lifecycle] Cannot determine active focuses for state rebuild (preserving all): ${err.message}`);
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
|
-
const
|
|
335
|
-
const
|
|
334
|
+
const focusById = new Map(activeFocuses.map(s => [s.focus_id, s]));
|
|
335
|
+
const focusByPrefix = new Map(activeFocuses.map(s => [s.focus_id.slice(0, 8), s]));
|
|
336
336
|
let validated = 0;
|
|
337
337
|
let removedStale = 0;
|
|
338
338
|
let deactivatedCleaned = 0;
|
|
339
339
|
// ── Phase 1: Validate DB-hydrated entries ──────────────────────
|
|
340
|
-
const allWorktrees =
|
|
340
|
+
const allWorktrees = getAllFocusWorktrees();
|
|
341
341
|
if (allWorktrees.size > 0) {
|
|
342
|
-
console.log(`[
|
|
343
|
-
for (const [
|
|
342
|
+
console.log(`[focus-lifecycle] Validating ${allWorktrees.size} DB-hydrated worktree(s) against disk`);
|
|
343
|
+
for (const [focusId, info] of [...allWorktrees]) {
|
|
344
344
|
if (!existsSync(info.worktreePath)) {
|
|
345
345
|
// Worktree was manually deleted — remove from state (also fires async DB remove)
|
|
346
|
-
console.log(`[
|
|
347
|
-
|
|
346
|
+
console.log(`[focus-lifecycle] DB-hydrated worktree no longer on disk: ${info.worktreePath}`);
|
|
347
|
+
removeFocusWorktree(focusId);
|
|
348
348
|
removedStale++;
|
|
349
349
|
continue;
|
|
350
350
|
}
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
// Active — enrich with
|
|
354
|
-
info.
|
|
351
|
+
const focus = focusById.get(focusId);
|
|
352
|
+
if (focus) {
|
|
353
|
+
// Active — enrich with focus name (not stored in DB)
|
|
354
|
+
info.focusName = focus.focus_name;
|
|
355
355
|
validated++;
|
|
356
|
-
console.log(`[
|
|
356
|
+
console.log(`[focus-lifecycle] Validated worktree for "${focus.focus_name}": ${info.worktreePath}`);
|
|
357
357
|
}
|
|
358
358
|
else {
|
|
359
359
|
// Deactivated — clean up via canonical path
|
|
360
|
-
console.log(`[
|
|
360
|
+
console.log(`[focus-lifecycle] DB-hydrated worktree for deactivated focus: ${info.worktreePath}`);
|
|
361
361
|
try {
|
|
362
|
-
const result = await
|
|
362
|
+
const result = await safeRemoveFocusWorktree({
|
|
363
363
|
config,
|
|
364
364
|
worktreePath: info.worktreePath,
|
|
365
365
|
branchName: info.branchName,
|
|
366
|
-
deliveryName: info.
|
|
367
|
-
deliveryId:
|
|
368
|
-
|
|
366
|
+
deliveryName: info.focusName || focusId,
|
|
367
|
+
deliveryId: focusId,
|
|
368
|
+
focusId,
|
|
369
369
|
});
|
|
370
370
|
if (result.removed)
|
|
371
371
|
deactivatedCleaned++;
|
|
372
372
|
}
|
|
373
373
|
catch (err) {
|
|
374
|
-
console.warn(`[
|
|
374
|
+
console.warn(`[focus-lifecycle] Failed to clean up deactivated worktree: ${err.message}`);
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
}
|
|
@@ -392,18 +392,18 @@ export async function rebuildStrategyWorktreeState(config) {
|
|
|
392
392
|
});
|
|
393
393
|
}
|
|
394
394
|
catch (err) {
|
|
395
|
-
console.warn(`[
|
|
395
|
+
console.warn(`[focus-lifecycle] Failed to scan worktree directory: ${err.message}`);
|
|
396
396
|
agentDirs = [];
|
|
397
397
|
}
|
|
398
398
|
// Filter to directories whose full path is NOT already tracked
|
|
399
|
-
const trackedPaths = new Set([...
|
|
399
|
+
const trackedPaths = new Set([...getAllFocusWorktrees().values()].map(w => w.worktreePath));
|
|
400
400
|
const orphanDirs = agentDirs.filter(dirName => {
|
|
401
401
|
const fullPath = resolve(join(worktreeDir, dirName));
|
|
402
402
|
return !trackedPaths.has(fullPath);
|
|
403
403
|
});
|
|
404
404
|
let diskRegistered = 0;
|
|
405
405
|
if (orphanDirs.length > 0) {
|
|
406
|
-
console.log(`[
|
|
406
|
+
console.log(`[focus-lifecycle] Found ${orphanDirs.length} untracked agent-* dir(s) on disk (migration fallback)`);
|
|
407
407
|
// Parse git worktree list for branch names
|
|
408
408
|
const pathToBranch = new Map();
|
|
409
409
|
const worktreeListResult = runGitSync(['worktree', 'list', '--porcelain'], repoPath);
|
|
@@ -427,24 +427,24 @@ export async function rebuildStrategyWorktreeState(config) {
|
|
|
427
427
|
const branchName = pathToBranch.get(fullPath) ?? dirName.replace(/-/g, '/');
|
|
428
428
|
const idMatch = dirName.match(/-([0-9a-f]{8})$/);
|
|
429
429
|
const idPrefix = idMatch ? idMatch[1] : null;
|
|
430
|
-
const
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
430
|
+
const focus = idPrefix ? focusByPrefix.get(idPrefix) : undefined;
|
|
431
|
+
if (focus) {
|
|
432
|
+
setFocusWorktree(focus.focus_id, {
|
|
433
|
+
focusId: focus.focus_id,
|
|
434
|
+
focusName: focus.focus_name,
|
|
435
435
|
worktreePath: fullPath,
|
|
436
436
|
branchName,
|
|
437
437
|
createdAt: new Date(),
|
|
438
438
|
hasQaDevServer: false,
|
|
439
439
|
});
|
|
440
440
|
diskRegistered++;
|
|
441
|
-
console.log(`[
|
|
441
|
+
console.log(`[focus-lifecycle] Registered orphan worktree for "${focus.focus_name}": ${fullPath}`);
|
|
442
442
|
}
|
|
443
443
|
else {
|
|
444
444
|
// Deactivated — canonical removal
|
|
445
|
-
console.log(`[
|
|
445
|
+
console.log(`[focus-lifecycle] Cleaning up orphan deactivated worktree: ${fullPath} [${branchName}]`);
|
|
446
446
|
try {
|
|
447
|
-
const result = await
|
|
447
|
+
const result = await safeRemoveFocusWorktree({
|
|
448
448
|
config,
|
|
449
449
|
worktreePath: fullPath,
|
|
450
450
|
branchName,
|
|
@@ -455,7 +455,7 @@ export async function rebuildStrategyWorktreeState(config) {
|
|
|
455
455
|
deactivatedCleaned++;
|
|
456
456
|
}
|
|
457
457
|
catch (err) {
|
|
458
|
-
console.warn(`[
|
|
458
|
+
console.warn(`[focus-lifecycle] Failed to clean up orphan worktree ${dirName}: ${err.message}`);
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
}
|
|
@@ -475,7 +475,7 @@ export async function rebuildStrategyWorktreeState(config) {
|
|
|
475
475
|
});
|
|
476
476
|
for (const qaDir of legacyQaDirs) {
|
|
477
477
|
const qaPath = join(worktreeDir, qaDir);
|
|
478
|
-
console.log(`[
|
|
478
|
+
console.log(`[focus-lifecycle] Removing legacy qa-* directory: ${qaPath}`);
|
|
479
479
|
try {
|
|
480
480
|
const removeResult = runGitSync(['worktree', 'remove', '--force', qaPath], repoPath);
|
|
481
481
|
if (!removeResult.success) {
|
|
@@ -486,7 +486,7 @@ export async function rebuildStrategyWorktreeState(config) {
|
|
|
486
486
|
legacyCleaned++;
|
|
487
487
|
}
|
|
488
488
|
catch (err) {
|
|
489
|
-
console.warn(`[
|
|
489
|
+
console.warn(`[focus-lifecycle] Failed to remove legacy QA directory ${qaDir}: ${err.message}`);
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
}
|
|
@@ -500,29 +500,29 @@ export async function rebuildStrategyWorktreeState(config) {
|
|
|
500
500
|
deactivatedCleaned > 0 ? `${deactivatedCleaned} deactivated cleaned` : null,
|
|
501
501
|
legacyCleaned > 0 ? `${legacyCleaned} legacy qa-* removed` : null,
|
|
502
502
|
].filter(Boolean);
|
|
503
|
-
console.log(`[
|
|
503
|
+
console.log(`[focus-lifecycle] State rebuild complete: ${parts.join(', ')}`);
|
|
504
504
|
}
|
|
505
505
|
// ── Git state reconciliation ──────────────────────────────────────
|
|
506
506
|
/**
|
|
507
|
-
* Reconcile git state for active
|
|
507
|
+
* Reconcile git state for active focus worktrees after startup.
|
|
508
508
|
*
|
|
509
|
-
* Checks all rebuilt
|
|
509
|
+
* Checks all rebuilt focus worktrees and re-reports `worktree_active`
|
|
510
510
|
* for deliveries in coding/queued status. This ensures the DB reflects
|
|
511
511
|
* accurate git state even if previous reportGitState calls were lost
|
|
512
512
|
* (network blip, circuit breaker, daemon crash).
|
|
513
513
|
*
|
|
514
|
-
* Must run AFTER
|
|
514
|
+
* Must run AFTER rebuildFocusWorktreeState().
|
|
515
515
|
*/
|
|
516
516
|
export async function reconcileGitState(_config) {
|
|
517
|
-
const worktrees =
|
|
517
|
+
const worktrees = getAllFocusWorktrees();
|
|
518
518
|
if (worktrees.size === 0) {
|
|
519
519
|
return;
|
|
520
520
|
}
|
|
521
|
-
console.log(`[
|
|
521
|
+
console.log(`[focus-lifecycle] Reconciling git state for ${worktrees.size} focus worktree(s)...`);
|
|
522
522
|
let reconciled = 0;
|
|
523
|
-
for (const [
|
|
523
|
+
for (const [focusId, worktree] of worktrees) {
|
|
524
524
|
try {
|
|
525
|
-
const deliveries = await
|
|
525
|
+
const deliveries = await getFocusDeliveries(focusId);
|
|
526
526
|
const activeDeliveries = deliveries.filter(d => isStatusAgentActionable(d.executionStatus ?? ''));
|
|
527
527
|
for (const delivery of activeDeliveries) {
|
|
528
528
|
await reportGitState(delivery.id, 'worktree_active', worktree.branchName);
|
|
@@ -530,15 +530,15 @@ export async function reconcileGitState(_config) {
|
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
532
|
catch (err) {
|
|
533
|
-
console.warn(`[
|
|
533
|
+
console.warn(`[focus-lifecycle] Failed to reconcile git state for "${worktree.focusName}": ` +
|
|
534
534
|
`${err.message}`);
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
537
|
if (reconciled > 0) {
|
|
538
|
-
console.log(`[
|
|
538
|
+
console.log(`[focus-lifecycle] Reconciled git state for ${reconciled} delivery(ies)`);
|
|
539
539
|
}
|
|
540
540
|
else {
|
|
541
|
-
console.log('[
|
|
541
|
+
console.log('[focus-lifecycle] No deliveries needed git state reconciliation');
|
|
542
542
|
}
|
|
543
543
|
}
|
|
544
|
-
//# sourceMappingURL=
|
|
544
|
+
//# sourceMappingURL=focus-lifecycle.js.map
|