@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,73 @@
|
|
|
1
|
+
export function classifyPullRequestBase(input) {
|
|
2
|
+
const baseBranch = input.baseBranch.trim();
|
|
3
|
+
const defaultBranch = input.defaultBranch.trim();
|
|
4
|
+
if (baseBranch === defaultBranch) {
|
|
5
|
+
return {
|
|
6
|
+
kind: "top_level",
|
|
7
|
+
pullRequestNumber: input.pullRequestNumber,
|
|
8
|
+
repoSlug: input.repoSlug,
|
|
9
|
+
baseBranch,
|
|
10
|
+
defaultBranch,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
kind: "non_default_parent",
|
|
15
|
+
pullRequestNumber: input.pullRequestNumber,
|
|
16
|
+
repoSlug: input.repoSlug,
|
|
17
|
+
baseBranch,
|
|
18
|
+
defaultBranch,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export const MANAGEMENT_LEARNING_BRANCH_PREFIXES = [
|
|
22
|
+
"agent/management-learning/",
|
|
23
|
+
"management-learning/",
|
|
24
|
+
];
|
|
25
|
+
export function classifyManagementPullRequest(input) {
|
|
26
|
+
const { repoId, repoSlug, pullRequestNumber, headBranch, managementRepo, managementPrs = [], managementBranchPrefixes = MANAGEMENT_LEARNING_BRANCH_PREFIXES, } = input;
|
|
27
|
+
// 1. Check if it matches a known management PR record
|
|
28
|
+
const matchingRecord = managementPrs.find((record) => record.mode === "pull_request" &&
|
|
29
|
+
record.prNumber === pullRequestNumber &&
|
|
30
|
+
(record.repoSlug === repoSlug || record.repoId === repoId));
|
|
31
|
+
if (matchingRecord) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "management",
|
|
34
|
+
reason: "Matched known management PR record",
|
|
35
|
+
managementPrRecordId: matchingRecord.id,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// 2. Check if it's in the management repo
|
|
39
|
+
const isManagementRepo = managementRepo &&
|
|
40
|
+
(managementRepo.repoId === repoId || managementRepo.repoSlug === repoSlug);
|
|
41
|
+
if (isManagementRepo) {
|
|
42
|
+
// 3. Check head branch prefix
|
|
43
|
+
if (headBranch) {
|
|
44
|
+
if (managementBranchPrefixes.some((prefix) => headBranch.startsWith(prefix))) {
|
|
45
|
+
return {
|
|
46
|
+
kind: "management",
|
|
47
|
+
reason: "Management repo PR with management branch prefix",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 4. Check if it matches a management record by branch
|
|
52
|
+
const matchingBranchRecord = managementPrs.find((record) => record.branchName === headBranch &&
|
|
53
|
+
(record.repoSlug === repoSlug || record.repoId === repoId));
|
|
54
|
+
if (matchingBranchRecord) {
|
|
55
|
+
return {
|
|
56
|
+
kind: "management",
|
|
57
|
+
reason: "Matched known management PR record by branch",
|
|
58
|
+
managementPrRecordId: matchingBranchRecord.id,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 5. If we don't have enough info about management repo, we might return unknown
|
|
63
|
+
if (!managementRepo) {
|
|
64
|
+
return {
|
|
65
|
+
kind: "unknown",
|
|
66
|
+
reason: "Management repository identity is unknown",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
kind: "operational",
|
|
71
|
+
reason: "Did not match management PR criteria",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class ScopedBrokerToolService {
|
|
2
|
+
delegate;
|
|
3
|
+
activeAllowedToolNames;
|
|
4
|
+
constructor(delegate) {
|
|
5
|
+
this.delegate = delegate;
|
|
6
|
+
}
|
|
7
|
+
listTools() {
|
|
8
|
+
return this.delegate.listTools().filter((tool) => this.isAllowed(tool.name));
|
|
9
|
+
}
|
|
10
|
+
async callTool(name, rawArguments) {
|
|
11
|
+
if (!this.isAllowed(name)) {
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
tool: name,
|
|
15
|
+
error: {
|
|
16
|
+
code: "tool_not_found",
|
|
17
|
+
message: `Tool "${name}" is not enabled for this agent run`
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return await this.delegate.callTool(name, rawArguments);
|
|
22
|
+
}
|
|
23
|
+
bindAllowedTools(allowedToolNames) {
|
|
24
|
+
this.activeAllowedToolNames = new Set(allowedToolNames);
|
|
25
|
+
}
|
|
26
|
+
clearAllowedTools() {
|
|
27
|
+
this.activeAllowedToolNames = undefined;
|
|
28
|
+
}
|
|
29
|
+
isAllowed(publicToolName) {
|
|
30
|
+
return (this.activeAllowedToolNames === undefined ||
|
|
31
|
+
this.activeAllowedToolNames.has(publicToolName) ||
|
|
32
|
+
this.activeAllowedToolNames.has("*") ||
|
|
33
|
+
Array.from(this.activeAllowedToolNames).some((allowedToolName) => allowedToolName.endsWith(".*") && publicToolName.startsWith(allowedToolName.slice(0, -1))));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export class SecurityFilteredGitHubGateway {
|
|
2
|
+
inner;
|
|
3
|
+
allowedUsers;
|
|
4
|
+
constructor(inner, allowedUsers) {
|
|
5
|
+
this.inner = inner;
|
|
6
|
+
this.allowedUsers = allowedUsers;
|
|
7
|
+
}
|
|
8
|
+
isAllowed(login) {
|
|
9
|
+
return this.allowedUsers.includes(login || "");
|
|
10
|
+
}
|
|
11
|
+
async getRepositoryMetadata(owner, name) {
|
|
12
|
+
return this.inner.getRepositoryMetadata(owner, name);
|
|
13
|
+
}
|
|
14
|
+
async getRepositoryDefaultBranch(owner, name) {
|
|
15
|
+
return this.inner.getRepositoryDefaultBranch(owner, name);
|
|
16
|
+
}
|
|
17
|
+
async getIssue(owner, name, issueNumber) {
|
|
18
|
+
const issue = await this.inner.getIssue(owner, name, issueNumber);
|
|
19
|
+
if (!this.isAllowed(issue.author?.login)) {
|
|
20
|
+
throw new Error(`Unauthorized: Issue #${issueNumber} author is not in allowedUsers`);
|
|
21
|
+
}
|
|
22
|
+
return this.filterLabels(owner, name, issue);
|
|
23
|
+
}
|
|
24
|
+
async getPullRequest(owner, name, prNumber) {
|
|
25
|
+
const pr = await this.inner.getPullRequest(owner, name, prNumber);
|
|
26
|
+
if (!this.isAllowed(pr.author?.login)) {
|
|
27
|
+
throw new Error(`Unauthorized: PR #${prNumber} author is not in allowedUsers`);
|
|
28
|
+
}
|
|
29
|
+
return this.filterLabels(owner, name, pr);
|
|
30
|
+
}
|
|
31
|
+
async findOpenPullRequestByHeadBranch(owner, name, headBranch) {
|
|
32
|
+
const pr = await this.inner.findOpenPullRequestByHeadBranch(owner, name, headBranch);
|
|
33
|
+
if (pr && !this.isAllowed(pr.author?.login)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (!pr) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return this.filterLabels(owner, name, pr);
|
|
40
|
+
}
|
|
41
|
+
async createPullRequest(input) {
|
|
42
|
+
const pr = await this.inner.createPullRequest(input);
|
|
43
|
+
if (!this.isAllowed(pr.author?.login)) {
|
|
44
|
+
throw new Error(`Unauthorized: Created PR author is not in allowedUsers`);
|
|
45
|
+
}
|
|
46
|
+
return this.filterLabels(input.owner, input.name, pr);
|
|
47
|
+
}
|
|
48
|
+
async listOpenIssues(owner, name) {
|
|
49
|
+
const issues = await this.inner.listOpenIssues(owner, name);
|
|
50
|
+
const filteredIssues = issues.filter((issue) => this.isAllowed(issue.author?.login));
|
|
51
|
+
return Promise.all(filteredIssues.map((issue) => this.filterLabels(owner, name, issue)));
|
|
52
|
+
}
|
|
53
|
+
async listOpenPullRequests(owner, name) {
|
|
54
|
+
const prs = await this.inner.listOpenPullRequests(owner, name);
|
|
55
|
+
const filteredPrs = prs.filter((pr) => this.isAllowed(pr.author?.login));
|
|
56
|
+
return Promise.all(filteredPrs.map((pr) => this.filterLabels(owner, name, pr)));
|
|
57
|
+
}
|
|
58
|
+
async listIssueComments(owner, name, issueNumber) {
|
|
59
|
+
const comments = await this.inner.listIssueComments(owner, name, issueNumber);
|
|
60
|
+
return comments.filter((comment) => this.isAllowed(comment.author?.login));
|
|
61
|
+
}
|
|
62
|
+
async listPullRequestComments(owner, name, prNumber) {
|
|
63
|
+
const comments = await this.inner.listPullRequestComments(owner, name, prNumber);
|
|
64
|
+
return comments.filter((comment) => this.isAllowed(comment.author?.login));
|
|
65
|
+
}
|
|
66
|
+
async listPullRequestReviews(owner, name, prNumber) {
|
|
67
|
+
const reviews = await this.inner.listPullRequestReviews(owner, name, prNumber);
|
|
68
|
+
return reviews.filter((review) => this.isAllowed(review.author?.login));
|
|
69
|
+
}
|
|
70
|
+
async listPullRequestReviewComments(owner, name, prNumber) {
|
|
71
|
+
const comments = await this.inner.listPullRequestReviewComments(owner, name, prNumber);
|
|
72
|
+
return comments.filter((comment) => this.isAllowed(comment.user?.login));
|
|
73
|
+
}
|
|
74
|
+
async listPullRequestChangedFiles(owner, name, prNumber) {
|
|
75
|
+
return this.inner.listPullRequestChangedFiles(owner, name, prNumber);
|
|
76
|
+
}
|
|
77
|
+
async searchCode(input) {
|
|
78
|
+
return this.inner.searchCode(input);
|
|
79
|
+
}
|
|
80
|
+
async postIssueComment(input) {
|
|
81
|
+
return this.inner.postIssueComment(input);
|
|
82
|
+
}
|
|
83
|
+
async updateIssueComment(input) {
|
|
84
|
+
return this.inner.updateIssueComment(input);
|
|
85
|
+
}
|
|
86
|
+
async addReaction(input) {
|
|
87
|
+
return this.inner.addReaction(input);
|
|
88
|
+
}
|
|
89
|
+
async postPullRequestReviewCommentReply(input) {
|
|
90
|
+
return this.inner.postPullRequestReviewCommentReply(input);
|
|
91
|
+
}
|
|
92
|
+
async submitPullRequestReview(input) {
|
|
93
|
+
return this.inner.submitPullRequestReview(input);
|
|
94
|
+
}
|
|
95
|
+
async listIssueEvents(owner, name, issueNumber) {
|
|
96
|
+
return this.inner.listIssueEvents(owner, name, issueNumber);
|
|
97
|
+
}
|
|
98
|
+
async listPullRequestCheckRuns(owner, name, headSha) {
|
|
99
|
+
return this.inner.listPullRequestCheckRuns(owner, name, headSha);
|
|
100
|
+
}
|
|
101
|
+
async filterLabels(owner, name, entity) {
|
|
102
|
+
const labels = entity.labels || [];
|
|
103
|
+
if (labels.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
...entity,
|
|
106
|
+
labels: []
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const events = await this.inner.listIssueEvents(owner, name, entity.number);
|
|
110
|
+
const allowedLabels = new Set();
|
|
111
|
+
for (const label of labels) {
|
|
112
|
+
const labelEvents = events.filter((e) => e.label?.name === label.name && (e.event === "labeled" || e.event === "unlabeled"));
|
|
113
|
+
const sortedEvents = [...labelEvents].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
114
|
+
const lastEvent = sortedEvents[sortedEvents.length - 1];
|
|
115
|
+
if (lastEvent && lastEvent.event === "labeled") {
|
|
116
|
+
if (this.isAllowed(lastEvent.actor?.login)) {
|
|
117
|
+
allowedLabels.add(label.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
...entity,
|
|
123
|
+
labels: labels.filter((l) => allowedLabels.has(l.name))
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { normalizeIssue, normalizeIssueComments, normalizePullRequest, normalizePullRequestReviewComments, normalizePullRequestReviews } from "./normalize.js";
|
|
2
|
+
export class GitHubScanService {
|
|
3
|
+
gitHubGateway;
|
|
4
|
+
now;
|
|
5
|
+
constructor(gitHubGateway, now = () => new Date().toISOString()) {
|
|
6
|
+
this.gitHubGateway = gitHubGateway;
|
|
7
|
+
this.now = now;
|
|
8
|
+
}
|
|
9
|
+
async scanRepositories(repos, repoResults, previousState) {
|
|
10
|
+
const seenAt = this.now();
|
|
11
|
+
const previousIssues = new Map(previousState.issues.map((issue) => [issue.id, issue]));
|
|
12
|
+
const previousPullRequests = new Map(previousState.prs.map((pr) => [pr.id, pr]));
|
|
13
|
+
const previousComments = new Map(previousState.comments.map((comment) => [comment.id, comment]));
|
|
14
|
+
const resultsByRepoId = new Map(repoResults.map((record) => [record.repoId, record]));
|
|
15
|
+
const issues = [];
|
|
16
|
+
const prs = [];
|
|
17
|
+
const comments = [];
|
|
18
|
+
const failures = [];
|
|
19
|
+
for (const repo of repos) {
|
|
20
|
+
const repoResult = resultsByRepoId.get(repo.id);
|
|
21
|
+
if (!repoResult) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const repoFailures = [];
|
|
25
|
+
let repoIssueCount = 0;
|
|
26
|
+
let repoPrCount = 0;
|
|
27
|
+
let repoCommentCount = 0;
|
|
28
|
+
const repoMetadata = await this.fetchRepositoryMetadata(repo, repoResult, repoFailures);
|
|
29
|
+
if (!repoMetadata) {
|
|
30
|
+
const carriedIssues = entitiesForRepo(previousState.issues, repo.id);
|
|
31
|
+
const carriedPrs = entitiesForRepo(previousState.prs, repo.id);
|
|
32
|
+
const carriedComments = entitiesForRepo(previousState.comments, repo.id);
|
|
33
|
+
issues.push(...carriedIssues);
|
|
34
|
+
prs.push(...carriedPrs);
|
|
35
|
+
comments.push(...carriedComments);
|
|
36
|
+
failures.push(...repoFailures);
|
|
37
|
+
repoResult.githubScan = {
|
|
38
|
+
status: "failed",
|
|
39
|
+
scannedAt: seenAt,
|
|
40
|
+
issueCount: carriedIssues.length,
|
|
41
|
+
pullRequestCount: carriedPrs.length,
|
|
42
|
+
commentCount: carriedComments.length,
|
|
43
|
+
failures: repoFailures
|
|
44
|
+
};
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const [issueListFetch, pullRequestListFetch] = await Promise.all([
|
|
48
|
+
this.fetchWithFailure(() => this.gitHubGateway.listOpenIssues(repo.owner, repo.name), repo.id, repoMetadata.nameWithOwner, "list_issues", "repo", repoFailures),
|
|
49
|
+
this.fetchWithFailure(() => this.gitHubGateway.listOpenPullRequests(repo.owner, repo.name), repo.id, repoMetadata.nameWithOwner, "list_pull_requests", "repo", repoFailures)
|
|
50
|
+
]);
|
|
51
|
+
const rawIssues = issueListFetch.value ?? [];
|
|
52
|
+
const rawPullRequests = pullRequestListFetch.value ?? [];
|
|
53
|
+
const rawPullRequestsByNumber = new Map(rawPullRequests.map((pr) => [pr.number, pr]));
|
|
54
|
+
if (issueListFetch.failed) {
|
|
55
|
+
const carriedIssues = entitiesForRepo(previousState.issues, repo.id);
|
|
56
|
+
const carriedIssueComments = commentsForParentType(previousState.comments, repo.id, "issue");
|
|
57
|
+
issues.push(...carriedIssues);
|
|
58
|
+
comments.push(...carriedIssueComments);
|
|
59
|
+
repoIssueCount += carriedIssues.length;
|
|
60
|
+
repoCommentCount += carriedIssueComments.length;
|
|
61
|
+
}
|
|
62
|
+
if (!issueListFetch.failed) {
|
|
63
|
+
for (const rawIssue of rawIssues) {
|
|
64
|
+
const issueCommentFetch = await this.fetchWithFailure(() => this.gitHubGateway.listIssueComments(repo.owner, repo.name, rawIssue.number), repo.id, repoMetadata.nameWithOwner, "issue_comments", `issue#${rawIssue.number}`, repoFailures);
|
|
65
|
+
const issueComments = issueCommentFetch.value ?? [];
|
|
66
|
+
const normalizedComments = normalizeIssueComments(issueComments, rawIssue.number, "issue", "issue_comment", {
|
|
67
|
+
repo,
|
|
68
|
+
repoMetadata,
|
|
69
|
+
seenAt,
|
|
70
|
+
previousIssues,
|
|
71
|
+
previousPullRequests,
|
|
72
|
+
previousComments
|
|
73
|
+
});
|
|
74
|
+
const carriedComments = issueCommentFetch.failed
|
|
75
|
+
? commentsForParent(previousState.comments, repo.id, "issue", rawIssue.number)
|
|
76
|
+
: [];
|
|
77
|
+
const mergedComments = mergeById(normalizedComments, carriedComments).sort(compareComments);
|
|
78
|
+
const normalizedIssue = normalizeIssue(rawIssue, mergedComments, {
|
|
79
|
+
repo,
|
|
80
|
+
repoMetadata,
|
|
81
|
+
seenAt,
|
|
82
|
+
previousIssues,
|
|
83
|
+
previousPullRequests,
|
|
84
|
+
previousComments
|
|
85
|
+
});
|
|
86
|
+
issues.push(normalizedIssue);
|
|
87
|
+
comments.push(...mergedComments);
|
|
88
|
+
repoIssueCount += 1;
|
|
89
|
+
repoCommentCount += mergedComments.length;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (pullRequestListFetch.failed) {
|
|
93
|
+
const carriedPrs = entitiesForRepo(previousState.prs, repo.id);
|
|
94
|
+
const carriedPrComments = commentsForParentType(previousState.comments, repo.id, "pull_request");
|
|
95
|
+
prs.push(...carriedPrs);
|
|
96
|
+
comments.push(...carriedPrComments);
|
|
97
|
+
repoPrCount += carriedPrs.length;
|
|
98
|
+
repoCommentCount += carriedPrComments.length;
|
|
99
|
+
}
|
|
100
|
+
if (!pullRequestListFetch.failed) {
|
|
101
|
+
for (const rawPr of rawPullRequests) {
|
|
102
|
+
const [prCommentsFetch, prReviewsFetch, prReviewCommentsFetch] = await Promise.all([
|
|
103
|
+
this.fetchWithFailure(() => this.gitHubGateway.listPullRequestComments(repo.owner, repo.name, rawPr.number), repo.id, repoMetadata.nameWithOwner, "pull_request_comments", `pull_request#${rawPr.number}`, repoFailures),
|
|
104
|
+
this.fetchWithFailure(() => this.gitHubGateway.listPullRequestReviews(repo.owner, repo.name, rawPr.number), repo.id, repoMetadata.nameWithOwner, "pull_request_reviews", `pull_request#${rawPr.number}`, repoFailures),
|
|
105
|
+
this.fetchWithFailure(() => this.gitHubGateway.listPullRequestReviewComments(repo.owner, repo.name, rawPr.number), repo.id, repoMetadata.nameWithOwner, "pull_request_review_comments", `pull_request#${rawPr.number}`, repoFailures)
|
|
106
|
+
]);
|
|
107
|
+
const normalizedComments = [
|
|
108
|
+
...normalizeIssueComments(prCommentsFetch.value ?? [], rawPr.number, "pull_request", "pull_request_comment", {
|
|
109
|
+
repo,
|
|
110
|
+
repoMetadata,
|
|
111
|
+
seenAt,
|
|
112
|
+
previousIssues,
|
|
113
|
+
previousPullRequests,
|
|
114
|
+
previousComments
|
|
115
|
+
}),
|
|
116
|
+
...normalizePullRequestReviews(prReviewsFetch.value ?? [], rawPr.number, {
|
|
117
|
+
repo,
|
|
118
|
+
repoMetadata,
|
|
119
|
+
seenAt,
|
|
120
|
+
previousIssues,
|
|
121
|
+
previousPullRequests,
|
|
122
|
+
previousComments
|
|
123
|
+
}),
|
|
124
|
+
...normalizePullRequestReviewComments(prReviewCommentsFetch.value ?? [], rawPr.number, {
|
|
125
|
+
repo,
|
|
126
|
+
repoMetadata,
|
|
127
|
+
seenAt,
|
|
128
|
+
previousIssues,
|
|
129
|
+
previousPullRequests,
|
|
130
|
+
previousComments
|
|
131
|
+
})
|
|
132
|
+
].sort((left, right) => left.id.localeCompare(right.id));
|
|
133
|
+
const carriedComments = prCommentsFetch.failed || prReviewsFetch.failed || prReviewCommentsFetch.failed
|
|
134
|
+
? commentsForParent(previousState.comments, repo.id, "pull_request", rawPr.number)
|
|
135
|
+
: [];
|
|
136
|
+
const mergedComments = mergeById(normalizedComments, carriedComments).sort(compareComments);
|
|
137
|
+
const normalizedPr = normalizePullRequest(rawPr, mergedComments, {
|
|
138
|
+
repo,
|
|
139
|
+
repoMetadata,
|
|
140
|
+
seenAt,
|
|
141
|
+
previousIssues,
|
|
142
|
+
previousPullRequests,
|
|
143
|
+
previousComments
|
|
144
|
+
});
|
|
145
|
+
prs.push(normalizedPr);
|
|
146
|
+
comments.push(...mergedComments);
|
|
147
|
+
repoPrCount += 1;
|
|
148
|
+
repoCommentCount += mergedComments.length;
|
|
149
|
+
}
|
|
150
|
+
const trackedMissingPullRequests = await this.fetchTrackedMissingPullRequests({
|
|
151
|
+
repo,
|
|
152
|
+
repoMetadata,
|
|
153
|
+
seenAt,
|
|
154
|
+
previousState,
|
|
155
|
+
previousIssues,
|
|
156
|
+
previousPullRequests,
|
|
157
|
+
previousComments,
|
|
158
|
+
rawPullRequestsByNumber,
|
|
159
|
+
failures: repoFailures
|
|
160
|
+
});
|
|
161
|
+
for (const normalizedPr of trackedMissingPullRequests) {
|
|
162
|
+
prs.push(normalizedPr);
|
|
163
|
+
repoPrCount += 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
failures.push(...repoFailures);
|
|
167
|
+
repoResult.githubScan = {
|
|
168
|
+
status: repoFailures.length === 0
|
|
169
|
+
? "scanned"
|
|
170
|
+
: repoIssueCount === 0 && repoPrCount === 0
|
|
171
|
+
? "failed"
|
|
172
|
+
: "partial",
|
|
173
|
+
scannedAt: seenAt,
|
|
174
|
+
issueCount: repoIssueCount,
|
|
175
|
+
pullRequestCount: repoPrCount,
|
|
176
|
+
commentCount: repoCommentCount,
|
|
177
|
+
failures: repoFailures
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
issues: issues.sort(compareEntities),
|
|
182
|
+
prs: prs.sort(compareEntities),
|
|
183
|
+
comments: comments.sort(compareComments),
|
|
184
|
+
repoResults,
|
|
185
|
+
failures
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
async fetchRepositoryMetadata(repo, repoResult, failures) {
|
|
189
|
+
try {
|
|
190
|
+
return await this.gitHubGateway.getRepositoryMetadata(repo.owner, repo.name);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
failures.push({
|
|
194
|
+
repoId: repo.id,
|
|
195
|
+
repoSlug: repoResult.repoSlug,
|
|
196
|
+
step: "repo_metadata",
|
|
197
|
+
scope: "repo",
|
|
198
|
+
message: error instanceof Error ? error.message : String(error)
|
|
199
|
+
});
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async fetchWithFailure(fetcher, repoId, repoSlug, step, scope, failures) {
|
|
204
|
+
try {
|
|
205
|
+
return { value: await fetcher(), failed: false };
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
failures.push({
|
|
209
|
+
repoId,
|
|
210
|
+
repoSlug,
|
|
211
|
+
step,
|
|
212
|
+
scope,
|
|
213
|
+
message: error instanceof Error ? error.message : String(error)
|
|
214
|
+
});
|
|
215
|
+
return { failed: true };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async fetchTrackedMissingPullRequests(input) {
|
|
219
|
+
const trackedNumbers = collectTrackedPullRequestNumbers(input.repo.id, input.repoMetadata.nameWithOwner, input.previousState);
|
|
220
|
+
const missingNumbers = [...trackedNumbers]
|
|
221
|
+
.filter((number) => !input.rawPullRequestsByNumber.has(number))
|
|
222
|
+
.sort((left, right) => left - right);
|
|
223
|
+
const fetched = [];
|
|
224
|
+
for (const prNumber of missingNumbers) {
|
|
225
|
+
const fetch = await this.fetchWithFailure(() => this.gitHubGateway.getPullRequest(input.repo.owner, input.repo.name, prNumber), input.repo.id, input.repoMetadata.nameWithOwner, "get_pull_request", `pull_request#${prNumber}`, input.failures);
|
|
226
|
+
if (fetch.value) {
|
|
227
|
+
fetched.push(normalizePullRequest(fetch.value, commentsForParent(input.previousState.comments, input.repo.id, "pull_request", prNumber), {
|
|
228
|
+
repo: input.repo,
|
|
229
|
+
repoMetadata: input.repoMetadata,
|
|
230
|
+
seenAt: input.seenAt,
|
|
231
|
+
previousIssues: input.previousIssues,
|
|
232
|
+
previousPullRequests: input.previousPullRequests,
|
|
233
|
+
previousComments: input.previousComments
|
|
234
|
+
}));
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const previousTrackedPr = input.previousPullRequests.get(`pull_request:${input.repoMetadata.nameWithOwner}:${prNumber}`);
|
|
238
|
+
if (previousTrackedPr) {
|
|
239
|
+
fetched.push(previousTrackedPr);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return fetched;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function collectTrackedPullRequestNumbers(repoId, repoSlug, previousState) {
|
|
246
|
+
const numbers = new Set();
|
|
247
|
+
const mergedPacketPrNumbers = new Set();
|
|
248
|
+
for (const workstream of previousState.workstreams) {
|
|
249
|
+
if (workstream.repoKey !== repoSlug) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
for (const packet of Object.values(workstream.packetPlan?.packets ?? {})) {
|
|
253
|
+
if (packet.prNumber === undefined) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (packet.status === "merged_to_parent") {
|
|
257
|
+
mergedPacketPrNumbers.add(packet.prNumber);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
numbers.add(packet.prNumber);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Collect PR numbers already belonging to terminal workstreams so we don't
|
|
265
|
+
// re-fetch them — once a workstream is closed/merged its PR state is final.
|
|
266
|
+
const terminalPrNumbers = new Set();
|
|
267
|
+
for (const workstream of previousState.workstreams) {
|
|
268
|
+
if (workstream.repoKey !== repoSlug) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (workstream.status === "closed" || workstream.status === "merged" || workstream.resolution === "merged") {
|
|
272
|
+
for (const linkedPr of workstream.linkedPrs) {
|
|
273
|
+
terminalPrNumbers.add(linkedPr.number);
|
|
274
|
+
}
|
|
275
|
+
if (workstream.activePrNumber !== undefined) {
|
|
276
|
+
terminalPrNumbers.add(workstream.activePrNumber);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const worktree of previousState.worktrees) {
|
|
281
|
+
if (worktree.repoId !== repoId || worktree.prNumber === undefined) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (worktree.packet) {
|
|
285
|
+
if (!mergedPacketPrNumbers.has(worktree.prNumber)) {
|
|
286
|
+
numbers.add(worktree.prNumber);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (!terminalPrNumbers.has(worktree.prNumber)) {
|
|
290
|
+
// Non-packet (integration) worktree PRs are not covered by the packet plan
|
|
291
|
+
// loop above, so track them until their merged state is observed.
|
|
292
|
+
numbers.add(worktree.prNumber);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return numbers;
|
|
296
|
+
}
|
|
297
|
+
function entitiesForRepo(items, repoId) {
|
|
298
|
+
return items.filter((item) => item.repoId === repoId);
|
|
299
|
+
}
|
|
300
|
+
function commentsForParentType(comments, repoId, parentType) {
|
|
301
|
+
return comments.filter((comment) => comment.repoId === repoId && comment.parentType === parentType);
|
|
302
|
+
}
|
|
303
|
+
function commentsForParent(comments, repoId, parentType, parentNumber) {
|
|
304
|
+
return comments.filter((comment) => comment.repoId === repoId &&
|
|
305
|
+
comment.parentType === parentType &&
|
|
306
|
+
comment.parentNumber === parentNumber);
|
|
307
|
+
}
|
|
308
|
+
function mergeById(fresh, carried) {
|
|
309
|
+
const byId = new Map();
|
|
310
|
+
for (const item of carried) {
|
|
311
|
+
byId.set(item.id, item);
|
|
312
|
+
}
|
|
313
|
+
for (const item of fresh) {
|
|
314
|
+
byId.set(item.id, item);
|
|
315
|
+
}
|
|
316
|
+
return [...byId.values()];
|
|
317
|
+
}
|
|
318
|
+
function compareEntities(left, right) {
|
|
319
|
+
if (left.repoSlug !== right.repoSlug) {
|
|
320
|
+
return left.repoSlug.localeCompare(right.repoSlug);
|
|
321
|
+
}
|
|
322
|
+
return left.number - right.number;
|
|
323
|
+
}
|
|
324
|
+
function compareComments(left, right) {
|
|
325
|
+
if (left.repoSlug !== right.repoSlug) {
|
|
326
|
+
return left.repoSlug.localeCompare(right.repoSlug);
|
|
327
|
+
}
|
|
328
|
+
if (left.parentNumber !== right.parentNumber) {
|
|
329
|
+
return left.parentNumber - right.parentNumber;
|
|
330
|
+
}
|
|
331
|
+
return left.id.localeCompare(right.id);
|
|
332
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function parseGitHubRemoteUrl(url) {
|
|
2
|
+
// git@github.com:owner/name.git
|
|
3
|
+
// https://github.com/owner/name.git
|
|
4
|
+
// https://github.com/owner/name
|
|
5
|
+
const sshRegex = /^git@github\.com:([^/]+)\/([^/]+)\.git$/;
|
|
6
|
+
const httpsRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/;
|
|
7
|
+
const sshMatch = url.match(sshRegex);
|
|
8
|
+
if (sshMatch) {
|
|
9
|
+
return { owner: sshMatch[1], name: sshMatch[2] };
|
|
10
|
+
}
|
|
11
|
+
const httpsMatch = url.match(httpsRegex);
|
|
12
|
+
if (httpsMatch) {
|
|
13
|
+
return { owner: httpsMatch[1], name: httpsMatch[2] };
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export class JournalReader {
|
|
4
|
+
journalPath;
|
|
5
|
+
constructor(journalDir) {
|
|
6
|
+
this.journalPath = path.join(journalDir, "journal.jsonl");
|
|
7
|
+
}
|
|
8
|
+
async read() {
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = await readFile(this.journalPath, "utf8");
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error.code === "ENOENT") {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
return raw
|
|
20
|
+
.split(/\r?\n/)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((line) => JSON.parse(line));
|
|
23
|
+
}
|
|
24
|
+
async query(q = {}) {
|
|
25
|
+
let entries = await this.read();
|
|
26
|
+
if (q.repoId !== undefined) {
|
|
27
|
+
entries = entries.filter((e) => e.repoId === q.repoId);
|
|
28
|
+
}
|
|
29
|
+
if (q.workstreamId !== undefined) {
|
|
30
|
+
entries = entries.filter((e) => e.workstreamId === q.workstreamId);
|
|
31
|
+
}
|
|
32
|
+
if (q.issueNumber !== undefined) {
|
|
33
|
+
entries = entries.filter((e) => e.issueNumber === q.issueNumber);
|
|
34
|
+
}
|
|
35
|
+
if (q.prNumber !== undefined) {
|
|
36
|
+
entries = entries.filter((e) => e.prNumber === q.prNumber);
|
|
37
|
+
}
|
|
38
|
+
if (q.cycleId !== undefined) {
|
|
39
|
+
entries = entries.filter((e) => e.cycleId === q.cycleId);
|
|
40
|
+
}
|
|
41
|
+
if (q.executionId !== undefined) {
|
|
42
|
+
entries = entries.filter((e) => e.executionId === q.executionId);
|
|
43
|
+
}
|
|
44
|
+
if (q.scope !== undefined) {
|
|
45
|
+
entries = entries.filter((e) => e.scope === q.scope);
|
|
46
|
+
}
|
|
47
|
+
if (q.category !== undefined) {
|
|
48
|
+
entries = entries.filter((e) => e.category === q.category);
|
|
49
|
+
}
|
|
50
|
+
if (q.severity !== undefined) {
|
|
51
|
+
entries = entries.filter((e) => e.severity === q.severity);
|
|
52
|
+
}
|
|
53
|
+
if (q.since !== undefined) {
|
|
54
|
+
const sinceMs = new Date(q.since).getTime();
|
|
55
|
+
entries = entries.filter((e) => new Date(e.timestamp).getTime() >= sinceMs);
|
|
56
|
+
}
|
|
57
|
+
if (q.limit !== undefined) {
|
|
58
|
+
entries = entries.slice(-q.limit);
|
|
59
|
+
}
|
|
60
|
+
return entries;
|
|
61
|
+
}
|
|
62
|
+
}
|