@backburner/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +329 -0
  3. package/dist/src/agents/antigravity-launch.js +162 -0
  4. package/dist/src/agents/antigravity.js +251 -0
  5. package/dist/src/agents/claude-launch.js +117 -0
  6. package/dist/src/agents/claude.js +271 -0
  7. package/dist/src/agents/codex-launch.js +82 -0
  8. package/dist/src/agents/codex.js +261 -0
  9. package/dist/src/agents/composite-runner.js +13 -0
  10. package/dist/src/agents/extraction.js +442 -0
  11. package/dist/src/agents/failure-feedback.js +132 -0
  12. package/dist/src/agents/gemini-launch.js +96 -0
  13. package/dist/src/agents/gemini.js +392 -0
  14. package/dist/src/agents/job-agent-assignments.js +180 -0
  15. package/dist/src/agents/lifecycle-feedback.js +147 -0
  16. package/dist/src/agents/limit-detector.js +83 -0
  17. package/dist/src/agents/opencode-launch.js +138 -0
  18. package/dist/src/agents/opencode.js +156 -0
  19. package/dist/src/agents/plan-breakdown-feedback.js +97 -0
  20. package/dist/src/agents/pool.js +168 -0
  21. package/dist/src/agents/prompt.js +462 -0
  22. package/dist/src/agents/provider-sessions.js +105 -0
  23. package/dist/src/agents/review-feedback.js +255 -0
  24. package/dist/src/agents/selection.js +113 -0
  25. package/dist/src/agents/service.js +1087 -0
  26. package/dist/src/agents/types.js +1 -0
  27. package/dist/src/agents/wave-assessment-feedback.js +69 -0
  28. package/dist/src/attention/derive.js +337 -0
  29. package/dist/src/capabilities/index.js +2 -0
  30. package/dist/src/capabilities/projection.js +183 -0
  31. package/dist/src/capabilities/types.js +1 -0
  32. package/dist/src/capabilities/validator.js +87 -0
  33. package/dist/src/cli/commands/broker-smoke.js +96 -0
  34. package/dist/src/cli/commands/broker.js +56 -0
  35. package/dist/src/cli/commands/init.js +4 -0
  36. package/dist/src/cli/commands/journal.js +43 -0
  37. package/dist/src/cli/commands/run-loop.js +56 -0
  38. package/dist/src/cli/commands/run.js +1241 -0
  39. package/dist/src/cli/commands/tui.js +187 -0
  40. package/dist/src/cli/dispatcher.js +51 -0
  41. package/dist/src/cli/errors.js +2 -0
  42. package/dist/src/cli/init.js +92 -0
  43. package/dist/src/cli/options.js +86 -0
  44. package/dist/src/cli/output.js +231 -0
  45. package/dist/src/cli/run-journal.js +342 -0
  46. package/dist/src/cli/run-types.js +1 -0
  47. package/dist/src/cli/run.js +32 -0
  48. package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
  49. package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
  50. package/dist/src/cli/runtime/provider-runtime.js +152 -0
  51. package/dist/src/cli/runtime/provider-tools.js +73 -0
  52. package/dist/src/cli/runtime/providers/antigravity.js +26 -0
  53. package/dist/src/cli/runtime/providers/claude.js +20 -0
  54. package/dist/src/cli/runtime/providers/codex.js +20 -0
  55. package/dist/src/cli/runtime/providers/gemini.js +20 -0
  56. package/dist/src/cli/runtime/providers/opencode.js +20 -0
  57. package/dist/src/cli/runtime/run-context.js +131 -0
  58. package/dist/src/cli/tui/index.js +3 -0
  59. package/dist/src/cli/tui/screen.js +388 -0
  60. package/dist/src/cli/tui/tracker.js +558 -0
  61. package/dist/src/cli/tui/types.js +1 -0
  62. package/dist/src/cli/tui/views.js +1006 -0
  63. package/dist/src/config/loader.js +136 -0
  64. package/dist/src/config/schemas.js +403 -0
  65. package/dist/src/config/types.js +1 -0
  66. package/dist/src/context/types.js +331 -0
  67. package/dist/src/domain/entities.js +1 -0
  68. package/dist/src/git/backburner-git-tool-service.js +680 -0
  69. package/dist/src/git/gitops-lifecycle.js +56 -0
  70. package/dist/src/git/merge-prep-workspace.js +357 -0
  71. package/dist/src/git/types.js +484 -0
  72. package/dist/src/github/broker-model-content.js +215 -0
  73. package/dist/src/github/broker-server.js +237 -0
  74. package/dist/src/github/broker.js +638 -0
  75. package/dist/src/github/composed.js +53 -0
  76. package/dist/src/github/gateway.js +478 -0
  77. package/dist/src/github/normalize.js +222 -0
  78. package/dist/src/github/pr-classification.js +73 -0
  79. package/dist/src/github/scoped-broker.js +35 -0
  80. package/dist/src/github/security.js +126 -0
  81. package/dist/src/github/service.js +332 -0
  82. package/dist/src/github/types.js +1 -0
  83. package/dist/src/github/utils.js +16 -0
  84. package/dist/src/journal/index.js +4 -0
  85. package/dist/src/journal/reader.js +62 -0
  86. package/dist/src/journal/renderer.js +50 -0
  87. package/dist/src/journal/service.js +72 -0
  88. package/dist/src/journal/types.js +1 -0
  89. package/dist/src/journal/writer.js +19 -0
  90. package/dist/src/mcps/bridge.js +166 -0
  91. package/dist/src/mcps/index.js +5 -0
  92. package/dist/src/mcps/reconcile.js +113 -0
  93. package/dist/src/mcps/smithery-client.js +186 -0
  94. package/dist/src/mcps/smithery.js +164 -0
  95. package/dist/src/mcps/types.js +1 -0
  96. package/dist/src/memory/json-store.js +71 -0
  97. package/dist/src/memory/prompt.js +16 -0
  98. package/dist/src/memory/scope.js +31 -0
  99. package/dist/src/memory/store.js +1 -0
  100. package/dist/src/memory/tool-service.js +142 -0
  101. package/dist/src/memory/types.js +12 -0
  102. package/dist/src/onboarding/config-writer.js +96 -0
  103. package/dist/src/onboarding/engine.js +86 -0
  104. package/dist/src/onboarding/prompt.js +102 -0
  105. package/dist/src/onboarding/services/provider-discovery.js +58 -0
  106. package/dist/src/onboarding/services/repo-discovery.js +246 -0
  107. package/dist/src/onboarding/steps/check-github-auth.js +32 -0
  108. package/dist/src/onboarding/steps/check-requirements.js +34 -0
  109. package/dist/src/onboarding/steps/configure-models.js +105 -0
  110. package/dist/src/onboarding/steps/configure-paths.js +33 -0
  111. package/dist/src/onboarding/steps/discover-providers.js +38 -0
  112. package/dist/src/onboarding/steps/discover-repos.js +39 -0
  113. package/dist/src/onboarding/steps/generate-config.js +106 -0
  114. package/dist/src/onboarding/steps/readiness-validation.js +158 -0
  115. package/dist/src/onboarding/steps/select-repos.js +76 -0
  116. package/dist/src/onboarding/steps/show-summary.js +97 -0
  117. package/dist/src/onboarding/steps/welcome.js +27 -0
  118. package/dist/src/onboarding/types.js +1 -0
  119. package/dist/src/onboarding/validators/git-access.js +63 -0
  120. package/dist/src/onboarding/validators/github-auth.js +85 -0
  121. package/dist/src/onboarding/validators/tool-checker.js +153 -0
  122. package/dist/src/onboarding/validators/workspace.js +83 -0
  123. package/dist/src/prompts/resolver.js +106 -0
  124. package/dist/src/retrospectives/candidates.js +224 -0
  125. package/dist/src/retrospectives/derive.js +169 -0
  126. package/dist/src/retrospectives/ingest.js +321 -0
  127. package/dist/src/retrospectives/management-prs.js +380 -0
  128. package/dist/src/retrospectives/proposals.js +199 -0
  129. package/dist/src/retrospectives/scopes.js +272 -0
  130. package/dist/src/retrospectives/scoring.js +171 -0
  131. package/dist/src/retrospectives/types.js +1 -0
  132. package/dist/src/state/loader.js +473 -0
  133. package/dist/src/state/types.js +15 -0
  134. package/dist/src/tasks/derivation/builders.js +70 -0
  135. package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
  136. package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
  137. package/dist/src/tasks/derivation/handlers/index.js +54 -0
  138. package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
  139. package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
  140. package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
  141. package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
  142. package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
  143. package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
  144. package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
  145. package/dist/src/tasks/derivation/handlers/shared.js +24 -0
  146. package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
  147. package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
  148. package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
  149. package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
  150. package/dist/src/tasks/derivation/runner.js +18 -0
  151. package/dist/src/tasks/derivation/types.js +1 -0
  152. package/dist/src/tasks/derive.js +810 -0
  153. package/dist/src/tasks/mcp-skip.js +10 -0
  154. package/dist/src/tasks/retry.js +10 -0
  155. package/dist/src/tasks/task.js +184 -0
  156. package/dist/src/tasks/types.js +1 -0
  157. package/dist/src/utils/command-runner.js +192 -0
  158. package/dist/src/utils/json.js +38 -0
  159. package/dist/src/utils/logger.js +43 -0
  160. package/dist/src/utils/paths.js +38 -0
  161. package/dist/src/utils/slug.js +10 -0
  162. package/dist/src/workflows/registry.js +174 -0
  163. package/dist/src/workflows/triage.js +90 -0
  164. package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
  165. package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
  166. package/dist/src/workflows/workstream-machine/events.js +1 -0
  167. package/dist/src/workflows/workstream-machine/machine.js +355 -0
  168. package/dist/src/workflows/workstream-machine/selectors.js +19 -0
  169. package/dist/src/workflows/workstream-machine/types.js +1 -0
  170. package/dist/src/workstreams/branch-sync.js +87 -0
  171. package/dist/src/workstreams/derive.js +629 -0
  172. package/dist/src/workstreams/mcp-blockers.js +420 -0
  173. package/dist/src/workstreams/packet-candidates.js +42 -0
  174. package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
  175. package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
  176. package/dist/src/workstreams/packet-plan.js +597 -0
  177. package/dist/src/workstreams/packet-projections.js +190 -0
  178. package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
  179. package/dist/src/workstreams/parent-branch-sync.js +148 -0
  180. package/dist/src/workstreams/phases.js +4 -0
  181. package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
  182. package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
  183. package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
  184. package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
  185. package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
  186. package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
  187. package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
  188. package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
  189. package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
  190. package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
  191. package/dist/src/workstreams/utils.js +15 -0
  192. package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
  193. package/dist/src/workstreams/wave-assessment/index.js +3 -0
  194. package/dist/src/workstreams/wave-assessment/session.js +84 -0
  195. package/dist/src/workstreams/wave-assessment/store.js +51 -0
  196. package/dist/src/workstreams/wave-assessment/tools.js +419 -0
  197. package/dist/src/workstreams/wave-assessment/types.js +1 -0
  198. package/dist/src/workstreams/wave-assessment/validate.js +78 -0
  199. package/dist/src/worktrees/service.js +839 -0
  200. package/dist/src/worktrees/workspace-ownership.js +34 -0
  201. package/dist/src/worktrees/workspace-recovery.js +177 -0
  202. package/package.json +61 -0
