issue-flow 0.17.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -100
- package/dist/agent-EWKA7357.js +32 -0
- package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
- package/dist/analyze-Z7ODC27F.js.map +1 -0
- package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
- package/dist/apply-VNSWQS6O.js.map +1 -0
- package/dist/artifacts-AMJQJFFV.js +82 -0
- package/dist/artifacts-AMJQJFFV.js.map +1 -0
- package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.js} +42 -42
- package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
- package/dist/chunk-2EYLOS5I.js +99 -0
- package/dist/chunk-2EYLOS5I.js.map +1 -0
- package/dist/{chunk-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
- package/dist/chunk-2EZW4X57.js.map +1 -0
- package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
- package/dist/chunk-2Q6PG3MT.js.map +1 -0
- package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
- package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
- package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
- package/dist/chunk-3JD25HHJ.js +278 -0
- package/dist/chunk-3JD25HHJ.js.map +1 -0
- package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
- package/dist/chunk-46S7APLQ.js.map +1 -0
- package/dist/chunk-6AOZCWN2.js +21 -0
- package/dist/chunk-6AOZCWN2.js.map +1 -0
- package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
- package/dist/chunk-7MJGYPHF.js +1571 -0
- package/dist/chunk-7MJGYPHF.js.map +1 -0
- package/dist/chunk-7ZZUSMBJ.js +35 -0
- package/dist/chunk-7ZZUSMBJ.js.map +1 -0
- package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
- package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
- package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
- package/dist/chunk-E2H3JV2E.js.map +1 -0
- package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
- package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
- package/dist/chunk-F2NFJ27Z.js.map +1 -0
- package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
- package/dist/chunk-GD7JZMSJ.js.map +1 -0
- package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
- package/dist/chunk-HEHHQU4G.js.map +1 -0
- package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
- package/dist/chunk-HHXWAWU6.js.map +1 -0
- package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
- package/dist/chunk-HWWB22HC.js.map +1 -0
- package/dist/chunk-IMB6FA2F.js +673 -0
- package/dist/chunk-IMB6FA2F.js.map +1 -0
- package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
- package/dist/chunk-IQYXXFRC.js.map +1 -0
- package/dist/chunk-J6VJ5M2U.js +1201 -0
- package/dist/chunk-J6VJ5M2U.js.map +1 -0
- package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
- package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
- package/dist/chunk-LRWIP7QS.js.map +1 -0
- package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
- package/dist/chunk-LYC57OXF.js.map +1 -0
- package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
- package/dist/chunk-MAJ6QDRL.js.map +1 -0
- package/dist/{chunk-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
- package/dist/chunk-MHA7Q3KU.js.map +1 -0
- package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
- package/dist/chunk-MQTVMB7W.js.map +1 -0
- package/dist/chunk-NP47HRX2.js +136 -0
- package/dist/chunk-NP47HRX2.js.map +1 -0
- package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
- package/dist/chunk-NYDAHE3Y.js.map +1 -0
- package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
- package/dist/chunk-P7ZYOC23.js.map +1 -0
- package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
- package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
- package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
- package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
- package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
- package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
- package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
- package/dist/chunk-TJVX42KY.js.map +1 -0
- package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
- package/dist/chunk-UDRAD7MF.js.map +1 -0
- package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
- package/dist/chunk-UHDXXLYH.js.map +1 -0
- package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
- package/dist/chunk-UPAAZU4W.js.map +1 -0
- package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
- package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
- package/dist/chunk-WB2FVIUS.js.map +1 -0
- package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
- package/dist/chunk-WPHC6JBR.js.map +1 -0
- package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
- package/dist/chunk-XHCP6ZNO.js.map +1 -0
- package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
- package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
- package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
- package/dist/chunk-XNK5UN2D.js.map +1 -0
- package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
- package/dist/chunk-XQLNZW7I.js.map +1 -0
- package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
- package/dist/chunk-ZAYATXJD.js.map +1 -0
- package/dist/cli.js +128 -60
- package/dist/cli.js.map +1 -1
- package/dist/compat-YPDZB34U.js +24 -0
- package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
- package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
- package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
- package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
- package/dist/db-5NU77PJA.js +264 -0
- package/dist/db-5NU77PJA.js.map +1 -0
- package/dist/diagnostics-UCTS2VUF.js +25 -0
- package/dist/execute-R7AVOLNG.js +38 -0
- package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
- package/dist/generate-O7GUXLN4.js.map +1 -0
- package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
- package/dist/history-6GSURU3H.js +101 -0
- package/dist/history-6GSURU3H.js.map +1 -0
- package/dist/init-DD6DBRC5.js +28 -0
- package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
- package/dist/operations-HU5ZFEBA.js.map +1 -0
- package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
- package/dist/plan-XR6B7EM3.js +38 -0
- package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
- package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
- package/dist/pr-OSCNJCOO.js +42 -0
- package/dist/pr-review-IKIYHFBO.js +33 -0
- package/dist/prd-HFDZJJQR.js +37 -0
- package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
- package/dist/ps-AKRHGPSR.js.map +1 -0
- package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
- package/dist/registry-DWRKD65A.js +17 -0
- package/dist/repository-TABBRW2C.js +80 -0
- package/dist/resume-GDXKK5XP.js +278 -0
- package/dist/resume-GDXKK5XP.js.map +1 -0
- package/dist/review-JJHMQA2O.js +38 -0
- package/dist/routing-FPNUVE7T.js +35 -0
- package/dist/run-RLCURLLL.js +61 -0
- package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
- package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
- package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
- package/dist/state-manager-IGNTD4AB.js.map +1 -0
- package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
- package/dist/usage-XW27AL5K.js.map +1 -0
- package/dist/web-NCDBVZ5Y.js +205 -0
- package/dist/web-NCDBVZ5Y.js.map +1 -0
- package/package.json +20 -7
- package/prompts/analyze.md +16 -19
- package/prompts/execute.md +73 -144
- package/prompts/generate.md +23 -10
- package/prompts/plan.md +32 -60
- package/prompts/pr-review.md +58 -40
- package/prompts/pr.md +50 -16
- package/prompts/prd.md +26 -19
- package/prompts/review.md +41 -18
- package/web/AGENTS.md +115 -7
- package/web/public/app.css +393 -245
- package/web/public/app.js +212 -143
- package/web/public/index.html +106 -78
- package/dist/agent-TB6W7PT7.js +0 -30
- package/dist/analyze-6M3KEOX7.js.map +0 -1
- package/dist/apply-ADB3ZK7W.js.map +0 -1
- package/dist/chunk-6YQT3SSM.js.map +0 -1
- package/dist/chunk-A4H7P7T7.js.map +0 -1
- package/dist/chunk-ANI2NTR4.js.map +0 -1
- package/dist/chunk-CRGIQFUQ.js.map +0 -1
- package/dist/chunk-D6ZBGJ3L.js.map +0 -1
- package/dist/chunk-DV7HUC4Z.js.map +0 -1
- package/dist/chunk-E6KXCV3T.js.map +0 -1
- package/dist/chunk-HAKDLR6N.js.map +0 -1
- package/dist/chunk-HJBF4WD2.js.map +0 -1
- package/dist/chunk-HLAPOUEM.js.map +0 -1
- package/dist/chunk-HY5HLFDU.js.map +0 -1
- package/dist/chunk-ICM463EO.js.map +0 -1
- package/dist/chunk-ISZ54SVE.js.map +0 -1
- package/dist/chunk-KZ5EY5XX.js.map +0 -1
- package/dist/chunk-KZXQWC7U.js.map +0 -1
- package/dist/chunk-MI5TREN3.js.map +0 -1
- package/dist/chunk-QMIQR7EE.js.map +0 -1
- package/dist/chunk-RCZJQUTC.js.map +0 -1
- package/dist/chunk-RNFUVKC3.js.map +0 -1
- package/dist/chunk-SGTTR4XW.js.map +0 -1
- package/dist/chunk-SOM4QA6C.js.map +0 -1
- package/dist/chunk-UK4E2FUA.js.map +0 -1
- package/dist/chunk-UWDCIFTP.js.map +0 -1
- package/dist/chunk-VE3IYNDN.js.map +0 -1
- package/dist/chunk-WCCZJL7W.js +0 -141
- package/dist/chunk-WCCZJL7W.js.map +0 -1
- package/dist/chunk-XEYH6ZZI.js +0 -289
- package/dist/chunk-XEYH6ZZI.js.map +0 -1
- package/dist/chunk-XZUEFTUP.js +0 -243
- package/dist/chunk-XZUEFTUP.js.map +0 -1
- package/dist/chunk-YII4U7ZD.js.map +0 -1
- package/dist/chunk-YVPZUN6X.js.map +0 -1
- package/dist/chunk-Z5O2XBWA.js.map +0 -1
- package/dist/diagnostics-HTCWF7F4.js +0 -23
- package/dist/execute-QL3ZQIAP.js +0 -37
- package/dist/generate-CX6KNHZS.js.map +0 -1
- package/dist/init-PDVQDRS6.js +0 -24
- package/dist/operations-CAKIT2BO.js.map +0 -1
- package/dist/plan-PRORGS3L.js +0 -34
- package/dist/pr-27RMQI5F.js +0 -39
- package/dist/pr-review-VNESMAMT.js +0 -32
- package/dist/prd-ZNC4T5BS.js +0 -33
- package/dist/ps-76L6ZAZL.js.map +0 -1
- package/dist/registry-XLHHYLTR.js +0 -15
- package/dist/resume-VLWIE264.js +0 -244
- package/dist/resume-VLWIE264.js.map +0 -1
- package/dist/review-7CREJDKB.js +0 -35
- package/dist/routing-VCDYHSXA.js +0 -33
- package/dist/run-3MPORLF5.js +0 -56
- package/dist/usage-U7QNGCPK.js.map +0 -1
- package/dist/web-HUNDHCJ4.js +0 -172
- package/dist/web-HUNDHCJ4.js.map +0 -1
- /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
- /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.js.map} +0 -0
- /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
- /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
- /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
- /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
- /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
- /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
- /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
- /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
- /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
- /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
- /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
- /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
- /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
- /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
- /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
- /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
- /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
- /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
- /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
- /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
- /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
- /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
- /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
- /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
- /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
- /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
- /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
- /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
package/web/public/index.html
CHANGED
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
</head>
|
|
31
31
|
<body>
|
|
32
32
|
<div id="banner-disconnected" class="banner" role="alert" hidden>
|
|
33
|
-
Desconectado do servidor
|
|
33
|
+
Desconectado do servidor. Tentando reconectar…
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
36
|
<main>
|
|
37
37
|
<div id="view-dashboard" class="view" hidden>
|
|
38
38
|
<header class="card header">
|
|
39
39
|
<div class="header-main">
|
|
40
|
-
<h1>
|
|
41
|
-
|
|
40
|
+
<h1>Execuções ativas</h1>
|
|
41
|
+
<p class="header-meta">
|
|
42
|
+
<span id="dashboard-summary" class="muted"></span>
|
|
42
43
|
<span id="app-version-dashboard" class="app-version" hidden></span>
|
|
43
|
-
</
|
|
44
|
-
<p class="muted">Execuções ativas</p>
|
|
44
|
+
</p>
|
|
45
45
|
</div>
|
|
46
46
|
<div class="header-side">
|
|
47
47
|
<label class="refresh-control">
|
|
@@ -70,12 +70,16 @@
|
|
|
70
70
|
<button type="button" id="back-to-dashboard" class="back-link" hidden>
|
|
71
71
|
← Todas as execuções
|
|
72
72
|
</button>
|
|
73
|
+
<!-- A linha mais visível da tela é a execução, não a marca: o produto
|
|
74
|
+
identifica-se pelo <title> do documento. Ver web/AGENTS.md. -->
|
|
73
75
|
<h1>
|
|
74
|
-
<span class="brand">issue-flow</span>
|
|
75
|
-
<span id="app-version" class="app-version" hidden></span>
|
|
76
76
|
<a id="issue-link" class="issue-link" target="_blank" rel="noopener" hidden>#—</a>
|
|
77
|
+
<span id="issue-headline">Nenhuma execução ativa</span>
|
|
77
78
|
</h1>
|
|
78
|
-
<p
|
|
79
|
+
<p class="header-meta">
|
|
80
|
+
<span id="branch-line" class="muted mono"></span>
|
|
81
|
+
<span id="app-version" class="app-version" hidden></span>
|
|
82
|
+
</p>
|
|
79
83
|
</div>
|
|
80
84
|
<div class="header-side">
|
|
81
85
|
<span id="status-badge" class="badge status-idle">aguardando</span>
|
|
@@ -105,99 +109,124 @@
|
|
|
105
109
|
|
|
106
110
|
<div class="tabs" role="tablist" aria-label="Visualizações do painel">
|
|
107
111
|
<button type="button" class="tab" role="tab" id="tab-execution"
|
|
108
|
-
aria-controls="panel-execution" aria-selected="true">Execução</button>
|
|
112
|
+
aria-controls="panel-execution" aria-selected="true" tabindex="0">Execução</button>
|
|
109
113
|
<button type="button" class="tab" role="tab" id="tab-kanban"
|
|
110
|
-
aria-controls="panel-kanban" aria-selected="false">Kanban</button>
|
|
114
|
+
aria-controls="panel-kanban" aria-selected="false" tabindex="-1">Kanban</button>
|
|
111
115
|
<button type="button" class="tab" role="tab" id="tab-history"
|
|
112
|
-
aria-controls="panel-history" aria-selected="false">Histórico</button>
|
|
116
|
+
aria-controls="panel-history" aria-selected="false" tabindex="-1">Histórico</button>
|
|
113
117
|
</div>
|
|
114
118
|
|
|
119
|
+
<!-- Aba Execução em quatro blocos, na ordem em que a pergunta aparece:
|
|
120
|
+
o que está acontecendo agora, sobre o quê, quanto já andou e o que
|
|
121
|
+
saiu disso. Cada bloco é UM cartão; o que antes era um cartão por
|
|
122
|
+
assunto virou uma sub-seção com <h3> e sem borda própria. -->
|
|
115
123
|
<div class="tab-panel" id="panel-execution" role="tabpanel" aria-labelledby="tab-execution" tabindex="0">
|
|
116
|
-
<div class="columns">
|
|
117
|
-
<section class="card">
|
|
118
|
-
<h2>Resumo da issue</h2>
|
|
119
|
-
<div id="issue-summary"></div>
|
|
120
|
-
</section>
|
|
121
|
-
<section class="card">
|
|
122
|
-
<h2>Repositório</h2>
|
|
123
|
-
<div id="repository"></div>
|
|
124
|
-
</section>
|
|
125
|
-
</div>
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
<!-- Estado agora. Primeiro de propósito: é o único bloco que precisa ser
|
|
126
|
+
legível sem rolagem. Não acrescente conteúdo aqui sem checar isso. -->
|
|
127
|
+
<section class="card block block-now">
|
|
128
|
+
<h2>Estado agora</h2>
|
|
129
|
+
|
|
130
|
+
<div aria-live="polite">
|
|
131
|
+
<div class="progress-row">
|
|
132
|
+
<progress id="progress-bar" max="100" value="0"></progress>
|
|
133
|
+
<span id="progress-percent" class="progress-percent">0%</span>
|
|
134
|
+
</div>
|
|
135
|
+
<p id="progress-counters" class="muted"></p>
|
|
132
136
|
</div>
|
|
133
|
-
<p id="progress-counters" class="muted"></p>
|
|
134
|
-
</section>
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
<div class="columns">
|
|
139
|
+
<div class="block-part">
|
|
140
|
+
<h3>Executando agora</h3>
|
|
141
|
+
<div id="now" aria-live="polite"></div>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="block-part">
|
|
144
|
+
<h3>Resiliência</h3>
|
|
145
|
+
<div id="resilience" aria-live="polite"></div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
140
148
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
<p class="next-steps-line">
|
|
150
|
+
<span class="next-steps-label">Próximos passos:</span>
|
|
151
|
+
<span id="next-steps"></span>
|
|
152
|
+
</p>
|
|
144
153
|
</section>
|
|
145
154
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
155
|
+
<!-- Contexto. Estável durante a execução, então densidade menor: é
|
|
156
|
+
referência para quando o estado acima não bastar. -->
|
|
157
|
+
<section class="card block block-context">
|
|
158
|
+
<h2>Contexto</h2>
|
|
159
|
+
|
|
160
|
+
<div class="columns">
|
|
161
|
+
<div class="block-part">
|
|
162
|
+
<h3>Issue</h3>
|
|
163
|
+
<div id="issue-summary"></div>
|
|
164
|
+
</div>
|
|
165
|
+
<div class="block-part">
|
|
166
|
+
<h3>Repositório</h3>
|
|
167
|
+
<div id="repository"></div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div class="block-part">
|
|
172
|
+
<h3>
|
|
173
|
+
Harnesses e configuração efetiva
|
|
174
|
+
<span class="muted block-hint">versões, preferências futuras e overrides desta execução</span>
|
|
175
|
+
</h3>
|
|
176
|
+
<div id="configuration"></div>
|
|
150
177
|
</div>
|
|
151
|
-
<div id="configuration"></div>
|
|
152
178
|
</section>
|
|
153
179
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
180
|
+
<!-- Andamento: o caminho já percorrido, fase a fase e story a story. -->
|
|
181
|
+
<section class="card block block-progress">
|
|
182
|
+
<h2>Andamento</h2>
|
|
183
|
+
|
|
184
|
+
<div class="block-part">
|
|
185
|
+
<h3>Fases</h3>
|
|
157
186
|
<ol id="phases" class="plain-list"></ol>
|
|
158
|
-
</
|
|
159
|
-
<section class="card">
|
|
160
|
-
<h2>Próximos passos</h2>
|
|
161
|
-
<div id="next-steps"></div>
|
|
162
|
-
</section>
|
|
163
|
-
</div>
|
|
187
|
+
</div>
|
|
164
188
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
<div class="block-part">
|
|
190
|
+
<h3>User stories</h3>
|
|
191
|
+
<ol id="stories" class="plain-list"></ol>
|
|
192
|
+
</div>
|
|
168
193
|
</section>
|
|
169
194
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
<ul id="commits" class="plain-list"></ul>
|
|
174
|
-
</section>
|
|
175
|
-
<section class="card">
|
|
176
|
-
<h2>Pull requests</h2>
|
|
177
|
-
<ul id="pull-requests" class="plain-list"></ul>
|
|
178
|
-
</section>
|
|
179
|
-
</div>
|
|
195
|
+
<!-- Saída: o que a execução produziu no repositório e no log. -->
|
|
196
|
+
<section class="card block block-output">
|
|
197
|
+
<h2>Saída</h2>
|
|
180
198
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
<div class="columns">
|
|
200
|
+
<div class="block-part">
|
|
201
|
+
<h3>Commits</h3>
|
|
202
|
+
<ul id="commits" class="plain-list"></ul>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="block-part">
|
|
205
|
+
<h3>Pull requests</h3>
|
|
206
|
+
<ul id="pull-requests" class="plain-list"></ul>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="block-part">
|
|
211
|
+
<div class="section-head">
|
|
212
|
+
<h3>Logs recentes</h3>
|
|
213
|
+
<label class="log-filter-control">
|
|
214
|
+
Nível
|
|
215
|
+
<select id="log-filter" aria-label="Filtro por nível de log">
|
|
216
|
+
<option value="all">todos</option>
|
|
217
|
+
<option value="info">info</option>
|
|
218
|
+
<option value="warn">avisos</option>
|
|
219
|
+
<option value="error">erros</option>
|
|
220
|
+
</select>
|
|
221
|
+
</label>
|
|
222
|
+
</div>
|
|
223
|
+
<ol id="logs" class="plain-list logs"></ol>
|
|
193
224
|
</div>
|
|
194
|
-
<ol id="logs" class="plain-list logs"></ol>
|
|
195
225
|
</section>
|
|
196
226
|
</div>
|
|
197
227
|
|
|
198
228
|
<div class="tab-panel" id="panel-kanban" role="tabpanel" aria-labelledby="tab-kanban" tabindex="0" hidden>
|
|
199
229
|
<section class="card">
|
|
200
|
-
<h2>Kanban de user stories</h2>
|
|
201
230
|
<div id="kanban" class="kanban"></div>
|
|
202
231
|
</section>
|
|
203
232
|
</div>
|
|
@@ -205,7 +234,6 @@
|
|
|
205
234
|
<div class="tab-panel" id="panel-history" role="tabpanel" aria-labelledby="tab-history" tabindex="0" hidden>
|
|
206
235
|
<section class="card">
|
|
207
236
|
<div class="section-head">
|
|
208
|
-
<h2>Histórico da execução</h2>
|
|
209
237
|
<label class="log-filter-control">
|
|
210
238
|
Evento
|
|
211
239
|
<select id="history-filter" aria-label="Filtro do histórico">
|
|
@@ -229,7 +257,7 @@
|
|
|
229
257
|
<aside id="drawer" class="drawer" role="dialog" aria-modal="true"
|
|
230
258
|
aria-labelledby="drawer-title" tabindex="-1" hidden>
|
|
231
259
|
<div class="drawer-head">
|
|
232
|
-
<h2 id="drawer-title"
|
|
260
|
+
<h2 id="drawer-title"></h2>
|
|
233
261
|
<button type="button" id="drawer-close" class="drawer-close" aria-label="Fechar detalhes">✕</button>
|
|
234
262
|
</div>
|
|
235
263
|
<div id="drawer-body" class="drawer-body"></div>
|
package/dist/agent-TB6W7PT7.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
AGENT_COMMAND_SCHEMA_VERSION,
|
|
4
|
-
persistFirstAgentChoice,
|
|
5
|
-
runAgent,
|
|
6
|
-
runAgentUse,
|
|
7
|
-
writeAgentPreference
|
|
8
|
-
} from "./chunk-UWDCIFTP.js";
|
|
9
|
-
import "./chunk-HY5HLFDU.js";
|
|
10
|
-
import "./chunk-DV7HUC4Z.js";
|
|
11
|
-
import "./chunk-XZUEFTUP.js";
|
|
12
|
-
import "./chunk-QPGEFLMY.js";
|
|
13
|
-
import "./chunk-HAKDLR6N.js";
|
|
14
|
-
import "./chunk-YVPZUN6X.js";
|
|
15
|
-
import "./chunk-DUS5ZNKL.js";
|
|
16
|
-
import "./chunk-YII4U7ZD.js";
|
|
17
|
-
import "./chunk-PPZ3K4YM.js";
|
|
18
|
-
import "./chunk-SOM4QA6C.js";
|
|
19
|
-
import "./chunk-ICM463EO.js";
|
|
20
|
-
import "./chunk-XEYH6ZZI.js";
|
|
21
|
-
import "./chunk-P3LRUNC6.js";
|
|
22
|
-
import "./chunk-3R27MJ5R.js";
|
|
23
|
-
export {
|
|
24
|
-
AGENT_COMMAND_SCHEMA_VERSION,
|
|
25
|
-
persistFirstAgentChoice,
|
|
26
|
-
runAgent,
|
|
27
|
-
runAgentUse,
|
|
28
|
-
writeAgentPreference
|
|
29
|
-
};
|
|
30
|
-
//# sourceMappingURL=agent-TB6W7PT7.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/analyze.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printInfo, printSuccess } from '../ui/logger.js';\n\nexport async function runAnalyze(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const analysisPath = paths.analysisFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(paths.issueDir, { recursive: true });\n\n const template = await loadPrompt('analyze');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __ANALYSIS_PATH__: analysisPath,\n // Last: the Issue content is substituted in but never scanned again, so a\n // body that happens to contain a placeholder is left untouched.\n ...issuePlaceholders(resolution.resolved),\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 30,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n addDirs: [paths.issueDir],\n statusMessage: `Analyzing issue #${issueNumber}...`,\n phase: 'analyze',\n permission: 'read-only',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('analyze', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n printError(`Analysis failed: ${result.error}`);\n return 1;\n }\n\n // Verify the file was created\n try {\n const content = await readFile(analysisPath, 'utf-8');\n if (content.length < 10) {\n printError('Analysis file was created but appears empty');\n return 1;\n }\n } catch {\n // File wasn't created by headless — save the result as analysis\n printInfo('Headless did not create analysis file; saving output directly');\n await writeFile(analysisPath, result.result, 'utf-8');\n }\n\n // Update pipeline state\n const tasksPath = paths.tasksFile;\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.analyzeCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet — that's OK for standalone analyze\n }\n\n printSuccess(`Analysis saved to ${analysisPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,UAAU,iBAAiB;AAY3C,eAAsB,WAAW,OAAe,eAAgD;AAC9F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,eAAe,MAAM;AAE3B,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE/C,QAAM,WAAW,MAAM,WAAW,SAAS;AAC3C,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,kBAAkB;AAAA,IAClB,mBAAmB;AAAA;AAAA;AAAA,IAGnB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,SAAS,MAAM,YAAY;AAAA,IAC/B;AAAA,IACA,UAAU;AAAA,IACV,SAAS,iBAAiB,KAAK;AAAA;AAAA;AAAA,IAG/B,cAAc;AAAA,IACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,IACtD,SAAS,CAAC,MAAM,QAAQ;AAAA,IACxB,eAAe,oBAAoB,WAAW;AAAA,IAC9C,OAAO;AAAA,IACP,YAAY;AAAA,EACd,CAAC;AAED,sBAAoB,WAAW,OAAO,MAAM,aAAa,OAAO,OAAO,QAAQ;AAE/E,MAAI,CAAC,OAAO,SAAS;AACnB,eAAW,oBAAoB,OAAO,KAAK,EAAE;AAC7C,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,UAAU,MAAM,SAAS,cAAc,OAAO;AACpD,QAAI,QAAQ,SAAS,IAAI;AACvB,iBAAW,6CAA6C;AACxD,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,cAAU,+DAA+D;AACzE,UAAM,UAAU,cAAc,OAAO,QAAQ,OAAO;AAAA,EACtD;AAGA,QAAM,YAAY,MAAM;AACxB,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,mBAAmB;AACjC,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,qBAAqB,YAAY,EAAE;AAChD,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scaffold/apply.ts","../src/scaffold/plan.ts","../src/conventions/defaults.ts","../src/scaffold/assets.ts"],"sourcesContent":["import { access, mkdir, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { loadRepositoryPolicy } from '../policy/index.js';\nimport { getProjectRoot } from '../utils/git.js';\nimport { buildScaffoldPlan, type RepositoryState, type ScaffoldPlan } from './plan.js';\n\n/**\n * Turning a plan into files — and the one place that writes anything.\n *\n * Writing is deliberately dumber than planning: it creates what the plan marked\n * `create` and touches nothing else. Every judgement about whether a file should\n * exist was already made, from the resolved policy, in `plan.ts`.\n */\n\nexport interface ApplyResult {\n written: string[];\n /** Marked `create` but found on disk between planning and writing. */\n skipped: string[];\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Write the `create` actions of a plan.\n *\n * Re-checks existence immediately before each write. The plan is built from the\n * resolved policy, which is cached for the process, so a file created between\n * the two would otherwise be overwritten — and \"initialization is\n * non-destructive\" has to hold even against itself.\n */\nexport async function applyScaffoldPlan(plan: ScaffoldPlan): Promise<ApplyResult> {\n const written: string[] = [];\n const skipped: string[] = [];\n\n for (const item of plan.actions) {\n if (item.kind !== 'create' || item.content === undefined) continue;\n\n const absolute = join(plan.root, item.path);\n if (await exists(absolute)) {\n skipped.push(item.path);\n continue;\n }\n\n await mkdir(dirname(absolute), { recursive: true });\n await writeFile(absolute, item.content, 'utf-8');\n written.push(item.path);\n }\n\n return { written, skipped };\n}\n\nexport interface ResolveStateOptions {\n /** Repository root. Defaults to the git project root. */\n root?: string;\n /** Subdirectory the conventions apply to, for monorepos. */\n scope?: string | null;\n}\n\n/**\n * The repository state the planner reads, assembled from the policy layer.\n *\n * There is no second discovery here on purpose: initialization sees exactly the\n * repository every other flow sees, so it can never propose a convention that\n * discovery already found.\n */\nexport async function resolveRepositoryState(\n options: ResolveStateOptions = {},\n): Promise<RepositoryState> {\n const root = options.root ?? (await getProjectRoot());\n const policy = await loadRepositoryPolicy({ root, scope: options.scope ?? null });\n\n return {\n policy,\n projectName: basename(root),\n exists: (relPath: string) => exists(join(root, relPath)),\n };\n}\n\n/** Resolve the state and build the plan, without writing anything. */\nexport async function planRepositoryScaffold(\n options: ResolveStateOptions = {},\n): Promise<ScaffoldPlan> {\n return buildScaffoldPlan(await resolveRepositoryState(options));\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { DEFAULT_ISSUE_TYPES } from '../conventions/defaults.js';\nimport type { RepositoryPolicy } from '../policy/types.js';\nimport {\n renderAgentsMd,\n renderClaudeMd,\n renderConventionsDoc,\n renderIssueForm,\n renderIssueTemplateConfig,\n renderLabelsFile,\n renderPullRequestTemplate,\n type ScaffoldAsset,\n} from './assets.js';\n\n/**\n * Deciding what a repository is missing — the shared core behind both the CLI\n * and the initialization skill.\n *\n * The plan is produced from the **resolved policy**, never from a fresh scan:\n * initialization and every other flow therefore see the same repository, and a\n * convention that discovery already found can never be proposed a second time.\n *\n * Two properties matter more than the file list.\n *\n * **Non-destructive.** Nothing that exists is ever overwritten. A repository\n * that already declares a convention keeps it, even when it differs from the\n * default — the whole point of the tool is to adapt to the repository, and\n * \"initialize\" must not become a euphemism for \"replace\".\n *\n * **Idempotent.** Running it twice changes nothing the second time, because the\n * plan is computed from what is present rather than from what a previous run\n * did.\n */\n\n/** What initialization intends to do with one file. */\nexport type ScaffoldActionKind =\n /** The file does not exist and the repository has no equivalent. */\n | 'create'\n /** Something equivalent already exists; initialization stays out of the way. */\n | 'keep'\n /**\n * Present, but inconsistent with the convention it claims to follow. Reported,\n * never rewritten: the repository may be right and the tool wrong.\n */\n | 'review';\n\nexport interface ScaffoldAction {\n path: string;\n kind: ScaffoldActionKind;\n tier: ScaffoldAsset['tier'];\n /** Why this action was chosen, in one sentence, for the report. */\n reason: string;\n /** Content to write. Only present for `create`. */\n content?: string;\n}\n\nexport interface ScaffoldPlan {\n root: string;\n /** Every action, in a stable order. */\n actions: ScaffoldAction[];\n /** Notes that are not tied to a single file. */\n notes: string[];\n}\n\n/** Everything the plan needs to know about the repository's current state. */\nexport interface RepositoryState {\n policy: RepositoryPolicy;\n /** Whether a repository-relative path exists. */\n exists(relPath: string): Promise<boolean>;\n /** Name to head `AGENTS.md` with. */\n projectName: string;\n}\n\nfunction action(\n path: string,\n kind: ScaffoldActionKind,\n tier: ScaffoldAsset['tier'],\n reason: string,\n content?: string,\n): ScaffoldAction {\n return content === undefined\n ? { path, kind, tier, reason }\n : { path, kind, tier, reason, content };\n}\n\n/**\n * Whether a `CLAUDE.md` is already the one-line bridge the convention asks for.\n *\n * A file that merely *mentions* `AGENTS.md` in the middle of its own\n * instructions is not a bridge — it is a second source that happens to cite the\n * first. The test is therefore about size and shape, not about a substring.\n */\nexport function isClaudeBridge(content: string): boolean {\n const meaningful = content\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line !== '' && !line.startsWith('#'));\n\n if (meaningful.length === 0 || meaningful.length > 2) return false;\n return meaningful.every((line) => /agents\\.md/i.test(line));\n}\n\n/**\n * Build the plan.\n *\n * `write` is what the caller intends to do with it; the plan itself never\n * touches the filesystem, which is what lets the CLI show it and the skill\n * reason about it before anything happens.\n */\nexport async function buildScaffoldPlan(state: RepositoryState): Promise<ScaffoldPlan> {\n const { policy } = state;\n const actions: ScaffoldAction[] = [];\n const notes: string[] = [];\n\n // ── Issue Templates ───────────────────────────────────────────────────────\n const localTemplates = policy.issues.templates.filter((t) => t.origin === 'filesystem');\n const orgTemplates = policy.issues.templates.filter((t) => t.origin === 'organization');\n\n if (localTemplates.length > 0) {\n actions.push(\n action(\n '.github/ISSUE_TEMPLATE/',\n 'keep',\n 'required',\n `${localTemplates.length} Issue Template(s) already declared in this repository.`,\n ),\n );\n } else if (orgTemplates.length > 0) {\n // The organization already answered this, and a local copy would fork it:\n // the org file is then edited once and the repository silently stops\n // following it.\n actions.push(\n action(\n '.github/ISSUE_TEMPLATE/',\n 'keep',\n 'required',\n `The organization serves ${orgTemplates.length} Issue Template(s) to this repository; a local copy would fork them.`,\n ),\n );\n notes.push(\n 'Issue Templates come from the organization. Change them there, not here — a local copy stops tracking the original the moment either is edited.',\n );\n } else {\n for (const type of DEFAULT_ISSUE_TYPES) {\n const path = `.github/ISSUE_TEMPLATE/${type.order}-${type.slug}.yml`;\n actions.push(\n action(\n path,\n 'create',\n 'required',\n `Issue Form for the \"${type.name}\" type.`,\n renderIssueForm(type),\n ),\n );\n }\n const configPath = '.github/ISSUE_TEMPLATE/config.yml';\n actions.push(\n action(\n configPath,\n (await state.exists(configPath)) ? 'keep' : 'create',\n 'required',\n 'Template chooser: keeps blank issues enabled and links the conventions.',\n renderIssueTemplateConfig(),\n ),\n );\n }\n\n // ── Pull Request template ────────────────────────────────────────────────\n if (policy.pullRequests.templates.length > 0) {\n actions.push(\n action(\n policy.pullRequests.templates[0]?.path ?? '.github/PULL_REQUEST_TEMPLATE.md',\n 'keep',\n 'required',\n 'A Pull Request template already exists.',\n ),\n );\n } else {\n actions.push(\n action(\n '.github/PULL_REQUEST_TEMPLATE.md',\n 'create',\n 'required',\n 'Gives every Pull Request a body that review can rely on.',\n renderPullRequestTemplate(),\n ),\n );\n }\n\n // ── Agent entry points ───────────────────────────────────────────────────\n const agents = policy.docs.find((doc) => doc.kind === 'agents' && doc.scope === '');\n const claude = policy.docs.find((doc) => doc.kind === 'claude' && doc.scope === '');\n\n const referenced = policy.docs\n .filter((doc) => doc.kind === 'contributing' || doc.kind === 'referenced')\n .map((doc) => doc.path);\n\n if (agents === undefined) {\n actions.push(\n action(\n 'AGENTS.md',\n 'create',\n 'required',\n 'The canonical entry point for any coding agent, as an index of the real documentation.',\n renderAgentsMd(state.projectName, referenced),\n ),\n );\n } else {\n actions.push(action('AGENTS.md', 'keep', 'required', 'Already the canonical entry point.'));\n }\n\n if (claude === undefined) {\n actions.push(\n action(\n 'CLAUDE.md',\n 'create',\n 'recommended',\n 'One-line bridge to AGENTS.md, so Claude Code reads the canonical source.',\n renderClaudeMd(),\n ),\n );\n } else if (isClaudeBridge(claude.content)) {\n actions.push(action('CLAUDE.md', 'keep', 'recommended', 'Already a bridge to AGENTS.md.'));\n } else if (agents === undefined) {\n // The common migration: instructions live in CLAUDE.md and there is no\n // AGENTS.md. Promoting the content is the user's call — the tool says so\n // rather than moving text it did not write.\n notes.push(\n 'CLAUDE.md carries its own instructions and there is no AGENTS.md. Move that content into AGENTS.md and reduce CLAUDE.md to the one-line bridge; nothing here rewrites it for you.',\n );\n actions.push(\n action(\n 'CLAUDE.md',\n 'review',\n 'recommended',\n 'Holds instructions of its own; AGENTS.md should be the canonical source.',\n ),\n );\n } else {\n notes.push(\n 'AGENTS.md and CLAUDE.md both carry instructions. Two copies of the same rules diverge the first time only one is edited — keep AGENTS.md and reduce CLAUDE.md to the one-line bridge.',\n );\n actions.push(\n action(\n 'CLAUDE.md',\n 'review',\n 'recommended',\n 'Duplicates instructions that AGENTS.md already owns.',\n ),\n );\n }\n\n // ── Documented conventions ───────────────────────────────────────────────\n const hasContributing = policy.docs.some((doc) => doc.kind === 'contributing');\n const conventionsPath = 'docs/conventions.md';\n const conventionsExists = await state.exists(conventionsPath);\n\n if (conventionsExists) {\n actions.push(\n action(conventionsPath, 'keep', 'recommended', 'Conventions are already documented.'),\n );\n } else if (orgTemplates.length > 0) {\n // The conventions are the organization's, and they are documented wherever\n // the organization documents them. A per-repository copy would be a second\n // source that drifts from the one everybody else follows.\n actions.push(\n action(\n conventionsPath,\n 'keep',\n 'recommended',\n \"The conventions are the organization's; documenting them again here would fork them.\",\n ),\n );\n } else if (hasContributing && localTemplates.length > 0) {\n // The repository already says how it works, in its own words and its own\n // file. A second document would be a competing source, which is the exact\n // failure this tool exists to avoid.\n actions.push(\n action(\n conventionsPath,\n 'keep',\n 'recommended',\n 'CONTRIBUTING.md and the Issue Templates already document how this repository works.',\n ),\n );\n } else {\n actions.push(\n action(\n conventionsPath,\n 'create',\n 'recommended',\n 'The source of truth for issue types, labels, branches and commits.',\n renderConventionsDoc(policy.issues.types.length > 0),\n ),\n );\n }\n\n // ── Labels ───────────────────────────────────────────────────────────────\n const labelsPath = '.github/labels.json';\n if (policy.issues.labels.length > 0) {\n actions.push(\n action(\n labelsPath,\n 'keep',\n 'contextual',\n `This repository already has ${policy.issues.labels.length} label(s); Issue Flow never rewrites a taxonomy.`,\n ),\n );\n } else if (await state.exists(labelsPath)) {\n actions.push(action(labelsPath, 'keep', 'contextual', 'A labels file already exists.'));\n } else {\n const includeTypeLabels = policy.issues.types.length === 0;\n actions.push(\n action(\n labelsPath,\n 'create',\n 'contextual',\n includeTypeLabels\n ? 'A baseline label set, including type labels because this organization has no Issue Types.'\n : 'A baseline label set. Type labels are omitted: this organization has native Issue Types.',\n renderLabelsFile(includeTypeLabels),\n ),\n );\n }\n\n if (policy.issues.types.length === 0) {\n notes.push(\n 'No GitHub Issue Types were found. They are an organization-level setting and cannot be created from here; defining them there is better than the `type:*` labels this proposes as a fallback.',\n );\n }\n\n return { root: policy.root, actions, notes };\n}\n\n/** Load a repository file, or null. Used by the state adapter and by tests. */\nexport async function readIfPresent(root: string, relPath: string): Promise<string | null> {\n try {\n return await readFile(join(root, relPath), 'utf-8');\n } catch {\n return null;\n }\n}\n","/**\n * The default convention set — Issue Flow's fallback when a repository declares\n * nothing of its own.\n *\n * This is the **last** rung of the precedence ladder, and it only ever applies\n * where the repository, its organization and the user's configuration are all\n * silent. It exists so that a brand-new repository still gets a usable,\n * professional baseline instead of an empty one.\n *\n * Two rules shaped every choice here.\n *\n * **Native structure before textual convention.** The preference order is\n * `native feature > structured field > label > free text`. Anything GitHub\n * already models — the type of an issue, its priority, its state — is not\n * re-implemented as a title prefix or a label, because that creates a second\n * truth that ages on its own. GitHub's own defaults are `task`, `bug` and\n * `feature`, and an organization may define up to 25 Issue Types.\n *\n * **Do not fragment the backlog.** A type per flavour of work makes no query\n * better and every filter worse. Documentation, maintenance, refactoring and\n * technical debt are *characteristics*, not natures, and they belong on labels\n * over a real type.\n */\n\n/** A default Issue Type, with the reasoning a template and a report can quote. */\nexport interface DefaultIssueType {\n /** Display name, matching GitHub's Issue Type casing convention. */\n name: string;\n /** Slug used for the Issue Form file name, prefixed by `order`. */\n slug: string;\n /** Order in the template chooser: capture first, execution last. */\n order: number;\n /** One line: what this type is. */\n summary: string;\n /**\n * Whether an issue of this type is executable once it is ready.\n *\n * The distinction matters more than it looks: an open issue is not approved\n * work. `Idea`, `Research` and `Epic` record intent and never authorize an\n * agent to start implementing.\n */\n executable: boolean;\n /** GitHub's own default types, which exist in every organization. */\n native: boolean;\n}\n\n/**\n * Six types, not thirteen.\n *\n * `Bug`, `Feature` and `Task` are GitHub's native defaults and cover all\n * executable work. `Idea`, `Research` and `Epic` are added because each answers\n * a question the other three cannot: *is this even worth doing*, *what is the\n * answer*, and *what is the umbrella*. Everything else the user might expect —\n * documentation, maintenance, refactor, technical debt, security, spike — is\n * deliberately **not** a type; see {@link NON_TYPES}.\n */\nexport const DEFAULT_ISSUE_TYPES: readonly DefaultIssueType[] = [\n {\n name: 'Idea',\n slug: 'idea',\n order: 1,\n summary: 'A hypothesis, opportunity or perceived problem, not yet analysed.',\n executable: false,\n native: false,\n },\n {\n name: 'Research',\n slug: 'research',\n order: 2,\n summary: 'An investigation that produces knowledge: analysis, benchmark, feasibility.',\n executable: false,\n native: false,\n },\n {\n name: 'Epic',\n slug: 'epic',\n order: 3,\n summary: 'An umbrella objective delivered through sub-issues.',\n executable: false,\n native: false,\n },\n {\n name: 'Feature',\n slug: 'feature',\n order: 4,\n summary: 'A new capability or a change to what the product does.',\n executable: true,\n native: true,\n },\n {\n name: 'Bug',\n slug: 'bug',\n order: 5,\n summary: 'Existing behaviour that diverges from what is expected.',\n executable: true,\n native: true,\n },\n {\n name: 'Task',\n slug: 'task',\n order: 6,\n summary: 'Concrete work that is neither a feature nor a bug.',\n executable: true,\n native: true,\n },\n] as const;\n\n/** Something people often expect to be a type, and why it is not one here. */\nexport interface NonType {\n concept: string;\n /** How to represent it instead. */\n instead: string;\n why: string;\n}\n\n/**\n * The list is as much a part of the convention as the types themselves: without\n * it, every repository re-derives the same six arguments and reaches a different\n * answer.\n */\nexport const NON_TYPES: readonly NonType[] = [\n {\n concept: 'Documentation',\n instead: '`Task` + label `docs`',\n why: 'The work is a task; what varies is the area it touches.',\n },\n {\n concept: 'Maintenance, chore',\n instead: '`Task`',\n why: 'That is already what `Task` means.',\n },\n {\n concept: 'Refactor, technical debt',\n instead: '`Task` + label `tech-debt`',\n why: 'A cross-cutting characteristic, not a distinct nature of work.',\n },\n {\n concept: 'Security',\n instead: 'the real type (`Bug`/`Task`/`Feature`) + label `security`',\n why: 'It cuts across every type; as a type it would hide whether the item is a flaw or preventive work.',\n },\n {\n concept: 'Spike, investigation',\n instead: '`Research`',\n why: 'The same concept under a different name.',\n },\n {\n concept: 'Enhancement',\n instead: '`Feature`',\n why: 'A change to what the product does is a feature, whether it is new or an improvement.',\n },\n {\n concept: 'Proposal, RFC',\n instead: '`Research`, and an ADR for the decision it produces',\n why: 'The issue carries the investigation; the decision belongs in a document that outlives it.',\n },\n {\n concept: 'Question',\n instead: 'a Discussion, or `Research` when it needs work',\n why: 'A question that needs no work is not a backlog item.',\n },\n] as const;\n\n/** A default label, with the reason it exists. */\nexport interface DefaultLabel {\n name: string;\n description: string;\n /** Hex colour without `#`, matching GitHub's palette conventions. */\n color: string;\n}\n\n/**\n * A deliberately small vocabulary, for what has **no native representation**:\n * technical area, component and cross-cutting characteristic.\n *\n * Nothing here duplicates a field. There is no `priority`, no `status`, no\n * `type` and no size label: GitHub models all four, and a label alongside a\n * field is a second truth that drifts. The one exception is\n * {@link FALLBACK_TYPE_LABELS}, for repositories whose organization has no Issue\n * Types at all.\n */\nexport const DEFAULT_LABELS: readonly DefaultLabel[] = [\n { name: 'api', description: 'Public interfaces and contracts', color: '006b75' },\n { name: 'backend', description: 'Server-side code', color: '5319e7' },\n { name: 'frontend', description: 'User-facing code', color: 'bfd4f2' },\n { name: 'database', description: 'Schema, queries and migrations', color: 'c2e0c6' },\n { name: 'infra', description: 'Infrastructure, CI and deployment', color: 'f9d0c4' },\n { name: 'docs', description: 'Documentation', color: '0075ca' },\n { name: 'security', description: 'Security or privacy impact', color: 'd93f0b' },\n {\n name: 'tech-debt',\n description: 'Debt paid down rather than capability added',\n color: 'e4e669',\n },\n { name: 'blocked', description: 'Waiting on something outside this repository', color: 'b60205' },\n { name: 'good first issue', description: 'Good for newcomers', color: '7057ff' },\n] as const;\n\n/**\n * Type labels, used **only** where the organization has no Issue Types.\n *\n * A repository that cannot use the native field still needs the type to be\n * queryable, and a label is the next best representation. The moment Issue Types\n * exist, these become the second truth the convention warns about — which is why\n * scaffolding proposes them only after finding none.\n */\nexport const FALLBACK_TYPE_LABELS: readonly DefaultLabel[] = DEFAULT_ISSUE_TYPES.map((type) => ({\n name: `type:${type.slug}`,\n description: type.summary,\n color: type.executable ? '1d76db' : 'd4c5f9',\n}));\n\n/**\n * The sections an issue body is expected to carry, by default.\n *\n * Kept short on purpose. A twelve-section template is excellent for an\n * architectural proposal and hostile to capturing an idea, and a form nobody\n * fills in honestly is worse than no form. Each default Issue Form asks only\n * what its type actually needs; this list is what the *prose* fallback uses when\n * a repository has no forms at all.\n */\nexport const DEFAULT_BODY_SECTIONS: readonly string[] = [\n 'Context',\n 'Problem',\n 'Objective',\n 'Scope',\n 'Out of scope',\n 'Acceptance criteria',\n 'Dependencies',\n 'Risks',\n 'References',\n] as const;\n\nimport { DEFAULT_BRANCH_CONVENTION } from './git/index.js';\n\nexport { DEFAULT_BRANCH_CONVENTION };\n\n/**\n * Commit convention.\n *\n * Conventional Commits is the closest thing to a lingua franca, and it is what\n * makes a changelog and a semver bump derivable from the history — which is\n * exactly why the type must reflect the change instead of always being `feat`.\n */\nexport const DEFAULT_COMMIT_CONVENTION = 'Conventional Commits — type(scope): subject';\n\n/**\n * Issue title convention.\n *\n * `null` on purpose: with Issue Types, a `[Bug]` prefix restates in text what a\n * structured field already carries. A repository whose organization has no Issue\n * Types may want one, and declares it.\n */\nexport const DEFAULT_ISSUE_TITLE_CONVENTION: string | null = null;\n\n/** Everything above, as one object, for the report and the scaffolding plan. */\nexport const DEFAULT_CONVENTIONS = {\n issueTypes: DEFAULT_ISSUE_TYPES,\n nonTypes: NON_TYPES,\n labels: DEFAULT_LABELS,\n fallbackTypeLabels: FALLBACK_TYPE_LABELS,\n bodySections: DEFAULT_BODY_SECTIONS,\n branchConvention: DEFAULT_BRANCH_CONVENTION,\n commitConvention: DEFAULT_COMMIT_CONVENTION,\n issueTitleConvention: DEFAULT_ISSUE_TITLE_CONVENTION,\n} as const;\n","import {\n DEFAULT_COMMIT_CONVENTION,\n DEFAULT_ISSUE_TYPES,\n DEFAULT_LABELS,\n type DefaultIssueType,\n FALLBACK_TYPE_LABELS,\n NON_TYPES,\n} from '../conventions/defaults.js';\nimport { DEFAULT_BRANCH_CONVENTION } from '../conventions/git/index.js';\n\n/**\n * The files initialization can write, rendered from the default convention set.\n *\n * Nothing here is a literal copy of a template: every asset is generated from\n * `conventions/defaults.ts`, so the convention and the files that express it can\n * never disagree. Changing a type or a label changes the forms, the labels file\n * and the documentation in one edit.\n */\n\nexport interface ScaffoldAsset {\n /** Repository-relative path, POSIX separators. */\n path: string;\n content: string;\n /**\n * Why this file exists. Shown in the plan, so a user can decline a specific\n * one and understand what they are declining.\n */\n purpose: string;\n /**\n * `required` files are what makes a repository legible to an agent at all.\n * `recommended` ones are worth having; `contextual` ones are only proposed\n * when the repository's state calls for them.\n */\n tier: 'required' | 'recommended' | 'contextual';\n}\n\n/** Fields every executable type needs, and no type should be without. */\nfunction acceptanceCriteriaField(): string {\n return [\n ' - type: textarea',\n ' id: acceptance',\n ' attributes:',\n ' label: Acceptance criteria',\n ' description: How anyone can tell this is done. Checks that fail today and pass afterwards.',\n ' value: |',\n ' - [ ] ',\n ' validations:',\n ' required: true',\n ].join('\\n');\n}\n\nfunction referencesField(): string {\n return [\n ' - type: textarea',\n ' id: references',\n ' attributes:',\n ' label: References',\n ' description: Links, related issues, prior discussion, evidence.',\n ].join('\\n');\n}\n\n/**\n * One Issue Form per default type.\n *\n * `type:` is emitted so that an organization with Issue Types gets the native\n * field filled in; GitHub ignores the key when the type does not exist, so the\n * same file works either way.\n *\n * Required fields are rationed on purpose. `Idea` asks for exactly one, because\n * the cost of recording a thought has to stay near zero — a capture form with\n * eight mandatory fields is a form nobody uses, and the ideas then live in\n * someone's head instead.\n */\nexport function renderIssueForm(type: DefaultIssueType): string {\n const header = [\n `name: \"${emojiFor(type.slug)} ${type.name}\"`,\n `description: \"${type.summary}\"`,\n `type: \"${type.name}\"`,\n 'body:',\n ];\n\n const bodies: Record<string, string[]> = {\n idea: [\n ' - type: markdown',\n ' attributes:',\n ' value: |',\n ' Quick capture. Only the first field is required — triage fills in the rest.',\n ' An Idea is **not approved work**: no agent implements it.',\n ' - type: textarea',\n ' id: idea',\n ' attributes:',\n ' label: The idea',\n ' description: What it is, in a few lines. It does not have to be solved or complete.',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: problem',\n ' attributes:',\n ' label: Problem or opportunity',\n ' description: What pain or gain is behind it? Leave blank if you do not know yet.',\n referencesField(),\n ' - type: textarea',\n ' id: questions',\n ' attributes:',\n ' label: Open questions',\n ' description: What would have to be answered before deciding anything.',\n ],\n research: [\n ' - type: textarea',\n ' id: question',\n ' attributes:',\n ' label: Question to answer',\n ' description: The decision this investigation has to unblock.',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: context',\n ' attributes:',\n ' label: Context',\n ' description: What is already known, and why the question is open.',\n ' - type: textarea',\n ' id: deliverable',\n ' attributes:',\n ' label: Expected deliverable',\n ' description: A conclusion and a recommendation. Say where durable knowledge will be written down.',\n ' validations:',\n ' required: true',\n referencesField(),\n ],\n epic: [\n ' - type: markdown',\n ' attributes:',\n ' value: |',\n ' An Epic is never executed directly: it is delivered through its sub-issues.',\n ' - type: textarea',\n ' id: objective',\n ' attributes:',\n ' label: Objective',\n ' description: The state of the product this Epic describes.',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: scope',\n ' attributes:',\n ' label: Scope',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: out-of-scope',\n ' attributes:',\n ' label: Out of scope',\n ' description: What this Epic deliberately does not cover. Absence here is what makes scope creep arguable.',\n ' - type: textarea',\n ' id: children',\n ' attributes:',\n ' label: Known sub-issues',\n ' description: Add them as sub-issues once they exist; list what you already foresee.',\n ],\n feature: [\n ' - type: textarea',\n ' id: problem',\n ' attributes:',\n ' label: Problem',\n ' description: What is not possible today, and for whom.',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: objective',\n ' attributes:',\n ' label: Objective',\n ' description: What becomes true when this is done.',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: scope',\n ' attributes:',\n ' label: Scope and out of scope',\n ' description: What is included, and what is deliberately not.',\n acceptanceCriteriaField(),\n ' - type: textarea',\n ' id: dependencies',\n ' attributes:',\n ' label: Dependencies and risks',\n ' description: What has to exist first, and what could go wrong.',\n referencesField(),\n ],\n bug: [\n ' - type: textarea',\n ' id: current',\n ' attributes:',\n ' label: Current behaviour',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: expected',\n ' attributes:',\n ' label: Expected behaviour',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: steps',\n ' attributes:',\n ' label: Steps to reproduce',\n ' value: |',\n ' 1.',\n ' 2.',\n ' 3.',\n ' validations:',\n ' required: true',\n ' - type: input',\n ' id: environment',\n ' attributes:',\n ' label: Environment',\n ' description: Where it happens — environment, version, component, browser.',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: evidence',\n ' attributes:',\n ' label: Evidence',\n ' description: Logs, request and response, screenshot, trace id, affected URL.',\n ' - type: dropdown',\n ' id: impact',\n ' attributes:',\n ' label: Impact',\n ' options:',\n ' - \"Data loss or improper exposure\"',\n ' - \"Feature unavailable, no workaround\"',\n ' - \"Feature degraded, workaround exists\"',\n ' - \"Cosmetic or low consequence\"',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: done',\n ' attributes:',\n ' label: How to tell it is fixed',\n ' description: The check that fails today and passes afterwards.',\n ' validations:',\n ' required: true',\n ],\n task: [\n ' - type: textarea',\n ' id: what',\n ' attributes:',\n ' label: What has to be done',\n ' validations:',\n ' required: true',\n ' - type: textarea',\n ' id: why',\n ' attributes:',\n ' label: Why',\n ' description: The reason this is worth doing now.',\n ' validations:',\n ' required: true',\n acceptanceCriteriaField(),\n referencesField(),\n ],\n };\n\n const body = bodies[type.slug];\n if (body === undefined) {\n throw new Error(`No Issue Form body defined for type \"${type.slug}\"`);\n }\n\n return `${[...header, ...body].join('\\n')}\\n`;\n}\n\nfunction emojiFor(slug: string): string {\n const map: Record<string, string> = {\n idea: '💡',\n research: '🔬',\n epic: '🗺️',\n feature: '✨',\n bug: '🐛',\n task: '🔧',\n };\n return map[slug] ?? '📌';\n}\n\n/**\n * The template chooser configuration.\n *\n * `blank_issues_enabled: true` is deliberate: a contributor who does not fit any\n * form should open the issue anyway. A closed chooser turns a report into\n * silence, which costs more than an unstructured issue.\n */\nexport function renderIssueTemplateConfig(): string {\n return [\n 'blank_issues_enabled: true',\n 'contact_links:',\n ' - name: How this backlog works',\n ' url: https://github.com/fabioassuncao/issue-flow/blob/main/docs/conventions.md',\n ' about: Issue types, what each one authorizes, labels and the lifecycle.',\n '',\n ].join('\\n');\n}\n\n/** The Pull Request template. */\nexport function renderPullRequestTemplate(): string {\n return [\n '## What changed',\n '',\n '<!-- What this Pull Request does, and why. One paragraph is enough. -->',\n '',\n '## How it was tested',\n '',\n '<!-- The checks you ran, and what they proved. \"CI is green\" is not a test plan. -->',\n '',\n '## Related issues',\n '',\n '<!--',\n 'One `Closes #N` per issue this resolves, each on its own line and as plain body',\n 'text — GitHub only auto-closes when the line is not inside a code fence.',\n '-->',\n '',\n '## Notes for reviewers',\n '',\n '<!-- Where to start, and anything you are unsure about. Write \"N/A\" rather than deleting a section. -->',\n '',\n ].join('\\n');\n}\n\n/** `AGENTS.md`, generated as an index and nothing else. */\nexport function renderAgentsMd(projectName: string, referenced: string[]): string {\n const lines = [\n `# ${projectName}`,\n '',\n 'This file is an **index**. It holds no rule, command or convention of its own —',\n 'those live in the documents referenced below, which are the source of truth.',\n '',\n '> Document once. Reference everywhere it is needed.',\n '',\n '## Start here',\n '',\n ];\n\n for (const path of referenced) {\n lines.push(`- [\\`${path}\\`](${path})`);\n }\n if (referenced.length === 0) {\n lines.push('- [`README.md`](README.md) — what this project is, and how to run it');\n }\n\n lines.push(\n '',\n '## Working on an issue',\n '',\n '- [`docs/conventions.md`](docs/conventions.md) — issue types, labels, branches,',\n ' commits and Pull Requests: what this repository expects and what it does not',\n '',\n '## What does not belong in this file',\n '',\n 'Anything that can live in a document of its own: build and test commands, code',\n 'style, architecture rules, testing strategy, operational procedures. If it is a',\n 'rule, a standard or reusable knowledge, it belongs in its own document and this',\n 'file only points at it.',\n '',\n 'An instruction that today exists **only** here does not stay here: move it to the',\n 'right document and leave a reference behind. Duplicated instructions in an agent',\n 'file age out of sight and start contradicting the source without anyone noticing.',\n '',\n );\n\n return lines.join('\\n');\n}\n\n/**\n * `CLAUDE.md`, which is one line by design.\n *\n * Claude Code reads `CLAUDE.md` as its memory file, and the temptation is to\n * put the instructions there too. That produces two copies of the same rules\n * which diverge the first time only one of them is edited — so this file is a\n * bridge, never a second source.\n */\nexport function renderClaudeMd(): string {\n return 'Read and follow the instructions in AGENTS.md.\\n';\n}\n\n/** The conventions document: the source of truth this scaffolding creates. */\nexport function renderConventionsDoc(hasIssueTypes: boolean): string {\n const typeRows = DEFAULT_ISSUE_TYPES.map(\n (t) =>\n `| **${t.name}** | ${t.summary} | ${t.executable ? 'Yes, once it is ready' : '**No**'} |`,\n ).join('\\n');\n\n const nonTypeRows = NON_TYPES.map((n) => `| ${n.concept} | ${n.instead} | ${n.why} |`).join('\\n');\n\n const labelRows = DEFAULT_LABELS.map((l) => `| \\`${l.name}\\` | ${l.description} |`).join('\\n');\n\n const typeMechanism = hasIssueTypes\n ? 'This organization has GitHub Issue Types, so the type lives in the **native field**.'\n : [\n 'This organization has no GitHub Issue Types, so the type is carried by a',\n '`type:*` label. If Issue Types are enabled later, move to them and drop these',\n 'labels — keeping both would create a second truth that drifts.',\n ].join('\\n');\n\n return `# Conventions\n\nHow work is recorded, classified and executed in this repository.\n\n## Principles\n\n1. **An open issue is not approved work.** Recording is cheap; executing requires\n explicit authorization.\n2. **Native structure before textual convention.** The order of preference is\n native feature > structured field > label > free text. Nothing GitHub already\n models is re-implemented as a title prefix or a label.\n3. **Each piece of information in one place.** If something has a field, it does\n not also have a label.\n4. **Capturing an idea is not bureaucratic.** The Idea form has one required\n field. The cost of recording a thought has to stay near zero.\n\n## Issue types\n\n${typeMechanism}\n\n| Type | What it is | Authorizes execution? |\n|---|---|---|\n${typeRows}\n\n### Choosing the type\n\nIn order — the first \"yes\" decides:\n\n1. **Is something broken?** → \\`Bug\\`, even if the expected behaviour never worked.\n2. **Is the question still unanswered?** → \\`Research\\`.\n3. **Is it just a record, with no analysis yet?** → \\`Idea\\`.\n4. **Is it large and delivered in parts?** → \\`Epic\\`, with sub-issues.\n5. **Does it change what the product does?** → \\`Feature\\`.\n6. **None of the above** → \\`Task\\`.\n\n### What is deliberately not a type\n\nA type per flavour of work fragments the backlog without improving a single\nquery.\n\n| Concept | Represent it as | Why |\n|---|---|---|\n${nonTypeRows}\n\n## Labels\n\nLabels are the last resort, for what has no native representation: technical\narea, component and cross-cutting characteristic.\n\n| Label | Meaning |\n|---|---|\n${labelRows}\n\n**Never use a label for** priority, type, state or size when a field exists for\nit: the label becomes a second truth that ages on its own.\n\n## Branches and commits\n\n- Branch: \\`${DEFAULT_BRANCH_CONVENTION}\\`\n- Commit: ${DEFAULT_COMMIT_CONVENTION}\n\nThe commit type must match the nature of the change. A bug fix committed as\n\\`feat:\\` corrupts the changelog and any version bump derived from the history.\n\n## Pull Requests\n\nFollow \\`.github/PULL_REQUEST_TEMPLATE.md\\`. Keep every section it defines and\nanswer the ones that do not apply with one line saying why — a deleted section\nreads as an unanswered one to automated review.\n\nLink the issues with one \\`Closes #N\\` per issue, as plain body text.\n\n## Agent entry points\n\n\\`AGENTS.md\\` is the canonical entry point for any coding agent, of any vendor.\nIt is an index: it says which documents to read, and holds no rule of its own.\n\n\\`CLAUDE.md\\` exists only as the Claude Code integration and contains a single\nline pointing at \\`AGENTS.md\\`. Any other agent adapter follows the same rule.\n\n\\`\\`\\`text\nCLAUDE.md → AGENTS.md → specialized documentation → single source of truth\n\\`\\`\\`\n`;\n}\n\n/** The labels file, consumed by the label sync step and readable by a human. */\nexport function renderLabelsFile(includeTypeLabels: boolean): string {\n const labels = includeTypeLabels ? [...FALLBACK_TYPE_LABELS, ...DEFAULT_LABELS] : DEFAULT_LABELS;\n\n return `${JSON.stringify(\n labels.map((label) => ({\n name: label.name,\n description: label.description,\n color: label.color,\n })),\n null,\n 2,\n )}\\n`;\n}\n"],"mappings":"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","names":["join","join"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/verify/present.ts","../src/verify/gate.ts","../src/routing/escalation.ts"],"sourcesContent":["import type { VerdictStatus, VerificationLevel } from './types.js';\n\n/** One-line label. `unverified` is never phrased as a verified success. */\nexport function formatVerificationLine(\n verdict: VerdictStatus,\n level: VerificationLevel | string | null = null,\n): string {\n const suffix = level !== null && level !== '' ? ` (${level})` : '';\n if (verdict === 'passed') return `Acceptance contract passed${suffix}`;\n if (verdict === 'failed') return `Acceptance contract failed${suffix}`;\n return `Acceptance contract unverified${suffix} — nothing objective confirmed`;\n}\n","import { dirname } from 'node:path';\nimport { setLastError } from '../core/state-manager.js';\nimport { failureFingerprint, fatalFailedCount } from '../routing/escalation.js';\nimport type { EngineConfig, ResolvedPaths, TaskPlan } from '../types.js';\nimport { printError, printSuccess, printWarning } from '../ui/logger.js';\nimport { formatVerificationLine } from './present.js';\nimport { type AcceptanceOutcome, runAcceptance } from './run-issue.js';\n\nconst contractHistory: { fingerprint: string; fatalFailed: number }[] = [];\n\n/** `prdFile` is `tasks.json` in the issue directory (global or standalone). */\nexport function resolveIssueDir(_config: EngineConfig, paths: ResolvedPaths): string {\n return dirname(paths.prdFile);\n}\n\nexport async function runAcceptanceGate(options: {\n issueDir: string;\n cwd?: string;\n addDirs?: string[];\n skipReviewer?: boolean;\n}): Promise<AcceptanceOutcome> {\n return runAcceptance({\n issueDir: options.issueDir,\n cwd: options.cwd,\n addDirs: options.addDirs,\n skipReviewer: options.skipReviewer,\n });\n}\n\nexport function applyAcceptanceToPlan(\n plan: TaskPlan,\n outcome: AcceptanceOutcome,\n): { plan: TaskPlan; failed: boolean } {\n const line = formatVerificationLine(outcome.verdict, outcome.level);\n if (outcome.verdict === 'failed') {\n printError(line);\n const results = outcome.contract.results;\n contractHistory.push({\n fingerprint: failureFingerprint(results),\n fatalFailed: fatalFailedCount(results),\n });\n return {\n plan: setLastError(plan, 'task_execution', line),\n failed: true,\n };\n }\n if (outcome.verdict === 'unverified') {\n printWarning(line);\n return { plan, failed: false };\n }\n printSuccess(line);\n return { plan, failed: false };\n}\n","import type { AgentProviderId } from '../agents/types.js';\nimport type { FailureKind } from '../resilience/errors.js';\nimport type { CheckResult } from '../verify/types.js';\nimport type { ModelEntry, ModelTier } from './models.js';\n\nexport type FailureClass = 'availability' | 'non-convergence' | 'environment';\n\nexport type EscalationRung =\n | 'current'\n | 'effort'\n | 'model'\n | 'harness'\n | 'review'\n | 'decompose'\n | 'blocked';\n\nexport const LADDER: readonly EscalationRung[] = [\n 'current',\n 'effort',\n 'model',\n 'harness',\n 'review',\n 'decompose',\n 'blocked',\n] as const;\n\nexport interface EscalationConfig {\n enabled: boolean;\n minAttemptsBeforeEscalation: number;\n maxEscalations: number;\n maxRungs: EscalationRung[];\n}\n\nexport const DEFAULT_ESCALATION: EscalationConfig = {\n enabled: false,\n minAttemptsBeforeEscalation: 2,\n maxEscalations: 2,\n maxRungs: ['effort', 'model', 'harness'],\n};\n\nconst AVAILABILITY: ReadonlySet<FailureKind> = new Set([\n 'provider_down',\n 'provider_crash',\n 'rate_limit',\n 'network',\n]);\n\nconst ENVIRONMENT: ReadonlySet<FailureKind> = new Set([\n 'configuration',\n 'repository_state',\n 'authentication',\n]);\n\nexport interface AttemptSignals {\n results: readonly CheckResult[];\n failureKind?: FailureKind | null;\n fingerprint?: string;\n fatalFailed?: number;\n diffBytes?: number;\n}\n\n/**\n * Class A stays with #69. Class C stays with #64. This function never\n * recommends a model change for either.\n */\nexport function classifyAttempt(input: AttemptSignals): FailureClass {\n if (input.failureKind && AVAILABILITY.has(input.failureKind)) return 'availability';\n if (input.failureKind && ENVIRONMENT.has(input.failureKind)) return 'environment';\n if (input.results.some((result) => result.status === 'could-not-run')) return 'environment';\n if (input.results.some((result) => result.status === 'failed')) return 'non-convergence';\n return 'environment';\n}\n\n/** Hash of the failed-check *ids*, never of the error text. */\nexport function failureFingerprint(results: readonly CheckResult[]): string {\n return results\n .filter((result) => result.status === 'failed')\n .map((result) => result.id)\n .sort()\n .join('\\n');\n}\n\nexport function fatalFailedCount(results: readonly CheckResult[]): number {\n return results.filter((result) => result.fatal && result.status === 'failed').length;\n}\n\nexport interface ConvergenceHistoryEntry {\n fingerprint: string;\n fatalFailed: number;\n diffBytes?: number;\n}\n\nexport function detectNonConvergence(input: {\n history: readonly ConvergenceHistoryEntry[];\n minAttempts?: number;\n}): { nonConverged: boolean; reason: string } {\n const minAttempts = input.minAttempts ?? DEFAULT_ESCALATION.minAttemptsBeforeEscalation;\n if (input.history.length < minAttempts) {\n return { nonConverged: false, reason: 'below-min-attempts' };\n }\n\n const latest = input.history[input.history.length - 1];\n const previous = input.history[input.history.length - 2];\n if (latest === undefined || previous === undefined) {\n return { nonConverged: false, reason: 'below-min-attempts' };\n }\n\n const progressed =\n latest.fatalFailed < previous.fatalFailed ||\n (latest.diffBytes ?? 0) > (previous.diffBytes ?? 0);\n if (progressed) {\n return { nonConverged: false, reason: 'progress' };\n }\n\n const window = input.history.slice(-minAttempts);\n const samePrint = window.every((entry) => entry.fingerprint === latest.fingerprint);\n if (samePrint && latest.fingerprint !== '') {\n return { nonConverged: true, reason: 'repeated-fingerprint' };\n }\n if (!progressed) {\n return { nonConverged: true, reason: 'no-progress' };\n }\n return { nonConverged: false, reason: 'progress' };\n}\n\nexport interface NextRungInput {\n tried: readonly EscalationRung[];\n capabilities: {\n reasoningEffort: boolean;\n modelSelection: boolean;\n otherHarness: boolean;\n };\n maxRungs?: readonly EscalationRung[];\n maxEscalations?: number;\n escalationsUsed?: number;\n}\n\nexport interface NextRungResult {\n rung: EscalationRung;\n skipped: EscalationRung[];\n exhausted: boolean;\n}\n\nfunction hasCapability(rung: EscalationRung, capabilities: NextRungInput['capabilities']): boolean {\n if (rung === 'effort') return capabilities.reasoningEffort;\n if (rung === 'model') return capabilities.modelSelection;\n if (rung === 'harness') return capabilities.otherHarness;\n return true;\n}\n\n/**\n * The ladder only climbs. A rejected target is never a candidate again.\n * `claude → codex → claude` is impossible because `harness` is consumed once.\n */\nexport function nextRung(input: NextRungInput): NextRungResult {\n const allowed = input.maxRungs ?? DEFAULT_ESCALATION.maxRungs;\n const maxEscalations = input.maxEscalations ?? DEFAULT_ESCALATION.maxEscalations;\n const used = input.escalationsUsed ?? input.tried.filter((rung) => rung !== 'current').length;\n if (used >= maxEscalations) {\n return { rung: 'blocked', skipped: [], exhausted: true };\n }\n\n const skipped: EscalationRung[] = [];\n const tried = new Set(input.tried);\n for (const rung of LADDER) {\n if (rung === 'current') continue;\n if (tried.has(rung)) continue;\n if (rung !== 'blocked' && !allowed.includes(rung)) continue;\n if (!hasCapability(rung, input.capabilities)) {\n skipped.push(rung);\n continue;\n }\n return { rung, skipped, exhausted: rung === 'blocked' };\n }\n return { rung: 'blocked', skipped, exhausted: true };\n}\n\n/** A harness already used on this task cannot be selected again. */\nexport function unusedHarness(\n current: AgentProviderId,\n triedHarnesses: readonly AgentProviderId[],\n available: readonly AgentProviderId[],\n): AgentProviderId | null {\n const seen = new Set<AgentProviderId>([current, ...triedHarnesses]);\n return available.find((id) => !seen.has(id)) ?? null;\n}\n\nconst TIER_ORDER: readonly ModelTier[] = ['fast', 'mid', 'strong'];\n\n/** Return the next stronger concrete model, never a tier/model already attempted. */\nexport function nextModelTier(\n current: ModelTier,\n tried: readonly ModelTier[],\n catalog: readonly ModelEntry[],\n): ModelEntry | null {\n const seen = new Set<ModelTier>([current, ...tried]);\n const currentIndex = TIER_ORDER.indexOf(current);\n for (const tier of TIER_ORDER.slice(currentIndex + 1)) {\n if (seen.has(tier)) continue;\n const entry = catalog.find((model) => model.tier === tier);\n if (entry !== undefined) return entry;\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;AAGO,SAAS,uBACd,SACA,QAA2C,MACnC;AACR,QAAM,SAAS,UAAU,QAAQ,UAAU,KAAK,KAAK,KAAK,MAAM;AAChE,MAAI,YAAY,SAAU,QAAO,6BAA6B,MAAM;AACpE,MAAI,YAAY,SAAU,QAAO,6BAA6B,MAAM;AACpE,SAAO,iCAAiC,MAAM;AAChD;;;ACXA,SAAS,eAAe;;;AC0EjB,SAAS,mBAAmB,SAAyC;AAC1E,SAAO,QACJ,OAAO,CAAC,WAAW,OAAO,WAAW,QAAQ,EAC7C,IAAI,CAAC,WAAW,OAAO,EAAE,EACzB,KAAK,EACL,KAAK,IAAI;AACd;AAEO,SAAS,iBAAiB,SAAyC;AACxE,SAAO,QAAQ,OAAO,CAAC,WAAW,OAAO,SAAS,OAAO,WAAW,QAAQ,EAAE;AAChF;;;AD5EA,IAAM,kBAAkE,CAAC;AAGlE,SAAS,gBAAgB,SAAuB,OAA8B;AACnF,SAAO,QAAQ,MAAM,OAAO;AAC9B;AAEA,eAAsB,kBAAkB,SAKT;AAC7B,SAAO,cAAc;AAAA,IACnB,UAAU,QAAQ;AAAA,IAClB,KAAK,QAAQ;AAAA,IACb,SAAS,QAAQ;AAAA,IACjB,cAAc,QAAQ;AAAA,EACxB,CAAC;AACH;AAEO,SAAS,sBACd,MACA,SACqC;AACrC,QAAM,OAAO,uBAAuB,QAAQ,SAAS,QAAQ,KAAK;AAClE,MAAI,QAAQ,YAAY,UAAU;AAChC,eAAW,IAAI;AACf,UAAM,UAAU,QAAQ,SAAS;AACjC,oBAAgB,KAAK;AAAA,MACnB,aAAa,mBAAmB,OAAO;AAAA,MACvC,aAAa,iBAAiB,OAAO;AAAA,IACvC,CAAC;AACD,WAAO;AAAA,MACL,MAAM,aAAa,MAAM,kBAAkB,IAAI;AAAA,MAC/C,QAAQ;AAAA,IACV;AAAA,EACF;AACA,MAAI,QAAQ,YAAY,cAAc;AACpC,iBAAa,IAAI;AACjB,WAAO,EAAE,MAAM,QAAQ,MAAM;AAAA,EAC/B;AACA,eAAa,IAAI;AACjB,SAAO,EAAE,MAAM,QAAQ,MAAM;AAC/B;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/execution/plan.ts","../src/execution/containers.ts","../src/issues/graph.ts","../src/execution/order.ts","../src/core/pipeline.ts"],"sourcesContent":["import { mkdir, readFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { ZodError } from 'zod';\nimport { isoNow } from '../core/state-manager.js';\nimport type { DependencyGraph } from '../issues/graph.js';\nimport type { Issue } from '../issues/types.js';\nimport { executionPlanSchema } from '../storage/schemas.js';\nimport type { LastError, PullRequestRef } from '../types.js';\nimport { writeFileAtomic } from '../utils/fs.js';\nimport { isContainer } from './containers.js';\nimport { priorityOf } from './order.js';\nimport type {\n ExecutionPlan,\n ExecutionPlanExcluded,\n ExecutionPlanIssue,\n QueueIssueRole,\n QueueIssueStatus,\n QueueStatus,\n} from './types.js';\n\n/**\n * Building, reading, writing and advancing the execution plan of a queue.\n *\n * The plan is the only state a multi-issue run adds to the storage tree, and it\n * is written **only** for a queue with more than one Issue — a single-issue run\n * leaves the layout exactly as it was.\n */\n\nexport interface BuildExecutionPlanInput {\n /** Project id of the global storage tree. */\n projectId: string;\n /** Identifiers the user asked for, in order; the first one names the queue. */\n requested: readonly string[];\n graph: DependencyGraph;\n /** Ids to run, already ordered by `computeExecutionOrder`. */\n order: readonly string[];\n noBranch?: boolean;\n prReview?: boolean;\n /** Injectable clock, so tests can assert `createdAt` vs `updatedAt`. */\n now?: () => string;\n}\n\nfunction parentOf(graph: DependencyGraph, id: string): string | null {\n for (const node of graph.nodes.values()) {\n if (node.relations.children.includes(id)) return node.id;\n }\n return null;\n}\n\n/** Title/url/number of a node, falling back to what the id alone tells us. */\nfunction describeIssue(\n issue: Issue | null,\n id: string,\n): {\n number: number | null;\n title: string;\n url: string | null;\n source: string;\n} {\n const parsed = Number.parseInt(id, 10);\n return {\n number: issue?.number ?? (Number.isNaN(parsed) ? null : parsed),\n title: issue?.title ?? '',\n url: issue?.remoteRef ?? null,\n source: issue?.source ?? 'github',\n };\n}\n\n/**\n * Turn a graph plus an order into the persisted plan.\n *\n * Every Issue of the graph that is *not* in the order becomes an `excluded`\n * entry: the consolidated Pull Request reports them as known pending work, so a\n * hierarchy the user chose to trim never disappears silently.\n */\nexport function buildExecutionPlan(input: BuildExecutionPlanInput): ExecutionPlan {\n const now = input.now ?? isoNow;\n const at = now();\n const scheduled = new Set(input.order);\n const requested = new Set(input.requested);\n\n const issues: ExecutionPlanIssue[] = input.order.map((id, index) => {\n const node = input.graph.nodes.get(id);\n const relations = node?.relations;\n const described = describeIssue(node?.issue ?? null, id);\n const children = relations?.children ?? [];\n const role: QueueIssueRole = isContainer({\n issue: node?.issue ?? null,\n children,\n })\n ? 'container'\n : 'executable';\n\n return {\n id,\n number: described.number,\n title: described.title,\n url: described.url,\n source: described.source,\n position: index + 1,\n status: 'pending',\n origin: requested.has(id) ? 'requested' : 'discovered',\n role,\n externalDependencies: (relations?.blockedBy ?? []).filter(\n (blocker) => !scheduled.has(blocker),\n ),\n // Only the constraints that survive inside the queue: a blocker left out\n // of it is reported in `excluded`, not enforced as a dependency.\n dependsOn: (relations?.blockedBy ?? []).filter((blocker) => scheduled.has(blocker)),\n parent: relations?.parent ?? parentOf(input.graph, id),\n priority: priorityOf(node?.issue?.labels ?? []),\n heuristic: (relations?.heuristic.length ?? 0) > 0,\n failedPhase: null,\n lastError: null,\n attempts: 0,\n blockedReason: null,\n startedAt: null,\n completedAt: null,\n };\n });\n\n const excluded: ExecutionPlanExcluded[] = [];\n for (const node of input.graph.nodes.values()) {\n if (scheduled.has(node.id)) continue;\n const described = describeIssue(node.issue, node.id);\n excluded.push({\n id: node.id,\n number: described.number,\n title: described.title,\n url: described.url,\n reason: 'Discovered in the hierarchy but not selected for this run',\n });\n }\n\n return {\n schemaVersion: 1,\n id: input.requested[0] ?? (input.order[0] as string),\n project: input.projectId,\n requested: [...input.requested],\n branchName: null,\n noBranch: input.noBranch ?? false,\n prReview: input.prReview ?? false,\n status: 'pending',\n createdAt: at,\n updatedAt: at,\n truncated: input.graph.truncated,\n issues,\n excluded,\n };\n}\n\n/** Read a persisted plan. Throws with the offending fields on invalid content. */\nexport async function loadExecutionPlan(path: string): Promise<ExecutionPlan> {\n const raw: unknown = JSON.parse(await readFile(path, 'utf-8'));\n\n try {\n return executionPlanSchema.parse(raw) as ExecutionPlan;\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 execution-plan.json at ${path}:\\n${issues}`);\n }\n throw err;\n }\n}\n\n/**\n * Persist a plan atomically, creating the queue directory on first write.\n *\n * `updatedAt` is stamped here so no call site can forget it — the file is the\n * only record of how far a queue got, and a stale timestamp on it is worse than\n * none.\n */\nexport async function saveExecutionPlan(\n path: string,\n plan: ExecutionPlan,\n now: () => string = isoNow,\n): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const stamped: ExecutionPlan = { ...plan, updatedAt: now() };\n await writeFileAtomic(path, `${JSON.stringify(stamped, null, 2)}\\n`);\n}\n\n/**\n * The Issue the queue should work on next, or `null` when every one of them is\n * completed.\n *\n * The order of the lookups *is* the resume policy: an interrupted issue\n * (`in_progress`) and a failed one are retried before the queue moves on, and\n * each of them picks up from its own `tasks.json` (phase-level resume) rather\n * than from the start. A `completed` issue is never revisited — that is the\n * whole point of persisting the queue.\n */\nexport interface NextQueueIssueOptions {\n /**\n * Ids this invocation is done with, however they ended. A queue that skips a\n * failing Issue and comes back to it needs a way to say \"not this one again\",\n * or \"come back later\" becomes \"come back forever\".\n */\n exclude?: ReadonlySet<string>;\n}\n\n/**\n * Whether every dependency this Issue declares *inside the queue* is finished.\n *\n * The execution order already places dependencies first, so this only bites\n * once an entry stops being `completed` — skipped, blocked or failed. Handing\n * out an Issue whose blocker was skipped is how a queue produces work that\n * cannot compile.\n */\nfunction dependenciesSatisfied(plan: ExecutionPlan, entry: ExecutionPlanIssue): boolean {\n return entry.dependsOn.every((id) => {\n const dependency = plan.issues.find((candidate) => candidate.id === id);\n if (dependency === undefined) return true;\n // A container is not work: waiting on it would deadlock the children\n // that complete it.\n if (dependency.role === 'container') return true;\n return dependency.status === 'completed';\n });\n}\n\n/**\n * The Issue to work on next.\n *\n * The order of the four lookups is the resumption policy:\n * an interrupted Issue first (it was mid-flight), then one that failed on a\n * previous invocation, then the untouched ones, and only at the end the ones\n * this queue deliberately set aside — which is what \"go on with the\n * independent work and come back to it\" means in one line.\n *\n * `blocked` is absent on purpose: it needs a human, and no ordering of the\n * queue changes that.\n */\nexport function nextQueueIssue(\n plan: ExecutionPlan,\n options: NextQueueIssueOptions = {},\n): ExecutionPlanIssue | null {\n const exclude = options.exclude ?? new Set<string>();\n const eligible = (status: QueueIssueStatus) => (entry: ExecutionPlanIssue) =>\n entry.role !== 'container' &&\n entry.status === status &&\n !exclude.has(entry.id) &&\n dependenciesSatisfied(plan, entry);\n\n return (\n plan.issues.find(eligible('in_progress')) ??\n plan.issues.find(eligible('failed')) ??\n plan.issues.find(eligible('pending')) ??\n plan.issues.find(eligible('skipped')) ??\n null\n );\n}\n\n/**\n * Status of the queue derived from its entries.\n *\n * `skipped` is *not* terminal — the queue means to come back to it — so a queue\n * holding one is still `in_progress`. `blocked` is: nothing the queue can do\n * moves it, so a queue that ends with one ends `failed`, which is what a person\n * needs to see.\n */\nexport function queueStatus(plan: ExecutionPlan): QueueStatus {\n if (plan.issues.some((entry) => entry.status === 'failed' || entry.status === 'blocked')) {\n return 'failed';\n }\n if (plan.issues.every((entry) => entry.status === 'completed')) return 'completed';\n if (plan.issues.some((entry) => entry.status !== 'pending')) return 'in_progress';\n return 'pending';\n}\n\n/** Whether every Issue of the queue has been resolved. */\nexport function isQueueComplete(plan: ExecutionPlan): boolean {\n return plan.issues.every((entry) => entry.status === 'completed');\n}\n\n/** Apply `patch` to one entry and recompute the queue status. Pure. */\nfunction updateIssue(\n plan: ExecutionPlan,\n id: string,\n patch: Partial<ExecutionPlanIssue>,\n): ExecutionPlan {\n const issues = plan.issues.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));\n const next: ExecutionPlan = { ...plan, issues };\n return { ...next, status: queueStatus(next) };\n}\n\nexport function markQueueIssueInProgress(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'in_progress',\n // Preserved across a resume: the queue reports when the work on this Issue\n // really began, not when it was last picked up.\n startedAt: current?.startedAt ?? now(),\n failedPhase: null,\n lastError: null,\n });\n}\n\nfunction completeResolvedContainers(plan: ExecutionPlan, now: () => string): ExecutionPlan {\n let next = plan;\n let changed = true;\n while (changed) {\n changed = false;\n for (const entry of next.issues) {\n if (entry.role !== 'container' || entry.status === 'completed') continue;\n const children = next.issues.filter((candidate) => candidate.parent === entry.id);\n if (children.length === 0) continue;\n if (children.every((child) => child.status === 'completed')) {\n next = updateIssue(next, entry.id, {\n status: 'completed',\n completedAt: now(),\n failedPhase: null,\n lastError: null,\n });\n changed = true;\n }\n }\n }\n return next;\n}\n\nexport function markQueueIssueCompleted(\n plan: ExecutionPlan,\n id: string,\n now: () => string = isoNow,\n): ExecutionPlan {\n return completeResolvedContainers(\n updateIssue(plan, id, {\n status: 'completed',\n completedAt: now(),\n failedPhase: null,\n lastError: null,\n }),\n now,\n );\n}\n\nexport function markQueueIssueFailed(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'failed',\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/**\n * Set an Issue aside and come back to it later.\n *\n * The difference from `failed` is the whole point of `--on-issue-failure skip`:\n * a failure that stopped this attempt is not a verdict on the eleven other\n * Issues in the queue, and `skipped` is the status that says \"not now\" rather\n * than \"not at all\". `attempts` is incremented here because the counter is what\n * keeps \"come back later\" from becoming \"come back forever\".\n */\nexport function markQueueIssueSkipped(\n plan: ExecutionPlan,\n id: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'skipped',\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/**\n * Stop working on an Issue until a person looks at it.\n *\n * `blocked` is never handed back out by `nextQueueIssue`: waiting cannot fix\n * a missing credential or a repository mid-rebase, and a queue that could\n * unblock itself would spin on the same cause forever.\n */\nexport function markQueueIssueBlocked(\n plan: ExecutionPlan,\n id: string,\n reason: string,\n failure: { phase: string | null; error: LastError | null },\n): ExecutionPlan {\n const current = plan.issues.find((entry) => entry.id === id);\n return updateIssue(plan, id, {\n status: 'blocked',\n blockedReason: reason,\n failedPhase: failure.phase,\n lastError: failure.error,\n attempts: (current?.attempts ?? 0) + 1,\n });\n}\n\n/** Record the branch every Issue of the queue shares. */\nexport function setQueueBranch(plan: ExecutionPlan, branchName: string): ExecutionPlan {\n return { ...plan, branchName };\n}\n\n/** Record the single Pull Request that consolidated the queue. */\nexport function setQueuePullRequest(\n plan: ExecutionPlan,\n pullRequest: PullRequestRef,\n): ExecutionPlan {\n return { ...plan, pullRequest };\n}\n","import type { DependencyGraph } from '../issues/graph.js';\nimport type { Issue } from '../issues/types.js';\n\nexport const CONTAINER_SIGNALS = ['children', 'issue-type', 'label', 'title-prefix'] as const;\n\nexport type ContainerSignal = (typeof CONTAINER_SIGNALS)[number];\n\nexport interface ContainerConfig {\n detect: readonly ContainerSignal[];\n issueTypes: readonly string[];\n labels: readonly string[];\n titlePrefixes: readonly string[];\n}\n\n/** Unconfigured repos: only the universal signal. */\nexport const DEFAULT_CONTAINER_CONFIG: ContainerConfig = {\n detect: ['children'],\n issueTypes: ['Epic'],\n labels: ['epic'],\n titlePrefixes: ['[Epic]'],\n};\n\nexport interface ContainerProbe {\n issue: Pick<Issue, 'title' | 'labels' | 'type'> | null;\n children: readonly string[];\n}\n\nfunction enabled(config: ContainerConfig, signal: ContainerSignal): boolean {\n return config.detect.includes(signal);\n}\n\nfunction hasTitlePrefix(title: string, prefixes: readonly string[]): boolean {\n const trimmed = title.trim();\n return prefixes.some((prefix) => trimmed.toLowerCase().startsWith(prefix.toLowerCase()));\n}\n\n/**\n * Precedence: children > issue type > label > title prefix.\n * Having children always wins when that signal is enabled.\n */\nexport function isContainer(\n probe: ContainerProbe,\n config: ContainerConfig = DEFAULT_CONTAINER_CONFIG,\n): boolean {\n if (enabled(config, 'children') && probe.children.length > 0) return true;\n\n const issue = probe.issue;\n if (issue === null) return false;\n\n if (enabled(config, 'issue-type')) {\n const type = issue.type?.trim();\n if (\n type !== undefined &&\n type !== '' &&\n config.issueTypes.some((name) => name.toLowerCase() === type.toLowerCase())\n ) {\n return true;\n }\n }\n\n if (enabled(config, 'label')) {\n const labels = new Set(issue.labels.map((label) => label.toLowerCase()));\n if (config.labels.some((label) => labels.has(label.toLowerCase()))) return true;\n }\n\n if (enabled(config, 'title-prefix') && hasTitlePrefix(issue.title, config.titlePrefixes)) {\n return true;\n }\n\n return false;\n}\n\n/** Root plus every descendant reached through `children`. */\nexport function collectCascadeIds(graph: DependencyGraph, roots: readonly string[]): string[] {\n const ids = new Set<string>();\n const walk = (id: string): void => {\n if (ids.has(id)) return;\n ids.add(id);\n for (const child of graph.nodes.get(id)?.relations.children ?? []) {\n walk(child);\n }\n };\n for (const root of roots) walk(root);\n return [...ids];\n}\n","import { emptyRelations, uniqueIds } from './relations.js';\nimport type { Issue, IssueRelations } from './types.js';\n\n/**\n * Dependency graph of a set of Issues, built by walking the relations every\n * provider exposes through `fetchRelations`.\n *\n * The traversal is deliberately *structural*: it follows hierarchy\n * (parent/children) and dependencies (blockedBy/blocking) and stops there. Plain\n * mentions are recorded on each node but never expanded — a body citing \"see\n * also #12\" would otherwise drag an unrelated Issue into an execution plan the\n * user is about to confirm.\n */\n\nexport interface DependencyGraphNode {\n id: string;\n relations: IssueRelations;\n /** Distance from the closest root, in traversal hops. */\n depth: number;\n /** The Issue itself, when the caller supplied a way to read it. */\n issue: Issue | null;\n /** True when the node was requested by the user rather than discovered. */\n root: boolean;\n}\n\nexport interface DependencyGraph {\n /** Ids the traversal started from, in the order they were requested. */\n roots: string[];\n /** Every discovered node, keyed by id, in discovery order. */\n nodes: Map<string, DependencyGraphNode>;\n /**\n * Dependency cycles found among the discovered nodes, each listed once,\n * starting at its smallest member. Reported rather than thrown: discovery\n * must still answer, and it is the ordering layer that refuses to run.\n */\n cycles: string[][];\n /** True when a limit stopped the traversal before it was exhausted. */\n truncated: boolean;\n limits: { maxNodes: number; maxDepth: number };\n}\n\n/**\n * Conservative defaults: a real hierarchy is a handful of Issues, and each node\n * costs several API calls. A malformed (or simply huge) graph must not turn a\n * `run` into a rate-limit incident.\n */\nexport const DEFAULT_MAX_NODES = 25;\nexport const DEFAULT_MAX_DEPTH = 3;\n\nexport interface BuildDependencyGraphOptions {\n maxNodes?: number;\n maxDepth?: number;\n /** Issues the caller already resolved, so the traversal does not re-read them. */\n known?: Iterable<Issue>;\n /** Reader for the Issues the traversal discovers. Optional. */\n fetchIssue?: (id: string) => Promise<Issue | null>;\n /** Warning sink for a source that failed. Silent by default. */\n warn?: (message: string) => void;\n}\n\n/** Edge `from` → `to`, meaning \"`from` must be resolved before `to`\". */\nexport interface DependencyEdge {\n from: string;\n to: string;\n}\n\n/**\n * Ordering constraints of a graph: only hierarchy-free, explicit dependencies.\n *\n * `blockedBy`/`blocking` are the two spellings of the same fact, so both are\n * normalized into a single direction here. Parent/child is **not** a constraint\n * — it is a presentation and tie-breaking concern (see `computeExecutionOrder`),\n * because a parent is usually an umbrella that neither blocks nor is blocked by\n * its children.\n *\n * Edges pointing outside the graph are dropped: an Issue the traversal never\n * reached cannot be scheduled, so a constraint against it is unenforceable.\n */\nexport function dependencyEdges(graph: DependencyGraph): DependencyEdge[] {\n const edges: DependencyEdge[] = [];\n const seen = new Set<string>();\n\n const add = (from: string, to: string): void => {\n if (from === to || !graph.nodes.has(from) || !graph.nodes.has(to)) return;\n const key = `${from}->${to}`;\n if (seen.has(key)) return;\n seen.add(key);\n edges.push({ from, to });\n };\n\n for (const node of graph.nodes.values()) {\n for (const blocker of node.relations.blockedBy) {\n add(blocker, node.id);\n }\n for (const blocked of node.relations.blocking) {\n add(node.id, blocked);\n }\n }\n\n return edges;\n}\n\n/** Every cycle of the dependency edges, each reported once. */\nexport function findCycles(graph: DependencyGraph): string[][] {\n const adjacency = new Map<string, string[]>();\n for (const { from, to } of dependencyEdges(graph)) {\n const list = adjacency.get(from);\n if (list === undefined) {\n adjacency.set(from, [to]);\n } else {\n list.push(to);\n }\n }\n\n const cycles: string[][] = [];\n const reported = new Set<string>();\n const state = new Map<string, 'visiting' | 'done'>();\n const stack: string[] = [];\n\n const record = (cycle: string[]): void => {\n // Rotate to the smallest member so the same cycle found from two entry\n // points is reported once.\n let pivot = 0;\n for (let i = 1; i < cycle.length; i++) {\n if (compareIds(cycle[i] as string, cycle[pivot] as string) < 0) pivot = i;\n }\n const normalized = [...cycle.slice(pivot), ...cycle.slice(0, pivot)];\n const key = normalized.join('->');\n if (reported.has(key)) return;\n reported.add(key);\n cycles.push(normalized);\n };\n\n const visit = (id: string): void => {\n state.set(id, 'visiting');\n stack.push(id);\n\n for (const next of adjacency.get(id) ?? []) {\n const status = state.get(next);\n if (status === 'visiting') {\n record(stack.slice(stack.indexOf(next)));\n continue;\n }\n if (status === undefined) {\n visit(next);\n }\n }\n\n stack.pop();\n state.set(id, 'done');\n };\n\n for (const id of graph.nodes.keys()) {\n if (!state.has(id)) visit(id);\n }\n\n return cycles;\n}\n\n/** Numeric when both ids are numbers, lexicographic otherwise. */\nexport function compareIds(a: string, b: string): number {\n const na = Number(a);\n const nb = Number(b);\n if (Number.isInteger(na) && Number.isInteger(nb)) {\n return na - nb;\n }\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/** Ids a node links to structurally, in traversal order. */\nfunction neighbours(relations: IssueRelations): string[] {\n return uniqueIds(\n [\n ...(relations.parent === null ? [] : [relations.parent]),\n ...relations.children,\n ...relations.blockedBy,\n ...relations.blocking,\n ],\n relations.id,\n );\n}\n\n/**\n * Walk the relations of `roots` breadth-first and return the whole structure.\n *\n * Failures never abort the walk: a node whose relations cannot be read enters\n * the graph with empty relations (and a warning), exactly like an Issue that\n * genuinely relates to nothing. That keeps a partially unavailable API from\n * costing the user the Issues that *were* discovered.\n *\n * Breadth-first is what makes `depth` meaningful under a limit: when the walk is\n * truncated, what is missing is the far edge of the graph, not a random subset.\n */\nexport async function buildDependencyGraph(\n roots: readonly string[],\n fetchRelations: (id: string) => Promise<IssueRelations | null>,\n options: BuildDependencyGraphOptions = {},\n): Promise<DependencyGraph> {\n const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;\n const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;\n const warn = options.warn ?? ((): void => {});\n\n const known = new Map<string, Issue>();\n for (const issue of options.known ?? []) {\n known.set(issue.id, issue);\n }\n\n const rootIds = uniqueIds(roots);\n const nodes = new Map<string, DependencyGraphNode>();\n const queued = new Set<string>(rootIds);\n let truncated = false;\n\n let frontier: { id: string; depth: number }[] = rootIds.map((id) => ({ id, depth: 0 }));\n\n while (frontier.length > 0) {\n const next: { id: string; depth: number }[] = [];\n\n for (const { id, depth } of frontier) {\n if (nodes.size >= maxNodes) {\n truncated = true;\n break;\n }\n\n let relations: IssueRelations | null = null;\n try {\n relations = await fetchRelations(id);\n } catch (err) {\n warn(\n `Could not read the relations of issue '${id}': ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let issue = known.get(id) ?? null;\n if (issue === null && options.fetchIssue !== undefined) {\n try {\n issue = await options.fetchIssue(id);\n } catch (err) {\n warn(`Could not read issue '${id}': ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n const resolved = relations ?? emptyRelations(id);\n nodes.set(id, { id, relations: resolved, depth, issue, root: rootIds.includes(id) });\n\n if (depth >= maxDepth) {\n // Only a node that actually has unexplored neighbours truncates the\n // graph; a leaf at max depth explored everything there was.\n if (neighbours(resolved).some((neighbour) => !queued.has(neighbour))) {\n truncated = true;\n }\n continue;\n }\n\n for (const neighbour of neighbours(resolved)) {\n if (queued.has(neighbour)) continue;\n queued.add(neighbour);\n next.push({ id: neighbour, depth: depth + 1 });\n }\n }\n\n if (nodes.size >= maxNodes && next.length > 0) {\n truncated = true;\n break;\n }\n frontier = next;\n }\n\n const graph: DependencyGraph = {\n roots: rootIds,\n nodes,\n cycles: [],\n truncated,\n limits: { maxNodes, maxDepth },\n };\n\n linkReferencedBy(graph);\n graph.cycles = findCycles(graph);\n\n return graph;\n}\n\n/**\n * Complete `referencedBy` with the inverse of `references` among the discovered\n * nodes.\n *\n * The origin only reports the citations it can see (GitHub's timeline covers\n * cross-references but not, for instance, a mention added by the textual\n * heuristic); inverting what the graph already holds costs no API call and\n * keeps the two directions consistent inside it.\n */\nfunction linkReferencedBy(graph: DependencyGraph): void {\n for (const node of graph.nodes.values()) {\n for (const cited of node.relations.references) {\n const target = graph.nodes.get(cited);\n if (target === undefined || target.id === node.id) continue;\n target.relations.referencedBy = uniqueIds(\n [...target.relations.referencedBy, node.id],\n target.id,\n );\n }\n }\n}\n","import { compareIds, type DependencyGraph, dependencyEdges, findCycles } from '../issues/graph.js';\nimport type { IssuePriority } from './types.js';\n\n/**\n * Turning a dependency graph into the order the queue runs in.\n *\n * The criteria are applied in the exact precedence the initiative specifies:\n *\n * 1. **dependencies and blocks** — hard constraints; an Issue never starts\n * before something it depends on has finished. This is the topological part\n * and the only one that can make the computation fail (a cycle);\n * 2. **hierarchy** — with two Issues both ready to run, the ancestor goes\n * first, so an umbrella Issue frames the work of its sub-issues;\n * 3. **priority** — the GitHub labels `high`, `medium` and `low`;\n * 4. **logical order** — the identifier, numerically when it is a number.\n *\n * Everything below the first item is a *tie-break among Issues that are already\n * free to run*, never a constraint: a `high` Issue that depends on a `low` one\n * still runs second, which is the whole point of the feature.\n */\n\nconst PRIORITY_LABELS: Record<string, IssuePriority> = {\n high: 'high',\n medium: 'medium',\n low: 'low',\n};\n\n/** Ranking of a priority; an Issue with no label sorts after every labelled one. */\nconst PRIORITY_RANK: Record<IssuePriority, number> = { high: 0, medium: 1, low: 2 };\nconst NO_PRIORITY_RANK = 3;\n\n/**\n * Priority carried by a label set, or `null` when there is none.\n *\n * Both the bare labels used by this repository (`high`) and the qualified\n * spellings other projects use (`priority: high`, `priority/high`, `P-high`)\n * are accepted; anything else is simply not a priority. The absence of a label\n * is never an error — most Issues have none.\n */\nexport function priorityOf(labels: readonly string[]): IssuePriority | null {\n for (const label of labels) {\n const normalized = label.trim().toLowerCase();\n const direct = PRIORITY_LABELS[normalized];\n if (direct !== undefined) return direct;\n\n const qualified = /^(?:priority|prio|p)\\s*[:/-]\\s*(high|medium|low)$/.exec(normalized);\n const matched = qualified?.[1];\n if (matched !== undefined) return PRIORITY_LABELS[matched] as IssuePriority;\n }\n return null;\n}\n\nexport interface ExecutionOrderOptions {\n /**\n * Subset of the graph to schedule. Defaults to every node — the \"run the\n * whole hierarchy\" answer; the \"only the ones I asked for\" answer passes the\n * requested ids.\n */\n include?: readonly string[];\n}\n\nexport type ExecutionOrderResult =\n | { ok: true; order: string[] }\n | { ok: false; cycles: string[][] };\n\n/** Number of ancestors of `id` inside the selection: 0 for a top-level Issue. */\nfunction hierarchyDepth(\n id: string,\n parentOf: Map<string, string | null>,\n selected: Set<string>,\n): number {\n let depth = 0;\n let current = parentOf.get(id) ?? null;\n const seen = new Set<string>([id]);\n\n while (current !== null && selected.has(current) && !seen.has(current)) {\n depth++;\n seen.add(current);\n current = parentOf.get(current) ?? null;\n }\n\n return depth;\n}\n\n/**\n * Order the selected Issues of a graph.\n *\n * Kahn's algorithm with a deterministic tie-break, so the same graph always\n * produces the same order — a plan the user confirmed must not reshuffle itself\n * on the next run.\n *\n * A cycle is reported, never worked around: an arbitrary order would silently\n * implement an Issue before its dependency, which is exactly the failure this\n * whole feature exists to prevent. Constraints pointing outside the selection\n * are dropped — an Issue that is not going to run cannot block anything.\n */\nexport function computeExecutionOrder(\n graph: DependencyGraph,\n options: ExecutionOrderOptions = {},\n): ExecutionOrderResult {\n const selected = new Set(\n (options.include ?? [...graph.nodes.keys()]).filter((id) => graph.nodes.has(id)),\n );\n\n const parentOf = new Map<string, string | null>();\n const priority = new Map<string, IssuePriority | null>();\n for (const id of selected) {\n const node = graph.nodes.get(id);\n parentOf.set(id, node?.relations.parent ?? null);\n priority.set(id, priorityOf(node?.issue?.labels ?? []));\n }\n\n const incoming = new Map<string, Set<string>>();\n const outgoing = new Map<string, string[]>();\n for (const id of selected) {\n incoming.set(id, new Set());\n outgoing.set(id, []);\n }\n for (const { from, to } of dependencyEdges(graph)) {\n if (!selected.has(from) || !selected.has(to) || from === to) continue;\n incoming.get(to)?.add(from);\n outgoing.get(from)?.push(to);\n }\n\n const depth = new Map<string, number>();\n for (const id of selected) {\n depth.set(id, hierarchyDepth(id, parentOf, selected));\n }\n\n const rank = (id: string): number => {\n const value = priority.get(id) ?? null;\n return value === null ? NO_PRIORITY_RANK : PRIORITY_RANK[value];\n };\n\n const better = (a: string, b: string): number => {\n const byHierarchy = (depth.get(a) ?? 0) - (depth.get(b) ?? 0);\n if (byHierarchy !== 0) return byHierarchy;\n const byPriority = rank(a) - rank(b);\n if (byPriority !== 0) return byPriority;\n return compareIds(a, b);\n };\n\n const ready = [...selected].filter((id) => (incoming.get(id)?.size ?? 0) === 0);\n const order: string[] = [];\n\n while (ready.length > 0) {\n ready.sort(better);\n const next = ready.shift() as string;\n order.push(next);\n\n for (const dependent of outgoing.get(next) ?? []) {\n const pending = incoming.get(dependent);\n if (pending === undefined) continue;\n pending.delete(next);\n if (pending.size === 0) {\n ready.push(dependent);\n }\n }\n }\n\n if (order.length !== selected.size) {\n // Prefer the cycles the graph already recorded; recompute only if the\n // selection is what closed the loop.\n const cycles = graph.cycles.length > 0 ? graph.cycles : findCycles(graph);\n const relevant = cycles.filter((cycle) => cycle.every((id) => selected.has(id)));\n return { ok: false, cycles: relevant.length > 0 ? relevant : cycles };\n }\n\n return { ok: true, order };\n}\n\n/** One-line description of the cycles, for an error message. */\nexport function describeCycles(cycles: readonly string[][]): string {\n return cycles.map((cycle) => [...cycle, cycle[0]].map((id) => `#${id}`).join(' → ')).join('; ');\n}\n","import type { PipelineState, TaskPlan } from '../types.js';\nimport { loadTaskPlan, saveTaskPlan } from './state-manager.js';\n\n/**\n * Ordered pipeline phases. Each phase must complete before the next can start.\n *\n * `pr-review` is deliberately NOT here: it is opt-in via `--pr-review`. Adding\n * it to the default set would make every existing `tasks.json` (which has no\n * `prReviewCompleted`) look unfinished, so `getNextPhase()` would resume into a\n * phase the user never asked for.\n */\nexport const PIPELINE_PHASES = ['init', 'prd', 'plan', 'execute', 'review', 'pr'] as const;\n\n/**\n * The default set plus the opt-in `pr-review` phase, used when the user passes\n * `--pr-review` (or the flag was persisted in `plan.prReview.enabled`).\n */\nexport const PIPELINE_PHASES_WITH_PR_REVIEW = [...PIPELINE_PHASES, 'pr-review'] as const;\n\nexport type PipelinePhase = (typeof PIPELINE_PHASES_WITH_PR_REVIEW)[number];\n\n/**\n * Pipeline phases excluding the 'pr' phase, for --no-branch mode.\n * `pr-review` is excluded too: without a branch there is no PR to review.\n */\nexport const PIPELINE_PHASES_NO_BRANCH = PIPELINE_PHASES.filter(\n (p): p is Exclude<PipelinePhase, 'pr' | 'pr-review'> => p !== 'pr',\n);\n\n/**\n * Map pipeline phases to their corresponding PipelineState field.\n * 'init' has no persisted state — it's a runtime-only check.\n */\nconst PHASE_TO_FIELD: Record<PipelinePhase, keyof PipelineState | null> = {\n init: null,\n prd: 'prdCompleted',\n plan: 'jsonCompleted',\n execute: 'executionCompleted',\n review: 'reviewCompleted',\n pr: 'prCreated',\n 'pr-review': 'prReviewCompleted',\n};\n\nexport class PipelineManager {\n private tasksJsonPath: string;\n private plan: TaskPlan;\n private activePhases: readonly PipelinePhase[];\n\n constructor(plan: TaskPlan, tasksJsonPath: string, activePhases?: readonly PipelinePhase[]) {\n this.plan = plan;\n this.tasksJsonPath = tasksJsonPath;\n this.activePhases = activePhases ?? PIPELINE_PHASES;\n }\n\n /**\n * Reload state from disk.\n */\n async reload(): Promise<void> {\n this.plan = await loadTaskPlan(this.tasksJsonPath);\n }\n\n /**\n * Check if a phase is complete.\n */\n isPhaseComplete(phase: PipelinePhase): boolean {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return true; // init is always \"complete\" after running\n return this.plan.pipeline?.[field] ?? false;\n }\n\n /**\n * Get the first incomplete phase in the pipeline.\n * Returns null if all phases are complete.\n */\n getNextPhase(): PipelinePhase | null {\n for (const phase of this.activePhases) {\n if (!this.isPhaseComplete(phase)) {\n return phase;\n }\n }\n return null;\n }\n\n /**\n * Get the current phase (alias for getNextPhase).\n */\n getCurrentPhase(): PipelinePhase | null {\n return this.getNextPhase();\n }\n\n /**\n * Check whether we can resume from a specific phase.\n * All prerequisite phases must be complete.\n */\n canResume(fromPhase: PipelinePhase): boolean {\n const idx = this.activePhases.indexOf(fromPhase);\n if (idx < 0) return false;\n\n // All active phases before fromPhase must be complete\n for (let i = 0; i < idx; i++) {\n if (!this.isPhaseComplete(this.activePhases[i])) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Mark a phase as complete and persist to disk.\n */\n async markPhaseComplete(phase: PipelinePhase): Promise<void> {\n const field = PHASE_TO_FIELD[phase];\n if (field === null) return; // init has no persisted state\n\n this.plan = {\n ...this.plan,\n pipeline: {\n ...(this.plan.pipeline ?? {\n prdCompleted: false,\n jsonCompleted: false,\n executionCompleted: false,\n reviewCompleted: false,\n prCreated: false,\n }),\n [field]: true,\n },\n };\n\n await saveTaskPlan(this.tasksJsonPath, this.plan);\n }\n}\n"],"mappings":"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","names":["parentOf"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/prd.ts"],"sourcesContent":["import { mkdir } from 'node:fs/promises';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { readFileWithGrace, runPhaseWithRetry } from '../core/phase-runner.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { loadTaskPlan, saveTaskPlan } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { issuePlaceholders, resolveCommandIssue } from '../issues/context.js';\nimport type { ResolvedIssue } from '../issues/types.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveIssuePaths } from '../storage/resolve.js';\nimport { printError, printSuccess } from '../ui/logger.js';\nimport { isTransientFailure } from '../utils/retry.js';\n\nexport async function runPrd(issue: string, resolvedIssue?: ResolvedIssue): Promise<number> {\n const issueNumber = issue.replace(/^#/, '');\n const paths = await resolveIssuePaths(issueNumber);\n const prdPath = paths.prdFile;\n\n const resolution = await resolveCommandIssue(issueNumber, resolvedIssue);\n if (!resolution.ok) {\n return resolution.code;\n }\n\n await mkdir(paths.issueDir, { recursive: true });\n\n const template = await loadPrompt('prd');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __ISSUE_NUMBER__: issueNumber,\n __PRD_PATH__: prdPath,\n // Absolute: the analysis lives in the global storage, outside the working\n // directory the agent is started in.\n __ANALYSIS_PATH__: paths.analysisFile,\n ...issuePlaceholders(resolution.resolved),\n });\n\n const outcome = await runPhaseWithRetry({\n phase: 'prd',\n attempt: async () => {\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 25,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n timeoutHistory: {\n phase: 'prd',\n journalFiles: [paths.rotatedEventsFile, paths.eventsFile],\n },\n // json (not text) so the CLI reports usage: the envelope's `result`\n // field carries the same assistant text this phase already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep', 'Write'],\n addDirs: [paths.issueDir],\n statusMessage: `Generating PRD for issue #${issueNumber}...`,\n phase: 'prd',\n permission: 'workspace',\n });\n // One event per attempt; the reducer sums them into the phase total.\n publishPhaseMetrics('prd', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n return {\n ok: false,\n transient: result.retryExhausted !== true && isTransientFailure(1, result.error ?? ''),\n error: `PRD generation failed: ${result.error}`,\n };\n }\n\n // Verify the file was created. A brief grace period absorbs a lag\n // between the Write tool completing and this process seeing it; if it\n // still isn't there, treat it as recoverable and let the phase retry.\n try {\n const content = await readFileWithGrace(prdPath);\n if (content.length < 10) {\n return { ok: false, transient: true, error: 'PRD file was created but appears empty' };\n }\n } catch {\n return { ok: false, transient: true, error: `PRD file was not created at ${prdPath}` };\n }\n\n return { ok: true };\n },\n });\n\n if (!outcome.ok) {\n printError(outcome.error ?? `PRD generation failed for issue #${issueNumber}`);\n return 1;\n }\n\n // Update pipeline state\n const tasksPath = paths.tasksFile;\n try {\n const plan = await loadTaskPlan(tasksPath);\n plan.pipeline.prdCompleted = true;\n await saveTaskPlan(tasksPath, plan);\n } catch {\n // tasks.json may not exist yet\n }\n\n printSuccess(`PRD saved to ${prdPath}`);\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa;AActB,eAAsB,OAAO,OAAe,eAAgD;AAC1F,QAAM,cAAc,MAAM,QAAQ,MAAM,EAAE;AAC1C,QAAM,QAAQ,MAAM,kBAAkB,WAAW;AACjD,QAAM,UAAU,MAAM;AAEtB,QAAM,aAAa,MAAM,oBAAoB,aAAa,aAAa;AACvE,MAAI,CAAC,WAAW,IAAI;AAClB,WAAO,WAAW;AAAA,EACpB;AAEA,QAAM,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAE/C,QAAM,WAAW,MAAM,WAAW,KAAK;AACvC,QAAM,SAAS,kBAAkB,UAAU;AAAA;AAAA;AAAA,IAGzC,GAAI,MAAM,0BAA0B;AAAA,IACpC,kBAAkB;AAAA,IAClB,cAAc;AAAA;AAAA;AAAA,IAGd,mBAAmB,MAAM;AAAA,IACzB,GAAG,kBAAkB,WAAW,QAAQ;AAAA,EAC1C,CAAC;AAED,QAAM,UAAU,MAAM,kBAAkB;AAAA,IACtC,OAAO;AAAA,IACP,SAAS,YAAY;AACnB,YAAM,cAAc,KAAK,IAAI;AAC7B,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B;AAAA,QACA,UAAU;AAAA,QACV,SAAS,iBAAiB,KAAK;AAAA,QAC/B,gBAAgB;AAAA,UACd,OAAO;AAAA,UACP,cAAc,CAAC,MAAM,mBAAmB,MAAM,UAAU;AAAA,QAC1D;AAAA;AAAA;AAAA,QAGA,cAAc;AAAA,QACd,cAAc,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,OAAO;AAAA,QACtD,SAAS,CAAC,MAAM,QAAQ;AAAA,QACxB,eAAe,6BAA6B,WAAW;AAAA,QACvD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AAED,0BAAoB,OAAO,OAAO,MAAM,aAAa,OAAO,OAAO,QAAQ;AAE3E,UAAI,CAAC,OAAO,SAAS;AACnB,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,WAAW,OAAO,mBAAmB,QAAQ,mBAAmB,GAAG,OAAO,SAAS,EAAE;AAAA,UACrF,OAAO,0BAA0B,OAAO,KAAK;AAAA,QAC/C;AAAA,MACF;AAKA,UAAI;AACF,cAAM,UAAU,MAAM,kBAAkB,OAAO;AAC/C,YAAI,QAAQ,SAAS,IAAI;AACvB,iBAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,yCAAyC;AAAA,QACvF;AAAA,MACF,QAAQ;AACN,eAAO,EAAE,IAAI,OAAO,WAAW,MAAM,OAAO,+BAA+B,OAAO,GAAG;AAAA,MACvF;AAEA,aAAO,EAAE,IAAI,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI,CAAC,QAAQ,IAAI;AACf,eAAW,QAAQ,SAAS,oCAAoC,WAAW,EAAE;AAC7E,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAM;AACxB,MAAI;AACF,UAAM,OAAO,MAAM,aAAa,SAAS;AACzC,SAAK,SAAS,eAAe;AAC7B,UAAM,aAAa,WAAW,IAAI;AAAA,EACpC,QAAQ;AAAA,EAER;AAEA,eAAa,gBAAgB,OAAO,EAAE;AACtC,SAAO;AACT;","names":[]}
|