@cluesmith/codev 3.3.2 → 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.
Files changed (157) hide show
  1. package/dashboard-dist/assets/{index-D3XaKpZW.js → index-CUIjS_Vb.js} +41 -41
  2. package/dashboard-dist/assets/index-CUIjS_Vb.js.map +1 -0
  3. package/dashboard-dist/assets/index-DnYLy5C_.css +32 -0
  4. package/dashboard-dist/index.html +2 -2
  5. package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
  6. package/dist/agent-farm/commands/inbox.js +67 -10
  7. package/dist/agent-farm/commands/inbox.js.map +1 -1
  8. package/dist/agent-farm/commands/interrupt.d.ts +4 -1
  9. package/dist/agent-farm/commands/interrupt.d.ts.map +1 -1
  10. package/dist/agent-farm/commands/interrupt.js +6 -3
  11. package/dist/agent-farm/commands/interrupt.js.map +1 -1
  12. package/dist/agent-farm/commands/reset.d.ts +4 -1
  13. package/dist/agent-farm/commands/reset.d.ts.map +1 -1
  14. package/dist/agent-farm/commands/reset.js +6 -3
  15. package/dist/agent-farm/commands/reset.js.map +1 -1
  16. package/dist/agent-farm/commands/send.d.ts +28 -0
  17. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  18. package/dist/agent-farm/commands/send.js +64 -6
  19. package/dist/agent-farm/commands/send.js.map +1 -1
  20. package/dist/agent-farm/db/index.d.ts +3 -1
  21. package/dist/agent-farm/db/index.d.ts.map +1 -1
  22. package/dist/agent-farm/db/index.js +8 -443
  23. package/dist/agent-farm/db/index.js.map +1 -1
  24. package/dist/agent-farm/db/mailbox.d.ts +47 -7
  25. package/dist/agent-farm/db/mailbox.d.ts.map +1 -1
  26. package/dist/agent-farm/db/mailbox.js +56 -12
  27. package/dist/agent-farm/db/mailbox.js.map +1 -1
  28. package/dist/agent-farm/db/migrations.d.ts +57 -0
  29. package/dist/agent-farm/db/migrations.d.ts.map +1 -0
  30. package/dist/agent-farm/db/migrations.js +530 -0
  31. package/dist/agent-farm/db/migrations.js.map +1 -0
  32. package/dist/agent-farm/db/schema.d.ts +1 -1
  33. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  34. package/dist/agent-farm/db/schema.js +1 -0
  35. package/dist/agent-farm/db/schema.js.map +1 -1
  36. package/dist/agent-farm/db/types.d.ts +22 -0
  37. package/dist/agent-farm/db/types.d.ts.map +1 -1
  38. package/dist/agent-farm/db/types.js.map +1 -1
  39. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  40. package/dist/agent-farm/lib/tunnel-client.js +43 -14
  41. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  42. package/dist/agent-farm/servers/cron-delivery.d.ts +7 -1
  43. package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -1
  44. package/dist/agent-farm/servers/cron-delivery.js +2 -1
  45. package/dist/agent-farm/servers/cron-delivery.js.map +1 -1
  46. package/dist/agent-farm/servers/gate-profiles.d.ts +21 -0
  47. package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
  48. package/dist/agent-farm/servers/gate-profiles.js +36 -4
  49. package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
  50. package/dist/agent-farm/servers/mailbox-delivery.d.ts +83 -8
  51. package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
  52. package/dist/agent-farm/servers/mailbox-delivery.js +102 -26
  53. package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
  54. package/dist/agent-farm/servers/mailbox-wiring.d.ts +48 -15
  55. package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
  56. package/dist/agent-farm/servers/mailbox-wiring.js +116 -22
  57. package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
  58. package/dist/agent-farm/servers/render-gate.d.ts +24 -1
  59. package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
  60. package/dist/agent-farm/servers/render-gate.js +50 -12
  61. package/dist/agent-farm/servers/render-gate.js.map +1 -1
  62. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  63. package/dist/agent-farm/servers/tower-cron.js +3 -2
  64. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  65. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  66. package/dist/agent-farm/servers/tower-instances.js +11 -3
  67. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  68. package/dist/agent-farm/servers/tower-routes.d.ts +32 -0
  69. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  70. package/dist/agent-farm/servers/tower-routes.js +133 -13
  71. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  72. package/dist/agent-farm/servers/tower-server.js +15 -2
  73. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  74. package/dist/agent-farm/servers/tower-terminals.d.ts +21 -0
  75. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  76. package/dist/agent-farm/servers/tower-terminals.js +53 -5
  77. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  78. package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
  79. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  80. package/dist/agent-farm/servers/tower-utils.js +39 -0
  81. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  82. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
  83. package/dist/agent-farm/servers/tower-websocket.js +8 -1
  84. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  85. package/dist/agent-farm/utils/message-format.d.ts +78 -2
  86. package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
  87. package/dist/agent-farm/utils/message-format.js +102 -5
  88. package/dist/agent-farm/utils/message-format.js.map +1 -1
  89. package/dist/cli.d.ts.map +1 -1
  90. package/dist/cli.js +3 -1
  91. package/dist/cli.js.map +1 -1
  92. package/dist/commands/doctor.js +2 -2
  93. package/dist/commands/generate-image.d.ts +20 -1
  94. package/dist/commands/generate-image.d.ts.map +1 -1
  95. package/dist/commands/generate-image.js +188 -1
  96. package/dist/commands/generate-image.js.map +1 -1
  97. package/dist/commands/porch/prompts.d.ts.map +1 -1
  98. package/dist/commands/porch/prompts.js +18 -2
  99. package/dist/commands/porch/prompts.js.map +1 -1
  100. package/dist/lib/forge-contracts.d.ts +66 -5
  101. package/dist/lib/forge-contracts.d.ts.map +1 -1
  102. package/dist/lib/forge-contracts.js.map +1 -1
  103. package/dist/lib/forge.d.ts +1 -1
  104. package/dist/lib/forge.d.ts.map +1 -1
  105. package/dist/lib/forge.js +33 -11
  106. package/dist/lib/forge.js.map +1 -1
  107. package/dist/terminal/pty-manager.d.ts +10 -1
  108. package/dist/terminal/pty-manager.d.ts.map +1 -1
  109. package/dist/terminal/pty-manager.js +38 -9
  110. package/dist/terminal/pty-manager.js.map +1 -1
  111. package/dist/terminal/pty-session.d.ts +52 -4
  112. package/dist/terminal/pty-session.d.ts.map +1 -1
  113. package/dist/terminal/pty-session.js +157 -18
  114. package/dist/terminal/pty-session.js.map +1 -1
  115. package/dist/terminal/shellper-client.d.ts +73 -0
  116. package/dist/terminal/shellper-client.d.ts.map +1 -1
  117. package/dist/terminal/shellper-client.js +151 -0
  118. package/dist/terminal/shellper-client.js.map +1 -1
  119. package/dist/terminal/shellper-main.js +20 -3
  120. package/dist/terminal/shellper-main.js.map +1 -1
  121. package/dist/terminal/shellper-process.d.ts +2 -0
  122. package/dist/terminal/shellper-process.d.ts.map +1 -1
  123. package/dist/terminal/shellper-process.js +17 -1
  124. package/dist/terminal/shellper-process.js.map +1 -1
  125. package/dist/terminal/shellper-protocol.d.ts +28 -0
  126. package/dist/terminal/shellper-protocol.d.ts.map +1 -1
  127. package/dist/terminal/shellper-protocol.js.map +1 -1
  128. package/package.json +4 -4
  129. package/scripts/forge/gitea/_lib.sh +214 -0
  130. package/scripts/forge/gitea/issue-comment.sh +8 -1
  131. package/scripts/forge/gitea/issue-view.sh +95 -4
  132. package/scripts/forge/gitea/pr-create.sh +155 -0
  133. package/scripts/forge/gitea/pr-exists.sh +32 -4
  134. package/scripts/forge/gitea/pr-list.sh +40 -31
  135. package/scripts/forge/gitea/pr-view.sh +65 -4
  136. package/scripts/forge/gitea/recently-merged.sh +91 -21
  137. package/scripts/forge/gitea/user-identity.sh +30 -1
  138. package/scripts/forge/github/issue-view.sh +5 -2
  139. package/scripts/forge/github/pr-create.sh +47 -0
  140. package/scripts/forge/github/pr-search.sh +3 -1
  141. package/scripts/forge/gitlab/issue-view.sh +24 -4
  142. package/scripts/forge/gitlab/pr-create.sh +53 -0
  143. package/scripts/forge/gitlab/pr-search.sh +3 -1
  144. package/scripts/forge/linear/issue-view.sh +13 -2
  145. package/skeleton/.claude/skills/generate-image/SKILL.md +22 -1
  146. package/skeleton/.codex/skills/generate-image/SKILL.md +22 -1
  147. package/skeleton/protocols/air/prompts/pr.md +6 -1
  148. package/skeleton/protocols/aspir/prompts/review.md +6 -1
  149. package/skeleton/protocols/bugfix/prompts/pr.md +6 -1
  150. package/skeleton/protocols/bugfix/protocol.md +2 -1
  151. package/skeleton/protocols/maintain/prompts/review.md +6 -1
  152. package/skeleton/protocols/pir/prompts/review.md +10 -7
  153. package/skeleton/protocols/spir/prompts/review.md +6 -1
  154. package/skeleton/resources/commands/agent-farm.md +1 -1
  155. package/templates/tower.html +16 -1
  156. package/dashboard-dist/assets/index-BZRTEMtH.css +0 -32
  157. package/dashboard-dist/assets/index-D3XaKpZW.js.map +0 -1
