issue-flow 0.16.0 → 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.
Files changed (168) hide show
  1. package/dist/agent-HMZTXCRT.js +30 -0
  2. package/dist/{analyze-556KCJKZ.js → analyze-Q4ONIWRZ.js} +22 -22
  3. package/dist/{apply-WVV7G7WN.js → apply-EEFPY2W4.js} +14 -14
  4. package/dist/{bench-UBFGKUHR.js → bench-WWWXLTFH.js} +23 -23
  5. package/dist/{chunk-GV2DABNZ.js → chunk-34OCSKOB.js} +12 -12
  6. package/dist/chunk-3R27MJ5R.js +25 -0
  7. package/dist/chunk-3R27MJ5R.js.map +1 -0
  8. package/dist/{chunk-PGN2VX4Y.js → chunk-45WZ6GMI.js} +32 -48
  9. package/dist/chunk-45WZ6GMI.js.map +1 -0
  10. package/dist/{chunk-H3EYBJZU.js → chunk-62GVVZQO.js} +10 -10
  11. package/dist/{chunk-GPLP7EPL.js → chunk-6K2LCFG6.js} +10 -10
  12. package/dist/{chunk-GPLP7EPL.js.map → chunk-6K2LCFG6.js.map} +1 -1
  13. package/dist/{chunk-OGVWJO4S.js → chunk-7F7LGMVO.js} +3 -3
  14. package/dist/{chunk-X6GY3CM2.js → chunk-7UMMFOL2.js} +104 -104
  15. package/dist/chunk-7UMMFOL2.js.map +1 -0
  16. package/dist/{chunk-OLUOZLU5.js → chunk-CWSQVHVV.js} +1691 -1075
  17. package/dist/chunk-CWSQVHVV.js.map +1 -0
  18. package/dist/{chunk-MXCOSPLJ.js → chunk-DGRTJD5R.js} +3 -3
  19. package/dist/{chunk-ROIIEVZH.js → chunk-DUS5ZNKL.js} +474 -361
  20. package/dist/chunk-DUS5ZNKL.js.map +1 -0
  21. package/dist/{chunk-BSBJXPLC.js → chunk-E4I6MMHD.js} +10 -10
  22. package/dist/{chunk-QZOBR6YF.js → chunk-ECU7YO7S.js} +4 -4
  23. package/dist/chunk-ECU7YO7S.js.map +1 -0
  24. package/dist/{chunk-EPFC4LHT.js → chunk-EG2MGDP5.js} +9 -9
  25. package/dist/chunk-HOAHMERN.js +229 -0
  26. package/dist/chunk-HOAHMERN.js.map +1 -0
  27. package/dist/{chunk-QVLIIGBU.js → chunk-ICM463EO.js} +60 -13
  28. package/dist/chunk-ICM463EO.js.map +1 -0
  29. package/dist/{chunk-4FUWWBTP.js → chunk-KMCS76ZW.js} +8 -8
  30. package/dist/{chunk-SW2MF47D.js → chunk-KRAGAUKD.js} +19 -19
  31. package/dist/{chunk-OOX3RMD3.js → chunk-LGHND47K.js} +4 -4
  32. package/dist/{chunk-VTF4RWS5.js → chunk-LI35PIIS.js} +7 -7
  33. package/dist/{chunk-Q2TZA6AC.js → chunk-LTH6XYZ3.js} +14 -14
  34. package/dist/{chunk-YA6FWFYW.js → chunk-NEWNXZ65.js} +62 -11
  35. package/dist/chunk-NEWNXZ65.js.map +1 -0
  36. package/dist/{chunk-2H43OVTU.js → chunk-OODXFCQV.js} +11 -11
  37. package/dist/{chunk-KV2K7MK2.js → chunk-PMLE7KAQ.js} +20 -12
  38. package/dist/chunk-PMLE7KAQ.js.map +1 -0
  39. package/dist/{chunk-R42YHLDV.js → chunk-PPZ3K4YM.js} +2 -2
  40. package/dist/{chunk-J2LJB2OH.js → chunk-QF7VEL3M.js} +739 -579
  41. package/dist/chunk-QF7VEL3M.js.map +1 -0
  42. package/dist/{chunk-VK65RNFA.js → chunk-QJ44VG5G.js} +3 -3
  43. package/dist/{chunk-FKWYD44M.js → chunk-QPGEFLMY.js} +2 -2
  44. package/dist/{chunk-NQCR3HSU.js → chunk-RNFUVKC3.js} +3 -3
  45. package/dist/{chunk-G5LACAXO.js → chunk-RYOUZ6EK.js} +2 -2
  46. package/dist/{chunk-MCLPSL6B.js → chunk-SA6HXL4Y.js} +3 -3
  47. package/dist/{chunk-GVOZ6NDO.js → chunk-SOM4QA6C.js} +3 -3
  48. package/dist/{chunk-73TS2H42.js → chunk-TZYINKCC.js} +154 -34
  49. package/dist/chunk-TZYINKCC.js.map +1 -0
  50. package/dist/{chunk-DBEMD34J.js → chunk-V7QOIQYC.js} +8 -8
  51. package/dist/{chunk-IAMIBZRF.js → chunk-WCCZJL7W.js} +2 -2
  52. package/dist/{chunk-OGN2NX57.js → chunk-XUEMATVY.js} +9 -9
  53. package/dist/{chunk-SVDEWOUD.js → chunk-XZUEFTUP.js} +6 -6
  54. package/dist/{chunk-WLJVL25O.js → chunk-YUNBOUQH.js} +6 -6
  55. package/dist/{chunk-WNROH2YE.js → chunk-YVPZUN6X.js} +2 -2
  56. package/dist/chunk-ZVZMWNCG.js +415 -0
  57. package/dist/chunk-ZVZMWNCG.js.map +1 -0
  58. package/dist/cli.js +57 -49
  59. package/dist/cli.js.map +1 -1
  60. package/dist/{config-UE7IP36J.js → config-LEBZZCJP.js} +13 -13
  61. package/dist/{conventions-Z3QC2NSG.js → conventions-XOT6YW5L.js} +15 -15
  62. package/dist/{diagnostics-2Z5HFQOO.js → diagnostics-HTCWF7F4.js} +7 -7
  63. package/dist/execute-LELOSR5T.js +37 -0
  64. package/dist/{generate-PB4L7V5N.js → generate-JGW5JSKB.js} +22 -22
  65. package/dist/{git-7MUOGVRG.js → git-A2IP32PQ.js} +2 -2
  66. package/dist/init-TO6MUXO4.js +26 -0
  67. package/dist/{operations-QDJAFZUN.js → operations-O4FORFIH.js} +15 -15
  68. package/dist/{permissions-XVXXENVP.js → permissions-G4XCAESF.js} +4 -4
  69. package/dist/plan-OXAT344Q.js +34 -0
  70. package/dist/{policy-6WQZB6B2.js → policy-U5NBFAO5.js} +14 -14
  71. package/dist/pr-UZHNKKZC.js +39 -0
  72. package/dist/pr-review-Q4ACAWDM.js +32 -0
  73. package/dist/prd-6CYEMLDY.js +33 -0
  74. package/dist/{ps-CX7756QG.js → ps-76L6ZAZL.js} +12 -12
  75. package/dist/{recorder-RK6XZYF3.js → recorder-2BAKPBRA.js} +7 -7
  76. package/dist/{registry-POSB35Q4.js → registry-XLHHYLTR.js} +5 -5
  77. package/dist/{resume-OQJZV3JH.js → resume-URCU6CIJ.js} +39 -38
  78. package/dist/{resume-OQJZV3JH.js.map → resume-URCU6CIJ.js.map} +1 -1
  79. package/dist/review-LVZT2TMW.js +35 -0
  80. package/dist/routing-EK6RBZM4.js +33 -0
  81. package/dist/run-2RXJCJKT.js +56 -0
  82. package/dist/{session-publisher-VVXOCJ2S.js → session-publisher-KAJQRHNZ.js} +3 -2
  83. package/dist/{state-manager-TIC73GQY.js → state-manager-UPHO2EA2.js} +4 -4
  84. package/dist/state-manager-UPHO2EA2.js.map +1 -0
  85. package/dist/{usage-WDDNSIHP.js → usage-KTMYXAPF.js} +12 -12
  86. package/dist/{web-IPGY52AK.js → web-B5UPHHR5.js} +20 -18
  87. package/dist/{web-IPGY52AK.js.map → web-B5UPHHR5.js.map} +1 -1
  88. package/package.json +6 -5
  89. package/web/AGENTS.md +125 -18
  90. package/web/public/app.css +420 -234
  91. package/web/public/app.js +371 -80
  92. package/web/public/index.html +106 -78
  93. package/dist/agent-PMWCDWOW.js +0 -30
  94. package/dist/chunk-73TS2H42.js.map +0 -1
  95. package/dist/chunk-7Z4Y3VKM.js +0 -26
  96. package/dist/chunk-7Z4Y3VKM.js.map +0 -1
  97. package/dist/chunk-AOAQU7PD.js +0 -180
  98. package/dist/chunk-AOAQU7PD.js.map +0 -1
  99. package/dist/chunk-J2LJB2OH.js.map +0 -1
  100. package/dist/chunk-KV2K7MK2.js.map +0 -1
  101. package/dist/chunk-OLUOZLU5.js.map +0 -1
  102. package/dist/chunk-PGN2VX4Y.js.map +0 -1
  103. package/dist/chunk-QVLIIGBU.js.map +0 -1
  104. package/dist/chunk-QZOBR6YF.js.map +0 -1
  105. package/dist/chunk-ROIIEVZH.js.map +0 -1
  106. package/dist/chunk-X6GY3CM2.js.map +0 -1
  107. package/dist/chunk-YA6FWFYW.js.map +0 -1
  108. package/dist/execute-QYLJ5L4T.js +0 -36
  109. package/dist/init-XBERAZ4O.js +0 -24
  110. package/dist/plan-RXOW32KY.js +0 -34
  111. package/dist/pr-3WFYAVA7.js +0 -39
  112. package/dist/pr-review-RK63ZEMM.js +0 -32
  113. package/dist/prd-LRV6IW3E.js +0 -33
  114. package/dist/review-AEFISJZD.js +0 -35
  115. package/dist/routing-EA5NJGNU.js +0 -94
  116. package/dist/routing-EA5NJGNU.js.map +0 -1
  117. package/dist/run-HSC7R33Y.js +0 -55
  118. /package/dist/{agent-PMWCDWOW.js.map → agent-HMZTXCRT.js.map} +0 -0
  119. /package/dist/{analyze-556KCJKZ.js.map → analyze-Q4ONIWRZ.js.map} +0 -0
  120. /package/dist/{apply-WVV7G7WN.js.map → apply-EEFPY2W4.js.map} +0 -0
  121. /package/dist/{bench-UBFGKUHR.js.map → bench-WWWXLTFH.js.map} +0 -0
  122. /package/dist/{chunk-GV2DABNZ.js.map → chunk-34OCSKOB.js.map} +0 -0
  123. /package/dist/{chunk-H3EYBJZU.js.map → chunk-62GVVZQO.js.map} +0 -0
  124. /package/dist/{chunk-OGVWJO4S.js.map → chunk-7F7LGMVO.js.map} +0 -0
  125. /package/dist/{chunk-MXCOSPLJ.js.map → chunk-DGRTJD5R.js.map} +0 -0
  126. /package/dist/{chunk-BSBJXPLC.js.map → chunk-E4I6MMHD.js.map} +0 -0
  127. /package/dist/{chunk-EPFC4LHT.js.map → chunk-EG2MGDP5.js.map} +0 -0
  128. /package/dist/{chunk-4FUWWBTP.js.map → chunk-KMCS76ZW.js.map} +0 -0
  129. /package/dist/{chunk-SW2MF47D.js.map → chunk-KRAGAUKD.js.map} +0 -0
  130. /package/dist/{chunk-OOX3RMD3.js.map → chunk-LGHND47K.js.map} +0 -0
  131. /package/dist/{chunk-VTF4RWS5.js.map → chunk-LI35PIIS.js.map} +0 -0
  132. /package/dist/{chunk-Q2TZA6AC.js.map → chunk-LTH6XYZ3.js.map} +0 -0
  133. /package/dist/{chunk-2H43OVTU.js.map → chunk-OODXFCQV.js.map} +0 -0
  134. /package/dist/{chunk-R42YHLDV.js.map → chunk-PPZ3K4YM.js.map} +0 -0
  135. /package/dist/{chunk-VK65RNFA.js.map → chunk-QJ44VG5G.js.map} +0 -0
  136. /package/dist/{chunk-FKWYD44M.js.map → chunk-QPGEFLMY.js.map} +0 -0
  137. /package/dist/{chunk-NQCR3HSU.js.map → chunk-RNFUVKC3.js.map} +0 -0
  138. /package/dist/{chunk-G5LACAXO.js.map → chunk-RYOUZ6EK.js.map} +0 -0
  139. /package/dist/{chunk-MCLPSL6B.js.map → chunk-SA6HXL4Y.js.map} +0 -0
  140. /package/dist/{chunk-GVOZ6NDO.js.map → chunk-SOM4QA6C.js.map} +0 -0
  141. /package/dist/{chunk-DBEMD34J.js.map → chunk-V7QOIQYC.js.map} +0 -0
  142. /package/dist/{chunk-IAMIBZRF.js.map → chunk-WCCZJL7W.js.map} +0 -0
  143. /package/dist/{chunk-OGN2NX57.js.map → chunk-XUEMATVY.js.map} +0 -0
  144. /package/dist/{chunk-SVDEWOUD.js.map → chunk-XZUEFTUP.js.map} +0 -0
  145. /package/dist/{chunk-WLJVL25O.js.map → chunk-YUNBOUQH.js.map} +0 -0
  146. /package/dist/{chunk-WNROH2YE.js.map → chunk-YVPZUN6X.js.map} +0 -0
  147. /package/dist/{config-UE7IP36J.js.map → config-LEBZZCJP.js.map} +0 -0
  148. /package/dist/{conventions-Z3QC2NSG.js.map → conventions-XOT6YW5L.js.map} +0 -0
  149. /package/dist/{diagnostics-2Z5HFQOO.js.map → diagnostics-HTCWF7F4.js.map} +0 -0
  150. /package/dist/{execute-QYLJ5L4T.js.map → execute-LELOSR5T.js.map} +0 -0
  151. /package/dist/{generate-PB4L7V5N.js.map → generate-JGW5JSKB.js.map} +0 -0
  152. /package/dist/{git-7MUOGVRG.js.map → git-A2IP32PQ.js.map} +0 -0
  153. /package/dist/{init-XBERAZ4O.js.map → init-TO6MUXO4.js.map} +0 -0
  154. /package/dist/{operations-QDJAFZUN.js.map → operations-O4FORFIH.js.map} +0 -0
  155. /package/dist/{permissions-XVXXENVP.js.map → permissions-G4XCAESF.js.map} +0 -0
  156. /package/dist/{plan-RXOW32KY.js.map → plan-OXAT344Q.js.map} +0 -0
  157. /package/dist/{policy-6WQZB6B2.js.map → policy-U5NBFAO5.js.map} +0 -0
  158. /package/dist/{pr-3WFYAVA7.js.map → pr-UZHNKKZC.js.map} +0 -0
  159. /package/dist/{pr-review-RK63ZEMM.js.map → pr-review-Q4ACAWDM.js.map} +0 -0
  160. /package/dist/{prd-LRV6IW3E.js.map → prd-6CYEMLDY.js.map} +0 -0
  161. /package/dist/{ps-CX7756QG.js.map → ps-76L6ZAZL.js.map} +0 -0
  162. /package/dist/{recorder-RK6XZYF3.js.map → recorder-2BAKPBRA.js.map} +0 -0
  163. /package/dist/{registry-POSB35Q4.js.map → registry-XLHHYLTR.js.map} +0 -0
  164. /package/dist/{review-AEFISJZD.js.map → review-LVZT2TMW.js.map} +0 -0
  165. /package/dist/{run-HSC7R33Y.js.map → routing-EK6RBZM4.js.map} +0 -0
  166. /package/dist/{session-publisher-VVXOCJ2S.js.map → run-2RXJCJKT.js.map} +0 -0
  167. /package/dist/{state-manager-TIC73GQY.js.map → session-publisher-KAJQRHNZ.js.map} +0 -0
  168. /package/dist/{usage-WDDNSIHP.js.map → usage-KTMYXAPF.js.map} +0 -0
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ publishIssueDetails,
4
+ publishStorySeed,
5
+ runPipeline
6
+ } from "./chunk-CWSQVHVV.js";
7
+ import "./chunk-QJ44VG5G.js";
8
+ import "./chunk-OODXFCQV.js";
9
+ import "./chunk-LTH6XYZ3.js";
10
+ import "./chunk-YUNBOUQH.js";
11
+ import "./chunk-XUEMATVY.js";
12
+ import "./chunk-EG2MGDP5.js";
13
+ import "./chunk-7UMMFOL2.js";
14
+ import "./chunk-PMLE7KAQ.js";
15
+ import "./chunk-KRAGAUKD.js";
16
+ import "./chunk-NEWNXZ65.js";
17
+ import "./chunk-ECU7YO7S.js";
18
+ import "./chunk-E4I6MMHD.js";
19
+ import "./chunk-CRGIQFUQ.js";
20
+ import "./chunk-L6HSKTQE.js";
21
+ import "./chunk-45WZ6GMI.js";
22
+ import "./chunk-HOAHMERN.js";
23
+ import "./chunk-34OCSKOB.js";
24
+ import "./chunk-V7QOIQYC.js";
25
+ import "./chunk-SA6HXL4Y.js";
26
+ import "./chunk-62GVVZQO.js";
27
+ import "./chunk-KMMASOA6.js";
28
+ import "./chunk-KMCS76ZW.js";
29
+ import "./chunk-7F7LGMVO.js";
30
+ import "./chunk-TZYINKCC.js";
31
+ import "./chunk-WCCZJL7W.js";
32
+ import "./chunk-DGRTJD5R.js";
33
+ import "./chunk-ZVZMWNCG.js";
34
+ import "./chunk-C5TL7T4V.js";
35
+ import "./chunk-3D4A4B46.js";
36
+ import "./chunk-LGHND47K.js";
37
+ import "./chunk-QF7VEL3M.js";
38
+ import "./chunk-LI35PIIS.js";
39
+ import "./chunk-XZUEFTUP.js";
40
+ import "./chunk-QPGEFLMY.js";
41
+ import "./chunk-HAKDLR6N.js";
42
+ import "./chunk-YVPZUN6X.js";
43
+ import "./chunk-DUS5ZNKL.js";
44
+ import "./chunk-6K2LCFG6.js";
45
+ import "./chunk-PPZ3K4YM.js";
46
+ import "./chunk-SOM4QA6C.js";
47
+ import "./chunk-ICM463EO.js";
48
+ import "./chunk-XEYH6ZZI.js";
49
+ import "./chunk-P3LRUNC6.js";
50
+ import "./chunk-3R27MJ5R.js";
51
+ export {
52
+ publishIssueDetails,
53
+ publishStorySeed,
54
+ runPipeline
55
+ };
56
+ //# sourceMappingURL=run-2RXJCJKT.js.map
@@ -2,10 +2,11 @@
2
2
  import {
3
3
  getSessionPublisher,
4
4
  setSessionPublisher
5
- } from "./chunk-ROIIEVZH.js";
5
+ } from "./chunk-DUS5ZNKL.js";
6
6
  import "./chunk-P3LRUNC6.js";
