@telora/daemon 0.17.33 → 0.17.40
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/build-info.json +5 -3
- package/dist/assembly-engine.d.ts +6 -0
- package/dist/assembly-engine.d.ts.map +1 -1
- package/dist/assembly-engine.js +19 -0
- package/dist/assembly-engine.js.map +1 -1
- package/dist/assembly-resolvers.d.ts +17 -8
- package/dist/assembly-resolvers.d.ts.map +1 -1
- package/dist/assembly-resolvers.js +19 -8
- package/dist/assembly-resolvers.js.map +1 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +4 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/session-state.d.ts +10 -0
- package/dist/cli/session-state.d.ts.map +1 -1
- package/dist/cli/session-state.js +31 -0
- package/dist/cli/session-state.js.map +1 -1
- package/dist/completion/completion-decision.d.ts +83 -0
- package/dist/completion/completion-decision.d.ts.map +1 -0
- package/dist/completion/completion-decision.js +48 -0
- package/dist/completion/completion-decision.js.map +1 -0
- package/dist/completion/event-escalations.d.ts +97 -0
- package/dist/completion/event-escalations.d.ts.map +1 -0
- package/dist/completion/event-escalations.js +213 -0
- package/dist/completion/event-escalations.js.map +1 -0
- package/dist/completion/event.d.ts +1 -72
- package/dist/completion/event.d.ts.map +1 -1
- package/dist/completion/event.js +149 -329
- package/dist/completion/event.js.map +1 -1
- package/dist/completion/exit-classification.d.ts +82 -0
- package/dist/completion/exit-classification.d.ts.map +1 -0
- package/dist/completion/exit-classification.js +61 -0
- package/dist/completion/exit-classification.js.map +1 -0
- package/dist/completion/index.d.ts +14 -5
- package/dist/completion/index.d.ts.map +1 -1
- package/dist/completion/index.js +14 -5
- package/dist/completion/index.js.map +1 -1
- package/dist/completion/merge-phase.d.ts +114 -0
- package/dist/completion/merge-phase.d.ts.map +1 -0
- package/dist/completion/merge-phase.js +198 -0
- package/dist/completion/merge-phase.js.map +1 -0
- package/dist/completion/review-exit-phase.d.ts +82 -0
- package/dist/completion/review-exit-phase.d.ts.map +1 -0
- package/dist/completion/review-exit-phase.js +228 -0
- package/dist/completion/review-exit-phase.js.map +1 -0
- package/dist/completion/status-advance-phase.d.ts +61 -0
- package/dist/completion/status-advance-phase.d.ts.map +1 -0
- package/dist/completion/status-advance-phase.js +182 -0
- package/dist/completion/status-advance-phase.js.map +1 -0
- package/dist/completion/team-completion.d.ts +28 -269
- package/dist/completion/team-completion.d.ts.map +1 -1
- package/dist/completion/team-completion.js +145 -676
- package/dist/completion/team-completion.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +70 -41
- package/dist/config.js.map +1 -1
- package/dist/daemon-process.d.ts +18 -2
- package/dist/daemon-process.d.ts.map +1 -1
- package/dist/daemon-process.js +15 -2
- package/dist/daemon-process.js.map +1 -1
- package/dist/directive/close-loop-stage.d.ts +50 -0
- package/dist/directive/close-loop-stage.d.ts.map +1 -0
- package/dist/directive/close-loop-stage.js +196 -0
- package/dist/directive/close-loop-stage.js.map +1 -0
- package/dist/directive/directive-assembly.d.ts +33 -0
- package/dist/directive/directive-assembly.d.ts.map +1 -0
- package/dist/directive/directive-assembly.js +77 -0
- package/dist/directive/directive-assembly.js.map +1 -0
- package/dist/directive/directive-dispatch.d.ts +103 -0
- package/dist/directive/directive-dispatch.d.ts.map +1 -0
- package/dist/directive/directive-dispatch.js +279 -0
- package/dist/directive/directive-dispatch.js.map +1 -0
- package/dist/directive/phase-sync.d.ts +89 -0
- package/dist/directive/phase-sync.d.ts.map +1 -0
- package/dist/directive/phase-sync.js +173 -0
- package/dist/directive/phase-sync.js.map +1 -0
- package/dist/directive-executor.d.ts +21 -223
- package/dist/directive-executor.d.ts.map +1 -1
- package/dist/directive-executor.js +28 -687
- package/dist/directive-executor.js.map +1 -1
- package/dist/focus-engine.d.ts.map +1 -1
- package/dist/focus-engine.js +16 -10
- package/dist/focus-engine.js.map +1 -1
- package/dist/focus-executor.d.ts +29 -8
- package/dist/focus-executor.d.ts.map +1 -1
- package/dist/focus-executor.js +29 -10
- package/dist/focus-executor.js.map +1 -1
- package/dist/focus-stage-lifecycle.d.ts +7 -5
- package/dist/focus-stage-lifecycle.d.ts.map +1 -1
- package/dist/focus-stage-lifecycle.js +11 -6
- package/dist/focus-stage-lifecycle.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/merge-back-loop.d.ts.map +1 -1
- package/dist/merge-back-loop.js +4 -10
- package/dist/merge-back-loop.js.map +1 -1
- package/dist/pipeline-config.d.ts +13 -0
- package/dist/pipeline-config.d.ts.map +1 -1
- package/dist/pipeline-config.js +15 -0
- package/dist/pipeline-config.js.map +1 -1
- package/dist/resolvers/agent-escalations.d.ts +8 -1
- package/dist/resolvers/agent-escalations.d.ts.map +1 -1
- package/dist/resolvers/agent-escalations.js +25 -22
- package/dist/resolvers/agent-escalations.js.map +1 -1
- package/dist/resolvers/agent-session-summaries.d.ts +8 -1
- package/dist/resolvers/agent-session-summaries.d.ts.map +1 -1
- package/dist/resolvers/agent-session-summaries.js +21 -18
- package/dist/resolvers/agent-session-summaries.js.map +1 -1
- package/dist/resolvers/delivery-acceptance-criteria.d.ts +7 -1
- package/dist/resolvers/delivery-acceptance-criteria.d.ts.map +1 -1
- package/dist/resolvers/delivery-acceptance-criteria.js +14 -11
- package/dist/resolvers/delivery-acceptance-criteria.js.map +1 -1
- package/dist/resolvers/delivery-description.d.ts +7 -1
- package/dist/resolvers/delivery-description.d.ts.map +1 -1
- package/dist/resolvers/delivery-description.js +14 -11
- package/dist/resolvers/delivery-description.js.map +1 -1
- package/dist/resolvers/delivery-issues.d.ts +8 -1
- package/dist/resolvers/delivery-issues.d.ts.map +1 -1
- package/dist/resolvers/delivery-issues.js +51 -48
- package/dist/resolvers/delivery-issues.js.map +1 -1
- package/dist/resolvers/delivery-listing.d.ts +16 -1
- package/dist/resolvers/delivery-listing.d.ts.map +1 -1
- package/dist/resolvers/delivery-listing.js +36 -33
- package/dist/resolvers/delivery-listing.js.map +1 -1
- package/dist/resolvers/delivery-tech-context.d.ts +7 -1
- package/dist/resolvers/delivery-tech-context.d.ts.map +1 -1
- package/dist/resolvers/delivery-tech-context.js +14 -11
- package/dist/resolvers/delivery-tech-context.js.map +1 -1
- package/dist/resolvers/deployment-profile.d.ts +8 -1
- package/dist/resolvers/deployment-profile.d.ts.map +1 -1
- package/dist/resolvers/deployment-profile.js +20 -17
- package/dist/resolvers/deployment-profile.js.map +1 -1
- package/dist/resolvers/focus-anchoring-injections.d.ts +26 -1
- package/dist/resolvers/focus-anchoring-injections.d.ts.map +1 -1
- package/dist/resolvers/focus-anchoring-injections.js +91 -88
- package/dist/resolvers/focus-anchoring-injections.js.map +1 -1
- package/dist/resolvers/focus-context.d.ts +9 -1
- package/dist/resolvers/focus-context.d.ts.map +1 -1
- package/dist/resolvers/focus-context.js +38 -35
- package/dist/resolvers/focus-context.js.map +1 -1
- package/dist/resolvers/focus-injections.d.ts +13 -1
- package/dist/resolvers/focus-injections.d.ts.map +1 -1
- package/dist/resolvers/focus-injections.js +60 -57
- package/dist/resolvers/focus-injections.js.map +1 -1
- package/dist/resolvers/focus-last-review-report.d.ts +2 -1
- package/dist/resolvers/focus-last-review-report.d.ts.map +1 -1
- package/dist/resolvers/focus-last-review-report.js +33 -30
- package/dist/resolvers/focus-last-review-report.js.map +1 -1
- package/dist/resolvers/focus-reality-tree.d.ts +15 -1
- package/dist/resolvers/focus-reality-tree.d.ts.map +1 -1
- package/dist/resolvers/focus-reality-tree.js +35 -32
- package/dist/resolvers/focus-reality-tree.js.map +1 -1
- package/dist/resolvers/git-diff-against-base.d.ts +8 -1
- package/dist/resolvers/git-diff-against-base.d.ts.map +1 -1
- package/dist/resolvers/git-diff-against-base.js +33 -30
- package/dist/resolvers/git-diff-against-base.js.map +1 -1
- package/dist/resolvers/guards-evaluation-results.d.ts +7 -1
- package/dist/resolvers/guards-evaluation-results.d.ts.map +1 -1
- package/dist/resolvers/guards-evaluation-results.js +25 -22
- package/dist/resolvers/guards-evaluation-results.js.map +1 -1
- package/dist/resolvers/index.d.ts +22 -40
- package/dist/resolvers/index.d.ts.map +1 -1
- package/dist/resolvers/index.js +112 -41
- package/dist/resolvers/index.js.map +1 -1
- package/dist/resolvers/loop-context.d.ts +18 -1
- package/dist/resolvers/loop-context.d.ts.map +1 -1
- package/dist/resolvers/loop-context.js +21 -18
- package/dist/resolvers/loop-context.js.map +1 -1
- package/dist/resolvers/loop-delivery-index.d.ts +17 -1
- package/dist/resolvers/loop-delivery-index.d.ts.map +1 -1
- package/dist/resolvers/loop-delivery-index.js +51 -48
- package/dist/resolvers/loop-delivery-index.js.map +1 -1
- package/dist/resolvers/loop-documents.d.ts +9 -1
- package/dist/resolvers/loop-documents.d.ts.map +1 -1
- package/dist/resolvers/loop-documents.js +22 -19
- package/dist/resolvers/loop-documents.js.map +1 -1
- package/dist/resolvers/loop-expected-effects.d.ts +11 -1
- package/dist/resolvers/loop-expected-effects.d.ts.map +1 -1
- package/dist/resolvers/loop-expected-effects.js +53 -50
- package/dist/resolvers/loop-expected-effects.js.map +1 -1
- package/dist/resolvers/loop-frt-statement.d.ts +11 -1
- package/dist/resolvers/loop-frt-statement.d.ts.map +1 -1
- package/dist/resolvers/loop-frt-statement.js +28 -25
- package/dist/resolvers/loop-frt-statement.js.map +1 -1
- package/dist/resolvers/loop-injection.d.ts +10 -1
- package/dist/resolvers/loop-injection.d.ts.map +1 -1
- package/dist/resolvers/loop-injection.js +38 -35
- package/dist/resolvers/loop-injection.js.map +1 -1
- package/dist/resolvers/loop-persona.d.ts +20 -1
- package/dist/resolvers/loop-persona.d.ts.map +1 -1
- package/dist/resolvers/loop-persona.js +20 -17
- package/dist/resolvers/loop-persona.js.map +1 -1
- package/dist/resolvers/loop-questions.d.ts +8 -1
- package/dist/resolvers/loop-questions.d.ts.map +1 -1
- package/dist/resolvers/loop-questions.js +39 -36
- package/dist/resolvers/loop-questions.js.map +1 -1
- package/dist/resolvers/loop-upstream-udes.d.ts +11 -1
- package/dist/resolvers/loop-upstream-udes.d.ts.map +1 -1
- package/dist/resolvers/loop-upstream-udes.js +74 -71
- package/dist/resolvers/loop-upstream-udes.js.map +1 -1
- package/dist/resolvers/prd-context.d.ts +2 -0
- package/dist/resolvers/prd-context.d.ts.map +1 -1
- package/dist/resolvers/prd-context.js +16 -13
- package/dist/resolvers/prd-context.js.map +1 -1
- package/dist/resolvers/prd-persona.d.ts +2 -0
- package/dist/resolvers/prd-persona.d.ts.map +1 -1
- package/dist/resolvers/prd-persona.js +6 -3
- package/dist/resolvers/prd-persona.js.map +1 -1
- package/dist/resolvers/reality-metrics.d.ts +8 -1
- package/dist/resolvers/reality-metrics.d.ts.map +1 -1
- package/dist/resolvers/reality-metrics.js +73 -70
- package/dist/resolvers/reality-metrics.js.map +1 -1
- package/dist/resolvers/reality-projections.d.ts +7 -1
- package/dist/resolvers/reality-projections.d.ts.map +1 -1
- package/dist/resolvers/reality-projections.js +35 -32
- package/dist/resolvers/reality-projections.js.map +1 -1
- package/dist/resolvers/reality-tree-snapshot.d.ts +10 -1
- package/dist/resolvers/reality-tree-snapshot.d.ts.map +1 -1
- package/dist/resolvers/reality-tree-snapshot.js +34 -31
- package/dist/resolvers/reality-tree-snapshot.js.map +1 -1
- package/dist/resolvers/retired-injections.d.ts +10 -1
- package/dist/resolvers/retired-injections.d.ts.map +1 -1
- package/dist/resolvers/retired-injections.js +68 -65
- package/dist/resolvers/retired-injections.js.map +1 -1
- package/dist/resolvers/review-outcomes.d.ts +11 -1
- package/dist/resolvers/review-outcomes.d.ts.map +1 -1
- package/dist/resolvers/review-outcomes.js +27 -24
- package/dist/resolvers/review-outcomes.js.map +1 -1
- package/dist/resolvers/security-advisory.d.ts +2 -1
- package/dist/resolvers/security-advisory.d.ts.map +1 -1
- package/dist/resolvers/security-advisory.js +47 -44
- package/dist/resolvers/security-advisory.js.map +1 -1
- package/dist/resolvers/wiki-search.d.ts +8 -1
- package/dist/resolvers/wiki-search.d.ts.map +1 -1
- package/dist/resolvers/wiki-search.js +17 -14
- package/dist/resolvers/wiki-search.js.map +1 -1
- package/dist/resolvers/wiki-topic.d.ts +9 -1
- package/dist/resolvers/wiki-topic.d.ts.map +1 -1
- package/dist/resolvers/wiki-topic.js +21 -18
- package/dist/resolvers/wiki-topic.js.map +1 -1
- package/dist/resolvers/workflow-stages.d.ts +7 -1
- package/dist/resolvers/workflow-stages.d.ts.map +1 -1
- package/dist/resolvers/workflow-stages.js +20 -17
- package/dist/resolvers/workflow-stages.js.map +1 -1
- package/dist/review-defect-detector.d.ts +4 -1
- package/dist/review-defect-detector.d.ts.map +1 -1
- package/dist/review-defect-detector.js +6 -8
- package/dist/review-defect-detector.js.map +1 -1
- package/dist/self-update.d.ts +6 -0
- package/dist/self-update.d.ts.map +1 -1
- package/dist/self-update.js +6 -1
- package/dist/self-update.js.map +1 -1
- package/dist/spawner/index.d.ts +2 -1
- package/dist/spawner/index.d.ts.map +1 -1
- package/dist/spawner/index.js +2 -1
- package/dist/spawner/index.js.map +1 -1
- package/dist/spawner/spawn-team.d.ts +14 -52
- package/dist/spawner/spawn-team.d.ts.map +1 -1
- package/dist/spawner/spawn-team.js +14 -469
- package/dist/spawner/spawn-team.js.map +1 -1
- package/dist/staleness.d.ts +124 -0
- package/dist/staleness.d.ts.map +1 -0
- package/dist/staleness.js +215 -0
- package/dist/staleness.js.map +1 -0
- package/dist/state-cascade.d.ts +3 -2
- package/dist/state-cascade.d.ts.map +1 -1
- package/dist/state-cascade.js +7 -3
- package/dist/state-cascade.js.map +1 -1
- package/dist/types/focus.d.ts +5 -4
- package/dist/types/focus.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/session.d.ts +2 -3
- package/dist/types/session.d.ts.map +1 -1
- package/dist/unified-engine-lifecycle.d.ts.map +1 -1
- package/dist/unified-engine-lifecycle.js +2 -23
- package/dist/unified-engine-lifecycle.js.map +1 -1
- package/dist/unified-shell-config.d.ts +2 -7
- package/dist/unified-shell-config.d.ts.map +1 -1
- package/dist/unified-shell-config.js +2 -23
- package/dist/unified-shell-config.js.map +1 -1
- package/dist/unified-shell-status.d.ts.map +1 -1
- package/dist/unified-shell-status.js +2 -4
- package/dist/unified-shell-status.js.map +1 -1
- package/dist/unified-shell.d.ts.map +1 -1
- package/dist/unified-shell.js +21 -24
- package/dist/unified-shell.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +6 -4
- package/dist/version-check.js.map +1 -1
- package/package.json +3 -3
- package/dist/pm/adaptive-poller.d.ts +0 -26
- package/dist/pm/adaptive-poller.d.ts.map +0 -1
- package/dist/pm/adaptive-poller.js +0 -53
- package/dist/pm/adaptive-poller.js.map +0 -1
- package/dist/pm/cascade-evaluator.d.ts +0 -29
- package/dist/pm/cascade-evaluator.d.ts.map +0 -1
- package/dist/pm/cascade-evaluator.js +0 -135
- package/dist/pm/cascade-evaluator.js.map +0 -1
- package/dist/pm/channel-email.d.ts +0 -42
- package/dist/pm/channel-email.d.ts.map +0 -1
- package/dist/pm/channel-email.js +0 -47
- package/dist/pm/channel-email.js.map +0 -1
- package/dist/pm/channel-slack.d.ts +0 -40
- package/dist/pm/channel-slack.d.ts.map +0 -1
- package/dist/pm/channel-slack.js +0 -46
- package/dist/pm/channel-slack.js.map +0 -1
- package/dist/pm/communication-drafter.d.ts +0 -44
- package/dist/pm/communication-drafter.d.ts.map +0 -1
- package/dist/pm/communication-drafter.js +0 -84
- package/dist/pm/communication-drafter.js.map +0 -1
- package/dist/pm/dialog-handler.d.ts +0 -32
- package/dist/pm/dialog-handler.d.ts.map +0 -1
- package/dist/pm/dialog-handler.js +0 -107
- package/dist/pm/dialog-handler.js.map +0 -1
- package/dist/pm/mitigation-correlator.d.ts +0 -29
- package/dist/pm/mitigation-correlator.d.ts.map +0 -1
- package/dist/pm/mitigation-correlator.js +0 -147
- package/dist/pm/mitigation-correlator.js.map +0 -1
- package/dist/pm/risk-scanner.d.ts +0 -28
- package/dist/pm/risk-scanner.d.ts.map +0 -1
- package/dist/pm/risk-scanner.js +0 -112
- package/dist/pm/risk-scanner.js.map +0 -1
- package/dist/pm-engine.d.ts +0 -96
- package/dist/pm-engine.d.ts.map +0 -1
- package/dist/pm-engine.js +0 -540
- package/dist/pm-engine.js.map +0 -1
- package/dist/pm-process.d.ts +0 -45
- package/dist/pm-process.d.ts.map +0 -1
- package/dist/pm-process.js +0 -195
- package/dist/pm-process.js.map +0 -1
- package/dist/pm-prompt-builder.d.ts +0 -23
- package/dist/pm-prompt-builder.d.ts.map +0 -1
- package/dist/pm-prompt-builder.js +0 -64
- package/dist/pm-prompt-builder.js.map +0 -1
- package/dist/session-lifecycle.d.ts +0 -78
- package/dist/session-lifecycle.d.ts.map +0 -1
- package/dist/session-lifecycle.js +0 -382
- package/dist/session-lifecycle.js.map +0 -1
- package/dist/types/pm.d.ts +0 -53
- package/dist/types/pm.d.ts.map +0 -1
- package/dist/types/pm.js +0 -10
- package/dist/types/pm.js.map +0 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directive dispatch: execute a stage directive against a focus.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the directive's execution mode, then dispatches: inject mode
|
|
5
|
+
* writes /compact + assembled content to the live team's stdin; spawn mode
|
|
6
|
+
* terminates the current team and stashes a PendingSpawnDirective for the
|
|
7
|
+
* next spawn cycle. Also holds the bounded-retry runner + the escalation
|
|
8
|
+
* path for exhausted retries. Extracted from directive-executor.ts; the
|
|
9
|
+
* executor re-exports the public surface so existing importers are
|
|
10
|
+
* unaffected.
|
|
11
|
+
*/
|
|
12
|
+
import { getActiveTeams } from '../focus-team-state.js';
|
|
13
|
+
import { resolveLineageSpec } from '../session-lineage.js';
|
|
14
|
+
import { sendMessage, ESCALATION_REASONS } from '@telora/daemon-core';
|
|
15
|
+
import { terminateTeam, waitForTeamExit } from '../completion/index.js';
|
|
16
|
+
import { createEscalation } from '../queries/issues.js';
|
|
17
|
+
import { pendingSpawnDirectives, computeDirectiveHash, SPAWN_GUARD_WINDOW_MS, shouldSkipSpawnDirective, } from './directive-queue.js';
|
|
18
|
+
import { assembleDirectiveContent } from './directive-assembly.js';
|
|
19
|
+
// ── Core execution ───────────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the execution mode a directive should dispatch under.
|
|
22
|
+
*
|
|
23
|
+
* Pure + exported for testing. The rule:
|
|
24
|
+
* - A valid mode ('inject' | 'spawn') passes through unchanged.
|
|
25
|
+
* - A review-lineage directive whose execution is missing/invalid defaults
|
|
26
|
+
* to 'spawn'. Review always spawns a fresh team, so 'spawn' is the only
|
|
27
|
+
* sensible mode for that lineage; defaulting here self-heals malformed or
|
|
28
|
+
* seed-drifted review directives (e.g. the focus 'review' stage whose
|
|
29
|
+
* agent_directive lost its execution field -- see the backfill migration)
|
|
30
|
+
* so the review-spawn path can no longer be silently stranded.
|
|
31
|
+
* - Anything else is UNRESOLVABLE (returns null). The caller treats null as
|
|
32
|
+
* a fail-loud condition (throw -> escalate + loop trigger) rather than a
|
|
33
|
+
* silent no-op.
|
|
34
|
+
*
|
|
35
|
+
* Note: a review-lineage directive defaults to spawn even for a non-empty but
|
|
36
|
+
* unrecognized execution value -- review is always spawnable, so we self-heal
|
|
37
|
+
* rather than fail. Non-review lineages have no safe default and fail loud.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveDirectiveExecutionMode(execution, lineage) {
|
|
40
|
+
if (execution === 'inject' || execution === 'spawn')
|
|
41
|
+
return execution;
|
|
42
|
+
if (lineage === 'review')
|
|
43
|
+
return 'spawn';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Execute a stage directive for a focus.
|
|
48
|
+
*
|
|
49
|
+
* inject mode: sends /compact + assembled context + prompt to the active team's stdin.
|
|
50
|
+
* spawn mode: terminates the current team, assembles directive content, stores for respawn.
|
|
51
|
+
*
|
|
52
|
+
* When the directive's execution mode cannot be resolved to a valid mode
|
|
53
|
+
* (see resolveDirectiveExecutionMode), this THROWS instead of silently
|
|
54
|
+
* no-oping. The throw propagates through runDirectiveWithRetry in
|
|
55
|
+
* checkDirectives, which escalates and emits a loop trigger -- so a malformed
|
|
56
|
+
* directive surfaces a signal instead of stranding the focus.
|
|
57
|
+
*
|
|
58
|
+
* Exported for testing.
|
|
59
|
+
*/
|
|
60
|
+
export async function executeDirective(config, focusId, directive, focusWorkflowStageId, stageName) {
|
|
61
|
+
// Resolve the session lineage + continuity policy DECLARED by the directive
|
|
62
|
+
// (INJ-B), falling back to the stage-name-derived default when the directive
|
|
63
|
+
// omits them ('reviewing' -> review/fresh; otherwise coding/resume). The
|
|
64
|
+
// review-exit gate (focus-completion) still keys off sessionType, which we
|
|
65
|
+
// derive from the resolved lineage: lineage 'review' tags a review session,
|
|
66
|
+
// every other lineage tags 'coding'. This drops the old hardcoded
|
|
67
|
+
// stageName==='reviewing' special-case while preserving the gate contract.
|
|
68
|
+
const spec = resolveLineageSpec({ declared: directive, stageName });
|
|
69
|
+
const sessionType = spec.lineage === 'review' ? 'review' : 'coding';
|
|
70
|
+
const mode = resolveDirectiveExecutionMode(directive.execution, spec.lineage);
|
|
71
|
+
console.log(`[directive-executor] Executing ${mode ?? `unresolved(${directive.execution})`} directive ` +
|
|
72
|
+
`for focus ${focusId.slice(0, 8)} (stage ${focusWorkflowStageId.slice(0, 8)})`);
|
|
73
|
+
if (mode === 'inject') {
|
|
74
|
+
await executeInjectDirective(config, focusId, directive, sessionType, spec);
|
|
75
|
+
}
|
|
76
|
+
else if (mode === 'spawn') {
|
|
77
|
+
await executeSpawnDirective(config, focusId, directive, sessionType, spec);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Fail loud: an unknown/undefined execution mode on a non-review lineage
|
|
81
|
+
// cannot be defaulted safely. Throwing routes through runDirectiveWithRetry
|
|
82
|
+
// -> escalateDirectiveFailure + emitLoopTrigger (checkDirectives) so the
|
|
83
|
+
// broken directive escalates instead of silently marking the stage
|
|
84
|
+
// processed.
|
|
85
|
+
throw new Error(`Unresolvable directive execution mode "${directive.execution}" ` +
|
|
86
|
+
`(lineage "${spec.lineage}", stage "${stageName ?? 'unknown'}") for focus ${focusId.slice(0, 8)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Inject mode: send /compact + assembled context + prompt to the active team's stdin.
|
|
91
|
+
*/
|
|
92
|
+
export async function executeInjectDirective(config, focusId, directive, sessionType = 'coding', spec = resolveLineageSpec({ declared: directive, stageName: undefined })) {
|
|
93
|
+
const activeTeams = getActiveTeams();
|
|
94
|
+
const team = activeTeams.get(focusId);
|
|
95
|
+
if (!team || !team.leadStdin) {
|
|
96
|
+
// No active team -- fall back to spawn mode so the directive is
|
|
97
|
+
// delivered when the next team spawns (e.g., review triggered while
|
|
98
|
+
// pipeline was stopped; the pipeline gets activated but the team
|
|
99
|
+
// hasn't spawned yet by the time the directive executor runs).
|
|
100
|
+
console.log(`[directive-executor] No active team for focus ${focusId.slice(0, 8)} -- ` +
|
|
101
|
+
`falling back to spawn directive for delivery on next spawn`);
|
|
102
|
+
const message = await assembleDirectiveContent(config, focusId, directive, null);
|
|
103
|
+
pendingSpawnDirectives.set(focusId, {
|
|
104
|
+
message,
|
|
105
|
+
model: directive.model ?? null,
|
|
106
|
+
sessionType,
|
|
107
|
+
lineage: spec.lineage,
|
|
108
|
+
continuity: spec.continuity,
|
|
109
|
+
contentHash: computeDirectiveHash(directive),
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Don't retask a live review team. Phase changes can fire while a review
|
|
114
|
+
// team is still working (e.g., the team mutates delivery state and the
|
|
115
|
+
// aggregate flips the focus's derived phase). Without this guard, the
|
|
116
|
+
// resulting inject sends /compact + a coding-stage prompt to the review
|
|
117
|
+
// team's stdin, which silently converts a reviewer into a developer
|
|
118
|
+
// mid-flight -- exactly what the sessionType separation is meant to
|
|
119
|
+
// prevent. Skip the inject and let the review team finish its directive
|
|
120
|
+
// on its own terms; if a coding-phase directive really is needed, the
|
|
121
|
+
// listener will spawn a fresh team after the review team exits.
|
|
122
|
+
if (team.sessionType === 'review') {
|
|
123
|
+
console.warn(`[directive-executor] Skipping inject for focus ${focusId.slice(0, 8)} -- ` +
|
|
124
|
+
`active team is a review session; phase-driven retasking would convert it ` +
|
|
125
|
+
`into a coder. Directive not delivered.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Tag the live team with the injected directive's sessionType. Without this,
|
|
129
|
+
// a keep-alive coding team that receives the reviewing-stage directive does
|
|
130
|
+
// the review work but stays labelled 'coding'. On a clean review exit (no
|
|
131
|
+
// gaps filed, no focus_reviews report) handleReviewExit's auto-approve gate
|
|
132
|
+
// requires sessionType === 'review' (focus-completion.ts), so it never fires:
|
|
133
|
+
// the verify delivery is left in verify and the next poll re-triggers
|
|
134
|
+
// auto-review -- an infinite review/respawn loop. Flipping the label here
|
|
135
|
+
// also arms the `team.sessionType === 'review'` retask guard above for any
|
|
136
|
+
// subsequent inject. (Guaranteed `!== 'review'` at this point by the guard.)
|
|
137
|
+
team.sessionType = sessionType;
|
|
138
|
+
// Keep the team's session-id map slot in step with the injected directive's
|
|
139
|
+
// lineage so a subsequent resume reads/writes the right slot (INJ-B).
|
|
140
|
+
team.lineage = spec.lineage;
|
|
141
|
+
// Step 1: Send /compact command if recipe is specified
|
|
142
|
+
if (directive.compact) {
|
|
143
|
+
const compactMessage = `/compact Preserve: ${directive.compact.preserve}. Shed: ${directive.compact.shed}.`;
|
|
144
|
+
console.log(`[directive-executor] Sending /compact for focus ${focusId.slice(0, 8)}`);
|
|
145
|
+
sendMessage(team.leadStdin, compactMessage);
|
|
146
|
+
}
|
|
147
|
+
// Step 2: Assemble and send context + prompt
|
|
148
|
+
const fullMessage = await assembleDirectiveContent(config, focusId, directive, team.worktreePath);
|
|
149
|
+
if (fullMessage.trim()) {
|
|
150
|
+
console.log(`[directive-executor] Sending directive message to focus ${focusId.slice(0, 8)} ` +
|
|
151
|
+
`(${fullMessage.length} chars)`);
|
|
152
|
+
sendMessage(team.leadStdin, fullMessage);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Spawn mode: terminate the current team, assemble directive content,
|
|
157
|
+
* and store it for the next spawn cycle to deliver.
|
|
158
|
+
*/
|
|
159
|
+
async function executeSpawnDirective(config, focusId, directive, sessionType = 'coding', spec = resolveLineageSpec({ declared: directive, stageName: undefined })) {
|
|
160
|
+
const activeTeams = getActiveTeams();
|
|
161
|
+
const team = activeTeams.get(focusId);
|
|
162
|
+
const worktreePath = team?.worktreePath ?? null;
|
|
163
|
+
// Assemble the directive content before terminating the team
|
|
164
|
+
// (the team's worktree path is needed for git diff resolution)
|
|
165
|
+
const message = await assembleDirectiveContent(config, focusId, directive, worktreePath);
|
|
166
|
+
const contentHash = computeDirectiveHash(directive);
|
|
167
|
+
// Store the assembled content for the next spawn to pick up
|
|
168
|
+
pendingSpawnDirectives.set(focusId, {
|
|
169
|
+
message,
|
|
170
|
+
model: directive.model ?? null,
|
|
171
|
+
sessionType,
|
|
172
|
+
lineage: spec.lineage,
|
|
173
|
+
continuity: spec.continuity,
|
|
174
|
+
contentHash,
|
|
175
|
+
});
|
|
176
|
+
// If a team was recently spawned, it may have already consumed this exact
|
|
177
|
+
// directive via the pending spawn directive mechanism. Skip the kill/respawn
|
|
178
|
+
// churn only when the directive content hash matches what the team last
|
|
179
|
+
// consumed -- divergent content (e.g. an updated stage directive) must
|
|
180
|
+
// trigger a respawn so the team operates under fresh intent instead of
|
|
181
|
+
// waiting for a natural exit.
|
|
182
|
+
if (team?.startedAt) {
|
|
183
|
+
const decision = shouldSkipSpawnDirective({
|
|
184
|
+
team: { startedAt: team.startedAt, lastConsumedDirectiveHash: team.lastConsumedDirectiveHash },
|
|
185
|
+
contentHash,
|
|
186
|
+
now: Date.now(),
|
|
187
|
+
});
|
|
188
|
+
const ageSec = Math.round((Date.now() - team.startedAt.getTime()) / 1000);
|
|
189
|
+
if (decision === 'skip') {
|
|
190
|
+
console.log(`[directive-executor] Team for focus ${focusId.slice(0, 8)} was recently spawned ` +
|
|
191
|
+
`(${ageSec}s ago) with identical directive content -- skipping respawn`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (ageSec < SPAWN_GUARD_WINDOW_MS / 1000) {
|
|
195
|
+
console.log(`[directive-executor] Team for focus ${focusId.slice(0, 8)} was recently spawned ` +
|
|
196
|
+
`(${ageSec}s ago) but directive content diverged -- proceeding with respawn`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Terminate existing team if one exists
|
|
200
|
+
if (team) {
|
|
201
|
+
console.log(`[directive-executor] Terminating existing team for focus ${focusId.slice(0, 8)} before spawn`);
|
|
202
|
+
terminateTeam(focusId, 'handoff');
|
|
203
|
+
const exited = await waitForTeamExit(focusId, 30000);
|
|
204
|
+
if (!exited) {
|
|
205
|
+
console.warn(`[directive-executor] Team for focus ${focusId.slice(0, 8)} did not exit cleanly`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
console.log(`[directive-executor] Spawn directive stored for focus ${focusId.slice(0, 8)} ` +
|
|
209
|
+
`(${message.length} chars, model: ${directive.model ?? 'default'}) -- team will respawn on next poll cycle`);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Run a directive function with bounded retry and exponential backoff.
|
|
213
|
+
* Returns a result tuple instead of throwing so the caller can decide
|
|
214
|
+
* whether to escalate or continue.
|
|
215
|
+
*/
|
|
216
|
+
export async function runDirectiveWithRetry(fn, options) {
|
|
217
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
218
|
+
const baseDelayMs = options.baseDelayMs ?? 1000;
|
|
219
|
+
const sleep = options.sleep ?? ((ms) => new Promise(r => setTimeout(r, ms)));
|
|
220
|
+
let lastError = null;
|
|
221
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
222
|
+
try {
|
|
223
|
+
await fn();
|
|
224
|
+
return { ok: true, attempts: attempt, error: null };
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
lastError = err;
|
|
228
|
+
if (attempt < maxAttempts) {
|
|
229
|
+
options.onAttemptFailed?.(attempt, lastError);
|
|
230
|
+
const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
|
|
231
|
+
console.warn(`[directive-executor] ${options.label} failed (attempt ${attempt}/${maxAttempts}): ` +
|
|
232
|
+
`${lastError.message}. Retrying in ${delayMs}ms...`);
|
|
233
|
+
await sleep(delayMs);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { ok: false, attempts: maxAttempts, error: lastError };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Escalate when bounded retry of a stage directive is exhausted.
|
|
241
|
+
*
|
|
242
|
+
* Visible signal so operators can find which focus + stage failed and
|
|
243
|
+
* why, instead of a buried console.warn. Mirrors escalatePlanningFailure
|
|
244
|
+
* from focus-completion-event.ts.
|
|
245
|
+
*/
|
|
246
|
+
export async function escalateDirectiveFailure(params, deps = { createEscalation }) {
|
|
247
|
+
const stageLabel = params.stageName ?? 'unknown';
|
|
248
|
+
try {
|
|
249
|
+
await deps.createEscalation({
|
|
250
|
+
organizationId: params.organizationId,
|
|
251
|
+
sessionId: params.sessionId,
|
|
252
|
+
issueId: null,
|
|
253
|
+
reasonType: ESCALATION_REASONS.ERROR_ENCOUNTERED,
|
|
254
|
+
description: `Stage directive execution for focus "${params.focusName}" failed after ` +
|
|
255
|
+
`${params.attempts} attempts on stage "${stageLabel}".\n\n` +
|
|
256
|
+
`**Focus:** ${params.focusName}\n` +
|
|
257
|
+
`**Focus ID:** ${params.focusId}\n` +
|
|
258
|
+
`**Stage:** ${stageLabel}\n` +
|
|
259
|
+
`**Last error:** ${params.error.message}\n\n` +
|
|
260
|
+
`The focus has been marked as processed for this stage to prevent a ` +
|
|
261
|
+
`poll-cycle retry loop. A human must investigate why the directive ` +
|
|
262
|
+
`cannot run (assembly resolver failure, transient network outage, etc.) ` +
|
|
263
|
+
`and re-trigger the stage if appropriate.`,
|
|
264
|
+
whatWasTried: `Bounded retry with exponential backoff (${params.attempts} attempts) on ` +
|
|
265
|
+
`executeDirective for focus "${params.focusName}" stage "${stageLabel}".`,
|
|
266
|
+
helpNeeded: `Inspect the daemon log for the failing assembly recipe or directive prompt. ` +
|
|
267
|
+
`If the error is transient, the focus will recover on the next legitimate ` +
|
|
268
|
+
`phase change. If it is structural (broken resolver, malformed directive), ` +
|
|
269
|
+
`fix the directive or assembly recipe.`,
|
|
270
|
+
});
|
|
271
|
+
console.log(`[directive-executor] Created directive-failure escalation for "${params.focusName}" ` +
|
|
272
|
+
`(stage "${stageLabel}", ${params.attempts} attempts)`);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
console.error(`[directive-executor] Failed to create directive-failure escalation for ` +
|
|
276
|
+
`"${params.focusName}": ${err.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=directive-dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directive-dispatch.js","sourceRoot":"","sources":["../../src/directive/directive-dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase sync: stage detection for the directive edge-trigger.
|
|
3
|
+
*
|
|
4
|
+
* Decides which workflow stage a focus should be in (new lifecycle stage
|
|
5
|
+
* names take priority over the legacy delivery-aggregate phase derivation),
|
|
6
|
+
* synchronizes `product_focuses.current_workflow_stage_id` with that decision,
|
|
7
|
+
* detects stranded reviews that need a directive re-fire, and fires on_enter
|
|
8
|
+
* transition triggers. Extracted from directive-executor.ts; the executor
|
|
9
|
+
* re-exports the public surface so existing importers are unaffected.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Fire on_enter triggers for a focus stage transition.
|
|
13
|
+
*
|
|
14
|
+
* Looks up the workflow transition from lastStageId to currentStageId,
|
|
15
|
+
* fetches triggers on that transition, and executes on_enter triggers.
|
|
16
|
+
* Currently supports advance_linked_injections; other action types
|
|
17
|
+
* are logged and skipped.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fireFocusTransitionTriggers(focusId: string, lastStageId: string | undefined, currentStageId: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Decide which workflow stage name a focus should be in.
|
|
22
|
+
*
|
|
23
|
+
* Pure helper, exported for testing. The selection rule is:
|
|
24
|
+
* 1. If `focus.stage` is one of the new lifecycle stage names
|
|
25
|
+
* (`queued`/`verify`/`review`/`close_loop`/`done`), use it directly.
|
|
26
|
+
* The denormalized stage is the authoritative signal under the new
|
|
27
|
+
* workflow.
|
|
28
|
+
* 2. Otherwise, fall back to the legacy phase derived from the delivery
|
|
29
|
+
* aggregate (kickoff/coding/verifying/reviewing/winding_down/blocked).
|
|
30
|
+
*
|
|
31
|
+
* Note: `review_requested_at` is still read by the legacy derivation for
|
|
32
|
+
* back-compat -- but on the new lifecycle path the `review` stage transition
|
|
33
|
+
* is driven by writes to `product_focuses.stage`, not by writes to
|
|
34
|
+
* `review_requested_at`. This is the Issue 1 contract.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveFocusStageName(input: {
|
|
37
|
+
focusStage: string | null | undefined;
|
|
38
|
+
deliveries: Array<{
|
|
39
|
+
executionStatus: string | null;
|
|
40
|
+
}>;
|
|
41
|
+
reviewRequestedAt: string | null;
|
|
42
|
+
}): string;
|
|
43
|
+
/**
|
|
44
|
+
* Decide whether to re-fire a focus's review-stage directive even though the
|
|
45
|
+
* edge-trigger (lastProcessedStages) already marked the stage processed.
|
|
46
|
+
*
|
|
47
|
+
* The edge-trigger fires a stage directive exactly once per stage entry. For
|
|
48
|
+
* the review stage that is normally correct -- a clean review completes, clears
|
|
49
|
+
* review_requested_at, and the focus leaves the review phase. But if the review
|
|
50
|
+
* team never spawned or was killed (e.g. a lifecycle SIGTERM) before routing,
|
|
51
|
+
* the focus stays in the review stage with review_requested_at still set and the
|
|
52
|
+
* directive never re-fires: a silent stuck focus (the founding bug of this focus).
|
|
53
|
+
*
|
|
54
|
+
* A focus STILL in the review stage with review_requested_at set is provably
|
|
55
|
+
* stranded -- any completed review clears review_requested_at and moves the
|
|
56
|
+
* phase. So when no review team is running, re-firing the directive is the
|
|
57
|
+
* self-healing recovery: it respawns the review team. Once a team is running the
|
|
58
|
+
* active-team guard suppresses the re-fire, and a successful review auto-approves
|
|
59
|
+
* and leaves the stage -- so the re-fire is bounded, not a per-poll spin.
|
|
60
|
+
*
|
|
61
|
+
* Pure and exported for unit testing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function shouldRefireStrandedReview(input: {
|
|
64
|
+
stageName: string | undefined;
|
|
65
|
+
reviewRequestedAt: string | null;
|
|
66
|
+
hasActiveTeam: boolean;
|
|
67
|
+
}): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Synchronize `product_focuses.current_workflow_stage_id` with the focus's
|
|
70
|
+
* authoritative stage. The authoritative stage is taken from `focus.stage`
|
|
71
|
+
* when it names one of the new lifecycle stages (Issue 1); otherwise the
|
|
72
|
+
* legacy delivery-aggregate phase derivation applies.
|
|
73
|
+
*
|
|
74
|
+
* Returns the stage ID the focus should be in (post-sync). Falls back to
|
|
75
|
+
* the recorded stage id on error so callers don't have to handle a third
|
|
76
|
+
* "unknown" state.
|
|
77
|
+
*/
|
|
78
|
+
export declare function syncFocusPhase(focus: {
|
|
79
|
+
focus_id: string;
|
|
80
|
+
current_workflow_stage_id: string | null;
|
|
81
|
+
review_requested_at: string | null;
|
|
82
|
+
/**
|
|
83
|
+
* Denormalized lifecycle stage from product_focuses.stage. Optional for
|
|
84
|
+
* back-compat with old API responses; absence is treated as "fall back to
|
|
85
|
+
* the legacy phase derivation".
|
|
86
|
+
*/
|
|
87
|
+
stage?: string | null;
|
|
88
|
+
}): Promise<string | null>;
|
|
89
|
+
//# sourceMappingURL=phase-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-sync.d.ts","sourceRoot":"","sources":["../../src/directive/phase-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAeH;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAyCf;AAwBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,UAAU,EAAE,KAAK,CAAC;QAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACtD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC,GAAG,MAAM,CAQT;AASD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAKV;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmCzB"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase sync: stage detection for the directive edge-trigger.
|
|
3
|
+
*
|
|
4
|
+
* Decides which workflow stage a focus should be in (new lifecycle stage
|
|
5
|
+
* names take priority over the legacy delivery-aggregate phase derivation),
|
|
6
|
+
* synchronizes `product_focuses.current_workflow_stage_id` with that decision,
|
|
7
|
+
* detects stranded reviews that need a directive re-fire, and fires on_enter
|
|
8
|
+
* transition triggers. Extracted from directive-executor.ts; the executor
|
|
9
|
+
* re-exports the public surface so existing importers are unaffected.
|
|
10
|
+
*/
|
|
11
|
+
import { fetchFocusWorkflow, fetchFocusWorkflowWithTransitions, getFocusDeliveries, updateFocusWorkflowStage, } from '../queries/focuses.js';
|
|
12
|
+
import { fetchTransitionGuards } from '../queries/guards.js';
|
|
13
|
+
import { executeAdvanceLinkedInjections } from '../trigger-executor.js';
|
|
14
|
+
import { deriveFocusPhase } from '../focus-phase.js';
|
|
15
|
+
// ── Focus transition trigger execution ────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Fire on_enter triggers for a focus stage transition.
|
|
18
|
+
*
|
|
19
|
+
* Looks up the workflow transition from lastStageId to currentStageId,
|
|
20
|
+
* fetches triggers on that transition, and executes on_enter triggers.
|
|
21
|
+
* Currently supports advance_linked_injections; other action types
|
|
22
|
+
* are logged and skipped.
|
|
23
|
+
*/
|
|
24
|
+
export async function fireFocusTransitionTriggers(focusId, lastStageId, currentStageId) {
|
|
25
|
+
if (!lastStageId)
|
|
26
|
+
return; // First time seeing this focus, no transition to fire
|
|
27
|
+
try {
|
|
28
|
+
// Find the transition between the two stages
|
|
29
|
+
const workflow = await fetchFocusWorkflowWithTransitions(focusId);
|
|
30
|
+
const transition = workflow.transitions?.find(t => t.from_stage_id === lastStageId && t.to_stage_id === currentStageId);
|
|
31
|
+
if (!transition)
|
|
32
|
+
return; // No transition found (possible for non-standard stage jumps)
|
|
33
|
+
// Fetch triggers on this transition
|
|
34
|
+
const resolved = await fetchTransitionGuards(transition.id);
|
|
35
|
+
const onEnterTriggers = resolved.triggers.filter(t => t.trigger_event === 'on_enter');
|
|
36
|
+
if (onEnterTriggers.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
for (const trigger of onEnterTriggers) {
|
|
39
|
+
try {
|
|
40
|
+
if (trigger.action_type === 'advance_linked_injections') {
|
|
41
|
+
await executeAdvanceLinkedInjections(focusId, trigger.action_config);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(`[directive-executor] Focus trigger "${trigger.name}" (${trigger.action_type}) ` +
|
|
45
|
+
`skipped -- not supported in focus context`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.warn(`[directive-executor] Failed to execute focus trigger "${trigger.name}":`, err.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.warn(`[directive-executor] Failed to fire focus transition triggers for ${focusId.slice(0, 8)}:`, err.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// ── Phase sync ───────────────────────────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* New focus workflow lifecycle stage names introduced by D1
|
|
60
|
+
* (20260513152817_focus_workflow_lifecycle_and_tree_closure.sql).
|
|
61
|
+
*
|
|
62
|
+
* When `product_focuses.stage` is one of these, the daemon takes the
|
|
63
|
+
* denormalized `focus.stage` as authoritative and maps it directly to the
|
|
64
|
+
* matching workflow_stages row — this is how the new lifecycle (verify ->
|
|
65
|
+
* review -> close_loop -> done) drives the daemon, rather than the legacy
|
|
66
|
+
* delivery-aggregate phase derivation. The legacy derivation is kept as a
|
|
67
|
+
* fallback for focuses still on the older
|
|
68
|
+
* kickoff/coding/verifying/reviewing/winding_down flow.
|
|
69
|
+
*/
|
|
70
|
+
const LIFECYCLE_STAGE_NAMES = new Set([
|
|
71
|
+
'queued',
|
|
72
|
+
'verify',
|
|
73
|
+
'review',
|
|
74
|
+
'close_loop',
|
|
75
|
+
'done',
|
|
76
|
+
]);
|
|
77
|
+
/**
|
|
78
|
+
* Decide which workflow stage name a focus should be in.
|
|
79
|
+
*
|
|
80
|
+
* Pure helper, exported for testing. The selection rule is:
|
|
81
|
+
* 1. If `focus.stage` is one of the new lifecycle stage names
|
|
82
|
+
* (`queued`/`verify`/`review`/`close_loop`/`done`), use it directly.
|
|
83
|
+
* The denormalized stage is the authoritative signal under the new
|
|
84
|
+
* workflow.
|
|
85
|
+
* 2. Otherwise, fall back to the legacy phase derived from the delivery
|
|
86
|
+
* aggregate (kickoff/coding/verifying/reviewing/winding_down/blocked).
|
|
87
|
+
*
|
|
88
|
+
* Note: `review_requested_at` is still read by the legacy derivation for
|
|
89
|
+
* back-compat -- but on the new lifecycle path the `review` stage transition
|
|
90
|
+
* is driven by writes to `product_focuses.stage`, not by writes to
|
|
91
|
+
* `review_requested_at`. This is the Issue 1 contract.
|
|
92
|
+
*/
|
|
93
|
+
export function resolveFocusStageName(input) {
|
|
94
|
+
if (input.focusStage && LIFECYCLE_STAGE_NAMES.has(input.focusStage)) {
|
|
95
|
+
return input.focusStage;
|
|
96
|
+
}
|
|
97
|
+
return deriveFocusPhase({
|
|
98
|
+
deliveries: input.deliveries,
|
|
99
|
+
reviewRequestedAt: input.reviewRequestedAt,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Stage names that represent the focus review window: 'reviewing' on the
|
|
104
|
+
* legacy delivery-aggregate phase derivation, 'review' on the new focus
|
|
105
|
+
* workflow lifecycle. Either means "a review is the gate to done".
|
|
106
|
+
*/
|
|
107
|
+
const REVIEW_STAGE_NAMES = new Set(['reviewing', 'review']);
|
|
108
|
+
/**
|
|
109
|
+
* Decide whether to re-fire a focus's review-stage directive even though the
|
|
110
|
+
* edge-trigger (lastProcessedStages) already marked the stage processed.
|
|
111
|
+
*
|
|
112
|
+
* The edge-trigger fires a stage directive exactly once per stage entry. For
|
|
113
|
+
* the review stage that is normally correct -- a clean review completes, clears
|
|
114
|
+
* review_requested_at, and the focus leaves the review phase. But if the review
|
|
115
|
+
* team never spawned or was killed (e.g. a lifecycle SIGTERM) before routing,
|
|
116
|
+
* the focus stays in the review stage with review_requested_at still set and the
|
|
117
|
+
* directive never re-fires: a silent stuck focus (the founding bug of this focus).
|
|
118
|
+
*
|
|
119
|
+
* A focus STILL in the review stage with review_requested_at set is provably
|
|
120
|
+
* stranded -- any completed review clears review_requested_at and moves the
|
|
121
|
+
* phase. So when no review team is running, re-firing the directive is the
|
|
122
|
+
* self-healing recovery: it respawns the review team. Once a team is running the
|
|
123
|
+
* active-team guard suppresses the re-fire, and a successful review auto-approves
|
|
124
|
+
* and leaves the stage -- so the re-fire is bounded, not a per-poll spin.
|
|
125
|
+
*
|
|
126
|
+
* Pure and exported for unit testing.
|
|
127
|
+
*/
|
|
128
|
+
export function shouldRefireStrandedReview(input) {
|
|
129
|
+
if (!input.stageName || !REVIEW_STAGE_NAMES.has(input.stageName))
|
|
130
|
+
return false;
|
|
131
|
+
if (!input.reviewRequestedAt)
|
|
132
|
+
return false;
|
|
133
|
+
if (input.hasActiveTeam)
|
|
134
|
+
return false;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Synchronize `product_focuses.current_workflow_stage_id` with the focus's
|
|
139
|
+
* authoritative stage. The authoritative stage is taken from `focus.stage`
|
|
140
|
+
* when it names one of the new lifecycle stages (Issue 1); otherwise the
|
|
141
|
+
* legacy delivery-aggregate phase derivation applies.
|
|
142
|
+
*
|
|
143
|
+
* Returns the stage ID the focus should be in (post-sync). Falls back to
|
|
144
|
+
* the recorded stage id on error so callers don't have to handle a third
|
|
145
|
+
* "unknown" state.
|
|
146
|
+
*/
|
|
147
|
+
export async function syncFocusPhase(focus) {
|
|
148
|
+
try {
|
|
149
|
+
const deliveries = await getFocusDeliveries(focus.focus_id);
|
|
150
|
+
const stageName = resolveFocusStageName({
|
|
151
|
+
focusStage: focus.stage ?? null,
|
|
152
|
+
deliveries: deliveries.map((d) => ({ executionStatus: d.executionStatus })),
|
|
153
|
+
reviewRequestedAt: focus.review_requested_at,
|
|
154
|
+
});
|
|
155
|
+
const workflow = await fetchFocusWorkflow(focus.focus_id);
|
|
156
|
+
const stage = workflow.stages.find((s) => s.name === stageName);
|
|
157
|
+
if (!stage) {
|
|
158
|
+
console.warn(`[directive-executor] No "${stageName}" stage in workflow for focus ` +
|
|
159
|
+
`${focus.focus_id.slice(0, 8)}; phase sync skipped.`);
|
|
160
|
+
return focus.current_workflow_stage_id;
|
|
161
|
+
}
|
|
162
|
+
if (stage.id !== focus.current_workflow_stage_id) {
|
|
163
|
+
await updateFocusWorkflowStage(focus.focus_id, stage.id);
|
|
164
|
+
console.log(`[directive-executor] Focus ${focus.focus_id.slice(0, 8)} stage -> ${stageName}`);
|
|
165
|
+
}
|
|
166
|
+
return stage.id;
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.warn(`[directive-executor] Phase sync failed for focus ${focus.focus_id.slice(0, 8)}:`, err.message);
|
|
170
|
+
return focus.current_workflow_stage_id;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=phase-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase-sync.js","sourceRoot":"","sources":["../../src/directive/phase-sync.ts"],"names":[],"mappings":"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"}
|