@marcomigozzi/trailhead 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.it.md +82 -25
- package/README.md +82 -25
- package/bin/base-preflight-docs.test.js +4 -1
- package/bin/bulk-cross-reference-docs.test.js +36 -0
- package/bin/capture-action.test.js +167 -0
- package/bin/capture-docs.test.js +32 -0
- package/bin/engine-route-docs.test.js +155 -0
- package/bin/facet-stamp-docs.test.js +79 -0
- package/bin/forced-mutation-docs.test.js +38 -0
- package/bin/grill-model-docs.test.js +155 -0
- package/bin/grilling-holistic-review-docs.test.js +7 -3
- package/bin/guard-axis-docs.test.js +69 -0
- package/bin/inbox-needs-info-docs.test.js +68 -0
- package/bin/lib/codex-projection.js +28 -19
- package/bin/lib/codex-projection.test.js +77 -20
- package/bin/lib/host-descriptor.js +1 -1
- package/bin/lib/host-descriptor.test.js +6 -0
- package/bin/needs-you-docs.test.js +9 -2
- package/bin/options-in-chat-docs.test.js +65 -0
- package/bin/pick-stress-test-docs.test.js +1 -1
- package/bin/pipeline-review-docs.test.js +118 -0
- package/bin/plan-review-budget-docs.test.js +63 -0
- package/bin/repo-target-docs.test.js +41 -0
- package/bin/report-status-docs.test.js +10 -2
- package/bin/seam-coverage-docs.test.js +46 -0
- package/bin/session-trust-docs.test.js +37 -0
- package/bin/standards-brief-docs.test.js +112 -0
- package/bin/surfaced-scope-docs.test.js +57 -0
- package/bin/test-once-docs.test.js +101 -0
- package/bin/test-run-docs.test.js +47 -0
- package/bin/trailhead.js +15 -8
- package/bin/trailhead.test.js +71 -17
- package/bin/unblock-docs.test.js +56 -0
- package/bin/yes-accepts-recommended-docs.test.js +54 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -1
- package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
- package/plugins/trailhead/agents/trailhead-grill.md +41 -0
- package/plugins/trailhead/agents/trailhead-plan.md +1 -1
- package/plugins/trailhead/agents/trailhead-verify.md +1 -0
- package/plugins/trailhead/hooks/lib/audit-walk.test.js +62 -0
- package/plugins/trailhead/hooks/lib/capture.test.js +439 -0
- package/plugins/trailhead/hooks/lib/checkout-find.test.js +195 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +83 -7
- package/plugins/trailhead/hooks/lib/dashboard.test.js +19 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.js +89 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.test.js +132 -0
- package/plugins/trailhead/hooks/lib/git-env-mod.test.js +55 -0
- package/plugins/trailhead/hooks/lib/git-env-spawn.test.js +73 -0
- package/plugins/trailhead/hooks/lib/git-env.test.js +51 -0
- package/plugins/trailhead/hooks/lib/global-config-dir.test.js +48 -0
- package/plugins/trailhead/hooks/lib/heredoc-mod.test.js +85 -0
- package/plugins/trailhead/hooks/lib/inbox-needs-info.test.js +179 -0
- package/plugins/trailhead/hooks/lib/inherit-session.js +17 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +165 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.js +22 -1
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +64 -1
- package/plugins/trailhead/hooks/lib/model-route.js +207 -0
- package/plugins/trailhead/hooks/lib/model-route.test.js +231 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +45 -1
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +272 -0
- package/plugins/trailhead/hooks/lib/repo-target.test.js +256 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +56 -1
- package/plugins/trailhead/hooks/lib/statusline-git-env.test.js +47 -0
- package/plugins/trailhead/hooks/lib/test-run.test.js +757 -0
- package/plugins/trailhead/hooks/lib/trailhead-git-env.js +40 -0
- package/plugins/trailhead/hooks/mod/README.md +42 -17
- package/plugins/trailhead/hooks/mod/auto-run.ts +2 -1
- package/plugins/trailhead/hooks/mod/git-env.ts +31 -0
- package/plugins/trailhead/hooks/mod/heredoc.ts +79 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +3 -1
- package/plugins/trailhead/hooks/mod/map-pane.tsx +204 -49
- package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
- package/plugins/trailhead/hooks/mod/repo-pin.ts +113 -9
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +2 -1
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +28 -14
- package/plugins/trailhead/hooks/trailhead-check-update.js +20 -2
- package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +2 -2
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +1 -1
- package/plugins/trailhead/hooks/trailhead-test-guard.js +232 -0
- package/plugins/trailhead/hooks/trailhead-test-guard.test.js +172 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +123 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +30 -16
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +19 -6
- package/plugins/trailhead/skills/_shared/substrate.md +4 -3
- package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
- package/plugins/trailhead/skills/_shared/teamwork.md +11 -5
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +22 -6
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +6 -3
- package/plugins/trailhead/skills/_shared/techniques/debug.md +5 -2
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +28 -3
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +5 -1
- package/plugins/trailhead/skills/_shared/techniques.md +8 -6
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -4
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +29 -4
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +11 -6
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +18 -3
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +12 -9
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +3 -1
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +13 -13
- package/plugins/trailhead/templates/statusline-trailhead.sh +10 -3
- package/plugins/trailhead/templates/trailhead-audit-walk.js +56 -4
- package/plugins/trailhead/templates/trailhead-auto-merge.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-queue.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-slot.js +2 -1
- package/plugins/trailhead/templates/trailhead-base-preflight.js +10 -5
- package/plugins/trailhead/templates/trailhead-capture.js +419 -0
- package/plugins/trailhead/templates/trailhead-checkout-find.js +257 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +78 -21
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
- package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
- package/plugins/trailhead/templates/trailhead-git-env.js +40 -0
- package/plugins/trailhead/templates/trailhead-inbox-needs-info.js +161 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +2 -1
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +9 -4
- package/plugins/trailhead/templates/trailhead-issue-body.js +161 -12
- package/plugins/trailhead/templates/trailhead-merge-ticket.js +2 -1
- package/plugins/trailhead/templates/trailhead-needs-you.js +38 -4
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +261 -7
- package/plugins/trailhead/templates/trailhead-repo-target.js +302 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +57 -9
- package/plugins/trailhead/templates/trailhead-test-run.js +752 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-followup.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-setup.js +9 -4
- package/plugins/trailhead/templates/trailhead-unblock.js +2 -1
- package/plugins/trailhead/tests/auto-run-ui.test.ts +2 -1
- package/plugins/trailhead/tests/git-env.test.ts +20 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +386 -50
- package/plugins/trailhead/tests/map-pane-view.test.ts +6 -0
- package/plugins/trailhead/tests/map-pane-write.test.ts +57 -1
- package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +3 -2
- package/plugins/trailhead/tests/repo-pin-relocate.test.ts +175 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
- package/plugins/trailhead/tests/unscrub.ts +8 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +3 -2
- package/plugins/trailhead/tests/work-state-worktree.test.ts +2 -1
- package/plugins/trailhead/types/index.d.ts +7 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Repo-location environment for git spawns.
|
|
3
|
+
//
|
|
4
|
+
// Git gives these variables precedence over `-C` and the cwd, so an exported
|
|
5
|
+
// GIT_DIR (a hook, `git rebase --exec`, a parent shell) silently redirects every
|
|
6
|
+
// git call to another repository. Each git spawn in the plugin builds its env
|
|
7
|
+
// through gitEnv() so it always acts on the checkout it was pointed at.
|
|
8
|
+
//
|
|
9
|
+
// The names are `git rev-parse --local-env-vars` minus GIT_CONFIG*: those carry
|
|
10
|
+
// the parent's `-c` options and are meant to be inherited. A static list keeps
|
|
11
|
+
// this free of a git spawn; refresh it from that command when git adds a name.
|
|
12
|
+
// Names must be deleted: an empty string is not "unset" (`fatal: '' is not a git repository`).
|
|
13
|
+
//
|
|
14
|
+
// hooks/lib/trailhead-git-env.js is a byte-identical copy for the hooks (they
|
|
15
|
+
// install without the templates); a test keeps the two in sync.
|
|
16
|
+
|
|
17
|
+
const GIT_LOCATION_VARS = Object.freeze([
|
|
18
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
19
|
+
'GIT_OBJECT_DIRECTORY',
|
|
20
|
+
'GIT_DIR',
|
|
21
|
+
'GIT_WORK_TREE',
|
|
22
|
+
'GIT_IMPLICIT_WORK_TREE',
|
|
23
|
+
'GIT_GRAFT_FILE',
|
|
24
|
+
'GIT_INDEX_FILE',
|
|
25
|
+
'GIT_NO_REPLACE_OBJECTS',
|
|
26
|
+
'GIT_REPLACE_REF_BASE',
|
|
27
|
+
'GIT_PREFIX',
|
|
28
|
+
'GIT_SHALLOW_FILE',
|
|
29
|
+
'GIT_COMMON_DIR',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// A new env object: `base` without the repo-location names, then `extra` on top.
|
|
33
|
+
// Never mutates `base` (process.env by default).
|
|
34
|
+
function gitEnv(base = process.env, extra = {}) {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [k, v] of Object.entries(base)) if (!GIT_LOCATION_VARS.includes(k)) out[k] = v;
|
|
37
|
+
return { ...out, ...extra };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { GIT_LOCATION_VARS, gitEnv };
|
|
@@ -96,11 +96,21 @@ command file (`commands/panel.md`), because a command registered by a mod cannot
|
|
|
96
96
|
only where no mod does (it says the panel is not available). Claude Code only: the Codex
|
|
97
97
|
projection leaves the `panel` verb out (`CODEX_MOD_ONLY_VERBS` in `bin/lib/codex-projection.js`).
|
|
98
98
|
|
|
99
|
-
- **What it shows**: the map as a link with its destination (dim), then `
|
|
100
|
-
`
|
|
101
|
-
|
|
99
|
+
- **What it shows**: the map as a link with its destination (dim), then `In progress · n` (first, right under
|
|
100
|
+
the maps list), `Needs you · n`, `Frontier · n`, `UAT · n`, `Paused · n` and `Blocked · n` (an empty group is left out). Each group, the
|
|
101
|
+
maps list included, has its own glyph and colour (`◈` Maps violet, `▶` In progress accent, `!` Needs you orange, `◆` Frontier green,
|
|
102
|
+
`◎` UAT cyan, `‖` Paused grey, `■` Blocked red): the heading is the glyph, the bold name in that colour and a dim rule to the pane's edge, and
|
|
103
|
+
every ticket's block (its row and the dim lines under it) sits after a gutter in the same colour, so the groups tell apart without
|
|
104
|
+
colour too: in the terminal a `│ ` on each of its lines, on the desktop one bar as tall as the block; the lines under the row are indented past it. A row is `#n title`
|
|
105
|
+
(a link to the issue) and the type (`build` cyan, `decision` orange, any other dim; dim on a blocked row). The unverified-count line is amber. Every assigned row shows its holder as `@login`; the ticket
|
|
102
106
|
in play in this session is marked `▸` in the accent and says `this session` in place of its holder;
|
|
103
|
-
blocked rows are dim.
|
|
107
|
+
blocked rows are dim. `In progress` is global for you: after the shown scope's own claimed tickets it lists your
|
|
108
|
+
open in-progress tickets (assigned to the `gh` login) of every other open map and of the whiteboard, each with a dim
|
|
109
|
+
`on map #<n> <title>` or `on whiteboard` line under it, in the map view and the Dashboard view alike (never a
|
|
110
|
+
ticket the view already lists; paused, blocked, UAT and needs-you ones stay in their own scope's groups). The login is
|
|
111
|
+
read once per host (`viewer{login}`), the tickets by one paged `gh api graphql` read of the open `trailhead:ticket`
|
|
112
|
+
issues with `filterBy:{assignee:<login>}`, in the same refresh as the view's reads; a failed or unreadable read keeps
|
|
113
|
+
the last list (the `myTickets` atom) and sets no footer error. The `ownTicket` atom keeps that mark when another session's work state takes
|
|
104
114
|
over the shared file (a state stamped with another session leaves it; a missing file clears it only
|
|
105
115
|
when the last state read was this session's, i.e. its own Resolve); with no own ticket the work
|
|
106
116
|
state decides, as before. A ticket carrying `trailhead:paused` (set by pause, cleared by resume or a
|
|
@@ -108,7 +118,7 @@ projection leaves the `panel` verb out (`CODEX_MOD_ONLY_VERBS` in `bin/lib/codex
|
|
|
108
118
|
button in place of its holder (it runs like `work`, with `/trailhead:resume <n>`, which checks the
|
|
109
119
|
claim); the label wins over `trailhead:blocked` and an assignee.
|
|
110
120
|
A ticket carrying `trailhead:needs-you` (an autonomous run parked it for the human; a `work`/`quick` claim
|
|
111
|
-
clears it) shows once, under `Needs you`,
|
|
121
|
+
clears it) shows once, under `Needs you`, between In progress and Frontier. The label wins over paused, UAT, blocked and an
|
|
112
122
|
assignee. Any assignee is shown as its holder, never a button, even on a paused or blocked row. A blocked
|
|
113
123
|
row with no assignee is dim with no button. Any other row gets a `work` button, never `resume`, even when
|
|
114
124
|
paused: the claim clears the mark and resume would not. A
|
|
@@ -128,13 +138,14 @@ projection leaves the `panel` verb out (`CODEX_MOD_ONLY_VERBS` in `bin/lib/codex
|
|
|
128
138
|
across the reset too. One press at a time: another while one is held or starting is ignored. A session that ends
|
|
129
139
|
while a press is held drops it. A press while the session is not in the pinned repo (a shell `cd` moved it) still
|
|
130
140
|
starts: the mod cannot move the session, so it appends the pinned checkout to the verb's args as `--in=<token>`
|
|
131
|
-
(base64url of the root) and the verb `cd`s there first
|
|
141
|
+
(base64url of the root, a `.`, then base64url of the repo as `host/owner/name`) and the verb `cd`s there first;
|
|
142
|
+
if the root has moved, the verb finds the checkout again by the repo's `origin` remote. With no repo pinned, or a checkout that cannot be read
|
|
132
143
|
(a removed worktree included), the press is refused with a toast before the `/clear`; both are checked again when
|
|
133
144
|
a held press is sent. A refused `/clear`, or one that
|
|
134
145
|
did not change the session, runs no work command. `trailhead:unverified` tickets are never listed,
|
|
135
146
|
only counted in one dim line; `trailhead:whiteboard` tickets are dropped. The footer is a `refresh`
|
|
136
147
|
button and `updated <age>`, or `⚠ <reason> · list from <age>` after a `gh` failure (the last good list is kept;
|
|
137
|
-
reasons: `gh not installed`, `gh not authenticated (run gh auth login)`,
|
|
148
|
+
reasons: `gh not installed`, `checkout not found: <path>` (the pinned folder is gone and no other checkout of the repo was found), `gh not authenticated (run gh auth login)`,
|
|
138
149
|
`offline: cannot reach GitHub`, `gh failed (exit <n>)`), and a `close` button at the right edge that
|
|
139
150
|
closes the panel. A status too long for the room beside the buttons moves to a row of its own under them. Titles come from GitHub and are untrusted:
|
|
140
151
|
flattened, stripped of control, zero-width and bidi characters, only drawn. Other states: `Loading tickets…`
|
|
@@ -185,7 +196,7 @@ projection leaves the `panel` verb out (`CODEX_MOD_ONLY_VERBS` in `bin/lib/codex
|
|
|
185
196
|
shown before (the view is kept), a `Maps · n` group with every open map pressable (a press leaves the
|
|
186
197
|
dashboard and views that map, the active one marked `●`), and a `Whiteboard · <n> open · <m> assigned` section (counted
|
|
187
198
|
over the listed tickets; `200+ open` when the list is capped): the open
|
|
188
|
-
`trailhead:ticket` + `trailhead:whiteboard` tickets as Needs you / Frontier /
|
|
199
|
+
`trailhead:ticket` + `trailhead:whiteboard` tickets as In progress / Needs you / Frontier / UAT / Paused / Blocked, with the same rows,
|
|
189
200
|
holders and `work` buttons (the work verb takes a whiteboard ticket as is). Unverified ones are counted only.
|
|
190
201
|
Its reads replace the map's in a refresh: the whiteboard list (the GraphQL open read above), `gh issue list --label trailhead:dashboard
|
|
191
202
|
--json number,url` and the maps list; a failed list keeps the last one with the footer reason, a failed index
|
|
@@ -245,14 +256,17 @@ projection leaves the `panel` verb out (`CODEX_MOD_ONLY_VERBS` in `bin/lib/codex
|
|
|
245
256
|
from it), and `.trailhead/active-map` and `.trailhead/work-state.json` are read by absolute path under
|
|
246
257
|
it, so a shell `cd` out of the repo changes nothing. A check outside any repo, or in one with no usable
|
|
247
258
|
remote, keeps the pin; a move into another repo moves it, drops the reads in flight and empties what the
|
|
248
|
-
pane held for the old one.
|
|
259
|
+
pane held for the old one. A pinned checkout deleted since (a finished worktree) moves the pin, silently, to a live checkout of
|
|
260
|
+
the same repo: the main working tree, else another `git worktree`, else a same-remote clone beside it (probes run as `git -C`
|
|
261
|
+
from `/`, retried at most once a minute). With nothing ever pinned the pane says
|
|
249
262
|
`Not in a GitHub repository: open the session in a trailhead repo` and makes no `gh` call.
|
|
250
263
|
- **When it refreshes**: only while the pane is open (a closed pane costs no `gh` call). Opening it,
|
|
251
264
|
`session.start`, `turn.complete`, a Bash call that writes to the tracker, the `refresh` button, and a slow
|
|
252
265
|
timer started on open. A write is `gh issue edit|close|create|comment|reopen|delete|transfer`, a `gh api` call on a
|
|
253
266
|
`repos/.../issues` path with a write method (`-X`/`--method` POST, PATCH, PUT, DELETE) or an implicit POST
|
|
254
267
|
(`-f`, `-F`, `--field`, `--raw-field`, `--input`) and no `-X GET`, or the `trailhead-issue-body.js` helper
|
|
255
|
-
(`GH_ISSUE_WRITE`)
|
|
268
|
+
(`GH_ISSUE_WRITE`), outside any heredoc body (`isTrackerWrite`: a body is data, so a `gh issue close` line in a
|
|
269
|
+
`cat <<EOF` writes nothing); reads never refresh. Never two fetches inside `MIN_REFRESH_MS` (save the turn-end read below): a request inside the floor
|
|
256
270
|
keeps one trailing refresh for the rest of it. A write that reads at once also arms that trailing refresh, so one
|
|
257
271
|
more read follows when the floor ends, after the search index (the history) caught up; a burst of writes still costs one
|
|
258
272
|
read per floor plus one trailing, and one more when the turn ends after it. The host drops a refused timer without a word (a refused `every` period ends
|
|
@@ -264,14 +278,25 @@ projection leaves the `panel` verb out (`CODEX_MOD_ONLY_VERBS` in `bin/lib/codex
|
|
|
264
278
|
involved; it leaves an armed trailing refresh alone, so the read after the search index caught up still follows.
|
|
265
279
|
A read asked while the pane is undrawn is dropped; a render
|
|
266
280
|
whose list (with no error) is older than `TIMER_MS + TIMER_GRACE_MS` asks for a read at once, as an empty view does.
|
|
267
|
-
- **Auto-open**:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
281
|
+
- **Auto-open**: in fullscreen (`e.viewport.isFullscreen === true` in the `AbovePrompt` render, or in the
|
|
282
|
+
viewport of a `session.attach`, so a surface that attaches later tries again), when a map is active. Never on
|
|
283
|
+
the main screen, never again after the person acted on the pane (the command, closing it by hand, the `close`
|
|
284
|
+
button). The open runs from a zero-delay timer because a render may not write state.
|
|
285
|
+
The auto-open counts as done only once the pane is actually drawn (seen placed by a later check, or by the pane's
|
|
286
|
+
own render, which fires only when drawn). Being unasked, the open can stay undrawn (`isPlaced: false`): below
|
|
287
|
+
144 columns, or on a surface that has not reported its layout yet, typical of a fresh session. That does not burn
|
|
288
|
+
the auto-open: while the pane is listed undrawn nothing opens it again (the host places it itself when it can),
|
|
289
|
+
and if it vanishes undrawn the next trigger opens it again. `/trailhead:panel` closes a pane only when it is
|
|
290
|
+
listed and drawn; a listed but undrawn pane is opened again as the person's request (drawn at any width), and if
|
|
291
|
+
even that leaves it undrawn the pane stays open and the command says so in a fixed sentence (`Map panel is open but not shown yet: ...`; the host's reason goes to the
|
|
292
|
+
debug log only), so it is never closed unseen. With no surface attached yet (`no-surface`, the start of a session) the command answers
|
|
293
|
+
that the panel will open once the screen is ready, again on every repeat; the pane's first render on the surface
|
|
294
|
+
that attaches asks for its read. A desktop session starts at its first message, not when it is created, so nothing
|
|
295
|
+
can open the pane on a session where nothing was typed, and a `/trailhead:panel` sent as that first message lands
|
|
296
|
+
right after the auto-open. Within `UNSEEN_MS` of a drawn auto-open the command keeps the pane and answers
|
|
297
|
+
`Map panel opened.` instead of closing a pane nobody could have seen.
|
|
273
298
|
- **Constants** (`map-pane.tsx`): `PANE_ID = 'trailhead-map'`, `PANE_TITLE = 'Map'`,
|
|
274
|
-
`MIN_REFRESH_MS = 15_000`, `TIMER_MS = 180_000`, `GH_TIMEOUT_MS = 20_000`, `TIMER_GRACE_MS = 30_000`,
|
|
299
|
+
`MIN_REFRESH_MS = 15_000`, `TIMER_MS = 180_000`, `GH_TIMEOUT_MS = 20_000`, `TIMER_GRACE_MS = 30_000`, `UNSEEN_MS = 2_000`,
|
|
275
300
|
`PANEL_COMMAND = 'trailhead:panel'`, `MAPS_LIMIT = MAPS_READ_LIMIT = 100` (`MAPS_LIMIT` not exported); `worktree-porcelain.ts`: `MAX_FAILURES = 3` (shared by the work-state lookup and the run scan); `auto-run-view.ts`: `RESUME_HINT`; `map-pane-view.ts`: `MAPS_READ_LIMIT = 100`, `LIST_LIMIT = 200`, `HISTORY_LIMIT = 5000`.
|
|
276
301
|
- **Atoms**: `mapPane` (with `maps`, the open maps), `activeMap`, `viewedMap` (`null` follows the active map), `isDashboard`, `dashboardPane`, `pendingWork`, `pendingPhase` (`queued` or `starting`), `paneAutoOpened`. Module variables hold the queue and
|
|
277
302
|
the timers, what `/clear` carries over (`carried`), the held press (`held`) and the one being sent (`inFlight`),
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { EngineInterface } from 'claude-code'
|
|
5
5
|
|
|
6
6
|
import type { AutoRun, RepoPin, RunCounts, WorkState } from '../../types'
|
|
7
|
+
import { gitArgv } from './git-env'
|
|
7
8
|
import { GH_TIMEOUT_MS, MIN_REFRESH_MS } from './map-pane'
|
|
8
9
|
import { aggregateRun, isRunMember, nextRunAfterFailure, parseRunCounts, parseRunScope, runCountsArgs, slotTicket } from './auto-run-view'
|
|
9
10
|
import type { Where } from './auto-run-view'
|
|
@@ -48,7 +49,7 @@ async function gate($: EngineInterface, main: string, own: WorkState | null): Pr
|
|
|
48
49
|
// La regola di cloneOnBranch (trailhead-auto-slot.js): git dir dentro la cartella e HEAD sul branch dello slot.
|
|
49
50
|
// undefined: git non è riuscito a girare.
|
|
50
51
|
async function cloneOnBranch($: EngineInterface, dir: string, ticket: number): Promise<boolean | undefined> {
|
|
51
|
-
const git = (args: string[]) => $.process.run(
|
|
52
|
+
const git = (args: string[]) => $.process.run(gitArgv('-C', dir, ...args), { cwd: dir, timeoutMs: GIT_TIMEOUT_MS })
|
|
52
53
|
try {
|
|
53
54
|
const g = await git(['rev-parse', '--absolute-git-dir'])
|
|
54
55
|
if (g.exitCode !== 0) return false
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Leaf without imports: git argv for the mod's process spawns.
|
|
2
|
+
//
|
|
3
|
+
// Git gives the repo-location variables precedence over `-C` and the cwd, and
|
|
4
|
+
// `$.process.run` can only add or override env, never delete. So an inherited
|
|
5
|
+
// GIT_DIR would redirect every git spawn: wrap the argv in `env -u NAME ...`.
|
|
6
|
+
// The list is templates/trailhead-git-env.js's GIT_LOCATION_VARS (git rev-parse
|
|
7
|
+
// --local-env-vars minus GIT_CONFIG*); hooks/lib/git-env-mod.test.js keeps them equal.
|
|
8
|
+
export const GIT_LOCATION_VARS: readonly string[] = [
|
|
9
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
10
|
+
'GIT_OBJECT_DIRECTORY',
|
|
11
|
+
'GIT_DIR',
|
|
12
|
+
'GIT_WORK_TREE',
|
|
13
|
+
'GIT_IMPLICIT_WORK_TREE',
|
|
14
|
+
'GIT_GRAFT_FILE',
|
|
15
|
+
'GIT_INDEX_FILE',
|
|
16
|
+
'GIT_NO_REPLACE_OBJECTS',
|
|
17
|
+
'GIT_REPLACE_REF_BASE',
|
|
18
|
+
'GIT_PREFIX',
|
|
19
|
+
'GIT_SHALLOW_FILE',
|
|
20
|
+
'GIT_COMMON_DIR',
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
// `argv` run with the repo-location variables removed from its env.
|
|
24
|
+
export const scrubGitEnv = (argv: readonly string[]): string[] => [
|
|
25
|
+
'env',
|
|
26
|
+
...GIT_LOCATION_VARS.flatMap(name => ['-u', name]),
|
|
27
|
+
...argv,
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
// `git <args>` that acts on the checkout it is pointed at (-C / cwd), never on an inherited GIT_DIR.
|
|
31
|
+
export const gitArgv = (...args: readonly string[]): string[] => scrubGitEnv(['git', ...args])
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Leaf without imports: heredoc bodies out of a Bash command (#369).
|
|
2
|
+
//
|
|
3
|
+
// A heredoc body is data (a file being written), not commands, so a `gh issue close` line inside
|
|
4
|
+
// one is no tracker write. Only the opener line stays, with every body and terminator line dropped.
|
|
5
|
+
// The mod imports nothing from hooks/lib, so this is a small twin of shell-scan.js's
|
|
6
|
+
// stripHeredocBodies; hooks/lib/heredoc-mod.test.js keeps their output equal.
|
|
7
|
+
// When it cannot tell, a line is kept: a lost write leaves a stale pane, an extra read costs one `gh` call.
|
|
8
|
+
// Known difference: an opener inside `$(..)` is not seen here (shell-scan sees it), so those lines stay.
|
|
9
|
+
|
|
10
|
+
// The heredoc openers of one line, in order: [{ tag, isDash }]. A `<<` counts outside quotes and outside
|
|
11
|
+
// `((..))` arithmetic, and not as a here-string (`<<<`); an opener with no word after it is no opener.
|
|
12
|
+
// The scan stops at an unquoted `#` that starts a comment (line start, or after whitespace or `;&|(`).
|
|
13
|
+
// `quoteIn` is the quote still open from an earlier line; the quote open at the end of the line is returned.
|
|
14
|
+
function openersOf(
|
|
15
|
+
line: string,
|
|
16
|
+
quoteIn: string,
|
|
17
|
+
): { found: Array<{ tag: string; isDash: boolean }>; quote: string } {
|
|
18
|
+
const found: Array<{ tag: string; isDash: boolean }> = []
|
|
19
|
+
let quote = quoteIn
|
|
20
|
+
let arith = 0
|
|
21
|
+
for (let i = 0; i < line.length; i++) {
|
|
22
|
+
const c = line[i]
|
|
23
|
+
if (quote !== '') {
|
|
24
|
+
if (c === '\\' && quote === '"') i++
|
|
25
|
+
else if (c === quote) quote = ''
|
|
26
|
+
} else if (c === '\\') {
|
|
27
|
+
i++
|
|
28
|
+
} else if (line.startsWith('${', i)) {
|
|
29
|
+
// A parameter expansion holds no opener (`${x:-<<EOF}`); skip to its `}`, or the rest of the line.
|
|
30
|
+
const end = line.indexOf('}', i)
|
|
31
|
+
if (end < 0) break
|
|
32
|
+
i = end
|
|
33
|
+
} else if (c === '#' && (i === 0 || /[\s;&|(]/.test(line[i - 1]))) {
|
|
34
|
+
break
|
|
35
|
+
} else if (c === "'" || c === '"') {
|
|
36
|
+
quote = c
|
|
37
|
+
} else if (line.startsWith('((', i)) {
|
|
38
|
+
arith++
|
|
39
|
+
i++
|
|
40
|
+
} else if (arith > 0 && line.startsWith('))', i)) {
|
|
41
|
+
arith--
|
|
42
|
+
i++
|
|
43
|
+
} else if (arith === 0 && line.startsWith('<<', i)) {
|
|
44
|
+
if (line[i + 2] === '<') {
|
|
45
|
+
i += 2
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
const isDash = line[i + 2] === '-'
|
|
49
|
+
const word = /^\s*((?:\\.|'[^']*'|"[^"]*"|[^\s;&|<>()'"\\])+)/.exec(line.slice(i + (isDash ? 3 : 2)))
|
|
50
|
+
if (word !== null) {
|
|
51
|
+
found.push({ tag: word[1].replace(/\\(.)|['"]/g, '$1'), isDash })
|
|
52
|
+
i += (isDash ? 3 : 2) + word[0].length - 1
|
|
53
|
+
} else {
|
|
54
|
+
i++
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { found, quote }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Every line from an opener to its terminator (the terminator included) goes; a body that never ends
|
|
62
|
+
// takes the rest of the command, as the shell reads it.
|
|
63
|
+
export function stripHeredocBodies(command: string): string {
|
|
64
|
+
const lines = command.split('\n')
|
|
65
|
+
const kept: string[] = []
|
|
66
|
+
let quote = ''
|
|
67
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68
|
+
kept.push(lines[i])
|
|
69
|
+
const scan = openersOf(lines[i], quote)
|
|
70
|
+
quote = scan.quote
|
|
71
|
+
for (const { tag, isDash } of scan.found) {
|
|
72
|
+
while (i + 1 < lines.length) {
|
|
73
|
+
i++
|
|
74
|
+
if ((isDash ? lines[i].replace(/^\t+/, '') : lines[i]) === tag) break
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return kept.join('\n')
|
|
79
|
+
}
|
|
@@ -15,7 +15,8 @@ export type TicketGroups = {
|
|
|
15
15
|
paused: readonly PaneTicket[]
|
|
16
16
|
blocked: readonly PaneTicket[]
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
// `missingRoot`: the folder `gh` was to run in, when it no longer exists (a spawn there fails like a missing binary).
|
|
19
|
+
export type Failure = { error: unknown; missingRoot?: string } | { exitCode: number; stderr: string }
|
|
19
20
|
export type RefreshDecision = { run: true } | { run: false; waitMs: number }
|
|
20
21
|
|
|
21
22
|
// The most rows one `gh issue list` is asked for.
|
|
@@ -429,6 +430,7 @@ const errorText = (err: unknown): string => {
|
|
|
429
430
|
// One short line saying why `gh` gave nothing.
|
|
430
431
|
export const failureReason = (failure: Failure): string => {
|
|
431
432
|
if ('error' in failure) {
|
|
433
|
+
if (failure.missingRoot !== undefined) return `checkout not found: ${failure.missingRoot}`
|
|
432
434
|
const text = errorText(failure.error)
|
|
433
435
|
if (/ENOENT|not found/i.test(text)) return 'gh not installed'
|
|
434
436
|
if (/timed? ?out/i.test(text)) return 'offline: cannot reach GitHub'
|