dsh-pstack 0.2.0-beta.1 → 0.2.0-beta.3
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 +2 -3
- package/README.md +135 -84
- package/README.zh.md +185 -0
- package/cordis.patch.yml +97 -27
- package/dist/host-contract-CUPSpeNk.d.mts +22 -0
- package/dist/index.d.mts +49 -238
- package/dist/index.mjs +250 -123
- package/dist/models.d.mts +44 -0
- package/dist/models.mjs +201 -0
- package/dist/paths-BT77khwm.mjs +48 -0
- package/dist/pstack/LICENSE +21 -0
- package/dist/pstack/README.md +291 -0
- package/dist/pstack/agents/poteto-agent.md +9 -0
- package/{assets → dist/pstack}/docs/guide/01-setup.md +13 -11
- package/{assets → dist/pstack}/docs/guide/02-poteto-mode.md +4 -4
- package/{assets → dist/pstack}/docs/guide/04-design.md +4 -4
- package/{assets → dist/pstack}/docs/guide/05-build-and-clean.md +2 -2
- package/{assets → dist/pstack}/docs/guide/06-verify-and-ship.md +13 -3
- package/{assets → dist/pstack}/docs/guide/07-overnight.md +15 -13
- package/{assets → dist/pstack}/docs/guide/09-make-it-yours.md +6 -2
- package/{assets → dist/pstack}/docs/guide/10-recipes-and-pitfalls.md +7 -5
- package/{assets → dist/pstack}/docs/guide/README.md +1 -1
- package/dist/pstack/docs/harness/dsh.md +98 -0
- package/{assets → dist/pstack}/skills/architect/SKILL.md +12 -12
- package/dist/pstack/skills/arena/SKILL.md +50 -0
- package/{assets → dist/pstack}/skills/automate-me/SKILL.md +14 -14
- package/{assets → dist/pstack}/skills/benchmark-checklist/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/blast-radius/SKILL.md +4 -6
- package/{assets → dist/pstack}/skills/correct/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/create-formal-verification-skill/SKILL.md +4 -2
- package/{assets → dist/pstack}/skills/create-verification-skill/SKILL.md +5 -5
- package/dist/pstack/skills/figure-it-out/SKILL.md +54 -0
- package/dist/pstack/skills/how/SKILL.md +38 -0
- package/{assets → dist/pstack}/skills/in-your-own-words/SKILL.md +1 -1
- package/dist/pstack/skills/interrogate/SKILL.md +33 -0
- package/{assets → dist/pstack}/skills/ketchup/SKILL.md +3 -5
- package/dist/pstack/skills/ketchup-video/SKILL.md +29 -0
- package/{assets → dist/pstack}/skills/maintain-formal-verification-skill/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/maintain-verification-skill/SKILL.md +3 -3
- package/dist/pstack/skills/make-bot-ui/SKILL.md +117 -0
- package/dist/pstack/skills/no-comments/SKILL.md +26 -0
- package/{assets → dist/pstack}/skills/poteto-help/SKILL.md +34 -31
- package/{assets → dist/pstack}/skills/poteto-help/references/prompting.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-help/references/recipes.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/SKILL.md +20 -19
- package/dist/pstack/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/babysit.md +4 -4
- package/dist/pstack/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/eval.md +3 -3
- package/dist/pstack/skills/poteto-mode/playbooks/feature.md +21 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/hillclimb.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/investigation.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/multi-phase-plan.md +23 -23
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
- package/dist/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/pause-safely.md +2 -2
- package/dist/pstack/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/prototype.md +4 -4
- package/dist/pstack/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/dist/pstack/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/dist/pstack/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/shipping.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
- package/dist/pstack/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/dist/pstack/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/check-plan.mjs +6 -8
- package/dist/pstack/skills/poteto-mode/scripts/check-plan.test.mjs +128 -0
- package/{assets/skills/poteto-mode/scripts/check-plan.test.mjs → dist/pstack/skills/poteto-mode/scripts/expect.mjs} +12 -101
- package/dist/pstack/skills/poteto-mode/scripts/support.mjs +93 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +123 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +217 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +119 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +626 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +521 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +657 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +523 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +106 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +9 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +5 -0
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/worktree-audit.mjs +15 -22
- package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +134 -0
- package/{assets → dist/pstack}/skills/principle-attack-the-premise/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-boundary-discipline/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-encode-lessons-in-structure/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-exhaust-the-design-space/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-experience-first/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-explain-the-number/SKILL.md +3 -2
- package/{assets → dist/pstack}/skills/principle-fix-root-causes/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-laziness-protocol/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-make-operations-idempotent/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-minimize-reader-load/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-model-the-domain/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-never-block-on-the-human/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-outcome-oriented-execution/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-prove-it-works/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/principle-redesign-from-first-principles/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-separate-before-serializing-shared-state/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-sequence-verifiable-units/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-subtract-before-you-add/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-test-behavior-not-implementation/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-type-system-discipline/SKILL.md +1 -1
- package/dist/pstack/skills/recall/SKILL.md +37 -0
- package/dist/pstack/skills/reflect/SKILL.md +71 -0
- package/{assets → dist/pstack}/skills/reflect/references/divergent-reviewer.md +4 -4
- package/{assets → dist/pstack}/skills/reflect/references/judgment-reviewer.md +2 -2
- package/{assets → dist/pstack}/skills/reflect/references/synthesizer.md +5 -5
- package/{assets → dist/pstack}/skills/reflect/references/tooling-reviewer.md +2 -2
- package/dist/pstack/skills/setup-pstack/SKILL.md +80 -0
- package/{assets → dist/pstack}/skills/sharpen/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/show-me-your-work/SKILL.md +8 -8
- package/{assets → dist/pstack}/skills/show-me-your-work/scripts/log.mjs +1 -3
- package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +56 -0
- package/dist/pstack/skills/statechart/SKILL.md +82 -0
- package/dist/pstack/skills/statechart/references/render.md +93 -0
- package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +31 -0
- package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +186 -0
- package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +62 -0
- package/dist/pstack/skills/statechart/scripts/backends/report.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +67 -0
- package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +94 -0
- package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +48 -0
- package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +101 -0
- package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +39 -0
- package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +180 -0
- package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +40 -0
- package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +99 -0
- package/dist/pstack/skills/statechart/scripts/ir.mjs +277 -0
- package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +92 -0
- package/dist/pstack/skills/statechart/scripts/passes/label.mjs +210 -0
- package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +47 -0
- package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +135 -0
- package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +82 -0
- package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/pipeline.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/run.mjs +440 -0
- package/dist/pstack/skills/statechart/scripts/statechart.mjs +155 -0
- package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +198 -0
- package/dist/pstack/skills/statechart/scripts/worker.mjs +31 -0
- package/dist/pstack/skills/swarm/SKILL.md +43 -0
- package/{assets → dist/pstack}/skills/tdd/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/teach/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/technical-writing/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/typescript-best-practices/SKILL.md +3 -6
- package/{assets → dist/pstack}/skills/why/SKILL.md +8 -8
- package/dist/script.d.mts +9 -111
- package/dist/script.mjs +32 -329
- package/dist/skill.d.mts +38 -0
- package/dist/skill.mjs +183 -0
- package/dist/skills/authored/decision-audit/SKILL.md +99 -0
- package/dist/skills/authored/decision-audit/SKILL.zh.md +99 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.md +17 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +17 -0
- package/dist/skills/authored/decision-write/SKILL.md +131 -0
- package/dist/skills/authored/decision-write/SKILL.zh.md +131 -0
- package/dist/skills/authored/decision-write/references/land.md +48 -0
- package/dist/skills/authored/decision-write/references/land.zh.md +48 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +58 -0
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +58 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +11 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +11 -0
- package/dist/skills/vendored/retro/SKILL.md +44 -0
- package/dist/skills/vendored/retro/agents/openai.yaml +5 -0
- package/dist/skills/vendored/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/dist/skills/vendored/writing-for-agents/SKILL.md +81 -0
- package/dist/skills/vendored/writing-for-agents/agents/openai.yaml +3 -0
- package/dist/subagent.d.mts +56 -193
- package/dist/subagent.mjs +443 -741
- package/locale/en.json +3 -2
- package/locale/zh.json +3 -2
- package/package.json +63 -66
- package/assets/agents/poteto-agent.md +0 -8
- package/assets/docs/harness/dsh.md +0 -60
- package/assets/docs/pstack-models.example.md +0 -54
- package/assets/skills/arena/SKILL.md +0 -50
- package/assets/skills/figure-it-out/SKILL.md +0 -54
- package/assets/skills/how/SKILL.md +0 -38
- package/assets/skills/interrogate/SKILL.md +0 -71
- package/assets/skills/interrogate/references/code-quality-review.md +0 -47
- package/assets/skills/interrogate/references/lead-judgment.md +0 -39
- package/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
- package/assets/skills/interrogate/references/rubric.md +0 -77
- package/assets/skills/no-comments/SKILL.md +0 -26
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -15
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
- package/assets/skills/poteto-mode/playbooks/feature.md +0 -21
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
- package/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -20
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +0 -4282
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +0 -2042
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +0 -5101
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +0 -1177
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +0 -1441
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +0 -4601
- package/assets/skills/recall/SKILL.md +0 -37
- package/assets/skills/reflect/SKILL.md +0 -69
- package/assets/skills/setup-pstack/SKILL.md +0 -67
- package/assets/skills/swarm/SKILL.md +0 -43
- package/dist/frontmatter-BQX3ekwP.mjs +0 -229
- package/dist/frontmatter-BQX3ekwP.mjs.map +0 -1
- package/dist/history.d.mts +0 -429
- package/dist/history.mjs +0 -1129
- package/dist/history.mjs.map +0 -1
- package/dist/host-contract-o47JH_r8.d.mts +0 -527
- package/dist/index.mjs.map +0 -1
- package/dist/paths-D_JlVmBk.mjs +0 -42
- package/dist/paths-D_JlVmBk.mjs.map +0 -1
- package/dist/script.mjs.map +0 -1
- package/dist/subagent.mjs.map +0 -1
- /package/{static/icon.png → assets/logo.png} +0 -0
- /package/{assets → dist/pstack}/agents/comment-sicko.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/03-understand.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/08-principles.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/design.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/overnight.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/recipes.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/router.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/understanding.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/verification.jpg +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/design-red-flags.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/rationale-template.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/runner-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/bro/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/README.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/search.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explainer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explorer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/references/bugbot-triage.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-build-the-lever/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-foundational-thinking/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-guard-the-context-window/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/show-me-your-work/references/decision-log-template.tsv +0 -0
- /package/{assets → dist/pstack}/skills/typescript-best-practices/references/patterns.md +0 -0
- /package/{assets → dist/pstack}/skills/unslop/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/epistemics.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/investigator-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/source-playbook.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/code-archaeology.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/databricks.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/datadog.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/incident-postmortem.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/linear.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/notion.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/sentry.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/slack.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/synthesizer-prompt.md +0 -0
|
@@ -1,1177 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import { inspect } from "node:util";
|
|
4
|
-
//#region .runtime-sources/skills/poteto-mode/scripts/expect.ts
|
|
5
|
-
/**
|
|
6
|
-
* A small `expect` for `node:test`.
|
|
7
|
-
*
|
|
8
|
-
* The five upstream test files were written against `bun:test`. Their assertions
|
|
9
|
-
* are ordinary value comparisons, so this module supplies exactly the matchers
|
|
10
|
-
* they use rather than a general test framework. It is deliberately not a drop-in
|
|
11
|
-
* replacement: a matcher that is not listed here fails to compile, which keeps the
|
|
12
|
-
* port auditable against the calls that actually exist.
|
|
13
|
-
*
|
|
14
|
-
* Covered: `toBe`, `toEqual`, `toContain`, `toMatch`, `toThrow`, `toBeNull`,
|
|
15
|
-
* `toBeInstanceOf`, `toHaveLength`, `toEndWith`, `toMatchObject`, `.not` on each,
|
|
16
|
-
* and `rejects` / `resolves` for promises.
|
|
17
|
-
*
|
|
18
|
-
* Two differences from `bun:test`, stated so a failure is not misread:
|
|
19
|
-
*
|
|
20
|
-
* - `toEqual` is structural for arrays and plain objects, `Object.is` otherwise.
|
|
21
|
-
* A class instance only equals itself; `toBeInstanceOf` is the matcher for type
|
|
22
|
-
* identity. The ported tests compare plain data, which is why this is enough.
|
|
23
|
-
* - A failure throws `AssertionError` with the two values rendered, not Bun's
|
|
24
|
-
* coloured diff.
|
|
25
|
-
*/
|
|
26
|
-
/** Raised by every failing assertion. */
|
|
27
|
-
var AssertionError = class extends Error {
|
|
28
|
-
constructor(message) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.name = "AssertionError";
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
function display(value) {
|
|
34
|
-
if (typeof value === "function") return `[Function ${value.name || "anonymous"}]`;
|
|
35
|
-
return inspect(value, {
|
|
36
|
-
depth: 3,
|
|
37
|
-
breakLength: 120,
|
|
38
|
-
maxArrayLength: 20
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function messageOf(error) {
|
|
42
|
-
if (error instanceof Error) return `${error.name}: ${error.message}`;
|
|
43
|
-
return String(error);
|
|
44
|
-
}
|
|
45
|
-
/** Structural equality for the shapes the ported tests compare. */
|
|
46
|
-
function deepEqual(left, right) {
|
|
47
|
-
if (Object.is(left, right)) return true;
|
|
48
|
-
if (left instanceof Date || right instanceof Date) return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
|
|
49
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
50
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
|
|
51
|
-
return left.every((item, index) => deepEqual(item, right[index]));
|
|
52
|
-
}
|
|
53
|
-
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") return false;
|
|
54
|
-
const leftProto = Object.getPrototypeOf(left);
|
|
55
|
-
if (leftProto !== Object.getPrototypeOf(right)) return false;
|
|
56
|
-
if (leftProto !== Object.prototype && leftProto !== null) return false;
|
|
57
|
-
const leftKeys = Object.keys(left);
|
|
58
|
-
const rightKeys = Object.keys(right);
|
|
59
|
-
if (leftKeys.length !== rightKeys.length) return false;
|
|
60
|
-
return leftKeys.every((key) => deepEqual(left[key], right[key]));
|
|
61
|
-
}
|
|
62
|
-
function contains(haystack, needle) {
|
|
63
|
-
if (typeof haystack === "string") return haystack.includes(String(needle));
|
|
64
|
-
if (Array.isArray(haystack)) return haystack.some((item) => deepEqual(item, needle));
|
|
65
|
-
if (haystack instanceof Set) return [...haystack].some((item) => deepEqual(item, needle));
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Whether `expected` is a partial description of `actual`.
|
|
70
|
-
*
|
|
71
|
-
* `bun:test`'s `toMatchObject` is recursive: every key in `expected` must exist and
|
|
72
|
-
* match, arrays match element by element with the same partial rule, and extra keys
|
|
73
|
-
* in `actual` are allowed. That is what lets an assertion name only the fields it
|
|
74
|
-
* cares about, which several ported cases rely on.
|
|
75
|
-
*/
|
|
76
|
-
function matchesObject(actual, expected) {
|
|
77
|
-
if (Array.isArray(expected)) {
|
|
78
|
-
if (!Array.isArray(actual) || actual.length !== expected.length) return false;
|
|
79
|
-
return expected.every((item, index) => matchesObject(actual[index], item));
|
|
80
|
-
}
|
|
81
|
-
if (expected instanceof Date) return deepEqual(actual, expected);
|
|
82
|
-
if (expected === null || typeof expected !== "object") return deepEqual(actual, expected);
|
|
83
|
-
if (actual === null || typeof actual !== "object") return false;
|
|
84
|
-
return Object.entries(expected).every(([key, value]) => matchesObject(actual[key], value));
|
|
85
|
-
}
|
|
86
|
-
function thrownBy(call) {
|
|
87
|
-
try {
|
|
88
|
-
call();
|
|
89
|
-
return { thrown: false };
|
|
90
|
-
} catch (error) {
|
|
91
|
-
return {
|
|
92
|
-
thrown: true,
|
|
93
|
-
error
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function buildMatchers(actual, negate) {
|
|
98
|
-
/**
|
|
99
|
-
* Fail when `ok` is not what the matcher asked for.
|
|
100
|
-
*
|
|
101
|
-
* `negate` inverts the requirement, so `.not.toContain(x)` passes on a haystack
|
|
102
|
-
* that does not contain `x` and fails when it does.
|
|
103
|
-
*/
|
|
104
|
-
const check = (ok, matcher, expected) => {
|
|
105
|
-
if (ok === !negate) return;
|
|
106
|
-
const relation = negate ? `not to ${matcher}` : matcher;
|
|
107
|
-
throw new AssertionError(`expected ${display(actual)} ${relation} ${display(expected)}`);
|
|
108
|
-
};
|
|
109
|
-
const checkPlain = (ok, matcher) => {
|
|
110
|
-
if (ok === !negate) return;
|
|
111
|
-
throw new AssertionError(`expected ${display(actual)} ${negate ? `not to ${matcher}` : matcher}`);
|
|
112
|
-
};
|
|
113
|
-
const matchers = {
|
|
114
|
-
toBe(expected) {
|
|
115
|
-
check(Object.is(actual, expected), "toBe", expected);
|
|
116
|
-
},
|
|
117
|
-
toEqual(expected) {
|
|
118
|
-
check(deepEqual(actual, expected), "toEqual", expected);
|
|
119
|
-
},
|
|
120
|
-
toContain(expected) {
|
|
121
|
-
check(contains(actual, expected), "toContain", expected);
|
|
122
|
-
},
|
|
123
|
-
toMatch(expected) {
|
|
124
|
-
const text = String(actual);
|
|
125
|
-
check(typeof expected === "string" ? text.includes(expected) : expected.test(text), "toMatch", expected);
|
|
126
|
-
},
|
|
127
|
-
toThrow(expected) {
|
|
128
|
-
if (typeof actual !== "function") throw new AssertionError(`toThrow needs a function, received ${display(actual)}`);
|
|
129
|
-
const result = thrownBy(actual);
|
|
130
|
-
if (!result.thrown) {
|
|
131
|
-
checkPlain(false, "toThrow");
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
if (expected === void 0) {
|
|
135
|
-
checkPlain(true, "toThrow");
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const message = messageOf(result.error);
|
|
139
|
-
const ok = typeof expected === "string" ? message.includes(expected) : expected instanceof RegExp ? expected.test(message) : result.error instanceof expected;
|
|
140
|
-
check(ok, "toThrow", expected);
|
|
141
|
-
},
|
|
142
|
-
toBeNull() {
|
|
143
|
-
checkPlain(actual === null, "toBeNull");
|
|
144
|
-
},
|
|
145
|
-
toBeInstanceOf(expected) {
|
|
146
|
-
check(actual instanceof expected, "toBeInstanceOf", expected);
|
|
147
|
-
},
|
|
148
|
-
toHaveLength(expected) {
|
|
149
|
-
const length = actual?.length;
|
|
150
|
-
check(length === expected, "toHaveLength", expected);
|
|
151
|
-
},
|
|
152
|
-
toEndWith(expected) {
|
|
153
|
-
check(String(actual).endsWith(expected), "toEndWith", expected);
|
|
154
|
-
},
|
|
155
|
-
toMatchObject(expected) {
|
|
156
|
-
check(matchesObject(actual, expected), "toMatchObject", expected);
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
Object.defineProperties(matchers, {
|
|
160
|
-
not: {
|
|
161
|
-
get: () => buildMatchers(actual, !negate),
|
|
162
|
-
enumerable: true
|
|
163
|
-
},
|
|
164
|
-
rejects: {
|
|
165
|
-
get: () => buildAsyncMatchers(actual, "rejects", negate),
|
|
166
|
-
enumerable: true
|
|
167
|
-
},
|
|
168
|
-
resolves: {
|
|
169
|
-
get: () => buildAsyncMatchers(actual, "resolves", negate),
|
|
170
|
-
enumerable: true
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
return matchers;
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Compare against a settled promise.
|
|
177
|
-
*
|
|
178
|
-
* `rejects` reads the rejection reason and fails when the promise resolved;
|
|
179
|
-
* `resolves` is the mirror. Each matcher awaits the settlement, which is what
|
|
180
|
-
* makes `await expect(promise).rejects.toThrow(...)` wait before it compares.
|
|
181
|
-
*/
|
|
182
|
-
function buildAsyncMatchers(actual, mode, negate) {
|
|
183
|
-
if (typeof actual?.then !== "function") throw new AssertionError(`expected a promise, received ${display(actual)}`);
|
|
184
|
-
const wantRejection = mode === "rejects" !== negate;
|
|
185
|
-
const settle = async () => {
|
|
186
|
-
let resolved;
|
|
187
|
-
let rejected;
|
|
188
|
-
await Promise.resolve(actual).then((value) => {
|
|
189
|
-
resolved = { value };
|
|
190
|
-
}, (error) => {
|
|
191
|
-
rejected = { error };
|
|
192
|
-
});
|
|
193
|
-
if (wantRejection) {
|
|
194
|
-
if (resolved !== void 0) throw new AssertionError(`expected the promise to reject, it resolved to ${display(resolved.value)}`);
|
|
195
|
-
return rejected?.error;
|
|
196
|
-
}
|
|
197
|
-
if (rejected !== void 0) throw new AssertionError(`expected the promise to resolve, it rejected with ${messageOf(rejected.error)}`);
|
|
198
|
-
return resolved?.value;
|
|
199
|
-
};
|
|
200
|
-
/**
|
|
201
|
-
* Run one matcher against the settled value.
|
|
202
|
-
*
|
|
203
|
-
* `toThrow` and `toBeInstanceOf` both read the rejection reason, so a thrown
|
|
204
|
-
* reason is turned back into a thunk for `toThrow` rather than special-cased
|
|
205
|
-
* twice. `negate` is already folded into the settlement mode, so the inner
|
|
206
|
-
* matchers run un-negated.
|
|
207
|
-
*/
|
|
208
|
-
const wrap = async (name, expected) => {
|
|
209
|
-
const subject = await settle();
|
|
210
|
-
if (name === "toThrow") {
|
|
211
|
-
buildMatchers(() => {
|
|
212
|
-
throw subject;
|
|
213
|
-
}, false).toThrow(expected);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (name === "toBeNull") {
|
|
217
|
-
buildMatchers(subject, false).toBeNull();
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const matchers = buildMatchers(subject, false);
|
|
221
|
-
const matcher = matchers[name];
|
|
222
|
-
if (typeof matcher !== "function") throw new AssertionError(`unknown matcher ${name}`);
|
|
223
|
-
matcher.call(matchers, expected);
|
|
224
|
-
};
|
|
225
|
-
const matchers = {
|
|
226
|
-
toBe: (expected) => wrap("toBe", expected),
|
|
227
|
-
toEqual: (expected) => wrap("toEqual", expected),
|
|
228
|
-
toContain: (expected) => wrap("toContain", expected),
|
|
229
|
-
toMatch: (expected) => wrap("toMatch", expected),
|
|
230
|
-
toThrow: (expected) => wrap("toThrow", expected),
|
|
231
|
-
toBeNull: () => wrap("toBeNull"),
|
|
232
|
-
toBeInstanceOf: (expected) => wrap("toBeInstanceOf", expected),
|
|
233
|
-
toHaveLength: (expected) => wrap("toHaveLength", expected),
|
|
234
|
-
toEndWith: (expected) => wrap("toEndWith", expected),
|
|
235
|
-
toMatchObject: (expected) => wrap("toMatchObject", expected)
|
|
236
|
-
};
|
|
237
|
-
Object.defineProperty(matchers, "not", {
|
|
238
|
-
get: () => buildAsyncMatchers(actual, mode, !negate),
|
|
239
|
-
enumerable: true
|
|
240
|
-
});
|
|
241
|
-
return matchers;
|
|
242
|
-
}
|
|
243
|
-
const expect = Object.assign((actual) => buildMatchers(actual, false), {
|
|
244
|
-
not: (actual) => buildMatchers(actual, true),
|
|
245
|
-
rejects: (actual) => buildAsyncMatchers(actual, "rejects", false),
|
|
246
|
-
resolves: (actual) => buildAsyncMatchers(actual, "resolves", false)
|
|
247
|
-
});
|
|
248
|
-
//#endregion
|
|
249
|
-
//#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/types.ts
|
|
250
|
-
function nonEmpty(items) {
|
|
251
|
-
return items.length === 0 ? null : [items[0], ...items.slice(1)];
|
|
252
|
-
}
|
|
253
|
-
function parsePrNumber(value, label = "PR number") {
|
|
254
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
|
|
255
|
-
return value;
|
|
256
|
-
}
|
|
257
|
-
//#endregion
|
|
258
|
-
//#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/github.ts
|
|
259
|
-
var WatcherQueryError = class extends Error {
|
|
260
|
-
failure;
|
|
261
|
-
constructor(failure) {
|
|
262
|
-
super(failure.detail);
|
|
263
|
-
this.name = "WatcherQueryError";
|
|
264
|
-
this.failure = failure;
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
var ChecksUnavailable = class extends WatcherQueryError {
|
|
268
|
-
constructor(detail) {
|
|
269
|
-
super({
|
|
270
|
-
kind: "checks-unavailable",
|
|
271
|
-
retryable: true,
|
|
272
|
-
detail
|
|
273
|
-
});
|
|
274
|
-
this.name = "ChecksUnavailable";
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
const firstLine = (value) => value.trim().split(/\r?\n/, 1)[0]?.slice(0, 240) ?? "";
|
|
278
|
-
function raw(value) {
|
|
279
|
-
try {
|
|
280
|
-
return JSON.stringify(value);
|
|
281
|
-
} catch {
|
|
282
|
-
return String(value);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
function missing(path, value) {
|
|
286
|
-
throw new WatcherQueryError({
|
|
287
|
-
kind: "missing-key",
|
|
288
|
-
retryable: true,
|
|
289
|
-
detail: value === void 0 ? `missing ${path}` : `invalid ${path}: ${raw(value)}`,
|
|
290
|
-
...value === void 0 ? {} : { rawValue: raw(value) }
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
function isRecord(value) {
|
|
294
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
295
|
-
}
|
|
296
|
-
function record(value, path) {
|
|
297
|
-
if (!isRecord(value)) missing(path, value);
|
|
298
|
-
return value;
|
|
299
|
-
}
|
|
300
|
-
function list(value, path) {
|
|
301
|
-
if (!Array.isArray(value)) missing(path, value);
|
|
302
|
-
return value;
|
|
303
|
-
}
|
|
304
|
-
function at(value, path) {
|
|
305
|
-
let current = value;
|
|
306
|
-
for (const key of path) {
|
|
307
|
-
const object = record(current, path.join("."));
|
|
308
|
-
if (!(key in object)) missing(path.join("."));
|
|
309
|
-
current = object[key];
|
|
310
|
-
}
|
|
311
|
-
return current;
|
|
312
|
-
}
|
|
313
|
-
function string(value, path) {
|
|
314
|
-
if (typeof value !== "string") missing(path, value);
|
|
315
|
-
return value;
|
|
316
|
-
}
|
|
317
|
-
const optionalString = (value, path) => value === null ? null : string(value, path);
|
|
318
|
-
function enumValue(value, values, path) {
|
|
319
|
-
if (typeof value === "string") {
|
|
320
|
-
for (const candidate of values) if (candidate === value) return candidate;
|
|
321
|
-
}
|
|
322
|
-
return missing(path, value);
|
|
323
|
-
}
|
|
324
|
-
const nullableEnum = (value, values, path) => value === null ? null : enumValue(value, values, path);
|
|
325
|
-
const MERGE_STATES = [
|
|
326
|
-
"BEHIND",
|
|
327
|
-
"BLOCKED",
|
|
328
|
-
"CLEAN",
|
|
329
|
-
"CONFLICTING",
|
|
330
|
-
"DIRTY",
|
|
331
|
-
"DRAFT",
|
|
332
|
-
"HAS_HOOKS",
|
|
333
|
-
"UNKNOWN",
|
|
334
|
-
"UNSTABLE"
|
|
335
|
-
];
|
|
336
|
-
const REVIEW_DECISIONS = [
|
|
337
|
-
"APPROVED",
|
|
338
|
-
"CHANGES_REQUESTED",
|
|
339
|
-
"REVIEW_REQUIRED"
|
|
340
|
-
];
|
|
341
|
-
const reviewDecision = (value) => nullableEnum(value === "" ? null : value, REVIEW_DECISIONS, "pull request.reviewDecision");
|
|
342
|
-
function checkDetails(value, nameKey) {
|
|
343
|
-
return {
|
|
344
|
-
name: string(value[nameKey], nameKey),
|
|
345
|
-
description: typeof value.description === "string" ? value.description : "",
|
|
346
|
-
link: typeof value.link === "string" ? value.link : typeof value.detailsUrl === "string" ? value.detailsUrl : "",
|
|
347
|
-
workflow: typeof value.workflow === "string" ? value.workflow : ""
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
function parseFastCheck(value) {
|
|
351
|
-
const object = record(value, "check");
|
|
352
|
-
const details = checkDetails(object, "name");
|
|
353
|
-
const state = string(object.state, "check.state").toUpperCase();
|
|
354
|
-
const bucket = string(object.bucket, "check.bucket");
|
|
355
|
-
if (state === "ACTION_REQUIRED") return gateOr("failed", details, state);
|
|
356
|
-
if (bucket === "fail" || ["FAILURE", "ERROR"].includes(state)) return {
|
|
357
|
-
...details,
|
|
358
|
-
kind: "failed",
|
|
359
|
-
reportedState: state
|
|
360
|
-
};
|
|
361
|
-
if (bucket === "pending") return gateOr("pending", details, state);
|
|
362
|
-
if (bucket === "pass") return {
|
|
363
|
-
...details,
|
|
364
|
-
kind: "passed",
|
|
365
|
-
reportedState: state
|
|
366
|
-
};
|
|
367
|
-
if (bucket === "skipping") return {
|
|
368
|
-
...details,
|
|
369
|
-
kind: "skipped",
|
|
370
|
-
reportedState: state
|
|
371
|
-
};
|
|
372
|
-
return {
|
|
373
|
-
...details,
|
|
374
|
-
kind: "failed",
|
|
375
|
-
reportedState: state
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
function gateOr(kind, details, reportedState) {
|
|
379
|
-
return details.name === "Code Review Gate" ? {
|
|
380
|
-
...details,
|
|
381
|
-
kind: "code-review-gate",
|
|
382
|
-
name: "Code Review Gate",
|
|
383
|
-
reportedState
|
|
384
|
-
} : {
|
|
385
|
-
...details,
|
|
386
|
-
kind,
|
|
387
|
-
reportedState
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
function mapRollupNode(value) {
|
|
391
|
-
const object = record(value, "rollup node");
|
|
392
|
-
const typename = object.__typename;
|
|
393
|
-
if (typename !== "CheckRun" && typename !== "StatusContext") return null;
|
|
394
|
-
const details = checkDetails(object, typename === "CheckRun" ? "name" : "context");
|
|
395
|
-
const link = typeof object.targetUrl === "string" ? object.targetUrl : details.link;
|
|
396
|
-
if (typename === "CheckRun") {
|
|
397
|
-
const status = typeof object.status === "string" ? object.status.toUpperCase() : "";
|
|
398
|
-
const conclusion = typeof object.conclusion === "string" ? object.conclusion.toUpperCase() : "";
|
|
399
|
-
if (status !== "COMPLETED") return gateOr("pending", {
|
|
400
|
-
...details,
|
|
401
|
-
link
|
|
402
|
-
}, "PENDING");
|
|
403
|
-
if (conclusion === "SUCCESS") return {
|
|
404
|
-
...details,
|
|
405
|
-
link,
|
|
406
|
-
kind: "passed",
|
|
407
|
-
reportedState: "SUCCESS"
|
|
408
|
-
};
|
|
409
|
-
if (conclusion === "NEUTRAL" || conclusion === "SKIPPED") return {
|
|
410
|
-
...details,
|
|
411
|
-
link,
|
|
412
|
-
kind: "skipped",
|
|
413
|
-
reportedState: conclusion
|
|
414
|
-
};
|
|
415
|
-
if (conclusion === "ACTION_REQUIRED") return gateOr("failed", {
|
|
416
|
-
...details,
|
|
417
|
-
link
|
|
418
|
-
}, conclusion);
|
|
419
|
-
return {
|
|
420
|
-
...details,
|
|
421
|
-
link,
|
|
422
|
-
kind: "failed",
|
|
423
|
-
reportedState: "FAILURE"
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
const state = typeof object.state === "string" ? object.state.toUpperCase() : "";
|
|
427
|
-
if (state === "PENDING" || state === "EXPECTED") return gateOr("pending", {
|
|
428
|
-
...details,
|
|
429
|
-
link
|
|
430
|
-
}, "PENDING");
|
|
431
|
-
return state === "SUCCESS" ? {
|
|
432
|
-
...details,
|
|
433
|
-
link,
|
|
434
|
-
kind: "passed",
|
|
435
|
-
reportedState: state
|
|
436
|
-
} : {
|
|
437
|
-
...details,
|
|
438
|
-
link,
|
|
439
|
-
kind: "failed",
|
|
440
|
-
reportedState: state || "FAILURE"
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
function parseComment(value) {
|
|
444
|
-
const object = record(value, "review comment");
|
|
445
|
-
const author = object.author === null ? null : record(object.author, "review comment.author");
|
|
446
|
-
return {
|
|
447
|
-
authorLogin: author === null ? null : optionalString(author.login, "review comment.author.login"),
|
|
448
|
-
body: string(object.body, "review comment.body"),
|
|
449
|
-
path: optionalString(object.path, "review comment.path"),
|
|
450
|
-
line: object.line === null ? null : Number.isInteger(object.line) ? Number(object.line) : missing("review comment.line", object.line),
|
|
451
|
-
createdAt: string(object.createdAt, "review comment.createdAt")
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
function isBugbot(comment) {
|
|
455
|
-
if (comment === null) return false;
|
|
456
|
-
const author = (comment.authorLogin ?? "").toLowerCase();
|
|
457
|
-
const body = comment.body.toLowerCase();
|
|
458
|
-
return author.includes("bugbot") || author === "cursor" && [
|
|
459
|
-
"bugbot",
|
|
460
|
-
"cursor_automation_id",
|
|
461
|
-
"agentic security review",
|
|
462
|
-
"description start",
|
|
463
|
-
"severity"
|
|
464
|
-
].some((token) => body.includes(token));
|
|
465
|
-
}
|
|
466
|
-
function passKey(comment) {
|
|
467
|
-
if (comment === null) return null;
|
|
468
|
-
for (const pattern of [/RUN_ID:\s*([a-zA-Z0-9_.:-]+)/, /CURSOR_AUTOMATION_ID:\s*([a-zA-Z0-9_.:-]+)/]) {
|
|
469
|
-
const match = pattern.exec(comment.body);
|
|
470
|
-
if (match?.[1]) return match[1];
|
|
471
|
-
}
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
474
|
-
function parseReviewThreadPage(value) {
|
|
475
|
-
const reviewThreads = record(at(value, [
|
|
476
|
-
"data",
|
|
477
|
-
"repository",
|
|
478
|
-
"pullRequest",
|
|
479
|
-
"reviewThreads"
|
|
480
|
-
]), "reviewThreads");
|
|
481
|
-
const nodes = list(reviewThreads.nodes, "reviewThreads.nodes");
|
|
482
|
-
const threads = [];
|
|
483
|
-
for (const node of nodes) {
|
|
484
|
-
const thread = record(node, "review thread");
|
|
485
|
-
if (typeof thread.isResolved !== "boolean") missing("review thread.isResolved", thread.isResolved);
|
|
486
|
-
const comments = list(at(thread, ["comments", "nodes"]), "review thread.comments.nodes");
|
|
487
|
-
threads.push({
|
|
488
|
-
id: string(thread.id, "review thread.id"),
|
|
489
|
-
firstComment: comments.length === 0 ? null : parseComment(comments[0]),
|
|
490
|
-
resolved: thread.isResolved
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
const page = record(reviewThreads.pageInfo, "reviewThreads.pageInfo");
|
|
494
|
-
if (typeof page.hasNextPage !== "boolean") missing("reviewThreads.pageInfo.hasNextPage", page.hasNextPage);
|
|
495
|
-
const cursor = optionalString(page.endCursor, "reviewThreads.pageInfo.endCursor");
|
|
496
|
-
return {
|
|
497
|
-
threads,
|
|
498
|
-
endCursor: page.hasNextPage && cursor ? cursor : null
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
function buildReviewThreads(threads) {
|
|
502
|
-
const keys = /* @__PURE__ */ new Set();
|
|
503
|
-
let keyless = false;
|
|
504
|
-
for (const thread of threads) {
|
|
505
|
-
if (!isBugbot(thread.firstComment)) continue;
|
|
506
|
-
const key = passKey(thread.firstComment);
|
|
507
|
-
if (key === null) keyless = true;
|
|
508
|
-
else keys.add(key);
|
|
509
|
-
}
|
|
510
|
-
const passes = keys.size > 0 ? keys.size : keyless ? 1 : 0;
|
|
511
|
-
return threads.filter((thread) => !thread.resolved).map(({ id, firstComment }) => ({
|
|
512
|
-
id,
|
|
513
|
-
firstComment,
|
|
514
|
-
isBugbot: isBugbot(firstComment),
|
|
515
|
-
bugbotReviewPasses: passes
|
|
516
|
-
}));
|
|
517
|
-
}
|
|
518
|
-
function parsePullRequest(value, context) {
|
|
519
|
-
const object = record(value, "pull request");
|
|
520
|
-
if (typeof object.isDraft !== "boolean") missing("pull request.isDraft", object.isDraft);
|
|
521
|
-
return {
|
|
522
|
-
context,
|
|
523
|
-
mergeable: enumValue(object.mergeable, [
|
|
524
|
-
"MERGEABLE",
|
|
525
|
-
"CONFLICTING",
|
|
526
|
-
"UNKNOWN"
|
|
527
|
-
], "pull request.mergeable"),
|
|
528
|
-
mergeStateStatus: enumValue(object.mergeStateStatus, MERGE_STATES, "pull request.mergeStateStatus"),
|
|
529
|
-
reviewDecision: reviewDecision(object.reviewDecision),
|
|
530
|
-
headRefOid: optionalString(object.headRefOid, "pull request.headRefOid"),
|
|
531
|
-
headRefName: string(object.headRefName, "pull request.headRefName"),
|
|
532
|
-
baseRefName: string(object.baseRefName, "pull request.baseRefName"),
|
|
533
|
-
state: enumValue(object.state, [
|
|
534
|
-
"OPEN",
|
|
535
|
-
"CLOSED",
|
|
536
|
-
"MERGED"
|
|
537
|
-
], "pull request.state"),
|
|
538
|
-
mergedAt: optionalString(object.mergedAt, "pull request.mergedAt"),
|
|
539
|
-
isDraft: object.isDraft
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
async function resolveChecks(reader, context) {
|
|
543
|
-
const fast = await reader.checksFastPath(context);
|
|
544
|
-
const direct = fast.kind === "checks" ? nonEmpty(fast.checks) : null;
|
|
545
|
-
if (direct !== null) return {
|
|
546
|
-
source: "gh-pr-checks",
|
|
547
|
-
checks: direct
|
|
548
|
-
};
|
|
549
|
-
const checks = [];
|
|
550
|
-
let after = null;
|
|
551
|
-
do {
|
|
552
|
-
const page = await reader.checkRollupPage(context, after);
|
|
553
|
-
checks.push(...page.checks);
|
|
554
|
-
after = page.endCursor;
|
|
555
|
-
} while (after !== null);
|
|
556
|
-
const fallback = nonEmpty(checks);
|
|
557
|
-
if (fallback !== null) return {
|
|
558
|
-
source: "graphql-rollup",
|
|
559
|
-
checks: fallback
|
|
560
|
-
};
|
|
561
|
-
throw new ChecksUnavailable(`could not read PR checks: ${fast.kind === "unusable" ? `fast path exit=${fast.exitCode}; GraphQL rollup was empty${firstLine(fast.stderr) ? `; ${firstLine(fast.stderr)}` : ""}` : "fast path and GraphQL rollup were empty"}`);
|
|
562
|
-
}
|
|
563
|
-
async function resolveReviewThreads(reader, context) {
|
|
564
|
-
const threads = [];
|
|
565
|
-
let after = null;
|
|
566
|
-
do {
|
|
567
|
-
const page = await reader.reviewThreadPage(context, after);
|
|
568
|
-
threads.push(...page.threads);
|
|
569
|
-
after = page.endCursor;
|
|
570
|
-
} while (after !== null);
|
|
571
|
-
return buildReviewThreads(threads);
|
|
572
|
-
}
|
|
573
|
-
async function resolveContext(args) {
|
|
574
|
-
if (args.pr !== null && args.owner !== null && args.repo !== null) return {
|
|
575
|
-
owner: args.owner,
|
|
576
|
-
repo: args.repo,
|
|
577
|
-
number: args.pr
|
|
578
|
-
};
|
|
579
|
-
if (args.pr !== null) {
|
|
580
|
-
const origin = await args.reader.originRepo();
|
|
581
|
-
if (origin !== null) return {
|
|
582
|
-
owner: args.owner ?? origin.owner,
|
|
583
|
-
repo: args.repo ?? origin.repo,
|
|
584
|
-
number: args.pr
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
const inferred = await args.reader.currentPr(args.pr);
|
|
588
|
-
return {
|
|
589
|
-
owner: args.owner ?? inferred.owner,
|
|
590
|
-
repo: args.repo ?? inferred.repo,
|
|
591
|
-
number: args.pr ?? inferred.number
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
function orderStack(context, open) {
|
|
595
|
-
const byNumber = new Map(open.map((pr) => [pr.number, pr]));
|
|
596
|
-
const byHead = new Map(open.map((pr) => [pr.headRefName, pr]));
|
|
597
|
-
const children = /* @__PURE__ */ new Map();
|
|
598
|
-
for (const pr of open) children.set(pr.baseRefName, [...children.get(pr.baseRefName) ?? [], pr]);
|
|
599
|
-
for (const values of children.values()) values.sort((a, b) => a.number - b.number);
|
|
600
|
-
const start = byNumber.get(context.number);
|
|
601
|
-
if (start === void 0) return [context];
|
|
602
|
-
const down = [];
|
|
603
|
-
let current = start;
|
|
604
|
-
while (byHead.has(current.baseRefName)) {
|
|
605
|
-
const parent = byHead.get(current.baseRefName);
|
|
606
|
-
if (parent === void 0) break;
|
|
607
|
-
down.push(parent);
|
|
608
|
-
current = parent;
|
|
609
|
-
}
|
|
610
|
-
const seen = /* @__PURE__ */ new Set([...down.map((pr) => pr.number), start.number]);
|
|
611
|
-
const up = [];
|
|
612
|
-
const visit = (parent) => {
|
|
613
|
-
for (const child of children.get(parent.headRefName) ?? []) {
|
|
614
|
-
if (seen.has(child.number)) continue;
|
|
615
|
-
seen.add(child.number);
|
|
616
|
-
up.push(child);
|
|
617
|
-
visit(child);
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
visit(start);
|
|
621
|
-
return nonEmpty([
|
|
622
|
-
...down.reverse(),
|
|
623
|
-
start,
|
|
624
|
-
...up
|
|
625
|
-
].map((pr) => ({
|
|
626
|
-
...context,
|
|
627
|
-
number: pr.number
|
|
628
|
-
}))) ?? [context];
|
|
629
|
-
}
|
|
630
|
-
//#endregion
|
|
631
|
-
//#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts
|
|
632
|
-
function toRawThread(thread) {
|
|
633
|
-
return {
|
|
634
|
-
id: thread.id,
|
|
635
|
-
firstComment: thread.firstComment,
|
|
636
|
-
resolved: false
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
function passingCheck(name = "ci") {
|
|
640
|
-
return {
|
|
641
|
-
kind: "passed",
|
|
642
|
-
name,
|
|
643
|
-
reportedState: "SUCCESS",
|
|
644
|
-
description: "",
|
|
645
|
-
link: "",
|
|
646
|
-
workflow: ""
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
function pendingCheck(name = "ci") {
|
|
650
|
-
return {
|
|
651
|
-
kind: "pending",
|
|
652
|
-
name,
|
|
653
|
-
reportedState: "PENDING",
|
|
654
|
-
description: "",
|
|
655
|
-
link: "",
|
|
656
|
-
workflow: ""
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
function failedCheck(name = "ci") {
|
|
660
|
-
return {
|
|
661
|
-
kind: "failed",
|
|
662
|
-
name,
|
|
663
|
-
reportedState: "FAILURE",
|
|
664
|
-
description: "",
|
|
665
|
-
link: "",
|
|
666
|
-
workflow: ""
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
function fakeReader(options = {}) {
|
|
670
|
-
const calls = [];
|
|
671
|
-
const context = options.current ?? {
|
|
672
|
-
owner: "owner",
|
|
673
|
-
repo: "repo",
|
|
674
|
-
number: parsePrNumber(1)
|
|
675
|
-
};
|
|
676
|
-
const defaults = {
|
|
677
|
-
context,
|
|
678
|
-
mergeable: "MERGEABLE",
|
|
679
|
-
mergeStateStatus: "CLEAN",
|
|
680
|
-
reviewDecision: "APPROVED",
|
|
681
|
-
headRefOid: "head",
|
|
682
|
-
headRefName: "feature",
|
|
683
|
-
baseRefName: "main",
|
|
684
|
-
state: "OPEN",
|
|
685
|
-
mergedAt: null,
|
|
686
|
-
isDraft: false
|
|
687
|
-
};
|
|
688
|
-
let page = 0;
|
|
689
|
-
let threadPage = 0;
|
|
690
|
-
return {
|
|
691
|
-
calls,
|
|
692
|
-
async originRepo() {
|
|
693
|
-
calls.push("originRepo");
|
|
694
|
-
return options.origin === void 0 ? {
|
|
695
|
-
owner: "owner",
|
|
696
|
-
repo: "repo"
|
|
697
|
-
} : options.origin;
|
|
698
|
-
},
|
|
699
|
-
async currentPr(pr) {
|
|
700
|
-
calls.push("currentPr");
|
|
701
|
-
return {
|
|
702
|
-
...context,
|
|
703
|
-
number: pr ?? context.number
|
|
704
|
-
};
|
|
705
|
-
},
|
|
706
|
-
async pullRequest(requested) {
|
|
707
|
-
calls.push("pullRequest");
|
|
708
|
-
return {
|
|
709
|
-
...defaults,
|
|
710
|
-
...options.facts,
|
|
711
|
-
context: requested
|
|
712
|
-
};
|
|
713
|
-
},
|
|
714
|
-
async openPullRequests() {
|
|
715
|
-
calls.push("openPullRequests");
|
|
716
|
-
return options.openPullRequests ?? [];
|
|
717
|
-
},
|
|
718
|
-
async checksFastPath() {
|
|
719
|
-
calls.push("checksFastPath");
|
|
720
|
-
return options.fastPath ?? {
|
|
721
|
-
kind: "checks",
|
|
722
|
-
checks: [passingCheck()]
|
|
723
|
-
};
|
|
724
|
-
},
|
|
725
|
-
async checkRollupPage(_requested, after) {
|
|
726
|
-
calls.push(`checkRollupPage:${after ?? "null"}`);
|
|
727
|
-
return options.rollupPages?.[page++] ?? {
|
|
728
|
-
checks: [],
|
|
729
|
-
endCursor: null
|
|
730
|
-
};
|
|
731
|
-
},
|
|
732
|
-
async reviewThreadPage(_requested, after) {
|
|
733
|
-
calls.push(`reviewThreadPage:${after ?? "null"}`);
|
|
734
|
-
return options.threadPages?.[threadPage++] ?? {
|
|
735
|
-
threads: (options.threads ?? []).map(toRawThread),
|
|
736
|
-
endCursor: null
|
|
737
|
-
};
|
|
738
|
-
},
|
|
739
|
-
async commitRollups() {
|
|
740
|
-
calls.push("commitRollups");
|
|
741
|
-
return options.commitRollups ?? [{
|
|
742
|
-
oid: "head",
|
|
743
|
-
state: "SUCCESS"
|
|
744
|
-
}];
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
//#endregion
|
|
749
|
-
//#region .runtime-sources/skills/poteto-mode/scripts/watch-pr/github.test.ts
|
|
750
|
-
const context = {
|
|
751
|
-
owner: "owner",
|
|
752
|
-
repo: "repo",
|
|
753
|
-
number: parsePrNumber(42)
|
|
754
|
-
};
|
|
755
|
-
describe("checks fallback chain", () => {
|
|
756
|
-
it("uses a non-empty fast-path result without a rollup query", async () => {
|
|
757
|
-
const reader = fakeReader({ fastPath: {
|
|
758
|
-
kind: "checks",
|
|
759
|
-
checks: [passingCheck("fast")]
|
|
760
|
-
} });
|
|
761
|
-
const read = await resolveChecks(reader, context);
|
|
762
|
-
expect(read.source).toBe("gh-pr-checks");
|
|
763
|
-
expect(read.checks.map((check) => check.name)).toEqual(["fast"]);
|
|
764
|
-
expect(reader.calls).toEqual(["checksFastPath"]);
|
|
765
|
-
});
|
|
766
|
-
it("paginates GraphQL when the fast path is unusable", async () => {
|
|
767
|
-
const reader = fakeReader({
|
|
768
|
-
fastPath: {
|
|
769
|
-
kind: "unusable",
|
|
770
|
-
exitCode: 8,
|
|
771
|
-
stderr: ""
|
|
772
|
-
},
|
|
773
|
-
rollupPages: [{
|
|
774
|
-
checks: [passingCheck("first")],
|
|
775
|
-
endCursor: "next"
|
|
776
|
-
}, {
|
|
777
|
-
checks: [failedCheck("second")],
|
|
778
|
-
endCursor: null
|
|
779
|
-
}]
|
|
780
|
-
});
|
|
781
|
-
const read = await resolveChecks(reader, context);
|
|
782
|
-
expect(read.source).toBe("graphql-rollup");
|
|
783
|
-
expect(read.checks.map((check) => check.name)).toEqual(["first", "second"]);
|
|
784
|
-
expect(reader.calls).toEqual([
|
|
785
|
-
"checksFastPath",
|
|
786
|
-
"checkRollupPage:null",
|
|
787
|
-
"checkRollupPage:next"
|
|
788
|
-
]);
|
|
789
|
-
});
|
|
790
|
-
it("falls back when valid fast-path JSON represented an empty list", async () => {
|
|
791
|
-
const reader = fakeReader({
|
|
792
|
-
fastPath: {
|
|
793
|
-
kind: "checks",
|
|
794
|
-
checks: []
|
|
795
|
-
},
|
|
796
|
-
rollupPages: [{
|
|
797
|
-
checks: [pendingCheck("fallback")],
|
|
798
|
-
endCursor: null
|
|
799
|
-
}]
|
|
800
|
-
});
|
|
801
|
-
expect((await resolveChecks(reader, context)).checks[0].name).toBe("fallback");
|
|
802
|
-
expect(reader.calls).toEqual(["checksFastPath", "checkRollupPage:null"]);
|
|
803
|
-
});
|
|
804
|
-
it("fails closed when both paths are empty", async () => {
|
|
805
|
-
const reader = fakeReader({ fastPath: {
|
|
806
|
-
kind: "unusable",
|
|
807
|
-
exitCode: 8,
|
|
808
|
-
stderr: "credential cannot read checks"
|
|
809
|
-
} });
|
|
810
|
-
await expect(resolveChecks(reader, context)).rejects.toBeInstanceOf(ChecksUnavailable);
|
|
811
|
-
expect(reader.calls).toEqual(["checksFastPath", "checkRollupPage:null"]);
|
|
812
|
-
});
|
|
813
|
-
});
|
|
814
|
-
describe("rollup node mapping", () => {
|
|
815
|
-
it("maps terminal and non-terminal CheckRun states fail closed", () => {
|
|
816
|
-
for (const [status, conclusion, kind, reportedState] of [
|
|
817
|
-
[
|
|
818
|
-
"IN_PROGRESS",
|
|
819
|
-
null,
|
|
820
|
-
"pending",
|
|
821
|
-
"PENDING"
|
|
822
|
-
],
|
|
823
|
-
[
|
|
824
|
-
"COMPLETED",
|
|
825
|
-
"SUCCESS",
|
|
826
|
-
"passed",
|
|
827
|
-
"SUCCESS"
|
|
828
|
-
],
|
|
829
|
-
[
|
|
830
|
-
"COMPLETED",
|
|
831
|
-
"NEUTRAL",
|
|
832
|
-
"skipped",
|
|
833
|
-
"NEUTRAL"
|
|
834
|
-
],
|
|
835
|
-
[
|
|
836
|
-
"COMPLETED",
|
|
837
|
-
"SKIPPED",
|
|
838
|
-
"skipped",
|
|
839
|
-
"SKIPPED"
|
|
840
|
-
],
|
|
841
|
-
[
|
|
842
|
-
"COMPLETED",
|
|
843
|
-
"ACTION_REQUIRED",
|
|
844
|
-
"failed",
|
|
845
|
-
"ACTION_REQUIRED"
|
|
846
|
-
],
|
|
847
|
-
[
|
|
848
|
-
"COMPLETED",
|
|
849
|
-
"TIMED_OUT",
|
|
850
|
-
"failed",
|
|
851
|
-
"FAILURE"
|
|
852
|
-
],
|
|
853
|
-
[
|
|
854
|
-
"COMPLETED",
|
|
855
|
-
"FUTURE_VALUE",
|
|
856
|
-
"failed",
|
|
857
|
-
"FAILURE"
|
|
858
|
-
]
|
|
859
|
-
]) expect(mapRollupNode({
|
|
860
|
-
__typename: "CheckRun",
|
|
861
|
-
name: "ci",
|
|
862
|
-
status,
|
|
863
|
-
conclusion
|
|
864
|
-
})).toMatchObject({
|
|
865
|
-
kind,
|
|
866
|
-
reportedState
|
|
867
|
-
});
|
|
868
|
-
});
|
|
869
|
-
it("classifies an in-progress Code Review Gate from the rollup as the gate", () => {
|
|
870
|
-
expect(mapRollupNode({
|
|
871
|
-
__typename: "CheckRun",
|
|
872
|
-
name: "Code Review Gate",
|
|
873
|
-
status: "IN_PROGRESS",
|
|
874
|
-
conclusion: null
|
|
875
|
-
})).toMatchObject({ kind: "code-review-gate" });
|
|
876
|
-
expect(mapRollupNode({
|
|
877
|
-
__typename: "StatusContext",
|
|
878
|
-
context: "Code Review Gate",
|
|
879
|
-
state: "PENDING"
|
|
880
|
-
})).toMatchObject({ kind: "code-review-gate" });
|
|
881
|
-
});
|
|
882
|
-
it("classifies a Code Review Gate waiting on an owner approval as the gate", () => {
|
|
883
|
-
expect(mapRollupNode({
|
|
884
|
-
__typename: "CheckRun",
|
|
885
|
-
name: "Code Review Gate",
|
|
886
|
-
status: "COMPLETED",
|
|
887
|
-
conclusion: "ACTION_REQUIRED",
|
|
888
|
-
description: "1 of 2 policies"
|
|
889
|
-
})).toMatchObject({
|
|
890
|
-
kind: "code-review-gate",
|
|
891
|
-
reportedState: "ACTION_REQUIRED"
|
|
892
|
-
});
|
|
893
|
-
});
|
|
894
|
-
it("classifies a fast-path Code Review Gate waiting on an owner approval as the gate", () => {
|
|
895
|
-
expect(parseFastCheck({
|
|
896
|
-
name: "Code Review Gate",
|
|
897
|
-
state: "ACTION_REQUIRED",
|
|
898
|
-
bucket: "fail"
|
|
899
|
-
})).toMatchObject({
|
|
900
|
-
kind: "code-review-gate",
|
|
901
|
-
reportedState: "ACTION_REQUIRED"
|
|
902
|
-
});
|
|
903
|
-
expect(parseFastCheck({
|
|
904
|
-
name: "ci",
|
|
905
|
-
state: "ACTION_REQUIRED",
|
|
906
|
-
bucket: "fail"
|
|
907
|
-
})).toMatchObject({
|
|
908
|
-
kind: "failed",
|
|
909
|
-
reportedState: "ACTION_REQUIRED"
|
|
910
|
-
});
|
|
911
|
-
});
|
|
912
|
-
it("maps StatusContext states and drops unknown typenames", () => {
|
|
913
|
-
expect(mapRollupNode({
|
|
914
|
-
__typename: "StatusContext",
|
|
915
|
-
context: "ci",
|
|
916
|
-
state: "EXPECTED"
|
|
917
|
-
})).toMatchObject({
|
|
918
|
-
kind: "pending",
|
|
919
|
-
reportedState: "PENDING"
|
|
920
|
-
});
|
|
921
|
-
expect(mapRollupNode({
|
|
922
|
-
__typename: "StatusContext",
|
|
923
|
-
context: "ci",
|
|
924
|
-
state: "FUTURE_VALUE"
|
|
925
|
-
})).toMatchObject({
|
|
926
|
-
kind: "failed",
|
|
927
|
-
reportedState: "FUTURE_VALUE"
|
|
928
|
-
});
|
|
929
|
-
expect(mapRollupNode({ __typename: "FutureNode" })).toBeNull();
|
|
930
|
-
});
|
|
931
|
-
});
|
|
932
|
-
describe("closed enum parsing", () => {
|
|
933
|
-
const rawPullRequest = {
|
|
934
|
-
mergeable: "MERGEABLE",
|
|
935
|
-
mergeStateStatus: "CLEAN",
|
|
936
|
-
reviewDecision: "APPROVED",
|
|
937
|
-
headRefOid: "head",
|
|
938
|
-
headRefName: "feature",
|
|
939
|
-
baseRefName: "main",
|
|
940
|
-
state: "OPEN",
|
|
941
|
-
mergedAt: null,
|
|
942
|
-
isDraft: false
|
|
943
|
-
};
|
|
944
|
-
it("accepts mergeStateStatus CONFLICTING", () => {
|
|
945
|
-
expect(parsePullRequest({
|
|
946
|
-
...rawPullRequest,
|
|
947
|
-
mergeStateStatus: "CONFLICTING"
|
|
948
|
-
}, context).mergeStateStatus).toBe("CONFLICTING");
|
|
949
|
-
});
|
|
950
|
-
it("reads gh's empty reviewDecision as no decision rather than a parse failure", () => {
|
|
951
|
-
expect(parsePullRequest({
|
|
952
|
-
...rawPullRequest,
|
|
953
|
-
reviewDecision: ""
|
|
954
|
-
}, context).reviewDecision).toBeNull();
|
|
955
|
-
});
|
|
956
|
-
it("still rejects an unknown reviewDecision", () => {
|
|
957
|
-
expect(() => parsePullRequest({
|
|
958
|
-
...rawPullRequest,
|
|
959
|
-
reviewDecision: "MAYBE"
|
|
960
|
-
}, context)).toThrow(WatcherQueryError);
|
|
961
|
-
});
|
|
962
|
-
it("rejects unknown enum values as retryable errors carrying the raw value", () => {
|
|
963
|
-
try {
|
|
964
|
-
parsePullRequest({
|
|
965
|
-
...rawPullRequest,
|
|
966
|
-
mergeStateStatus: "FUTURE_STATE"
|
|
967
|
-
}, context);
|
|
968
|
-
throw new Error("expected parser to throw");
|
|
969
|
-
} catch (error) {
|
|
970
|
-
expect(error).toBeInstanceOf(WatcherQueryError);
|
|
971
|
-
if (!(error instanceof WatcherQueryError)) throw error;
|
|
972
|
-
expect(error.failure).toMatchObject({
|
|
973
|
-
kind: "missing-key",
|
|
974
|
-
retryable: true,
|
|
975
|
-
rawValue: "\"FUTURE_STATE\""
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
});
|
|
979
|
-
});
|
|
980
|
-
it("annotates Bugbot threads with distinct review-pass counts", () => {
|
|
981
|
-
const threads = buildReviewThreads(parseReviewThreadPage({ data: { repository: { pullRequest: { reviewThreads: {
|
|
982
|
-
pageInfo: {
|
|
983
|
-
hasNextPage: false,
|
|
984
|
-
endCursor: null
|
|
985
|
-
},
|
|
986
|
-
nodes: [
|
|
987
|
-
{
|
|
988
|
-
id: "one",
|
|
989
|
-
isResolved: false,
|
|
990
|
-
comments: { nodes: [{
|
|
991
|
-
body: "RUN_ID: run-1",
|
|
992
|
-
createdAt: "now",
|
|
993
|
-
path: "a.ts",
|
|
994
|
-
line: 1,
|
|
995
|
-
author: { login: "bugbot" }
|
|
996
|
-
}] }
|
|
997
|
-
},
|
|
998
|
-
{
|
|
999
|
-
id: "two",
|
|
1000
|
-
isResolved: false,
|
|
1001
|
-
comments: { nodes: [{
|
|
1002
|
-
body: "CURSOR_AUTOMATION_ID: run-2 severity high",
|
|
1003
|
-
createdAt: "now",
|
|
1004
|
-
path: null,
|
|
1005
|
-
line: null,
|
|
1006
|
-
author: { login: "cursor" }
|
|
1007
|
-
}] }
|
|
1008
|
-
},
|
|
1009
|
-
{
|
|
1010
|
-
id: "resolved",
|
|
1011
|
-
isResolved: true,
|
|
1012
|
-
comments: { nodes: [{
|
|
1013
|
-
body: "RUN_ID: run-3",
|
|
1014
|
-
createdAt: "now",
|
|
1015
|
-
path: null,
|
|
1016
|
-
line: null,
|
|
1017
|
-
author: { login: "bugbot" }
|
|
1018
|
-
}] }
|
|
1019
|
-
}
|
|
1020
|
-
]
|
|
1021
|
-
} } } } }).threads);
|
|
1022
|
-
expect(threads).toHaveLength(2);
|
|
1023
|
-
expect(threads.map((thread) => thread.isBugbot)).toEqual([true, true]);
|
|
1024
|
-
expect(threads.map((thread) => thread.bugbotReviewPasses)).toEqual([3, 3]);
|
|
1025
|
-
});
|
|
1026
|
-
describe("review thread pagination", () => {
|
|
1027
|
-
const thread = (id, resolved = false) => ({
|
|
1028
|
-
id,
|
|
1029
|
-
firstComment: {
|
|
1030
|
-
authorLogin: "someone",
|
|
1031
|
-
body: "please fix",
|
|
1032
|
-
path: "a.ts",
|
|
1033
|
-
line: 1,
|
|
1034
|
-
createdAt: "now"
|
|
1035
|
-
},
|
|
1036
|
-
resolved
|
|
1037
|
-
});
|
|
1038
|
-
it("keeps walking pages until the cursor is exhausted", async () => {
|
|
1039
|
-
const reader = fakeReader({ threadPages: [
|
|
1040
|
-
{
|
|
1041
|
-
threads: [thread("one")],
|
|
1042
|
-
endCursor: "next"
|
|
1043
|
-
},
|
|
1044
|
-
{
|
|
1045
|
-
threads: [thread("two")],
|
|
1046
|
-
endCursor: "last"
|
|
1047
|
-
},
|
|
1048
|
-
{
|
|
1049
|
-
threads: [thread("three")],
|
|
1050
|
-
endCursor: null
|
|
1051
|
-
}
|
|
1052
|
-
] });
|
|
1053
|
-
const threads = await resolveReviewThreads(reader, context);
|
|
1054
|
-
expect(threads.map((item) => item.id)).toEqual([
|
|
1055
|
-
"one",
|
|
1056
|
-
"two",
|
|
1057
|
-
"three"
|
|
1058
|
-
]);
|
|
1059
|
-
expect(reader.calls).toEqual([
|
|
1060
|
-
"reviewThreadPage:null",
|
|
1061
|
-
"reviewThreadPage:next",
|
|
1062
|
-
"reviewThreadPage:last"
|
|
1063
|
-
]);
|
|
1064
|
-
});
|
|
1065
|
-
it("sees an unresolved thread that sits past the first page", async () => {
|
|
1066
|
-
const threads = await resolveReviewThreads(fakeReader({ threadPages: [{
|
|
1067
|
-
threads: [thread("early", true)],
|
|
1068
|
-
endCursor: "next"
|
|
1069
|
-
}, {
|
|
1070
|
-
threads: [thread("late")],
|
|
1071
|
-
endCursor: null
|
|
1072
|
-
}] }), context);
|
|
1073
|
-
expect(threads.map((item) => item.id)).toEqual(["late"]);
|
|
1074
|
-
});
|
|
1075
|
-
it("stops after a single page when the cursor is exhausted", async () => {
|
|
1076
|
-
const reader = fakeReader({ threadPages: [{
|
|
1077
|
-
threads: [thread("only")],
|
|
1078
|
-
endCursor: null
|
|
1079
|
-
}] });
|
|
1080
|
-
expect((await resolveReviewThreads(reader, context)).length).toBe(1);
|
|
1081
|
-
expect(reader.calls).toEqual(["reviewThreadPage:null"]);
|
|
1082
|
-
});
|
|
1083
|
-
it("counts Bugbot review passes across every page", async () => {
|
|
1084
|
-
const bugbot = (id, runId) => ({
|
|
1085
|
-
id,
|
|
1086
|
-
firstComment: {
|
|
1087
|
-
authorLogin: "bugbot",
|
|
1088
|
-
body: `RUN_ID: ${runId}`,
|
|
1089
|
-
path: "a.ts",
|
|
1090
|
-
line: 1,
|
|
1091
|
-
createdAt: "now"
|
|
1092
|
-
},
|
|
1093
|
-
resolved: false
|
|
1094
|
-
});
|
|
1095
|
-
const threads = await resolveReviewThreads(fakeReader({ threadPages: [{
|
|
1096
|
-
threads: [bugbot("one", "run-1")],
|
|
1097
|
-
endCursor: "next"
|
|
1098
|
-
}, {
|
|
1099
|
-
threads: [bugbot("two", "run-2")],
|
|
1100
|
-
endCursor: null
|
|
1101
|
-
}] }), context);
|
|
1102
|
-
expect(threads.map((item) => item.bugbotReviewPasses)).toEqual([2, 2]);
|
|
1103
|
-
});
|
|
1104
|
-
it("drops resolved threads from later pages too", async () => {
|
|
1105
|
-
const reader = fakeReader({ threadPages: [{
|
|
1106
|
-
threads: [thread("one")],
|
|
1107
|
-
endCursor: "next"
|
|
1108
|
-
}, {
|
|
1109
|
-
threads: [thread("two", true)],
|
|
1110
|
-
endCursor: null
|
|
1111
|
-
}] });
|
|
1112
|
-
expect((await resolveReviewThreads(reader, context)).map((item) => item.id)).toEqual(["one"]);
|
|
1113
|
-
});
|
|
1114
|
-
it("rejects a page that omits pageInfo instead of assuming one page", async () => {
|
|
1115
|
-
const response = { data: { repository: { pullRequest: { reviewThreads: { nodes: [] } } } } };
|
|
1116
|
-
expect(() => parseReviewThreadPage(response)).toThrow(WatcherQueryError);
|
|
1117
|
-
});
|
|
1118
|
-
});
|
|
1119
|
-
describe("context and stack discovery", () => {
|
|
1120
|
-
it("returns a fully explicit context without any reader call", async () => {
|
|
1121
|
-
const reader = fakeReader();
|
|
1122
|
-
expect(await resolveContext({
|
|
1123
|
-
reader,
|
|
1124
|
-
owner: "explicit",
|
|
1125
|
-
repo: "repo",
|
|
1126
|
-
pr: context.number
|
|
1127
|
-
})).toEqual({
|
|
1128
|
-
owner: "explicit",
|
|
1129
|
-
repo: "repo",
|
|
1130
|
-
number: context.number
|
|
1131
|
-
});
|
|
1132
|
-
expect(reader.calls).toEqual([]);
|
|
1133
|
-
});
|
|
1134
|
-
it("uses the local origin before currentPr for an explicit number", async () => {
|
|
1135
|
-
const reader = fakeReader({ origin: {
|
|
1136
|
-
owner: "local",
|
|
1137
|
-
repo: "checkout"
|
|
1138
|
-
} });
|
|
1139
|
-
expect(await resolveContext({
|
|
1140
|
-
reader,
|
|
1141
|
-
owner: null,
|
|
1142
|
-
repo: null,
|
|
1143
|
-
pr: context.number
|
|
1144
|
-
})).toEqual({
|
|
1145
|
-
owner: "local",
|
|
1146
|
-
repo: "checkout",
|
|
1147
|
-
number: context.number
|
|
1148
|
-
});
|
|
1149
|
-
expect(reader.calls).toEqual(["originRepo"]);
|
|
1150
|
-
});
|
|
1151
|
-
it("orders the connected stack bottom-to-top", () => {
|
|
1152
|
-
const ordered = orderStack(context, [
|
|
1153
|
-
{
|
|
1154
|
-
number: parsePrNumber(41),
|
|
1155
|
-
headRefName: "base-feature",
|
|
1156
|
-
baseRefName: "main"
|
|
1157
|
-
},
|
|
1158
|
-
{
|
|
1159
|
-
number: context.number,
|
|
1160
|
-
headRefName: "feature",
|
|
1161
|
-
baseRefName: "base-feature"
|
|
1162
|
-
},
|
|
1163
|
-
{
|
|
1164
|
-
number: parsePrNumber(43),
|
|
1165
|
-
headRefName: "upstack",
|
|
1166
|
-
baseRefName: "feature"
|
|
1167
|
-
}
|
|
1168
|
-
]);
|
|
1169
|
-
expect(ordered.map((item) => Number(item.number))).toEqual([
|
|
1170
|
-
41,
|
|
1171
|
-
42,
|
|
1172
|
-
43
|
|
1173
|
-
]);
|
|
1174
|
-
});
|
|
1175
|
-
});
|
|
1176
|
-
//#endregion
|
|
1177
|
-
export {};
|