issue-flow 0.11.1 → 0.13.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.md +79 -274
- package/dist/agent-BXRIEKVM.js +238 -0
- package/dist/agent-BXRIEKVM.js.map +1 -0
- package/dist/{analyze-XV3K25K3.js → analyze-WS47UYGN.js} +32 -15
- package/dist/analyze-WS47UYGN.js.map +1 -0
- package/dist/{apply-WUJHDD7A.js → apply-OS62UWRN.js} +15 -5
- package/dist/{apply-WUJHDD7A.js.map → apply-OS62UWRN.js.map} +1 -1
- package/dist/bench-SA3BOME5.js +906 -0
- package/dist/bench-SA3BOME5.js.map +1 -0
- package/dist/chunk-37N2RJW2.js +120 -0
- package/dist/chunk-37N2RJW2.js.map +1 -0
- package/dist/chunk-3XLCNVMD.js +180 -0
- package/dist/chunk-3XLCNVMD.js.map +1 -0
- package/dist/chunk-5TB6KLC2.js +72 -0
- package/dist/chunk-5TB6KLC2.js.map +1 -0
- package/dist/{chunk-OBLVLIUS.js → chunk-664Y3NDX.js} +25 -13
- package/dist/chunk-664Y3NDX.js.map +1 -0
- package/dist/chunk-6FDFRNXZ.js +357 -0
- package/dist/chunk-6FDFRNXZ.js.map +1 -0
- package/dist/chunk-7KD66LIM.js +658 -0
- package/dist/chunk-7KD66LIM.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/chunk-ARFVHC2F.js +1111 -0
- package/dist/chunk-ARFVHC2F.js.map +1 -0
- package/dist/{chunk-RKTEV6EG.js → chunk-BHJCGWQX.js} +141 -12
- package/dist/chunk-BHJCGWQX.js.map +1 -0
- package/dist/chunk-BQDQTIM7.js +74 -0
- package/dist/chunk-BQDQTIM7.js.map +1 -0
- package/dist/chunk-CNBYCMSU.js +2709 -0
- package/dist/chunk-CNBYCMSU.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/chunk-DIG5XKQQ.js +298 -0
- package/dist/chunk-DIG5XKQQ.js.map +1 -0
- package/dist/chunk-FZDR7CGH.js +71 -0
- package/dist/chunk-FZDR7CGH.js.map +1 -0
- package/dist/chunk-HEF2RMKN.js +413 -0
- package/dist/chunk-HEF2RMKN.js.map +1 -0
- package/dist/{chunk-IVHZEYAX.js → chunk-HJGGNLWI.js} +25 -19
- package/dist/chunk-HJGGNLWI.js.map +1 -0
- package/dist/chunk-HQAHNIAR.js +283 -0
- package/dist/chunk-HQAHNIAR.js.map +1 -0
- package/dist/{chunk-GANSDJV7.js → chunk-IHIEXUTC.js} +46 -24
- package/dist/chunk-IHIEXUTC.js.map +1 -0
- package/dist/chunk-IV5SS4JN.js +84 -0
- package/dist/chunk-IV5SS4JN.js.map +1 -0
- package/dist/{chunk-HC2MT7BX.js → chunk-IYV6KNVF.js} +158 -592
- package/dist/chunk-IYV6KNVF.js.map +1 -0
- package/dist/{chunk-LVKQ2AKT.js → chunk-J62QSVP6.js} +10 -8
- package/dist/chunk-J62QSVP6.js.map +1 -0
- package/dist/chunk-KMMASOA6.js +134 -0
- package/dist/chunk-KMMASOA6.js.map +1 -0
- package/dist/chunk-KQHB7ZT3.js +559 -0
- package/dist/chunk-KQHB7ZT3.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/{chunk-6N7LZZ5U.js → chunk-LZ6GFQAG.js} +474 -160
- package/dist/chunk-LZ6GFQAG.js.map +1 -0
- package/dist/{chunk-HQYXVIH6.js → chunk-MIVHK5YI.js} +5 -5
- package/dist/chunk-NEZZ6KDR.js +1217 -0
- package/dist/chunk-NEZZ6KDR.js.map +1 -0
- package/dist/{run-DBF2VWUN.js → chunk-OPNGPUKJ.js} +747 -594
- package/dist/chunk-OPNGPUKJ.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-BXCPXRBR.js → chunk-PIVGJM6K.js} +129 -32
- package/dist/chunk-PIVGJM6K.js.map +1 -0
- package/dist/{chunk-AAY6KM7N.js → chunk-PV4OX5TC.js} +167 -196
- package/dist/chunk-PV4OX5TC.js.map +1 -0
- package/dist/chunk-QCXPNSEL.js +141 -0
- package/dist/chunk-QCXPNSEL.js.map +1 -0
- package/dist/{chunk-34WNTK3K.js → chunk-QXDNBYQF.js} +53 -13
- package/dist/chunk-QXDNBYQF.js.map +1 -0
- package/dist/chunk-SNTTOQAD.js +30 -0
- package/dist/chunk-SNTTOQAD.js.map +1 -0
- package/dist/{chunk-EY6WMHV2.js → chunk-TM67EHSV.js} +67 -23
- package/dist/chunk-TM67EHSV.js.map +1 -0
- package/dist/chunk-TVN2JVOF.js +77 -0
- package/dist/chunk-TVN2JVOF.js.map +1 -0
- package/dist/chunk-TX5WIVYP.js +129 -0
- package/dist/chunk-TX5WIVYP.js.map +1 -0
- package/dist/{chunk-KQ4JQN7T.js → chunk-UJ7XWK7J.js} +83 -21
- package/dist/chunk-UJ7XWK7J.js.map +1 -0
- package/dist/{chunk-47VIN33V.js → chunk-UOCPC2Y2.js} +596 -17
- package/dist/chunk-UOCPC2Y2.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +390 -40
- package/dist/cli.js.map +1 -1
- package/dist/config-MEM2IWWN.js +76 -0
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-WV4NDXGH.js +143 -0
- package/dist/conventions-WV4NDXGH.js.map +1 -0
- package/dist/execute-NITIHHCK.js +31 -0
- package/dist/{generate-TBVTLMOP.js → generate-MVQI7A45.js} +39 -19
- package/dist/{generate-TBVTLMOP.js.map → generate-MVQI7A45.js.map} +1 -1
- package/dist/git-YRVRUN3C.js +31 -0
- package/dist/init-OKWEC3EV.js +21 -0
- package/dist/operations-LU6JA3BX.js +374 -0
- package/dist/operations-LU6JA3BX.js.map +1 -0
- package/dist/permissions-7NMDFU27.js +18 -0
- package/dist/plan-LEJJF3DE.js +31 -0
- package/dist/{policy-XQW5NUIE.js → policy-JSYEK5B2.js} +17 -4
- package/dist/policy-JSYEK5B2.js.map +1 -0
- package/dist/pr-HPVUZ7S3.js +36 -0
- package/dist/pr-review-R2QZBT3F.js +29 -0
- package/dist/pr-review-R2QZBT3F.js.map +1 -0
- package/dist/prd-5O6GYCC6.js +30 -0
- package/dist/prd-5O6GYCC6.js.map +1 -0
- package/dist/ps-TNE44R5V.js +90 -0
- package/dist/ps-TNE44R5V.js.map +1 -0
- package/dist/recorder-XGF55WXI.js +31 -0
- package/dist/recorder-XGF55WXI.js.map +1 -0
- package/dist/registry-LGPE53QD.js +15 -0
- package/dist/registry-LGPE53QD.js.map +1 -0
- package/dist/resume-QMEPMSZW.js +238 -0
- package/dist/resume-QMEPMSZW.js.map +1 -0
- package/dist/review-YKWD6SNN.js +32 -0
- package/dist/review-YKWD6SNN.js.map +1 -0
- package/dist/routing-2ZUMZPJB.js +91 -0
- package/dist/routing-2ZUMZPJB.js.map +1 -0
- package/dist/run-YU4TR62R.js +50 -0
- package/dist/run-YU4TR62R.js.map +1 -0
- package/dist/state-manager-X3PT3FGT.js +36 -0
- package/dist/state-manager-X3PT3FGT.js.map +1 -0
- package/dist/usage-VCFLDA5Y.js +174 -0
- package/dist/usage-VCFLDA5Y.js.map +1 -0
- package/dist/{web-ANQQTTRD.js → web-XVHLX62B.js} +43 -27
- package/dist/web-XVHLX62B.js.map +1 -0
- package/package.json +6 -2
- package/prompts/execute.md +7 -8
- package/prompts/plan.md +4 -5
- package/prompts/pr.md +13 -6
- package/web/AGENTS.md +3 -3
- package/web/public/app.css +38 -0
- package/web/public/app.js +132 -10
- package/web/public/index.html +24 -0
- package/dist/analyze-XV3K25K3.js.map +0 -1
- package/dist/chunk-34WNTK3K.js.map +0 -1
- package/dist/chunk-3BOHTH4V.js +0 -58
- package/dist/chunk-3BOHTH4V.js.map +0 -1
- package/dist/chunk-47VIN33V.js.map +0 -1
- package/dist/chunk-6N7LZZ5U.js.map +0 -1
- package/dist/chunk-AAY6KM7N.js.map +0 -1
- package/dist/chunk-BXCPXRBR.js.map +0 -1
- package/dist/chunk-EWDJENFW.js +0 -303
- package/dist/chunk-EWDJENFW.js.map +0 -1
- package/dist/chunk-EY6WMHV2.js.map +0 -1
- package/dist/chunk-GANSDJV7.js.map +0 -1
- package/dist/chunk-HC2MT7BX.js.map +0 -1
- package/dist/chunk-HDV3YDTG.js +0 -52
- package/dist/chunk-HDV3YDTG.js.map +0 -1
- package/dist/chunk-IVHZEYAX.js.map +0 -1
- package/dist/chunk-KQ4JQN7T.js.map +0 -1
- package/dist/chunk-LVKQ2AKT.js.map +0 -1
- package/dist/chunk-OBLVLIUS.js.map +0 -1
- package/dist/chunk-RKTEV6EG.js.map +0 -1
- package/dist/chunk-VQ5VN7DF.js +0 -1018
- package/dist/chunk-VQ5VN7DF.js.map +0 -1
- package/dist/config-4THHOXNN.js +0 -41
- package/dist/execute-5OEKLM5X.js +0 -15
- package/dist/init-PRMOTQ7N.js +0 -9
- package/dist/plan-Y2BYZD24.js +0 -17
- package/dist/policy-XQW5NUIE.js.map +0 -1
- package/dist/pr-H6GLOSXF.js +0 -21
- package/dist/pr-review-CK5FR6WX.js +0 -17
- package/dist/prd-4PWLCGH5.js +0 -17
- package/dist/review-QGVAHEZF.js +0 -15
- package/dist/run-DBF2VWUN.js.map +0 -1
- package/dist/web-ANQQTTRD.js.map +0 -1
- /package/dist/{chunk-HQYXVIH6.js.map → chunk-MIVHK5YI.js.map} +0 -0
- /package/dist/{config-4THHOXNN.js.map → config-MEM2IWWN.js.map} +0 -0
- /package/dist/{execute-5OEKLM5X.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{init-PRMOTQ7N.js.map → execute-NITIHHCK.js.map} +0 -0
- /package/dist/{plan-Y2BYZD24.js.map → git-YRVRUN3C.js.map} +0 -0
- /package/dist/{pr-H6GLOSXF.js.map → init-OKWEC3EV.js.map} +0 -0
- /package/dist/{pr-review-CK5FR6WX.js.map → permissions-7NMDFU27.js.map} +0 -0
- /package/dist/{prd-4PWLCGH5.js.map → plan-LEJJF3DE.js.map} +0 -0
- /package/dist/{review-QGVAHEZF.js.map → pr-HPVUZ7S3.js.map} +0 -0
package/prompts/pr.md
CHANGED
|
@@ -14,13 +14,20 @@ __ISSUE_BODY__
|
|
|
14
14
|
</issue-body>
|
|
15
15
|
|
|
16
16
|
Steps:
|
|
17
|
-
1.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
1. Check whether this branch already has an open Pull Request:
|
|
18
|
+
`gh pr list --head __BRANCH_NAME__ --state open --json number,url`.
|
|
19
|
+
If it does, do NOT create a second one — report the existing URL and stop.
|
|
20
|
+
The pipeline checks this before invoking you and normally skips this phase
|
|
21
|
+
entirely, so reaching this point with a Pull Request already open means the
|
|
22
|
+
check could not run: it is the last guard against a duplicate, not a
|
|
23
|
+
formality.
|
|
24
|
+
2. Read the task plan from __TASKS_PATH__ if it exists
|
|
25
|
+
3. Review the git log for this branch: git log __BASE_BRANCH__..HEAD --oneline
|
|
26
|
+
4. Review the diff: git diff __BASE_BRANCH__...HEAD --stat
|
|
27
|
+
5. Create a well-structured PR using gh pr create
|
|
21
28
|
|
|
22
29
|
The PR should:
|
|
23
|
-
-
|
|
30
|
+
- Use this title verbatim: `__PR_TITLE_CONVENTION__`
|
|
24
31
|
- Include a summary of changes
|
|
25
32
|
- Include a test plan
|
|
26
33
|
- Cite where the demand is described: the reference above (`__ISSUE_URL__`)
|
|
@@ -29,7 +36,7 @@ Issue reference line(s):
|
|
|
29
36
|
- Include the lines below verbatim in the PR body, each entry on its own line,
|
|
30
37
|
with no surrounding code fence or quoting — GitHub only auto-closes an issue
|
|
31
38
|
when the `Closes #N` line is plain body text:
|
|
32
|
-
|
|
39
|
+
__ISSUE_REFERENCE__
|
|
33
40
|
- When nothing follows the line above, the issue has no GitHub counterpart: do
|
|
34
41
|
NOT invent a "Closes #" reference, and cite `__ISSUE_URL__` in the body instead
|
|
35
42
|
|
package/web/AGENTS.md
CHANGED
|
@@ -10,7 +10,7 @@ vitest. Toda verificação de mudança aqui é manual, no navegador.
|
|
|
10
10
|
|
|
11
11
|
O painel consome `GET api/sessions` (lista enriquecida para o dashboard),
|
|
12
12
|
`GET api/status` (o `SessionSnapshot` serializado, opcionalmente com
|
|
13
|
-
`?session=<id>`) e `GET api/health`. Ele precisa renderizar **session.json de
|
|
13
|
+
`?session=<id>`), `GET api/events?session=<id>` (journal) e `GET api/health`. Ele precisa renderizar **session.json de
|
|
14
14
|
execuções antigas**, então todo campo pode chegar como `undefined` (não
|
|
15
15
|
existia na versão que gravou o arquivo) além do `null` (existe, não
|
|
16
16
|
informado). Os dois significam "não informado" e nunca podem virar `0`,
|
|
@@ -36,7 +36,7 @@ do snapshot.
|
|
|
36
36
|
|
|
37
37
|
## Abas, Kanban e drawer
|
|
38
38
|
|
|
39
|
-
O painel tem
|
|
39
|
+
O painel tem três abas ("Execução", "Kanban" e "Histórico") e um drawer de detalhes por
|
|
40
40
|
story. Três regras seguram esse conjunto:
|
|
41
41
|
|
|
42
42
|
- **Acesso a story sempre por `getStoryById()` / `getStories()`.** Elas são a
|
|
@@ -51,7 +51,7 @@ story. Três regras seguram esse conjunto:
|
|
|
51
51
|
então uma referência guardada na abertura apontaria para um nó fora do
|
|
52
52
|
documento. Pelo mesmo motivo o foco volta ao card via
|
|
53
53
|
`[data-story-id="…"]` no momento de fechar.
|
|
54
|
-
- **`renderKanban()` e `renderDrawer()` são chamadas incondicionalmente** dentro
|
|
54
|
+
- **`renderKanban()`, `renderHistory()` e `renderDrawer()` são chamadas incondicionalmente** dentro
|
|
55
55
|
de `render()`, não ao trocar de aba. Uma aba inativa não pode ficar defasada,
|
|
56
56
|
e é o `renderDrawer()` de cada poll que mantém o drawer aberto em dia (e o
|
|
57
57
|
fecha quando a story some do plano).
|
package/web/public/app.css
CHANGED
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
box-sizing: border-box;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
[hidden] {
|
|
51
|
+
display: none !important;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
body {
|
|
51
55
|
margin: 0;
|
|
52
56
|
background: var(--bg);
|
|
@@ -454,6 +458,34 @@ progress {
|
|
|
454
458
|
border-bottom: 1px solid var(--border);
|
|
455
459
|
}
|
|
456
460
|
|
|
461
|
+
.plain-list .history-entry {
|
|
462
|
+
align-items: flex-start;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.history-type {
|
|
466
|
+
flex: 0 0 9rem;
|
|
467
|
+
color: var(--muted);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.history-message {
|
|
471
|
+
min-width: 0;
|
|
472
|
+
overflow-wrap: anywhere;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@media (max-width: 560px) {
|
|
476
|
+
.plain-list .history-entry {
|
|
477
|
+
flex-wrap: wrap;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.history-type {
|
|
481
|
+
flex-basis: auto;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.history-message {
|
|
485
|
+
flex-basis: 100%;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
457
489
|
.plain-list li:last-child {
|
|
458
490
|
border-bottom: none;
|
|
459
491
|
}
|
|
@@ -904,6 +936,12 @@ progress {
|
|
|
904
936
|
font: inherit;
|
|
905
937
|
cursor: pointer;
|
|
906
938
|
display: grid;
|
|
939
|
+
/* O grid externo estica os cards de uma linha para a mesma altura. Sem isto,
|
|
940
|
+
`align-content: stretch` distribui a sobra entre as faixas — inclusive a do
|
|
941
|
+
resumo — e a faixa fica mais alta que as 3 linhas do clamp, deixando vazar
|
|
942
|
+
uma 4ª linha cortada por baixo do reticente. Empacotar no topo manda a
|
|
943
|
+
sobra para o fim do card, que é onde ela não engana ninguém. */
|
|
944
|
+
align-content: start;
|
|
907
945
|
gap: 10px;
|
|
908
946
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
909
947
|
}
|
package/web/public/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Interface do monitoramento web do issue-flow.
|
|
2
2
|
// JS puro, sem framework e sem recursos externos — funciona offline.
|
|
3
|
-
// Consome GET api/sessions, GET api/status (ETag/304) e GET api/health;
|
|
3
|
+
// Consome GET api/sessions, GET api/status (ETag/304), GET api/events e GET api/health;
|
|
4
4
|
// todo texto dinâmico entra via textContent (nunca innerHTML com dados
|
|
5
5
|
// do snapshot).
|
|
6
6
|
(() => {
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
progressPercent: document.getElementById('progress-percent'),
|
|
80
80
|
progressCounters: document.getElementById('progress-counters'),
|
|
81
81
|
now: document.getElementById('now'),
|
|
82
|
+
resilience: document.getElementById('resilience'),
|
|
82
83
|
phases: document.getElementById('phases'),
|
|
83
84
|
nextSteps: document.getElementById('next-steps'),
|
|
84
85
|
stories: document.getElementById('stories'),
|
|
@@ -86,6 +87,8 @@
|
|
|
86
87
|
pullRequests: document.getElementById('pull-requests'),
|
|
87
88
|
logFilter: document.getElementById('log-filter'),
|
|
88
89
|
logs: document.getElementById('logs'),
|
|
90
|
+
historyFilter: document.getElementById('history-filter'),
|
|
91
|
+
history: document.getElementById('history'),
|
|
89
92
|
sessionMeta: document.getElementById('session-meta'),
|
|
90
93
|
tabs: Array.prototype.slice.call(document.querySelectorAll('[role="tab"]')),
|
|
91
94
|
kanban: document.getElementById('kanban'),
|
|
@@ -105,6 +108,8 @@
|
|
|
105
108
|
polling: false,
|
|
106
109
|
logFilter: 'all',
|
|
107
110
|
activeTab: 'tab-execution',
|
|
111
|
+
historyFilter: 'all',
|
|
112
|
+
events: [],
|
|
108
113
|
// Só o id: o card que abriu o drawer é destruído no próximo render, então
|
|
109
114
|
// guardar o nó levaria a uma referência morta.
|
|
110
115
|
selectedStoryId: null,
|
|
@@ -116,6 +121,7 @@
|
|
|
116
121
|
detailSessionId: null,
|
|
117
122
|
viewMode: 'detail', // 'detail' | 'dashboard'
|
|
118
123
|
statusUrl: 'api/status',
|
|
124
|
+
eventsUrl: null,
|
|
119
125
|
// Se selectSession/clearSessionSelection chega durante um poll, reexecuta.
|
|
120
126
|
pollAgain: false,
|
|
121
127
|
};
|
|
@@ -372,9 +378,6 @@
|
|
|
372
378
|
if (!stillThere) {
|
|
373
379
|
state.selectedSessionId = null;
|
|
374
380
|
state.etag = null;
|
|
375
|
-
} else if (sessions.length === 1) {
|
|
376
|
-
// Seleção explícita deixa de fazer sentido com uma única sessão.
|
|
377
|
-
state.selectedSessionId = null;
|
|
378
381
|
}
|
|
379
382
|
}
|
|
380
383
|
|
|
@@ -387,10 +390,6 @@
|
|
|
387
390
|
return { mode: 'detail', session: null };
|
|
388
391
|
}
|
|
389
392
|
|
|
390
|
-
if (sessions.length === 1) {
|
|
391
|
-
return { mode: 'detail', session: sessions[0] };
|
|
392
|
-
}
|
|
393
|
-
|
|
394
393
|
return { mode: 'dashboard', session: null };
|
|
395
394
|
}
|
|
396
395
|
|
|
@@ -398,9 +397,9 @@
|
|
|
398
397
|
state.viewMode = mode;
|
|
399
398
|
els.viewDashboard.hidden = mode !== 'dashboard';
|
|
400
399
|
els.viewDetail.hidden = mode !== 'detail';
|
|
401
|
-
// Back
|
|
400
|
+
// Back quando o usuário escolheu um card e existe uma lista para voltar.
|
|
402
401
|
const showBack =
|
|
403
|
-
mode === 'detail' && state.selectedSessionId !== null && state.sessions.length >=
|
|
402
|
+
mode === 'detail' && state.selectedSessionId !== null && state.sessions.length >= 1;
|
|
404
403
|
els.backToDashboard.hidden = !showBack;
|
|
405
404
|
if (mode === 'dashboard' && state.selectedStoryId !== null) {
|
|
406
405
|
closeDrawer();
|
|
@@ -414,10 +413,17 @@
|
|
|
414
413
|
return session.statusUrl.replace(/^\//, '');
|
|
415
414
|
}
|
|
416
415
|
|
|
416
|
+
function eventsUrlFor(session) {
|
|
417
|
+
if (!session || !session.eventsUrl) return null;
|
|
418
|
+
return session.eventsUrl.replace(/^\//, '');
|
|
419
|
+
}
|
|
420
|
+
|
|
417
421
|
function clearDetailState() {
|
|
418
422
|
state.etag = null;
|
|
419
423
|
state.snapshot = null;
|
|
420
424
|
state.detailSessionId = null;
|
|
425
|
+
state.events = [];
|
|
426
|
+
state.eventsUrl = null;
|
|
421
427
|
}
|
|
422
428
|
|
|
423
429
|
function requestPoll() {
|
|
@@ -491,6 +497,7 @@
|
|
|
491
497
|
state.statusUrl = nextUrl;
|
|
492
498
|
state.etag = null;
|
|
493
499
|
}
|
|
500
|
+
state.eventsUrl = eventsUrlFor(resolved.session);
|
|
494
501
|
|
|
495
502
|
// Evita flash da sessão anterior enquanto o status novo carrega.
|
|
496
503
|
if (sessionChanged || !state.snapshot) {
|
|
@@ -516,6 +523,13 @@
|
|
|
516
523
|
state.snapshot = await res.json();
|
|
517
524
|
render();
|
|
518
525
|
}
|
|
526
|
+
if (state.eventsUrl) {
|
|
527
|
+
const eventsRes = await fetch(state.eventsUrl, { cache: 'no-store' });
|
|
528
|
+
if (!eventsRes.ok) throw new Error('HTTP ' + eventsRes.status);
|
|
529
|
+
const entries = await eventsRes.json();
|
|
530
|
+
state.events = Array.isArray(entries) ? entries : [];
|
|
531
|
+
renderHistory();
|
|
532
|
+
}
|
|
519
533
|
setViewMode('detail');
|
|
520
534
|
state.failures = 0;
|
|
521
535
|
els.banner.hidden = true;
|
|
@@ -614,6 +628,24 @@
|
|
|
614
628
|
? formatAgo(session.startedAt)
|
|
615
629
|
: '—';
|
|
616
630
|
meta.appendChild(el('span', null, elapsed));
|
|
631
|
+
// Resilience: how hard this run has had to work, and when it last moved.
|
|
632
|
+
// A card that only shows a percentage cannot tell a run that is
|
|
633
|
+
// progressing from one that has been retrying for twenty minutes.
|
|
634
|
+
if (typeof session.retries === 'number' && session.retries > 0) {
|
|
635
|
+
meta.appendChild(el('span', null, session.retries + ' retry(s)'));
|
|
636
|
+
}
|
|
637
|
+
if (typeof session.correctionCycle === 'number' && session.correctionCycle > 0) {
|
|
638
|
+
meta.appendChild(el('span', null, 'correção ' + session.correctionCycle));
|
|
639
|
+
}
|
|
640
|
+
if (session.updatedAt) {
|
|
641
|
+
meta.appendChild(
|
|
642
|
+
el('span', null, 'atividade ' + formatAgo(session.lastActivityAt || session.updatedAt)),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (session.provider) meta.appendChild(el('span', null, 'provider ' + session.provider));
|
|
646
|
+
if (typeof session.attempt === 'number' && session.attempt > 0) {
|
|
647
|
+
meta.appendChild(el('span', null, 'tentativa ' + session.attempt));
|
|
648
|
+
}
|
|
617
649
|
card.appendChild(meta);
|
|
618
650
|
|
|
619
651
|
const progress = el('span', 'dashboard-progress');
|
|
@@ -656,6 +688,7 @@
|
|
|
656
688
|
renderRepository(snapshot);
|
|
657
689
|
renderProgress(snapshot);
|
|
658
690
|
renderNow(snapshot);
|
|
691
|
+
renderResilience(snapshot);
|
|
659
692
|
renderPhases(snapshot);
|
|
660
693
|
renderNextSteps(snapshot);
|
|
661
694
|
renderStories(snapshot);
|
|
@@ -664,6 +697,7 @@
|
|
|
664
697
|
renderDrawer(snapshot);
|
|
665
698
|
renderGit(snapshot);
|
|
666
699
|
renderLogs(snapshot);
|
|
700
|
+
renderHistory();
|
|
667
701
|
renderMeta(snapshot);
|
|
668
702
|
renderTimers();
|
|
669
703
|
renderTitle(snapshot);
|
|
@@ -845,6 +879,26 @@
|
|
|
845
879
|
els.now.appendChild(grid);
|
|
846
880
|
}
|
|
847
881
|
|
|
882
|
+
function renderResilience(snapshot) {
|
|
883
|
+
clear(els.resilience);
|
|
884
|
+
const resilience = snapshot.resilience || {};
|
|
885
|
+
const grid = el('dl', 'now-grid');
|
|
886
|
+
nowRow(grid, 'Tentativa', resilience.attempt > 0 ? String(resilience.attempt) : '—');
|
|
887
|
+
nowRow(grid, 'Provider', resilience.provider || '—');
|
|
888
|
+
nowRow(grid, 'Modelo', resilience.model || '—');
|
|
889
|
+
nowRow(grid, 'Última falha', resilience.lastFailureKind || '—');
|
|
890
|
+
nowRow(
|
|
891
|
+
grid,
|
|
892
|
+
'Cooldown',
|
|
893
|
+
resilience.cooldownUntil ? formatClock(resilience.cooldownUntil) : '—',
|
|
894
|
+
);
|
|
895
|
+
const activity = resilience.lastActivityAt
|
|
896
|
+
? formatClock(resilience.lastActivityAt) + ' (' + formatAgo(resilience.lastActivityAt) + ')'
|
|
897
|
+
: '—';
|
|
898
|
+
nowRow(grid, 'Última atividade', activity);
|
|
899
|
+
els.resilience.appendChild(grid);
|
|
900
|
+
}
|
|
901
|
+
|
|
848
902
|
function renderPhases(snapshot) {
|
|
849
903
|
clear(els.phases);
|
|
850
904
|
if (snapshot.phases.length === 0) {
|
|
@@ -1135,6 +1189,70 @@
|
|
|
1135
1189
|
}
|
|
1136
1190
|
}
|
|
1137
1191
|
|
|
1192
|
+
const RESILIENCE_EVENTS = new Set([
|
|
1193
|
+
'retry',
|
|
1194
|
+
'agent:attempt',
|
|
1195
|
+
'agent:activity',
|
|
1196
|
+
'agent:result',
|
|
1197
|
+
'failover',
|
|
1198
|
+
]);
|
|
1199
|
+
|
|
1200
|
+
function historyMessage(event) {
|
|
1201
|
+
switch (event.type) {
|
|
1202
|
+
case 'session:start':
|
|
1203
|
+
return 'Sessão iniciada';
|
|
1204
|
+
case 'session:end':
|
|
1205
|
+
return 'Sessão encerrada: ' + event.status;
|
|
1206
|
+
case 'phase:start':
|
|
1207
|
+
return 'Fase iniciada: ' + event.phase;
|
|
1208
|
+
case 'phase:end':
|
|
1209
|
+
return 'Fase encerrada: ' + event.phase + (event.success ? ' (ok)' : ' (falhou)');
|
|
1210
|
+
case 'iteration:start':
|
|
1211
|
+
return 'Iteração ' + event.iteration + ' iniciada';
|
|
1212
|
+
case 'iteration:end':
|
|
1213
|
+
return 'Iteração ' + event.iteration + ' encerrada';
|
|
1214
|
+
case 'retry':
|
|
1215
|
+
return 'Retry ' + event.attempt + (event.kind ? ': ' + event.kind : '');
|
|
1216
|
+
case 'agent:attempt':
|
|
1217
|
+
return 'Tentativa ' + event.attempt + ' com ' + event.provider;
|
|
1218
|
+
case 'agent:activity':
|
|
1219
|
+
return 'Atividade recebida de ' + event.provider;
|
|
1220
|
+
case 'agent:result':
|
|
1221
|
+
return event.provider + (event.success ? ' concluiu a tentativa' : ' falhou: ' + (event.failureKind || 'desconhecida'));
|
|
1222
|
+
case 'failover':
|
|
1223
|
+
return 'Failover de ' + event.from + ' para ' + event.to + (event.reason ? ': ' + event.reason : '');
|
|
1224
|
+
case 'correction:cycle':
|
|
1225
|
+
return 'Ciclo de correção ' + event.cycle + '/' + event.maxCycles;
|
|
1226
|
+
case 'log':
|
|
1227
|
+
return event.message;
|
|
1228
|
+
default:
|
|
1229
|
+
return event.type;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function renderHistory() {
|
|
1234
|
+
clear(els.history);
|
|
1235
|
+
const entries = state.events.filter((entry) => {
|
|
1236
|
+
const type = entry && entry.event && entry.event.type;
|
|
1237
|
+
if (!type) return false;
|
|
1238
|
+
if (state.historyFilter === 'resilience') return RESILIENCE_EVENTS.has(type);
|
|
1239
|
+
if (state.historyFilter === 'pipeline') return !RESILIENCE_EVENTS.has(type);
|
|
1240
|
+
return true;
|
|
1241
|
+
});
|
|
1242
|
+
if (entries.length === 0) {
|
|
1243
|
+
els.history.appendChild(el('p', 'empty', 'Nenhum evento para exibir.'));
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
for (const entry of entries.slice().reverse()) {
|
|
1247
|
+
const event = entry.event;
|
|
1248
|
+
const item = el('li', 'history-entry');
|
|
1249
|
+
item.appendChild(el('span', 'log-time', formatClock(event.at)));
|
|
1250
|
+
item.appendChild(el('span', 'history-type mono', event.type));
|
|
1251
|
+
item.appendChild(el('span', 'history-message', historyMessage(event)));
|
|
1252
|
+
els.history.appendChild(item);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1138
1256
|
function renderMeta(snapshot) {
|
|
1139
1257
|
const parts = [];
|
|
1140
1258
|
if (snapshot.sessionId) parts.push('sessão ' + snapshot.sessionId);
|
|
@@ -1201,6 +1319,10 @@
|
|
|
1201
1319
|
state.logFilter = els.logFilter.value;
|
|
1202
1320
|
if (state.snapshot) renderLogs(state.snapshot);
|
|
1203
1321
|
});
|
|
1322
|
+
els.historyFilter.addEventListener('change', () => {
|
|
1323
|
+
state.historyFilter = els.historyFilter.value;
|
|
1324
|
+
renderHistory();
|
|
1325
|
+
});
|
|
1204
1326
|
|
|
1205
1327
|
document.addEventListener('visibilitychange', () => {
|
|
1206
1328
|
if (document.hidden) clearTimer();
|
package/web/public/index.html
CHANGED
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
aria-controls="panel-execution" aria-selected="true">Execução</button>
|
|
70
70
|
<button type="button" class="tab" role="tab" id="tab-kanban"
|
|
71
71
|
aria-controls="panel-kanban" aria-selected="false">Kanban</button>
|
|
72
|
+
<button type="button" class="tab" role="tab" id="tab-history"
|
|
73
|
+
aria-controls="panel-history" aria-selected="false">Histórico</button>
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
76
|
<div class="tab-panel" id="panel-execution" role="tabpanel" aria-labelledby="tab-execution" tabindex="0">
|
|
@@ -97,6 +99,11 @@
|
|
|
97
99
|
<div id="now"></div>
|
|
98
100
|
</section>
|
|
99
101
|
|
|
102
|
+
<section class="card" aria-live="polite">
|
|
103
|
+
<h2>Resiliência</h2>
|
|
104
|
+
<div id="resilience"></div>
|
|
105
|
+
</section>
|
|
106
|
+
|
|
100
107
|
<div class="columns">
|
|
101
108
|
<section class="card">
|
|
102
109
|
<h2>Fases</h2>
|
|
@@ -148,6 +155,23 @@
|
|
|
148
155
|
</section>
|
|
149
156
|
</div>
|
|
150
157
|
|
|
158
|
+
<div class="tab-panel" id="panel-history" role="tabpanel" aria-labelledby="tab-history" tabindex="0" hidden>
|
|
159
|
+
<section class="card">
|
|
160
|
+
<div class="section-head">
|
|
161
|
+
<h2>Histórico da execução</h2>
|
|
162
|
+
<label class="log-filter-control">
|
|
163
|
+
Evento
|
|
164
|
+
<select id="history-filter" aria-label="Filtro do histórico">
|
|
165
|
+
<option value="all">todos</option>
|
|
166
|
+
<option value="resilience">resiliência</option>
|
|
167
|
+
<option value="pipeline">pipeline</option>
|
|
168
|
+
</select>
|
|
169
|
+
</label>
|
|
170
|
+
</div>
|
|
171
|
+
<ol id="history" class="plain-list history"></ol>
|
|
172
|
+
</section>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
151
175
|
<footer class="muted footer">
|
|
152
176
|
<span id="session-meta"></span>
|
|
153
177
|
</footer>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const analysisPath = paths.analysisFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(paths.issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n // Last: the Issue content is substituted in but never scanned again, so a\n // body that happens to contain a placeholder is left untouched.\n ...issuePlaceholders(resolution.resolved),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n addDirs: [paths.issueDir],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('analyze', result.cost, startedAtMs);\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = paths.tasksFile;\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAY3C,eAAsB,WAAW,OAAe,eAAgD;AAC9F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,eAAe,MAAM;AAE3B,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE/C,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA;AAAA;AAAA,IAGnB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,SAAS,CAAC,MAAM,QAAQ;AAAA,IACxB,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAED,sBAAoB,WAAW,OAAO,MAAM,WAAW;AAEvD,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,MAAM;AACxB,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/review.ts"],"sourcesContent":["import { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\n\nexport interface ReviewResult {\n status: 'PASS' | 'FAIL';\n findings?: string;\n}\n\n/**\n * Parse the <review-result> block from headless output.\n */\nfunction parseReviewResult(output: string): ReviewResult {\n const match = output.match(/<review-result>([\\s\\S]*?)<\\/review-result>/);\n if (!match) {\n // Try to detect PASS/FAIL from the raw output\n if (/STATUS:\\s*PASS/i.test(output)) {\n return { status: 'PASS' };\n }\n if (/STATUS:\\s*FAIL/i.test(output)) {\n const findingsMatch = output.match(/FINDINGS:([\\s\\S]*?)(?:$|<\\/)/);\n return { status: 'FAIL', findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings' };\n }\n // Default to PASS if no explicit status found and no errors\n return { status: 'PASS' };\n }\n\n const block = match[1];\n if (/STATUS:\\s*PASS/i.test(block)) {\n return { status: 'PASS' };\n }\n\n const findingsMatch = block.match(/FINDINGS:([\\s\\S]*)/);\n return {\n status: 'FAIL',\n findings: findingsMatch?.[1]?.trim() ?? 'Unknown findings',\n };\n}\n\nexport async function runReview(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const tasksPath = paths.tasksFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n const template = await loadPrompt('review');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __TASKS_PATH__: tasksPath,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseReviewResult() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [paths.issueDir],\n statusMessage: `Reviewing issue #${issueNumber} resolution...`,\n });\n // Before the success check: the tokens were spent either way. A correction\n // cycle calls runReview() again and the reducer sums both invocations.\n publishPhaseMetrics('review', result.cost, startedAtMs);\n\n if (!result.success) {\n printError(`Review failed: ${result.error}`);\n return 1;\n }\n\n const review = parseReviewResult(result.result);\n\n if (review.status === 'PASS') {\n // Update pipeline state\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = true;\n plan.lastReviewFindings = null;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n printSuccess('Review PASSED — implementation meets acceptance criteria');\n return 0;\n }\n\n printError('Review FAILED — issues found:');\n if (review.findings) {\n console.log(review.findings);\n }\n\n // Persist the findings so a correction cycle can act on them: without this,\n // a re-run of execute sees every userStories[].passes already true and\n // exits immediately without attempting any fix (see engine.ts's\n // \"already complete\" guards).\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.reviewCompleted = false;\n plan.lastReviewFindings = review.findings ?? 'Unknown findings';\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist\n }\n\n return 1;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,SAAS,kBAAkB,QAA8B;AACvD,QAAM,QAAQ,OAAO,MAAM,4CAA4C;AACvE,MAAI,CAAC,OAAO;AAEV,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,aAAO,EAAE,QAAQ,OAAO;AAAA,IAC1B;AACA,QAAI,kBAAkB,KAAK,MAAM,GAAG;AAClC,YAAMA,iBAAgB,OAAO,MAAM,8BAA8B;AACjE,aAAO,EAAE,QAAQ,QAAQ,UAAUA,iBAAgB,CAAC,GAAG,KAAK,KAAK,mBAAmB;AAAA,IACtF;AAEA,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,kBAAkB,KAAK,KAAK,GAAG;AACjC,WAAO,EAAE,QAAQ,OAAO;AAAA,EAC1B;AAEA,QAAM,gBAAgB,MAAM,MAAM,oBAAoB;AACtD,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU,gBAAgB,CAAC,GAAG,KAAK,KAAK;AAAA,EAC1C;AACF;AAEA,eAAsB,UAAU,OAAe,eAAgD;AAC7F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,YAAY,MAAM;AAExB,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,WAAW,MAAM,WAAW,QAAQ;AAC1C,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,kBAAkB;AAAA,IAClB,gBAAgB;AAAA,IAChB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IAC7C,SAAS,CAAC,MAAM,QAAQ;AAAA,IACxB,eAAe,oBAAoB,WAAW;AAAA,EAChD,CAAC;AAGD,sBAAoB,UAAU,OAAO,MAAM,WAAW;AAEtD,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,kBAAkB,OAAO,KAAK,EAAE;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,kBAAkB,OAAO,MAAM;AAE9C,MAAI,OAAO,WAAW,QAAQ;AAE5B,QAAI;AACF,YAAM,OAAO,MAAM,aAAa,SAAS;AACzC,WAAK,SAAS,kBAAkB;AAChC,WAAK,qBAAqB;AAC1B,YAAM,aAAa,WAAW,IAAI;AAAA,IACpC,QAAQ;AAAA,IAER;AAEA,iBAAa,+DAA0D;AACvE,WAAO;AAAA,EACT;AAEA,aAAW,oCAA+B;AAC1C,MAAI,OAAO,UAAU;AACnB,YAAQ,IAAI,OAAO,QAAQ;AAAA,EAC7B;AAMA,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,kBAAkB;AAChC,SAAK,qBAAqB,OAAO,YAAY;AAC7C,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;","names":["findingsMatch"]}
|
package/dist/chunk-3BOHTH4V.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
retryDelaySeconds,
|
|
4
|
-
sleep
|
|
5
|
-
} from "./chunk-HDV3YDTG.js";
|
|
6
|
-
import {
|
|
7
|
-
getSessionPublisher,
|
|
8
|
-
isoNow,
|
|
9
|
-
printRetry
|
|
10
|
-
} from "./chunk-HC2MT7BX.js";
|
|
11
|
-
|
|
12
|
-
// src/core/phase-runner.ts
|
|
13
|
-
import { readFile } from "fs/promises";
|
|
14
|
-
var DEFAULT_PHASE_RETRY_LIMIT = 3;
|
|
15
|
-
var DEFAULT_BACKOFF_BASE_SECONDS = 15;
|
|
16
|
-
var DEFAULT_BACKOFF_MAX_SECONDS = 120;
|
|
17
|
-
async function runPhaseWithRetry(options) {
|
|
18
|
-
const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;
|
|
19
|
-
const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;
|
|
20
|
-
const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;
|
|
21
|
-
let lastResult = { ok: false, error: "Phase never attempted" };
|
|
22
|
-
let retryCount = 0;
|
|
23
|
-
for (let attemptNumber = 1; attemptNumber <= retryLimit; attemptNumber++) {
|
|
24
|
-
lastResult = await options.attempt(attemptNumber);
|
|
25
|
-
if (lastResult.ok) return lastResult;
|
|
26
|
-
if (!lastResult.transient || attemptNumber === retryLimit) return lastResult;
|
|
27
|
-
retryCount++;
|
|
28
|
-
const delaySeconds = retryDelaySeconds(retryCount, backoffBaseSeconds, backoffMaxSeconds);
|
|
29
|
-
getSessionPublisher().publish({
|
|
30
|
-
type: "retry",
|
|
31
|
-
at: isoNow(),
|
|
32
|
-
attempt: retryCount,
|
|
33
|
-
delaySeconds,
|
|
34
|
-
reason: lastResult.error
|
|
35
|
-
});
|
|
36
|
-
printRetry(
|
|
37
|
-
`Phase ${options.phase} hit a recoverable failure (attempt ${attemptNumber}/${retryLimit}): ${lastResult.error}. Retrying in ${delaySeconds}s.`
|
|
38
|
-
);
|
|
39
|
-
await sleep(delaySeconds);
|
|
40
|
-
}
|
|
41
|
-
return lastResult;
|
|
42
|
-
}
|
|
43
|
-
async function readFileWithGrace(path, delaysMs = [300, 800]) {
|
|
44
|
-
for (let i = 0; ; i++) {
|
|
45
|
-
try {
|
|
46
|
-
return await readFile(path, "utf-8");
|
|
47
|
-
} catch (err) {
|
|
48
|
-
if (i >= delaysMs.length) throw err;
|
|
49
|
-
await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export {
|
|
55
|
-
runPhaseWithRetry,
|
|
56
|
-
readFileWithGrace
|
|
57
|
-
};
|
|
58
|
-
//# sourceMappingURL=chunk-3BOHTH4V.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/phase-runner.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { printRetry } from '../ui/logger.js';\nimport { retryDelaySeconds, sleep } from '../utils/retry.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { isoNow } from './state-manager.js';\n\nexport interface PhaseAttemptResult {\n ok: boolean;\n /** Whether a failed attempt is worth retrying. Ignored when ok is true. */\n transient?: boolean;\n error?: string;\n}\n\nexport interface PhaseRetryOptions {\n phase: string;\n attempt: (attemptNumber: number) => Promise<PhaseAttemptResult>;\n retryLimit?: number;\n backoffBaseSeconds?: number;\n backoffMaxSeconds?: number;\n}\n\nconst DEFAULT_PHASE_RETRY_LIMIT = 3;\nconst DEFAULT_BACKOFF_BASE_SECONDS = 15;\nconst DEFAULT_BACKOFF_MAX_SECONDS = 120;\n\n/**\n * Bounded retry wrapper for single-shot phases (prd, plan, pr) that invoke\n * the Claude CLI once and validate an artifact. Mirrors the transient-retry\n * pattern already used by the execute phase's iteration loop (core/engine.ts:\n * isTransientFailure + retryDelaySeconds), applied here to phases that\n * previously aborted the whole pipeline on the first failure with no chance\n * to recover. Non-transient failures (or exhausting the retry budget) still\n * end the phase — this bounds recovery attempts, it never retries forever.\n */\nexport async function runPhaseWithRetry(options: PhaseRetryOptions): Promise<PhaseAttemptResult> {\n const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;\n const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;\n const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;\n\n let lastResult: PhaseAttemptResult = { ok: false, error: 'Phase never attempted' };\n let retryCount = 0;\n\n for (let attemptNumber = 1; attemptNumber <= retryLimit; attemptNumber++) {\n lastResult = await options.attempt(attemptNumber);\n if (lastResult.ok) return lastResult;\n if (!lastResult.transient || attemptNumber === retryLimit) return lastResult;\n\n retryCount++;\n const delaySeconds = retryDelaySeconds(retryCount, backoffBaseSeconds, backoffMaxSeconds);\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt: retryCount,\n delaySeconds,\n reason: lastResult.error,\n });\n printRetry(\n `Phase ${options.phase} hit a recoverable failure (attempt ${attemptNumber}/${retryLimit}): ${lastResult.error}. Retrying in ${delaySeconds}s.`,\n );\n await sleep(delaySeconds);\n }\n\n return lastResult;\n}\n\n/**\n * Re-read a file a few times with short delays before giving up. Absorbs a\n * brief filesystem-visibility lag between the Claude CLI's Write tool\n * completing and this process observing the result, without spending a full\n * phase retry (and a new Claude invocation) on what may just be a timing gap.\n */\nexport async function readFileWithGrace(\n path: string,\n delaysMs: number[] = [300, 800],\n): Promise<string> {\n for (let i = 0; ; i++) {\n try {\n return await readFile(path, 'utf-8');\n } catch (err) {\n if (i >= delaysMs.length) throw err;\n await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,gBAAgB;AAqBzB,IAAM,4BAA4B;AAClC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AAWpC,eAAsB,kBAAkB,SAAyD;AAC/F,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,MAAI,aAAiC,EAAE,IAAI,OAAO,OAAO,wBAAwB;AACjF,MAAI,aAAa;AAEjB,WAAS,gBAAgB,GAAG,iBAAiB,YAAY,iBAAiB;AACxE,iBAAa,MAAM,QAAQ,QAAQ,aAAa;AAChD,QAAI,WAAW,GAAI,QAAO;AAC1B,QAAI,CAAC,WAAW,aAAa,kBAAkB,WAAY,QAAO;AAElE;AACA,UAAM,eAAe,kBAAkB,YAAY,oBAAoB,iBAAiB;AACxF,wBAAoB,EAAE,QAAQ;AAAA,MAC5B,MAAM;AAAA,MACN,IAAI,OAAO;AAAA,MACX,SAAS;AAAA,MACT;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB,CAAC;AACD;AAAA,MACE,SAAS,QAAQ,KAAK,uCAAuC,aAAa,IAAI,UAAU,MAAM,WAAW,KAAK,iBAAiB,YAAY;AAAA,IAC7I;AACA,UAAM,MAAM,YAAY;AAAA,EAC1B;AAEA,SAAO;AACT;AAQA,eAAsB,kBACpB,MACA,WAAqB,CAAC,KAAK,GAAG,GACb;AACjB,WAAS,IAAI,KAAK,KAAK;AACrB,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,SAAS,KAAK;AACZ,UAAI,KAAK,SAAS,OAAQ,OAAM;AAChC,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,SAAS,CAAC,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AACF;","names":[]}
|