issue-flow 0.7.6 → 0.8.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/dist/{analyze-TNFTUMOO.js → analyze-RBNKXWL5.js} +10 -8
- package/dist/{analyze-TNFTUMOO.js.map → analyze-RBNKXWL5.js.map} +1 -1
- package/dist/{chunk-JECNPBBO.js → chunk-36ECROTC.js} +11 -9
- package/dist/{chunk-JECNPBBO.js.map → chunk-36ECROTC.js.map} +1 -1
- package/dist/{chunk-6S2XT3JE.js → chunk-A4M6AKIK.js} +10 -8
- package/dist/{chunk-6S2XT3JE.js.map → chunk-A4M6AKIK.js.map} +1 -1
- package/dist/{chunk-5NLLEWJD.js → chunk-BASRRXAM.js} +12 -2
- package/dist/{chunk-5NLLEWJD.js.map → chunk-BASRRXAM.js.map} +1 -1
- package/dist/{chunk-5Q2TMW7K.js → chunk-BH7DYADV.js} +11 -1
- package/dist/chunk-BH7DYADV.js.map +1 -0
- package/dist/chunk-F52S6ZGF.js +447 -0
- package/dist/chunk-F52S6ZGF.js.map +1 -0
- package/dist/{chunk-CD7BMWXW.js → chunk-HSGTZ3CT.js} +3 -3
- package/dist/{chunk-QLVAEDVD.js → chunk-J3PJBZXJ.js} +3 -3
- package/dist/{chunk-DWF46N65.js → chunk-J4SICTAN.js} +11 -9
- package/dist/{chunk-DWF46N65.js.map → chunk-J4SICTAN.js.map} +1 -1
- package/dist/{chunk-DF7SUHT5.js → chunk-JWZKV3CL.js} +2 -2
- package/dist/{chunk-5IRBHUYO.js → chunk-MK43N4X3.js} +11 -9
- package/dist/{chunk-5IRBHUYO.js.map → chunk-MK43N4X3.js.map} +1 -1
- package/dist/{chunk-I7FTA5FI.js → chunk-QO2MWMAZ.js} +2 -47
- package/dist/chunk-QO2MWMAZ.js.map +1 -0
- package/dist/{chunk-D56VWHSM.js → chunk-RQW5W4XC.js} +2 -2
- package/dist/chunk-SLHZPB3C.js +50 -0
- package/dist/chunk-SLHZPB3C.js.map +1 -0
- package/dist/{chunk-BMRBQ23Q.js → chunk-TGP6WK65.js} +9 -7
- package/dist/{chunk-BMRBQ23Q.js.map → chunk-TGP6WK65.js.map} +1 -1
- package/dist/{chunk-OE7RYTUT.js → chunk-VPDWTZ3F.js} +2 -2
- package/dist/{chunk-A42R36BX.js → chunk-VSF2CR3G.js} +10 -8
- package/dist/{chunk-A42R36BX.js.map → chunk-VSF2CR3G.js.map} +1 -1
- package/dist/cli.js +28 -13
- package/dist/cli.js.map +1 -1
- package/dist/{config-U3FC4WH6.js → config-R7O5YC65.js} +3 -3
- package/dist/execute-LU3HWA54.js +14 -0
- package/dist/{generate-RHPOWGDX.js → generate-TGJQLGMV.js} +10 -8
- package/dist/{generate-RHPOWGDX.js.map → generate-TGJQLGMV.js.map} +1 -1
- package/dist/init-WP7Z2LFQ.js +9 -0
- package/dist/plan-2MWMCKRY.js +16 -0
- package/dist/pr-XVQDEQ7B.js +16 -0
- package/dist/pr-review-VO6ZXAFM.js +16 -0
- package/dist/prd-FPZAHQEO.js +16 -0
- package/dist/review-AHALLMXO.js +14 -0
- package/dist/{run-Z6ZDXZB6.js → run-2CXVR3VZ.js} +23 -196
- package/dist/run-2CXVR3VZ.js.map +1 -0
- package/dist/web-JVPK2PIF.js +188 -0
- package/dist/web-JVPK2PIF.js.map +1 -0
- package/package.json +1 -1
- package/web/CLAUDE.md +48 -0
- package/web/public/app.css +266 -0
- package/web/public/app.js +237 -0
- package/web/public/index.html +26 -0
- package/dist/chunk-5Q2TMW7K.js.map +0 -1
- package/dist/chunk-I7FTA5FI.js.map +0 -1
- package/dist/execute-BNBA77WJ.js +0 -13
- package/dist/init-IF72GR7O.js +0 -9
- package/dist/plan-FITZZXZQ.js +0 -15
- package/dist/pr-GO244D7M.js +0 -15
- package/dist/pr-review-XY6VL62M.js +0 -15
- package/dist/prd-2Y6RMVTF.js +0 -15
- package/dist/review-ITKXJMX3.js +0 -13
- package/dist/run-Z6ZDXZB6.js.map +0 -1
- /package/dist/{chunk-CD7BMWXW.js.map → chunk-HSGTZ3CT.js.map} +0 -0
- /package/dist/{chunk-QLVAEDVD.js.map → chunk-J3PJBZXJ.js.map} +0 -0
- /package/dist/{chunk-DF7SUHT5.js.map → chunk-JWZKV3CL.js.map} +0 -0
- /package/dist/{chunk-D56VWHSM.js.map → chunk-RQW5W4XC.js.map} +0 -0
- /package/dist/{chunk-OE7RYTUT.js.map → chunk-VPDWTZ3F.js.map} +0 -0
- /package/dist/{config-U3FC4WH6.js.map → config-R7O5YC65.js.map} +0 -0
- /package/dist/{execute-BNBA77WJ.js.map → execute-LU3HWA54.js.map} +0 -0
- /package/dist/{init-IF72GR7O.js.map → init-WP7Z2LFQ.js.map} +0 -0
- /package/dist/{plan-FITZZXZQ.js.map → plan-2MWMCKRY.js.map} +0 -0
- /package/dist/{pr-GO244D7M.js.map → pr-XVQDEQ7B.js.map} +0 -0
- /package/dist/{pr-review-XY6VL62M.js.map → pr-review-VO6ZXAFM.js.map} +0 -0
- /package/dist/{prd-2Y6RMVTF.js.map → prd-FPZAHQEO.js.map} +0 -0
- /package/dist/{review-ITKXJMX3.js.map → review-AHALLMXO.js.map} +0 -0
package/web/public/app.js
CHANGED
|
@@ -32,6 +32,15 @@
|
|
|
32
32
|
done: 'concluída',
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
// Colunas do Kanban, na ordem em que a execução avança. Os títulos são os das
|
|
36
|
+
// colunas, não os rótulos dos badges (STORY_STATUS_LABELS), que seguem em minúsculas.
|
|
37
|
+
const KANBAN_COLUMNS = [
|
|
38
|
+
{ status: 'backlog', title: 'Backlog' },
|
|
39
|
+
{ status: 'in_progress', title: 'Em andamento' },
|
|
40
|
+
{ status: 'in_review', title: 'Em revisão' },
|
|
41
|
+
{ status: 'done', title: 'Concluído' },
|
|
42
|
+
];
|
|
43
|
+
|
|
35
44
|
const els = {
|
|
36
45
|
banner: document.getElementById('banner-disconnected'),
|
|
37
46
|
issueLink: document.getElementById('issue-link'),
|
|
@@ -56,6 +65,13 @@
|
|
|
56
65
|
logFilter: document.getElementById('log-filter'),
|
|
57
66
|
logs: document.getElementById('logs'),
|
|
58
67
|
sessionMeta: document.getElementById('session-meta'),
|
|
68
|
+
tabs: Array.prototype.slice.call(document.querySelectorAll('[role="tab"]')),
|
|
69
|
+
kanban: document.getElementById('kanban'),
|
|
70
|
+
drawer: document.getElementById('drawer'),
|
|
71
|
+
drawerOverlay: document.getElementById('drawer-overlay'),
|
|
72
|
+
drawerClose: document.getElementById('drawer-close'),
|
|
73
|
+
drawerTitle: document.getElementById('drawer-title'),
|
|
74
|
+
drawerBody: document.getElementById('drawer-body'),
|
|
59
75
|
};
|
|
60
76
|
|
|
61
77
|
const state = {
|
|
@@ -66,6 +82,10 @@
|
|
|
66
82
|
timer: null,
|
|
67
83
|
polling: false,
|
|
68
84
|
logFilter: 'all',
|
|
85
|
+
activeTab: 'tab-execution',
|
|
86
|
+
// Só o id: o card que abriu o drawer é destruído no próximo render, então
|
|
87
|
+
// guardar o nó levaria a uma referência morta.
|
|
88
|
+
selectedStoryId: null,
|
|
69
89
|
};
|
|
70
90
|
|
|
71
91
|
// ---- Utilitários ----------------------------------------------------------
|
|
@@ -189,6 +209,61 @@
|
|
|
189
209
|
return match ? match[1] : null;
|
|
190
210
|
}
|
|
191
211
|
|
|
212
|
+
// ---- Camada de leitura das stories ----------------------------------------
|
|
213
|
+
// Único ponto de acesso a uma story individual: o Kanban e o drawer nunca
|
|
214
|
+
// varrem snapshot.stories por conta própria. Quando a escrita existir, é aqui
|
|
215
|
+
// que a leitura passa a conversar com ela, sem tocar na UI.
|
|
216
|
+
|
|
217
|
+
function text(value) {
|
|
218
|
+
return typeof value === 'string' ? value : '';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function list(value) {
|
|
222
|
+
return Array.isArray(value) ? value : [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Normaliza os campos que um session.json anterior pode não ter gravado, para
|
|
226
|
+
// que nenhum consumidor precise repetir a checagem de ausência.
|
|
227
|
+
function normalizeStory(story) {
|
|
228
|
+
const status = story.status !== null && story.status !== undefined ? story.status : 'backlog';
|
|
229
|
+
return {
|
|
230
|
+
...story,
|
|
231
|
+
status: STORY_STATUS_LABELS[status] !== undefined ? status : 'backlog',
|
|
232
|
+
dependencies: list(story.dependencies),
|
|
233
|
+
description: text(story.description),
|
|
234
|
+
acceptanceCriteria: list(story.acceptanceCriteria),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function getStoryById(snapshot, id) {
|
|
239
|
+
if (!snapshot) return null;
|
|
240
|
+
const stories = list(snapshot.stories);
|
|
241
|
+
for (const story of stories) {
|
|
242
|
+
if (story.id === id) return normalizeStory(story);
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getStories(snapshot) {
|
|
248
|
+
if (!snapshot) return [];
|
|
249
|
+
return list(snapshot.stories).map(normalizeStory);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---- Abas -----------------------------------------------------------------
|
|
253
|
+
// Troca apenas visibilidade e estado ARIA. Não toca no polling: render()
|
|
254
|
+
// segue atualizando os dois painéis, então a aba inativa nunca fica defasada.
|
|
255
|
+
|
|
256
|
+
function setActiveTab(tabId) {
|
|
257
|
+
state.activeTab = tabId;
|
|
258
|
+
for (const tab of els.tabs) {
|
|
259
|
+
const active = tab.id === tabId;
|
|
260
|
+
tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
261
|
+
tab.classList.toggle('is-active', active);
|
|
262
|
+
const panel = document.getElementById(tab.getAttribute('aria-controls'));
|
|
263
|
+
if (panel) panel.hidden = !active;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
192
267
|
// ---- Polling: intervalo configurável, aba oculta e backoff ----------------
|
|
193
268
|
|
|
194
269
|
function readStoredRefresh() {
|
|
@@ -282,6 +357,9 @@
|
|
|
282
357
|
renderPhases(snapshot);
|
|
283
358
|
renderNextSteps(snapshot);
|
|
284
359
|
renderStories(snapshot);
|
|
360
|
+
// Incondicional: a aba inativa não pode ficar defasada até ser aberta.
|
|
361
|
+
renderKanban(snapshot);
|
|
362
|
+
renderDrawer(snapshot);
|
|
285
363
|
renderGit(snapshot);
|
|
286
364
|
renderLogs(snapshot);
|
|
287
365
|
renderMeta(snapshot);
|
|
@@ -544,6 +622,157 @@
|
|
|
544
622
|
}
|
|
545
623
|
}
|
|
546
624
|
|
|
625
|
+
// As stories chegam já normalizadas por getStories(), então aqui nenhum campo
|
|
626
|
+
// precisa de checagem de ausência.
|
|
627
|
+
// <button> em vez de <div role="button">: acionamento por Enter/Espaço e foco
|
|
628
|
+
// saem de graça. Por isso todo o conteúdo é <span> — <p>/<div> não são
|
|
629
|
+
// conteúdo válido dentro de um botão.
|
|
630
|
+
function storyCard(story) {
|
|
631
|
+
const card = el('button', 'kanban-card');
|
|
632
|
+
card.type = 'button';
|
|
633
|
+
card.dataset.storyId = story.id;
|
|
634
|
+
|
|
635
|
+
const head = el('span', 'kanban-card-head');
|
|
636
|
+
head.appendChild(
|
|
637
|
+
el('span', 'item-icon icon-' + (story.passes ? 'completed' : 'pending'), story.passes ? '✓' : '○'),
|
|
638
|
+
);
|
|
639
|
+
head.appendChild(el('span', 'story-id', story.id));
|
|
640
|
+
card.appendChild(head);
|
|
641
|
+
|
|
642
|
+
card.appendChild(el('span', 'kanban-card-title', story.title));
|
|
643
|
+
if (story.description) card.appendChild(el('span', 'kanban-card-desc', story.description));
|
|
644
|
+
card.appendChild(
|
|
645
|
+
el('span', 'badge story-status-' + story.status, STORY_STATUS_LABELS[story.status]),
|
|
646
|
+
);
|
|
647
|
+
card.addEventListener('click', () => openDrawer(story.id));
|
|
648
|
+
return card;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function renderKanban(snapshot) {
|
|
652
|
+
clear(els.kanban);
|
|
653
|
+
const stories = getStories(snapshot);
|
|
654
|
+
for (const column of KANBAN_COLUMNS) {
|
|
655
|
+
const entries = stories.filter((story) => story.status === column.status);
|
|
656
|
+
|
|
657
|
+
const node = el('section', 'kanban-column');
|
|
658
|
+
const head = el('div', 'kanban-column-head');
|
|
659
|
+
head.appendChild(el('h3', 'kanban-column-title', column.title));
|
|
660
|
+
head.appendChild(el('span', 'kanban-column-count', String(entries.length)));
|
|
661
|
+
node.appendChild(head);
|
|
662
|
+
|
|
663
|
+
if (entries.length === 0) {
|
|
664
|
+
node.appendChild(el('p', 'empty kanban-empty', 'Nenhuma story.'));
|
|
665
|
+
} else {
|
|
666
|
+
for (const story of entries) node.appendChild(storyCard(story));
|
|
667
|
+
}
|
|
668
|
+
els.kanban.appendChild(node);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// ---- Drawer de detalhes da story ------------------------------------------
|
|
673
|
+
|
|
674
|
+
function onDrawerKeydown(event) {
|
|
675
|
+
if (event.key === 'Escape') closeDrawer();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Reidrata a partir do id a cada render: o drawer aberto sobrevive ao poll.
|
|
679
|
+
function renderDrawer(snapshot) {
|
|
680
|
+
if (state.selectedStoryId === null) return;
|
|
681
|
+
const story = getStoryById(snapshot, state.selectedStoryId);
|
|
682
|
+
// A story saiu do plano: manter o drawer aberto exibiria dados obsoletos.
|
|
683
|
+
if (story === null) {
|
|
684
|
+
closeDrawer();
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
els.drawerTitle.textContent = story.id + ' · ' + story.title;
|
|
688
|
+
|
|
689
|
+
clear(els.drawerBody);
|
|
690
|
+
els.drawerBody.appendChild(
|
|
691
|
+
el('span', 'badge story-status-' + story.status, STORY_STATUS_LABELS[story.status]),
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
const timing = itemSideText([
|
|
695
|
+
story.completedAt ? 'concluída ' + formatClock(story.completedAt) : '',
|
|
696
|
+
metric(story.durationSeconds) !== null ? formatDuration(story.durationSeconds) : '',
|
|
697
|
+
]);
|
|
698
|
+
if (timing) els.drawerBody.appendChild(el('p', 'muted drawer-timing', timing));
|
|
699
|
+
|
|
700
|
+
drawerSection('Descrição', (body) => {
|
|
701
|
+
body.appendChild(
|
|
702
|
+
story.description
|
|
703
|
+
? el('p', 'drawer-text', story.description)
|
|
704
|
+
: el('p', 'empty', 'Sem descrição.'),
|
|
705
|
+
);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
drawerSection('Critérios de aceite', (body) => {
|
|
709
|
+
if (story.acceptanceCriteria.length === 0) {
|
|
710
|
+
body.appendChild(el('p', 'empty', 'Nenhum critério declarado.'));
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const items = el('ul', 'drawer-list');
|
|
714
|
+
for (const criterion of story.acceptanceCriteria) items.appendChild(el('li', null, criterion));
|
|
715
|
+
body.appendChild(items);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
drawerSection('Dependências', (body) => {
|
|
719
|
+
if (story.dependencies.length === 0) {
|
|
720
|
+
body.appendChild(el('p', 'empty', 'Nenhuma dependência.'));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const row = el('div', 'badge-row');
|
|
724
|
+
for (const dependency of story.dependencies) {
|
|
725
|
+
row.appendChild(el('span', 'badge label-badge', dependency));
|
|
726
|
+
}
|
|
727
|
+
body.appendChild(row);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// Nenhum campo de histórico existe no snapshot hoje; a seção só aparece se
|
|
731
|
+
// uma versão futura publicar um.
|
|
732
|
+
const history = list(story.history);
|
|
733
|
+
if (history.length > 0) {
|
|
734
|
+
drawerSection('Histórico', (body) => {
|
|
735
|
+
const entries = el('ol', 'drawer-list');
|
|
736
|
+
for (const entry of history) {
|
|
737
|
+
const item = el('li');
|
|
738
|
+
if (entry.at) item.appendChild(el('span', 'log-time', formatClock(entry.at)));
|
|
739
|
+
item.appendChild(document.createTextNode(text(entry.message)));
|
|
740
|
+
entries.appendChild(item);
|
|
741
|
+
}
|
|
742
|
+
body.appendChild(entries);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function drawerSection(title, fill) {
|
|
748
|
+
const section = el('section', 'drawer-section');
|
|
749
|
+
section.appendChild(el('h3', 'drawer-section-title', title));
|
|
750
|
+
fill(section);
|
|
751
|
+
els.drawerBody.appendChild(section);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function openDrawer(id) {
|
|
755
|
+
state.selectedStoryId = id;
|
|
756
|
+
els.drawer.hidden = false;
|
|
757
|
+
els.drawerOverlay.hidden = false;
|
|
758
|
+
document.addEventListener('keydown', onDrawerKeydown);
|
|
759
|
+
renderDrawer(state.snapshot);
|
|
760
|
+
els.drawer.focus();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function closeDrawer() {
|
|
764
|
+
const id = state.selectedStoryId;
|
|
765
|
+
state.selectedStoryId = null;
|
|
766
|
+
els.drawer.hidden = true;
|
|
767
|
+
els.drawerOverlay.hidden = true;
|
|
768
|
+
document.removeEventListener('keydown', onDrawerKeydown);
|
|
769
|
+
clear(els.drawerBody);
|
|
770
|
+
// O card é recriado a cada render, então o foco volta pelo id, não por uma
|
|
771
|
+
// referência guardada na abertura.
|
|
772
|
+
const card = id ? els.kanban.querySelector('[data-story-id="' + id + '"]') : null;
|
|
773
|
+
if (card) card.focus();
|
|
774
|
+
}
|
|
775
|
+
|
|
547
776
|
function renderGit(snapshot) {
|
|
548
777
|
const repo = repoUrl(snapshot);
|
|
549
778
|
|
|
@@ -634,6 +863,14 @@
|
|
|
634
863
|
// ---- Inicialização --------------------------------------------------------
|
|
635
864
|
|
|
636
865
|
async function init() {
|
|
866
|
+
for (const tab of els.tabs) {
|
|
867
|
+
tab.addEventListener('click', () => setActiveTab(tab.id));
|
|
868
|
+
}
|
|
869
|
+
setActiveTab(state.activeTab);
|
|
870
|
+
|
|
871
|
+
els.drawerClose.addEventListener('click', closeDrawer);
|
|
872
|
+
els.drawerOverlay.addEventListener('click', closeDrawer);
|
|
873
|
+
|
|
637
874
|
els.refreshSelect.addEventListener('change', () => {
|
|
638
875
|
state.refreshSeconds = Number(els.refreshSelect.value);
|
|
639
876
|
storeRefresh(state.refreshSeconds);
|
package/web/public/index.html
CHANGED
|
@@ -41,6 +41,14 @@
|
|
|
41
41
|
<div id="alerts-body"></div>
|
|
42
42
|
</section>
|
|
43
43
|
|
|
44
|
+
<div class="tabs" role="tablist" aria-label="Visualizações do painel">
|
|
45
|
+
<button type="button" class="tab" role="tab" id="tab-execution"
|
|
46
|
+
aria-controls="panel-execution" aria-selected="true">Execução</button>
|
|
47
|
+
<button type="button" class="tab" role="tab" id="tab-kanban"
|
|
48
|
+
aria-controls="panel-kanban" aria-selected="false">Kanban</button>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="tab-panel" id="panel-execution" role="tabpanel" aria-labelledby="tab-execution" tabindex="0">
|
|
44
52
|
<div class="columns">
|
|
45
53
|
<section class="card">
|
|
46
54
|
<h2>Resumo da issue</h2>
|
|
@@ -108,12 +116,30 @@
|
|
|
108
116
|
</div>
|
|
109
117
|
<ol id="logs" class="plain-list logs"></ol>
|
|
110
118
|
</section>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="tab-panel" id="panel-kanban" role="tabpanel" aria-labelledby="tab-kanban" tabindex="0" hidden>
|
|
122
|
+
<section class="card">
|
|
123
|
+
<h2>Kanban de user stories</h2>
|
|
124
|
+
<div id="kanban" class="kanban"></div>
|
|
125
|
+
</section>
|
|
126
|
+
</div>
|
|
111
127
|
|
|
112
128
|
<footer class="muted footer">
|
|
113
129
|
<span id="session-meta"></span>
|
|
114
130
|
</footer>
|
|
115
131
|
</main>
|
|
116
132
|
|
|
133
|
+
<div id="drawer-overlay" class="drawer-overlay" hidden></div>
|
|
134
|
+
<aside id="drawer" class="drawer" role="dialog" aria-modal="true"
|
|
135
|
+
aria-labelledby="drawer-title" tabindex="-1" hidden>
|
|
136
|
+
<div class="drawer-head">
|
|
137
|
+
<h2 id="drawer-title">Detalhes da user story</h2>
|
|
138
|
+
<button type="button" id="drawer-close" class="drawer-close" aria-label="Fechar detalhes">✕</button>
|
|
139
|
+
</div>
|
|
140
|
+
<div id="drawer-body" class="drawer-body"></div>
|
|
141
|
+
</aside>
|
|
142
|
+
|
|
117
143
|
<noscript>
|
|
118
144
|
<p class="noscript">Esta interface precisa de JavaScript. O estado bruto está disponível em
|
|
119
145
|
<a href="status.json">status.json</a>.</p>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schemas.ts","../src/core/session-state.ts","../src/core/session-publisher.ts","../src/core/state-manager.ts","../src/utils/fs.ts","../src/core/metrics.ts","../src/core/verbose.ts","../src/ui/logger.ts"],"sourcesContent":["import { z } from 'zod';\nimport type { ClaudeUsage } from './core/metrics.js';\nimport type { SessionSnapshot } from './core/session-state.js';\nimport type { IssueMetadata, IssuesConfig } from './issues/types.js';\n\n/**\n * Zod schemas for validating tasks.json structure, Issue metadata, headless\n * invocation outputs, the web monitoring session snapshot and the web\n * configuration.\n */\n\n/**\n * Token/cost metrics of a single `claude` invocation, mirroring ClaudeUsage in\n * src/core/metrics.ts. Every field is optional: the CLI only reports what it\n * knows, and an absent field means \"not reported\", never zero.\n */\nexport const claudeUsageSchema = z.object({\n inputTokens: z.number().optional(),\n outputTokens: z.number().optional(),\n cacheReadTokens: z.number().optional(),\n cacheCreationTokens: z.number().optional(),\n costUsd: z.number().optional(),\n}) satisfies z.ZodType<ClaudeUsage>;\n\nexport const userStoryStatusSchema = z.enum(['backlog', 'in_progress', 'in_review', 'done']);\n\n/**\n * The metrics fields are additive and optional: plans written before they\n * existed keep parsing, and nothing is filled in with artificial zeros.\n *\n * `status` and `dependencies` follow the same rule and are `.optional()` rather\n * than `.default()` on purpose: a legacy plan must not gain a `'backlog'` and an\n * empty array it never declared just because `saveTaskPlan` rewrote it.\n */\nexport const userStorySchema = z.object({\n id: z.string(),\n title: z.string(),\n description: z.string(),\n acceptanceCriteria: z.array(z.string()),\n priority: z.number().int().positive(),\n passes: z.boolean(),\n notes: z.string(),\n ...claudeUsageSchema.shape,\n durationSeconds: z.number().optional(),\n status: userStoryStatusSchema.optional(),\n dependencies: z.array(z.string()).optional(),\n});\n\nexport const pipelineStateSchema = z.object({\n analyzeCompleted: z.boolean().optional(),\n prdCompleted: z.boolean(),\n jsonCompleted: z.boolean(),\n executionCompleted: z.boolean(),\n reviewCompleted: z.boolean(),\n prCreated: z.boolean(),\n prReviewCompleted: z.boolean().optional(),\n});\n\n/**\n * Pull Request opened by the `pr` phase. Optional on the plan: plans written\n * before the field existed (and runs with `--no-branch`) simply omit it.\n */\nexport const pullRequestRefSchema = z.object({\n number: z.number().int().positive(),\n url: z.string(),\n headBranch: z.string(),\n createdAt: z.string(),\n});\n\nexport const prReviewRecommendationSchema = z.enum([\n 'APPROVE',\n 'APPROVE_WITH_SUGGESTIONS',\n 'REQUEST_CHANGES',\n]);\n\n/**\n * State of the opt-in `pr-review` phase. `enabled` and `rounds` carry defaults\n * so a partially written object still parses instead of invalidating the plan.\n */\nexport const prReviewStateSchema = z.object({\n enabled: z.boolean().default(false),\n pullRequestNumber: z.number().int().positive().optional(),\n rounds: z.number().int().min(0).default(0),\n lastRecommendation: prReviewRecommendationSchema.optional(),\n lastReviewedAt: z.string().optional(),\n});\n\nconst lastErrorSchema = z.object({\n category: z.string(),\n message: z.string(),\n at: z.string(),\n});\n\n/**\n * Persisted metadata for an Issue stored on disk (issues/<id>/metadata.json).\n * `satisfies` keeps this schema in lockstep with the IssueMetadata interface in\n * src/issues/types.ts — changing one without the other fails the typecheck.\n */\nexport const issueMetadataSchema = z.object({\n schemaVersion: z.literal(1),\n id: z.string().min(1),\n number: z.number().int().positive().nullable(),\n source: z.enum(['github', 'local']),\n title: z.string(),\n labels: z.array(z.string()),\n state: z.enum(['open', 'closed']),\n createdAt: z.string(),\n updatedAt: z.string(),\n contentHash: z.string(),\n remote: z\n .object({\n provider: z.enum(['github', 'local']),\n ref: z.string(),\n syncedAt: z.string(),\n syncedContentHash: z.string(),\n })\n .optional(),\n}) satisfies z.ZodType<IssueMetadata>;\n\n/**\n * tasks.json structure. Deliberately permissive: plans written by older\n * versions must keep loading, so `issueUrl` is optional (Issues with no remote\n * have no URL) and `issueNumber` accepts non-numeric local identifiers.\n */\nexport const taskPlanSchema = z.object({\n project: z.string(),\n issueNumber: z.union([z.number().int().positive(), z.string().min(1)]),\n issueUrl: z.string().optional().default(''),\n branchName: z.string(),\n noBranch: z.boolean().optional().default(false),\n description: z.string(),\n issueStatus: z.enum(['pending', 'in_progress', 'completed']),\n completedAt: z.string().nullable(),\n lastAttemptAt: z.string().nullable(),\n lastError: lastErrorSchema.nullable(),\n correctionCycle: z.number().int().min(0),\n maxCorrectionCycles: z.number().int().min(0),\n /**\n * Findings from the most recent failed review, verbatim. Non-null means the\n * issue has a pending correction even if every userStories[].passes is\n * already true — the execute phase must address these before the field is\n * cleared back to null. See core/engine.ts's early-return guards.\n */\n lastReviewFindings: z.string().nullable().optional().default(null),\n pipeline: pipelineStateSchema,\n pullRequest: pullRequestRefSchema.optional(),\n prReview: prReviewStateSchema.optional(),\n userStories: z.array(userStorySchema),\n});\n\nexport const headlessResultSchema = z.object({\n success: z.boolean(),\n result: z.string(),\n cost: claudeUsageSchema.nullable(),\n error: z.string().nullable(),\n});\n\nconst sessionLogEntrySchema = z.object({\n at: z.string(),\n level: z.enum(['info', 'warn', 'error']),\n message: z.string(),\n});\n\n/**\n * Usage counters attached to a phase or a story in the session snapshot.\n * Unlike claudeUsageSchema (a single invocation, optional fields), these are\n * always present and nullable: null means \"never reported\", not zero.\n *\n * They default to null on input so a session.json written before the metrics\n * existed still parses -- absent and null mean the same thing here, and the\n * parsed value keeps the `number | null` shape the snapshot interface declares.\n */\nconst sessionUsageShape = {\n inputTokens: z.number().nullable().default(null),\n outputTokens: z.number().nullable().default(null),\n cacheReadTokens: z.number().nullable().default(null),\n cacheCreationTokens: z.number().nullable().default(null),\n costUsd: z.number().nullable().default(null),\n};\n\nconst sessionPhaseSchema = z.object({\n name: z.string(),\n status: z.enum(['pending', 'running', 'completed', 'failed']),\n startedAt: z.string().nullable(),\n endedAt: z.string().nullable(),\n durationSeconds: z.number().nullable(),\n error: z.string().nullable(),\n ...sessionUsageShape,\n});\n\nconst sessionStorySchema = z.object({\n id: z.string(),\n title: z.string(),\n priority: z.number(),\n passes: z.boolean(),\n completedAt: z.string().nullable(),\n // Also introduced with the metrics, hence the same tolerant default.\n durationSeconds: z.number().nullable().default(null),\n // Snapshot fields are always present on output and defaulted on input, so a\n // session.json written before they existed still parses. This is the mirror\n // image of userStorySchema, where the same two fields are plainly optional.\n status: userStoryStatusSchema.default('backlog'),\n dependencies: z.array(z.string()).default([]),\n ...sessionUsageShape,\n});\n\n/**\n * Session snapshot served by the web monitoring mode (session.json and the\n * HTTP endpoint). `satisfies` keeps this schema in lockstep with the\n * SessionSnapshot interface in src/core/session-state.ts — changing one\n * without the other fails the typecheck.\n */\nexport const sessionSnapshotSchema = z.object({\n schemaVersion: z.literal(1),\n sessionId: z.string().nullable(),\n readOnly: z.literal(true),\n capabilities: z.array(z.string()),\n issue: z.object({\n number: z.number().nullable(),\n url: z.string().nullable(),\n // Additive: a session.json written before the Issue section was enriched\n // parses into the same \"not reported\" values createInitialSnapshot() uses.\n title: z.string().nullable().default(null),\n description: z.string().nullable().default(null),\n labels: z.array(z.string()).default([]),\n state: z.string().nullable().default(null),\n }),\n status: z.enum(['idle', 'running', 'completed', 'failed']),\n startedAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n endedAt: z.string().nullable(),\n elapsedSeconds: z.number().nullable(),\n estimatedRemainingSeconds: z.number().nullable(),\n progress: z.object({\n percent: z.number(),\n phasesCompleted: z.number(),\n phasesTotal: z.number(),\n storiesCompleted: z.number(),\n storiesTotal: z.number(),\n }),\n currentPhase: z.string().nullable(),\n currentActivity: z\n .object({\n story: z.string().nullable(),\n tool: z.string().nullable(),\n detail: z.string().nullable(),\n since: z.string(),\n })\n .nullable(),\n phases: z.array(sessionPhaseSchema),\n stories: z.array(sessionStorySchema),\n // The whole aggregate is additive: a snapshot from before it existed parses\n // into the same \"nothing reported\" object the reducer starts from.\n metrics: z\n .object({\n totalInputTokens: z.number().nullable().default(null),\n totalOutputTokens: z.number().nullable().default(null),\n totalCacheReadTokens: z.number().nullable().default(null),\n totalCacheCreationTokens: z.number().nullable().default(null),\n totalCostUsd: z.number().nullable().default(null),\n })\n .default({\n totalInputTokens: null,\n totalOutputTokens: null,\n totalCacheReadTokens: null,\n totalCacheCreationTokens: null,\n totalCostUsd: null,\n }),\n execution: z.object({\n iteration: z.number(),\n retries: z.number(),\n correctionCycle: z.number(),\n maxCorrectionCycles: z.number().nullable(),\n }),\n git: z.object({\n branch: z.string().nullable(),\n baseBranch: z.string().nullable(),\n commits: z.array(z.object({ hash: z.string(), subject: z.string() })),\n }),\n // Additive like the metrics aggregate: a session.json written before the\n // repository section existed parses into the same all-null object\n // createInitialSnapshot() starts from.\n repository: z\n .object({\n name: z.string().nullable().default(null),\n remoteUrl: z.string().nullable().default(null),\n branch: z.string().nullable().default(null),\n headCommit: z.string().nullable().default(null),\n root: z.string().nullable().default(null),\n })\n .default({ name: null, remoteUrl: null, branch: null, headCommit: null, root: null }),\n pullRequests: z.array(z.object({ number: z.number(), url: z.string(), title: z.string() })),\n logs: z.array(sessionLogEntrySchema),\n errors: z.array(sessionLogEntrySchema),\n warnings: z.array(sessionLogEntrySchema),\n lastError: z.object({ message: z.string(), at: z.string() }).nullable(),\n nextSteps: z.array(z.string()),\n environment: z.object({ node: z.string(), platform: z.string() }).nullable(),\n}) satisfies z.ZodType<SessionSnapshot>;\n\n/**\n * Resolved web monitoring configuration. Every field has a default, so\n * parsing a partial object (e.g. the `web` key of .issue-flow.json) fills in\n * the documented defaults.\n */\nexport const webConfigSchema = z.object({\n enabled: z.boolean().default(false),\n port: z.number().int().min(1).max(65535).default(3737),\n host: z.string().min(1).default('0.0.0.0'),\n refreshSeconds: z.number().positive().default(5),\n logLimit: z.number().int().positive().default(200),\n includeLogs: z.boolean().default(true),\n});\n\n/**\n * Resolved Issue provider configuration. Every field has a default that keeps\n * the GitHub-only behaviour, so parsing an empty object yields the exact\n * behaviour of releases without the provider layer.\n */\nexport const issuesConfigSchema = z.object({\n defaultGenerateTarget: z.enum(['github', 'local', 'both']).default('github'),\n preferredProvider: z.enum(['github', 'local']).default('github'),\n conflictPolicy: z.enum(['ask', 'prefer-local', 'prefer-github']).default('ask'),\n requireConfirmation: z.boolean().default(true),\n}) satisfies z.ZodType<IssuesConfig>;\n\n/**\n * Resolved `pr-review` configuration (the `prReview` key of .issue-flow.json).\n *\n * `publisher` selects the implementation `createPrReviewPublisher()` builds. v1\n * ships only the local one, so the enum has a single member on purpose: adding\n * a GitHub adapter is a new entry here plus a new factory, never a change to\n * the phase.\n */\nexport const prReviewConfigSchema = z.object({\n publisher: z.enum(['local']).default('local'),\n});\n\nexport type ValidatedTaskPlan = z.infer<typeof taskPlanSchema>;\nexport type ValidatedIssueMetadata = z.infer<typeof issueMetadataSchema>;\nexport type ValidatedHeadlessResult = z.infer<typeof headlessResultSchema>;\nexport type ValidatedSessionSnapshot = z.infer<typeof sessionSnapshotSchema>;\nexport type WebConfig = z.infer<typeof webConfigSchema>;\nexport type PrReviewConfig = z.infer<typeof prReviewConfigSchema>;\n","import { mkdir, rename, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { stripVTControlCharacters } from 'node:util';\nimport type { UserStory, UserStoryStatus } from '../types.js';\n\n/**\n * Session state publishing layer for the optional web monitoring mode.\n *\n * Instrumentation points call publish() with SessionEvents; a pure reducer\n * folds each event into an in-memory SessionSnapshot that any surface\n * (file, HTTP endpoint, future webhooks) can consume in a single format.\n *\n * Monitoring must never affect the pipeline: publish() is synchronous and\n * never throws, and all publisher I/O failures are swallowed after a\n * single warning.\n */\n\nexport type SessionLogLevel = 'info' | 'warn' | 'error';\nexport type SessionStatus = 'idle' | 'running' | 'completed' | 'failed';\nexport type SessionPhaseStatus = 'pending' | 'running' | 'completed' | 'failed';\n\n/** Default max entries retained in the logs ring buffer. */\nexport const DEFAULT_LOG_LIMIT = 200;\n\n/** Default minimum interval between FilePublisher disk writes. */\nexport const DEFAULT_THROTTLE_MS = 1000;\n\nexport type SessionEvent =\n | {\n type: 'session:start';\n at: string;\n sessionId: string;\n /** `null` for local identifiers that are not numbers. */\n issueNumber: number | null;\n issueUrl?: string;\n branch?: string;\n baseBranch?: string;\n phases: string[];\n environment?: SessionEnvironment;\n }\n | {\n /**\n * Structural data of the Issue being worked on, published once the\n * provider has resolved it. Merged over the `issue` section instead of\n * replacing it, so the number and the URL that came with `session:start`\n * survive an origin that reports neither.\n */\n type: 'issue:update';\n at: string;\n number: number | null;\n url?: string;\n title: string | null;\n /** Issue body, published whole — no truncation. */\n description: string | null;\n labels: string[];\n state: string | null;\n }\n | { type: 'phase:start'; at: string; phase: string }\n | { type: 'phase:end'; at: string; phase: string; success: boolean; error?: string }\n | { type: 'iteration:start'; at: string; iteration: number }\n | { type: 'iteration:end'; at: string; iteration: number }\n | { type: 'retry'; at: string; attempt: number; delaySeconds?: number; reason?: string }\n | { type: 'stories:update'; at: string; stories: UserStory[] }\n | { type: 'activity'; at: string; story?: string; tool?: string; detail?: string }\n | { type: 'log'; at: string; level: SessionLogLevel; message: string }\n | { type: 'correction:cycle'; at: string; cycle: number; maxCycles: number }\n | {\n type: 'metrics:update';\n at: string;\n /**\n * Where the metrics land:\n * - `phase`: the named phase plus the issue-wide aggregate;\n * - `iteration`: one execute-loop pass — same targets as `phase`;\n * - `story`: the story alone, never the phase nor the aggregate (the\n * iteration event of the same cycle already counted those tokens).\n */\n scope: 'phase' | 'iteration' | 'story';\n phase?: string;\n storyId?: string;\n iteration?: number;\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n costUsd?: number;\n durationSeconds?: number;\n }\n | {\n /**\n * One publication feeds both the `git` section (branch, base, commits)\n * and the `repository` section (identity and location). They come from\n * the same collection pass, so extending this event keeps `branch`\n * consistent across the two instead of racing a second event.\n *\n * Every field is optional: `undefined` means \"not collected in this\n * publication\" and leaves the snapshot untouched, while an explicit\n * `null` means \"collected and unavailable\" and is written as-is.\n */\n type: 'git:update';\n at: string;\n branch?: string;\n baseBranch?: string;\n commits?: SessionCommit[];\n pullRequests?: SessionPullRequest[];\n /** `owner/repo`, derived from the origin remote. */\n repositoryName?: string | null;\n remoteUrl?: string | null;\n headCommit?: string | null;\n repositoryRoot?: string | null;\n }\n | { type: 'session:end'; at: string; status: 'completed' | 'failed'; error?: string };\n\nexport interface SessionEnvironment {\n node: string;\n platform: string;\n}\n\nexport interface SessionLogEntry {\n at: string;\n level: SessionLogLevel;\n message: string;\n}\n\n/**\n * Token/cost counters attached to a phase or a story.\n *\n * `null` means \"never reported\" — the `claude` CLI does not always return\n * usage data, and a metric that was never observed must stay distinguishable\n * from an observed zero.\n */\nexport interface SessionUsageSnapshot {\n inputTokens: number | null;\n outputTokens: number | null;\n cacheReadTokens: number | null;\n cacheCreationTokens: number | null;\n costUsd: number | null;\n}\n\n/** Issue-wide totals, accumulated from phase- and iteration-scoped metrics. */\nexport interface SessionMetricsSnapshot {\n totalInputTokens: number | null;\n totalOutputTokens: number | null;\n totalCacheReadTokens: number | null;\n totalCacheCreationTokens: number | null;\n totalCostUsd: number | null;\n}\n\nexport interface SessionPhaseSnapshot extends SessionUsageSnapshot {\n name: string;\n status: SessionPhaseStatus;\n startedAt: string | null;\n endedAt: string | null;\n durationSeconds: number | null;\n error: string | null;\n}\n\nexport interface SessionStorySnapshot extends SessionUsageSnapshot {\n id: string;\n title: string;\n priority: number;\n passes: boolean;\n /**\n * When the story flipped to passing during this session; null for stories\n * that were already passing at session start (their duration is unknown).\n */\n completedAt: string | null;\n /** Wall-clock seconds attributed to the story, or null when unknown. */\n durationSeconds: number | null;\n /**\n * Board-style status, recomputed on every reduction by\n * {@link deriveStoryStatus}. Observational: the pipeline keeps deciding what\n * to execute from `passes`.\n */\n status: UserStoryStatus;\n /** IDs of the stories this one depends on, as declared in the plan. */\n dependencies: string[];\n}\n\nexport interface SessionActivity {\n story: string | null;\n tool: string | null;\n detail: string | null;\n since: string;\n}\n\nexport interface SessionCommit {\n hash: string;\n subject: string;\n}\n\nexport interface SessionPullRequest {\n number: number;\n url: string;\n title: string;\n}\n\n/**\n * The Issue under execution, as far as the session knows it.\n *\n * `number`/`url` come with `session:start`; the remaining fields arrive with\n * `issue:update`, once the provider has resolved the Issue. Everything is\n * nullable because a run may start before (or without) that resolution —\n * `null` means \"not reported\", never \"empty\".\n */\nexport interface SessionIssueSnapshot {\n number: number | null;\n url: string | null;\n title: string | null;\n /** Issue body in full; the consumer decides how to fold it. */\n description: string | null;\n labels: string[];\n /** Provider lifecycle state ('open' / 'closed' for the built-ins). */\n state: string | null;\n}\n\n/**\n * Where the run is happening: which repository, which checkout, which commit.\n *\n * Fed by `git:update` (see `publishGitState`). Every field is nullable because\n * each source is independent and failure-tolerant — no remote configured, a\n * repository with no commits yet or a missing git binary all show up as\n * `null` instead of failing the publication.\n */\nexport interface SessionRepositorySnapshot {\n /** `owner/repo`, derived from the origin remote; null without one. */\n name: string | null;\n remoteUrl: string | null;\n branch: string | null;\n /** Abbreviated hash of HEAD. */\n headCommit: string | null;\n /** Absolute path of the working directory the pipeline runs from. */\n root: string | null;\n}\n\nexport interface SessionSnapshot {\n schemaVersion: 1;\n sessionId: string | null;\n readOnly: true;\n capabilities: string[];\n issue: SessionIssueSnapshot;\n status: SessionStatus;\n startedAt: string | null;\n updatedAt: string | null;\n endedAt: string | null;\n elapsedSeconds: number | null;\n estimatedRemainingSeconds: number | null;\n progress: {\n percent: number;\n phasesCompleted: number;\n phasesTotal: number;\n storiesCompleted: number;\n storiesTotal: number;\n };\n currentPhase: string | null;\n currentActivity: SessionActivity | null;\n phases: SessionPhaseSnapshot[];\n stories: SessionStorySnapshot[];\n metrics: SessionMetricsSnapshot;\n execution: {\n iteration: number;\n retries: number;\n correctionCycle: number;\n maxCorrectionCycles: number | null;\n };\n git: { branch: string | null; baseBranch: string | null; commits: SessionCommit[] };\n repository: SessionRepositorySnapshot;\n pullRequests: SessionPullRequest[];\n logs: SessionLogEntry[];\n errors: SessionLogEntry[];\n warnings: SessionLogEntry[];\n lastError: { message: string; at: string } | null;\n nextSteps: string[];\n environment: SessionEnvironment | null;\n}\n\nexport interface SessionReducerOptions {\n /** Max entries retained in the logs ring buffer. */\n logLimit?: number;\n}\n\n/** Fresh, all-null usage counters for a new phase or story entry. */\nfunction emptyUsage(): SessionUsageSnapshot {\n return {\n inputTokens: null,\n outputTokens: null,\n cacheReadTokens: null,\n cacheCreationTokens: null,\n costUsd: null,\n };\n}\n\nfunction emptyMetrics(): SessionMetricsSnapshot {\n return {\n totalInputTokens: null,\n totalOutputTokens: null,\n totalCacheReadTokens: null,\n totalCacheCreationTokens: null,\n totalCostUsd: null,\n };\n}\n\nexport function createInitialSnapshot(): SessionSnapshot {\n return {\n schemaVersion: 1,\n sessionId: null,\n readOnly: true,\n capabilities: [],\n issue: { number: null, url: null, title: null, description: null, labels: [], state: null },\n status: 'idle',\n startedAt: null,\n updatedAt: null,\n endedAt: null,\n elapsedSeconds: null,\n estimatedRemainingSeconds: null,\n progress: {\n percent: 0,\n phasesCompleted: 0,\n phasesTotal: 0,\n storiesCompleted: 0,\n storiesTotal: 0,\n },\n currentPhase: null,\n currentActivity: null,\n phases: [],\n stories: [],\n metrics: emptyMetrics(),\n execution: { iteration: 0, retries: 0, correctionCycle: 0, maxCorrectionCycles: null },\n git: { branch: null, baseBranch: null, commits: [] },\n repository: { name: null, remoteUrl: null, branch: null, headCommit: null, root: null },\n pullRequests: [],\n logs: [],\n errors: [],\n warnings: [],\n lastError: null,\n nextSteps: [],\n environment: null,\n };\n}\n\ntype MetricsUpdateEvent = Extract<SessionEvent, { type: 'metrics:update' }>;\n\n/**\n * Add a reported delta to an accumulator. `undefined` means \"not reported\":\n * it leaves the accumulator untouched, so a metric the CLI never returned\n * stays null instead of collapsing to zero.\n */\nfunction accumulate(current: number | null, delta: number | undefined): number | null {\n return delta === undefined ? current : (current ?? 0) + delta;\n}\n\n/** Fold the event's token/cost fields into a phase or story entry. */\nfunction accumulateUsage<T extends SessionUsageSnapshot>(target: T, event: MetricsUpdateEvent): T {\n return {\n ...target,\n inputTokens: accumulate(target.inputTokens, event.inputTokens),\n outputTokens: accumulate(target.outputTokens, event.outputTokens),\n cacheReadTokens: accumulate(target.cacheReadTokens, event.cacheReadTokens),\n cacheCreationTokens: accumulate(target.cacheCreationTokens, event.cacheCreationTokens),\n costUsd: accumulate(target.costUsd, event.costUsd),\n };\n}\n\n/**\n * Pick between a reported value and the one already in the snapshot.\n * Unlike `??`, an explicitly reported `null` wins: only `undefined` — the\n * absence of the field on the event — keeps the previous value.\n */\nfunction reported<T>(value: T | undefined, previous: T): T {\n return value === undefined ? previous : value;\n}\n\n/**\n * Seconds between two ISO timestamps, or null when either is unparsable.\n */\nfunction secondsBetween(from: string | null, to: string): number | null {\n if (from === null) return null;\n const fromMs = Date.parse(from);\n const toMs = Date.parse(to);\n if (Number.isNaN(fromMs) || Number.isNaN(toMs)) return null;\n return Math.max(0, Math.round((toMs - fromMs) / 1000));\n}\n\nfunction computePercent(phasesCompleted: number, phasesTotal: number): number {\n if (phasesTotal === 0) return 0;\n return Math.round((phasesCompleted / phasesTotal) * 100);\n}\n\n/**\n * Estimated seconds until all stories pass: average duration of the stories\n * completed during this session × pending stories. Durations are the gaps\n * between consecutive completions (the first is measured from startedAt).\n * Published as null with fewer than two samples.\n */\nfunction estimateRemainingSeconds(snapshot: SessionSnapshot): number | null {\n const completions = snapshot.stories\n .map((story) => (story.completedAt === null ? Number.NaN : Date.parse(story.completedAt)))\n .filter((ms) => !Number.isNaN(ms))\n .sort((a, b) => a - b);\n if (completions.length < 2) return null;\n\n const startMs = snapshot.startedAt === null ? Number.NaN : Date.parse(snapshot.startedAt);\n const boundaries = Number.isNaN(startMs) ? completions : [startMs, ...completions];\n const durations: number[] = [];\n for (let i = 1; i < boundaries.length; i++) {\n durations.push(Math.max(0, boundaries[i] - boundaries[i - 1]));\n }\n if (durations.length === 0) return null;\n\n const averageMs = durations.reduce((sum, d) => sum + d, 0) / durations.length;\n const pending = snapshot.stories.filter((story) => !story.passes).length;\n return Math.round((averageMs * pending) / 1000);\n}\n\n/**\n * Remaining pipeline phases, in order. The phase list originates from the\n * session:start event, whose caller passes PIPELINE_PHASES (or\n * PIPELINE_PHASES_NO_BRANCH in --no-branch mode) from src/core/pipeline.ts.\n */\nfunction deriveNextSteps(snapshot: SessionSnapshot): string[] {\n if (snapshot.status === 'completed') return [];\n return snapshot.phases.filter((phase) => phase.status === 'pending').map((phase) => phase.name);\n}\n\n/**\n * Board status of a single story, in a fixed order that makes the derivation\n * idempotent — recomputing it over its own result yields the same value:\n *\n * 1. `passes: true` → `done` (execution is the only authority on completion);\n * 2. already `in_review` → kept, since nothing here can produce or clear it;\n * 3. the story owns the current activity → `in_progress`;\n * 4. otherwise → `backlog`.\n *\n * `in_review` is therefore never derived: it only enters through an explicit\n * `status` in tasks.json, and leaves when the story starts passing.\n */\nfunction deriveStoryStatus(\n story: SessionStorySnapshot,\n activeStory: string | null,\n): UserStoryStatus {\n if (story.passes) return 'done';\n if (story.status === 'in_review') return 'in_review';\n if (activeStory !== null && activeStory === story.id) return 'in_progress';\n return 'backlog';\n}\n\n/** Recompute every story's status; entries that do not change keep identity. */\nfunction deriveStoryStatuses(snapshot: SessionSnapshot): SessionStorySnapshot[] {\n const activeStory = snapshot.currentActivity?.story ?? null;\n return snapshot.stories.map((story) => {\n const status = deriveStoryStatus(story, activeStory);\n return status === story.status ? story : { ...story, status };\n });\n}\n\n/**\n * Fold a SessionEvent into a SessionSnapshot. Pure: never mutates the input\n * and performs no I/O. Unknown event types return the snapshot unchanged.\n *\n * errors/warnings are derived slices of the logs ring buffer, recomputed on\n * each reduction — they are never accumulated separately. The same applies\n * to estimatedRemainingSeconds, nextSteps and each story's status.\n */\nexport function reduceSessionEvent(\n snapshot: SessionSnapshot,\n event: SessionEvent,\n options?: SessionReducerOptions,\n): SessionSnapshot {\n const next = applyEvent(snapshot, event, options);\n if (next === snapshot) return snapshot;\n\n const elapsedSeconds = secondsBetween(next.startedAt, event.at) ?? next.elapsedSeconds;\n return {\n ...next,\n updatedAt: event.at,\n elapsedSeconds,\n stories: deriveStoryStatuses(next),\n estimatedRemainingSeconds: estimateRemainingSeconds(next),\n errors: next.logs.filter((entry) => entry.level === 'error'),\n warnings: next.logs.filter((entry) => entry.level === 'warn'),\n nextSteps: deriveNextSteps(next),\n };\n}\n\nfunction applyEvent(\n snapshot: SessionSnapshot,\n event: SessionEvent,\n options?: SessionReducerOptions,\n): SessionSnapshot {\n switch (event.type) {\n case 'session:start': {\n const initial = createInitialSnapshot();\n return {\n ...initial,\n sessionId: event.sessionId,\n issue: { ...initial.issue, number: event.issueNumber, url: event.issueUrl ?? null },\n status: 'running',\n startedAt: event.at,\n progress: {\n ...initial.progress,\n phasesTotal: event.phases.length,\n },\n phases: event.phases.map((name) => ({\n name,\n status: 'pending' as const,\n startedAt: null,\n endedAt: null,\n durationSeconds: null,\n error: null,\n ...emptyUsage(),\n })),\n git: { branch: event.branch ?? null, baseBranch: event.baseBranch ?? null, commits: [] },\n // The branch is the one piece of repository identity the session\n // already knows here; the rest waits for publishGitState. Seeding it\n // keeps git.branch and repository.branch consistent for a poll that\n // lands before the first git:update.\n repository: { ...initial.repository, branch: event.branch ?? null },\n environment: event.environment ?? null,\n };\n }\n\n case 'issue:update':\n return {\n ...snapshot,\n issue: {\n ...snapshot.issue,\n // Merge, not replacement: the run may know a number and a URL that\n // the provider does not report (a local Issue mirroring a remote\n // one), and enriching the section must never erase them.\n number: event.number ?? snapshot.issue.number,\n url: event.url ?? snapshot.issue.url,\n title: event.title,\n description: event.description,\n labels: event.labels,\n state: event.state,\n },\n };\n\n case 'phase:start': {\n const known = snapshot.phases.some((p) => p.name === event.phase);\n const phases = known\n ? snapshot.phases.map((p) =>\n p.name === event.phase\n ? {\n ...p,\n status: 'running' as const,\n startedAt: event.at,\n endedAt: null,\n error: null,\n }\n : p,\n )\n : [\n ...snapshot.phases,\n {\n name: event.phase,\n status: 'running' as const,\n startedAt: event.at,\n endedAt: null,\n durationSeconds: null,\n error: null,\n ...emptyUsage(),\n },\n ];\n return {\n ...snapshot,\n currentPhase: event.phase,\n phases,\n progress: known ? snapshot.progress : { ...snapshot.progress, phasesTotal: phases.length },\n };\n }\n\n case 'phase:end': {\n const phases = snapshot.phases.map((p) =>\n p.name === event.phase\n ? {\n ...p,\n status: event.success ? ('completed' as const) : ('failed' as const),\n endedAt: event.at,\n durationSeconds: secondsBetween(p.startedAt, event.at),\n error: event.error ? stripVTControlCharacters(event.error) : null,\n }\n : p,\n );\n const phasesCompleted = phases.filter((p) => p.status === 'completed').length;\n return {\n ...snapshot,\n currentPhase: snapshot.currentPhase === event.phase ? null : snapshot.currentPhase,\n currentActivity: null,\n phases,\n progress: {\n ...snapshot.progress,\n phasesCompleted,\n percent: computePercent(phasesCompleted, snapshot.progress.phasesTotal),\n },\n };\n }\n\n case 'iteration:start':\n return {\n ...snapshot,\n execution: { ...snapshot.execution, iteration: event.iteration },\n };\n\n case 'iteration:end':\n return { ...snapshot, currentActivity: null };\n\n case 'retry':\n return {\n ...snapshot,\n execution: { ...snapshot.execution, retries: snapshot.execution.retries + 1 },\n };\n\n case 'stories:update': {\n const previous = new Map(snapshot.stories.map((story) => [story.id, story]));\n const stories = event.stories.map((story) => {\n const before = previous.get(story.id);\n // Stamp the flip to passing; stories already passing when first seen\n // keep null (completed before this session, duration unknown).\n const completedAt = !story.passes\n ? null\n : before && !before.passes\n ? event.at\n : (before?.completedAt ?? null);\n // stories:update rebuilds the array from the plan on every publish, so\n // metrics already attributed to a story must be carried over here or\n // the next update would wipe them.\n return {\n id: story.id,\n title: story.title,\n priority: story.priority,\n passes: story.passes,\n completedAt,\n // Seed only: deriveStoryStatus() recomputes it right after, so an\n // explicit 'done' in the plan on a story with passes: false is not\n // honoured — `passes` remains the single source of truth. The plan's\n // value survives just for 'in_review', which no derivation produces.\n status: story.status ?? before?.status ?? 'backlog',\n dependencies: story.dependencies ?? [],\n durationSeconds: before?.durationSeconds ?? null,\n inputTokens: before?.inputTokens ?? null,\n outputTokens: before?.outputTokens ?? null,\n cacheReadTokens: before?.cacheReadTokens ?? null,\n cacheCreationTokens: before?.cacheCreationTokens ?? null,\n costUsd: before?.costUsd ?? null,\n };\n });\n return {\n ...snapshot,\n stories,\n progress: {\n ...snapshot.progress,\n storiesCompleted: stories.filter((s) => s.passes).length,\n storiesTotal: stories.length,\n },\n };\n }\n\n case 'activity': {\n const story = event.story ?? null;\n const tool = event.tool ?? null;\n const detail = event.detail ?? null;\n const current = snapshot.currentActivity;\n // Same activity repeated: keep `since` so the UI can show \"for how long\".\n const since =\n current && current.story === story && current.tool === tool && current.detail === detail\n ? current.since\n : event.at;\n return { ...snapshot, currentActivity: { story, tool, detail, since } };\n }\n\n case 'log': {\n const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;\n const entry: SessionLogEntry = {\n at: event.at,\n level: event.level,\n message: stripVTControlCharacters(event.message),\n };\n const logs = [...snapshot.logs, entry].slice(-Math.max(1, limit));\n return { ...snapshot, logs };\n }\n\n case 'git:update':\n return {\n ...snapshot,\n git: {\n branch: event.branch ?? snapshot.git.branch,\n baseBranch: event.baseBranch ?? snapshot.git.baseBranch,\n commits: event.commits ?? snapshot.git.commits,\n },\n repository: {\n // `undefined` is \"not collected\", so the previous value stands; an\n // explicit `null` is \"collected and unavailable\" and overwrites it.\n name: reported(event.repositoryName, snapshot.repository.name),\n remoteUrl: reported(event.remoteUrl, snapshot.repository.remoteUrl),\n branch: event.branch ?? snapshot.repository.branch,\n headCommit: reported(event.headCommit, snapshot.repository.headCommit),\n root: reported(event.repositoryRoot, snapshot.repository.root),\n },\n pullRequests: event.pullRequests ?? snapshot.pullRequests,\n };\n\n case 'correction:cycle':\n return {\n ...snapshot,\n execution: {\n ...snapshot.execution,\n correctionCycle: event.cycle,\n maxCorrectionCycles: event.maxCycles,\n },\n };\n\n case 'metrics:update': {\n if (event.scope === 'story') {\n // Story metrics are a rateio of the iteration that completed them;\n // the iteration-scoped event already fed the phase and the aggregate,\n // so counting them again here would double the totals.\n if (event.storyId === undefined) return snapshot;\n if (!snapshot.stories.some((s) => s.id === event.storyId)) return snapshot;\n return {\n ...snapshot,\n stories: snapshot.stories.map((story) =>\n story.id === event.storyId\n ? {\n ...accumulateUsage(story, event),\n durationSeconds: accumulate(story.durationSeconds, event.durationSeconds),\n }\n : story,\n ),\n };\n }\n\n // phase and iteration scopes both land on the named phase. An event for\n // a phase the snapshot never saw is ignored rather than appended: it\n // would show up as a phantom entry in the UI.\n if (event.phase === undefined) return snapshot;\n if (!snapshot.phases.some((p) => p.name === event.phase)) return snapshot;\n return {\n ...snapshot,\n // durationSeconds is deliberately untouched here: phase:start and\n // phase:end remain the single source of a phase's wall-clock time.\n phases: snapshot.phases.map((phase) =>\n phase.name === event.phase ? accumulateUsage(phase, event) : phase,\n ),\n metrics: {\n totalInputTokens: accumulate(snapshot.metrics.totalInputTokens, event.inputTokens),\n totalOutputTokens: accumulate(snapshot.metrics.totalOutputTokens, event.outputTokens),\n totalCacheReadTokens: accumulate(\n snapshot.metrics.totalCacheReadTokens,\n event.cacheReadTokens,\n ),\n totalCacheCreationTokens: accumulate(\n snapshot.metrics.totalCacheCreationTokens,\n event.cacheCreationTokens,\n ),\n totalCostUsd: accumulate(snapshot.metrics.totalCostUsd, event.costUsd),\n },\n };\n }\n\n case 'session:end':\n return {\n ...snapshot,\n status: event.status,\n endedAt: event.at,\n currentPhase: null,\n currentActivity: null,\n lastError: event.error\n ? { message: stripVTControlCharacters(event.error), at: event.at }\n : snapshot.lastError,\n };\n\n default:\n return snapshot;\n }\n}\n\nexport interface SessionPublisher {\n /**\n * Publish an event. Synchronous, never throws, never returns a promise —\n * safe to call from any instrumentation point without affecting execution.\n */\n publish(event: SessionEvent): void;\n /** Current in-memory snapshot. */\n snapshot(): SessionSnapshot;\n /** Monotonic counter, bumped on every applied event (basis for ETags). */\n version(): number;\n /** Force any pending output to be written. Never rejects. */\n flush(): Promise<void>;\n /** Flush and release resources. Never rejects. */\n close(): Promise<void>;\n}\n\n/**\n * Default publisher when monitoring is off: every call is a no-op, so each\n * instrumentation point costs a method call that returns immediately.\n */\nexport class NullPublisher implements SessionPublisher {\n private readonly empty = createInitialSnapshot();\n\n publish(_event: SessionEvent): void {}\n\n snapshot(): SessionSnapshot {\n return this.empty;\n }\n\n version(): number {\n return 0;\n }\n\n async flush(): Promise<void> {}\n\n async close(): Promise<void> {}\n}\n\nexport interface MemoryPublisherOptions extends SessionReducerOptions {\n /** Called at most once, on the first internal failure. */\n onWarn?: (message: string) => void;\n /**\n * When false, log events are dropped before reaching the snapshot, so no\n * log line is ever published (session.json or HTTP). Default true.\n */\n includeLogs?: boolean;\n}\n\n/**\n * In-memory publisher: reduces events over a snapshot and tracks a monotonic\n * version. Base class for publishers with an output surface (file, HTTP).\n */\nexport class MemoryPublisher implements SessionPublisher {\n protected state: SessionSnapshot = createInitialSnapshot();\n protected versionCounter = 0;\n private warned = false;\n private readonly onWarn: (message: string) => void;\n private readonly reducerOptions: SessionReducerOptions;\n private readonly includeLogs: boolean;\n\n constructor(options: MemoryPublisherOptions = {}) {\n this.onWarn = options.onWarn ?? ((message) => process.stderr.write(`${message}\\n`));\n this.reducerOptions = { logLimit: options.logLimit };\n this.includeLogs = options.includeLogs ?? true;\n }\n\n publish(event: SessionEvent): void {\n if (event.type === 'log' && !this.includeLogs) return;\n try {\n this.state = reduceSessionEvent(this.state, event, this.reducerOptions);\n this.versionCounter++;\n this.afterPublish(event);\n } catch (err) {\n this.warnOnce(err);\n }\n }\n\n /** Hook for subclasses; runs inside publish()'s try/catch. */\n protected afterPublish(_event: SessionEvent): void {}\n\n snapshot(): SessionSnapshot {\n return this.state;\n }\n\n version(): number {\n return this.versionCounter;\n }\n\n async flush(): Promise<void> {}\n\n async close(): Promise<void> {}\n\n protected warnOnce(err: unknown): void {\n if (this.warned) return;\n this.warned = true;\n const message = err instanceof Error ? err.message : String(err);\n try {\n this.onWarn(\n `issue-flow: web monitoring hit an error (will keep retrying silently): ${message}`,\n );\n } catch {\n // Even a failing warn callback must not propagate to the pipeline.\n }\n }\n}\n\nexport interface FilePublisherOptions extends MemoryPublisherOptions {\n /** Minimum interval between disk writes (ms). Default 1000. */\n throttleMs?: number;\n}\n\n/**\n * Publisher that mirrors the snapshot to issues/N/session.json.\n *\n * Writes are atomic (write-to-temp + rename) and throttled; terminal events\n * (phase:end, session:end) force an immediate write. All I/O failures are\n * swallowed after a single warning.\n */\nexport class FilePublisher extends MemoryPublisher {\n private readonly filePath: string;\n private readonly throttleMs: number;\n private timer: NodeJS.Timeout | null = null;\n private lastWriteStartedAt = 0;\n private lastWrittenVersion = 0;\n private writeChain: Promise<void> = Promise.resolve();\n private closed = false;\n\n constructor(filePath: string, options: FilePublisherOptions = {}) {\n super(options);\n this.filePath = filePath;\n this.throttleMs = options.throttleMs ?? DEFAULT_THROTTLE_MS;\n }\n\n protected override afterPublish(event: SessionEvent): void {\n if (this.closed) return;\n const terminal = event.type === 'phase:end' || event.type === 'session:end';\n this.scheduleWrite(terminal);\n }\n\n private scheduleWrite(force: boolean): void {\n if (force) {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n this.enqueueWrite();\n return;\n }\n if (this.timer !== null) return;\n const wait = Math.max(0, this.throttleMs - (Date.now() - this.lastWriteStartedAt));\n if (wait === 0) {\n this.enqueueWrite();\n return;\n }\n this.timer = setTimeout(() => {\n this.timer = null;\n this.enqueueWrite();\n }, wait);\n this.timer.unref();\n }\n\n private enqueueWrite(): void {\n this.lastWriteStartedAt = Date.now();\n this.writeChain = this.writeChain.then(async () => {\n const version = this.versionCounter;\n if (version === this.lastWrittenVersion) return;\n const payload = `${JSON.stringify(this.state, null, 2)}\\n`;\n try {\n await atomicWriteFile(this.filePath, payload);\n this.lastWrittenVersion = version;\n } catch (err) {\n this.warnOnce(err);\n }\n });\n }\n\n override async flush(): Promise<void> {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n if (this.versionCounter !== this.lastWrittenVersion) {\n this.enqueueWrite();\n }\n await this.writeChain;\n }\n\n override async close(): Promise<void> {\n await this.flush();\n this.closed = true;\n }\n}\n\n/**\n * Atomic write: write to a temp file next to the target, then rename. The\n * same-directory temp keeps the rename on a single filesystem (rename is\n * atomic; no EXDEV fallback needed, unlike an os.tmpdir() temp on Linux\n * tmpfs) and leaves nothing behind. The FilePublisher write chain is the\n * single writer, so the fixed .tmp name never races.\n *\n * The target directory (issues/N/) may not exist yet the first time a fresh\n * issue publishes — pipeline phases create it lazily, and this can be the\n * very first write. mkdir recursive is idempotent, so it's cheap to ensure\n * on every write rather than relying on call order elsewhere.\n */\nasync function atomicWriteFile(path: string, content: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const tmpFile = `${path}.tmp`;\n await writeFile(tmpFile, content, 'utf-8');\n await rename(tmpFile, path);\n}\n","import { NullPublisher, type SessionPublisher } from './session-state.js';\n\n/**\n * Global slot for the active SessionPublisher, following the same pattern as\n * setVerbose in verbose.ts. Instrumentation points (logger, engine, headless)\n * read the slot via getSessionPublisher(); run.ts installs a FilePublisher\n * when web monitoring is enabled. The default NullPublisher makes every\n * instrumentation point a no-op when monitoring is off.\n */\n\nconst NULL_PUBLISHER = new NullPublisher();\n\nlet _publisher: SessionPublisher = NULL_PUBLISHER;\n\n/**\n * Install the active session publisher. Pass undefined to reset to the\n * default NullPublisher.\n */\nexport function setSessionPublisher(publisher: SessionPublisher | undefined): void {\n _publisher = publisher ?? NULL_PUBLISHER;\n}\n\nexport function getSessionPublisher(): SessionPublisher {\n return _publisher;\n}\n","import { readFile } from 'node:fs/promises';\nimport { ZodError } from 'zod';\nimport { taskPlanSchema } from '../schemas.js';\nimport type { LastError, PipelineState, TaskPlan, UserStory } from '../types.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { type ClaudeUsage, sumUsage } from './metrics.js';\n\n/**\n * Get the current ISO timestamp.\n */\nexport function isoNow(): string {\n return new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * Load and parse a tasks.json file.\n * Validates that it has the expected structure.\n */\nexport async function loadTaskPlan(path: string): Promise<TaskPlan> {\n const content = await readFile(path, 'utf-8');\n const raw = JSON.parse(content);\n\n try {\n return taskPlanSchema.parse(raw) as TaskPlan;\n } catch (err) {\n if (err instanceof ZodError) {\n const issues = err.issues.map((i) => ` - ${i.path.join('.')}: ${i.message}`).join('\\n');\n throw new Error(`Invalid tasks.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Save a TaskPlan to disk atomically (write-to-temp + rename).\n * This prevents corruption if the process is interrupted during write.\n */\nexport async function saveTaskPlan(path: string, plan: TaskPlan): Promise<void> {\n await writeFileAtomic(path, `${JSON.stringify(plan, null, 2)}\\n`);\n}\n\n/**\n * Initialize default state fields on a TaskPlan.\n * Fills in missing fields with defaults, matching the Bash script behavior.\n *\n * The opt-in fields (`pullRequest`, `prReview`, `pipeline.prReviewCompleted`)\n * are deliberately absent from the defaults: they ride along on the spread when\n * present and stay out of the file entirely when the phases never ran.\n */\nexport function initializeState(plan: TaskPlan): TaskPlan {\n const defaultPipeline: PipelineState = {\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n };\n\n return {\n ...plan,\n issueStatus: plan.issueStatus ?? 'pending',\n completedAt: plan.completedAt ?? null,\n lastAttemptAt: plan.lastAttemptAt ?? null,\n lastError: plan.lastError ?? null,\n correctionCycle: plan.correctionCycle ?? 0,\n maxCorrectionCycles: plan.maxCorrectionCycles ?? 3,\n lastReviewFindings: plan.lastReviewFindings ?? null,\n pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline,\n };\n}\n\n/**\n * Whether the issue has a review failure that execute must still address,\n * even if every userStories[].passes is already true.\n */\nexport function hasPendingCorrection(plan: TaskPlan): boolean {\n return Boolean(plan.lastReviewFindings);\n}\n\n/**\n * Check if all user stories have passes=true.\n * Returns false if there are no stories.\n */\nexport function allStoriesPass(plan: TaskPlan): boolean {\n if (plan.userStories.length === 0) {\n return false;\n }\n\n return plan.userStories.every((story) => story.passes === true);\n}\n\n/**\n * Mark a specific story as passing.\n */\nexport function markStoryPassing(plan: TaskPlan, storyId: string): TaskPlan {\n return {\n ...plan,\n userStories: plan.userStories.map((story) =>\n story.id === storyId ? { ...story, passes: true } : story,\n ),\n };\n}\n\n/**\n * Accumulate a usage share (and duration) onto the given stories.\n *\n * Pure, like the rest of this module: returns a new plan and never touches the\n * input. Accumulation is summing and treats `undefined` as \"not reported\": a\n * field neither the story nor the share carries stays absent, so a plan written\n * by a CLI that reports no usage never gains artificial zeros.\n *\n * The share is what `divideUsage()` already split across the stories that\n * completed in the same execute iteration — see core/engine.ts.\n */\nexport function applyStoryMetrics(\n plan: TaskPlan,\n storyIds: string[],\n share: ClaudeUsage,\n durationSeconds?: number,\n): TaskPlan {\n if (storyIds.length === 0) return plan;\n\n const targets = new Set(storyIds);\n\n return {\n ...plan,\n userStories: plan.userStories.map((story) => {\n if (!targets.has(story.id)) return story;\n\n const updated: UserStory = { ...story, ...sumUsage(story, share) };\n if (story.durationSeconds !== undefined || durationSeconds !== undefined) {\n updated.durationSeconds = (story.durationSeconds ?? 0) + (durationSeconds ?? 0);\n }\n return updated;\n }),\n };\n}\n\n/**\n * Mark the issue as in_progress.\n */\nexport function markIssueInProgress(plan: TaskPlan, timestamp?: string): TaskPlan {\n const ts = timestamp ?? isoNow();\n return {\n ...plan,\n issueStatus: 'in_progress',\n completedAt: null,\n lastAttemptAt: ts,\n };\n}\n\n/**\n * Mark the issue as completed.\n */\nexport function markIssueCompleted(plan: TaskPlan): TaskPlan {\n const ts = isoNow();\n return {\n ...plan,\n issueStatus: 'completed',\n completedAt: ts,\n lastAttemptAt: ts,\n lastError: null,\n pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline,\n };\n}\n\n/**\n * Set the lastError field on the task plan.\n */\nexport function setLastError(plan: TaskPlan, category: string, message: string): TaskPlan {\n const ts = isoNow();\n const error: LastError = {\n category,\n message,\n at: ts,\n };\n\n return {\n ...plan,\n lastAttemptAt: ts,\n lastError: error,\n };\n}\n\n/**\n * Clear the lastError field, but only if it was set before the attempt started.\n * This prevents clearing errors that were set during the current attempt.\n */\nexport function clearLastError(plan: TaskPlan, attemptStartedAt: string): TaskPlan {\n const ts = isoNow();\n\n // If the error was set after the attempt started, keep it\n if (plan.lastError && plan.lastError.at > attemptStartedAt) {\n return {\n ...plan,\n lastAttemptAt: ts,\n };\n }\n\n return {\n ...plan,\n lastAttemptAt: ts,\n lastError: null,\n };\n}\n\n/**\n * Trim an error message to at most 8 non-empty lines.\n */\nexport function trimErrorMessage(message: string): string {\n return message\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .slice(0, 8)\n .join('\\n');\n}\n","import { copyFile, mkdtemp, rename, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, join } from 'node:path';\n\n/**\n * Write a file atomically (write-to-temp + rename).\n *\n * A crash mid-write leaves the previous content untouched instead of a\n * truncated file, which matters for every artifact the pipeline reloads on the\n * next run (tasks.json, metadata.json).\n */\nexport async function writeFileAtomic(path: string, content: string): Promise<void> {\n const tmpDir = await mkdtemp(join(tmpdir(), 'issue-flow-'));\n const tmpFile = join(tmpDir, basename(path));\n\n await writeFile(tmpFile, content, 'utf-8');\n\n try {\n await rename(tmpFile, path);\n } catch (err: unknown) {\n // EXDEV: rename fails across different devices/drives (common on Windows)\n if ((err as NodeJS.ErrnoException).code === 'EXDEV') {\n await copyFile(tmpFile, path);\n await unlink(tmpFile);\n } else {\n throw err;\n }\n }\n}\n","/**\n * Single source of truth for translating the `claude` CLI JSON output into\n * token/cost metrics.\n *\n * Three call sites consume this (headless json, headless stream-json and the\n * execute-phase executor); keeping the parsing here is what stops them from\n * diverging again the next time the CLI payload changes shape.\n */\n\nexport interface ClaudeUsage {\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n costUsd?: number;\n}\n\n/** Field names of {@link ClaudeUsage}, used by the summing helper. */\nconst USAGE_FIELDS = [\n 'inputTokens',\n 'outputTokens',\n 'cacheReadTokens',\n 'cacheCreationTokens',\n 'costUsd',\n] as const;\n\n/**\n * Coerce a value to a finite number, or undefined when it is absent/unusable.\n * Never throws — malformed payloads simply yield undefined.\n */\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Parse a `claude` CLI result payload into a {@link ClaudeUsage}.\n *\n * Reads the current format (`total_cost_usd` plus a nested `usage` object) and\n * falls back to the legacy flat keys (`cost_usd`, `num_input_tokens`,\n * `num_output_tokens`) when the newer ones are absent.\n *\n * Returns null when the payload carries no recognizable field at all, and a\n * partial object when only some fields are present. Never throws.\n */\nexport function parseUsage(payload: unknown): ClaudeUsage | null {\n if (!isRecord(payload)) return null;\n\n const usageRaw = isRecord(payload.usage) ? payload.usage : undefined;\n\n const usage: ClaudeUsage = {};\n\n const inputTokens = usageRaw ? num(usageRaw.input_tokens) : undefined;\n const outputTokens = usageRaw ? num(usageRaw.output_tokens) : undefined;\n const cacheReadTokens = usageRaw ? num(usageRaw.cache_read_input_tokens) : undefined;\n const cacheCreationTokens = usageRaw ? num(usageRaw.cache_creation_input_tokens) : undefined;\n const costUsd = num(payload.total_cost_usd);\n\n // Legacy fallbacks — only consulted when the modern key is missing.\n const legacyInput = num(payload.num_input_tokens);\n const legacyOutput = num(payload.num_output_tokens);\n const legacyCost = num(payload.cost_usd);\n\n const resolvedInput = inputTokens ?? legacyInput;\n const resolvedOutput = outputTokens ?? legacyOutput;\n const resolvedCost = costUsd ?? legacyCost;\n\n if (resolvedInput !== undefined) usage.inputTokens = resolvedInput;\n if (resolvedOutput !== undefined) usage.outputTokens = resolvedOutput;\n if (cacheReadTokens !== undefined) usage.cacheReadTokens = cacheReadTokens;\n if (cacheCreationTokens !== undefined) usage.cacheCreationTokens = cacheCreationTokens;\n if (resolvedCost !== undefined) usage.costUsd = resolvedCost;\n\n return Object.keys(usage).length > 0 ? usage : null;\n}\n\n/**\n * Sum two usages field by field.\n *\n * `undefined` means \"not reported\", not zero: a field absent on both sides\n * stays absent in the result, while a field present on a single side is\n * carried over untouched.\n */\nexport function sumUsage(\n a: ClaudeUsage | null | undefined,\n b: ClaudeUsage | null | undefined,\n): ClaudeUsage {\n const result: ClaudeUsage = {};\n\n for (const field of USAGE_FIELDS) {\n const left = a?.[field];\n const right = b?.[field];\n if (left === undefined && right === undefined) continue;\n result[field] = (left ?? 0) + (right ?? 0);\n }\n\n return result;\n}\n\n/**\n * Split a usage evenly across `parts`.\n *\n * This is the rateio used when several stories complete in the same execute\n * iteration: the iteration's tokens cannot be attributed to a single story, so\n * each one receives an equal share. Token counts are rounded to integers;\n * the cost keeps full precision.\n *\n * `parts <= 1` (or a non-finite value) returns the usage unchanged. Fields the\n * CLI never reported stay absent — dividing \"not reported\" still yields\n * \"not reported\", never zero.\n */\nexport function divideUsage(usage: ClaudeUsage | null | undefined, parts: number): ClaudeUsage {\n const result: ClaudeUsage = {};\n if (!usage) return result;\n\n const divisor = Number.isFinite(parts) && parts > 1 ? parts : 1;\n\n for (const field of USAGE_FIELDS) {\n const value = usage[field];\n if (value === undefined) continue;\n result[field] = field === 'costUsd' ? value / divisor : Math.round(value / divisor);\n }\n\n return result;\n}\n\n/**\n * Compact token count: `1523` → `1.5k`, `2_400_000` → `2.4M`.\n */\nfunction compactTokens(value: number): string {\n const abs = Math.abs(value);\n if (abs >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n if (abs >= 1_000) return `${(value / 1_000).toFixed(1)}k`;\n return String(Math.round(value));\n}\n\n/**\n * Human-readable one-liner, e.g. `2 in / 4 out · 15.5k cache · ~$0.1607`.\n *\n * Segments whose data is missing are omitted entirely — callers can rely on an\n * empty string meaning \"no metrics at all\" and skip the line altogether rather\n * than printing zeros or NaN.\n */\nexport function formatTokens(usage: ClaudeUsage | null | undefined): string {\n if (!usage) return '';\n\n const segments: string[] = [];\n\n const io: string[] = [];\n if (usage.inputTokens !== undefined) io.push(`${compactTokens(usage.inputTokens)} in`);\n if (usage.outputTokens !== undefined) io.push(`${compactTokens(usage.outputTokens)} out`);\n if (io.length > 0) segments.push(io.join(' / '));\n\n const cache =\n usage.cacheReadTokens !== undefined || usage.cacheCreationTokens !== undefined\n ? (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0)\n : undefined;\n if (cache !== undefined) segments.push(`${compactTokens(cache)} cache`);\n\n if (usage.costUsd !== undefined) segments.push(`~$${usage.costUsd.toFixed(4)}`);\n\n return segments.join(' · ');\n}\n\n/**\n * True when the usage carries at least one reported field. Handy for deciding\n * whether a metrics line or event is worth emitting at all.\n */\nexport function hasUsageData(usage: ClaudeUsage | null | undefined): boolean {\n if (!usage) return false;\n return USAGE_FIELDS.some((field) => usage[field] !== undefined);\n}\n","let _verbose = false;\n\nexport function setVerbose(enabled: boolean): void {\n _verbose = enabled;\n}\n\nexport function isVerbose(): boolean {\n return _verbose;\n}\n\nlet _onOutput: ((line: string) => void) | undefined;\n\n/**\n * Set a global output callback for routing verbose output (e.g., through listr2 task.output).\n * Pass undefined to clear and fall back to direct stderr writes.\n */\nexport function setOutputCallback(callback: ((line: string) => void) | undefined): void {\n _onOutput = callback;\n}\n\nexport function getOutputCallback(): ((line: string) => void) | undefined {\n return _onOutput;\n}\n\nlet _onStoryUpdate: ((stories: import('../types.js').UserStory[]) => void) | undefined;\n\n/**\n * Set a global callback for story progress updates during engine execution.\n * Called after each iteration with the latest user stories state.\n * Pass undefined to clear.\n */\nexport function setStoryUpdateCallback(\n callback: ((stories: import('../types.js').UserStory[]) => void) | undefined,\n): void {\n _onStoryUpdate = callback;\n}\n\nexport function getStoryUpdateCallback():\n | ((stories: import('../types.js').UserStory[]) => void)\n | undefined {\n return _onStoryUpdate;\n}\n\nlet _globalTimeout: number | undefined;\n\nexport function setGlobalTimeout(ms: number): void {\n _globalTimeout = ms;\n}\n\nexport function getGlobalTimeout(): number | undefined {\n return _globalTimeout;\n}\n","import chalk from 'chalk';\nimport ora, { type Ora } from 'ora';\nimport { getSessionPublisher } from '../core/session-publisher.js';\nimport type { SessionLogLevel } from '../core/session-state.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getOutputCallback } from '../core/verbose.js';\n\n/**\n * Detect if unicode output is supported.\n */\nexport function useUnicode(): boolean {\n if (process.env.NO_COLOR != null && process.env.NO_COLOR !== '') return false;\n if (!process.stdout.isTTY) return false;\n return true;\n}\n\n/**\n * Detect if color output is supported.\n */\nexport function useColor(): boolean {\n if (process.env.NO_COLOR != null && process.env.NO_COLOR !== '') return false;\n if (!process.stdout.isTTY) return false;\n return true;\n}\n\nexport interface Icons {\n success: string;\n fail: string;\n pending: string;\n retry: string;\n warn: string;\n start: string;\n end: string;\n notReached: string;\n tool: string;\n connector: string;\n}\n\nexport function getIcons(): Icons {\n if (useUnicode()) {\n return {\n success: '\\u2713',\n fail: '\\u2717',\n pending: '\\u23F3',\n retry: '\\u21BB',\n warn: '\\u26A0',\n start: '\\u25B6',\n end: '\\u25A0',\n notReached: '\\u25CB',\n tool: '\\u25B8',\n connector: '\\u2502',\n };\n }\n return {\n success: '[OK]',\n fail: '[FAIL]',\n pending: '[...]',\n retry: '[RETRY]',\n warn: '[WARN]',\n start: '[START]',\n end: '[END]',\n notReached: '[ ]',\n tool: '>',\n connector: '|',\n };\n}\n\n/**\n * Get the terminal width.\n */\nexport function getTermWidth(): number {\n return process.stdout.columns ?? 80;\n}\n\n/**\n * Emit a line of output. Routes through the global output callback when\n * running inside a listr2 task context, otherwise falls back to console.log.\n *\n * Also forwards the line to the session publisher with its structured level;\n * ANSI codes are stripped by the reducer, so terminal output stays untouched.\n */\nfunction emit(line: string, level: SessionLogLevel = 'info'): void {\n getSessionPublisher().publish({ type: 'log', at: isoNow(), level, message: line });\n const cb = getOutputCallback();\n if (cb) {\n cb(line);\n } else {\n console.log(line);\n }\n}\n\nexport function printSuccess(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.green(`${icons.success} ${message}`));\n } else {\n emit(`${icons.success} ${message}`);\n }\n}\n\nexport function printError(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.red(`${icons.fail} ${message}`), 'error');\n } else {\n emit(`${icons.fail} ${message}`, 'error');\n }\n}\n\nexport function printWarning(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.yellow(`${icons.warn} ${message}`), 'warn');\n } else {\n emit(`${icons.warn} ${message}`, 'warn');\n }\n}\n\nexport function printRetry(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.yellow(`${icons.retry} ${message}`), 'warn');\n } else {\n emit(`${icons.retry} ${message}`, 'warn');\n }\n}\n\nexport function printInfo(message: string): void {\n const icons = getIcons();\n if (useColor()) {\n emit(chalk.blue(`${icons.start} ${message}`));\n } else {\n emit(`${icons.start} ${message}`);\n }\n}\n\n/**\n * Create an ora spinner with consistent styling.\n */\nexport function createSpinner(message: string): Ora {\n return ora({\n text: message,\n color: 'blue',\n spinner: 'dots',\n });\n}\n\n/**\n * Format a duration in seconds to a human-readable string.\n */\nexport function formatDuration(totalSeconds: number): string {\n const hours = Math.floor(totalSeconds / 3600);\n const mins = Math.floor((totalSeconds % 3600) / 60);\n const secs = totalSeconds % 60;\n\n if (hours > 0) {\n return `${hours}h ${mins}m ${secs}s`;\n }\n if (mins > 0) {\n return `${mins}m ${secs}s`;\n }\n return `${secs}s`;\n}\n\n/**\n * Timer that calls a callback every second with the formatted elapsed time.\n */\nexport class ElapsedTimer {\n private startTime: number;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private cleanup = () => this.stop();\n\n constructor(private onTick: (elapsed: string) => void) {\n this.startTime = Date.now();\n }\n\n start(): this {\n this.startTime = Date.now();\n this.intervalId = setInterval(() => {\n const seconds = Math.floor((Date.now() - this.startTime) / 1000);\n this.onTick(formatDuration(seconds));\n }, 1_000);\n // Prevent the timer from keeping the process alive\n if (this.intervalId && typeof this.intervalId === 'object' && 'unref' in this.intervalId) {\n this.intervalId.unref();\n }\n process.on('exit', this.cleanup);\n return this;\n }\n\n getElapsedSeconds(): number {\n return Math.floor((Date.now() - this.startTime) / 1000);\n }\n\n stop(): number {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n process.removeListener('exit', this.cleanup);\n return this.getElapsedSeconds();\n }\n}\n"],"mappings":"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","names":["rename","writeFile"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/prompt-resolver.ts","../src/core/session-metrics.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Resolve the absolute path to a directory shipped at the package root\n * (e.g. 'prompts', 'web/public'). Works from both source (src/core/) and\n * compiled (dist/) locations by walking up the directory tree until the\n * named folder is found. Returns null when it cannot be located.\n *\n * `startDir` is injectable for tests; it defaults to this module's directory.\n */\nexport function resolvePackageDir(name: string, startDir?: string): string | null {\n let dir = startDir ?? dirname(fileURLToPath(import.meta.url));\n\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, name);\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n\n return null;\n}\n\nfunction getPromptsDir(): string {\n const dir = resolvePackageDir('prompts');\n if (dir === null) {\n throw new Error(\n 'Could not locate the prompts/ directory. Ensure the package is installed correctly.',\n );\n }\n return dir;\n}\n\n/**\n * Load a prompt template by name from the package's prompts/ directory.\n *\n * @param name - Prompt name without extension (e.g., 'execute', 'analyze')\n * @returns The raw template content with placeholders intact\n */\nexport async function loadPrompt(name: string): Promise<string> {\n const promptsDir = getPromptsDir();\n const filePath = join(promptsDir, `${name}.md`);\n\n try {\n return await readFile(filePath, 'utf-8');\n } catch {\n throw new Error(`Prompt template not found: ${filePath}`);\n }\n}\n\n/**\n * Replace placeholders in a prompt template with actual values.\n *\n * Placeholders use the format __KEY__ (e.g., __ISSUE_NUMBER__, __PRD_FILE__).\n */\nexport function applyPlaceholders(template: string, vars: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(vars)) {\n result = result.replaceAll(key, value);\n }\n return result;\n}\n","import { type ClaudeUsage, hasUsageData, sumUsage } from './metrics.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { isoNow } from './state-manager.js';\n\n/**\n * Publication helper for the single-invocation phases (analyze, generate, prd,\n * plan, review, pr, pr-review).\n *\n * Each of them owns its own `runHeadless` call, so the metrics are published\n * from inside the command rather than from the `instrumentedRunners` wrapper in\n * run.ts: the wrapper only sees `() => Promise<void>` and never touches the\n * HeadlessResult. Publishing here also covers standalone invocations\n * (`issue-flow prd 42`), which never go through the pipeline wrapper at all.\n */\n\n/** Whole seconds elapsed since a `Date.now()` mark, never negative. */\nexport function elapsedSecondsSince(startedAtMs: number): number {\n return Math.max(0, Math.round((Date.now() - startedAtMs) / 1000));\n}\n\n/**\n * Process-owned totals, accumulated alongside every published event.\n *\n * The session snapshot cannot be the source of the terminal summary: with web\n * monitoring off the publisher is a `NullPublisher` and its snapshot stays\n * empty, so the numbers would silently vanish exactly in the default mode.\n * These counters live in the process and are therefore independent of any\n * publisher.\n *\n * Only phase- and iteration-scoped usage is recorded. Story metrics are a\n * rateio of an iteration already counted here — adding them would double the\n * totals, the same rule the reducer follows.\n */\nconst runTotals = {\n all: {} as ClaudeUsage,\n byPhase: new Map<string, ClaudeUsage>(),\n};\n\nfunction recordRunUsage(phase: string, usage: ClaudeUsage): void {\n runTotals.all = sumUsage(runTotals.all, usage);\n runTotals.byPhase.set(phase, sumUsage(runTotals.byPhase.get(phase), usage));\n}\n\n/** Everything this process has spent so far, across all phases. */\nexport function getRunUsageTotals(): ClaudeUsage {\n return { ...runTotals.all };\n}\n\n/** What a single phase has spent so far. Empty when the phase reported nothing. */\nexport function getPhaseUsageTotals(phase: string): ClaudeUsage {\n return { ...(runTotals.byPhase.get(phase) ?? {}) };\n}\n\n/** Test seam: the counters are module state and must be cleared between runs. */\nexport function resetRunUsageTotals(): void {\n runTotals.all = {};\n runTotals.byPhase.clear();\n}\n\n/**\n * Publish one `metrics:update` event of scope `phase` for a headless\n * invocation.\n *\n * A no-op when the CLI reported no usage at all: an event with every field\n * undefined would bump the snapshot version without adding information, and the\n * reducer would leave every counter at null anyway.\n *\n * A command with more than one invocation (the retrying phases, or `review`\n * inside a correction cycle) calls this once per invocation — the reducer's\n * summing is what turns them into the phase total.\n *\n * Never throws and never returns a value: publishing metrics must not be able\n * to change a command's exit code.\n */\nexport function publishPhaseMetrics(\n phase: string,\n usage: ClaudeUsage | null | undefined,\n startedAtMs?: number,\n): void {\n if (usage == null || !hasUsageData(usage)) return;\n\n recordRunUsage(phase, usage);\n\n getSessionPublisher().publish({\n type: 'metrics:update',\n at: isoNow(),\n scope: 'phase',\n phase,\n ...usage,\n // Informational only: a phase's durationSeconds keeps coming exclusively\n // from phase:start/phase:end, so the reducer ignores this for scope 'phase'.\n durationSeconds: startedAtMs === undefined ? undefined : elapsedSecondsSince(startedAtMs),\n });\n}\n\n/** The execute loop is the only producer of iteration- and story-scoped metrics. */\nexport const EXECUTE_PHASE = 'execute';\n\n/**\n * Publish one `metrics:update` event of scope `iteration` for a pass of the\n * execute loop.\n *\n * Lands on the `execute` phase and on the issue-wide aggregate, exactly like a\n * phase-scoped event: an iteration *is* a slice of the execute phase. Failed\n * iterations (transient or fatal) publish too — those tokens were spent.\n *\n * A no-op when the CLI reported no usage. Never throws.\n */\nexport function publishIterationMetrics(\n iteration: number,\n usage: ClaudeUsage | null | undefined,\n durationSeconds?: number,\n): void {\n if (usage == null || !hasUsageData(usage)) return;\n\n recordRunUsage(EXECUTE_PHASE, usage);\n\n getSessionPublisher().publish({\n type: 'metrics:update',\n at: isoNow(),\n scope: 'iteration',\n phase: EXECUTE_PHASE,\n iteration,\n ...usage,\n // Informational only: the reducer keeps a phase's durationSeconds coming\n // exclusively from phase:start/phase:end.\n durationSeconds,\n });\n}\n\n/**\n * Publish one `metrics:update` event of scope `story`.\n *\n * The values are the rateio of the iteration that completed the story, so the\n * reducer deliberately keeps them off the phase and the aggregate — the\n * iteration event of the same cycle already counted them there.\n *\n * A no-op when there is neither usage nor a duration to report. Never throws.\n */\nexport function publishStoryMetrics(\n storyId: string,\n usage: ClaudeUsage | null | undefined,\n durationSeconds?: number,\n): void {\n if (!hasUsageData(usage) && durationSeconds === undefined) return;\n\n getSessionPublisher().publish({\n type: 'metrics:update',\n at: isoNow(),\n scope: 'story',\n storyId,\n ...(usage ?? {}),\n durationSeconds,\n });\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAUvB,SAAS,kBAAkB,MAAc,UAAkC;AAChF,MAAI,MAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE5D,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,KAAK,KAAK,IAAI;AAChC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,GAAG;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,SAAS,gBAAwB;AAC/B,QAAM,MAAM,kBAAkB,SAAS;AACvC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQA,eAAsB,WAAW,MAA+B;AAC9D,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,KAAK,YAAY,GAAG,IAAI,KAAK;AAE9C,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,QAAQ;AACN,UAAM,IAAI,MAAM,8BAA8B,QAAQ,EAAE;AAAA,EAC1D;AACF;AAOO,SAAS,kBAAkB,UAAkB,MAAsC;AACxF,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,KAAK,KAAK;AAAA,EACvC;AACA,SAAO;AACT;;;ACjDO,SAAS,oBAAoB,aAA6B;AAC/D,SAAO,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,IAAI,IAAI,eAAe,GAAI,CAAC;AAClE;AAeA,IAAM,YAAY;AAAA,EAChB,KAAK,CAAC;AAAA,EACN,SAAS,oBAAI,IAAyB;AACxC;AAEA,SAAS,eAAe,OAAe,OAA0B;AAC/D,YAAU,MAAM,SAAS,UAAU,KAAK,KAAK;AAC7C,YAAU,QAAQ,IAAI,OAAO,SAAS,UAAU,QAAQ,IAAI,KAAK,GAAG,KAAK,CAAC;AAC5E;AAGO,SAAS,oBAAiC;AAC/C,SAAO,EAAE,GAAG,UAAU,IAAI;AAC5B;AAGO,SAAS,oBAAoB,OAA4B;AAC9D,SAAO,EAAE,GAAI,UAAU,QAAQ,IAAI,KAAK,KAAK,CAAC,EAAG;AACnD;AAuBO,SAAS,oBACd,OACA,OACA,aACM;AACN,MAAI,SAAS,QAAQ,CAAC,aAAa,KAAK,EAAG;AAE3C,iBAAe,OAAO,KAAK;AAE3B,sBAAoB,EAAE,QAAQ;AAAA,IAC5B,MAAM;AAAA,IACN,IAAI,OAAO;AAAA,IACX,OAAO;AAAA,IACP;AAAA,IACA,GAAG;AAAA;AAAA;AAAA,IAGH,iBAAiB,gBAAgB,SAAY,SAAY,oBAAoB,WAAW;AAAA,EAC1F,CAAC;AACH;AAGO,IAAM,gBAAgB;AAYtB,SAAS,wBACd,WACA,OACA,iBACM;AACN,MAAI,SAAS,QAAQ,CAAC,aAAa,KAAK,EAAG;AAE3C,iBAAe,eAAe,KAAK;AAEnC,sBAAoB,EAAE,QAAQ;AAAA,IAC5B,MAAM;AAAA,IACN,IAAI,OAAO;AAAA,IACX,OAAO;AAAA,IACP,OAAO;AAAA,IACP;AAAA,IACA,GAAG;AAAA;AAAA;AAAA,IAGH;AAAA,EACF,CAAC;AACH;AAWO,SAAS,oBACd,SACA,OACA,iBACM;AACN,MAAI,CAAC,aAAa,KAAK,KAAK,oBAAoB,OAAW;AAE3D,sBAAoB,EAAE,QAAQ;AAAA,IAC5B,MAAM;AAAA,IACN,IAAI,OAAO;AAAA,IACX,OAAO;AAAA,IACP;AAAA,IACA,GAAI,SAAS,CAAC;AAAA,IACd;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/execute-BNBA77WJ.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
runExecute
|
|
4
|
-
} from "./chunk-BMRBQ23Q.js";
|
|
5
|
-
import "./chunk-HDV3YDTG.js";
|
|
6
|
-
import "./chunk-QLVAEDVD.js";
|
|
7
|
-
import "./chunk-I7FTA5FI.js";
|
|
8
|
-
import "./chunk-5NLLEWJD.js";
|
|
9
|
-
import "./chunk-5Q2TMW7K.js";
|
|
10
|
-
export {
|
|
11
|
-
runExecute
|
|
12
|
-
};
|
|
13
|
-
//# sourceMappingURL=execute-BNBA77WJ.js.map
|
package/dist/init-IF72GR7O.js
DELETED
package/dist/plan-FITZZXZQ.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
runPlan
|
|
4
|
-
} from "./chunk-5IRBHUYO.js";
|
|
5
|
-
import "./chunk-D56VWHSM.js";
|
|
6
|
-
import "./chunk-CD7BMWXW.js";
|
|
7
|
-
import "./chunk-HDV3YDTG.js";
|
|
8
|
-
import "./chunk-DF7SUHT5.js";
|
|
9
|
-
import "./chunk-I7FTA5FI.js";
|
|
10
|
-
import "./chunk-5NLLEWJD.js";
|
|
11
|
-
import "./chunk-5Q2TMW7K.js";
|
|
12
|
-
export {
|
|
13
|
-
runPlan
|
|
14
|
-
};
|
|
15
|
-
//# sourceMappingURL=plan-FITZZXZQ.js.map
|