@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.
Files changed (88) hide show
  1. package/README.it.md +67 -18
  2. package/README.md +67 -18
  3. package/bin/capture-action.test.js +167 -0
  4. package/bin/capture-docs.test.js +32 -0
  5. package/bin/config-dir-templates-docs.test.js +52 -0
  6. package/bin/decision-builds-docs.test.js +75 -0
  7. package/bin/engine-route-docs.test.js +148 -0
  8. package/bin/fog-vs-ticket-docs.test.js +56 -0
  9. package/bin/grill-model-docs.test.js +155 -0
  10. package/bin/grilling-holistic-review-docs.test.js +7 -3
  11. package/bin/lib/codex-projection.js +30 -14
  12. package/bin/lib/codex-projection.test.js +81 -17
  13. package/bin/lib/host-descriptor.js +15 -2
  14. package/bin/lib/host-descriptor.test.js +24 -0
  15. package/bin/lib/runtime-command-literals.test.js +184 -0
  16. package/bin/report-status-docs.test.js +11 -3
  17. package/bin/session-trust-docs.test.js +37 -0
  18. package/bin/surfaced-scope-docs.test.js +57 -0
  19. package/bin/trailhead.js +7 -5
  20. package/bin/trailhead.test.js +53 -11
  21. package/bin/unblock-docs.test.js +56 -0
  22. package/package.json +1 -1
  23. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  24. package/plugins/trailhead/agents/trailhead-code-review.md +2 -2
  25. package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
  26. package/plugins/trailhead/agents/trailhead-grill.md +41 -0
  27. package/plugins/trailhead/agents/trailhead-verify.md +2 -2
  28. package/plugins/trailhead/hooks/lib/capture.test.js +439 -0
  29. package/plugins/trailhead/hooks/lib/checkout-find.test.js +195 -0
  30. package/plugins/trailhead/hooks/lib/checkout-in.test.js +83 -7
  31. package/plugins/trailhead/hooks/lib/dashboard.test.js +19 -0
  32. package/plugins/trailhead/hooks/lib/gh-auth-preflight.js +89 -0
  33. package/plugins/trailhead/hooks/lib/gh-auth-preflight.test.js +132 -0
  34. package/plugins/trailhead/hooks/lib/inherit-session.js +17 -0
  35. package/plugins/trailhead/hooks/lib/issue-body.test.js +84 -0
  36. package/plugins/trailhead/hooks/lib/model-defaults-review.js +72 -22
  37. package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +113 -0
  38. package/plugins/trailhead/hooks/lib/model-route.js +207 -0
  39. package/plugins/trailhead/hooks/lib/model-route.test.js +231 -0
  40. package/plugins/trailhead/hooks/lib/report-status.test.js +124 -2
  41. package/plugins/trailhead/hooks/lib/shell-scan.js +25 -0
  42. package/plugins/trailhead/hooks/lib/statusline-context.test.js +22 -1
  43. package/plugins/trailhead/hooks/lib/unmodelled.js +6 -1
  44. package/plugins/trailhead/hooks/mod/README.md +24 -10
  45. package/plugins/trailhead/hooks/mod/map-pane.tsx +124 -44
  46. package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
  47. package/plugins/trailhead/hooks/mod/repo-pin.ts +8 -5
  48. package/plugins/trailhead/hooks/trailhead-check-update.js +17 -1
  49. package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
  50. package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +40 -1
  51. package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +68 -0
  52. package/plugins/trailhead/skills/_shared/capture-core.md +118 -0
  53. package/plugins/trailhead/skills/_shared/choices.md +2 -2
  54. package/plugins/trailhead/skills/_shared/configuration-reference.md +20 -18
  55. package/plugins/trailhead/skills/_shared/configuration.md +1 -1
  56. package/plugins/trailhead/skills/_shared/session-handoff.md +2 -2
  57. package/plugins/trailhead/skills/_shared/substrate-commands.md +8 -4
  58. package/plugins/trailhead/skills/_shared/substrate.md +1 -1
  59. package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
  60. package/plugins/trailhead/skills/_shared/teamwork.md +9 -5
  61. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
  62. package/plugins/trailhead/skills/_shared/techniques/code-review.md +5 -2
  63. package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +4 -1
  64. package/plugins/trailhead/skills/_shared/techniques/debug.md +4 -1
  65. package/plugins/trailhead/skills/_shared/techniques/grilling.md +26 -1
  66. package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
  67. package/plugins/trailhead/skills/_shared/techniques/verify.md +4 -2
  68. package/plugins/trailhead/skills/_shared/techniques.md +7 -5
  69. package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -4
  70. package/plugins/trailhead/skills/trailhead-capture/references/capture.md +29 -4
  71. package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
  72. package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
  73. package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +3 -3
  74. package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +5 -4
  75. package/plugins/trailhead/skills/trailhead-work/SKILL.md +13 -11
  76. package/plugins/trailhead/skills/trailhead-work/references/auto.md +1 -1
  77. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +11 -10
  78. package/plugins/trailhead/templates/statusline-trailhead.sh +3 -3
  79. package/plugins/trailhead/templates/trailhead-capture.js +418 -0
  80. package/plugins/trailhead/templates/trailhead-checkout-find.js +256 -0
  81. package/plugins/trailhead/templates/trailhead-checkout-in.js +76 -20
  82. package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
  83. package/plugins/trailhead/templates/trailhead-issue-body.js +69 -6
  84. package/plugins/trailhead/templates/trailhead-report-status.js +123 -11
  85. package/plugins/trailhead/tests/map-pane-ui.test.ts +299 -37
  86. package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
  87. package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
  88. 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
- test('checkoutArg hands the root over as one shell-safe word the helper decodes back', () => {
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 token = arg.slice('--in='.length)
120
- const bytes = Uint8Array.from(atob(token.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(token.length / 4) * 4, '=')), c => c.charCodeAt(0))
121
- expect(new TextDecoder().decode(bytes)).toBe(root)
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