@ulpi/cli 0.1.5 → 0.1.7
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 +143 -214
- package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
- package/dist/{auth-BFFBUJUC.js → auth-HDK7ECJL.js} +2 -1
- package/dist/{chunk-RJIRWQJD.js → chunk-3BCW6ABU.js} +402 -142
- package/dist/{chunk-L3PWNHSA.js → chunk-3WB5CXH4.js} +180 -5
- package/dist/{chunk-K4OVPFY2.js → chunk-4UCJIAOU.js} +2 -2
- package/dist/chunk-4XTHZVDS.js +109 -0
- package/dist/chunk-4ZPOZULQ.js +6522 -0
- package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
- package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
- package/dist/{chunk-AV5RB3N2.js → chunk-76D3BYJD.js} +48 -0
- package/dist/{chunk-DOIKS6C5.js → chunk-AWOSRA5F.js} +1 -1
- package/dist/{chunk-UCMT5OKP.js → chunk-BFEKZZHM.js} +274 -57
- package/dist/chunk-C7CLUQI6.js +1286 -0
- package/dist/{chunk-ELTGWMDE.js → chunk-E3B5NROU.js} +7 -7
- package/dist/chunk-EJ7TW77N.js +1418 -0
- package/dist/{chunk-P2RESJRN.js → chunk-EWLYVXQ4.js} +2 -2
- package/dist/{chunk-6OURRFP7.js → chunk-IV6MWETF.js} +383 -168
- package/dist/chunk-IZPJHSPX.js +1478 -0
- package/dist/chunk-JLHNLM3C.js +228 -0
- package/dist/chunk-PO4NUZUU.js +147 -0
- package/dist/chunk-S6ANCSYO.js +1271 -0
- package/dist/chunk-SEU7WWNQ.js +1251 -0
- package/dist/chunk-SNQ7NAIS.js +453 -0
- package/dist/{ulpi-RMMCUAGP-EWYUE7RU.js → chunk-TSLDGT5O.js} +73 -35
- package/dist/{chunk-EIWYSP3A.js → chunk-UXHCHOWQ.js} +83 -62
- package/dist/chunk-WED4LM5N.js +322 -0
- package/dist/chunk-WVOZE25N.js +6757 -0
- package/dist/{chunk-5SCG7UYM.js → chunk-XKF4DPUM.js} +7 -7
- package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
- package/dist/chunk-Z53CAR7G.js +298 -0
- package/dist/{ci-JQ56YIKC.js → ci-COZRTPGQ.js} +124 -26
- package/dist/cloud-2F3NLVHN.js +274 -0
- package/dist/{codemap-HMYBXJL2.js → codemap-XNGMAF3F.js} +37 -37
- package/dist/codex-MB5YTMRT.js +132 -0
- package/dist/{config-YYWEN7U2.js → config-OOELBYTH.js} +1 -1
- package/dist/dist-2BJYR5EI.js +59 -0
- package/dist/dist-3EIQTZHT.js +1380 -0
- package/dist/{dist-WAMAQVPK.js → dist-4U5L2X2C.js} +2 -2
- package/dist/{dist-4XTJ6HLM.js → dist-54KAMNLO.js} +16 -15
- package/dist/dist-6M4MZWZW.js +58 -0
- package/dist/dist-6X576SU2.js +27 -0
- package/dist/dist-7QOEYLFX.js +103 -0
- package/dist/dist-AYBGHEDY.js +2541 -0
- package/dist/dist-EK45QNEM.js +45 -0
- package/dist/{dist-U7ZIJMZD.js → dist-FKFEJRPX.js} +16 -15
- package/dist/dist-GTEJUBBT.js +66 -0
- package/dist/dist-HA74OKJZ.js +40 -0
- package/dist/{dist-XG2GG5SD.js → dist-HU5RZAON.js} +14 -2
- package/dist/dist-IYE3OBRB.js +374 -0
- package/dist/{dist-7WLLPWWB.js → dist-JLU26AB6.js} +12 -9
- package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
- package/dist/dist-NUEMFZFL.js +33 -0
- package/dist/{dist-GWGTAHNM.js → dist-NUXMDXZ3.js} +31 -3
- package/dist/{dist-5R4RYNQO.js → dist-YCNWHSLN.js} +15 -5
- package/dist/{dist-6MFVWIFF.js → dist-YFFG2ZD6.js} +9 -16
- package/dist/dist-ZG4OKCSR.js +15 -0
- package/dist/doctor-FKYSIHER.js +345 -0
- package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
- package/dist/{history-RNUWO4JZ.js → history-UMGQNQQ7.js} +7 -7
- package/dist/{hooks-installer-K2JXEBNN.js → hooks-installer-YEYTYA6Q.js} +2 -2
- package/dist/index.js +398 -622
- package/dist/{init-NQWFZPKO.js → init-TJYW5ROZ.js} +78 -12
- package/dist/job-HIDMAFW2.js +376 -0
- package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
- package/dist/kiro-VMUHDFGK.js +153 -0
- package/dist/{launchd-OYXUAVW6.js → launchd-U3MSWBRH.js} +9 -17
- package/dist/mcp-PDUD7SGP.js +249 -0
- package/dist/mcp-installer-PQU3XOGO.js +259 -0
- package/dist/mcp-setup-OA7IB3H3.js +263 -0
- package/dist/{memory-D6ZFFCI2.js → memory-ZNAEAK3B.js} +17 -17
- package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
- package/dist/{openai-E7G2YAHU-IG33BFYF.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
- package/dist/portal-JYWVHXDU.js +210 -0
- package/dist/prd-Q4J5NVAR.js +408 -0
- package/dist/repos-WWZXNN3P.js +271 -0
- package/dist/review-integration-RQE4KMAV.js +14 -0
- package/dist/{rules-3OFGWHP4.js → rules-Y4VSOY5Y.js} +3 -3
- package/dist/run-VPNXEIBY.js +687 -0
- package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
- package/dist/server-U7PQ6FTS-MG4MJPTS.js +20 -0
- package/dist/{skills-GY2CTPWN.js → skills-QEYU2N27.js} +4 -2
- package/dist/start-IJKY5RVT.js +303 -0
- package/dist/{status-SE43TIFJ.js → status-BHQYYGAL.js} +2 -2
- package/dist/{templates-O2XDKB5R.js → templates-CBRUJ66V.js} +6 -5
- package/dist/tui-DP7736EX.js +61 -0
- package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
- package/dist/{uninstall-KWGSGZTI.js → uninstall-BX6FOV77.js} +3 -3
- package/dist/{update-QYZA4D23.js → update-AQKTHFVQ.js} +3 -3
- package/dist/{version-checker-MVB74DEX.js → version-checker-5L5PUOEX.js} +2 -2
- package/package.json +13 -4
- package/dist/chunk-26LLDX2T.js +0 -553
- package/dist/chunk-DDRLI6JU.js +0 -331
- package/dist/chunk-IFATANHR.js +0 -453
- package/dist/chunk-JWUUVXIV.js +0 -13694
- package/dist/chunk-LD52XG3X.js +0 -4273
- package/dist/chunk-MIAQVCFW.js +0 -39
- package/dist/chunk-YYZOFYS6.js +0 -415
- package/dist/dist-XD4YI27T.js +0 -26
- package/dist/mcp-installer-TOYDP77X.js +0 -124
- package/dist/projects-COUJP4ZC.js +0 -271
- package/dist/review-KMGP2S25.js +0 -152
- package/dist/server-USLHY6GH-F4JSXCWA.js +0 -18
- package/dist/server-X5P6WH2M-ULZF5WHZ.js +0 -11
- package/dist/ui-4SM2SUI6.js +0 -167
- package/dist/ui.html +0 -698
- /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/SKILL.md +0 -0
- /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/references/framework-rules.md +0 -0
- /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/references/language-rules.md +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadMcpCatalog
|
|
3
|
+
} from "./chunk-4ZPOZULQ.js";
|
|
4
|
+
import {
|
|
5
|
+
MCPS_LIBRARY_FILE,
|
|
6
|
+
ULPI_GLOBAL_DIR,
|
|
7
|
+
projectMcpsFile
|
|
8
|
+
} from "./chunk-C7CLUQI6.js";
|
|
9
|
+
import {
|
|
10
|
+
McpLibrarySchema,
|
|
11
|
+
McpRepoConfigSchema
|
|
12
|
+
} from "./chunk-YOKL7RB5.js";
|
|
13
|
+
|
|
14
|
+
// ../../packages/mcp-gateway-engine/dist/index.js
|
|
15
|
+
import * as fs from "fs";
|
|
16
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
17
|
+
import * as fs2 from "fs";
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
|
|
20
|
+
import * as fs3 from "fs";
|
|
21
|
+
import * as path2 from "path";
|
|
22
|
+
function loadLibrary() {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(MCPS_LIBRARY_FILE)) {
|
|
25
|
+
return { mcps: {} };
|
|
26
|
+
}
|
|
27
|
+
const raw = fs.readFileSync(MCPS_LIBRARY_FILE, "utf-8");
|
|
28
|
+
const parsed = parseYaml(raw);
|
|
29
|
+
return McpLibrarySchema.parse(parsed ?? {});
|
|
30
|
+
} catch {
|
|
31
|
+
return { mcps: {} };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function saveLibrary(library) {
|
|
35
|
+
fs.mkdirSync(ULPI_GLOBAL_DIR, { recursive: true });
|
|
36
|
+
const yaml = stringifyYaml(library, { lineWidth: 120 });
|
|
37
|
+
fs.writeFileSync(MCPS_LIBRARY_FILE, yaml, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
function addToLibrary(name, definition) {
|
|
40
|
+
const library = loadLibrary();
|
|
41
|
+
library.mcps[name] = { ...definition, name, added_at: definition.added_at ?? (/* @__PURE__ */ new Date()).toISOString() };
|
|
42
|
+
saveLibrary(library);
|
|
43
|
+
}
|
|
44
|
+
function removeFromLibrary(name) {
|
|
45
|
+
const library = loadLibrary();
|
|
46
|
+
if (!(name in library.mcps)) return false;
|
|
47
|
+
delete library.mcps[name];
|
|
48
|
+
saveLibrary(library);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
function getLibraryEntry(name) {
|
|
52
|
+
const library = loadLibrary();
|
|
53
|
+
return library.mcps[name];
|
|
54
|
+
}
|
|
55
|
+
function listLibrary() {
|
|
56
|
+
const library = loadLibrary();
|
|
57
|
+
return Object.values(library.mcps);
|
|
58
|
+
}
|
|
59
|
+
function loadActivations(projectDir) {
|
|
60
|
+
try {
|
|
61
|
+
const filePath = projectMcpsFile(projectDir);
|
|
62
|
+
if (!fs2.existsSync(filePath)) {
|
|
63
|
+
return { mcps: {} };
|
|
64
|
+
}
|
|
65
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
66
|
+
const parsed = parseYaml2(raw);
|
|
67
|
+
return McpRepoConfigSchema.parse(parsed ?? {});
|
|
68
|
+
} catch {
|
|
69
|
+
return { mcps: {} };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function saveActivations(projectDir, config) {
|
|
73
|
+
const filePath = projectMcpsFile(projectDir);
|
|
74
|
+
fs2.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
75
|
+
const yaml = stringifyYaml2(config, { lineWidth: 120 });
|
|
76
|
+
fs2.writeFileSync(filePath, yaml, "utf-8");
|
|
77
|
+
}
|
|
78
|
+
function enableMcp(projectDir, name, config) {
|
|
79
|
+
const current = loadActivations(projectDir);
|
|
80
|
+
current.mcps[name] = { enabled: true, config: config ?? current.mcps[name]?.config ?? {} };
|
|
81
|
+
saveActivations(projectDir, current);
|
|
82
|
+
}
|
|
83
|
+
function disableMcp(projectDir, name) {
|
|
84
|
+
const current = loadActivations(projectDir);
|
|
85
|
+
if (!(name in current.mcps)) return false;
|
|
86
|
+
current.mcps[name] = { ...current.mcps[name], enabled: false, config: current.mcps[name]?.config ?? {} };
|
|
87
|
+
saveActivations(projectDir, current);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
function updateActivationConfig(projectDir, name, config) {
|
|
91
|
+
const current = loadActivations(projectDir);
|
|
92
|
+
const existing = current.mcps[name];
|
|
93
|
+
current.mcps[name] = { enabled: existing?.enabled ?? true, config };
|
|
94
|
+
saveActivations(projectDir, current);
|
|
95
|
+
}
|
|
96
|
+
function getActivation(projectDir, name) {
|
|
97
|
+
const config = loadActivations(projectDir);
|
|
98
|
+
return config.mcps[name];
|
|
99
|
+
}
|
|
100
|
+
function listActivations(projectDir) {
|
|
101
|
+
const config = loadActivations(projectDir);
|
|
102
|
+
return Object.entries(config.mcps).map(([name, activation]) => ({ name, activation }));
|
|
103
|
+
}
|
|
104
|
+
function loadCatalog() {
|
|
105
|
+
return loadMcpCatalog();
|
|
106
|
+
}
|
|
107
|
+
function getCatalogEntry(name) {
|
|
108
|
+
return loadMcpCatalog().find((e) => e.name === name);
|
|
109
|
+
}
|
|
110
|
+
function searchCatalog(query) {
|
|
111
|
+
const q = query.toLowerCase();
|
|
112
|
+
return loadMcpCatalog().filter((entry) => {
|
|
113
|
+
if (entry.name.toLowerCase().includes(q)) return true;
|
|
114
|
+
if (entry.display_name?.toLowerCase().includes(q)) return true;
|
|
115
|
+
if (entry.description?.toLowerCase().includes(q)) return true;
|
|
116
|
+
if (entry.tags?.some((t) => t.toLowerCase().includes(q))) return true;
|
|
117
|
+
if (entry.category?.toLowerCase().includes(q)) return true;
|
|
118
|
+
return false;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function suggestMcps(input) {
|
|
122
|
+
const catalog = loadMcpCatalog();
|
|
123
|
+
const library = loadLibrary();
|
|
124
|
+
const suggestions = [];
|
|
125
|
+
for (const entry of catalog) {
|
|
126
|
+
const signals = entry.detect_signals;
|
|
127
|
+
if (!signals) continue;
|
|
128
|
+
const evidence = [];
|
|
129
|
+
const depMatches = (signals.dependencies ?? []).filter(
|
|
130
|
+
(d) => input.dependencies.some((dep) => dep === d || dep.startsWith(d + "/"))
|
|
131
|
+
);
|
|
132
|
+
if (depMatches.length > 0) {
|
|
133
|
+
evidence.push(`Dependencies: ${depMatches.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
const envMatches = (signals.env_vars ?? []).filter((e) => input.envVars.includes(e));
|
|
136
|
+
if (envMatches.length > 0) {
|
|
137
|
+
evidence.push(`Env vars: ${envMatches.join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
const dockerMatches = (signals.docker_services ?? []).filter((s) => input.dockerServices.includes(s));
|
|
140
|
+
if (dockerMatches.length > 0) {
|
|
141
|
+
evidence.push(`Docker services: ${dockerMatches.join(", ")}`);
|
|
142
|
+
}
|
|
143
|
+
if (evidence.length === 0) continue;
|
|
144
|
+
let confidence = "low";
|
|
145
|
+
if (depMatches.length > 0 && (envMatches.length > 0 || dockerMatches.length > 0)) {
|
|
146
|
+
confidence = "high";
|
|
147
|
+
} else if (depMatches.length > 0 || envMatches.length > 0) {
|
|
148
|
+
confidence = "medium";
|
|
149
|
+
}
|
|
150
|
+
suggestions.push({
|
|
151
|
+
name: entry.name,
|
|
152
|
+
displayName: entry.display_name ?? entry.name,
|
|
153
|
+
description: entry.description ?? "",
|
|
154
|
+
category: entry.category ?? "custom",
|
|
155
|
+
confidence,
|
|
156
|
+
evidence,
|
|
157
|
+
alreadyAdded: entry.name in library.mcps
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const order = { high: 0, medium: 1, low: 2 };
|
|
161
|
+
suggestions.sort((a, b) => order[a.confidence] - order[b.confidence] || a.name.localeCompare(b.name));
|
|
162
|
+
return suggestions;
|
|
163
|
+
}
|
|
164
|
+
function collectProjectSignals(projectDir) {
|
|
165
|
+
const dependencies = [];
|
|
166
|
+
const envVars = [];
|
|
167
|
+
const dockerServices = [];
|
|
168
|
+
const pkgJsonPath = path2.join(projectDir, "package.json");
|
|
169
|
+
try {
|
|
170
|
+
if (fs3.existsSync(pkgJsonPath)) {
|
|
171
|
+
const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
|
|
172
|
+
dependencies.push(...Object.keys(pkg.dependencies ?? {}));
|
|
173
|
+
dependencies.push(...Object.keys(pkg.devDependencies ?? {}));
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
for (const envFile of [".env", ".env.local", ".env.example"]) {
|
|
178
|
+
const envPath = path2.join(projectDir, envFile);
|
|
179
|
+
try {
|
|
180
|
+
if (fs3.existsSync(envPath)) {
|
|
181
|
+
const content = fs3.readFileSync(envPath, "utf-8");
|
|
182
|
+
for (const line of content.split("\n")) {
|
|
183
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
184
|
+
if (match?.[1]) envVars.push(match[1]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const dcFile of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
|
|
191
|
+
const dcPath = path2.join(projectDir, dcFile);
|
|
192
|
+
try {
|
|
193
|
+
if (fs3.existsSync(dcPath)) {
|
|
194
|
+
const content = fs3.readFileSync(dcPath, "utf-8");
|
|
195
|
+
const servicesMatch = content.match(/^services:\s*\n((?:\s+\S+.*\n?)*)/m);
|
|
196
|
+
if (servicesMatch?.[1]) {
|
|
197
|
+
for (const line of servicesMatch[1].split("\n")) {
|
|
198
|
+
const svcMatch = line.match(/^\s{2}(\w[\w-]*):/);
|
|
199
|
+
if (svcMatch?.[1]) dockerServices.push(svcMatch[1]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { dependencies, envVars, dockerServices };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {
|
|
210
|
+
loadLibrary,
|
|
211
|
+
saveLibrary,
|
|
212
|
+
addToLibrary,
|
|
213
|
+
removeFromLibrary,
|
|
214
|
+
getLibraryEntry,
|
|
215
|
+
listLibrary,
|
|
216
|
+
loadActivations,
|
|
217
|
+
saveActivations,
|
|
218
|
+
enableMcp,
|
|
219
|
+
disableMcp,
|
|
220
|
+
updateActivationConfig,
|
|
221
|
+
getActivation,
|
|
222
|
+
listActivations,
|
|
223
|
+
loadCatalog,
|
|
224
|
+
getCatalogEntry,
|
|
225
|
+
searchCatalog,
|
|
226
|
+
suggestMcps,
|
|
227
|
+
collectProjectSignals
|
|
228
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REVIEW_FLAGS_DIR,
|
|
3
|
+
getPortalPort,
|
|
4
|
+
loadUlpiSettings
|
|
5
|
+
} from "./chunk-C7CLUQI6.js";
|
|
6
|
+
|
|
7
|
+
// src/hooks/review-integration.ts
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import { execFileSync } from "child_process";
|
|
11
|
+
function isReviewEnabled(type, rules) {
|
|
12
|
+
const runMode = process.env.ULPI_RUN_MODE;
|
|
13
|
+
if (runMode === "loop" || runMode === "parallel" || runMode === "ci") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const reviewConfig = rules.review;
|
|
17
|
+
if (reviewConfig) {
|
|
18
|
+
if (reviewConfig.enabled === false) return false;
|
|
19
|
+
if (type === "plan" && reviewConfig.plan_review === false) return false;
|
|
20
|
+
if (type === "code" && reviewConfig.code_review === false) return false;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const settings = loadUlpiSettings();
|
|
24
|
+
if (!settings.review.enabled) return false;
|
|
25
|
+
if (type === "plan" && !settings.review.plan_review) return false;
|
|
26
|
+
if (type === "code" && !settings.review.code_review) return false;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
function extractPlanForReview(input) {
|
|
32
|
+
const toolInput = input.tool_input;
|
|
33
|
+
if (toolInput?.plan && typeof toolInput.plan === "string") {
|
|
34
|
+
return toolInput.plan;
|
|
35
|
+
}
|
|
36
|
+
if (toolInput?.content && typeof toolInput.content === "string") {
|
|
37
|
+
return toolInput.content;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const plansDir = path.join(input.cwd, ".claude", "plans");
|
|
41
|
+
if (!fs.existsSync(plansDir)) return null;
|
|
42
|
+
const entries = fs.readdirSync(plansDir);
|
|
43
|
+
let newest = null;
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (!entry.endsWith(".md")) continue;
|
|
46
|
+
const fullPath = path.join(plansDir, entry);
|
|
47
|
+
const stat = fs.statSync(fullPath);
|
|
48
|
+
if (!newest || stat.mtimeMs > newest.mtime) {
|
|
49
|
+
newest = { name: entry, mtime: stat.mtimeMs };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (newest) {
|
|
53
|
+
return fs.readFileSync(path.join(plansDir, newest.name), "utf-8");
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
async function runPlanReviewSession(plan, projectDir, sessionId) {
|
|
60
|
+
try {
|
|
61
|
+
const { discoverUlpiServer, registerWithServer, waitForServerDecision } = await import("./dist-ZG4OKCSR.js");
|
|
62
|
+
const server = await discoverUlpiServer();
|
|
63
|
+
if (!server) return null;
|
|
64
|
+
const settings = loadUlpiSettings();
|
|
65
|
+
const registration = await registerWithServer(
|
|
66
|
+
server.port,
|
|
67
|
+
{ type: "plan", projectPath: projectDir, plan },
|
|
68
|
+
server.secret
|
|
69
|
+
);
|
|
70
|
+
if (!registration?.success) return null;
|
|
71
|
+
const reviewSessionId = registration.sessionId;
|
|
72
|
+
if (settings.review.auto_open_browser) {
|
|
73
|
+
const url = `http://localhost:${getPortalPort()}/review/plan?session=${reviewSessionId}`;
|
|
74
|
+
try {
|
|
75
|
+
execFileSync("open", [url], { timeout: 5e3 });
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const maxWaitMs = settings.review.review_timeout_seconds > 0 ? settings.review.review_timeout_seconds * 1e3 : void 0;
|
|
80
|
+
const decision = await waitForServerDecision(
|
|
81
|
+
server.port,
|
|
82
|
+
reviewSessionId,
|
|
83
|
+
3e4,
|
|
84
|
+
maxWaitMs,
|
|
85
|
+
registration.token,
|
|
86
|
+
server.secret
|
|
87
|
+
);
|
|
88
|
+
if (!decision) {
|
|
89
|
+
if (settings.review.timeout_behavior === "deny") {
|
|
90
|
+
return { behavior: "deny", feedback: "Plan review timed out." };
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const planDecision = decision;
|
|
95
|
+
const behavior = planDecision.behavior === "deny" ? "deny" : "allow";
|
|
96
|
+
const feedback = planDecision.feedback || planDecision.message || void 0;
|
|
97
|
+
const clearContext = planDecision.clearContext ?? false;
|
|
98
|
+
writeFlagFiles(sessionId, { feedback, clearContext });
|
|
99
|
+
return { behavior, feedback, clearContext };
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function writeFlagFiles(sessionId, ctx) {
|
|
105
|
+
try {
|
|
106
|
+
const dir = path.join(REVIEW_FLAGS_DIR, sessionId);
|
|
107
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
108
|
+
if (ctx.feedback) {
|
|
109
|
+
fs.writeFileSync(path.join(dir, "review-feedback.flag"), ctx.feedback, "utf-8");
|
|
110
|
+
}
|
|
111
|
+
if (ctx.clearContext) {
|
|
112
|
+
fs.writeFileSync(path.join(dir, "clear-context.flag"), "1", "utf-8");
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function collectPostReviewContexts(sessionId) {
|
|
118
|
+
const result = {};
|
|
119
|
+
try {
|
|
120
|
+
const dir = path.join(REVIEW_FLAGS_DIR, sessionId);
|
|
121
|
+
if (!fs.existsSync(dir)) return result;
|
|
122
|
+
const feedbackPath = path.join(dir, "review-feedback.flag");
|
|
123
|
+
if (fs.existsSync(feedbackPath)) {
|
|
124
|
+
result.feedback = fs.readFileSync(feedbackPath, "utf-8");
|
|
125
|
+
fs.unlinkSync(feedbackPath);
|
|
126
|
+
}
|
|
127
|
+
const clearPath = path.join(dir, "clear-context.flag");
|
|
128
|
+
if (fs.existsSync(clearPath)) {
|
|
129
|
+
result.clearContext = true;
|
|
130
|
+
fs.unlinkSync(clearPath);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const remaining = fs.readdirSync(dir);
|
|
134
|
+
if (remaining.length === 0) fs.rmdirSync(dir);
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
isReviewEnabled,
|
|
144
|
+
extractPlanForReview,
|
|
145
|
+
runPlanReviewSession,
|
|
146
|
+
collectPostReviewContexts
|
|
147
|
+
};
|