@@ -0,0 +1,53 @@
1
+ #!/bin/sh
2
+ # Forge concept: pr-create (GitLab via glab CLI — merge requests)
3
+ # forge-executable: glab
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
+ # ⚠️ UNVERIFIED — `glab` is not available in the authoring environment (same
11
+ # constraint as gitea/issue-search.sh, #920). Written against glab's
12
+ # documented `mr create` flags; smoke-test before relying on it. Without this
13
+ # file the gitlab preset would fall through to the GitHub default and shell
14
+ # out to `gh` — the bug this concept exists to close (#1455).
15
+ #
16
+ # `glab mr create --yes` skips the interactive prompts and prints the new MR's
17
+ # URL; the number (GitLab's `iid`) is its last path segment.
18
+ set -e
19
+
20
+ if [ -z "$CODEV_PR_TITLE" ]; then
21
+ echo "pr-create: CODEV_PR_TITLE is required" >&2
22
+ exit 2
23
+ fi
24
+
25
+ # An empty body is allowed; an *absent* one is not. Without this, forgetting the
26
+ # variable opens a PR with no body at exit 0 — the silent failure #1455 is about.
27
+ if [ -z "${CODEV_PR_BODY+x}" ]; then
28
+ echo "pr-create: CODEV_PR_BODY is required (set it to \"\" for an empty body)" >&2
29
+ exit 2
30
+ fi
31
+
32
+ set -- --title "$CODEV_PR_TITLE" --description "$CODEV_PR_BODY" --yes
33
+ if [ -n "$CODEV_PR_BASE" ]; then set -- "$@" --target-branch "$CODEV_PR_BASE"; fi
34
+ if [ -n "$CODEV_PR_HEAD" ]; then set -- "$@" --source-branch "$CODEV_PR_HEAD"; fi
35
+ if [ -n "$CODEV_PR_REPO" ]; then set -- "$@" --repo "$CODEV_PR_REPO"; fi
36
+ if [ "$CODEV_PR_DRAFT" = "1" ]; then set -- "$@" --draft; fi
37
+
38
+ url=$(glab mr create "$@" | grep -E '^https?://' | tail -n 1)
39
+
40
+ if [ -z "$url" ]; then
41
+ echo "pr-create: glab mr create returned no MR URL" >&2
42
+ exit 1
43
+ fi
44
+
45
+ number=${url##*/}
46
+ case "$number" in
47
+ '' | *[!0-9]*)
48
+ echo "pr-create: could not parse an MR number from '$url'" >&2
49
+ exit 1
50
+ ;;
51
+ esac
52
+
53
+ printf '{"number":%s,"url":"%s"}\n' "$number" "$url"
@@ -1,3 +1,5 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: pr-search (GitLab via glab CLI)
3
- exec glab mr list --search "$CODEV_SEARCH_QUERY" --output json
3
+ # --all is required so the search includes merged/closed MRs; without it
4
+ # `glab mr list` defaults to opened only and post-merge lookups return nothing (#759).
5
+ exec glab mr list --all --search "$CODEV_SEARCH_QUERY" --output json
@@ -1,7 +1,13 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-view (Linear via GraphQL API)
3
3
  # Input: CODEV_ISSUE_ID (e.g. "ENG-123")
4
- # Output: JSON {title, body, state, url, comments[]}
4
+ # Output: JSON {title, body, state, url, author, createdAt, assignees, labels, milestone, comments[]}
5
+ #
6
+ # Maps Linear's fields into the forge-neutral IssueView shape. Linear has a
7
+ # single `assignee` (emitted as a one-element `assignees` array) and `project`
8
+ # rather than milestones (mapped into `milestone.title`). These fields are
9
+ # optional by contract, so anything Linear leaves null degrades to an omitted
10
+ # line. Best-effort preset (Spec 589).
5
11
  set -e
6
12
 
7
13
  if [ -z "$LINEAR_API_KEY" ]; then
@@ -18,7 +24,7 @@ curl -sf -X POST https://api.linear.app/graphql \
18
24
  -H "Content-Type: application/json" \
19
25
  -H "Authorization: $LINEAR_API_KEY" \
20
26
  -d "$(jq -n --arg id "$CODEV_ISSUE_ID" '{
21
- query: "query($id: String!) { issueVcsByFilter: issues(filter: { identifier: { eq: $id } }) { nodes { title url description state { name } comments { nodes { body createdAt user { displayName } } } } } }",
27
+ query: "query($id: String!) { issueVcsByFilter: issues(filter: { identifier: { eq: $id } }) { nodes { title url description createdAt state { name } creator { displayName } assignee { displayName } labels { nodes { name } } project { name } comments { nodes { body createdAt user { displayName } } } } } }",
22
28
  variables: { id: $id }
23
29
  }')" \
24
30
  | jq 'if (.data.issueVcsByFilter.nodes | length) == 0 then
@@ -29,6 +35,11 @@ curl -sf -X POST https://api.linear.app/graphql \
29
35
  url: .url,