7
+ import "./chunk-3R27MJ5R.js";
7
8
  export {
8
9
  getSessionPublisher,
9
10
  setSessionPublisher
10
11
  };
11
- //# sourceMappingURL=session-publisher-VVXOCJ2S.js.map
12
+ //# sourceMappingURL=session-publisher-KAJQRHNZ.js.map
@@ -14,9 +14,9 @@ import {
14
14
  selectActiveStory,
15
15
  setLastError,
16
16
  trimErrorMessage
17
- } from "./chunk-GVOZ6NDO.js";
18
- import "./chunk-QVLIIGBU.js";
19
- import "./chunk-7Z4Y3VKM.js";
17
+ } from "./chunk-SOM4QA6C.js";
18
+ import "./chunk-ICM463EO.js";
19
+ import "./chunk-3R27MJ5R.js";
20
20
  export {
21
21
  allStoriesPass,
22
22
  applyStoryMetrics,
@@ -33,4 +33,4 @@ export {
33
33
  setLastError,
34
34
  trimErrorMessage
35
35
  };
36
- //# sourceMappingURL=state-manager-TIC73GQY.js.map
36
+ //# sourceMappingURL=state-manager-UPHO2EA2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -5,28 +5,28 @@ import {
5
5
  } from "./chunk-CRGIQFUQ.js";