@@ -0,0 +1,169 @@
1
+ import { workflowRegistry } from "../workflows/registry.js";
2
+ const RETROSPECTIVE_TASK_KINDS = [
3
+ "extract_retrospective_problems",
4
+ "investigate_learning_opportunity",
5
+ "draft_management_learning_proposal",
6
+ ];
7
+ export function deriveRetrospectiveTasks(input) {
8
+ const { existingTasks, candidates, problems, opportunities, agents, contextBuilder, now, } = input;
9
+ const resultTasks = [];
10
+ const resultCandidates = [...candidates];
11
+ const resultProblems = [...problems];
12
+ const resultOpportunities = [...opportunities];
13
+ const blocked = [];
14
+ // 1. Check idleness
15
+ const hasPendingOperationalTasks = existingTasks.some((t) => (t.status === "pending" || t.status === "dispatched") &&
16
+ !RETROSPECTIVE_TASK_KINDS.includes(t.kind) &&
17
+ t.relevant);
18
+ if (hasPendingOperationalTasks) {
19
+ return {
20
+ tasks: [],
21
+ candidates: resultCandidates,
22
+ problems: resultProblems,
23
+ opportunities: resultOpportunities,
24
+ blocked: [],
25
+ };
26
+ }
27
+ // 2. Check if we already have a retrospective task in flight
28
+ const hasInFlightRetrospectiveTask = existingTasks.some((t) => (t.status === "pending" || t.status === "dispatched") &&
29
+ RETROSPECTIVE_TASK_KINDS.includes(t.kind));
30
+ if (hasInFlightRetrospectiveTask) {
31
+ return {
32
+ tasks: [],
33
+ candidates: resultCandidates,
34
+ problems: resultProblems,
35
+ opportunities: resultOpportunities,
36
+ blocked: [],
37
+ };
38
+ }
39
+ // 3. Check for retrospective agent role
40
+ const hasRetrospectiveAgent = agents.some((a) => a.enabled && a.modelClass === "strong" && a.roles.includes("retrospective"));
41
+ const NO_AGENT_REASON = 'No enabled strong agent is configured for role "retrospective".';
42
+ // 4. Derivation in priority order: Extraction > Investigation > Proposal
43
+ // A. Candidate to Extraction
44
+ const eligibleCandidates = resultCandidates
45
+ .filter((c) => c.status === "new" || c.status === "blocked")
46
+ .sort((a, b) => a.detectedAt.localeCompare(b.detectedAt) || a.id.localeCompare(b.id));
47
+ for (const candidate of eligibleCandidates) {
48
+ if (!hasRetrospectiveAgent) {
49
+ candidate.status = "blocked";
50
+ candidate.skipReason = NO_AGENT_REASON;
51
+ candidate.updatedAt = now;
52
+ blocked.push({ sourceId: candidate.id, reason: NO_AGENT_REASON });
53
+ continue;
54
+ }
55
+ const taskId = `extract_retrospective_problems:${candidate.id}`;
56
+ if (existingTasks.some((t) => t.id === taskId))
57
+ continue;
58
+ const task = createTask(taskId, "extract_retrospective_problems", "retrospective_candidate", candidate.id, candidate.url || `https://github.com/${candidate.repoSlug}`, candidate.repoId, candidate.repoSlug, `Extracting problems from candidate ${candidate.id}`, contextBuilder, now, candidate);
59
+ candidate.status = "extracting";
60
+ candidate.updatedAt = now;
61
+ return {
62
+ tasks: [task],
63
+ candidates: resultCandidates,
64
+ problems: resultProblems,
65
+ opportunities: resultOpportunities,
66
+ blocked: [],
67
+ };
68
+ }
69
+ // B. Problem to Investigation
70
+ const eligibleProblems = resultProblems
71
+ .filter((p) => (p.status === "pending" || p.status === "blocked") && p.thresholdDecision === "pass")
72
+ .sort((a, b) => a.detectedAt.localeCompare(b.detectedAt) || a.id.localeCompare(b.id));
73
+ for (const problem of eligibleProblems) {
74
+ if (!hasRetrospectiveAgent) {
75
+ problem.status = "blocked";
76
+ problem.decisionReason = NO_AGENT_REASON;
77
+ problem.updatedAt = now;
78
+ blocked.push({ sourceId: problem.id, reason: NO_AGENT_REASON });
79
+ continue;
80
+ }
81
+ const taskId = `investigate_learning_opportunity:${problem.id}`;
82
+ if (existingTasks.some((t) => t.id === taskId))
83
+ continue;
84
+ const candidate = resultCandidates.find(c => c.id === problem.candidateId);
85
+ const repoId = candidate?.repoId || problem.affectedRepoIds[0] || "unknown";
86
+ const repoSlug = candidate?.repoSlug || problem.affectedRepoIds[0] || "unknown";
87
+ const task = createTask(taskId, "investigate_learning_opportunity", "retrospective_problem", problem.id, `https://github.com/${repoSlug}`, repoId, repoSlug, `Investigating learning opportunity for problem ${problem.id}`, contextBuilder, now, undefined, problem);
88
+ problem.status = "investigating";
89
+ problem.updatedAt = now;
90
+ return {
91
+ tasks: [task],
92
+ candidates: resultCandidates,
93
+ problems: resultProblems,
94
+ opportunities: resultOpportunities,
95
+ blocked: [],
96
+ };
97
+ }
98
+ // C. Opportunity to Proposal
99
+ const eligibleOpportunities = resultOpportunities
100
+ .filter((o) => (o.status === "investigated" || o.status === "blocked"))
101
+ .sort((a, b) => a.detectedAt.localeCompare(b.detectedAt) || a.id.localeCompare(b.id));
102
+ for (const opportunity of eligibleOpportunities) {
103
+ // Check if proposal already exists
104
+ if (input.proposals.some(p => p.opportunityId === opportunity.id))
105
+ continue;
106
+ if (!hasRetrospectiveAgent) {
107
+ opportunity.status = "blocked";
108
+ opportunity.decisionReason = NO_AGENT_REASON;
109
+ opportunity.updatedAt = now;
110
+ blocked.push({ sourceId: opportunity.id, reason: NO_AGENT_REASON });
111
+ continue;
112
+ }
113
+ const taskId = `draft_management_learning_proposal:${opportunity.id}`;
114
+ if (existingTasks.some((t) => t.id === taskId))
115
+ continue;
116
+ // We need a repo for the task. We use the one from the problem/candidate if possible.
117
+ const problem = problems.find(p => p.id === opportunity.problemId);
118
+ const candidate = resultCandidates.find(c => c.id === problem?.candidateId);
119
+ const repoId = candidate?.repoId || problem?.affectedRepoIds[0] || "unknown";
120
+ const repoSlug = candidate?.repoSlug || problem?.affectedRepoIds[0] || "unknown";
121
+ const task = createTask(taskId, "draft_management_learning_proposal", "learning_opportunity", opportunity.id, `https://github.com/${repoSlug}`, repoId, repoSlug, `Drafting learning proposal for opportunity ${opportunity.id}`, contextBuilder, now, undefined, undefined, opportunity);
122
+ return {
123
+ tasks: [task],
124
+ candidates: resultCandidates,
125
+ problems: resultProblems,
126
+ opportunities: resultOpportunities,
127
+ blocked: [],
128
+ };
129
+ }
130
+ return {
131
+ tasks: [],
132
+ candidates: resultCandidates,
133
+ problems: resultProblems,
134
+ opportunities: resultOpportunities,
135
+ blocked,
136
+ };
137
+ }
138
+ function createTask(id, kind, sourceKind, sourceId, sourceUrl, repoId, repoSlug, summary, contextBuilder, now, candidate, problem, opportunity) {
139
+ const policy = workflowRegistry.resolveTaskExecutionPolicy(kind);
140
+ const context = contextBuilder.build({
141
+ taskKind: kind,
142
+ repoIdentifier: repoSlug,
143
+ sourceKind,
144
+ sourceUrl,
145
+ labels: [],
146
+ ...(candidate ? { retrospectiveCandidate: candidate } : {}),
147
+ ...(problem ? { retrospectiveProblem: problem } : {}),
148
+ ...(opportunity ? { learningOpportunity: opportunity } : {}),
149
+ });
150
+ return {
151
+ id,
152
+ kind,
153
+ status: "pending",
154
+ executionPermission: policy.executionPermission,
155
+ handoffPolicy: policy.handoffPolicy,
156
+ repoId,
157
+ repoName: repoSlug,
158
+ repoSlug,
159
+ sourceKind,
160
+ sourceId,
161
+ sourceUrl,
162
+ relevant: true,
163
+ reason: "Idle retrospective work",
164
+ summary,
165
+ context,
166
+ createdAt: now,
167
+ updatedAt: now,
168
+ };
169
+ }
@@ -0,0 +1,321 @@
1
+ import { normalizeRetrospectiveScopes } from "./scopes.js";
2
+ export async function ingestRetrospectiveExecutions(input) {
3
+ const { tasks, executions, candidates, problems, opportunities, proposals, readArtifact, now, } = input;
4
+ const resultCandidates = [...candidates];
5
+ const resultProblems = [...problems];
6
+ const resultOpportunities = [...opportunities];
7
+ const resultProposals = [...proposals];
8
+ const consumedExecutionIds = [];
9
+ const declined = [];
10
+ // Filter for retrospective tasks and completed executions
11
+ const retrospectiveTasks = tasks.filter((t) => [
12
+ "extract_retrospective_problems",
13
+ "investigate_learning_opportunity",
14
+ "draft_management_learning_proposal",
15
+ ].includes(t.kind));
16
+ const completedExecutions = executions.filter((e) => e.taskStatus === "completed" &&
17
+ retrospectiveTasks.some((t) => t.id === e.taskId));
18
+ for (const execution of completedExecutions) {
19
+ const task = retrospectiveTasks.find((t) => t.id === execution.taskId);
20
+ // Skip if already consumed
21
+ if (isExecutionConsumed(execution.id, resultCandidates, resultProblems, resultOpportunities, resultProposals)) {
22
+ continue;
23
+ }
24
+ try {
25
+ const stdout = await readArtifact(execution.stdoutArtifactPath);
26
+ const json = extractJson(stdout);
27
+ if (!json) {
28
+ throw new Error("No valid JSON found in stdout");
29
+ }
30
+ switch (task.kind) {
31
+ case "extract_retrospective_problems":
32
+ await ingestExtraction(json, execution, task, resultCandidates, resultProblems, now);
33
+ break;
34
+ case "investigate_learning_opportunity":
35
+ await ingestInvestigation(json, execution, task, resultProblems, resultOpportunities, now);
36
+ break;
37
+ case "draft_management_learning_proposal":
38
+ await ingestProposal(json, execution, task, resultOpportunities, resultProposals, now);
39
+ break;
40
+ }
41
+ consumedExecutionIds.push(execution.id);
42
+ }
43
+ catch (err) {
44
+ const reason = err.message;
45
+ declined.push({ executionId: execution.id, taskId: execution.taskId, reason });
46
+ handleIngestionFailure(execution, task, resultCandidates, resultProblems, resultOpportunities, resultProposals, reason, now);
47
+ }
48
+ }
49
+ return {
50
+ candidates: resultCandidates,
51
+ problems: resultProblems,
52
+ opportunities: resultOpportunities,
53
+ proposals: resultProposals,
54
+ consumedExecutionIds,
55
+ declined,
56
+ };
57
+ }
58
+ function isExecutionConsumed(executionId, candidates, problems, opportunities, proposals) {
59
+ return (candidates.some((c) => c.sourceExecutionId === executionId) ||
60
+ problems.some((p) => p.sourceExecutionId === executionId) ||
61
+ opportunities.some((o) => o.sourceExecutionId === executionId) ||
62
+ proposals.some((p) => p.sourceExecutionId === executionId));
63
+ }
64
+ function extractJson(stdout) {
65
+ const match = stdout.match(/\{[\s\S]*\}/);
66
+ if (!match)
67
+ return null;
68
+ try {
69
+ return JSON.parse(match[0]);
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function ingestExtraction(json, execution, task, candidates, problems, now) {
76
+ const problemsArray = json.problems;
77
+ let sourceItems;
78
+ let explicitEmpty = false;
79
+ if (problemsArray !== undefined) {
80
+ if (!Array.isArray(problemsArray)) {
81
+ throw new Error("Field 'problems' must be an array");
82
+ }
83
+ sourceItems = problemsArray;
84
+ if (sourceItems.length === 0) {
85
+ explicitEmpty = true;
86
+ }
87
+ }
88
+ else {
89
+ // Fallback: treat the whole object as a single problem result
90
+ sourceItems = [json];
91
+ }
92
+ // Validate all items if not explicitly empty
93
+ if (!explicitEmpty) {
94
+ for (const [index, prob] of sourceItems.entries()) {
95
+ if (!validateExtractionProblem(prob, task.sourceId)) {
96
+ const location = problemsArray !== undefined ? `at index ${index}` : "at top-level";
97
+ throw new Error(`Invalid problem record ${location}`);
98
+ }
99
+ }
100
+ // All valid, ingest them
101
+ for (const prob of sourceItems) {
102
+ const { semanticScopes, candidateLearningScopes } = normalizeRetrospectiveScopes({
103
+ rawSuggestedScopes: prob.suggestedScopes,
104
+ affectedRepoIds: prob.affectedRepos,
105
+ roles: prob.affectedRoles,
106
+ taskKinds: prob.affectedTaskKinds,
107
+ providers: prob.affectedProviders,
108
+ });
109
+ const problemRecord = {
110
+ id: `retrospective-problem:${prob.candidateId}:${hashProse(prob.problemSummary).slice(0, 8)}`,
111
+ candidateId: prob.candidateId,
112
+ status: "pending",
113
+ evidenceReferences: prob.evidenceReferences,
114
+ affectedRepoIds: prob.affectedRepos,
115
+ roles: prob.affectedRoles,
116
+ taskKinds: prob.affectedTaskKinds,
117
+ providers: prob.affectedProviders,
118
+ confidence: prob.confidence,
119
+ impact: prob.impact,
120
+ transferability: prob.transferability,
121
+ improvementLikelihood: prob.improvementLikelihood,
122
+ overfittingRisk: prob.overfittingRisk,
123
+ suggestedScopes: candidateLearningScopes,
124
+ rawSuggestedScopes: prob.suggestedScopes,
125
+ semanticSuggestedScopes: semanticScopes,
126
+ detectedAt: now,
127
+ updatedAt: now,
128
+ sourceTaskId: task.id,
129
+ sourceExecutionId: execution.id,
130
+ structuredObservations: prob.problemSummary + "\n\n" + prob.reasonItMightRecur,
131
+ };
132
+ problems.push(problemRecord);
133
+ }
134
+ }
135
+ const candidate = candidates.find((c) => c.id === task.sourceId);
136
+ if (candidate) {
137
+ candidate.status = explicitEmpty ? "skipped" : "extracted";
138
+ if (explicitEmpty) {
139
+ candidate.skipReason = "Agent reported no problems found";
140
+ }
141
+ else {
142
+ delete candidate.skipReason;
143
+ }
144
+ candidate.sourceExecutionId = execution.id;
145
+ candidate.sourceTaskId = task.id;
146
+ candidate.updatedAt = now;
147
+ }
148
+ }
149
+ function validateExtractionProblem(prob, expectedCandidateId) {
150
+ return (prob.candidateId === expectedCandidateId &&
151
+ typeof prob.problemSummary === "string" &&
152
+ Array.isArray(prob.evidenceReferences) &&
153
+ Array.isArray(prob.affectedRepos) &&
154
+ Array.isArray(prob.affectedRoles) &&
155
+ Array.isArray(prob.affectedTaskKinds) &&
156
+ Array.isArray(prob.affectedProviders) &&
157
+ Array.isArray(prob.suggestedScopes) &&
158
+ typeof prob.reasonItMightRecur === "string");
159
+ }
160
+ async function ingestInvestigation(json, execution, task, problems, opportunities, now) {
161
+ if (!validateInvestigation(json, task.sourceId)) {
162
+ throw new Error("Invalid investigation JSON");
163
+ }
164
+ const opportunityRecord = {
165
+ id: `learning-opportunity:${json.problemId}`,
166
+ problemId: json.problemId,
167
+ status: "investigated",
168
+ investigationSummary: json.investigationSummary,
169
+ investigationResult: json.investigationResult,
170
+ selectedScope: json.selectedScope,
171
+ evidenceReferences: json.evidenceReferences,
172
+ detectedAt: now,
173
+ updatedAt: now,
174
+ sourceTaskId: task.id,
175
+ sourceExecutionId: execution.id,
176
+ };
177
+ opportunities.push(opportunityRecord);
178
+ const problem = problems.find((p) => p.id === task.sourceId);
179
+ if (problem) {
180
+ problem.status = "completed";
181
+ problem.sourceExecutionId = execution.id;
182
+ problem.sourceTaskId = task.id;
183
+ problem.updatedAt = now;
184
+ }
185
+ }
186
+ function validateInvestigation(json, expectedProblemId) {
187
+ return (json.problemId === expectedProblemId &&
188
+ typeof json.investigationSummary === "string" &&
189
+ typeof json.investigationResult === "string" &&
190
+ Array.isArray(json.evidenceReferences) &&
191
+ validateLearningScopeStructure(json.selectedScope));
192
+ }
193
+ async function ingestProposal(json, execution, task, opportunities, proposals, now) {
194
+ if (!validateProposal(json, task.sourceId)) {
195
+ throw new Error("Invalid proposal JSON");
196
+ }
197
+ // Avoid duplicate proposals for the same opportunity
198
+ if (proposals.some(p => p.opportunityId === json.opportunityId)) {
199
+ return;
200
+ }
201
+ const proposalRecord = {
202
+ id: `learning-proposal:${json.opportunityId}`,
203
+ opportunityId: json.opportunityId,
204
+ problemId: json.problemId,
205
+ status: "pending",
206
+ selectedScope: json.selectedScope,
207
+ proposedEditMode: json.proposedEditMode,
208
+ proposedGuidanceText: json.proposedGuidanceText,
209
+ rationale: json.rationale,
210
+ expectedBehaviorChange: json.expectedBehaviorChange,
211
+ detectedAt: now,
212
+ updatedAt: now,
213
+ sourceTaskId: task.id,
214
+ sourceExecutionId: execution.id,
215
+ };
216
+ proposals.push(proposalRecord);
217
+ // Mark execution as consumed on the opportunity to prevent re-derivation
218
+ const opportunity = opportunities.find(o => o.id === task.sourceId);
219
+ if (opportunity) {
220
+ opportunity.sourceExecutionId = execution.id;
221
+ opportunity.sourceTaskId = task.id;
222
+ opportunity.updatedAt = now;
223
+ }
224
+ }
225
+ function validateProposal(json, expectedOpportunityId) {
226
+ const validEditModes = ["append", "refine", "supersede", "create"];
227
+ return (json.opportunityId === expectedOpportunityId &&
228
+ validateLearningScopeStructure(json.selectedScope) &&
229
+ validEditModes.includes(json.proposedEditMode) &&
230
+ typeof json.proposedGuidanceText === "string" &&
231
+ json.proposedGuidanceText.trim().length > 0 &&
232
+ typeof json.rationale === "string" &&
233
+ json.rationale.trim().length > 0 &&
234
+ typeof json.expectedBehaviorChange === "string" &&
235
+ json.expectedBehaviorChange.trim().length > 0);
236
+ }
237
+ function handleIngestionFailure(execution, task, candidates, problems, opportunities, proposals, reason, now) {
238
+ switch (task.kind) {
239
+ case "extract_retrospective_problems":
240
+ const candidate = candidates.find((c) => c.id === task.sourceId);
241
+ if (candidate) {
242
+ candidate.status = "failed";
243
+ candidate.skipReason = `Ingestion failed: ${reason}`;
244
+ candidate.sourceExecutionId = execution.id;
245
+ candidate.sourceTaskId = task.id;
246
+ candidate.updatedAt = now;
247
+ }
248
+ break;
249
+ case "investigate_learning_opportunity":
250
+ const problem = problems.find((p) => p.id === task.sourceId);
251
+ if (problem) {
252
+ problem.status = "failed";
253
+ problem.decisionReason = `Ingestion failed: ${reason}`;
254
+ problem.sourceExecutionId = execution.id;
255
+ problem.sourceTaskId = task.id;
256
+ problem.updatedAt = now;
257
+ }
258
+ break;
259
+ case "draft_management_learning_proposal":
260
+ const opportunity = opportunities.find((o) => o.id === task.sourceId);
261
+ if (opportunity) {
262
+ opportunity.status = "declined";
263
+ opportunity.decisionReason = `Ingestion failed: ${reason}`;
264
+ opportunity.sourceExecutionId = execution.id;
265
+ opportunity.sourceTaskId = task.id;
266
+ opportunity.updatedAt = now;
267
+ }
268
+ // Also persist a declined proposal record if it doesn't already exist
269
+ if (!proposals.some(p => p.opportunityId === task.sourceId)) {
270
+ proposals.push({
271
+ id: `learning-proposal:${task.sourceId}`,
272
+ opportunityId: task.sourceId,
273
+ status: "declined",
274
+ declineReason: `Ingestion failed: ${reason}`,
275
+ detectedAt: now,
276
+ updatedAt: now,
277
+ sourceTaskId: task.id,
278
+ sourceExecutionId: execution.id,
279
+ });
280
+ }
281
+ break;
282
+ }
283
+ }
284
+ function validateLearningScopeStructure(scope) {
285
+ if (!scope || typeof scope !== "object" || Array.isArray(scope))
286
+ return false;
287
+ if (typeof scope.kind !== "string")
288
+ return false;
289
+ switch (scope.kind) {
290
+ case "global":
291
+ return true;
292
+ case "provider":
293
+ return typeof scope.provider === "string" && scope.provider.length > 0;
294
+ case "role":
295
+ return typeof scope.role === "string" && scope.role.length > 0;
296
+ case "task_kind":
297
+ return typeof scope.taskKind === "string" && scope.taskKind.length > 0;
298
+ case "repo":
299
+ return typeof scope.repoId === "string" && scope.repoId.length > 0;
300
+ case "repo_provider":
301
+ return (typeof scope.repoId === "string" && scope.repoId.length > 0 &&
302
+ typeof scope.provider === "string" && scope.provider.length > 0);
303
+ case "repo_role":
304
+ return (typeof scope.repoId === "string" && scope.repoId.length > 0 &&
305
+ typeof scope.role === "string" && scope.role.length > 0);
306
+ case "repo_task_kind":
307
+ return (typeof scope.repoId === "string" && scope.repoId.length > 0 &&
308
+ typeof scope.taskKind === "string" && scope.taskKind.length > 0);
309
+ default:
310
+ return false;
311
+ }
312
+ }
313
+ function hashProse(text) {
314
+ let hash = 0;
315
+ for (let i = 0; i < text.length; i++) {
316
+ const char = text.charCodeAt(i);
317
+ hash = ((hash << 5) - hash) + char;
318
+ hash = hash & hash; // Convert to 32bit integer
319
+ }
320
+ return Math.abs(hash).toString(16);
321
+ }