30
36
  body: (.description // ""),
31
37
  state: .state.name,
38
+ author: (if .creator then { login: .creator.displayName } else null end),
39
+ createdAt: .createdAt,
40
+ assignees: (if .assignee then [{ login: .assignee.displayName }] else [] end),
41
+ labels: [(.labels.nodes // [])[] | { name: .name }],
42
+ milestone: (if .project then { title: .project.name } else null end),
32
43
  comments: [.comments.nodes[] | {
33
44
  body: .body,
34
45
  createdAt: .createdAt,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-image
3
- description: AI image generation via Gemini. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY.
3
+ description: AI image generation via Gemini, or Atlas Cloud with `-p atlas`. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY, or ATLASCLOUD_API_KEY when using `-p atlas`.
4
4
  ---
5
5
 
6
6
  # generate-image - AI Image Generation
@@ -22,6 +22,7 @@ Note: this is a `codev` subcommand, not standalone.
22
22
  -r, --resolution <res> Resolution: 1K, 2K, 4K (default: 1K)
23
23
  -a, --aspect <ratio> Aspect ratio (default: 1:1)
24
24
  --ref <image> Reference image (repeatable, max 14)
25
+ -p, --provider <name> Provider: gemini (default) or atlas
25
26
  ```
26
27
 
27
28
  ## Aspect ratios
@@ -43,3 +44,23 @@ codev generate-image prompt.txt -o result.png # Prompt from .txt file
43
44
  - Prompt can be a `.txt` file path (auto-detected by extension)
44
45
  - Reference images must exist on disk
45
46
  - Requires `GEMINI_API_KEY` or `GOOGLE_API_KEY` environment variable
47
+
48
+ ## Alternative provider: Atlas Cloud
49
+
50
+ `-p atlas` generates through Atlas Cloud, which serves the same Nano Banana Pro
51
+ model over a submit-then-poll REST API. Useful when a Google AI Studio key is
52
+ not available. Requires `ATLASCLOUD_API_KEY`; the default provider is unchanged.
53
+
54
+ ```bash
55
+ codev generate-image "A sunset over mountains" -p atlas -a 16:9
56
+ ```
57
+
58
+ Measured differences on that path:
59
+
60
+ - `-a/--aspect` works (`1:1` returns 1024x1024, `16:9` returns 1376x768).
61
+ - `-r/--resolution` has no equivalent field; a note is printed and the model's
62
+ default resolution comes back.
63
+ - `--ref` is not supported (text-to-image only) and exits with an error rather
64
+ than silently ignoring the images.
65
+ - The model returns JPEG, so the output file is named after its actual bytes
66
+ instead of writing JPEG into the default `output.png`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-image
3
- description: AI image generation via Gemini. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY.
3
+ description: AI image generation via Gemini, or Atlas Cloud with `-p atlas`. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY, or ATLASCLOUD_API_KEY when using `-p atlas`.
4
4
  ---
5
5
 
6
6
  # generate-image - AI Image Generation
@@ -22,6 +22,7 @@ Note: this is a `codev` subcommand, not standalone.
22
22
  -r, --resolution <res> Resolution: 1K, 2K, 4K (default: 1K)
23
23
  -a, --aspect <ratio> Aspect ratio (default: 1:1)
24
24
  --ref <image> Reference image (repeatable, max 14)
25
+ -p, --provider <name> Provider: gemini (default) or atlas
25
26
  ```
26
27
 
27
28
  ## Aspect ratios
@@ -43,3 +44,23 @@ codev generate-image prompt.txt -o result.png # Prompt from .txt file
43
44
  - Prompt can be a `.txt` file path (auto-detected by extension)
44
45
  - Reference images must exist on disk
45
46
  - Requires `GEMINI_API_KEY` or `GOOGLE_API_KEY` environment variable
47
+
48
+ ## Alternative provider: Atlas Cloud
49
+
50
+ `-p atlas` generates through Atlas Cloud, which serves the same Nano Banana Pro
51
+ model over a submit-then-poll REST API. Useful when a Google AI Studio key is
52
+ not available. Requires `ATLASCLOUD_API_KEY`; the default provider is unchanged.
53
+
54
+ ```bash
55
+ codev generate-image "A sunset over mountains" -p atlas -a 16:9
56
+ ```
57
+
58
+ Measured differences on that path:
59
+
60
+ - `-a/--aspect` works (`1:1` returns 1024x1024, `16:9` returns 1376x768).
61
+ - `-r/--resolution` has no equivalent field; a note is printed and the model's
62
+ default resolution comes back.
63
+ - `--ref` is not supported (text-to-image only) and exits with an error rather
64
+ than silently ignoring the images.
65
+ - The model returns JPEG, so the output file is named after its actual bytes
66
+ instead of writing JPEG into the default `output.png`.
@@ -18,7 +18,8 @@ Open the PR with the review embedded in its body, optionally run CMAP, and notif
18
18
  The body must carry `Closes #<N>` for the driving issue — one per issue if several — so GitHub auto-closes it on merge. **Exception:** a partial fix uses `Refs #<N>` or `Part of #<N>` instead. Substitute the real number for `<N>`; leave no `{{...}}` tag or `<N>` placeholder in the committed body.
19
19
 
20
20
  ```bash
21
- gh pr create --title "[Air #<N>] feat: <brief description>" --body "$(cat <<'EOF'
21
+ export CODEV_PR_TITLE="[Air #<N>] feat: <brief description>"
22
+ export CODEV_PR_BODY="$(cat <<'EOF'
22
23
  ## Summary
23
24
 
24
25
  <1-2 sentence description of the feature>
@@ -44,8 +45,12 @@ Closes #<N> <!-- Substitute <N>; use "Refs #<N>" for a partial fix -->
44
45
  <anything the reviewer should focus on, or "Standard implementation — no special concerns">
45
46
  EOF
46
47
  )"
48
+
49
+ {{pr_create_command}}
47
50
  ```
48
51
 
52
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
53
+
49
54
  ## Optional CMAP review
50
55
 
51
56
  CMAP is your judgement call for AIR. Skip it for simple changes (config, small UI); run it for features touching core logic or several modules:
@@ -44,7 +44,8 @@ The review's `## Architecture Updates` and `## Lessons Learned Updates` sections
44
44
  The PR body must carry `Closes #<N>` (feature) or `Fixes #<N>` (bug) for the driving issue — one keyword per issue if several — so GitHub auto-closes on merge. **Exception:** a PR that only partially addresses its issue uses `Refs #<N>` or `Part of #<N>` instead, leaving the issue open for the follow-up.
45
45
 
46
46
  ```bash
47
- gh pr create --title "[Spec {{project_id}}] {{title}}" --body "$(cat <<'EOF'
47
+ export CODEV_PR_TITLE="[Spec {{project_id}}] {{title}}"
48
+ export CODEV_PR_BODY="$(cat <<'EOF'
48
49
  ## Summary
49
50
  [what was implemented]
50
51
 
@@ -63,8 +64,12 @@ codev/specs/{{artifact_name}}.md
63
64
  codev/reviews/{{artifact_name}}.md
64
65
  EOF
65
66
  )"
67
+
68
+ {{pr_create_command}}
66
69
  ```
67
70
 
71
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
72
+
68
73
  ## Signals
69
74
 
70
75
  - Review document complete:
@@ -16,7 +16,8 @@ Open the PR, run CMAP review on it, address feedback, and hand off to the archit
16
16
  The PR body must carry `Fixes #<N>` for the driving issue — one per issue if several — so GitHub auto-closes it on merge. **Exception:** a PR that only partially addresses the issue uses `Refs #<N>` or `Part of #<N>` instead, leaving it open for the follow-up. Substitute the real number for `<N>`; leave no `{{...}}` tag or `<N>` placeholder in the committed body.
17
17
 
18
18
  ```bash
19
- gh pr create --title "Fix #<N>: <brief description>" --body "$(cat <<'EOF'
19
+ export CODEV_PR_TITLE="Fix #<N>: <brief description>"
20
+ export CODEV_PR_BODY="$(cat <<'EOF'
20
21
  ## Summary
21
22
 
22
23
  <1-2 sentence description of the bug and fix>
@@ -38,8 +39,12 @@ Fixes #<N> <!-- Substitute <N>; use "Refs #<N>" for a partial fix -->
38
39
  - [ ] All tests pass
39
40
  EOF
40
41
  )"
42
+
43
+ {{pr_create_command}}
41
44
  ```
42
45
 
46
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
47
+
43
48
  ## Run CMAP review
44
49
 
45
50
  BUGFIX runs its own 3-way consultation on the PR (porch does not do it for you). Dispatch all three in the background:
@@ -27,7 +27,8 @@ refactor surrounding code, fix unrelated bugs (file separate issues), or add fea
27
27
  [Bugfix #42] Test: regression for unencoded username
28
28
  ```
29
29
 
30
- **PR** — open with `gh pr create`, body carrying Summary, Root Cause, Fix and Test Plan plus
30
+ **PR** — open with the `pr-create` forge concept command the PR-phase prompt hands you (`gh
31
+ pr create` on GitHub), body carrying Summary, Root Cause, Fix and Test Plan plus
31
32
  `Fixes #<N>` so the issue closes on merge. Run one CMAP pass (Gemini, Codex, Claude), record
32
33
  each verdict, and address or rebut every `REQUEST_CHANGES`. Notify the architect with the
33
34
  verdicts, then `porch done <id>` and wait.
@@ -46,7 +46,8 @@ If the run was not tied to any issue, the `Closes` line can be omitted.
46
46
  ```bash
47
47
  git push origin HEAD
48
48
 
49
- gh pr create --title "[Maintain] Codebase maintenance run NNNN" --body "$(cat <<'PREOF'
49
+ export CODEV_PR_TITLE="[Maintain] Codebase maintenance run NNNN"
50
+ export CODEV_PR_BODY="$(cat <<'PREOF'
50
51
  ## Summary
51
52
 
52
53
  <2-3 bullet points of what was done>
@@ -67,8 +68,12 @@ Closes #<N> <!-- Only if this run was triggered by a GitHub issue. Use "Refs #<
67
68
  - [x] Documentation links resolve
68
69
  PREOF
69
70
  )"
71
+
72
+ {{pr_create_command}}
70
73
  ```
71
74
 
75
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
76
+
72
77
  ## Signals
73
78
 
74
79
  When PR is created:
@@ -108,13 +108,16 @@ git push
108
108
  PR_TITLE="<concise description of the change>"
109
109
  BRANCH="$(git branch --show-current)"
110
110
 
111
- gh pr create \
112
- --base main \
113
- --head "$BRANCH" \
114
- --title "$PR_TITLE" \
115
- --body-file codev/reviews/{{artifact_name}}.md
111
+ export CODEV_PR_TITLE="$PR_TITLE"
112
+ export CODEV_PR_BODY="$(cat codev/reviews/{{artifact_name}}.md)"
113
+ export CODEV_PR_BASE=main
114
+ export CODEV_PR_HEAD="$BRANCH"
115
+
116
+ {{pr_create_command}}
116
117
  ```
117
118
 
119
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It prints `{"number": <int>, "url": "<url>"}`. The inputs are **exported** so an inline override that spells `--title "$CODEV_PR_TITLE"` sees them too. The body goes in as an environment variable, not `--body-file` — read the created PR back and confirm the body arrived intact before moving on.
120
+
118
121
  **Verify the PR body contains `Fixes #{{issue.number}}`** (it should — the review file has it at the top). If somehow missing, edit and re-apply:
119
122
 
120
123
  ```bash
@@ -154,7 +157,7 @@ For any `REQUEST_CHANGES`:
154
157
 
155
158
  1. Read the finding in full (`codev/projects/{{project_id}}-*/{{project_id}}-<model>.txt`).
156
159
  2. **Assess it honestly:**
157
- - **Real defect** (correctness / cancellation / security / data-loss): fix it in code, add a regression test that fails without the fix, commit + push (the PR updates automatically — no new `gh pr create`). Then document the finding, your fix, and the pinning test in the review file's **"Things to Look At During PR Review"** section.
160
+ - **Real defect** (correctness / cancellation / security / data-loss): fix it in code, add a regression test that fails without the fix, commit + push (the PR updates automatically — no second PR). Then document the finding, your fix, and the pinning test in the review file's **"Things to Look At During PR Review"** section.
158
161
  - **False positive / out of scope**: write a brief rebuttal in that same section explaining why no change is warranted.
159
162
  3. Do **not** re-run `porch done` expecting another consultation pass — `max_iterations: 1` means it will not re-review. Proceed to step 7.
160
163
 
@@ -257,7 +260,7 @@ Together with the `--pr` record from step 4a and the `--merged` record from step
257
260
  ```
258
261
  - Resolve conflicts (do NOT use destructive shortcuts)
259
262
  - Force-push with lease: `git push --force-with-lease`
260
- - Re-run `gh pr create`
263
+ - Re-run the PR creation command from step 4
261
264
 
262
265
  **If porch's consultation fails (e.g., model unavailable):**
263
266
  - `porch done` will report the failure. Inspect `codev/projects/{{project_id}}-*/{{project_id}}-<model>.txt` for the failure details.
@@ -44,7 +44,8 @@ The review's `## Architecture Updates` and `## Lessons Learned Updates` sections
44
44
  The PR body must carry `Closes #<N>` (feature) or `Fixes #<N>` (bug) for the driving issue — one keyword per issue if several — so GitHub auto-closes on merge. **Exception:** a PR that only partially addresses its issue uses `Refs #<N>` or `Part of #<N>` instead, leaving the issue open for the follow-up.
45
45
 
46
46
  ```bash
47
- gh pr create --title "[Spec {{project_id}}] {{title}}" --body "$(cat <<'EOF'
47
+ export CODEV_PR_TITLE="[Spec {{project_id}}] {{title}}"
48
+ export CODEV_PR_BODY="$(cat <<'EOF'
48
49
  ## Summary
49
50
  [what was implemented]
50
51
 
@@ -63,8 +64,12 @@ codev/specs/{{artifact_name}}.md
63
64
  codev/reviews/{{artifact_name}}.md
64
65
  EOF
65
66
  )"
67
+
68
+ {{pr_create_command}}
66
69
  ```
67
70
 
71
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
72
+
68
73
  ## Signals
69
74
 
70
75
  - Review document complete:
@@ -432,7 +432,7 @@ afx inbox dismiss <id> [options]
432
432
  | `ID` | Mailbox row id (pass to `show` / `dismiss`) |
433
433
  | `AGE` | How long the message has been held (`5s`, `3m`, `2h`, `1d`) |
434
434
  | `REASON` | Why-held: `busy`, `no-profile`, or `no-live-pty`; a trailing `!` marks a row past the escalation age |
435
- | `FROM → TO` | Sender → recipient agent |
435
+ | `FROM → TO` | Sender → recipient agent. An architect sender carries its name (`architect:main`); the column is sized to its content, so long ids are never truncated |
436
436
  | `WORKSPACE` | Owning workspace |
437
437
 
438
438
  **Options:**
@@ -1667,6 +1667,11 @@
1667
1667
 
1668
1668
  async function fetchCloudStatus() {
1669
1669
  try {
1670
+ // Served through the tunnel, /api/tunnel/* is local-only — blocked at
1671
+ // the tunnel edge and again by Tower (#1370), so this poll can only
1672
+ // ever 403 (#1599). The widget renders nothing cloud-served anyway;
1673
+ // skip the forbidden request instead of paying it every refresh.
1674
+ if (isCloudServed()) return null;
1670
1675
  const res = await authFetch('./api/tunnel/status');
1671
1676
  if (res.status === 404) return null;
1672
1677
  if (!res.ok) return { state: 'error' };
@@ -1676,6 +1681,16 @@
1676
1681
  }
1677
1682
  }
1678
1683
 
1684
+ // Is this page being viewed through Codev Cloud rather than served by the
1685
+ // local Tower directly? One shared predicate for the tunnel-status poll
1686
+ // and the cloud widget render, so the two can never drift (#1599).
1687
+ // Exact-or-subdomain match, not a bare suffix — `endsWith('codevos.ai')`
1688
+ // would also match an unrelated `evilcodevos.ai` (CMAP).
1689
+ function isCloudServed() {
1690
+ const h = window.location.hostname;
1691
+ return h === 'codevos.ai' || h.endsWith('.codevos.ai');
1692
+ }
1693
+
1679
1694
  function formatUptime(ms) {
1680
1695
  const s = Math.floor(ms / 1000);
1681
1696
  if (s < 60) return s + 's';
@@ -1723,7 +1738,7 @@
1723
1738
  noteCloudStateChange(status && status.state ? status.state : null);
1724
1739
 
1725
1740
  // When accessed through codevos.ai, cloud status is irrelevant
1726
- if (window.location.hostname.endsWith('codevos.ai')) {
1741
+ if (isCloudServed()) {
1727
1742
  el.innerHTML = '';
1728
1743
  return;
1729
1744
  }
@@ -1,32 +0,0 @@
1
- /**
2
- * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3
- * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4
- * https://github.com/chjj/term.js
5
- * @license MIT
6
- *
7
- * Permission is hereby granted, free of charge, to any person obtaining a copy
8
- * of this software and associated documentation files (the "Software"), to deal
9
- * in the Software without restriction, including without limitation the rights
10
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- * copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions:
13
- *
14
- * The above copyright notice and this permission notice shall be included in
15
- * all copies or substantial portions of the Software.
16
- *
17
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- * THE SOFTWARE.
24
- *
25
- * Originally forked from (with the author's permission):
26
- * Fabrice Bellard's javascript vt100 for jslinux:
27
- * http://bellard.org/jslinux/
28
- * Copyright (c) 2011 Fabrice Bellard
29
- * The original design remains. The terminal itself
30
- * has been extended to include xterm CSI codes, among
31
- * other features.
32
- */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}:root{--bg-primary: #1a1a1a;--bg-secondary: #252525;--bg-tertiary: #2a2a2a;--bg-hover: #333333;--text-primary: #e0e0e0;--text-secondary: #a0a0a0;--text-muted: #666666;--border-color: #333333;--accent: #3b82f6;--accent-hover: #2563eb;--status-active: #22c55e;--status-waiting: #eab308;--status-error: #ef4444;--status-implementing: #f97316}*{margin:0;padding:0;box-sizing:border-box}html,body,#root{height:100%;width:100%;overflow:hidden}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,monospace;background:var(--bg-primary);color:var(--text-primary);font-size:13px}.fullscreen-terminal{height:100vh;width:100vw;overflow:hidden}.fullscreen-terminal>div{height:100%;width:100%}.app{display:flex;flex-direction:column;height:100vh}.app-header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);height:40px;flex-shrink:0}.app-title{font-size:14px;font-weight:600;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.header-version{font-size:11px;color:var(--text-secondary);font-weight:400}.app-body{flex:1;overflow:hidden}.split-pane{display:flex;height:100%;width:100%}.split-left,.split-right{height:100%;overflow:hidden;position:relative}.split-handle{width:4px;cursor:col-resize;background:var(--border-color);flex-shrink:0}.split-handle:hover{background:var(--accent)}.expand-bar{width:24px;height:100%;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--bg-secondary);border:none;border-left:1px solid var(--border-color);border-right:1px solid var(--border-color);color:var(--text-muted);cursor:pointer;padding:0;font-family:inherit}.expand-bar:hover{background:var(--bg-hover);color:var(--accent)}.header-controls{display:flex;align-items:center;gap:4px}.header-btn{display:flex;align-items:center;justify-content:center;width:32px;height:28px;padding:0;background:transparent;border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);cursor:pointer}.header-btn:hover{background:var(--bg-hover);color:var(--text-primary);border-color:var(--accent)}.right-panel{display:flex;flex-direction:column;height:100%}.tab-bar{display:flex;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);overflow-x:auto;flex-shrink:0;height:34px}.tab{display:flex;align-items:center;gap:6px;padding:6px 12px;background:none;border:none;border-bottom:2px solid transparent;color:var(--text-secondary);font-size:12px;cursor:pointer;white-space:nowrap;font-family:inherit}.tab:hover{background:var(--bg-hover);color:var(--text-primary)}.tab-active{color:var(--text-primary);border-bottom-color:var(--accent)}.tab-icon{flex-shrink:0}.tab-label{max-width:120px;overflow:hidden;text-overflow:ellipsis}.tab-close{font-size:14px;line-height:1;opacity:.5;padding:0 2px;border-radius:3px}.tab-close:hover{opacity:1;background:var(--bg-hover)}.remove-architect-modal-backdrop,.confirm-dialog-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.remove-architect-modal,.confirm-dialog{background:var(--bg-primary, #1e1e1e);color:var(--text-primary, #e0e0e0);border-radius:6px;padding:24px;max-width:500px;width:90%;box-shadow:0 10px 40px #00000080}.remove-architect-modal h2,.confirm-dialog h2{margin:0 0 12px;font-size:18px}.remove-architect-modal p,.confirm-dialog p{margin:0 0 16px;line-height:1.5}.remove-architect-modal code,.confirm-dialog code{background:var(--bg-secondary, #2a2a2a);padding:2px 6px;border-radius:3px;font-family:ui-monospace,monospace}.remove-architect-error{color:var(--text-error, #ff6b6b)}.remove-architect-modal-actions,.confirm-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}.remove-architect-modal-actions button,.confirm-dialog-actions button{padding:6px 14px;border:1px solid var(--border-color, #444);background:var(--bg-secondary, #2a2a2a);color:var(--text-primary, #e0e0e0);border-radius:4px;cursor:pointer}.remove-architect-modal-actions button:hover:not(:disabled),.confirm-dialog-actions button:hover:not(:disabled){background:var(--bg-hover, #333)}.remove-architect-modal-actions button.primary,.confirm-dialog-actions button.primary{background:var(--accent-color, #f44747);color:#fff;border-color:var(--accent-color, #f44747)}.remove-architect-modal-actions button:disabled,.confirm-dialog-actions button:disabled{opacity:.6;cursor:not-allowed}.tab-content{flex:1;overflow:hidden}.terminal-tab-pane{width:100%;height:100%}.file-path-decoration{border-bottom:1px dotted rgba(255,255,255,.3)!important;background:transparent!important;pointer-events:none}.file-path-decoration-hover{border-bottom-color:#ffffffb3!important}.terminal-iframe{display:block}.file-tree{padding:8px;overflow-y:auto;height:100%}.file-node{display:flex;align-items:center;gap:6px;padding:3px 0;cursor:pointer;font-size:12px;border-radius:4px}.file-node:hover{background:var(--bg-hover)}.file-icon{font-size:14px;flex-shrink:0}.file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-tree-error,.file-tree-loading{padding:16px;color:var(--text-muted);font-size:12px}.no-architect{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}.architect-pane{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;min-height:0}.architect-pane-body{flex:1;min-height:0}.virtual-keyboard{display:flex;gap:6px;padding:6px 8px;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);flex-shrink:0}.virtual-key{padding:4px 12px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);font-size:12px;font-family:inherit;cursor:pointer;touch-action:manipulation;user-select:none;-webkit-user-select:none}.virtual-key:active{background:var(--bg-hover);color:var(--text-primary)}.virtual-key-active{background:var(--accent);border-color:var(--accent);color:#fff}.virtual-key-active:active{background:var(--accent-hover)}.terminal-controls{position:absolute;top:8px;right:12px;z-index:20;display:flex;align-items:center;gap:4px;padding:4px 6px;background:#1e1e1ed9;border:1px solid rgba(255,255,255,.12);border-radius:8px}.terminal-control-btn{display:flex;align-items:center;justify-content:center;min-width:32px;min-height:32px;padding:0;background:transparent;border:1px solid transparent;border-radius:5px;color:silver;cursor:pointer;touch-action:manipulation;user-select:none;-webkit-user-select:none}.terminal-control-btn:hover{background:#ffffff1a;border-color:#ffffff26;color:#fff}.terminal-control-btn:active{background:#ffffff26}.toolbar-divider{width:1px;height:20px;background:#ffffff26;margin:0 2px;flex-shrink:0}.terminal-status-icon{cursor:default}.terminal-status-icon.terminal-status-connected{color:#22c55e;opacity:.7}.terminal-status-icon.terminal-status-reconnecting{color:#eab308;opacity:.8;animation:terminal-pulse 1.2s ease-in-out infinite}.terminal-status-icon.terminal-status-disconnected{color:#ef4444;opacity:.8}@keyframes terminal-pulse{0%,to{opacity:.8}50%{opacity:.2}}.mobile-layout{display:flex;flex-direction:column;height:100vh}.mobile-content{flex:1;overflow:hidden;display:flex;flex-direction:column}@media(max-width:768px){.app-header{padding:6px 12px}.tab{padding:4px 8px;font-size:11px}}.file-viewer{display:flex;flex-direction:column;height:100%;background:var(--bg-primary)}.file-loading,.file-error{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-secondary)}.file-error{color:var(--status-error)}.file-header{display:flex;align-items:center;gap:12px;padding:8px 12px;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);flex-shrink:0}.file-path{font-family:monospace;font-size:12px;color:var(--text-primary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-language{font-size:11px;color:var(--text-muted);text-transform:uppercase}.file-save-btn{padding:4px 12px;font-size:11px;background:var(--accent);color:#fff;border:none;border-radius:4px;cursor:pointer}.file-save-btn:hover{background:var(--accent-hover)}.file-save-btn:disabled{opacity:.5;cursor:not-allowed}.file-content{display:flex;flex:1;overflow:auto;background:var(--bg-primary)}.line-numbers{display:flex;flex-direction:column;padding:12px 8px;background:var(--bg-secondary);color:var(--text-muted);font-family:Menlo,Monaco,Courier New,monospace;font-size:13px;line-height:1.5;text-align:right;-webkit-user-select:none;user-select:none;flex-shrink:0;min-width:40px}.line-number{padding-right:8px}.line-number.highlighted-line{background:#3b82f64d}.file-text{flex:1;padding:12px;font-family:Menlo,Monaco,Courier New,monospace;font-size:13px;line-height:1.5;background:var(--bg-primary);color:var(--text-primary);border:none;outline:none;resize:none;white-space:pre;overflow:auto}.file-image-container,.file-video-container{flex:1;display:flex;align-items:center;justify-content:center;overflow:auto;padding:20px}.file-image{max-width:100%;max-height:100%;object-fit:contain}.file-video{max-width:100%;max-height:100%}.file-tree-container{display:flex;flex-direction:column;height:100%;overflow:hidden}.file-search{position:relative;padding:8px;border-bottom:1px solid var(--border-color);flex-shrink:0}.file-search-input{width:100%;padding:6px 10px;font-size:12px;font-family:inherit;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);outline:none}.file-search-input:focus{border-color:var(--accent)}.file-search-input::placeholder{color:var(--text-muted)}.file-search-suggestions{position:absolute;top:100%;left:8px;right:8px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:4px;box-shadow:0 4px 12px #0000004d;z-index:100;max-height:240px;overflow-y:auto}.file-search-suggestion{display:flex;flex-direction:column;padding:8px 10px;cursor:pointer;border-bottom:1px solid var(--border-color)}.file-search-suggestion:last-child{border-bottom:none}.file-search-suggestion:hover{background:var(--bg-hover)}.suggestion-name{font-size:12px;color:var(--text-primary);font-weight:500}.suggestion-path{font-size:10px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-tree-section{display:flex;flex-direction:column;overflow:hidden}.file-tree-section:first-of-type{flex-shrink:0}.file-tree-section:last-of-type{flex:1;overflow:hidden}.file-tree-section-header{padding:8px 12px;font-size:11px;font-weight:600;text-transform:uppercase;color:var(--text-muted);letter-spacing:.5px;border-bottom:1px solid var(--border-color);flex-shrink:0}.recent-file{color:var(--text-secondary)}.recent-file:hover{color:var(--text-primary)}.file-path-hint{font-size:10px;color:var(--text-muted);margin-left:auto;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.git-indicator{font-family:monospace;font-size:10px;font-weight:700;padding:1px 4px;border-radius:3px;margin-left:auto;flex-shrink:0}.git-modified{color:var(--status-implementing);background:#f9731626}.git-staged{color:var(--status-active);background:#22c55e26}.git-untracked{color:var(--text-muted);background:#66666626}.cloud-status{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);margin-right:12px}.cloud-status--none{color:var(--text-muted)}.cloud-status--error{color:var(--status-error)}.cloud-dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0}.cloud-dot--green{background:var(--status-active)}.cloud-dot--yellow{background:var(--status-waiting);animation:cloud-pulse 1.2s ease-in-out infinite}@keyframes cloud-pulse{0%,to{opacity:1}50%{opacity:.3}}.cloud-dot--red{background:var(--status-error)}.cloud-dot--gray{background:var(--text-muted)}.cloud-uptime{color:var(--text-muted);font-size:11px}.held-badge{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--text-secondary);white-space:nowrap}.held-badge--attention{color:var(--status-waiting);font-weight:600}.held-dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--text-muted)}.held-dot--attention{background:var(--status-waiting);animation:cloud-pulse 1.2s ease-in-out infinite}.cloud-hint{color:var(--text-muted);font-size:11px;font-style:italic}.cloud-link{color:var(--accent);text-decoration:none;font-size:11px}.cloud-link:hover{text-decoration:underline}.cloud-btn{background:transparent;border:1px solid var(--border-color);color:var(--text-secondary);font-size:11px;padding:1px 6px;border-radius:3px;cursor:pointer}.cloud-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.cloud-btn:disabled{opacity:.5;cursor:default}.mobile-wrapper{display:flex;flex-direction:column;height:100vh}.mobile-header{padding:4px 8px;border-bottom:1px solid var(--border-color);background:var(--bg-primary)}.mobile-wrapper .mobile-layout{flex:1;min-height:0}.work-view{display:flex;flex-direction:column;height:100%;overflow:hidden}.work-content{flex:1;overflow-y:auto;padding:12px 16px}.work-view.file-panel-open .work-content{flex:2}.work-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.work-title{font-size:19px;font-weight:600;color:var(--text-primary)}.work-actions{display:flex;gap:6px}.work-btn{background:var(--accent);color:#fff;border:none;padding:4px 10px;border-radius:4px;font-size:14px;cursor:pointer}.work-btn:hover{background:var(--accent-hover)}.work-btn-secondary{background:transparent;border:1px solid var(--border-color);color:var(--text-secondary)}.work-btn-secondary:hover{background:var(--bg-hover);color:var(--text-primary)}.tip-banner{display:flex;align-items:center;gap:8px;padding:6px 12px;margin-bottom:16px;background:var(--bg-tertiary);border-radius:4px;font-size:14px;color:var(--text-secondary)}.tip-banner-label{font-weight:600;color:var(--text-muted);white-space:nowrap}.tip-banner-text{flex:1;min-width:0}.tip-banner-text code{font-family:var(--font-mono, monospace);background:var(--bg-primary);padding:1px 4px;border-radius:2px;font-size:13px;color:var(--text-primary)}.tip-banner-nav{background:none;border:none;color:var(--text-muted);cursor:pointer;padding:2px 4px;font-size:14px;line-height:1;border-radius:2px}.tip-banner-nav:hover{color:var(--text-primary);background:var(--bg-hover)}.tip-banner-dismiss{background:none;border:none;color:var(--text-muted);cursor:pointer;padding:2px 4px;font-size:14px;line-height:1;border-radius:2px;margin-left:4px}.tip-banner-dismiss:hover{color:var(--text-primary);background:var(--bg-hover)}.work-section{margin-bottom:20px}.work-section-title{font-size:13px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;display:flex;align-items:center;gap:6px}.work-empty{color:var(--text-muted);font-style:italic;font-size:14px;padding:8px 0}.work-unavailable{color:var(--status-waiting);font-size:14px;padding:8px 0}.work-error{color:var(--status-error);font-size:14px;padding:8px;margin-bottom:12px;background:#ef44441a;border-radius:4px}.work-loading{color:var(--text-muted);padding:16px}.builder-table{width:100%;border-collapse:separate;border-spacing:0 4px}.builder-table thead th{font-size:13px;font-weight:500;color:var(--text-secondary);text-align:left;padding:0 10px 4px;white-space:nowrap}.builder-row td{background:var(--bg-secondary);padding:8px 10px;font-size:16px;border-top:1px solid var(--border-color);border-bottom:1px solid var(--border-color)}.builder-row td:first-child{border-left:1px solid var(--border-color);border-radius:8px 0 0 8px}.builder-row td:last-child{border-right:1px solid var(--border-color);border-radius:0 8px 8px 0}.builder-row:hover td{border-color:var(--accent)}.builder-row--blocked td:first-child{border-left:3px solid var(--status-waiting)}.builder-col-id{font-weight:500;color:var(--accent);white-space:nowrap;min-width:60px}.builder-attribution{font-size:.9em;color:var(--text-secondary);font-weight:400}.builder-col-title{color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:0;width:40%}.builder-col-state{white-space:nowrap;width:140px}.builder-state-active{font-size:14px;color:var(--accent)}.builder-state-blocked{font-size:14px;color:var(--status-waiting)}.builder-col-progress{width:120px;white-space:nowrap}.progress-bar{width:80px;height:4px;background:var(--bg-tertiary);border-radius:2px;overflow:hidden;display:inline-block;vertical-align:middle}.progress-fill{height:100%;background:var(--status-active);border-radius:2px;transition:width .3s ease}.progress-fill--blocked{background:var(--status-waiting)}.progress-pct{font-size:13px;color:var(--text-secondary);margin-left:6px;vertical-align:middle}.builder-col-elapsed{font-size:14px;color:var(--text-secondary);white-space:nowrap;width:70px}.builder-col-actions{width:60px;text-align:right}.builder-row-open{font-size:13px;color:var(--accent);background:#3b82f614;border:1px solid rgba(59,130,246,.2);padding:3px 10px;border-radius:5px;cursor:pointer;white-space:nowrap}.builder-row-open:hover{background:#3b82f62e}.pr-rows{display:flex;flex-direction:column;gap:4px}a.pr-row{text-decoration:none;color:inherit;cursor:pointer}.pr-row{display:flex;align-items:center;gap:10px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:10px 14px}.pr-row:hover{border-color:var(--accent)}.pr-row-number{font-size:16px;font-weight:500;color:var(--accent);white-space:nowrap}.pr-row-title{flex:1;font-size:16px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pr-row-status{font-size:13px;white-space:nowrap}.pr-status--approved{color:var(--status-active)}.pr-status--reviewing{color:var(--status-waiting)}.pr-status--changes{color:var(--status-error)}.pr-row-age{font-size:13px;color:var(--text-muted);white-space:nowrap}.pr-row-author{font-size:12px;color:var(--text-muted);white-space:nowrap}.attention-rows{display:flex;flex-direction:column;gap:4px}a.attention-row{text-decoration:none;color:inherit;cursor:pointer}.attention-row{display:flex;align-items:center;gap:10px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:10px 14px}.attention-row:hover{border-color:var(--accent)}.attention-row-id{font-size:16px;font-weight:500;color:var(--accent);white-space:nowrap}.attention-row-title{flex:1;font-size:16px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.attention-row-kind{font-size:13px;white-space:nowrap}.attention-kind--pr{color:var(--status-waiting)}.attention-kind--spec,.attention-kind--plan{color:var(--status-error)}.attention-kind--dev{color:var(--status-implementing)}.attention-kind--verify{color:var(--status-waiting)}.attention-row-age{font-size:13px;color:var(--text-muted);white-space:nowrap}.backlog-rows{display:flex;flex-direction:column;gap:2px}.backlog-row{display:flex;align-items:center;gap:6px;padding:4px 14px;border-radius:6px}.backlog-row:hover{background:var(--bg-secondary)}.backlog-row-main{display:flex;align-items:center;gap:10px;flex:1;min-width:0;text-decoration:none;color:inherit}.backlog-row-main:hover .backlog-row-title{color:var(--accent)}.backlog-artifacts{display:flex;gap:4px;flex-shrink:0}.backlog-artifact-link{font-size:12px;padding:1px 5px;border-radius:3px;border:1px solid var(--border-color);background:transparent;color:var(--text-secondary);cursor:pointer;white-space:nowrap}.backlog-artifact-link:hover{background:var(--bg-hover);color:var(--accent);border-color:var(--accent)}.backlog-priority-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.priority-dot--high{background:var(--status-error)}.priority-dot--med{background:var(--status-waiting)}.priority-dot--low{background:var(--text-muted)}.backlog-row-number{font-size:16px;font-weight:500;color:var(--accent);white-space:nowrap}.backlog-type-tag{font-size:12px;padding:1px 5px;border-radius:3px;font-weight:500;white-space:nowrap}.type-tag--bug{background:#ef444426;color:var(--status-error)}.type-tag--project{background:#a855f71f;color:#a855f7}.type-tag--spike{background:#0ea5e91f;color:#0ea5e9}.backlog-row-title{flex:1;font-size:16px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.backlog-row-age{font-size:13px;color:var(--text-muted);white-space:nowrap}.backlog-row-author,.backlog-row-assignees{font-size:12px;color:var(--text-muted);white-space:nowrap}.recently-closed-rows{display:flex;flex-direction:column;gap:2px}.recently-closed-row{display:flex;align-items:center;gap:6px;padding:4px 14px;border-radius:6px;opacity:.7}.recently-closed-row:hover{background:var(--bg-secondary);opacity:1}.recently-closed-row-main{display:flex;align-items:center;gap:10px;flex:1;min-width:0;text-decoration:none;color:inherit}.recently-closed-check{font-size:13px;color:var(--status-active);flex-shrink:0}.work-file-panel{border-top:1px solid var(--border-color);background:var(--bg-secondary)}.work-file-panel.expanded{flex:1;display:flex;flex-direction:column;overflow:hidden}.work-file-panel.collapsed{flex-shrink:0}.work-file-panel-header{display:flex;align-items:center;gap:6px;padding:6px 12px;cursor:pointer;-webkit-user-select:none;user-select:none}.work-file-panel-header:hover{background:var(--bg-hover)}.work-file-panel-toggle{font-size:12px;color:var(--text-muted)}.work-file-panel-label{font-size:14px;font-weight:500;color:var(--text-secondary)}.work-file-panel-search{flex:1;margin-left:8px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:3px;color:var(--text-primary);font-size:14px;padding:3px 8px;outline:none}.work-file-panel-search::placeholder{color:var(--text-muted)}.work-file-panel-search:focus{border-color:var(--accent)}.work-file-panel-content{flex:1;overflow-y:auto;padding:0 12px 12px}@media(max-width:768px){.work-content{padding:8px 12px}.work-header{flex-wrap:wrap;gap:8px}.pr-row,.attention-row{flex-wrap:wrap}.builder-table{font-size:14px}.builder-col-elapsed,.builder-table thead th:nth-child(5){display:none}.pr-row-title,.attention-row-title,.backlog-row-title{min-width:60%}}.analytics-view{display:flex;flex-direction:column;height:100%;overflow:hidden}.analytics-content{flex:1;overflow-y:auto;padding:12px 16px}.analytics-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.analytics-title{font-size:16px;font-weight:600;color:var(--text-primary)}.analytics-actions{display:flex;gap:6px;align-items:center}.analytics-range-selector{display:flex;border:1px solid var(--border-color);border-radius:4px;overflow:hidden}.analytics-range-btn{background:transparent;border:none;border-right:1px solid var(--border-color);padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text-secondary)}.analytics-range-btn:last-child{border-right:none}.analytics-range-btn:hover{background:var(--bg-hover)}.analytics-range-btn.active{background:var(--accent);color:#fff}.analytics-section{margin-bottom:16px}.analytics-section-title{font-size:11px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;display:flex;align-items:center;gap:4px}.analytics-collapse-icon{font-size:10px;width:12px;display:inline-block}.analytics-error{color:var(--status-error);font-size:12px;margin-bottom:8px;padding:6px 8px;background:#ef44441a;border-radius:4px}.analytics-loading{color:var(--text-secondary);font-size:13px;padding:20px 0;text-align:center}.analytics-metric-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px}.analytics-metric{display:flex;flex-direction:column;padding:8px 10px;background:var(--bg-secondary, var(--bg-hover));border-radius:4px}.analytics-metric-value{font-size:18px;font-weight:600;color:var(--text-primary);line-height:1.2}.analytics-metric-label{font-size:10px;color:var(--text-secondary);margin-top:2px}.analytics-sub-section{margin-top:10px}.analytics-sub-title{font-size:11px;font-weight:500;color:var(--text-secondary);margin-bottom:4px}.analytics-table{width:100%;border-collapse:collapse;font-size:11px}.analytics-table th{text-align:left;color:var(--text-secondary);font-weight:500;padding:3px 6px;border-bottom:1px solid var(--border-color)}.analytics-table td{padding:3px 6px;color:var(--text-primary);border-bottom:1px solid var(--border-subtle, var(--border-color))}.analytics-list{list-style:none;padding:0;margin:0}.analytics-list li{display:flex;justify-content:space-between;padding:2px 0;font-size:12px}.analytics-list-label{color:var(--text-secondary)}.analytics-list-value{color:var(--text-primary);font-weight:500}.analytics-charts-row{display:flex;gap:8px}.analytics-chart-half{flex:1;min-width:0}.team-view{display:flex;flex-direction:column;height:100%;overflow:hidden}.team-content{flex:1;overflow-y:auto;padding:12px 16px}.team-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.team-title{font-size:16px;font-weight:600;color:var(--text-primary);margin:0}.team-refresh-btn{background:transparent;border:1px solid var(--border-color);border-radius:4px;padding:4px 10px;font-size:11px;color:var(--text-secondary);cursor:pointer}.team-refresh-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.team-refresh-btn:disabled{opacity:.5;cursor:default}.team-error{color:var(--status-error);font-size:12px;margin-bottom:8px;padding:6px 8px;background:#ef44441a;border-radius:4px}.team-loading{color:var(--text-secondary);font-size:13px;padding:20px 0;text-align:center}.team-section{margin-bottom:16px}.team-section-title{font-size:11px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px}.team-member-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px}.team-member-card{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:6px;padding:10px 12px}.team-member-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.team-member-name{font-size:13px;font-weight:600;color:var(--text-primary)}.team-member-role{font-size:10px;color:var(--text-muted);background:var(--bg-tertiary);padding:1px 5px;border-radius:3px;text-transform:uppercase;letter-spacing:.3px}.team-member-github{font-size:11px;color:var(--accent);text-decoration:none;display:block;margin-bottom:6px}.team-member-github:hover{text-decoration:underline}.team-member-section{margin-top:6px}.team-section-label{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.3px;display:block;margin-bottom:2px}.team-item-list{display:flex;flex-direction:column}.team-item-link{display:block;font-size:11px;color:var(--accent);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;padding:1px 0}.team-item-link:hover{text-decoration:underline}.team-item-empty{font-size:11px;color:var(--text-muted);font-style:italic}.team-member-activity{display:flex;gap:8px;font-size:10px;color:var(--text-muted);margin-top:4px}.team-review-blocking-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:2px}.team-review-blocking-item{display:flex;align-items:baseline;gap:6px;font-size:11px;line-height:1.3;min-width:0}.team-review-blocking-sentence{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.team-review-blocking-sentence strong{font-weight:600}.team-review-blocking-link{display:inline;white-space:normal}.team-review-blocking-age{flex:0 0 auto;font-size:10px;color:var(--text-muted);font-style:italic}.team-activity-label{color:var(--text-muted);font-style:italic}.team-messages{display:flex;flex-direction:column;gap:6px}.team-message{padding:8px 10px;background:var(--bg-secondary);border-radius:4px;border-left:2px solid var(--border-color)}.team-message-header{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}.team-message-author{font-size:12px;font-weight:600;color:var(--text-primary)}.team-message-time{font-size:10px;color:var(--text-muted)}.team-message-body{font-size:12px;color:var(--text-secondary);white-space:pre-wrap;word-break:break-word}.team-no-messages{font-size:12px;color:var(--text-muted);padding:8px 0}.team-activity-feed{display:flex;flex-direction:column;gap:2px}.team-activity-entry{display:flex;align-items:baseline;gap:6px;font-size:12px;color:var(--text-secondary);text-decoration:none;padding:4px 8px;border-radius:3px}.team-activity-entry:hover{background:var(--bg-secondary)}.team-activity-date{font-size:10px;color:var(--text-muted);min-width:50px;flex-shrink:0}.team-activity-author{color:var(--accent);flex-shrink:0}.team-activity-action{color:var(--text-muted);flex-shrink:0}.team-activity-ref{color:var(--text-secondary);flex-shrink:0}.team-activity-title{color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.ofs-rows{display:flex;flex-direction:column;gap:2px}.ofs-subgroup-label{font-size:11px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em;padding:4px 14px 2px}.ofs-row{display:flex;align-items:center;gap:8px;padding:5px 14px;border-radius:6px;cursor:pointer;font-size:14px}.ofs-row:hover{background:var(--bg-secondary)}.ofs-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.ofs-dot--running{background:var(--status-active)}.ofs-dot--idle{background:var(--text-secondary)}.ofs-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ofs-idle{font-size:12px;color:var(--text-secondary);white-space:nowrap}.ofs-file-icon{font-size:14px;flex-shrink:0}.ofs-path{font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}