6
6
  import {
7
7
  resolveProjectPaths
8
- } from "./chunk-VTF4RWS5.js";
8
+ } from "./chunk-LI35PIIS.js";
9
9
  import {
10
10
  printError,
11
11
  printInfo
12
- } from "./chunk-SVDEWOUD.js";
13
- import "./chunk-ROIIEVZH.js";
14
- import "./chunk-FKWYD44M.js";
12
+ } from "./chunk-XZUEFTUP.js";
13
+ import "./chunk-QPGEFLMY.js";
15
14
  import {
16
15
  getIssuePaths
17
16
  } from "./chunk-HAKDLR6N.js";
18
- import "./chunk-WNROH2YE.js";
17
+ import "./chunk-YVPZUN6X.js";
18
+ import "./chunk-DUS5ZNKL.js";
19
19
  import {
20
20
  discardedExecutionCount
21
- } from "./chunk-GPLP7EPL.js";
22
- import "./chunk-P3LRUNC6.js";
23
- import "./chunk-R42YHLDV.js";
21
+ } from "./chunk-6K2LCFG6.js";
22
+ import "./chunk-PPZ3K4YM.js";
24
23
  import {
25
24
  loadTaskPlan
26
- } from "./chunk-GVOZ6NDO.js";
27
- import "./chunk-QVLIIGBU.js";
28
- import "./chunk-7Z4Y3VKM.js";
25
+ } from "./chunk-SOM4QA6C.js";
26
+ import "./chunk-ICM463EO.js";
29
27
  import "./chunk-XEYH6ZZI.js";
