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
|
@@ -0,0 +1,1201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
writeDiagnostic
|
|
4
|
+
} from "./chunk-7CMVL4X6.js";
|
|
5
|
+
import {
|
|
6
|
+
MemoryPublisher,
|
|
7
|
+
getSessionPublisher
|
|
8
|
+
} from "./chunk-DIMDAYZS.js";
|
|
9
|
+
import {
|
|
10
|
+
bindTelemetry
|
|
11
|
+
} from "./chunk-HEHHQU4G.js";
|
|
12
|
+
import {
|
|
13
|
+
METADATA_FILENAME,
|
|
14
|
+
migrateLegacyStorage,
|
|
15
|
+
resolveStorageMode
|
|
16
|
+
} from "./chunk-LYC57OXF.js";
|
|
17
|
+
import {
|
|
18
|
+
isoNow
|
|
19
|
+
} from "./chunk-GD7JZMSJ.js";
|
|
20
|
+
import {
|
|
21
|
+
globalConfigSchema,
|
|
22
|
+
projectMetadataSchema,
|
|
23
|
+
providersHealthSchema,
|
|
24
|
+
storageConfigInputSchema
|
|
25
|
+
} from "./chunk-IMB6FA2F.js";
|
|
26
|
+
import {
|
|
27
|
+
CURRENT_SCHEMA_VERSION,
|
|
28
|
+
getDatabasePath,
|
|
29
|
+
openDatabase,
|
|
30
|
+
openIssueFlowDatabase,
|
|
31
|
+
registerPlanRepository,
|
|
32
|
+
registerProjectDatabaseOptions,
|
|
33
|
+
registerQueueRepository,
|
|
34
|
+
registerStorageProjections,
|
|
35
|
+
resetPlanRepositories,
|
|
36
|
+
resetProjectDatabaseOptions,
|
|
37
|
+
writePlanRows
|
|
38
|
+
} from "./chunk-7MJGYPHF.js";
|
|
39
|
+
import {
|
|
40
|
+
taskPlanSchema
|
|
41
|
+
} from "./chunk-XNK5UN2D.js";
|
|
42
|
+
import {
|
|
43
|
+
EVENTS_FILENAME,
|
|
44
|
+
GLOBAL_ROOT_ENV,
|
|
45
|
+
ISSUES_DIR_NAME,
|
|
46
|
+
PROVIDERS_HEALTH_FILENAME,
|
|
47
|
+
QUEUES_DIR_NAME,
|
|
48
|
+
ROTATED_EVENTS_FILENAME,
|
|
49
|
+
RUN_LOCK_FILENAME,
|
|
50
|
+
VERIFY_FILENAME,
|
|
51
|
+
getGlobalRoot,
|
|
52
|
+
getIssuePathsAt,
|
|
53
|
+
getQueuePathsAt
|
|
54
|
+
} from "./chunk-MAJ6QDRL.js";
|
|
55
|
+
import {
|
|
56
|
+
getProjectRoot
|
|
57
|
+
} from "./chunk-TJVX42KY.js";
|
|
58
|
+
|
|
59
|
+
// src/core/verbose.ts
|
|
60
|
+
var _verbose = false;
|
|
61
|
+
function setVerbose(enabled) {
|
|
62
|
+
_verbose = enabled;
|
|
63
|
+
}
|
|
64
|
+
function isVerbose() {
|
|
65
|
+
return _verbose;
|
|
66
|
+
}
|
|
67
|
+
var _onOutput;
|
|
68
|
+
function setOutputCallback(callback) {
|
|
69
|
+
_onOutput = callback;
|
|
70
|
+
}
|
|
71
|
+
function getOutputCallback() {
|
|
72
|
+
return _onOutput;
|
|
73
|
+
}
|
|
74
|
+
var _onStoryUpdate;
|
|
75
|
+
function setStoryUpdateCallback(callback) {
|
|
76
|
+
_onStoryUpdate = callback;
|
|
77
|
+
}
|
|
78
|
+
function getStoryUpdateCallback() {
|
|
79
|
+
return _onStoryUpdate;
|
|
80
|
+
}
|
|
81
|
+
var _onStoryStage;
|
|
82
|
+
function setStoryStageCallback(callback) {
|
|
83
|
+
_onStoryStage = callback;
|
|
84
|
+
}
|
|
85
|
+
function getStoryStageCallback() {
|
|
86
|
+
return _onStoryStage;
|
|
87
|
+
}
|
|
88
|
+
var _onActivity;
|
|
89
|
+
function setActivityCallback(callback) {
|
|
90
|
+
_onActivity = callback;
|
|
91
|
+
}
|
|
92
|
+
function getActivityCallback() {
|
|
93
|
+
return _onActivity;
|
|
94
|
+
}
|
|
95
|
+
var _globalTimeout;
|
|
96
|
+
function setGlobalTimeout(ms) {
|
|
97
|
+
_globalTimeout = ms;
|
|
98
|
+
}
|
|
99
|
+
function getGlobalTimeout() {
|
|
100
|
+
return _globalTimeout;
|
|
101
|
+
}
|
|
102
|
+
var _inactivityTimeout;
|
|
103
|
+
function setInactivityTimeout(ms) {
|
|
104
|
+
_inactivityTimeout = ms;
|
|
105
|
+
}
|
|
106
|
+
function getInactivityTimeout() {
|
|
107
|
+
return _inactivityTimeout;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/ui/logger.ts
|
|
111
|
+
import chalk from "chalk";
|
|
112
|
+
import ora from "ora";
|
|
113
|
+
function useUnicode() {
|
|
114
|
+
if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
|
|
115
|
+
if (!process.stdout.isTTY) return false;
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
function useColor() {
|
|
119
|
+
if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "") return false;
|
|
120
|
+
if (!process.stdout.isTTY) return false;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
function getIcons() {
|
|
124
|
+
if (useUnicode()) {
|
|
125
|
+
return {
|
|
126
|
+
success: "\u2713",
|
|
127
|
+
fail: "\u2717",
|
|
128
|
+
pending: "\u23F3",
|
|
129
|
+
retry: "\u21BB",
|
|
130
|
+
warn: "\u26A0",
|
|
131
|
+
start: "\u25B6",
|
|
132
|
+
end: "\u25A0",
|
|
133
|
+
notReached: "\u25CB",
|
|
134
|
+
tool: "\u25B8",
|
|
135
|
+
connector: "\u2502",
|
|
136
|
+
info: "\xB7"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
success: "[OK]",
|
|
141
|
+
fail: "[FAIL]",
|
|
142
|
+
pending: "[...]",
|
|
143
|
+
retry: "[RETRY]",
|
|
144
|
+
warn: "[WARN]",
|
|
145
|
+
start: "[START]",
|
|
146
|
+
end: "[END]",
|
|
147
|
+
notReached: "[ ]",
|
|
148
|
+
tool: ">",
|
|
149
|
+
connector: "|",
|
|
150
|
+
info: "-"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function getTermWidth() {
|
|
154
|
+
return process.stdout.columns ?? 80;
|
|
155
|
+
}
|
|
156
|
+
function emit(line, level = "info") {
|
|
157
|
+
getSessionPublisher().publish({ type: "log", at: isoNow(), level, message: line });
|
|
158
|
+
writeDiagnostic({
|
|
159
|
+
level: level === "warn" ? "warning" : level,
|
|
160
|
+
message: line
|
|
161
|
+
});
|
|
162
|
+
const cb = getOutputCallback();
|
|
163
|
+
if (cb) {
|
|
164
|
+
cb(line);
|
|
165
|
+
} else {
|
|
166
|
+
console.log(line);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function printSuccess(message) {
|
|
170
|
+
const icons = getIcons();
|
|
171
|
+
if (useColor()) {
|
|
172
|
+
emit(chalk.green(`${icons.success} ${message}`));
|
|
173
|
+
} else {
|
|
174
|
+
emit(`${icons.success} ${message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function printError(message) {
|
|
178
|
+
const icons = getIcons();
|
|
179
|
+
if (useColor()) {
|
|
180
|
+
emit(chalk.red(`${icons.fail} ${message}`), "error");
|
|
181
|
+
} else {
|
|
182
|
+
emit(`${icons.fail} ${message}`, "error");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function printWarning(message) {
|
|
186
|
+
const icons = getIcons();
|
|
187
|
+
if (useColor()) {
|
|
188
|
+
emit(chalk.yellow(`${icons.warn} ${message}`), "warn");
|
|
189
|
+
} else {
|
|
190
|
+
emit(`${icons.warn} ${message}`, "warn");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function printRetry(message) {
|
|
194
|
+
const icons = getIcons();
|
|
195
|
+
if (useColor()) {
|
|
196
|
+
emit(chalk.yellow(`${icons.retry} ${message}`), "warn");
|
|
197
|
+
} else {
|
|
198
|
+
emit(`${icons.retry} ${message}`, "warn");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function printInfo(message) {
|
|
202
|
+
const icons = getIcons();
|
|
203
|
+
if (useColor()) {
|
|
204
|
+
emit(chalk.blue(`${icons.info} ${message}`));
|
|
205
|
+
} else {
|
|
206
|
+
emit(`${icons.info} ${message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function createSpinner(message) {
|
|
210
|
+
return ora({
|
|
211
|
+
text: message,
|
|
212
|
+
color: "blue",
|
|
213
|
+
spinner: "dots"
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function formatDuration(totalSeconds) {
|
|
217
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
218
|
+
const mins = Math.floor(totalSeconds % 3600 / 60);
|
|
219
|
+
const secs = totalSeconds % 60;
|
|
220
|
+
if (hours > 0) {
|
|
221
|
+
return `${hours}h ${mins}m ${secs}s`;
|
|
222
|
+
}
|
|
223
|
+
if (mins > 0) {
|
|
224
|
+
return `${mins}m ${secs}s`;
|
|
225
|
+
}
|
|
226
|
+
return `${secs}s`;
|
|
227
|
+
}
|
|
228
|
+
var ElapsedTimer = class {
|
|
229
|
+
constructor(onTick) {
|
|
230
|
+
this.onTick = onTick;
|
|
231
|
+
this.startTime = Date.now();
|
|
232
|
+
}
|
|
233
|
+
onTick;
|
|
234
|
+
startTime;
|
|
235
|
+
intervalId = null;
|
|
236
|
+
cleanup = () => this.stop();
|
|
237
|
+
start() {
|
|
238
|
+
this.startTime = Date.now();
|
|
239
|
+
this.intervalId = setInterval(() => {
|
|
240
|
+
const seconds = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
241
|
+
this.onTick(formatDuration(seconds));
|
|
242
|
+
}, 1e3);
|
|
243
|
+
if (this.intervalId && typeof this.intervalId === "object" && "unref" in this.intervalId) {
|
|
244
|
+
this.intervalId.unref();
|
|
245
|
+
}
|
|
246
|
+
process.on("exit", this.cleanup);
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
getElapsedSeconds() {
|
|
250
|
+
return Math.floor((Date.now() - this.startTime) / 1e3);
|
|
251
|
+
}
|
|
252
|
+
stop() {
|
|
253
|
+
if (this.intervalId) {
|
|
254
|
+
clearInterval(this.intervalId);
|
|
255
|
+
this.intervalId = null;
|
|
256
|
+
}
|
|
257
|
+
process.removeListener("exit", this.cleanup);
|
|
258
|
+
return this.getElapsedSeconds();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/config/sources.ts
|
|
263
|
+
import { existsSync } from "node:fs";
|
|
264
|
+
import { readFile } from "node:fs/promises";
|
|
265
|
+
import { dirname, join } from "node:path";
|
|
266
|
+
var PROJECT_CONFIG_FILENAME = ".issue-flow.json";
|
|
267
|
+
function findProjectRootFromCwd(start = process.cwd()) {
|
|
268
|
+
let dir = start;
|
|
269
|
+
while (true) {
|
|
270
|
+
if (existsSync(join(dir, PROJECT_CONFIG_FILENAME)) || existsSync(join(dir, ".git"))) {
|
|
271
|
+
return dir;
|
|
272
|
+
}
|
|
273
|
+
const parent = dirname(dir);
|
|
274
|
+
if (parent === dir) return void 0;
|
|
275
|
+
dir = parent;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function readProjectConfigFile(projectRoot, warn) {
|
|
279
|
+
const root = projectRoot ?? findProjectRootFromCwd();
|
|
280
|
+
if (root === void 0) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const filePath = join(root, PROJECT_CONFIG_FILENAME);
|
|
284
|
+
let raw;
|
|
285
|
+
try {
|
|
286
|
+
raw = await readFile(filePath, "utf-8");
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
let parsed;
|
|
291
|
+
try {
|
|
292
|
+
parsed = JSON.parse(raw);
|
|
293
|
+
} catch {
|
|
294
|
+
warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: invalid JSON.`);
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
298
|
+
warn(`Ignoring ${PROJECT_CONFIG_FILENAME}: expected a JSON object.`);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
return parsed;
|
|
302
|
+
}
|
|
303
|
+
var GLOBAL_CONFIG_FILENAME = "config.json";
|
|
304
|
+
async function loadGlobalConfig(options = {}) {
|
|
305
|
+
const warn = options.warn ?? printWarning;
|
|
306
|
+
let root;
|
|
307
|
+
try {
|
|
308
|
+
root = options.globalRoot ?? getGlobalRoot({ env: options.env ?? process.env });
|
|
309
|
+
} catch (err) {
|
|
310
|
+
warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${err.message}`);
|
|
311
|
+
return {};
|
|
312
|
+
}
|
|
313
|
+
const filePath = join(root, GLOBAL_CONFIG_FILENAME);
|
|
314
|
+
let raw;
|
|
315
|
+
try {
|
|
316
|
+
raw = await readFile(filePath, "utf-8");
|
|
317
|
+
} catch (err) {
|
|
318
|
+
if (err.code !== "ENOENT") {
|
|
319
|
+
warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: ${err.message}`);
|
|
320
|
+
}
|
|
321
|
+
return {};
|
|
322
|
+
}
|
|
323
|
+
let parsed;
|
|
324
|
+
try {
|
|
325
|
+
parsed = JSON.parse(raw);
|
|
326
|
+
} catch {
|
|
327
|
+
warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: invalid JSON.`);
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
331
|
+
warn(`Ignoring ${GLOBAL_CONFIG_FILENAME}: expected a JSON object.`);
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
const config = {};
|
|
335
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
336
|
+
const result = globalConfigSchema.safeParse({ [key]: value });
|
|
337
|
+
if (!result.success) {
|
|
338
|
+
warn(
|
|
339
|
+
`Ignoring "${key}" key of ${GLOBAL_CONFIG_FILENAME}: ${result.error.issues[0]?.message ?? "invalid value"}.`
|
|
340
|
+
);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
Object.assign(config, result.data);
|
|
344
|
+
}
|
|
345
|
+
return config;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/core/journal.ts
|
|
349
|
+
import { appendFile, mkdir, rename, stat, unlink } from "node:fs/promises";
|
|
350
|
+
import { dirname as dirname2 } from "node:path";
|
|
351
|
+
var DEFAULT_JOURNAL_MAX_BYTES = 0;
|
|
352
|
+
var JournalPublisher = class extends MemoryPublisher {
|
|
353
|
+
filePath;
|
|
354
|
+
rotatedPath;
|
|
355
|
+
maxFileBytes;
|
|
356
|
+
seq = 0;
|
|
357
|
+
writeChain = Promise.resolve();
|
|
358
|
+
closed = false;
|
|
359
|
+
/** Bytes in the current file. `null` until the first write measures it. */
|
|
360
|
+
bytes = null;
|
|
361
|
+
constructor(filePath, rotatedPath, options = {}) {
|
|
362
|
+
super(options);
|
|
363
|
+
this.filePath = filePath;
|
|
364
|
+
this.rotatedPath = rotatedPath;
|
|
365
|
+
this.maxFileBytes = options.maxFileBytes ?? DEFAULT_JOURNAL_MAX_BYTES;
|
|
366
|
+
}
|
|
367
|
+
afterPublish(event) {
|
|
368
|
+
if (this.closed) return;
|
|
369
|
+
this.seq++;
|
|
370
|
+
const line = `${JSON.stringify({ seq: this.seq, event })}
|
|
371
|
+
`;
|
|
372
|
+
this.enqueue(line);
|
|
373
|
+
}
|
|
374
|
+
enqueue(line) {
|
|
375
|
+
this.writeChain = this.writeChain.then(async () => {
|
|
376
|
+
try {
|
|
377
|
+
await this.appendLine(line);
|
|
378
|
+
} catch (err) {
|
|
379
|
+
this.warnOnce(err);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async appendLine(line) {
|
|
384
|
+
const size = Buffer.byteLength(line);
|
|
385
|
+
if (this.bytes === null) {
|
|
386
|
+
await mkdir(dirname2(this.filePath), { recursive: true });
|
|
387
|
+
this.bytes = await currentSize(this.filePath);
|
|
388
|
+
}
|
|
389
|
+
if (this.maxFileBytes > 0 && this.bytes > 0 && this.bytes + size > this.maxFileBytes) {
|
|
390
|
+
await this.rotate();
|
|
391
|
+
}
|
|
392
|
+
await appendFile(this.filePath, line, "utf-8");
|
|
393
|
+
this.bytes += size;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Keep exactly one previous generation only when a caller explicitly asks
|
|
397
|
+
* for rotation. The default never enters this path, so history is never
|
|
398
|
+
* discarded without an explicit policy.
|
|
399
|
+
*/
|
|
400
|
+
async rotate() {
|
|
401
|
+
try {
|
|
402
|
+
await unlink(this.rotatedPath);
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
await rename(this.filePath, this.rotatedPath);
|
|
406
|
+
this.bytes = 0;
|
|
407
|
+
}
|
|
408
|
+
async flush() {
|
|
409
|
+
await this.writeChain;
|
|
410
|
+
}
|
|
411
|
+
async close() {
|
|
412
|
+
await this.flush();
|
|
413
|
+
this.closed = true;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
async function currentSize(path) {
|
|
417
|
+
try {
|
|
418
|
+
return (await stat(path)).size;
|
|
419
|
+
} catch {
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function parseJournal(content) {
|
|
424
|
+
const entries = [];
|
|
425
|
+
for (const line of content.split("\n")) {
|
|
426
|
+
const trimmed = line.trim();
|
|
427
|
+
if (trimmed === "") continue;
|
|
428
|
+
let parsed;
|
|
429
|
+
try {
|
|
430
|
+
parsed = JSON.parse(trimmed);
|
|
431
|
+
} catch {
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (!isEntry(parsed)) continue;
|
|
435
|
+
entries.push(parsed);
|
|
436
|
+
}
|
|
437
|
+
return entries;
|
|
438
|
+
}
|
|
439
|
+
function isEntry(value) {
|
|
440
|
+
if (typeof value !== "object" || value === null) return false;
|
|
441
|
+
const candidate = value;
|
|
442
|
+
if (typeof candidate.seq !== "number") return false;
|
|
443
|
+
const event = candidate.event;
|
|
444
|
+
return typeof event === "object" && event !== null && typeof event.type === "string";
|
|
445
|
+
}
|
|
446
|
+
var MultiPublisher = class {
|
|
447
|
+
members;
|
|
448
|
+
primary;
|
|
449
|
+
constructor(members) {
|
|
450
|
+
if (members.length === 0) {
|
|
451
|
+
throw new Error("MultiPublisher needs at least one member");
|
|
452
|
+
}
|
|
453
|
+
this.members = members;
|
|
454
|
+
this.primary = members[0];
|
|
455
|
+
}
|
|
456
|
+
publish(event) {
|
|
457
|
+
for (const member of this.members) {
|
|
458
|
+
member.publish(event);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
snapshot() {
|
|
462
|
+
return this.primary.snapshot();
|
|
463
|
+
}
|
|
464
|
+
version() {
|
|
465
|
+
return this.primary.version();
|
|
466
|
+
}
|
|
467
|
+
async flush() {
|
|
468
|
+
await Promise.all(this.members.map((member) => member.flush()));
|
|
469
|
+
}
|
|
470
|
+
async close() {
|
|
471
|
+
await Promise.all(this.members.map((member) => member.close()));
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// src/storage/db/import.ts
|
|
476
|
+
import { createHash } from "node:crypto";
|
|
477
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
478
|
+
import { readdir, readFile as readFile2, rename as rename2 } from "node:fs/promises";
|
|
479
|
+
import { basename, join as join2, relative } from "node:path";
|
|
480
|
+
var EMPTY_COUNTS = {
|
|
481
|
+
projects: 0,
|
|
482
|
+
issues: 0,
|
|
483
|
+
pipelines: 0,
|
|
484
|
+
stories: 0,
|
|
485
|
+
executions: 0,
|
|
486
|
+
events: 0,
|
|
487
|
+
queues: 0,
|
|
488
|
+
provider_health: 0,
|
|
489
|
+
verifications: 0,
|
|
490
|
+
pull_requests: 0,
|
|
491
|
+
user_story_numbering: 0
|
|
492
|
+
};
|
|
493
|
+
function digest(content) {
|
|
494
|
+
return createHash("sha256").update(content).digest("hex");
|
|
495
|
+
}
|
|
496
|
+
function stableId(prefix, value) {
|
|
497
|
+
return `${prefix}-${digest(value).slice(0, 24)}`;
|
|
498
|
+
}
|
|
499
|
+
async function fileIfPresent(path) {
|
|
500
|
+
try {
|
|
501
|
+
return await readFile2(path, "utf-8");
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (error.code === "ENOENT") return null;
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function collectArtifacts(projectDir, withEvents) {
|
|
508
|
+
const artifacts = [];
|
|
509
|
+
const addJson = async (path, kind, issueId) => {
|
|
510
|
+
const content = await fileIfPresent(path);
|
|
511
|
+
if (content === null) return;
|
|
512
|
+
artifacts.push({
|
|
513
|
+
path,
|
|
514
|
+
relativePath: relative(projectDir, path),
|
|
515
|
+
sha256: digest(content),
|
|
516
|
+
kind,
|
|
517
|
+
issueId,
|
|
518
|
+
value: JSON.parse(content)
|
|
519
|
+
});
|
|
520
|
+
};
|
|
521
|
+
await addJson(join2(projectDir, METADATA_FILENAME), "metadata");
|
|
522
|
+
await addJson(join2(projectDir, PROVIDERS_HEALTH_FILENAME), "health");
|
|
523
|
+
const issuesDir = join2(projectDir, ISSUES_DIR_NAME);
|
|
524
|
+
let issueEntries = [];
|
|
525
|
+
try {
|
|
526
|
+
issueEntries = await readdir(issuesDir);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (error.code !== "ENOENT") throw error;
|
|
529
|
+
}
|
|
530
|
+
for (const issueId of issueEntries) {
|
|
531
|
+
const issueDir = join2(issuesDir, issueId);
|
|
532
|
+
await addJson(join2(issueDir, "tasks.json"), "tasks", issueId);
|
|
533
|
+
await addJson(join2(issueDir, VERIFY_FILENAME), "verify", issueId);
|
|
534
|
+
if (withEvents) {
|
|
535
|
+
for (const name of [ROTATED_EVENTS_FILENAME, EVENTS_FILENAME]) {
|
|
536
|
+
const path = join2(issueDir, name);
|
|
537
|
+
const content = await fileIfPresent(path);
|
|
538
|
+
if (content !== null) {
|
|
539
|
+
artifacts.push({
|
|
540
|
+
path,
|
|
541
|
+
relativePath: relative(projectDir, path),
|
|
542
|
+
sha256: digest(content),
|
|
543
|
+
kind: "events",
|
|
544
|
+
issueId,
|
|
545
|
+
value: parseJournal(content)
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const queuesDir = join2(projectDir, QUEUES_DIR_NAME);
|
|
552
|
+
let queueEntries = [];
|
|
553
|
+
try {
|
|
554
|
+
queueEntries = await readdir(queuesDir);
|
|
555
|
+
} catch (error) {
|
|
556
|
+
if (error.code !== "ENOENT") throw error;
|
|
557
|
+
}
|
|
558
|
+
for (const queueId of queueEntries) {
|
|
559
|
+
await addJson(join2(queuesDir, queueId, "execution-plan.json"), "queue");
|
|
560
|
+
}
|
|
561
|
+
return artifacts;
|
|
562
|
+
}
|
|
563
|
+
function increment(counts, table) {
|
|
564
|
+
counts[table] = (counts[table] ?? 0) + 1;
|
|
565
|
+
}
|
|
566
|
+
function insertIssue(database, projectId, id, status, title, branchName, timestamp, counts) {
|
|
567
|
+
database.prepare(
|
|
568
|
+
`INSERT INTO issues (project_id, id, title, status, branch_name, created_at, updated_at)
|
|
569
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
570
|
+
ON CONFLICT(project_id, id) DO UPDATE SET
|
|
571
|
+
title = excluded.title, status = excluded.status, branch_name = excluded.branch_name,
|
|
572
|
+
updated_at = excluded.updated_at`
|
|
573
|
+
).run(projectId, id, title, status, branchName, timestamp, timestamp);
|
|
574
|
+
increment(counts, "issues");
|
|
575
|
+
}
|
|
576
|
+
function importArtifact(database, artifact, projectId, fallback, counts) {
|
|
577
|
+
const value = artifact.value;
|
|
578
|
+
if (artifact.kind === "metadata") {
|
|
579
|
+
const metadata = projectMetadataSchema.parse(value);
|
|
580
|
+
database.prepare(
|
|
581
|
+
`INSERT INTO projects (id, root, remote_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?)
|
|
582
|
+
ON CONFLICT(id) DO UPDATE SET root = excluded.root, remote_url = excluded.remote_url,
|
|
583
|
+
updated_at = excluded.updated_at`
|
|
584
|
+
).run(projectId, metadata.root, metadata.remoteUrl, metadata.createdAt, metadata.updatedAt);
|
|
585
|
+
increment(counts, "projects");
|
|
586
|
+
if (metadata.userStoryNumbering !== void 0) {
|
|
587
|
+
const decision = metadata.userStoryNumbering;
|
|
588
|
+
database.prepare(
|
|
589
|
+
`INSERT INTO user_story_numbering (project_id, next_number, source, issue_id, decided_at, detail)
|
|
590
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
591
|
+
ON CONFLICT(project_id) DO UPDATE SET next_number = excluded.next_number,
|
|
592
|
+
source = excluded.source, issue_id = excluded.issue_id, decided_at = excluded.decided_at,
|
|
593
|
+
detail = excluded.detail`
|
|
594
|
+
).run(
|
|
595
|
+
projectId,
|
|
596
|
+
decision.nextNumber,
|
|
597
|
+
decision.source,
|
|
598
|
+
decision.issueNumber,
|
|
599
|
+
decision.decidedAt,
|
|
600
|
+
decision.detail ?? null
|
|
601
|
+
);
|
|
602
|
+
increment(counts, "user_story_numbering");
|
|
603
|
+
}
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (artifact.kind === "tasks") {
|
|
607
|
+
const parsed = taskPlanSchema.safeParse(value);
|
|
608
|
+
if (!parsed.success) return;
|
|
609
|
+
const plan = parsed.data;
|
|
610
|
+
const issueId = artifact.issueId ?? String(plan.issueNumber);
|
|
611
|
+
writePlanRows(
|
|
612
|
+
database,
|
|
613
|
+
{
|
|
614
|
+
tasksPath: artifact.path,
|
|
615
|
+
projectId,
|
|
616
|
+
issueId,
|
|
617
|
+
projectRoot: fallback.root
|
|
618
|
+
},
|
|
619
|
+
plan
|
|
620
|
+
);
|
|
621
|
+
increment(counts, "issues");
|
|
622
|
+
increment(counts, "pipelines");
|
|
623
|
+
counts.stories += plan.userStories.length;
|
|
624
|
+
for (const execution of plan.executions ?? []) {
|
|
625
|
+
const cost = execution.cost.status === "unknown" ? null : execution.cost.amount;
|
|
626
|
+
database.prepare(
|
|
627
|
+
`INSERT INTO executions
|
|
628
|
+
(id, project_id, issue_id, status, started_at, finished_at, duration_ms, cost_status, cost_amount, payload_json)
|
|
629
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
630
|
+
ON CONFLICT(id) DO UPDATE SET status = excluded.status, finished_at = excluded.finished_at,
|
|
631
|
+
duration_ms = excluded.duration_ms, cost_status = excluded.cost_status,
|
|
632
|
+
cost_amount = excluded.cost_amount, payload_json = excluded.payload_json`
|
|
633
|
+
).run(
|
|
634
|
+
execution.id,
|
|
635
|
+
projectId,
|
|
636
|
+
issueId,
|
|
637
|
+
execution.status,
|
|
638
|
+
execution.startedAt,
|
|
639
|
+
execution.finishedAt,
|
|
640
|
+
execution.durationMs,
|
|
641
|
+
execution.cost.status,
|
|
642
|
+
cost,
|
|
643
|
+
JSON.stringify(execution)
|
|
644
|
+
);
|
|
645
|
+
increment(counts, "executions");
|
|
646
|
+
}
|
|
647
|
+
if (plan.pullRequest !== void 0) {
|
|
648
|
+
const id = stableId("pr", `${projectId}:${issueId}:${plan.pullRequest.number}`);
|
|
649
|
+
database.prepare(
|
|
650
|
+
`INSERT INTO pull_requests (id, project_id, issue_id, number, url, status, created_at)
|
|
651
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
652
|
+
ON CONFLICT(id) DO UPDATE SET url = excluded.url, status = excluded.status`
|
|
653
|
+
).run(
|
|
654
|
+
id,
|
|
655
|
+
projectId,
|
|
656
|
+
issueId,
|
|
657
|
+
plan.pullRequest.number,
|
|
658
|
+
plan.pullRequest.url,
|
|
659
|
+
"created",
|
|
660
|
+
plan.pullRequest.createdAt
|
|
661
|
+
);
|
|
662
|
+
increment(counts, "pull_requests");
|
|
663
|
+
}
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (artifact.kind === "health") {
|
|
667
|
+
const health = providersHealthSchema.parse(value);
|
|
668
|
+
for (const [provider, record] of Object.entries(health.providers)) {
|
|
669
|
+
database.prepare(
|
|
670
|
+
`INSERT INTO provider_health (project_id, provider, payload_json, updated_at) VALUES (?, ?, ?, ?)
|
|
671
|
+
ON CONFLICT(project_id, provider) DO UPDATE SET payload_json = excluded.payload_json,
|
|
672
|
+
updated_at = excluded.updated_at`
|
|
673
|
+
).run(projectId, provider, JSON.stringify(record), fallback.timestamp);
|
|
674
|
+
increment(counts, "provider_health");
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (artifact.kind === "events") {
|
|
679
|
+
const issueId = artifact.issueId ?? "unknown";
|
|
680
|
+
const runId = `legacy:${projectId}:${issueId}`;
|
|
681
|
+
insertIssue(database, projectId, issueId, "imported", null, null, fallback.timestamp, counts);
|
|
682
|
+
database.prepare(
|
|
683
|
+
`INSERT INTO runs (id, project_id, issue_id, status, started_at, finished_at, session_id)
|
|
684
|
+
VALUES (?, ?, ?, 'imported', ?, ?, ?)
|
|
685
|
+
ON CONFLICT(id) DO NOTHING`
|
|
686
|
+
).run(runId, projectId, issueId, fallback.timestamp, fallback.timestamp, runId);
|
|
687
|
+
let nextSequence = (database.prepare("SELECT COALESCE(MAX(sequence), 0) AS sequence FROM events WHERE run_id = ?").get(runId)?.sequence ?? 0) + 1;
|
|
688
|
+
for (const [index, entry] of artifact.value.entries()) {
|
|
689
|
+
const id = stableId("event", `${artifact.relativePath}:${index}`);
|
|
690
|
+
const event = entry.event;
|
|
691
|
+
database.prepare(
|
|
692
|
+
`INSERT INTO events
|
|
693
|
+
(id, project_id, run_id, occurred_at, kind, payload_json, session_id, sequence)
|
|
694
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
695
|
+
ON CONFLICT(id) DO UPDATE SET occurred_at = excluded.occurred_at, kind = excluded.kind,
|
|
696
|
+
payload_json = excluded.payload_json`
|
|
697
|
+
).run(
|
|
698
|
+
id,
|
|
699
|
+
projectId,
|
|
700
|
+
runId,
|
|
701
|
+
event.at ?? fallback.timestamp,
|
|
702
|
+
event.type,
|
|
703
|
+
JSON.stringify(entry),
|
|
704
|
+
runId,
|
|
705
|
+
nextSequence++
|
|
706
|
+
);
|
|
707
|
+
increment(counts, "events");
|
|
708
|
+
}
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (artifact.kind === "verify") {
|
|
712
|
+
insertIssue(
|
|
713
|
+
database,
|
|
714
|
+
projectId,
|
|
715
|
+
artifact.issueId ?? "unknown",
|
|
716
|
+
"imported",
|
|
717
|
+
null,
|
|
718
|
+
null,
|
|
719
|
+
fallback.timestamp,
|
|
720
|
+
counts
|
|
721
|
+
);
|
|
722
|
+
const id = stableId("verify", artifact.relativePath);
|
|
723
|
+
database.prepare(
|
|
724
|
+
`INSERT INTO verifications (id, project_id, issue_id, status, created_at, payload_json)
|
|
725
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
726
|
+
ON CONFLICT(id) DO UPDATE SET status = excluded.status, payload_json = excluded.payload_json`
|
|
727
|
+
).run(
|
|
728
|
+
id,
|
|
729
|
+
projectId,
|
|
730
|
+
artifact.issueId ?? null,
|
|
731
|
+
String(value.verdict ?? "unknown"),
|
|
732
|
+
fallback.timestamp,
|
|
733
|
+
JSON.stringify(value)
|
|
734
|
+
);
|
|
735
|
+
increment(counts, "verifications");
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const queue = value;
|
|
739
|
+
database.prepare(
|
|
740
|
+
`INSERT INTO queues (id, project_id, status, payload_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
741
|
+
ON CONFLICT(id) DO UPDATE SET status = excluded.status, payload_json = excluded.payload_json,
|
|
742
|
+
updated_at = excluded.updated_at`
|
|
743
|
+
).run(
|
|
744
|
+
queue.id,
|
|
745
|
+
projectId,
|
|
746
|
+
queue.status,
|
|
747
|
+
JSON.stringify(queue),
|
|
748
|
+
queue.createdAt,
|
|
749
|
+
queue.updatedAt
|
|
750
|
+
);
|
|
751
|
+
increment(counts, "queues");
|
|
752
|
+
for (const entry of queue.issues) {
|
|
753
|
+
insertIssue(database, projectId, entry.id, entry.status, null, null, queue.updatedAt, counts);
|
|
754
|
+
database.prepare(
|
|
755
|
+
`INSERT INTO queue_issues (queue_id, project_id, issue_id, position, status) VALUES (?, ?, ?, ?, ?)
|
|
756
|
+
ON CONFLICT(queue_id, project_id, issue_id) DO UPDATE SET position = excluded.position,
|
|
757
|
+
status = excluded.status`
|
|
758
|
+
).run(queue.id, projectId, entry.id, entry.position, entry.status);
|
|
759
|
+
}
|
|
760
|
+
if (queue.pullRequest !== void 0) {
|
|
761
|
+
const id = stableId("pr", `${projectId}:queue:${queue.id}:${queue.pullRequest.number}`);
|
|
762
|
+
database.prepare(
|
|
763
|
+
`INSERT INTO pull_requests (id, project_id, number, url, status, created_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
764
|
+
ON CONFLICT(id) DO UPDATE SET url = excluded.url, status = excluded.status`
|
|
765
|
+
).run(
|
|
766
|
+
id,
|
|
767
|
+
projectId,
|
|
768
|
+
queue.pullRequest.number,
|
|
769
|
+
queue.pullRequest.url,
|
|
770
|
+
"created",
|
|
771
|
+
queue.pullRequest.createdAt
|
|
772
|
+
);
|
|
773
|
+
increment(counts, "pull_requests");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async function retainBackups(directory, retention) {
|
|
777
|
+
let names = [];
|
|
778
|
+
try {
|
|
779
|
+
names = (await readdir(directory)).filter(
|
|
780
|
+
(name) => name.startsWith("issue-flow-") && name.endsWith(".db")
|
|
781
|
+
);
|
|
782
|
+
} catch {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
for (const name of names.sort().slice(0, Math.max(0, names.length - retention))) {
|
|
786
|
+
await import("node:fs/promises").then(
|
|
787
|
+
({ unlink: unlink2 }) => unlink2(join2(directory, name)).catch(() => void 0)
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function applyRetention(database, projectId, retention) {
|
|
792
|
+
const trim = (table, limit) => {
|
|
793
|
+
if (limit === void 0 || limit === 0) return;
|
|
794
|
+
const timestamp = table === "executions" ? "started_at" : table === "events" ? "occurred_at" : "updated_at";
|
|
795
|
+
database.prepare(
|
|
796
|
+
`DELETE FROM ${table} WHERE id IN (
|
|
797
|
+
SELECT id FROM ${table} WHERE project_id = ?
|
|
798
|
+
ORDER BY ${timestamp} DESC, rowid DESC LIMIT -1 OFFSET ?
|
|
799
|
+
)`
|
|
800
|
+
).run(projectId, limit);
|
|
801
|
+
};
|
|
802
|
+
trim("executions", retention?.executions);
|
|
803
|
+
trim("events", retention?.events);
|
|
804
|
+
trim("snapshots", retention?.snapshots);
|
|
805
|
+
}
|
|
806
|
+
var DEFAULT_BACKUP_RETENTION = 5;
|
|
807
|
+
async function prepareDatabase(options) {
|
|
808
|
+
const path = getDatabasePath(options);
|
|
809
|
+
if (!existsSync2(path)) return;
|
|
810
|
+
const raw = await openDatabase(path, options);
|
|
811
|
+
try {
|
|
812
|
+
const integrity = raw.integrityCheck();
|
|
813
|
+
if (integrity !== "ok") {
|
|
814
|
+
raw.close();
|
|
815
|
+
const quarantine = `${path}.corrupt-${Date.now()}`;
|
|
816
|
+
await rename2(path, quarantine);
|
|
817
|
+
options.onWarning?.(
|
|
818
|
+
`SQLite integrity check failed; isolated ${path} as ${quarantine} and will re-import preserved JSON artifacts.`
|
|
819
|
+
);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const version = Number(
|
|
823
|
+
raw.prepare("PRAGMA user_version").get()?.user_version ?? 0
|
|
824
|
+
);
|
|
825
|
+
const backupDirectory = join2(getDatabasePath(options).replace(/[/\\][^/\\]+$/, ""), "backups");
|
|
826
|
+
if (version < CURRENT_SCHEMA_VERSION) {
|
|
827
|
+
const backup = join2(backupDirectory, `issue-flow-${Date.now()}.db`);
|
|
828
|
+
raw.backup(backup);
|
|
829
|
+
}
|
|
830
|
+
await retainBackups(backupDirectory, options.backupRetention ?? DEFAULT_BACKUP_RETENTION);
|
|
831
|
+
} finally {
|
|
832
|
+
try {
|
|
833
|
+
raw.close();
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
async function importProjectArtifacts(options) {
|
|
839
|
+
const counts = { ...EMPTY_COUNTS };
|
|
840
|
+
try {
|
|
841
|
+
await prepareDatabase(options);
|
|
842
|
+
const database = await openIssueFlowDatabase(options);
|
|
843
|
+
try {
|
|
844
|
+
const adopted = database.prepare("SELECT project_id FROM project_imports WHERE project_id = ?").get(options.projectId);
|
|
845
|
+
const artifacts = (await collectArtifacts(options.projectDir, options.withEvents === true)).filter((artifact) => {
|
|
846
|
+
if (adopted === void 0) return true;
|
|
847
|
+
if (options.withEvents === true) return artifact.kind === "events";
|
|
848
|
+
return artifact.kind === "tasks";
|
|
849
|
+
});
|
|
850
|
+
let imported = 0;
|
|
851
|
+
let skipped = 0;
|
|
852
|
+
database.transaction(() => {
|
|
853
|
+
database.prepare(
|
|
854
|
+
`INSERT INTO projects (id, root, remote_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?)
|
|
855
|
+
ON CONFLICT(id) DO UPDATE SET root = excluded.root, remote_url = excluded.remote_url,
|
|
856
|
+
updated_at = excluded.updated_at`
|
|
857
|
+
).run(
|
|
858
|
+
options.projectId,
|
|
859
|
+
options.projectRoot,
|
|
860
|
+
options.remoteUrl,
|
|
861
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
862
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
863
|
+
);
|
|
864
|
+
increment(counts, "projects");
|
|
865
|
+
for (const artifact of artifacts) {
|
|
866
|
+
const previous = database.prepare("SELECT sha256 FROM migrated_artifacts WHERE source_path = ?").get(artifact.path);
|
|
867
|
+
const requiresPlanProjection = artifact.kind === "tasks" && taskPlanSchema.safeParse(artifact.value).success && database.prepare("SELECT project FROM pipelines WHERE project_id = ? AND issue_id = ?").get(options.projectId, artifact.issueId ?? "")?.project === null;
|
|
868
|
+
if (previous?.sha256 === artifact.sha256 && !requiresPlanProjection) {
|
|
869
|
+
skipped++;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
importArtifact(
|
|
873
|
+
database,
|
|
874
|
+
artifact,
|
|
875
|
+
options.projectId,
|
|
876
|
+
{
|
|
877
|
+
root: options.projectRoot,
|
|
878
|
+
remoteUrl: options.remoteUrl,
|
|
879
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
880
|
+
},
|
|
881
|
+
counts
|
|
882
|
+
);
|
|
883
|
+
database.prepare(
|
|
884
|
+
`INSERT INTO migrated_artifacts (source_path, sha256, migrated_at, table_counts_json)
|
|
885
|
+
VALUES (?, ?, ?, ?)
|
|
886
|
+
ON CONFLICT(source_path) DO UPDATE SET sha256 = excluded.sha256,
|
|
887
|
+
migrated_at = excluded.migrated_at, table_counts_json = excluded.table_counts_json`
|
|
888
|
+
).run(artifact.path, artifact.sha256, (/* @__PURE__ */ new Date()).toISOString(), JSON.stringify(counts));
|
|
889
|
+
imported++;
|
|
890
|
+
}
|
|
891
|
+
applyRetention(database, options.projectId, options.retention);
|
|
892
|
+
database.prepare(
|
|
893
|
+
`INSERT INTO project_imports (project_id, completed_at) VALUES (?, ?)
|
|
894
|
+
ON CONFLICT(project_id) DO NOTHING`
|
|
895
|
+
).run(options.projectId, (/* @__PURE__ */ new Date()).toISOString());
|
|
896
|
+
});
|
|
897
|
+
return { imported, skipped, tableCounts: counts, failed: false };
|
|
898
|
+
} finally {
|
|
899
|
+
database.close();
|
|
900
|
+
}
|
|
901
|
+
} catch (error) {
|
|
902
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
903
|
+
if (message.includes("newer than this Issue Flow supports") || message.includes("SQLite storage requires Node.js")) {
|
|
904
|
+
throw error;
|
|
905
|
+
}
|
|
906
|
+
const path = getDatabasePath(options);
|
|
907
|
+
if (existsSync2(path)) {
|
|
908
|
+
const failed = `${path}.failed-${Date.now()}`;
|
|
909
|
+
try {
|
|
910
|
+
await rename2(path, failed);
|
|
911
|
+
options.onWarning?.(
|
|
912
|
+
`SQLite import failed; preserved the database for diagnosis as ${basename(failed)}. Continuing with the intact JSON storage.`
|
|
913
|
+
);
|
|
914
|
+
} catch {
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
options.onWarning?.(`SQLite import skipped: ${message}`);
|
|
918
|
+
return { imported: 0, skipped: 0, tableCounts: counts, failed: true };
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/storage/resolve.ts
|
|
923
|
+
import { stat as stat3 } from "node:fs/promises";
|
|
924
|
+
import { basename as basename2, join as join4, resolve } from "node:path";
|
|
925
|
+
|
|
926
|
+
// src/storage/artifact-storage.ts
|
|
927
|
+
import { readFile as readFile3, stat as stat2, writeFile } from "node:fs/promises";
|
|
928
|
+
import { join as join3 } from "node:path";
|
|
929
|
+
var WORKSPACE_STORAGE_DIR = ".issue-flow";
|
|
930
|
+
var WORKSPACE_IGNORE_BLOCK = [
|
|
931
|
+
"# Issue Flow operational storage (managed)",
|
|
932
|
+
"/issues/",
|
|
933
|
+
"/queues/",
|
|
934
|
+
"/issue-flow.db",
|
|
935
|
+
"/issue-flow.db-*",
|
|
936
|
+
"/run.lock",
|
|
937
|
+
"/providers.json",
|
|
938
|
+
"/metadata.json",
|
|
939
|
+
"/backups/",
|
|
940
|
+
"/.gitignore"
|
|
941
|
+
].join("\n");
|
|
942
|
+
async function directoryExists(path) {
|
|
943
|
+
try {
|
|
944
|
+
return (await stat2(path)).isDirectory();
|
|
945
|
+
} catch {
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function selectArtifactStorage(projectRoot, globalRoot, projectId, workspaceSelected) {
|
|
950
|
+
return workspaceSelected ? { storageMode: "workspace", projectDir: join3(projectRoot, WORKSPACE_STORAGE_DIR) } : { storageMode: "global", projectDir: join3(globalRoot, "projects", projectId) };
|
|
951
|
+
}
|
|
952
|
+
async function ensureWorkspaceStorageIgnored(projectRoot) {
|
|
953
|
+
const path = join3(projectRoot, WORKSPACE_STORAGE_DIR, ".gitignore");
|
|
954
|
+
let current = "";
|
|
955
|
+
try {
|
|
956
|
+
current = await readFile3(path, "utf8");
|
|
957
|
+
} catch (error) {
|
|
958
|
+
if (error.code !== "ENOENT") throw error;
|
|
959
|
+
}
|
|
960
|
+
if (WORKSPACE_IGNORE_BLOCK.split("\n").slice(1).every((line) => current.includes(line)))
|
|
961
|
+
return;
|
|
962
|
+
const prefix = current.length === 0 ? "" : `${current.trimEnd()}
|
|
963
|
+
|
|
964
|
+
`;
|
|
965
|
+
await writeFile(path, `${prefix}${WORKSPACE_IGNORE_BLOCK}
|
|
966
|
+
`, "utf8");
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/storage/resolve.ts
|
|
970
|
+
async function loadStorageResolutionConfig(projectRoot, options) {
|
|
971
|
+
const global = await loadGlobalConfig({ env: options.env, warn: options.notice ?? printInfo });
|
|
972
|
+
const project = await readProjectConfigFile(projectRoot, options.notice ?? printInfo);
|
|
973
|
+
const parsedProject = storageConfigInputSchema.safeParse(project?.storage);
|
|
974
|
+
if (!parsedProject.success && project?.storage !== void 0) {
|
|
975
|
+
(options.notice ?? printInfo)(
|
|
976
|
+
`Ignoring "storage" key of ${PROJECT_CONFIG_FILENAME}: ${parsedProject.error.issues[0]?.message ?? "invalid value"}.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
return {
|
|
980
|
+
driver: parsedProject.success ? parsedProject.data.driver ?? global.storage?.driver ?? "sqlite" : global.storage?.driver ?? "sqlite",
|
|
981
|
+
backupRetention: (parsedProject.success ? parsedProject.data.backupRetention : void 0) ?? global.storage?.backupRetention,
|
|
982
|
+
retention: (parsedProject.success ? parsedProject.data.retention : void 0) ?? global.storage?.retention
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
var projectCache = /* @__PURE__ */ new Map();
|
|
986
|
+
var checkedIssues = /* @__PURE__ */ new Set();
|
|
987
|
+
function resetStorageResolutionCache() {
|
|
988
|
+
projectCache.clear();
|
|
989
|
+
checkedIssues.clear();
|
|
990
|
+
resetPlanRepositories();
|
|
991
|
+
resetProjectDatabaseOptions();
|
|
992
|
+
}
|
|
993
|
+
async function pathExists(path) {
|
|
994
|
+
try {
|
|
995
|
+
await stat3(path);
|
|
996
|
+
return true;
|
|
997
|
+
} catch {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function databaseOptionsFor(projectRoot, storageMode, options) {
|
|
1002
|
+
if (storageMode === "global") return options.env === void 0 ? {} : { env: options.env };
|
|
1003
|
+
return {
|
|
1004
|
+
env: {
|
|
1005
|
+
...options.env ?? process.env,
|
|
1006
|
+
[GLOBAL_ROOT_ENV]: join4(projectRoot, WORKSPACE_STORAGE_DIR)
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function announceMigration(result, notice = printInfo) {
|
|
1011
|
+
if (result.copied.length === 0) return;
|
|
1012
|
+
const count = result.copied.length;
|
|
1013
|
+
const target = join4(result.globalDir, ISSUES_DIR_NAME);
|
|
1014
|
+
notice(`Migrated ${count} file${count === 1 ? "" : "s"} from ${result.legacyDir} to ${target}`);
|
|
1015
|
+
notice(
|
|
1016
|
+
`The legacy directory was not modified or removed \u2014 ${result.legacyDir} is kept as-is, read-only, for compatibility.`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
async function resolveProject(projectRoot, options) {
|
|
1020
|
+
const status = await resolveStorageMode(projectRoot, options);
|
|
1021
|
+
const storage = await loadStorageResolutionConfig(projectRoot, options);
|
|
1022
|
+
const workspaceIssuesDir = join4(projectRoot, WORKSPACE_STORAGE_DIR, ISSUES_DIR_NAME);
|
|
1023
|
+
const selected = selectArtifactStorage(
|
|
1024
|
+
projectRoot,
|
|
1025
|
+
getGlobalRoot(options),
|
|
1026
|
+
status.projectId,
|
|
1027
|
+
await directoryExists(workspaceIssuesDir)
|
|
1028
|
+
);
|
|
1029
|
+
const { storageMode, projectDir } = selected;
|
|
1030
|
+
const databaseOptions = databaseOptionsFor(projectRoot, storageMode, options);
|
|
1031
|
+
registerProjectDatabaseOptions(status.projectId, databaseOptions);
|
|
1032
|
+
if (storageMode === "workspace") {
|
|
1033
|
+
await ensureWorkspaceStorageIgnored(projectRoot);
|
|
1034
|
+
} else if (status.mode === "needs-migration") {
|
|
1035
|
+
announceMigration(
|
|
1036
|
+
await migrateLegacyStorage(projectRoot, options),
|
|
1037
|
+
options.notice ?? printInfo
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const imported = storage.driver === "sqlite" && !await pathExists(join4(projectDir, RUN_LOCK_FILENAME)) ? await importProjectArtifacts({
|
|
1041
|
+
...databaseOptions,
|
|
1042
|
+
projectId: status.projectId,
|
|
1043
|
+
projectDir,
|
|
1044
|
+
projectRoot,
|
|
1045
|
+
remoteUrl: status.remoteUrl,
|
|
1046
|
+
...(storage.retention?.backups ?? storage.backupRetention) === void 0 ? {} : { backupRetention: storage.retention?.backups ?? storage.backupRetention },
|
|
1047
|
+
...storage.retention === void 0 ? {} : { retention: storage.retention },
|
|
1048
|
+
onWarning: options.notice ?? printInfo
|
|
1049
|
+
}) : null;
|
|
1050
|
+
if (imported !== null && imported.imported > 0) {
|
|
1051
|
+
const counts = Object.entries(imported.tableCounts).filter(([, count]) => count > 0).map(([table, count]) => `${table}: ${count}`).join(", ");
|
|
1052
|
+
(options.notice ?? printInfo)(
|
|
1053
|
+
`Imported ${imported.imported} structured artifact${imported.imported === 1 ? "" : "s"} from ${projectDir} into ${getDatabasePath(databaseOptions)} (${counts || "no rows"}). No source artifacts were removed.`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
projectId: status.projectId,
|
|
1058
|
+
legacyDir: status.legacyDir,
|
|
1059
|
+
projectDir,
|
|
1060
|
+
storageMode,
|
|
1061
|
+
databaseOptions,
|
|
1062
|
+
// An import failure is a successful *resolution* through the JSON
|
|
1063
|
+
// compatibility path. Do not subsequently register a SQLite repository,
|
|
1064
|
+
// or telemetry would create a fresh empty database and hide the fallback.
|
|
1065
|
+
driver: imported?.failed === true ? "json" : storage.driver,
|
|
1066
|
+
retention: storage.retention
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function getProjectResolution(projectRoot, options) {
|
|
1070
|
+
const key = `${getGlobalRoot(options)}::${projectRoot}`;
|
|
1071
|
+
const cached = projectCache.get(key);
|
|
1072
|
+
if (cached) return cached;
|
|
1073
|
+
const pending = resolveProject(projectRoot, options).catch((err) => {
|
|
1074
|
+
projectCache.delete(key);
|
|
1075
|
+
throw err;
|
|
1076
|
+
});
|
|
1077
|
+
projectCache.set(key, pending);
|
|
1078
|
+
return pending;
|
|
1079
|
+
}
|
|
1080
|
+
async function resolveProjectPaths(options = {}) {
|
|
1081
|
+
const { projectRoot: projectRootOption, ...rootOptions } = options;
|
|
1082
|
+
const projectRoot = resolve(projectRootOption ?? await getProjectRoot());
|
|
1083
|
+
const project = await getProjectResolution(projectRoot, rootOptions);
|
|
1084
|
+
const projectDir = project.projectDir;
|
|
1085
|
+
const providersHealthFile = join4(projectDir, PROVIDERS_HEALTH_FILENAME);
|
|
1086
|
+
if (project.driver === "sqlite") {
|
|
1087
|
+
registerStorageProjections({
|
|
1088
|
+
context: {
|
|
1089
|
+
tasksPath: "",
|
|
1090
|
+
projectId: project.projectId,
|
|
1091
|
+
issueId: "",
|
|
1092
|
+
projectRoot,
|
|
1093
|
+
databaseOptions: project.databaseOptions,
|
|
1094
|
+
retention: project.retention
|
|
1095
|
+
},
|
|
1096
|
+
providersHealthFile
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
projectId: project.projectId,
|
|
1101
|
+
storageDriver: project.driver,
|
|
1102
|
+
storageMode: project.storageMode,
|
|
1103
|
+
databaseOptions: project.databaseOptions,
|
|
1104
|
+
projectDir,
|
|
1105
|
+
issuesDir: join4(projectDir, ISSUES_DIR_NAME),
|
|
1106
|
+
runLockFile: join4(projectDir, RUN_LOCK_FILENAME),
|
|
1107
|
+
providersHealthFile
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
async function resolveQueuePaths(queueId, options = {}) {
|
|
1111
|
+
const { projectRoot: projectRootOption, ...rootOptions } = options;
|
|
1112
|
+
const projectRoot = resolve(projectRootOption ?? await getProjectRoot());
|
|
1113
|
+
const project = await getProjectResolution(projectRoot, rootOptions);
|
|
1114
|
+
const paths = getQueuePathsAt(project.projectDir, queueId);
|
|
1115
|
+
if (project.driver === "sqlite") {
|
|
1116
|
+
registerQueueRepository({
|
|
1117
|
+
planFile: paths.planFile,
|
|
1118
|
+
projectId: project.projectId,
|
|
1119
|
+
projectRoot,
|
|
1120
|
+
databaseOptions: project.databaseOptions,
|
|
1121
|
+
retention: project.retention
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
return paths;
|
|
1125
|
+
}
|
|
1126
|
+
async function resolveIssuePaths(issueNumber, options = {}) {
|
|
1127
|
+
const { projectRoot: projectRootOption, ...rootOptions } = options;
|
|
1128
|
+
const projectRoot = resolve(projectRootOption ?? await getProjectRoot());
|
|
1129
|
+
const project = await getProjectResolution(projectRoot, rootOptions);
|
|
1130
|
+
const paths = getIssuePathsAt(project.projectDir, issueNumber);
|
|
1131
|
+
if (!checkedIssues.has(paths.issueDir)) {
|
|
1132
|
+
const legacyIssueDir = join4(project.legacyDir, basename2(paths.issueDir));
|
|
1133
|
+
if (project.storageMode === "global" && !await directoryExists(paths.issueDir) && await directoryExists(legacyIssueDir)) {
|
|
1134
|
+
announceMigration(await migrateLegacyStorage(projectRoot, rootOptions));
|
|
1135
|
+
}
|
|
1136
|
+
checkedIssues.add(paths.issueDir);
|
|
1137
|
+
}
|
|
1138
|
+
if (project.driver === "sqlite") {
|
|
1139
|
+
const context = {
|
|
1140
|
+
tasksPath: paths.tasksFile,
|
|
1141
|
+
projectId: project.projectId,
|
|
1142
|
+
issueId: basename2(paths.issueDir),
|
|
1143
|
+
projectRoot,
|
|
1144
|
+
databaseOptions: project.databaseOptions,
|
|
1145
|
+
retention: project.retention
|
|
1146
|
+
};
|
|
1147
|
+
registerPlanRepository(context);
|
|
1148
|
+
registerStorageProjections({
|
|
1149
|
+
context,
|
|
1150
|
+
verifyFile: paths.verifyFile,
|
|
1151
|
+
lastBranchFile: paths.lastBranchFile,
|
|
1152
|
+
providersHealthFile: join4(project.projectDir, PROVIDERS_HEALTH_FILENAME)
|
|
1153
|
+
});
|
|
1154
|
+
bindTelemetry({ tasksPath: paths.tasksFile });
|
|
1155
|
+
} else {
|
|
1156
|
+
bindTelemetry(null);
|
|
1157
|
+
}
|
|
1158
|
+
return paths;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export {
|
|
1162
|
+
setVerbose,
|
|
1163
|
+
isVerbose,
|
|
1164
|
+
setOutputCallback,
|
|
1165
|
+
getOutputCallback,
|
|
1166
|
+
setStoryUpdateCallback,
|
|
1167
|
+
getStoryUpdateCallback,
|
|
1168
|
+
setStoryStageCallback,
|
|
1169
|
+
getStoryStageCallback,
|
|
1170
|
+
setActivityCallback,
|
|
1171
|
+
getActivityCallback,
|
|
1172
|
+
setGlobalTimeout,
|
|
1173
|
+
getGlobalTimeout,
|
|
1174
|
+
setInactivityTimeout,
|
|
1175
|
+
getInactivityTimeout,
|
|
1176
|
+
useUnicode,
|
|
1177
|
+
useColor,
|
|
1178
|
+
getIcons,
|
|
1179
|
+
getTermWidth,
|
|
1180
|
+
printSuccess,
|
|
1181
|
+
printError,
|
|
1182
|
+
printWarning,
|
|
1183
|
+
printRetry,
|
|
1184
|
+
printInfo,
|
|
1185
|
+
createSpinner,
|
|
1186
|
+
formatDuration,
|
|
1187
|
+
ElapsedTimer,
|
|
1188
|
+
PROJECT_CONFIG_FILENAME,
|
|
1189
|
+
readProjectConfigFile,
|
|
1190
|
+
GLOBAL_CONFIG_FILENAME,
|
|
1191
|
+
loadGlobalConfig,
|
|
1192
|
+
JournalPublisher,
|
|
1193
|
+
parseJournal,
|
|
1194
|
+
MultiPublisher,
|
|
1195
|
+
importProjectArtifacts,
|
|
1196
|
+
resetStorageResolutionCache,
|
|
1197
|
+
resolveProjectPaths,
|
|
1198
|
+
resolveQueuePaths,
|
|
1199
|
+
resolveIssuePaths
|
|
1200
|
+
};
|
|
1201
|
+
//# sourceMappingURL=chunk-J6VJ5M2U.js.map
|