@marcomigozzi/trailhead 0.16.0 → 0.17.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 +67 -18
- package/README.md +67 -18
- package/bin/capture-action.test.js +167 -0
- package/bin/capture-docs.test.js +32 -0
- package/bin/config-dir-templates-docs.test.js +52 -0
- package/bin/decision-builds-docs.test.js +75 -0
- package/bin/engine-route-docs.test.js +148 -0
- package/bin/fog-vs-ticket-docs.test.js +56 -0
- package/bin/grill-model-docs.test.js +155 -0
- package/bin/grilling-holistic-review-docs.test.js +7 -3
- package/bin/lib/codex-projection.js +30 -14
- package/bin/lib/codex-projection.test.js +81 -17
- package/bin/lib/host-descriptor.js +15 -2
- package/bin/lib/host-descriptor.test.js +24 -0
- package/bin/lib/runtime-command-literals.test.js +184 -0
- package/bin/report-status-docs.test.js +11 -3
- package/bin/session-trust-docs.test.js +37 -0
- package/bin/surfaced-scope-docs.test.js +57 -0
- package/bin/trailhead.js +7 -5
- package/bin/trailhead.test.js +53 -11
- package/bin/unblock-docs.test.js +56 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -2
- package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
- package/plugins/trailhead/agents/trailhead-grill.md +41 -0
- package/plugins/trailhead/agents/trailhead-verify.md +2 -2
- 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/inherit-session.js +17 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +84 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.js +72 -22
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +113 -0
- 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/report-status.test.js +124 -2
- package/plugins/trailhead/hooks/lib/shell-scan.js +25 -0
- package/plugins/trailhead/hooks/lib/statusline-context.test.js +22 -1
- package/plugins/trailhead/hooks/lib/unmodelled.js +6 -1
- package/plugins/trailhead/hooks/mod/README.md +24 -10
- package/plugins/trailhead/hooks/mod/map-pane.tsx +124 -44
- package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
- package/plugins/trailhead/hooks/mod/repo-pin.ts +8 -5
- package/plugins/trailhead/hooks/trailhead-check-update.js +17 -1
- package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +40 -1
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +68 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +118 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +20 -18
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +2 -2
- package/plugins/trailhead/skills/_shared/substrate-commands.md +8 -4
- package/plugins/trailhead/skills/_shared/substrate.md +1 -1
- package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
- package/plugins/trailhead/skills/_shared/teamwork.md +9 -5
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +5 -2
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +26 -1
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques.md +7 -5
- 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 +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +3 -3
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +5 -4
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +13 -11
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +1 -1
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +11 -10
- package/plugins/trailhead/templates/statusline-trailhead.sh +3 -3
- package/plugins/trailhead/templates/trailhead-capture.js +418 -0
- package/plugins/trailhead/templates/trailhead-checkout-find.js +256 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +76 -20
- package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
- package/plugins/trailhead/templates/trailhead-issue-body.js +69 -6
- package/plugins/trailhead/templates/trailhead-report-status.js +123 -11
- package/plugins/trailhead/tests/map-pane-ui.test.ts +299 -37
- package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
- package/plugins/trailhead/types/index.d.ts +7 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { expect, test } from 'claude-code/testing'
|
|
2
|
+
|
|
3
|
+
import { parseTickets } from '../hooks/mod/map-pane-view'
|
|
4
|
+
import { elsewhereInProgress, myTicketsFromRows, parseLogin, scopeLine } from '../hooks/mod/my-tickets'
|
|
5
|
+
|
|
6
|
+
const row = (number: number, names: string[], logins: string[] = ['me']) => ({
|
|
7
|
+
number,
|
|
8
|
+
title: `t${number}`,
|
|
9
|
+
url: `https://github.com/o/r/issues/${number}`,
|
|
10
|
+
labels: names.map(name => ({ name })),
|
|
11
|
+
assignees: logins.map(login => ({ login })),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('parseLogin reads the viewer login, null on anything else', () => {
|
|
15
|
+
expect(parseLogin(JSON.stringify({ data: { viewer: { login: 'me' } } }))).toBe('me')
|
|
16
|
+
expect(parseLogin(JSON.stringify({ data: { viewer: { login: '' } } }))).toBeNull()
|
|
17
|
+
expect(parseLogin(JSON.stringify({ errors: [{}], data: { viewer: { login: 'me' } } }))).toBeNull()
|
|
18
|
+
expect(parseLogin('not json')).toBeNull()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('myTicketsFromRows tags each ticket with its map or the whiteboard, and drops the rest', () => {
|
|
22
|
+
const mine = myTicketsFromRows([
|
|
23
|
+
row(5, ['trailhead:ticket', 'trailhead:build', 'trailhead:map-12']),
|
|
24
|
+
row(6, ['trailhead:ticket', 'trailhead:bug', 'trailhead:whiteboard']),
|
|
25
|
+
// no scope, contradictory scopes, and quarantined: never shown
|
|
26
|
+
row(7, ['trailhead:ticket', 'trailhead:build']),
|
|
27
|
+
row(8, ['trailhead:ticket', 'trailhead:map-1', 'trailhead:whiteboard']),
|
|
28
|
+
row(9, ['trailhead:ticket', 'trailhead:map-1', 'trailhead:map-2']),
|
|
29
|
+
row(10, ['trailhead:ticket', 'trailhead:map-1', 'trailhead:unverified']),
|
|
30
|
+
])
|
|
31
|
+
expect(mine.map(t => [t.number, t.scope])).toEqual([
|
|
32
|
+
[5, { kind: 'map', number: 12 }],
|
|
33
|
+
[6, { kind: 'whiteboard' }],
|
|
34
|
+
])
|
|
35
|
+
expect(mine[0]?.state).toBe('in-progress')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('elsewhereInProgress keeps my in-progress tickets of the other scopes only', () => {
|
|
39
|
+
const mine = myTicketsFromRows([
|
|
40
|
+
row(1, ['trailhead:ticket', 'trailhead:map-12']),
|
|
41
|
+
row(2, ['trailhead:ticket', 'trailhead:map-30']),
|
|
42
|
+
row(3, ['trailhead:ticket', 'trailhead:whiteboard']),
|
|
43
|
+
// not in progress: paused, blocked, needs-you, UAT
|
|
44
|
+
row(4, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:paused']),
|
|
45
|
+
row(5, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:blocked']),
|
|
46
|
+
row(6, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:needs-you']),
|
|
47
|
+
row(7, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:uat']),
|
|
48
|
+
])
|
|
49
|
+
expect(elsewhereInProgress(mine, { kind: 'map', number: 12 }, []).map(t => t.number)).toEqual([2, 3])
|
|
50
|
+
expect(elsewhereInProgress(mine, { kind: 'whiteboard' }, []).map(t => t.number)).toEqual([1, 2])
|
|
51
|
+
// a ticket the view already lists is never shown twice
|
|
52
|
+
const shown = parseTickets(JSON.stringify([row(2, ['trailhead:ticket', 'trailhead:map-12'])]))?.tickets ?? []
|
|
53
|
+
expect(elsewhereInProgress(mine, { kind: 'map', number: 12 }, shown).map(t => t.number)).toEqual([3])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('scopeLine names the map by number and title, or the whiteboard, cut to the width', () => {
|
|
57
|
+
const [onMap, onBoard] = myTicketsFromRows([
|
|
58
|
+
row(1, ['trailhead:ticket', 'trailhead:map-30']),
|
|
59
|
+
row(2, ['trailhead:ticket', 'trailhead:whiteboard']),
|
|
60
|
+
])
|
|
61
|
+
const maps = [{ number: 30, title: 'Billing rework', counts: null }]
|
|
62
|
+
expect(onMap && scopeLine(onMap, maps, 60)).toBe('on map #30 Billing rework')
|
|
63
|
+
expect(onMap && scopeLine(onMap, [], 60)).toBe('on map #30')
|
|
64
|
+
expect(onBoard && scopeLine(onBoard, maps, 60)).toBe('on whiteboard')
|
|
65
|
+
expect((onMap && scopeLine(onMap, maps, 16))?.length).toBeLessThanOrEqual(16)
|
|
66
|
+
})
|
|
@@ -112,12 +112,27 @@ test('repoFromGit keeps a repo with no origin remote unpinned, so the last pin s
|
|
|
112
112
|
expect(nextPin(current, repo)).toBe(current)
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
const fromBase64url = (token: string): string => {
|
|
116
|
+
const bytes = Uint8Array.from(atob(token.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(token.length / 4) * 4, '=')), c => c.charCodeAt(0))
|
|
117
|
+
return new TextDecoder().decode(bytes)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
test('checkoutArg hands the root and the repo over as one shell-safe word the helper decodes back', () => {
|
|
116
121
|
for (const root of ['/repo', '/with space/r', "/it's/r", '/-dash/r', '/uni\u00e8\u4e2d/r', '/a"b$c`d']) {
|
|
117
122
|
const arg = checkoutArg({ ...GH, root })
|
|
118
|
-
expect(arg).toMatch(/^--in=[A-Za-z0-9_-]+$/)
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
expect(
|
|
123
|
+
expect(arg).toMatch(/^--in=[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/)
|
|
124
|
+
const [path, repo] = arg.slice('--in='.length).split('.')
|
|
125
|
+
expect(fromBase64url(path)).toBe(root)
|
|
126
|
+
expect(fromBase64url(repo)).toBe('github.com/o/r')
|
|
122
127
|
}
|
|
123
128
|
})
|
|
129
|
+
|
|
130
|
+
test('checkoutArg carries host and a nested slug in the repo part', () => {
|
|
131
|
+
const arg = checkoutArg({ root: '/w/p', host: 'gitlab.com', slug: 'g/sub/r' })
|
|
132
|
+
expect(fromBase64url(arg.split('.')[1])).toBe('gitlab.com/g/sub/r')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('checkoutArg carries a dotless host in the repo part', () => {
|
|
136
|
+
const arg = checkoutArg({ root: '/w/p', host: 'gitea', slug: 'owner/name' })
|
|
137
|
+
expect(fromBase64url(arg.split('.')[1])).toBe('gitea/owner/name')
|
|
138
|
+
})
|
|
@@ -42,6 +42,11 @@ export type PaneTicket = {
|
|
|
42
42
|
uatOf: UatParent | null
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Where one of my tickets lives: an open map by number, or the whiteboard.
|
|
46
|
+
export type TicketScope = { kind: 'map'; number: number } | { kind: 'whiteboard' }
|
|
47
|
+
// A ticket assigned to the gh login, tagged with its scope (the In progress group lists it outside that scope).
|
|
48
|
+
export type MyTicket = PaneTicket & { scope: TicketScope }
|
|
49
|
+
|
|
45
50
|
// A map's sub-issue counts: `open` still to do, `total` all of them.
|
|
46
51
|
export type MapCounts = { open: number; total: number }
|
|
47
52
|
// One open map of the repo, as the maps list shows it; `counts` null = unknown, drawn name-only.
|
|
@@ -113,6 +118,8 @@ declare module 'claude-code' {
|
|
|
113
118
|
// The pane shows the dashboard (maps index + whiteboard) instead of a map.
|
|
114
119
|
isDashboard: boolean
|
|
115
120
|
dashboardPane: DashboardPaneData
|
|
121
|
+
// My open tickets across every map and the whiteboard (the last good read), for every view's In progress.
|
|
122
|
+
myTickets: readonly MyTicket[]
|
|
116
123
|
pendingWork: number | null
|
|
117
124
|
// Whether the pending press waits for the turn to end or has been sent.
|
|
118
125
|
pendingPhase: 'queued' | 'starting' | null
|