28
+ import "./chunk-P3LRUNC6.js";
29
+ import "./chunk-3R27MJ5R.js";
30
30
 
31
31
  // src/commands/usage.ts
32
32
  import { readdir } from "fs/promises";
@@ -174,4 +174,4 @@ export {
174
174
  formatUsageReport,
175
175
  runUsage
176
176
  };
177
- //# sourceMappingURL=usage-WDDNSIHP.js.map
177
+ //# sourceMappingURL=usage-KTMYXAPF.js.map
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readSessionFile
4
- } from "./chunk-G5LACAXO.js";
4
+ } from "./chunk-RYOUZ6EK.js";
5
5
  import {
6
6
  ensureSingleWebServer,
7
7
  stopWebMonitor
8
- } from "./chunk-YA6FWFYW.js";
9
- import "./chunk-PGN2VX4Y.js";
10
- import "./chunk-OGVWJO4S.js";
8
+ } from "./chunk-NEWNXZ65.js";
9
+ import "./chunk-45WZ6GMI.js";
10
+ import "./chunk-HOAHMERN.js";
11
+ import "./chunk-7F7LGMVO.js";
11
12
  import {
12
13
  parseJournal
13
- } from "./chunk-IAMIBZRF.js";
14
+ } from "./chunk-WCCZJL7W.js";
15
+ import "./chunk-ZVZMWNCG.js";
14
16
  import "./chunk-C5TL7T4V.js";
