@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,272 @@
1
+ import * as path from "node:path";
2
+ export function normalizeRetrospectiveScopes(input) {
3
+ const { rawSuggestedScopes, affectedRepoIds, roles, taskKinds, providers } = input;
4
+ const semanticScopes = [];
5
+ const candidateLearningScopes = [];
6
+ const seenKeys = new Set();
7
+ const addCandidate = (scope) => {
8
+ const key = JSON.stringify(scope);
9
+ if (!seenKeys.has(key)) {
10
+ seenKeys.add(key);
11
+ candidateLearningScopes.push(scope);
12
+ }
13
+ };
14
+ const semanticKinds = [
15
+ "workflow",
16
+ "configuration",
17
+ "guidance",
18
+ "orchestration",
19
+ ];
20
+ for (const raw of rawSuggestedScopes) {
21
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
22
+ addCandidate(raw);
23
+ continue;
24
+ }
25
+ const obj = raw;
26
+ const kind = obj.kind;
27
+ if (typeof kind !== "string") {
28
+ continue;
29
+ }
30
+ // Is it already a valid learning scope kind?
31
+ if (SCOPE_PRECEDENCE_ORDER.includes(kind)) {
32
+ addCandidate(raw);
33
+ continue;
34
+ }
35
+ // Is it a known semantic kind?
36
+ if (semanticKinds.includes(kind)) {
37
+ const semanticKind = kind;
38
+ semanticScopes.push({
39
+ kind: semanticKind,
40
+ raw,
41
+ reason: typeof obj.reason === "string" ? obj.reason : undefined,
42
+ });
43
+ // Expand candidates
44
+ switch (semanticKind) {
45
+ case "workflow":
46
+ for (const repoId of affectedRepoIds) {
47
+ for (const taskKind of taskKinds) {
48
+ addCandidate({ kind: "repo_task_kind", repoId, taskKind });
49
+ }
50
+ }
51
+ for (const taskKind of taskKinds) {
52
+ addCandidate({ kind: "task_kind", taskKind });
53
+ }
54
+ for (const repoId of affectedRepoIds) {
55
+ addCandidate({ kind: "repo", repoId });
56
+ }
57
+ addCandidate({ kind: "global" });
58
+ break;
59
+ case "configuration":
60
+ for (const repoId of affectedRepoIds) {
61
+ for (const provider of providers) {
62
+ addCandidate({ kind: "repo_provider", repoId, provider });
63
+ }
64
+ }
65
+ for (const provider of providers) {
66
+ addCandidate({ kind: "provider", provider });
67
+ }
68
+ for (const repoId of affectedRepoIds) {
69
+ addCandidate({ kind: "repo", repoId });
70
+ }
71
+ addCandidate({ kind: "global" });
72
+ break;
73
+ case "guidance":
74
+ for (const repoId of affectedRepoIds) {
75
+ for (const role of roles) {
76
+ addCandidate({ kind: "repo_role", repoId, role });
77
+ }
78
+ }
79
+ for (const repoId of affectedRepoIds) {
80
+ for (const taskKind of taskKinds) {
81
+ addCandidate({ kind: "repo_task_kind", repoId, taskKind });
82
+ }
83
+ }
84
+ for (const role of roles) {
85
+ addCandidate({ kind: "role", role });
86
+ }
87
+ for (const taskKind of taskKinds) {
88
+ addCandidate({ kind: "task_kind", taskKind });
89
+ }
90
+ addCandidate({ kind: "global" });
91
+ break;
92
+ case "orchestration":
93
+ for (const repoId of affectedRepoIds) {
94
+ for (const taskKind of taskKinds) {
95
+ addCandidate({ kind: "repo_task_kind", repoId, taskKind });
96
+ }
97
+ }
98
+ for (const repoId of affectedRepoIds) {
99
+ for (const role of roles) {
100
+ addCandidate({ kind: "repo_role", repoId, role });
101
+ }
102
+ }
103
+ for (const repoId of affectedRepoIds) {
104
+ addCandidate({ kind: "repo", repoId });
105
+ }
106
+ addCandidate({ kind: "global" });
107
+ break;
108
+ }
109
+ }
110
+ else {
111
+ // Not a known semantic kind, pass it through as a candidate so validation can report it
112
+ addCandidate(raw);
113
+ }
114
+ }
115
+ return {
116
+ semanticScopes,
117
+ candidateLearningScopes,
118
+ };
119
+ }
120
+ export const SCOPE_PRECEDENCE_ORDER = [
121
+ "repo_task_kind",
122
+ "repo_role",
123
+ "repo_provider",
124
+ "repo",
125
+ "task_kind",
126
+ "role",
127
+ "provider",
128
+ "global",
129
+ ];
130
+ export function validateLearningScope(input, config) {
131
+ // Guard: must be a plain non-null object
132
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
133
+ return { ok: false, error: "Scope input must be a plain object" };
134
+ }
135
+ const obj = input;
136
+ // Guard: kind must be present and must be a known ScopeKind string
137
+ const { kind, ...rest } = obj;
138
+ if (typeof kind !== "string") {
139
+ return { ok: false, error: "Scope input must have a string \"kind\" field" };
140
+ }
141
+ if (!SCOPE_PRECEDENCE_ORDER.includes(kind)) {
142
+ return { ok: false, error: `Unknown scope kind: "${kind}"` };
143
+ }
144
+ // Dispatch to per-kind validation
145
+ switch (kind) {
146
+ case "global": {
147
+ if (Object.keys(rest).length > 0) {
148
+ return { ok: false, error: `Scope "global" must have no keys besides "kind", got extra: ${Object.keys(rest).join(", ")}` };
149
+ }
150
+ return { ok: true, scope: { kind: "global" } };
151
+ }
152
+ case "provider": {
153
+ const { provider, ...extra } = rest;
154
+ if (Object.keys(extra).length > 0) {
155
+ return { ok: false, error: `Scope "provider" has extra keys: ${Object.keys(extra).join(", ")}` };
156
+ }
157
+ if (typeof provider !== "string" || !config.providers.includes(provider)) {
158
+ return { ok: false, error: `Unknown provider: "${provider}"` };
159
+ }
160
+ return { ok: true, scope: { kind: "provider", provider: provider } };
161
+ }
162
+ case "role": {
163
+ const { role, ...extra } = rest;
164
+ if (Object.keys(extra).length > 0) {
165
+ return { ok: false, error: `Scope "role" has extra keys: ${Object.keys(extra).join(", ")}` };
166
+ }
167
+ if (typeof role !== "string" || !config.roles.includes(role)) {
168
+ return { ok: false, error: `Unknown role: "${role}"` };
169
+ }
170
+ return { ok: true, scope: { kind: "role", role: role } };
171
+ }
172
+ case "task_kind": {
173
+ const { taskKind, ...extra } = rest;
174
+ if (Object.keys(extra).length > 0) {
175
+ return { ok: false, error: `Scope "task_kind" has extra keys: ${Object.keys(extra).join(", ")}` };
176
+ }
177
+ if (typeof taskKind !== "string" || !config.taskKinds.includes(taskKind)) {
178
+ return { ok: false, error: `Unknown task kind: "${taskKind}"` };
179
+ }
180
+ return { ok: true, scope: { kind: "task_kind", taskKind: taskKind } };
181
+ }
182
+ case "repo": {
183
+ const { repoId, ...extra } = rest;
184
+ if (Object.keys(extra).length > 0) {
185
+ return { ok: false, error: `Scope "repo" has extra keys: ${Object.keys(extra).join(", ")}` };
186
+ }
187
+ if (typeof repoId !== "string" || !config.repoIds.includes(repoId)) {
188
+ return { ok: false, error: `Unknown repo ID: "${repoId}"` };
189
+ }
190
+ return { ok: true, scope: { kind: "repo", repoId } };
191
+ }
192
+ case "repo_provider": {
193
+ const { repoId, provider, ...extra } = rest;
194
+ if (Object.keys(extra).length > 0) {
195
+ return { ok: false, error: `Scope "repo_provider" has extra keys: ${Object.keys(extra).join(", ")}` };
196
+ }
197
+ if (typeof repoId !== "string" || !config.repoIds.includes(repoId)) {
198
+ return { ok: false, error: `Unknown repo ID: "${repoId}"` };
199
+ }
200
+ if (typeof provider !== "string" || !config.providers.includes(provider)) {
201
+ return { ok: false, error: `Unknown provider: "${provider}"` };
202
+ }
203
+ return { ok: true, scope: { kind: "repo_provider", repoId, provider: provider } };
204
+ }
205
+ case "repo_role": {
206
+ const { repoId, role, ...extra } = rest;
207
+ if (Object.keys(extra).length > 0) {
208
+ return { ok: false, error: `Scope "repo_role" has extra keys: ${Object.keys(extra).join(", ")}` };
209
+ }
210
+ if (typeof repoId !== "string" || !config.repoIds.includes(repoId)) {
211
+ return { ok: false, error: `Unknown repo ID: "${repoId}"` };
212
+ }
213
+ if (typeof role !== "string" || !config.roles.includes(role)) {
214
+ return { ok: false, error: `Unknown role: "${role}"` };
215
+ }
216
+ return { ok: true, scope: { kind: "repo_role", repoId, role: role } };
217
+ }
218
+ case "repo_task_kind": {
219
+ const { repoId, taskKind, ...extra } = rest;
220
+ if (Object.keys(extra).length > 0) {
221
+ return { ok: false, error: `Scope "repo_task_kind" has extra keys: ${Object.keys(extra).join(", ")}` };
222
+ }
223
+ if (typeof repoId !== "string" || !config.repoIds.includes(repoId)) {
224
+ return { ok: false, error: `Unknown repo ID: "${repoId}"` };
225
+ }
226
+ if (typeof taskKind !== "string" || !config.taskKinds.includes(taskKind)) {
227
+ return { ok: false, error: `Unknown task kind: "${taskKind}"` };
228
+ }
229
+ return { ok: true, scope: { kind: "repo_task_kind", repoId, taskKind: taskKind } };
230
+ }
231
+ default: {
232
+ const exhaustiveCheck = kind;
233
+ return { ok: false, error: `Unhandled scope kind: "${exhaustiveCheck}"` };
234
+ }
235
+ }
236
+ }
237
+ export function resolveScopePath(scope, managementRoot) {
238
+ const learningsRoot = path.resolve(managementRoot, "learnings");
239
+ let relative;
240
+ switch (scope.kind) {
241
+ case "global":
242
+ relative = "global.md";
243
+ break;
244
+ case "provider":
245
+ relative = path.join("providers", `${scope.provider}.md`);
246
+ break;
247
+ case "role":
248
+ relative = path.join("roles", `${scope.role}.md`);
249
+ break;
250
+ case "task_kind":
251
+ relative = path.join("task-kinds", `${scope.taskKind}.md`);
252
+ break;
253
+ case "repo":
254
+ relative = path.join("repos", `${scope.repoId}.md`);
255
+ break;
256
+ case "repo_provider":
257
+ relative = path.join("repo-providers", scope.repoId, `${scope.provider}.md`);
258
+ break;
259
+ case "repo_role":
260
+ relative = path.join("repo-roles", scope.repoId, `${scope.role}.md`);
261
+ break;
262
+ case "repo_task_kind":
263
+ relative = path.join("repo-task-kinds", scope.repoId, `${scope.taskKind}.md`);
264
+ break;
265
+ }
266
+ const resolved = path.resolve(learningsRoot, relative);
267
+ // Guard: resolved path must stay under learningsRoot
268
+ if (!resolved.startsWith(learningsRoot + path.sep) && resolved !== learningsRoot) {
269
+ throw new Error(`Path traversal detected: scope resolved to "${resolved}" which escapes learnings root "${learningsRoot}"`);
270
+ }
271
+ return resolved;
272
+ }
@@ -0,0 +1,171 @@
1
+ import { validateLearningScope, normalizeRetrospectiveScopes, SCOPE_PRECEDENCE_ORDER, } from "./scopes.js";
2
+ import { workflowRegistry } from "../workflows/registry.js";
3
+ export function scoreRetrospectiveProblem(input) {
4
+ const { problem, scopeValidationConfig, now } = input;
5
+ const criteria = [];
6
+ const reasons = [];
7
+ // 1. Impact must be high
8
+ const impactPassed = problem.impact === "high";
9
+ const impactCriterion = {
10
+ criterion: "impact",
11
+ passed: impactPassed,
12
+ actual: problem.impact,
13
+ expected: "high",
14
+ reason: impactPassed ? "impact is high" : `impact is ${problem.impact || "missing"}; expected "high"`,
15
+ };
16
+ criteria.push(impactCriterion);
17
+ if (!impactPassed)
18
+ reasons.push(impactCriterion.reason);
19
+ // 2. Transferability must be high
20
+ const transferabilityPassed = problem.transferability === "high";
21
+ const transferabilityCriterion = {
22
+ criterion: "transferability",
23
+ passed: transferabilityPassed,
24
+ actual: problem.transferability,
25
+ expected: "high",
26
+ reason: transferabilityPassed ? "transferability is high" : `transferability is ${problem.transferability || "missing"}; expected "high"`,
27
+ };
28
+ criteria.push(transferabilityCriterion);
29
+ if (!transferabilityPassed)
30
+ reasons.push(transferabilityCriterion.reason);
31
+ // 3. Confidence must be high
32
+ const confidencePassed = problem.confidence === "high";
33
+ const confidenceCriterion = {
34
+ criterion: "confidence",
35
+ passed: confidencePassed,
36
+ actual: problem.confidence,
37
+ expected: "high",
38
+ reason: confidencePassed ? "confidence is high" : `confidence is ${problem.confidence || "missing"}; expected "high"`,
39
+ };
40
+ criteria.push(confidenceCriterion);
41
+ if (!confidencePassed)
42
+ reasons.push(confidenceCriterion.reason);
43
+ // 4. Improvement Likelihood must be high
44
+ const improvementLikelihoodPassed = problem.improvementLikelihood === "high";
45
+ const improvementLikelihoodCriterion = {
46
+ criterion: "improvementLikelihood",
47
+ passed: improvementLikelihoodPassed,
48
+ actual: problem.improvementLikelihood,
49
+ expected: "high",
50
+ reason: improvementLikelihoodPassed ? "improvementLikelihood is high" : `improvementLikelihood is ${problem.improvementLikelihood || "missing"}; expected "high"`,
51
+ };
52
+ criteria.push(improvementLikelihoodCriterion);
53
+ if (!improvementLikelihoodPassed)
54
+ reasons.push(improvementLikelihoodCriterion.reason);
55
+ // 5. Overfitting Risk must be low
56
+ const overfittingRiskPassed = problem.overfittingRisk === "low";
57
+ const overfittingRiskCriterion = {
58
+ criterion: "overfittingRisk",
59
+ passed: overfittingRiskPassed,
60
+ actual: problem.overfittingRisk,
61
+ expected: "low",
62
+ reason: overfittingRiskPassed ? "overfittingRisk is low" : `overfittingRisk is ${problem.overfittingRisk || "missing"}; expected "low"`,
63
+ };
64
+ criteria.push(overfittingRiskCriterion);
65
+ if (!overfittingRiskPassed)
66
+ reasons.push(overfittingRiskCriterion.reason);
67
+ // 6. Scope validation
68
+ let selectedScope;
69
+ let scopeValidationReason;
70
+ const validScopes = [];
71
+ const invalidScopeReasons = [];
72
+ // Defensive normalization for older persisted state or direct callers
73
+ const { semanticScopes, candidateLearningScopes } = normalizeRetrospectiveScopes({
74
+ rawSuggestedScopes: problem.suggestedScopes,
75
+ affectedRepoIds: problem.affectedRepoIds,
76
+ roles: problem.roles,
77
+ taskKinds: problem.taskKinds,
78
+ providers: problem.providers,
79
+ });
80
+ const normalizedSuggestedScopes = candidateLearningScopes;
81
+ const normalizedAnySemantic = semanticScopes.length > 0 || (problem.semanticSuggestedScopes?.length ?? 0) > 0;
82
+ for (const suggested of normalizedSuggestedScopes) {
83
+ const validation = validateLearningScope(suggested, scopeValidationConfig);
84
+ if (validation.ok) {
85
+ validScopes.push(validation.scope);
86
+ }
87
+ else {
88
+ invalidScopeReasons.push(validation.error);
89
+ }
90
+ }
91
+ if (validScopes.length > 0) {
92
+ // Select the narrowest scope by SCOPE_PRECEDENCE_ORDER
93
+ validScopes.sort((a, b) => {
94
+ const aIdx = SCOPE_PRECEDENCE_ORDER.indexOf(a.kind);
95
+ const bIdx = SCOPE_PRECEDENCE_ORDER.indexOf(b.kind);
96
+ return aIdx - bIdx;
97
+ });
98
+ selectedScope = validScopes[0];
99
+ if (selectedScope) {
100
+ scopeValidationReason = `selected scope ${selectedScope.kind} validated${normalizedAnySemantic ? " after normalizing semantic scopes" : ""}`;
101
+ }
102
+ else {
103
+ // Should not happen given validScopes.length > 0
104
+ scopeValidationReason = "no valid scopes found after validation";
105
+ }
106
+ }
107
+ else {
108
+ scopeValidationReason = invalidScopeReasons.length > 0
109
+ ? `no suggested scope validated successfully: ${invalidScopeReasons.join("; ")}`
110
+ : "no suggested scopes provided";
111
+ }
112
+ const scopePassed = !!selectedScope;
113
+ const scopeCriterion = {
114
+ criterion: "scope",
115
+ passed: scopePassed,
116
+ expected: "at least one valid scope",
117
+ reason: scopeValidationReason,
118
+ };
119
+ criteria.push(scopeCriterion);
120
+ if (!scopePassed)
121
+ reasons.push(scopeValidationReason);
122
+ const allPassed = criteria.every((c) => c.passed);
123
+ const decision = allPassed ? "pass" : "fail";
124
+ return {
125
+ problem: {
126
+ ...problem,
127
+ suggestedScopes: normalizedSuggestedScopes,
128
+ rawSuggestedScopes: problem.rawSuggestedScopes || problem.suggestedScopes,
129
+ semanticSuggestedScopes: problem.semanticSuggestedScopes || (semanticScopes.length > 0 ? semanticScopes : undefined),
130
+ thresholdDecision: decision,
131
+ thresholdCriteria: criteria,
132
+ thresholdReasons: reasons,
133
+ selectedScope,
134
+ scopeValidationReason,
135
+ decisionReason: allPassed ? "All criteria passed" : reasons.join("; "),
136
+ updatedAt: now,
137
+ },
138
+ };
139
+ }
140
+ export function scoreRetrospectiveProblems(problems, scopeValidationConfig, now) {
141
+ return problems.map((p) => {
142
+ // Only rescore if needed (no decision yet, or newly ingested)
143
+ // For now, let's implement the idempotent rescoring as requested.
144
+ const result = scoreRetrospectiveProblem({ problem: p, scopeValidationConfig, now });
145
+ // To avoid needlessly changing updatedAt on already-scored unchanged problems,
146
+ // we can compare the result.
147
+ if (p.thresholdDecision === result.problem.thresholdDecision &&
148
+ p.decisionReason === result.problem.decisionReason &&
149
+ JSON.stringify(p.thresholdCriteria) === JSON.stringify(result.problem.thresholdCriteria) &&
150
+ JSON.stringify(p.selectedScope) === JSON.stringify(result.problem.selectedScope) &&
151
+ JSON.stringify(p.suggestedScopes) === JSON.stringify(result.problem.suggestedScopes) &&
152
+ JSON.stringify(p.rawSuggestedScopes) === JSON.stringify(result.problem.rawSuggestedScopes) &&
153
+ JSON.stringify(p.semanticSuggestedScopes) === JSON.stringify(result.problem.semanticSuggestedScopes)) {
154
+ return p;
155
+ }
156
+ return result.problem;
157
+ });
158
+ }
159
+ export function buildScopeValidationConfigFromRuntime(input) {
160
+ const { repos, agents } = input;
161
+ const repoIds = repos.filter((r) => r.enabled).map((r) => r.id);
162
+ const providers = agents.filter((a) => a.enabled).map((a) => a.provider);
163
+ const roles = Array.from(new Set(agents.filter((a) => a.enabled).flatMap((a) => a.roles)));
164
+ const taskKinds = workflowRegistry.definitionsList.map((def) => def.kind);
165
+ return {
166
+ repoIds,
167
+ providers,
168
+ roles,
169
+ taskKinds,
170
+ };
171
+ }
@@ -0,0 +1 @@
1
+ export {};