@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.
- package/LICENSE +21 -0
- package/README.md +329 -0
- package/dist/src/agents/antigravity-launch.js +162 -0
- package/dist/src/agents/antigravity.js +251 -0
- package/dist/src/agents/claude-launch.js +117 -0
- package/dist/src/agents/claude.js +271 -0
- package/dist/src/agents/codex-launch.js +82 -0
- package/dist/src/agents/codex.js +261 -0
- package/dist/src/agents/composite-runner.js +13 -0
- package/dist/src/agents/extraction.js +442 -0
- package/dist/src/agents/failure-feedback.js +132 -0
- package/dist/src/agents/gemini-launch.js +96 -0
- package/dist/src/agents/gemini.js +392 -0
- package/dist/src/agents/job-agent-assignments.js +180 -0
- package/dist/src/agents/lifecycle-feedback.js +147 -0
- package/dist/src/agents/limit-detector.js +83 -0
- package/dist/src/agents/opencode-launch.js +138 -0
- package/dist/src/agents/opencode.js +156 -0
- package/dist/src/agents/plan-breakdown-feedback.js +97 -0
- package/dist/src/agents/pool.js +168 -0
- package/dist/src/agents/prompt.js +462 -0
- package/dist/src/agents/provider-sessions.js +105 -0
- package/dist/src/agents/review-feedback.js +255 -0
- package/dist/src/agents/selection.js +113 -0
- package/dist/src/agents/service.js +1087 -0
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/wave-assessment-feedback.js +69 -0
- package/dist/src/attention/derive.js +337 -0
- package/dist/src/capabilities/index.js +2 -0
- package/dist/src/capabilities/projection.js +183 -0
- package/dist/src/capabilities/types.js +1 -0
- package/dist/src/capabilities/validator.js +87 -0
- package/dist/src/cli/commands/broker-smoke.js +96 -0
- package/dist/src/cli/commands/broker.js +56 -0
- package/dist/src/cli/commands/init.js +4 -0
- package/dist/src/cli/commands/journal.js +43 -0
- package/dist/src/cli/commands/run-loop.js +56 -0
- package/dist/src/cli/commands/run.js +1241 -0
- package/dist/src/cli/commands/tui.js +187 -0
- package/dist/src/cli/dispatcher.js +51 -0
- package/dist/src/cli/errors.js +2 -0
- package/dist/src/cli/init.js +92 -0
- package/dist/src/cli/options.js +86 -0
- package/dist/src/cli/output.js +231 -0
- package/dist/src/cli/run-journal.js +342 -0
- package/dist/src/cli/run-types.js +1 -0
- package/dist/src/cli/run.js +32 -0
- package/dist/src/cli/runtime/dispatch-lifecycle.js +772 -0
- package/dist/src/cli/runtime/provider-runtime-types.js +1 -0
- package/dist/src/cli/runtime/provider-runtime.js +152 -0
- package/dist/src/cli/runtime/provider-tools.js +73 -0
- package/dist/src/cli/runtime/providers/antigravity.js +26 -0
- package/dist/src/cli/runtime/providers/claude.js +20 -0
- package/dist/src/cli/runtime/providers/codex.js +20 -0
- package/dist/src/cli/runtime/providers/gemini.js +20 -0
- package/dist/src/cli/runtime/providers/opencode.js +20 -0
- package/dist/src/cli/runtime/run-context.js +131 -0
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/cli/tui/screen.js +388 -0
- package/dist/src/cli/tui/tracker.js +558 -0
- package/dist/src/cli/tui/types.js +1 -0
- package/dist/src/cli/tui/views.js +1006 -0
- package/dist/src/config/loader.js +136 -0
- package/dist/src/config/schemas.js +403 -0
- package/dist/src/config/types.js +1 -0
- package/dist/src/context/types.js +331 -0
- package/dist/src/domain/entities.js +1 -0
- package/dist/src/git/backburner-git-tool-service.js +680 -0
- package/dist/src/git/gitops-lifecycle.js +56 -0
- package/dist/src/git/merge-prep-workspace.js +357 -0
- package/dist/src/git/types.js +484 -0
- package/dist/src/github/broker-model-content.js +215 -0
- package/dist/src/github/broker-server.js +237 -0
- package/dist/src/github/broker.js +638 -0
- package/dist/src/github/composed.js +53 -0
- package/dist/src/github/gateway.js +478 -0
- package/dist/src/github/normalize.js +222 -0
- package/dist/src/github/pr-classification.js +73 -0
- package/dist/src/github/scoped-broker.js +35 -0
- package/dist/src/github/security.js +126 -0
- package/dist/src/github/service.js +332 -0
- package/dist/src/github/types.js +1 -0
- package/dist/src/github/utils.js +16 -0
- package/dist/src/journal/index.js +4 -0
- package/dist/src/journal/reader.js +62 -0
- package/dist/src/journal/renderer.js +50 -0
- package/dist/src/journal/service.js +72 -0
- package/dist/src/journal/types.js +1 -0
- package/dist/src/journal/writer.js +19 -0
- package/dist/src/mcps/bridge.js +166 -0
- package/dist/src/mcps/index.js +5 -0
- package/dist/src/mcps/reconcile.js +113 -0
- package/dist/src/mcps/smithery-client.js +186 -0
- package/dist/src/mcps/smithery.js +164 -0
- package/dist/src/mcps/types.js +1 -0
- package/dist/src/memory/json-store.js +71 -0
- package/dist/src/memory/prompt.js +16 -0
- package/dist/src/memory/scope.js +31 -0
- package/dist/src/memory/store.js +1 -0
- package/dist/src/memory/tool-service.js +142 -0
- package/dist/src/memory/types.js +12 -0
- package/dist/src/onboarding/config-writer.js +96 -0
- package/dist/src/onboarding/engine.js +86 -0
- package/dist/src/onboarding/prompt.js +102 -0
- package/dist/src/onboarding/services/provider-discovery.js +58 -0
- package/dist/src/onboarding/services/repo-discovery.js +246 -0
- package/dist/src/onboarding/steps/check-github-auth.js +32 -0
- package/dist/src/onboarding/steps/check-requirements.js +34 -0
- package/dist/src/onboarding/steps/configure-models.js +105 -0
- package/dist/src/onboarding/steps/configure-paths.js +33 -0
- package/dist/src/onboarding/steps/discover-providers.js +38 -0
- package/dist/src/onboarding/steps/discover-repos.js +39 -0
- package/dist/src/onboarding/steps/generate-config.js +106 -0
- package/dist/src/onboarding/steps/readiness-validation.js +158 -0
- package/dist/src/onboarding/steps/select-repos.js +76 -0
- package/dist/src/onboarding/steps/show-summary.js +97 -0
- package/dist/src/onboarding/steps/welcome.js +27 -0
- package/dist/src/onboarding/types.js +1 -0
- package/dist/src/onboarding/validators/git-access.js +63 -0
- package/dist/src/onboarding/validators/github-auth.js +85 -0
- package/dist/src/onboarding/validators/tool-checker.js +153 -0
- package/dist/src/onboarding/validators/workspace.js +83 -0
- package/dist/src/prompts/resolver.js +106 -0
- package/dist/src/retrospectives/candidates.js +224 -0
- package/dist/src/retrospectives/derive.js +169 -0
- package/dist/src/retrospectives/ingest.js +321 -0
- package/dist/src/retrospectives/management-prs.js +380 -0
- package/dist/src/retrospectives/proposals.js +199 -0
- package/dist/src/retrospectives/scopes.js +272 -0
- package/dist/src/retrospectives/scoring.js +171 -0
- package/dist/src/retrospectives/types.js +1 -0
- package/dist/src/state/loader.js +473 -0
- package/dist/src/state/types.js +15 -0
- package/dist/src/tasks/derivation/builders.js +70 -0
- package/dist/src/tasks/derivation/handlers/comment-response.js +169 -0
- package/dist/src/tasks/derivation/handlers/implement-plan.js +103 -0
- package/dist/src/tasks/derivation/handlers/index.js +54 -0
- package/dist/src/tasks/derivation/handlers/packet-pr-control.js +92 -0
- package/dist/src/tasks/derivation/handlers/packet-worktree.js +29 -0
- package/dist/src/tasks/derivation/handlers/plan-breakdown.js +45 -0
- package/dist/src/tasks/derivation/handlers/prepare-for-merge.js +91 -0
- package/dist/src/tasks/derivation/handlers/product-discovery.js +36 -0
- package/dist/src/tasks/derivation/handlers/retry-failed-task.js +125 -0
- package/dist/src/tasks/derivation/handlers/review-pr.js +28 -0
- package/dist/src/tasks/derivation/handlers/shared.js +24 -0
- package/dist/src/tasks/derivation/handlers/sync-parent-branch.js +100 -0
- package/dist/src/tasks/derivation/handlers/wave-assessment.js +223 -0
- package/dist/src/tasks/derivation/handlers/write-plan.js +101 -0
- package/dist/src/tasks/derivation/handlers/write-product-spec.js +49 -0
- package/dist/src/tasks/derivation/runner.js +18 -0
- package/dist/src/tasks/derivation/types.js +1 -0
- package/dist/src/tasks/derive.js +810 -0
- package/dist/src/tasks/mcp-skip.js +10 -0
- package/dist/src/tasks/retry.js +10 -0
- package/dist/src/tasks/task.js +184 -0
- package/dist/src/tasks/types.js +1 -0
- package/dist/src/utils/command-runner.js +192 -0
- package/dist/src/utils/json.js +38 -0
- package/dist/src/utils/logger.js +43 -0
- package/dist/src/utils/paths.js +38 -0
- package/dist/src/utils/slug.js +10 -0
- package/dist/src/workflows/registry.js +174 -0
- package/dist/src/workflows/triage.js +90 -0
- package/dist/src/workflows/workstream-machine/derive-events.js +121 -0
- package/dist/src/workflows/workstream-machine/evaluate.js +24 -0
- package/dist/src/workflows/workstream-machine/events.js +1 -0
- package/dist/src/workflows/workstream-machine/machine.js +355 -0
- package/dist/src/workflows/workstream-machine/selectors.js +19 -0
- package/dist/src/workflows/workstream-machine/types.js +1 -0
- package/dist/src/workstreams/branch-sync.js +87 -0
- package/dist/src/workstreams/derive.js +629 -0
- package/dist/src/workstreams/mcp-blockers.js +420 -0
- package/dist/src/workstreams/packet-candidates.js +42 -0
- package/dist/src/workstreams/packet-lifecycle-feedback.js +60 -0
- package/dist/src/workstreams/packet-lifecycle-report.js +199 -0
- package/dist/src/workstreams/packet-plan.js +597 -0
- package/dist/src/workstreams/packet-projections.js +190 -0
- package/dist/src/workstreams/parent-branch-sync-action.js +312 -0
- package/dist/src/workstreams/parent-branch-sync.js +148 -0
- package/dist/src/workstreams/phases.js +4 -0
- package/dist/src/workstreams/plan-breakdown-generator/export.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/index.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/inspect.js +50 -0
- package/dist/src/workstreams/plan-breakdown-generator/patch.js +139 -0
- package/dist/src/workstreams/plan-breakdown-generator/session.js +19 -0
- package/dist/src/workstreams/plan-breakdown-generator/store.js +27 -0
- package/dist/src/workstreams/plan-breakdown-generator/tools.js +407 -0
- package/dist/src/workstreams/plan-breakdown-generator/types.js +6 -0
- package/dist/src/workstreams/plan-breakdown-generator/validate.js +212 -0
- package/dist/src/workstreams/plan-breakdown-schema.js +175 -0
- package/dist/src/workstreams/utils.js +15 -0
- package/dist/src/workstreams/wave-assessment/checkpoint.js +172 -0
- package/dist/src/workstreams/wave-assessment/index.js +3 -0
- package/dist/src/workstreams/wave-assessment/session.js +84 -0
- package/dist/src/workstreams/wave-assessment/store.js +51 -0
- package/dist/src/workstreams/wave-assessment/tools.js +419 -0
- package/dist/src/workstreams/wave-assessment/types.js +1 -0
- package/dist/src/workstreams/wave-assessment/validate.js +78 -0
- package/dist/src/worktrees/service.js +839 -0
- package/dist/src/worktrees/workspace-ownership.js +34 -0
- package/dist/src/worktrees/workspace-recovery.js +177 -0
- 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 {};
|