15
17
  import {
16
18
  loadWebConfig
17
- } from "./chunk-J2LJB2OH.js";
18
- import "./chunk-VTF4RWS5.js";
19
- import "./chunk-SVDEWOUD.js";
20
- import "./chunk-ROIIEVZH.js";
21
- import "./chunk-FKWYD44M.js";
19
+ } from "./chunk-QF7VEL3M.js";
20
+ import "./chunk-LI35PIIS.js";
21
+ import "./chunk-XZUEFTUP.js";
22
+ import "./chunk-QPGEFLMY.js";
22
23
  import {
23
24
  EVENTS_FILENAME,
24
25
  ISSUES_DIR_NAME,
@@ -27,14 +28,15 @@ import {
27
28
  SESSION_FILENAME,
28
29
  getGlobalRoot
29
30
  } from "./chunk-HAKDLR6N.js";
30
- import "./chunk-WNROH2YE.js";
31
- import "./chunk-GPLP7EPL.js";
32
- import "./chunk-P3LRUNC6.js";
33
- import "./chunk-R42YHLDV.js";
34
- import "./chunk-GVOZ6NDO.js";
35
- import "./chunk-QVLIIGBU.js";
36
- import "./chunk-7Z4Y3VKM.js";
31
+ import "./chunk-YVPZUN6X.js";
32
+ import "./chunk-DUS5ZNKL.js";
33
+ import "./chunk-6K2LCFG6.js";
34
+ import "./chunk-PPZ3K4YM.js";
35
+ import "./chunk-SOM4QA6C.js";
36
+ import "./chunk-ICM463EO.js";
37
37
  import "./chunk-XEYH6ZZI.js";
38
+ import "./chunk-P3LRUNC6.js";
39
+ import "./chunk-3R27MJ5R.js";
38
40
 
39
41
  // src/web/session-directory.ts
40
42
  import { readdir, readFile } from "fs/promises";
@@ -167,4 +169,4 @@ export {
167
169
  runWebServe,
168
170
  runWebStop
169
171
  };
170
- //# sourceMappingURL=web-IPGY52AK.js.map
172
+ //# sourceMappingURL=web-B5UPHHR5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/web/session-directory.ts","../src/commands/web.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type JournalEntry, parseJournal } from '../core/journal.js';\nimport type { ValidatedSessionSnapshot } from '../schemas.js';\nimport {\n EVENTS_FILENAME,\n type GetGlobalRootOptions,\n getGlobalRoot,\n ISSUES_DIR_NAME,\n PROJECTS_DIR_NAME,\n ROTATED_EVENTS_FILENAME,\n SESSION_FILENAME,\n} from '../storage/paths.js';\nimport { readSessionFile } from '../storage/session-file.js';\n\n/**\n * Multi-session discovery for the web monitoring server (US-003).\n *\n * The single-instance server (`web/lock.ts`) runs decoupled from any one\n * pipeline invocation, so it cannot hold a `SessionPublisher` in memory for\n * \"the\" run being monitored — there may be zero, one or several running at\n * once, each in its own process. Instead this module polls every\n * `~/.issue-flow/projects/<project>/issues/<n>/session.json` on disk (the\n * same file `FilePublisher` already writes) and keeps an in-memory map of the\n * ones that are both well-formed and recently updated.\n *\n * Polling rather than `fs.watch` is a deliberate choice, not a placeholder:\n * `fs.watch`'s `recursive` option is only reliable on macOS and Windows (Linux\n * needs a manual per-directory watch tree, and the whole thing still degrades\n * to polling on network filesystems), while a `~/.issue-flow` tree is small\n * and local — a cheap poll of it is simpler and behaves identically on every\n * platform, which is what the US-003 acceptance criteria ask to validate.\n */\n\n/** How often the directory tree is rescanned, unless overridden. */\nexport const DEFAULT_POLL_INTERVAL_MS = 3000;\n\n/**\n * A session stops being reported once its file has gone this long without an\n * update. `FilePublisher` touches a live session every 10s without rewriting\n * its content. Three missed heartbeats plus ample scheduling/filesystem slack\n * avoid flapping while still removing an abruptly killed run promptly.\n */\nexport const DEFAULT_STALE_AFTER_MS = 90_000;\n\nexport interface ActiveSession {\n /** Directory the session.json was read from, one level up from the file. */\n issueDir: string;\n filePath: string;\n snapshot: ValidatedSessionSnapshot;\n /** mtime of the file at the last successful read, in epoch ms. */\n updatedAtMs: number;\n}\n\nexport interface SessionDirectoryOptions extends GetGlobalRootOptions {\n /** Poll interval, in ms. Default {@link DEFAULT_POLL_INTERVAL_MS}. */\n pollIntervalMs?: number;\n /** Staleness threshold, in ms. Default {@link DEFAULT_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Invoked (at most once) when a poll fails unexpectedly. Best-effort logging only. */\n onWarn?: (message: string) => void;\n}\n\nexport interface SessionDirectoryHandle {\n /** Snapshot of every session currently considered active. */\n sessions(): ActiveSession[];\n /** A single active session by id, or undefined. */\n getSession(sessionId: string): ActiveSession | undefined;\n /** Journal entries for one active session, oldest generation first. */\n events(sessionId: string): Promise<JournalEntry[] | undefined>;\n /** Force an immediate rescan instead of waiting for the next tick. Never throws. */\n refresh(): Promise<void>;\n /** Stop polling. Idempotent. */\n close(): void;\n}\n\nasync function listSubdirectories(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n // Symlinks are skipped on purpose, same discipline as the storage\n // migration walk: following one could pull session data from outside the\n // global tree into the listing.\n return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);\n } catch {\n // Missing/unreadable projects (or issues) directory reads as \"none yet\" —\n // this is routinely the case before the first --web run ever happens.\n return [];\n }\n}\n\n/** Every `session.json` path currently on disk, one per project/issue pair. */\nasync function discoverSessionFiles(root: string): Promise<string[]> {\n const projectsDir = join(root, PROJECTS_DIR_NAME);\n const projectIds = await listSubdirectories(projectsDir);\n\n const perProject = await Promise.all(\n projectIds.map(async (projectId) => {\n const issuesDir = join(projectsDir, projectId, ISSUES_DIR_NAME);\n const issueIds = await listSubdirectories(issuesDir);\n return issueIds.map((issueId) => join(issuesDir, issueId, SESSION_FILENAME));\n }),\n );\n\n return perProject.flat();\n}\n\nasync function readJournalFile(filePath: string): Promise<JournalEntry[]> {\n try {\n return parseJournal(await readFile(filePath, 'utf-8'));\n } catch {\n return [];\n }\n}\n\n/**\n * Start polling the global storage tree for active sessions.\n *\n * Returns a handle immediately; the first scan happens synchronously in the\n * background and `sessions()` answers `[]` until it completes, same as \"no\n * sessions found yet\" — there is nothing to await here that would not also\n * delay the server's very first bind.\n */\nexport function watchSessionDirectory(\n options: SessionDirectoryOptions = {},\n): SessionDirectoryHandle {\n const root = getGlobalRoot(options);\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;\n const warn = options.onWarn;\n\n // Keyed by sessionId, not by file path: a session's identity is the id\n // inside its content, and that is what /api/status?session=<id> looks up.\n let sessions = new Map<string, ActiveSession>();\n let warned = false;\n\n async function scan(): Promise<void> {\n let files: string[];\n try {\n files = await discoverSessionFiles(root);\n } catch (err) {\n if (!warned) {\n warned = true;\n warn?.(\n `issue-flow: web monitor could not scan the session directory (will keep retrying silently): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n return;\n }\n\n const now = Date.now();\n const next = new Map<string, ActiveSession>();\n for (const filePath of files) {\n const result = await readSessionFile(filePath);\n if (result === null) continue;\n if (now - result.updatedAtMs > staleAfterMs) continue; // stopped updating: not active\n const sessionId = result.snapshot.sessionId as string;\n next.set(sessionId, {\n issueDir: join(filePath, '..'),\n filePath,\n snapshot: result.snapshot,\n updatedAtMs: result.updatedAtMs,\n });\n }\n sessions = next;\n }\n\n // The first scan runs immediately (not just after the first interval tick)\n // so a server started right after a session begins does not report \"no\n // sessions\" for a whole poll interval.\n void scan();\n const timer = setInterval(() => void scan(), pollIntervalMs);\n timer.unref();\n\n return {\n sessions: () => [...sessions.values()],\n getSession: (sessionId: string) => sessions.get(sessionId),\n events: async (sessionId: string) => {\n const session = sessions.get(sessionId);\n if (session === undefined) return undefined;\n const [rotated, current] = await Promise.all([\n readJournalFile(join(session.issueDir, ROTATED_EVENTS_FILENAME)),\n readJournalFile(join(session.issueDir, EVENTS_FILENAME)),\n ]);\n return [...rotated, ...current];\n },\n refresh: scan,\n close: () => clearInterval(timer),\n };\n}\n","import { loadWebConfig } from '../config.js';\nimport type { WebConfig } from '../schemas.js';\nimport { ensureSingleWebServer, stopWebMonitor } from '../web/lock.js';\nimport { watchSessionDirectory } from '../web/session-directory.js';\n\n/**\n * `issue-flow web serve` / `issue-flow web stop` (US-002).\n *\n * `serve` is the process `ensureWebMonitor()` (`web/lock.ts`) spawns detached\n * — it is not meant to be run interactively, though nothing stops a user from\n * doing so to watch every session on the machine without going through a\n * pipeline command first. It never exits on its own: the server stays bound\n * (see `unref: false` in `web/server.ts`) until `stop` sends it `SIGTERM`, at\n * which point `startWebServer`'s own signal handler closes it (removing the\n * lock) and re-raises the signal for the default termination behavior.\n */\n\nexport interface RunWebServeOptions {\n port?: number;\n host?: string;\n refresh?: number;\n}\n\n/**\n * Bind (or defer to) the single web monitor instance and keep the process\n * alive for as long as it stays bound. Logging is intentionally silent on the\n * happy path: this process is spawned with `stdio: 'ignore'`, so anything\n * printed here goes nowhere — the caller (`ensureWebMonitor`) is the one that\n * tells the user the server is up.\n */\nexport async function runWebServe(options: RunWebServeOptions): Promise<number> {\n // Built conditionally, not `{ port: options.port, ... }`: loadWebConfig()\n // spreads this object directly over the lower-precedence layers, and an\n // explicit `undefined` value would overwrite an env/.issue-flow.json\n // setting instead of falling through to it (only an *absent* key does).\n const cli: Partial<WebConfig> = {};\n if (options.port !== undefined) cli.port = options.port;\n if (options.host !== undefined) cli.host = options.host;\n if (options.refresh !== undefined) cli.refreshSeconds = options.refresh;\n const webConfig = await loadWebConfig({ cli });\n\n const sessions = watchSessionDirectory();\n\n const noop = (): void => {};\n const handle = await ensureSingleWebServer({\n sessions,\n port: webConfig.port,\n host: webConfig.host,\n refreshSeconds: webConfig.refreshSeconds,\n unref: false,\n info: noop,\n warn: noop,\n });\n\n if (handle === null) {\n sessions.close();\n return 1;\n }\n\n if (handle.server === undefined) {\n // Another instance already won the race (or was already running by the\n // time this process got here): nothing to serve, so it exits right away\n // instead of idling as a redundant detached process.\n sessions.close();\n return 0;\n }\n\n const originalClose = handle.close;\n handle.close = async () => {\n await originalClose();\n sessions.close();\n };\n\n return 0;\n}\n\n/** Stop the single running web monitor instance, if any. */\nexport async function runWebStop(): Promise<number> {\n const webConfig = await loadWebConfig();\n const result = await stopWebMonitor({ port: webConfig.port, host: webConfig.host });\n return result === 'failed' || result === 'unowned' ? 1 : 0;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/web/session-directory.ts","../src/commands/web.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type JournalEntry, parseJournal } from '../core/journal.js';\nimport type { ValidatedSessionSnapshot } from '../schemas.js';\nimport {\n EVENTS_FILENAME,\n type GetGlobalRootOptions,\n getGlobalRoot,\n ISSUES_DIR_NAME,\n PROJECTS_DIR_NAME,\n ROTATED_EVENTS_FILENAME,\n SESSION_FILENAME,\n} from '../storage/paths.js';\nimport { readSessionFile } from '../storage/session-file.js';\n\n/**\n * Multi-session discovery for the web monitoring server (US-003).\n *\n * The single-instance server (`web/lock.ts`) runs decoupled from any one\n * pipeline invocation, so it cannot hold a `SessionPublisher` in memory for\n * \"the\" run being monitored — there may be zero, one or several running at\n * once, each in its own process. Instead this module polls every\n * `~/.issue-flow/projects/<project>/issues/<n>/session.json` on disk (the\n * same file `FilePublisher` already writes) and keeps an in-memory map of the\n * ones that are both well-formed and recently updated.\n *\n * Polling rather than `fs.watch` is a deliberate choice, not a placeholder:\n * `fs.watch`'s `recursive` option is only reliable on macOS and Windows (Linux\n * needs a manual per-directory watch tree, and the whole thing still degrades\n * to polling on network filesystems), while a `~/.issue-flow` tree is small\n * and local — a cheap poll of it is simpler and behaves identically on every\n * platform, which is what the US-003 acceptance criteria ask to validate.\n */\n\n/** How often the directory tree is rescanned, unless overridden. */\nexport const DEFAULT_POLL_INTERVAL_MS = 3000;\n\n/**\n * A session stops being reported once its file has gone this long without an\n * update. `FilePublisher` touches a live session every 10s without rewriting\n * its content. Three missed heartbeats plus ample scheduling/filesystem slack\n * avoid flapping while still removing an abruptly killed run promptly.\n */\nexport const DEFAULT_STALE_AFTER_MS = 90_000;\n\nexport interface ActiveSession {\n /** Directory the session.json was read from, one level up from the file. */\n issueDir: string;\n filePath: string;\n snapshot: ValidatedSessionSnapshot;\n /** mtime of the file at the last successful read, in epoch ms. */\n updatedAtMs: number;\n}\n\nexport interface SessionDirectoryOptions extends GetGlobalRootOptions {\n /** Poll interval, in ms. Default {@link DEFAULT_POLL_INTERVAL_MS}. */\n pollIntervalMs?: number;\n /** Staleness threshold, in ms. Default {@link DEFAULT_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Invoked (at most once) when a poll fails unexpectedly. Best-effort logging only. */\n onWarn?: (message: string) => void;\n}\n\nexport interface SessionDirectoryHandle {\n /** Snapshot of every session currently considered active. */\n sessions(): ActiveSession[];\n /** A single active session by id, or undefined. */\n getSession(sessionId: string): ActiveSession | undefined;\n /** Journal entries for one active session, oldest generation first. */\n events(sessionId: string): Promise<JournalEntry[] | undefined>;\n /** Force an immediate rescan instead of waiting for the next tick. Never throws. */\n refresh(): Promise<void>;\n /** Stop polling. Idempotent. */\n close(): void;\n}\n\nasync function listSubdirectories(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n // Symlinks are skipped on purpose, same discipline as the storage\n // migration walk: following one could pull session data from outside the\n // global tree into the listing.\n return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);\n } catch {\n // Missing/unreadable projects (or issues) directory reads as \"none yet\" —\n // this is routinely the case before the first --web run ever happens.\n return [];\n }\n}\n\n/** Every `session.json` path currently on disk, one per project/issue pair. */\nasync function discoverSessionFiles(root: string): Promise<string[]> {\n const projectsDir = join(root, PROJECTS_DIR_NAME);\n const projectIds = await listSubdirectories(projectsDir);\n\n const perProject = await Promise.all(\n projectIds.map(async (projectId) => {\n const issuesDir = join(projectsDir, projectId, ISSUES_DIR_NAME);\n const issueIds = await listSubdirectories(issuesDir);\n return issueIds.map((issueId) => join(issuesDir, issueId, SESSION_FILENAME));\n }),\n );\n\n return perProject.flat();\n}\n\nasync function readJournalFile(filePath: string): Promise<JournalEntry[]> {\n try {\n return parseJournal(await readFile(filePath, 'utf-8'));\n } catch {\n return [];\n }\n}\n\n/**\n * Start polling the global storage tree for active sessions.\n *\n * Returns a handle immediately; the first scan happens synchronously in the\n * background and `sessions()` answers `[]` until it completes, same as \"no\n * sessions found yet\" — there is nothing to await here that would not also\n * delay the server's very first bind.\n */\nexport function watchSessionDirectory(\n options: SessionDirectoryOptions = {},\n): SessionDirectoryHandle {\n const root = getGlobalRoot(options);\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;\n const warn = options.onWarn;\n\n // Keyed by sessionId, not by file path: a session's identity is the id\n // inside its content, and that is what /api/status?session=<id> looks up.\n let sessions = new Map<string, ActiveSession>();\n let warned = false;\n\n async function scan(): Promise<void> {\n let files: string[];\n try {\n files = await discoverSessionFiles(root);\n } catch (err) {\n if (!warned) {\n warned = true;\n warn?.(\n `issue-flow: web monitor could not scan the session directory (will keep retrying silently): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n return;\n }\n\n const now = Date.now();\n const next = new Map<string, ActiveSession>();\n for (const filePath of files) {\n const result = await readSessionFile(filePath);\n if (result === null) continue;\n if (now - result.updatedAtMs > staleAfterMs) continue; // stopped updating: not active\n const sessionId = result.snapshot.sessionId as string;\n next.set(sessionId, {\n issueDir: join(filePath, '..'),\n filePath,\n snapshot: result.snapshot,\n updatedAtMs: result.updatedAtMs,\n });\n }\n sessions = next;\n }\n\n // The first scan runs immediately (not just after the first interval tick)\n // so a server started right after a session begins does not report \"no\n // sessions\" for a whole poll interval.\n void scan();\n const timer = setInterval(() => void scan(), pollIntervalMs);\n timer.unref();\n\n return {\n sessions: () => [...sessions.values()],\n getSession: (sessionId: string) => sessions.get(sessionId),\n events: async (sessionId: string) => {\n const session = sessions.get(sessionId);\n if (session === undefined) return undefined;\n const [rotated, current] = await Promise.all([\n readJournalFile(join(session.issueDir, ROTATED_EVENTS_FILENAME)),\n readJournalFile(join(session.issueDir, EVENTS_FILENAME)),\n ]);\n return [...rotated, ...current];\n },\n refresh: scan,\n close: () => clearInterval(timer),\n };\n}\n","import { loadWebConfig } from '../config.js';\nimport type { WebConfig } from '../schemas.js';\nimport { ensureSingleWebServer, stopWebMonitor } from '../web/lock.js';\nimport { watchSessionDirectory } from '../web/session-directory.js';\n\n/**\n * `issue-flow web serve` / `issue-flow web stop` (US-002).\n *\n * `serve` is the process `ensureWebMonitor()` (`web/lock.ts`) spawns detached\n * — it is not meant to be run interactively, though nothing stops a user from\n * doing so to watch every session on the machine without going through a\n * pipeline command first. It never exits on its own: the server stays bound\n * (see `unref: false` in `web/server.ts`) until `stop` sends it `SIGTERM`, at\n * which point `startWebServer`'s own signal handler closes it (removing the\n * lock) and re-raises the signal for the default termination behavior.\n */\n\nexport interface RunWebServeOptions {\n port?: number;\n host?: string;\n refresh?: number;\n}\n\n/**\n * Bind (or defer to) the single web monitor instance and keep the process\n * alive for as long as it stays bound. Logging is intentionally silent on the\n * happy path: this process is spawned with `stdio: 'ignore'`, so anything\n * printed here goes nowhere — the caller (`ensureWebMonitor`) is the one that\n * tells the user the server is up.\n */\nexport async function runWebServe(options: RunWebServeOptions): Promise<number> {\n // Built conditionally, not `{ port: options.port, ... }`: loadWebConfig()\n // spreads this object directly over the lower-precedence layers, and an\n // explicit `undefined` value would overwrite an env/.issue-flow.json\n // setting instead of falling through to it (only an *absent* key does).\n const cli: Partial<WebConfig> = {};\n if (options.port !== undefined) cli.port = options.port;\n if (options.host !== undefined) cli.host = options.host;\n if (options.refresh !== undefined) cli.refreshSeconds = options.refresh;\n const webConfig = await loadWebConfig({ cli });\n\n const sessions = watchSessionDirectory();\n\n const noop = (): void => {};\n const handle = await ensureSingleWebServer({\n sessions,\n port: webConfig.port,\n host: webConfig.host,\n refreshSeconds: webConfig.refreshSeconds,\n unref: false,\n info: noop,\n warn: noop,\n });\n\n if (handle === null) {\n sessions.close();\n return 1;\n }\n\n if (handle.server === undefined) {\n // Another instance already won the race (or was already running by the\n // time this process got here): nothing to serve, so it exits right away\n // instead of idling as a redundant detached process.\n sessions.close();\n return 0;\n }\n\n const originalClose = handle.close;\n handle.close = async () => {\n await originalClose();\n sessions.close();\n };\n\n return 0;\n}\n\n/** Stop the single running web monitor instance, if any. */\nexport async function runWebStop(): Promise<number> {\n const webConfig = await loadWebConfig();\n const result = await stopWebMonitor({ port: webConfig.port, host: webConfig.host });\n return result === 'failed' || result === 'unowned' ? 1 : 0;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issue-flow",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Unified CLI for orchestrating the full issue-flow pipeline via Claude Code, Codex CLI, Cursor CLI or Antigravity CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,10 +14,11 @@
14
14
  "test:watch": "vitest",
15
15
  "test:integration": "vitest run --config vitest.integration.config.ts",
16
16
  "smoke": "bash scripts/smoke-issue-providers.sh",
17
- "lint": "biome check src/",
18
- "lint:fix": "biome check --write src/",
19
- "format": "biome format --write src/",
20
- "check": "biome check --write src/ && tsc --noEmit",
17
+ "lint": "biome check .",
18
+ "lint:fix": "biome check --write .",
19
+ "format": "biome format --write .",
20
+ "check": "biome check . && tsc --noEmit",
21
+ "fix": "biome check --write . && tsc --noEmit",
21
22
  "prepack": "npm run build",
22
23
  "prepublishOnly": "npm run lint && npm run typecheck && npm test",
23
24
  "preversion": "node scripts/git-version.mjs check",
package/web/AGENTS.md CHANGED
@@ -46,7 +46,9 @@ do snapshot.
46
46
  ## Abas, Kanban e drawer
47
47
 
48
48
  O painel tem três abas ("Execução", "Kanban" e "Histórico") e um único drawer de detalhes
49
- para fases e stories (inclusive cards do Kanban). Três regras seguram esse conjunto:
49
+ para fases e stories (inclusive cards do Kanban). As abas seguem o padrão ARIA de
50
+ tablist: setas ←/→ movem o foco, Home/End vão às pontas, e só a aba ativa tem
51
+ `tabindex="0"` (as demais `-1`). Três regras seguram esse conjunto:
50
52
 
51
53
  - **Acesso a story sempre por `getStoryById()` / `getStories()`.** Elas são a
52
54
  camada de leitura: normalizam num lugar só o que pode faltar num
@@ -75,6 +77,110 @@ grid`/`flex` da regra base vence o atributo `hidden`. E o overlay/drawer ficam
75
77
  em `z-index` 20/21 para cobrir o `.banner` de desconexão, que é `sticky` com
76
78
  `z-index: 10`.
77
79
 
80
+ ## Header: informação, não marca
81
+
82
+ O `h1` das duas views **não** carrega o nome do produto — a marca vive só no
83
+ `<title>` do documento, que `renderTitle()`/`renderDashboard()` mantêm no
84
+ formato `<contexto> · issue-flow`. No detalhe o `h1` é a execução (`#N` como
85
+ link para a issue, seguido do título dela); no dashboard é "Execuções ativas".
86
+ Não devolva "issue-flow" para dentro do `h1`: a linha mais visível da tela é
87
+ para o que está acontecendo.
88
+
89
+ O resto da identidade da execução fica ao redor do `h1`: branch e chip de
90
+ versão na `.header-meta` logo abaixo, status, tempo decorrido e estimativa no
91
+ `.header-side`. O título da issue aparece **uma vez só** — por isso
92
+ `renderIssueSummary()` não repete número nem título no bloco "Contexto",
93
+ deixando ali estado, labels e descrição.
94
+
95
+ Layout: `.header-main` é `flex: 1 1 320px` e o `.header-side` fica com o
96
+ `flex` padrão (`0 1 auto`). O `.header-side` **precisa** poder encolher — os
97
+ timers são largos e, fixados em `flex: 0 0 auto`, estouram a largura em 360px.
98
+ O `h1` é fluxo inline (não flex), senão um título longo empurra o `#N` para
99
+ uma linha sozinha.
100
+
101
+ ## Blocos da aba Execução
102
+
103
+ A aba "Execução" tem **quatro** cartões, nesta ordem, e a ordem é a hierarquia:
104
+
105
+ | Bloco | O que carrega |
106
+ | --- | --- |
107
+ | Estado agora | progresso, "Executando agora", "Resiliência" e a linha "Próximos passos" |
108
+ | Contexto | issue, repositório e "Harnesses e configuração efetiva" |
109
+ | Andamento | fases e user stories |
110
+ | Saída | commits, pull requests e logs recentes |
111
+
112
+ Cada assunto dentro de um bloco é uma `.block-part` com `<h3>` — **sem borda,
113
+ sem fundo, sem sombra própria**: quem separa é o `gap` do grid de `.block`.
114
+ Assunto novo entra como `.block-part` de um bloco existente; um cartão novo na
115
+ aba só se justifica se não couber em nenhum dos quatro. Foi a proliferação de
116
+ cartões de mesmo peso (doze deles) que a issue #98 desfez, e ela volta sozinha
117
+ se cada mudança acrescentar "só mais um".
118
+
119
+ "Estado agora" é o único bloco com um requisito de layout: precisa caber sem
120
+ rolagem em 1440x900 **com o cartão de erros e avisos aberto**. Antes de
121
+ acrescentar linha ali, meça (`getBoundingClientRect().bottom <= innerHeight`).
122
+
123
+ A partir de 960px o `#panel-execution` vira um grid de duas colunas: Contexto
124
+ fica ao lado de Estado agora / Andamento, e Saída ocupa a largura toda. Os
125
+ dois breakpoints do painel são 640px (estreito) e 960px (largo) — um
126
+ componente novo se encaixa nesses, não inventa o terceiro. `main` tem
127
+ `max-width: 1200px`. Sem rolagem horizontal em 360, 768 e 1440.
128
+
129
+ "Contexto" roda um degrau abaixo (`--font-size-md` em todo o bloco): é
130
+ referência, não estado. "Próximos passos" é uma linha só — `renderNextSteps()`
131
+ junta os passos com `·` num `<span>`, e o rótulo vem do HTML
132
+ (`.next-steps-label`), não do JS.
133
+
134
+ ## Glossário
135
+
136
+ Termos da interface — um por conceito, em todo `index.html` e `app.js` visível
137
+ ao usuário. Comentários de código podem falar a língua do domínio (`session`,
138
+ `story`); a tela não.
139
+
140
+ | Conceito | Termo na UI | Não usar |
141
+ | --- | --- | --- |
142
+ | Uma corrida do pipeline | **execução** / **execuções** | sessão (exceto no identificador técnico, e mesmo aí o rótulo é "execução `<id>`") |
143
+ | Item do plano | **user story** / **user stories** | story, stories, User Story misturado |
144
+ | Estado da corrida | **aguardando / executando / concluído / falhou** | sinônimos soltos no badge |
145
+ | Indicador de corrida ativa | **ao vivo** + `.live` | "live", segundo badge, ponto com uppercase |
146
+
147
+ Travessão (`—`) fica só em placeholders de valor ausente (`#—`, timers). Em
148
+ frase, use ponto ou vírgula: "Desconectado do servidor. Tentando reconectar…",
149
+ "Execução falhou. Veja os erros acima."
150
+
151
+ O título do drawer é o da user story (`US-00N · título`) ou `Fase · <nome>`,
152
+ nunca "Detalhes da user story".
153
+
154
+ ## Escalas de tipografia, espaçamento e raio
155
+
156
+ Ao lado das cores, `:root` declara três escalas **fechadas**. Um componente
157
+ novo escolhe um degrau que já existe; não introduz um valor local.
158
+
159
+ | Escala | Tokens |
160
+ | --- | --- |
161
+ | Tipografia | `--font-size-xs` 0.75rem · `sm` 0.8125 · `md` 0.875 · `base` 0.9375 · `lg` 1 · `xl` 1.25 |
162
+ | Espaçamento | `--space-4` · `--space-8` · `--space-12` · `--space-16` · `--space-24` |
163
+ | Raio | `--radius-small` 6px · `--radius-medium` 10px · `--radius-pill` 999px |
164
+
165
+ `--font-size-base` é o tamanho do `body`; `xl` é o `h1` e `lg` o `h2`. O
166
+ espaçamento cobre `gap`, `padding` e `margin` — os quatro valores de rem que
167
+ existiam (`0.65rem`, `0.75rem`, `0.8rem`, `1rem`) foram arredondados para o
168
+ degrau mais próximo, e é isso que se faz com qualquer valor novo. Raio:
169
+ `medium` para superfícies com cara de cartão (`.card`, `.kanban-card`,
170
+ `.kanban-column`, `.execution-entry`), `small` para linhas, controles e caixas
171
+ internas, `pill` para badges, trilhas de progresso e pontos — inclusive no
172
+ lugar do antigo `border-radius: 50%`.
173
+
174
+ **Três exceções, e só elas**, cada uma com comentário no `app.css`: o
175
+ `margin-bottom: -1px` das abas (compensa a borda, é alinhamento), o `gap: 1px`
176
+ de `.config-phase-grid` (o fundo `--border` vazando pelo gap é a linha
177
+ divisória) e o `calc(var(--space-12) - 3px)` de `.story-executing` (desconta a
178
+ `box-shadow` interna para preservar o ritmo). Um valor solto sem um motivo
179
+ dessa ordem é dívida — troque pelo degrau.
180
+
181
+ Múltiplos são escritos como `calc()` sobre um token (`calc(var(--space-24) *
182
+ 2)` no rodapé do `main`), não como um sexto token de espaçamento.
183
+
78
184
  ## Paleta e tema
