@cluesmith/codev 3.3.1 → 3.3.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/dashboard-dist/assets/{index-D3XaKpZW.js → index-CUIjS_Vb.js} +41 -41
- package/dashboard-dist/assets/index-CUIjS_Vb.js.map +1 -0
- package/dashboard-dist/assets/index-DnYLy5C_.css +32 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +52 -1
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
- package/dist/agent-farm/commands/inbox.js +67 -10
- package/dist/agent-farm/commands/inbox.js.map +1 -1
- package/dist/agent-farm/commands/interrupt.d.ts +4 -1
- package/dist/agent-farm/commands/interrupt.d.ts.map +1 -1
- package/dist/agent-farm/commands/interrupt.js +6 -3
- package/dist/agent-farm/commands/interrupt.js.map +1 -1
- package/dist/agent-farm/commands/reset/constants.d.ts +118 -0
- package/dist/agent-farm/commands/reset/constants.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/constants.js +118 -0
- package/dist/agent-farm/commands/reset/constants.js.map +1 -1
- package/dist/agent-farm/commands/reset/context.d.ts +18 -0
- package/dist/agent-farm/commands/reset/context.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/context.js +41 -0
- package/dist/agent-farm/commands/reset/context.js.map +1 -1
- package/dist/agent-farm/commands/reset/receipt.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/receipt.js +16 -5
- package/dist/agent-farm/commands/reset/receipt.js.map +1 -1
- package/dist/agent-farm/commands/reset/reorient.d.ts +1 -1
- package/dist/agent-farm/commands/reset/reorient.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/reorient.js +8 -1
- package/dist/agent-farm/commands/reset/reorient.js.map +1 -1
- package/dist/agent-farm/commands/reset/self.d.ts +335 -0
- package/dist/agent-farm/commands/reset/self.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/self.js +636 -0
- package/dist/agent-farm/commands/reset/self.js.map +1 -0
- package/dist/agent-farm/commands/reset.d.ts +4 -1
- package/dist/agent-farm/commands/reset.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset.js +29 -21
- package/dist/agent-farm/commands/reset.js.map +1 -1
- package/dist/agent-farm/commands/self-refresh.d.ts +60 -0
- package/dist/agent-farm/commands/self-refresh.d.ts.map +1 -0
- package/dist/agent-farm/commands/self-refresh.js +506 -0
- package/dist/agent-farm/commands/self-refresh.js.map +1 -0
- package/dist/agent-farm/commands/send.d.ts +28 -0
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +90 -7
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +7 -0
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts +3 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +8 -443
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/mailbox.d.ts +47 -7
- package/dist/agent-farm/db/mailbox.d.ts.map +1 -1
- package/dist/agent-farm/db/mailbox.js +56 -12
- package/dist/agent-farm/db/mailbox.js.map +1 -1
- package/dist/agent-farm/db/migrations.d.ts +57 -0
- package/dist/agent-farm/db/migrations.d.ts.map +1 -0
- package/dist/agent-farm/db/migrations.js +530 -0
- package/dist/agent-farm/db/migrations.js.map +1 -0
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +1 -0
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +22 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts +12 -0
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +12 -0
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +43 -14
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts +7 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.js +2 -1
- package/dist/agent-farm/servers/cron-delivery.js.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.d.ts +21 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.js +36 -4
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +218 -15
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.js +259 -27
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +66 -11
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.js +202 -48
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +58 -18
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +73 -28
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +37 -1
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
- package/dist/agent-farm/servers/render-gate.js +71 -12
- package/dist/agent-farm/servers/render-gate.js.map +1 -1
- package/dist/agent-farm/servers/session-submit.d.ts +220 -27
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
- package/dist/agent-farm/servers/session-submit.js +333 -42
- package/dist/agent-farm/servers/session-submit.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +3 -2
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +11 -3
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts +32 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +245 -36
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +15 -2
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +21 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +53 -5
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +14 -0
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +39 -0
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +8 -1
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/types.d.ts +36 -1
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.d.ts +136 -4
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.js +212 -22
- package/dist/agent-farm/utils/message-format.js.map +1 -1
- package/dist/agent-farm/utils/server-utils.d.ts +8 -2
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +19 -2
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/generate-image.d.ts +20 -1
- package/dist/commands/generate-image.d.ts.map +1 -1
- package/dist/commands/generate-image.js +188 -1
- package/dist/commands/generate-image.js.map +1 -1
- package/dist/commands/porch/context-refresh.d.ts +181 -0
- package/dist/commands/porch/context-refresh.d.ts.map +1 -0
- package/dist/commands/porch/context-refresh.js +270 -0
- package/dist/commands/porch/context-refresh.js.map +1 -0
- package/dist/commands/porch/index.d.ts +6 -0
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +46 -0
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts +0 -9
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +133 -4
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +18 -2
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +102 -0
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/types.d.ts +61 -0
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/lib/forge-contracts.d.ts +66 -5
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge.d.ts +1 -1
- package/dist/lib/forge.d.ts.map +1 -1
- package/dist/lib/forge.js +33 -11
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/self-refresh-invocation.d.ts +60 -0
- package/dist/lib/self-refresh-invocation.d.ts.map +1 -0
- package/dist/lib/self-refresh-invocation.js +64 -0
- package/dist/lib/self-refresh-invocation.js.map +1 -0
- package/dist/lib/test-env.d.ts +50 -0
- package/dist/lib/test-env.d.ts.map +1 -1
- package/dist/lib/test-env.js +76 -0
- package/dist/lib/test-env.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +10 -1
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +38 -9
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +52 -4
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +157 -18
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +73 -0
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +151 -0
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-main.js +20 -3
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +2 -0
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +17 -1
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +28 -0
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/package.json +4 -4
- package/scripts/forge/gitea/_lib.sh +214 -0
- package/scripts/forge/gitea/issue-comment.sh +8 -1
- package/scripts/forge/gitea/issue-view.sh +95 -4
- package/scripts/forge/gitea/pr-create.sh +155 -0
- package/scripts/forge/gitea/pr-exists.sh +32 -4
- package/scripts/forge/gitea/pr-list.sh +40 -31
- package/scripts/forge/gitea/pr-view.sh +65 -4
- package/scripts/forge/gitea/recently-merged.sh +91 -21
- package/scripts/forge/gitea/user-identity.sh +30 -1
- package/scripts/forge/github/issue-view.sh +5 -2
- package/scripts/forge/github/pr-create.sh +47 -0
- package/scripts/forge/github/pr-search.sh +3 -1
- package/scripts/forge/gitlab/issue-view.sh +24 -4
- package/scripts/forge/gitlab/pr-create.sh +53 -0
- package/scripts/forge/gitlab/pr-search.sh +3 -1
- package/scripts/forge/linear/issue-view.sh +13 -2
- package/skeleton/.claude/skills/arch-save/SKILL.md +8 -2
- package/skeleton/.claude/skills/builder-refresh/SKILL.md +138 -0
- package/skeleton/.claude/skills/generate-image/SKILL.md +22 -1
- package/skeleton/.codex/skills/arch-save/SKILL.md +8 -2
- package/skeleton/.codex/skills/builder-refresh/SKILL.md +138 -0
- package/skeleton/.codex/skills/generate-image/SKILL.md +22 -1
- package/skeleton/protocol-schema.json +17 -0
- package/skeleton/protocols/air/prompts/pr.md +6 -1
- package/skeleton/protocols/air/protocol.json +1 -1
- package/skeleton/protocols/aspir/prompts/review.md +6 -1
- package/skeleton/protocols/aspir/protocol.json +5 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +6 -1
- package/skeleton/protocols/bugfix/protocol.json +1 -1
- package/skeleton/protocols/bugfix/protocol.md +2 -1
- package/skeleton/protocols/experiment/protocol.json +1 -1
- package/skeleton/protocols/maintain/prompts/review.md +6 -1
- package/skeleton/protocols/maintain/protocol.json +1 -1
- package/skeleton/protocols/pir/prompts/review.md +10 -7
- package/skeleton/protocols/pir/protocol.json +1 -1
- package/skeleton/protocols/protocol-schema.json +17 -0
- package/skeleton/protocols/research/protocol.json +1 -1
- package/skeleton/protocols/spike/protocol.json +1 -1
- package/skeleton/protocols/spir/prompts/review.md +6 -1
- package/skeleton/protocols/spir/protocol.json +5 -1
- package/skeleton/resources/commands/agent-farm.md +1 -1
- package/templates/tower.html +16 -1
- package/dashboard-dist/assets/index-BZRTEMtH.css +0 -32
- package/dashboard-dist/assets/index-D3XaKpZW.js.map +0 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Forge concept: pr-create (Gitea/Forgejo via tea CLI)
|
|
3
|
+
# forge-executable: tea
|
|
4
|
+
#
|
|
5
|
+
# Input: CODEV_PR_TITLE (required)
|
|
6
|
+
# CODEV_PR_BODY (required, may be empty)
|
|
7
|
+
# CODEV_PR_BASE, CODEV_PR_HEAD, CODEV_PR_REPO, CODEV_PR_DRAFT (optional)
|
|
8
|
+
# CODEV_PR_LOGIN (optional, gitea-only: tea's --login, for multi-login hosts)
|
|
9
|
+
# Output: {"number": <int>, "url": "<web url>"}
|
|
10
|
+
#
|
|
11
|
+
# Creates the PR with the raw REST passthrough (`tea api -X POST …/pulls`),
|
|
12
|
+
# which RETURNS THE CREATED PR — number and `html_url` — in its response body.
|
|
13
|
+
# There is no lookup afterwards, so there is nothing to race and nothing to
|
|
14
|
+
# paginate.
|
|
15
|
+
#
|
|
16
|
+
# Why not `tea pulls create`: it prints a rendered, line-wrapped, ANSI-decorated
|
|
17
|
+
# view of the new PR rather than anything parseable, so it has to be followed by
|
|
18
|
+
# a search for the PR it just made. That search was the bug. #1146 established
|
|
19
|
+
# that Gitea caps every list response at the server's `max_response_items`
|
|
20
|
+
# (default 50 — confirmed against Forgejo 15.0.2: `settings/api` reports 50, and
|
|
21
|
+
# a `?limit=200` request returns exactly 50 items where paging at 50 returns 53),
|
|
22
|
+
# so `tea pulls list --limit 200` silently truncates. On a busy repo the
|
|
23
|
+
# just-created PR falls off the first page and this script would have reported
|
|
24
|
+
# failure for a PR that exists — inviting a duplicate retry.
|
|
25
|
+
#
|
|
26
|
+
# Verified against tea 0.14.2 + Forgejo 15.0.2:
|
|
27
|
+
# - POST repos/{owner}/{repo}/pulls returns the full PR object, incl. `number`
|
|
28
|
+
# and `html_url` (its `url` is the browser page too on create, unlike the GET
|
|
29
|
+
# shape where `url` is the API endpoint — hence `.html_url // .url`).
|
|
30
|
+
# - `{owner}`/`{repo}` are substituted by tea from the repo context, and
|
|
31
|
+
# `--repo <owner>/<name>` supplies that context when the cwd has no Gitea
|
|
32
|
+
# remote. Verified with https and scp-style Gitea remotes.
|
|
33
|
+
# - `base` is REQUIRED by the API (it answers `[Base]: Required` without it),
|
|
34
|
+
# unlike `tea pulls create`, which defaults it client-side. So when
|
|
35
|
+
# CODEV_PR_BASE is unset this resolves the repo's default branch first.
|
|
36
|
+
# - `draft: true` in the payload is SILENTLY IGNORED (the response comes back
|
|
37
|
+
# `draft: false`). Gitea marks a PR draft by a `WIP:` title prefix, which is
|
|
38
|
+
# exactly what `tea pulls create --draft` does, so that is replicated here.
|
|
39
|
+
# - `tea api` EXITS 0 on HTTP errors, printing the error body (JSON with
|
|
40
|
+
# `.message`, or bare text like `404 page not found`). Failure therefore has
|
|
41
|
+
# to be detected by inspecting the response, never by exit status — see the
|
|
42
|
+
# assertion below, which is the load-bearing check in this script.
|
|
43
|
+
set -e
|
|
44
|
+
|
|
45
|
+
if [ -z "$CODEV_PR_TITLE" ]; then
|
|
46
|
+
echo "pr-create: CODEV_PR_TITLE is required" >&2
|
|
47
|
+
exit 2
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# An empty body is allowed; an *absent* one is not. Without this, forgetting the
|
|
51
|
+
# variable opens a PR with no body at exit 0 — the silent failure #1455 is about.
|
|
52
|
+
if [ -z "${CODEV_PR_BODY+x}" ]; then
|
|
53
|
+
echo "pr-create: CODEV_PR_BODY is required (set it to \"\" for an empty body)" >&2
|
|
54
|
+
exit 2
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
head=$CODEV_PR_HEAD
|
|
58
|
+
if [ -z "$head" ]; then
|
|
59
|
+
head=$(git rev-parse --abbrev-ref HEAD)
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Gitea has no draft flag on the API; a "WIP:" title prefix is the draft marker.
|
|
63
|
+
title=$CODEV_PR_TITLE
|
|
64
|
+
if [ "$CODEV_PR_DRAFT" = "1" ]; then
|
|
65
|
+
title="WIP: $CODEV_PR_TITLE"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Shared tea options: which host/repo to talk to. `{owner}`/`{repo}` in the
|
|
69
|
+
# endpoint are filled from this context.
|
|
70
|
+
set --
|
|
71
|
+
if [ -n "$CODEV_PR_REPO" ]; then set -- "$@" --repo "$CODEV_PR_REPO"; fi
|
|
72
|
+
if [ -n "$CODEV_PR_LOGIN" ]; then set -- "$@" --login "$CODEV_PR_LOGIN"; fi
|
|
73
|
+
|
|
74
|
+
base=$CODEV_PR_BASE
|
|
75
|
+
if [ -z "$base" ]; then
|
|
76
|
+
# The API rejects a missing base, so mirror `tea pulls create`'s default.
|
|
77
|
+
base_response=$(tea api "$@" 'repos/{owner}/{repo}') || base_response=
|
|
78
|
+
base=$(printf '%s' "$base_response" | jq -r '.default_branch // empty' 2>/dev/null) || base=
|
|
79
|
+
if [ -z "$base" ]; then
|
|
80
|
+
# An unresolvable repo (404 — same cause as the POST path below) means the
|
|
81
|
+
# actual remedy is CODEV_PR_REPO, not CODEV_PR_BASE; only a resolvable repo
|
|
82
|
+
# with a genuinely missing default_branch calls for CODEV_PR_BASE.
|
|
83
|
+
case "$base_response" in
|
|
84
|
+
404*)
|
|
85
|
+
echo "pr-create: Gitea answered 404 — could not resolve owner/repo for this repository." >&2
|
|
86
|
+
echo "pr-create: set CODEV_PR_REPO=owner/repo, or run from a checkout whose remote is a configured Gitea host." >&2
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
echo "pr-create: could not resolve the repository's default branch; set CODEV_PR_BASE" >&2
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Build the payload with jq so an arbitrary body — quotes, backticks, newlines,
|
|
97
|
+
# backslashes, unicode — is JSON-escaped rather than interpolated, and feed it
|
|
98
|
+
# on stdin (`-d @-`) so it never has to survive an argv round-trip.
|
|
99
|
+
response=$(
|
|
100
|
+
jq -n --arg title "$title" --arg body "$CODEV_PR_BODY" \
|
|
101
|
+
--arg head "$head" --arg base "$base" \
|
|
102
|
+
'{title: $title, body: $body, head: $head, base: $base}' \
|
|
103
|
+
| tea api "$@" -X POST -d @- 'repos/{owner}/{repo}/pulls'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# `tea api` exits 0 on HTTP errors, so the response body is the ONLY signal that
|
|
107
|
+
# the PR was created. Trusting the exit code here would reintroduce exactly the
|
|
108
|
+
# silent success #1455 exists to kill — this time in the fix for it. So assert
|
|
109
|
+
# the response really is a PR object: an object carrying a numeric `number` AND
|
|
110
|
+
# a non-empty browser URL. Anything else is a failure, however it is dressed.
|
|
111
|
+
result=$(printf '%s' "$response" | jq -c '
|
|
112
|
+
if type != "object" then empty
|
|
113
|
+
elif (.number | type) != "number" then empty
|
|
114
|
+
else
|
|
115
|
+
(.html_url // .url) as $url
|
|
116
|
+
| if ($url | type) == "string" and ($url | length) > 0
|
|
117
|
+
then {number: .number, url: $url}
|
|
118
|
+
else empty
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
' 2>/dev/null) || result=
|
|
122
|
+
|
|
123
|
+
if [ -z "$result" ]; then
|
|
124
|
+
# A numeric `number` with no usable URL means the PR WAS created and only the
|
|
125
|
+
# URL is missing. Say so, and name the number: the operator must not read this
|
|
126
|
+
# as "nothing happened" and retry into a duplicate.
|
|
127
|
+
number=$(printf '%s' "$response" | jq -r '
|
|
128
|
+
if type == "object" and (.number | type) == "number" then .number else empty end
|
|
129
|
+
' 2>/dev/null) || number=
|
|
130
|
+
if [ -n "$number" ]; then
|
|
131
|
+
echo "pr-create: PR #$number WAS CREATED, but the Gitea response carried no browser URL." >&2
|
|
132
|
+
echo "pr-create: do not retry — that would open a duplicate. Response: $response" >&2
|
|
133
|
+
exit 1
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
message=$(printf '%s' "$response" | jq -r '.message // empty' 2>/dev/null) || message=
|
|
137
|
+
if [ -n "$message" ]; then
|
|
138
|
+
echo "pr-create: Gitea refused to create the PR: $message" >&2
|
|
139
|
+
else
|
|
140
|
+
case "$response" in
|
|
141
|
+
404*)
|
|
142
|
+
# `{owner}`/`{repo}` stayed unsubstituted, so tea requested `repos//pulls`.
|
|
143
|
+
# Name the remedy rather than leaving a bare 404 (cf. `_lib.sh#gitea_repo`).
|
|
144
|
+
echo "pr-create: Gitea answered 404 — could not resolve owner/repo for this repository." >&2
|
|
145
|
+
echo "pr-create: set CODEV_PR_REPO=owner/repo, or run from a checkout whose remote is a configured Gitea host." >&2
|
|
146
|
+
;;
|
|
147
|
+
*)
|
|
148
|
+
echo "pr-create: unexpected response from the Gitea API: $response" >&2
|
|
149
|
+
;;
|
|
150
|
+
esac
|
|
151
|
+
fi
|
|
152
|
+
exit 1
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
printf '%s\n' "$result"
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Forge concept: pr-exists (Gitea via tea CLI)
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
3
|
+
# forge-executable: tea
|
|
4
|
+
# Input: CODEV_BRANCH_NAME
|
|
5
|
+
# Output: "true" or "false"
|
|
6
|
+
#
|
|
7
|
+
# Returns true for OPEN or MERGED pulls only; closed-not-merged pulls are
|
|
8
|
+
# excluded. `tea pulls list` emits `.head` as a string (not `{ref}`) and reports
|
|
9
|
+
# merged PRs as state "merged" with no `.merged` boolean, so its output can't
|
|
10
|
+
# satisfy the `.head.ref` / `.merged` predicate below. Route through the raw
|
|
11
|
+
# REST passthrough, whose PR objects carry nested `.head.ref` and a `.merged`
|
|
12
|
+
# bool. `tea api` needs an explicit owner/repo in the path (unlike `tea pulls`,
|
|
13
|
+
# which auto-detects it from the local git remote), so resolve it here: honor
|
|
14
|
+
# CODEV_REPO when set, else derive owner/repo from origin's URL (handles https,
|
|
15
|
+
# ssh, and scp-style remotes, with or without a .git suffix).
|
|
16
|
+
#
|
|
17
|
+
# Caveat (Gitea behavior, not a codev bug): for a merged PR whose source branch
|
|
18
|
+
# was deleted, Gitea returns `.head.ref == "refs/pull/N/head"` instead of the
|
|
19
|
+
# original branch name, so a branch-name match won't hit a merged+deleted
|
|
20
|
+
# branch. That doesn't affect the "does an open/merged PR exist for the branch
|
|
21
|
+
# I'm about to push" use case.
|
|
22
|
+
#
|
|
23
|
+
# `state=all` is paginated (Gitea caps a page at max_response_items, default 50)
|
|
24
|
+
# so a branch whose PR isn't in the most recent ~50 would false-negative and
|
|
25
|
+
# block a porch pr_exists gate — tea_api_paged walks every page (see _lib.sh).
|
|
26
|
+
. "$(dirname "$0")/_lib.sh"
|
|
27
|
+
REPO="$(gitea_repo)" || exit 1
|
|
28
|
+
# Capture the paginator's output before piping to jq: in POSIX sh (no
|
|
29
|
+
# pipefail), a `cmd | jq` pipeline reports jq's exit status (0) even when
|
|
30
|
+
# `cmd` failed mid-walk, which would surface a real error as a false-negative
|
|
31
|
+
# "no PR exists" and silently pass a porch pr_exists gate.
|
|
32
|
+
PULLS="$(tea_api_paged "repos/${REPO}/pulls" "state=all")" || exit 1
|
|
33
|
+
printf '%s' "$PULLS" | jq --arg branch "$CODEV_BRANCH_NAME" \
|
|
34
|
+
'[.[] | select(.head.ref == $branch and (.state == "open" or .merged == true))] | length > 0'
|
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# Forge concept: pr-list (Gitea via tea CLI)
|
|
2
|
+
# Forge concept: pr-list (Gitea via tea CLI) — open pulls
|
|
3
|
+
# forge-executable: tea
|
|
4
|
+
# Output: JSON [{number, title, url, reviewDecision, body, createdAt, author,
|
|
5
|
+
# reviewRequests, isDraft}] (PrListItem in forge-contracts.ts)
|
|
3
6
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
# `tea pulls list --fields …,description` errors ("invalid field 'description'")
|
|
8
|
+
# and its flattened output can't carry a PR body, draft flag, or requested
|
|
9
|
+
# reviewers. Route through the raw REST passthrough instead, whose PR objects
|
|
10
|
+
# expose all of them. `tea api` needs an explicit owner/repo in the path (unlike
|
|
11
|
+
# `tea pulls`, which auto-detects it from the local git remote), so resolve it
|
|
12
|
+
# here: honor CODEV_REPO when set, else derive owner/repo from origin's URL
|
|
13
|
+
# (handles https, ssh, and scp-style remotes, with or without a .git suffix).
|
|
14
|
+
#
|
|
15
|
+
# Field mapping:
|
|
16
|
+
# .number -> number (already an int in the REST shape)
|
|
17
|
+
# .html_url -> url (browser page; Gitea `.url` is the API endpoint)
|
|
18
|
+
# .body -> body
|
|
19
|
+
# .created_at -> createdAt
|
|
20
|
+
# .user.login -> author.login
|
|
21
|
+
# .requested_reviewers[].login -> reviewRequests (user logins; teams have no login → dropped)
|
|
22
|
+
# .draft -> isDraft
|
|
23
|
+
# reviewDecision -> "" (Gitea has no GitHub-equivalent review-decision summary)
|
|
24
|
+
#
|
|
25
|
+
# The open-pulls list is paginated (Gitea caps a page at max_response_items,
|
|
26
|
+
# default 50), so tea_api_paged walks every page rather than silently truncating
|
|
27
|
+
# at ~50 open PRs (see _lib.sh).
|
|
28
|
+
. "$(dirname "$0")/_lib.sh"
|
|
29
|
+
REPO="$(gitea_repo)" || exit 1
|
|
30
|
+
# Capture the paginator's output before piping to jq: in POSIX sh (no
|
|
31
|
+
# pipefail), a `cmd | jq` pipeline reports jq's exit status (0) even when
|
|
32
|
+
# `cmd` failed mid-walk, which would silently truncate the list instead of
|
|
33
|
+
# surfacing the error.
|
|
34
|
+
PULLS="$(tea_api_paged "repos/${REPO}/pulls" "state=open")" || exit 1
|
|
35
|
+
printf '%s' "$PULLS" | jq '[.[] | {
|
|
36
|
+
number,
|
|
27
37
|
title,
|
|
28
|
-
|
|
29
|
-
url,
|
|
38
|
+
url: (.html_url // .url),
|
|
30
39
|
reviewDecision: "",
|
|
31
|
-
body: (.
|
|
32
|
-
createdAt: .
|
|
33
|
-
author: {login: .
|
|
34
|
-
reviewRequests: [],
|
|
35
|
-
isDraft: false
|
|
40
|
+
body: (.body // ""),
|
|
41
|
+
createdAt: .created_at,
|
|
42
|
+
author: {login: .user.login},
|
|
43
|
+
reviewRequests: [ (.requested_reviewers // [])[] | .login // empty ],
|
|
44
|
+
isDraft: (.draft // false)
|
|
36
45
|
}]'
|
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Forge concept: pr-view (Gitea via tea CLI)
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
3
|
+
# forge-executable: tea
|
|
4
|
+
# Input: CODEV_PR_NUMBER
|
|
5
|
+
# Output: JSON {title, body, state, url, author{login}, baseRefName, headRefName,
|
|
6
|
+
# additions, deletions} (see PrViewResult in forge-contracts.ts)
|
|
7
|
+
#
|
|
8
|
+
# `tea pulls view N --output json` returns a table header / empty list rather
|
|
9
|
+
# than the PR object, so route through the raw REST passthrough. `tea api`
|
|
10
|
+
# needs an explicit owner/repo in the path (unlike `tea pulls`, which
|
|
11
|
+
# auto-detects it from the local git remote), so resolve it here: honor
|
|
12
|
+
# CODEV_REPO when set, else derive owner/repo from origin's URL (handles
|
|
13
|
+
# https, ssh, and scp-style remotes, with or without a .git suffix).
|
|
14
|
+
#
|
|
15
|
+
# `url` is the PR's browser page (`html_url`). Gitea's own `url` field is the
|
|
16
|
+
# API endpoint (would render raw JSON in a browser), so map `html_url` and fall
|
|
17
|
+
# back to `url` only if it's absent — the same choice PIR #1179 made when this
|
|
18
|
+
# concept still went through `tea pulls view`.
|
|
19
|
+
#
|
|
20
|
+
# SHAPE VALIDATION. `tea api` exits 0 on an HTTP error and prints the error
|
|
21
|
+
# BODY, e.g. {"message":"pull does not exist [index: 42]","url":"…/swagger"}.
|
|
22
|
+
# Normalizing that unchecked produced a structurally valid, entirely wrong
|
|
23
|
+
# contract object at exit 0 — every field null except `url`, which took the
|
|
24
|
+
# error body's own `url` (the swagger link) and shipped it to callers as the
|
|
25
|
+
# PR's browser page. Required fields are type-checked first and anything else
|
|
26
|
+
# is a hard failure carrying the server's message on stderr.
|
|
27
|
+
. "$(dirname "$0")/_lib.sh"
|
|
28
|
+
REPO="$(gitea_repo)" || exit 1
|
|
29
|
+
# Capture before piping: in POSIX sh (no pipefail) a `tea api … | jq` pipeline
|
|
30
|
+
# reports jq's exit status, so a failed fetch would surface as jq's exit-0 on
|
|
31
|
+
# empty stdin rather than an error.
|
|
32
|
+
PR="$(tea api "repos/${REPO}/pulls/${CODEV_PR_NUMBER}")" || exit 1
|
|
33
|
+
# An empty body at exit 0 would give jq nothing to work on: it emits no output
|
|
34
|
+
# and exits 0, so the script would "succeed" with empty stdout instead of
|
|
35
|
+
# failing. The validator below never runs on an empty response, so guard here.
|
|
36
|
+
if [ -z "$PR" ]; then
|
|
37
|
+
echo "gitea forge: empty \`tea api\` response for pull ${CODEV_PR_NUMBER}" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
printf '%s' "$PR" | jq '
|
|
41
|
+
if (type == "object")
|
|
42
|
+
and ((.number | type) == "number")
|
|
43
|
+
and ((.title | type) == "string")
|
|
44
|
+
and ((.state | type) == "string")
|
|
45
|
+
and ((.user.login | type) == "string")
|
|
46
|
+
and ((.base.ref | type) == "string")
|
|
47
|
+
and ((.head.ref | type) == "string")
|
|
48
|
+
then .
|
|
49
|
+
else
|
|
50
|
+
("gitea forge: unexpected `tea api` response for pull "
|
|
51
|
+
+ (env.CODEV_PR_NUMBER // "?") + ": "
|
|
52
|
+
+ ((.message // .) | tostring)
|
|
53
|
+
+ "\n") | halt_error(1)
|
|
54
|
+
end
|
|
55
|
+
| {
|
|
56
|
+
title,
|
|
57
|
+
body: (.body // ""),
|
|
58
|
+
state,
|
|
59
|
+
url: (.html_url // .url),
|
|
60
|
+
author: {login: .user.login},
|
|
61
|
+
baseRefName: .base.ref,
|
|
62
|
+
headRefName: .head.ref,
|
|
63
|
+
# Type-checked, not just defaulted: the contract types these as numbers, and
|
|
64
|
+
# a non-numeric value here would be passed straight through.
|
|
65
|
+
additions: (if (.additions | type) == "number" then .additions else 0 end),
|
|
66
|
+
deletions: (if (.deletions | type) == "number" then .deletions else 0 end)
|
|
67
|
+
}'
|
|
@@ -1,27 +1,97 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Forge concept: recently-merged (Gitea via tea CLI)
|
|
3
|
+
# forge-executable: tea
|
|
4
|
+
# Input: CODEV_SINCE_DATE (optional, ISO-8601 timestamp)
|
|
5
|
+
# Output: JSON [{number, title, url, body, createdAt, mergedAt, headRefName}]
|
|
6
|
+
# (MergedPrItem in forge-contracts.ts)
|
|
3
7
|
#
|
|
4
|
-
# `tea pulls list --
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
# `tea pulls list --fields …,head,description,merged` errors on the `description`
|
|
9
|
+
# field and emits `.head` as a string, so it can't populate `body` or
|
|
10
|
+
# `.head.ref`. Route through the raw REST passthrough instead, whose closed
|
|
11
|
+
# pulls carry `.merged`, `.merged_at`, nested `.head.ref`, and `.body`. Keep
|
|
12
|
+
# only merged pulls (closed-without-merge have `.merged == false`). `tea api`
|
|
13
|
+
# needs an explicit owner/repo in the path (unlike `tea pulls`, which
|
|
14
|
+
# auto-detects it from the local git remote), so resolve it here: honor
|
|
15
|
+
# CODEV_REPO when set, else derive owner/repo from origin's URL (handles https,
|
|
16
|
+
# ssh, and scp-style remotes, with or without a .git suffix).
|
|
17
|
+
#
|
|
18
|
+
# The closed-pulls list is paginated (Gitea caps a page at max_response_items,
|
|
19
|
+
# default 50), so on a busy repo the most-recent merges could push older ones
|
|
20
|
+
# past the first page — tea_api_paged walks every page (see _lib.sh).
|
|
21
|
+
#
|
|
22
|
+
# CODEV_SINCE_DATE BOUNDS THE WALK. This concept feeds a 24h analytics window,
|
|
23
|
+
# but the closed-pulls list is the repo's whole merge history: on an
|
|
24
|
+
# established repo an unbounded walk issues up to GITEA_MAX_PAGES sequential
|
|
25
|
+
# requests inside forge's 30s timeout, and a timeout yields `null` — a worse
|
|
26
|
+
# outcome for the dashboard than truncation was. When CODEV_SINCE_DATE is set
|
|
27
|
+
# we ask the server for update-time-descending order and stop at the first page
|
|
28
|
+
# that reaches back past the cutoff.
|
|
29
|
+
#
|
|
30
|
+
# The stop filter does not take the sort on trust. What it actually needs is
|
|
31
|
+
# the ORDERING, not the parameter, so it checks for the ordering directly and
|
|
32
|
+
# fires only when all of this holds:
|
|
33
|
+
# - the current page is non-increasing in `updated_at`,
|
|
34
|
+
# - the PREVIOUS page was too, and its oldest entry is no older than this
|
|
35
|
+
# page's newest — i.e. the order survives a page boundary, so page-local
|
|
36
|
+
# sorting or a coincidentally-descending first page isn't enough,
|
|
37
|
+
# - some entry on this page predates the cutoff.
|
|
38
|
+
# Never on page 1: with nothing to compare against, one internally-descending
|
|
39
|
+
# page proves nothing about the pages behind it. Costing one extra request is
|
|
40
|
+
# the right trade against dropping a merge.
|
|
41
|
+
#
|
|
42
|
+
# Given that ordering, `updated_at >= merged_at` (a merge updates the PR) means
|
|
43
|
+
# nothing merged after the cutoff can sit beyond the first page whose update
|
|
44
|
+
# times have fallen behind it. A server that ignores `sort=recentupdate` fails
|
|
45
|
+
# these checks and we fall back to the full walk — slower, never wrong.
|
|
46
|
+
#
|
|
47
|
+
# Timestamps go through `gitea_epoch` because Gitea emits RFC3339 in the
|
|
48
|
+
# server's timezone, not necessarily `Z`.
|
|
49
|
+
. "$(dirname "$0")/_lib.sh"
|
|
50
|
+
REPO="$(gitea_repo)" || exit 1
|
|
51
|
+
|
|
52
|
+
if [ -n "$CODEV_SINCE_DATE" ]; then
|
|
53
|
+
QUERY="state=closed&sort=recentupdate"
|
|
54
|
+
# `$prev` is the previous page, bound by tea_api_paged (null on page 1).
|
|
55
|
+
STOP="${GITEA_JQ_LIB}"'
|
|
56
|
+
def descending: . as $a | [ range(($a | length) - 1) | $a[.] >= $a[. + 1] ] | all;
|
|
57
|
+
def times: [ .[] | (.updated_at | gitea_epoch) ];
|
|
58
|
+
|
|
59
|
+
times as $t
|
|
60
|
+
| ($prev | if . == null then null else times end) as $p
|
|
61
|
+
| (env.CODEV_SINCE_DATE | gitea_epoch) as $since
|
|
62
|
+
| ($since != null)
|
|
63
|
+
and ($p != null)
|
|
64
|
+
and (($t | length) > 0) and (($p | length) > 0)
|
|
65
|
+
and ([ $t[] | . != null ] | all) and ([ $p[] | . != null ] | all)
|
|
66
|
+
and ($t | descending) and ($p | descending)
|
|
67
|
+
and (($p | min) >= ($t | max))
|
|
68
|
+
and (($t | min) < $since)
|
|
69
|
+
'
|
|
70
|
+
else
|
|
71
|
+
QUERY="state=closed"
|
|
72
|
+
STOP=""
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Capture the paginator's output before piping to jq: in POSIX sh (no
|
|
76
|
+
# pipefail), a `cmd | jq` pipeline reports jq's exit status (0) even when
|
|
77
|
+
# `cmd` failed mid-walk, which would silently truncate the list instead of
|
|
78
|
+
# surfacing the error.
|
|
79
|
+
PULLS="$(tea_api_paged "repos/${REPO}/pulls" "$QUERY" "$STOP")" || exit 1
|
|
80
|
+
printf '%s' "$PULLS" | jq "${GITEA_JQ_LIB}"'
|
|
81
|
+
(env.CODEV_SINCE_DATE | gitea_epoch) as $since
|
|
82
|
+
| [ .[]
|
|
83
|
+
| select(.merged == true)
|
|
84
|
+
# Drop merges older than the cutoff. An unparseable/absent timestamp on
|
|
85
|
+
# either side keeps the item — the caller filters the window again, so
|
|
86
|
+
# over-reporting is harmless where dropping a real merge is not.
|
|
87
|
+
| select($since == null
|
|
88
|
+
or ((.merged_at | gitea_epoch) as $m | $m == null or $m >= $since))
|
|
89
|
+
| {
|
|
90
|
+
number,
|
|
20
91
|
title,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
mergedAt: .updated,
|
|
92
|
+
url: (.html_url // .url),
|
|
93
|
+
body: (.body // ""),
|
|
94
|
+
createdAt: .created_at,
|
|
95
|
+
mergedAt: .merged_at,
|
|
26
96
|
headRefName: (.head.ref // "")
|
|
27
97
|
}]'
|
|
@@ -1,3 +1,32 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Forge concept: user-identity (Gitea via tea CLI)
|
|
3
|
-
|
|
3
|
+
# forge-executable: tea
|
|
4
|
+
# Output: plain text username
|
|
5
|
+
#
|
|
6
|
+
# `tea whoami` has no `--output json` flag (its only documented option is
|
|
7
|
+
# --help), so it can't feed a jq pipeline. Route through the raw REST
|
|
8
|
+
# passthrough instead: `tea api user` returns the Gitea `User` object, whose
|
|
9
|
+
# `.login` is the authenticated username (mirrors `gh api user --jq .login`).
|
|
10
|
+
#
|
|
11
|
+
# SHAPE VALIDATION. `tea api` exits 0 on an HTTP error and prints the error
|
|
12
|
+
# BODY, which has no `.login` — `jq -r .login` then printed the literal string
|
|
13
|
+
# "null" at exit 0, and callers took that as the current user's handle. Check
|
|
14
|
+
# for a non-empty string login first and fail with the server's message
|
|
15
|
+
# otherwise. The response is captured before the pipe because POSIX sh has no
|
|
16
|
+
# pipefail: `tea api user | jq` would report jq's status, not tea's.
|
|
17
|
+
USER_JSON="$(tea api user)" || exit 1
|
|
18
|
+
# jq given empty input emits nothing and exits 0, so an empty body at exit 0
|
|
19
|
+
# would leave the script "succeeding" with no username at all.
|
|
20
|
+
if [ -z "$USER_JSON" ]; then
|
|
21
|
+
echo "gitea forge: empty \`tea api user\` response" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
printf '%s' "$USER_JSON" | jq -r '
|
|
25
|
+
if (type == "object") and ((.login | type) == "string")
|
|
26
|
+
and ((.login | test("^\\s*$")) | not)
|
|
27
|
+
then .login
|
|
28
|
+
else
|
|
29
|
+
("gitea forge: unexpected `tea api user` response: "
|
|
30
|
+
+ ((.message // .) | tostring)
|
|
31
|
+
+ "\n") | halt_error(1)
|
|
32
|
+
end'
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Forge concept: issue-view (GitHub via gh CLI)
|
|
3
3
|
# Input: CODEV_ISSUE_ID
|
|
4
|
-
# Output: JSON {title, body, state, url, comments[]}
|
|
5
|
-
|
|
4
|
+
# Output: JSON {title, body, state, url, author, createdAt, assignees, labels, milestone, comments[]}
|
|
5
|
+
# author/createdAt/assignees/labels/milestone are optional by contract; `gh`
|
|
6
|
+
# emits them in the neutral shapes the IssueView contract expects (author.login,
|
|
7
|
+
# assignees[].login, labels[].name, milestone.title or null when unset).
|
|
8
|
+
exec gh issue view "$CODEV_ISSUE_ID" --json title,body,state,url,author,createdAt,assignees,labels,milestone,comments
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Forge concept: pr-create (GitHub via gh CLI)
|
|
3
|
+
# forge-executable: gh
|
|
4
|
+
#
|
|
5
|
+
# Input: CODEV_PR_TITLE (required)
|
|
6
|
+
# CODEV_PR_BODY (required, may be empty)
|
|
7
|
+
# CODEV_PR_BASE, CODEV_PR_HEAD, CODEV_PR_REPO, CODEV_PR_DRAFT (optional)
|
|
8
|
+
# Output: {"number": <int>, "url": "<web url>"}
|
|
9
|
+
#
|
|
10
|
+
# `gh pr create` prints the new PR's URL on stdout; the number is its last path
|
|
11
|
+
# segment. Anything gh writes that isn't a URL (hints, prompts) is ignored.
|
|
12
|
+
set -e
|
|
13
|
+
|
|
14
|
+
if [ -z "$CODEV_PR_TITLE" ]; then
|
|
15
|
+
echo "pr-create: CODEV_PR_TITLE is required" >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# An empty body is allowed; an *absent* one is not. Without this, forgetting the
|
|
20
|
+
# variable opens a PR with no body at exit 0 — the silent failure #1455 is about.
|
|
21
|
+
if [ -z "${CODEV_PR_BODY+x}" ]; then
|
|
22
|
+
echo "pr-create: CODEV_PR_BODY is required (set it to \"\" for an empty body)" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
set -- --title "$CODEV_PR_TITLE" --body "$CODEV_PR_BODY"
|
|
27
|
+
if [ -n "$CODEV_PR_BASE" ]; then set -- "$@" --base "$CODEV_PR_BASE"; fi
|
|
28
|
+
if [ -n "$CODEV_PR_HEAD" ]; then set -- "$@" --head "$CODEV_PR_HEAD"; fi
|
|
29
|
+
if [ -n "$CODEV_PR_REPO" ]; then set -- "$@" --repo "$CODEV_PR_REPO"; fi
|
|
30
|
+
if [ "$CODEV_PR_DRAFT" = "1" ]; then set -- "$@" --draft; fi
|
|
31
|
+
|
|
32
|
+
url=$(gh pr create "$@" | grep -E '^https?://' | tail -n 1)
|
|
33
|
+
|
|
34
|
+
if [ -z "$url" ]; then
|
|
35
|
+
echo "pr-create: gh pr create returned no PR URL" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
number=${url##*/}
|
|
40
|
+
case "$number" in
|
|
41
|
+
'' | *[!0-9]*)
|
|
42
|
+
echo "pr-create: could not parse a PR number from '$url'" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
printf '{"number":%s,"url":"%s"}\n' "$number" "$url"
|
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
# Forge concept: pr-search (GitHub via gh CLI)
|
|
3
3
|
# Input: CODEV_SEARCH_QUERY
|
|
4
4
|
# Output: JSON [{number, headRefName, baseRefName}]
|
|
5
|
-
|
|
5
|
+
# --state all is required so the search includes merged/closed PRs; without it
|
|
6
|
+
# `gh pr list` defaults to --state open and post-merge lookups return nothing (#759).
|
|
7
|
+
exec gh pr list --state all --search "$CODEV_SEARCH_QUERY" --json number,headRefName,baseRefName
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Forge concept: issue-view (GitLab via glab CLI)
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
3
|
+
# Maps GitLab's fields into the forge-neutral IssueView shape. `. + {…}` is
|
|
4
|
+
# non-destructive for keys we don't touch; the mapped keys are optional by
|
|
5
|
+
# contract, so a shape mismatch degrades to an omitted line rather than a crash
|
|
6
|
+
# (jq returns null for missing keys, and the guards below tolerate null):
|
|
7
|
+
# web_url -> url
|
|
8
|
+
# author.username -> author.login
|
|
9
|
+
# created_at -> createdAt
|
|
10
|
+
# assignees[].username -> assignees[].login
|
|
11
|
+
# labels[] -> labels[].name (newer glab emits label objects; older emits strings)
|
|
12
|
+
# milestone.title -> milestone.title (null when unset)
|
|
13
|
+
# Best-effort preset (Spec 589): fields GitLab doesn't expose stay absent. The
|
|
14
|
+
# contract's optional fields are added only when present (via `+ {…}` merges) so
|
|
15
|
+
# an absent field is OMITTED, not emitted as null — `author?`/`createdAt?` are not
|
|
16
|
+
# nullable (only `milestone` is). `exec` is dropped: with a pipe it would replace
|
|
17
|
+
# only the glab subshell, not the whole pipeline, so it's misleading here.
|
|
18
|
+
glab issue view "$CODEV_ISSUE_ID" --output json | jq '
|
|
19
|
+
.
|
|
20
|
+
+ {url: .web_url}
|
|
21
|
+
+ {assignees: [(.assignees // [])[] | {login: .username}]}
|
|
22
|
+
+ {labels: [(.labels // [])[] | if type == "string" then {name: .} else {name: .name} end]}
|
|
23
|
+
+ (if .author.username then {author: {login: .author.username}} else {} end)
|
|
24
|
+
+ (if .created_at then {createdAt: .created_at} else {} end)
|
|
25
|
+
+ (if .milestone then {milestone: {title: .milestone.title}} else {} end)
|
|
26
|
+
'
|