79
185
 
80
186
  As cores do `app.css` são **tokens nomeados por papel**, não por local de uso:
@@ -169,7 +275,7 @@ correspondente** — a maior parte da paleta clara passa com pouca folga.
169
275
  | `--accent-text` | `--state-error` | 4,5:1 | 6,47 | 6,78 |
170
276
 
171
277
  O limiar dos badges de estado é **4,5:1 e não 3:1** porque `.badge` é
172
- `font-size: 0.78rem; font-weight: 600` — abaixo do que a WCAG chama de texto
278
+ `font-size: var(--font-size-sm); font-weight: 600` — abaixo do que a WCAG chama de texto
173
279
  grande. Já `--focus-ring` é um componente gráfico, não texto: 3:1 basta.
174
280
 
175
281
  No tema claro as quatro cores de estado ficam no nível 700 da escala — é o tom
@@ -178,11 +284,13 @@ mais claro que ainda atende 4,5:1 sobre a superfície do próprio badge; `--stat
178
284
  preenchimentos sólidos são claros, então `--accent-text` inverte para
179
285
  `#0f1218`: era branco sobre `--state-error` no banner de desconexão, 2,98:1.
180
286
 
181
- Hover e foco por teclado precisam ser **distinguíveis um do outro**. Em
182
- `.dashboard-card` o hover só acende a borda (`--accent`); o foco desenha
183
- `outline: 2px solid var(--focus-ring)` com `outline-offset: 2px`, que continua
184
- visível em cima do hover. É a única regra do `app.css` que mexe em `outline` —
185
- antes ela o suprimia e dava a mesma aparência aos dois estados.
287
+ Hover e foco por teclado precisam ser **distinguíveis um do outro**. Uma
288
+ única regra `:focus-visible` compartilhada desenha `outline: 2px solid
289
+ var(--focus-ring)` com `outline-offset: 2px` em todo interativo (abas, cards
290
+ do dashboard e do Kanban, `<select>`s, fechar do drawer, "Todas as execuções",
291
+ linhas de fase/story). O hover muda cor, borda ou fundo — nunca o anel.
292
+ Inclusive em `.dashboard-card.is-live`, que já tem `border-color` própria: o
293
+ foco precisa do outline, não de outra troca de borda.
186
294
 
187
295
  ## Como verificar uma mudança aqui
188
296
 
@@ -217,17 +325,16 @@ o token que um tema herda do outro por engano.
217
325
 
218
326
  ## Escrita limitada a preferências futuras
219
327
 
220
- O estado de execução continua somente leitura (`snapshot.readOnly === true`). A
221
- única mutação é `POST /api/config/agent`, que salva preferência global para
222
- execuções **futuras**, aparece via capability e só funciona em loopback. Nunca
223
- inferir permissão pela versão: o client renderiza o formulário apenas quando
224
- `/api/health.capabilities` contém `config:agent:write`.
225
-
226
- Quando essa etapa chegar, o contrato esperado é: as rotas de escrita passam a
227
- ser anunciadas em `capabilities` (o client decide o que renderizar a partir
228
- disso, nunca da versão do servidor), `readOnly` passa a `false`, e cada
229
- mutação responde com o snapshot atualizado para que a UI não precise adivinhar
230
- o efeito nem esperar o próximo poll. Enquanto `capabilities` estiver vazio,
328
+ O estado de execução continua somente leitura (`snapshot.readOnly === true`). As
329
+ únicas mutações são `POST /api/config/agent` e `POST /api/config/routing`, que
330
+ salvam preferências globais para execuções **futuras**, aparecem via capability
331
+ e só funcionam em loopback. Nunca inferir permissão pela versão: o client
332
+ renderiza os formulários apenas quando `/api/health.capabilities` anuncia as
333
+ duas capacidades correspondentes.
334
+
335
+ Cada mutação responde com `{ ok, file, appliesTo: 'future executions' }`; o
336
+ client atualiza a preferência viva no próximo `GET /api/config`, sem jamais
337
+ alterar o snapshot da execução. Enquanto as capabilities estiverem ausentes,
231
338
  nenhum controle de escrita deve aparecer na tela.
232
339
 
233
340
  ## Métricas (tokens e custo)