@ulpi/cli 0.1.4 → 0.1.6
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/LICENSE +21 -0
- package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
- package/dist/{auth-ECQ3IB4E.js → auth-HDK7ECJL.js} +2 -1
- package/dist/{chunk-3SBPZRB5.js → chunk-3BCW6ABU.js} +402 -142
- package/dist/{chunk-JGBXM5NC.js → chunk-3WB5CXH4.js} +180 -5
- package/dist/{chunk-2HEE5OKX.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-76D3BYJD.js +221 -0
- package/dist/{chunk-ZLYRPD7I.js → chunk-AWOSRA5F.js} +1 -1
- package/dist/{chunk-PDR55ZNW.js → chunk-BFEKZZHM.js} +274 -57
- package/dist/chunk-C7CLUQI6.js +1286 -0
- package/dist/{chunk-7AL4DOEJ.js → chunk-E3B5NROU.js} +7 -7
- package/dist/chunk-EJ7TW77N.js +1418 -0
- package/dist/{chunk-5J6NLQUN.js → chunk-IV6MWETF.js} +383 -168
- package/dist/chunk-IZPJHSPX.js +1478 -0
- package/dist/chunk-JLHNLM3C.js +228 -0
- package/dist/{chunk-BZL5H4YQ.js → chunk-KYYI23AQ.js} +2 -2
- package/dist/{chunk-2CLNOKPA.js → chunk-RSFJ6QSR.js} +18 -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-JCJ273T6.js → chunk-TSLDGT5O.js} +73 -35
- package/dist/{chunk-SPOI23SB.js → chunk-UXHCHOWQ.js} +83 -62
- package/dist/chunk-V2H5D6Y3.js +146 -0
- package/dist/{chunk-QJ5GSMEC.js → chunk-VVEDXI7E.js} +2 -1
- package/dist/chunk-VXH5Y4FO.js +6761 -0
- package/dist/chunk-WED4LM5N.js +322 -0
- package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
- package/dist/chunk-Z53CAR7G.js +298 -0
- package/dist/ci-X3U2W4HC.js +854 -0
- package/dist/cloud-2F3NLVHN.js +274 -0
- package/dist/{codemap-RKSD4MIE.js → codemap-XNGMAF3F.js} +37 -37
- package/dist/codex-MB5YTMRT.js +132 -0
- package/dist/{config-EGAXXCGL.js → config-OOELBYTH.js} +1 -1
- package/dist/dist-2BJYR5EI.js +59 -0
- package/dist/dist-2K7IEVTA.js +43 -0
- package/dist/dist-3EIQTZHT.js +1380 -0
- package/dist/{dist-YA2BWZB2.js → dist-4U5L2X2C.js} +2 -2
- package/dist/{dist-UKMCJBB2.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-CS2VKNYS.js → dist-FKFEJRPX.js} +16 -15
- package/dist/dist-GTEJUBBT.js +66 -0
- package/dist/dist-HA74OKJZ.js +40 -0
- package/dist/dist-HU5RZAON.js +48 -0
- package/dist/dist-IYE3OBRB.js +374 -0
- package/dist/{dist-GJYT2OQV.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-RKOGLK7R.js → dist-NUXMDXZ3.js} +31 -3
- package/dist/{dist-QAU3LGJN.js → dist-YCNWHSLN.js} +15 -5
- package/dist/{dist-CB5D5LMO.js → dist-YFFG2ZD6.js} +9 -16
- package/dist/dist-ZG4OKCSR.js +15 -0
- package/dist/doctor-SI4LLLDZ.js +345 -0
- package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
- package/dist/{history-3MOBX4MA.js → history-5NE46ZAH.js} +7 -7
- package/dist/hooks-installer-UN5JZLDQ.js +19 -0
- package/dist/index.js +395 -619
- package/dist/{init-6CH4HV5T.js → init-5FK3VKRT.js} +79 -13
- 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-LF2QMSKZ.js → launchd-6AWT54HR.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-Y6OZTXJ2.js → memory-ZNAEAK3B.js} +17 -17
- package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
- package/dist/{openai-E7G2YAHU-UYY4ZWON.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-5WHEJU2A.js +14 -0
- package/dist/{rules-E427DKYJ.js → rules-Y4VSOY5Y.js} +3 -3
- package/dist/run-VPNXEIBY.js +687 -0
- package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
- package/dist/server-KKSETHDV-XSSLEENT.js +20 -0
- package/dist/{skills-CX73O3IV.js → skills-QEYU2N27.js} +4 -2
- package/dist/start-JYOEL7AJ.js +303 -0
- package/dist/{status-4DFHDJMN.js → status-BHQYYGAL.js} +2 -2
- package/dist/{templates-U7T6MARD.js → templates-CBRUJ66V.js} +4 -3
- package/dist/tui-DP7736EX.js +61 -0
- package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
- package/dist/{uninstall-6SW35IK4.js → uninstall-ICUV6DDV.js} +3 -3
- package/dist/{update-M6IBJNYP.js → update-7ZMAYRBH.js} +3 -3
- package/dist/{version-checker-Q6YTYAGP.js → version-checker-4ZFMZA7Y.js} +2 -2
- package/package.json +39 -31
- package/dist/chunk-2MZER6ND.js +0 -415
- package/dist/chunk-2VYFVYJL.js +0 -4273
- package/dist/chunk-6OCEY7JY.js +0 -422
- package/dist/chunk-7LXY5UVC.js +0 -330
- package/dist/chunk-B55DDP24.js +0 -136
- package/dist/chunk-JWUUVXIV.js +0 -13694
- package/dist/chunk-MIAQVCFW.js +0 -39
- package/dist/chunk-YM2HV4IA.js +0 -505
- package/dist/ci-STSL2LSP.js +0 -370
- package/dist/mcp-installer-NQCGKQ23.js +0 -124
- package/dist/projects-ATHDD3D6.js +0 -271
- package/dist/review-ADUPV3PN.js +0 -152
- package/dist/server-USLHY6GH-AEOJC5ST.js +0 -18
- package/dist/server-X5P6WH2M-7K2RY34N.js +0 -11
- package/dist/skills/ulpi-generate-guardian/SKILL.md +0 -750
- package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +0 -849
- package/dist/skills/ulpi-generate-guardian/references/language-rules.md +0 -591
- package/dist/ui-OWXZ3YSR.js +0 -167
- package/dist/ui.html +0 -698
package/dist/index.js
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
3
|
+
getDefaultRepo,
|
|
4
|
+
getRepo,
|
|
5
|
+
registerRepo
|
|
6
|
+
} from "./chunk-UXHCHOWQ.js";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
isGitCommitCommand,
|
|
13
|
-
parseMarkdownToBlocks
|
|
14
|
-
} from "./chunk-3SBPZRB5.js";
|
|
15
|
-
import {
|
|
16
|
-
detectStack
|
|
17
|
-
} from "./chunk-2CLNOKPA.js";
|
|
18
|
-
import {
|
|
19
|
-
getDefaultProject,
|
|
20
|
-
getProject,
|
|
21
|
-
registerProject
|
|
22
|
-
} from "./chunk-SPOI23SB.js";
|
|
8
|
+
extractPlanForReview,
|
|
9
|
+
isReviewEnabled,
|
|
10
|
+
runPlanReviewSession
|
|
11
|
+
} from "./chunk-V2H5D6Y3.js";
|
|
23
12
|
import {
|
|
24
13
|
injectSkill,
|
|
25
14
|
loadSkillSync
|
|
26
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-4ZPOZULQ.js";
|
|
27
16
|
import {
|
|
28
17
|
evaluateRules,
|
|
29
18
|
loadRulesSync,
|
|
30
19
|
matchesFilePattern
|
|
31
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-5MI5GIXM.js";
|
|
21
|
+
import {
|
|
22
|
+
detectStack
|
|
23
|
+
} from "./chunk-RSFJ6QSR.js";
|
|
32
24
|
import {
|
|
33
25
|
buildPrePromptSnapshot,
|
|
34
26
|
buildSessionSummary,
|
|
@@ -46,7 +38,7 @@ import {
|
|
|
46
38
|
readTranscript,
|
|
47
39
|
updateEntryTranscript,
|
|
48
40
|
writeHistoryEntry
|
|
49
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3WB5CXH4.js";
|
|
50
42
|
import {
|
|
51
43
|
JsonSessionStore,
|
|
52
44
|
appendEvent,
|
|
@@ -54,29 +46,30 @@ import {
|
|
|
54
46
|
projectDirToSlug,
|
|
55
47
|
readEvents,
|
|
56
48
|
updateStateFromInput
|
|
57
|
-
} from "./chunk-
|
|
58
|
-
import "./chunk-KIKPIH6N.js";
|
|
49
|
+
} from "./chunk-SEU7WWNQ.js";
|
|
59
50
|
import {
|
|
51
|
+
API_LOCK_FILE,
|
|
60
52
|
NOTIFICATIONS_LOG_FILE,
|
|
61
|
-
REVIEW_FLAGS_DIR,
|
|
62
53
|
SESSIONS_DIR,
|
|
63
54
|
ULPI_GLOBAL_DIR,
|
|
64
|
-
getApiHost,
|
|
65
55
|
getApiPort,
|
|
66
56
|
globalGuardsFile,
|
|
67
|
-
loadUlpiSettings,
|
|
68
57
|
projectGuardsFile,
|
|
69
58
|
projectGuardsFileAlt,
|
|
70
59
|
projectNoAutoGenFile
|
|
71
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-C7CLUQI6.js";
|
|
61
|
+
import "./chunk-YOKL7RB5.js";
|
|
62
|
+
import "./chunk-KIKPIH6N.js";
|
|
72
63
|
import "./chunk-4VNS5WPM.js";
|
|
73
64
|
|
|
74
65
|
// src/index.ts
|
|
75
66
|
import * as fs4 from "fs";
|
|
76
67
|
import * as path4 from "path";
|
|
68
|
+
import chalk from "chalk";
|
|
77
69
|
|
|
78
70
|
// src/hooks/handler.ts
|
|
79
71
|
import * as fs3 from "fs";
|
|
72
|
+
import * as http from "http";
|
|
80
73
|
import * as path3 from "path";
|
|
81
74
|
|
|
82
75
|
// src/hooks/session-start.ts
|
|
@@ -138,10 +131,10 @@ async function handleSessionStart(ctx) {
|
|
|
138
131
|
if (shouldPromptForGeneration(projectDir2)) {
|
|
139
132
|
outputGenerationPrompt();
|
|
140
133
|
}
|
|
141
|
-
import("./version-checker-
|
|
134
|
+
import("./version-checker-4ZFMZA7Y.js").then((m) => m.checkForUpdates()).catch(() => {
|
|
142
135
|
});
|
|
143
136
|
try {
|
|
144
|
-
const { isMemoryEnabled, loadMemoryConfig, getTopMemories, formatMemoriesForAgent } = await import("./dist-
|
|
137
|
+
const { isMemoryEnabled, loadMemoryConfig, getTopMemories, formatMemoriesForAgent } = await import("./dist-JLU26AB6.js");
|
|
145
138
|
if (isMemoryEnabled(projectDir2)) {
|
|
146
139
|
const config = loadMemoryConfig(projectDir2);
|
|
147
140
|
if (config.surfaceOnStart) {
|
|
@@ -155,15 +148,15 @@ async function handleSessionStart(ctx) {
|
|
|
155
148
|
} catch {
|
|
156
149
|
}
|
|
157
150
|
try {
|
|
158
|
-
const { loadCodemapConfig } = await import("./dist-
|
|
159
|
-
const { getCodemapBranch, getCurrentBranch } = await import("./dist-
|
|
160
|
-
const { historyBranchExists: historyBranchExists2 } = await import("./dist-
|
|
151
|
+
const { loadCodemapConfig } = await import("./dist-YFFG2ZD6.js");
|
|
152
|
+
const { getCodemapBranch, getCurrentBranch } = await import("./dist-NUXMDXZ3.js");
|
|
153
|
+
const { historyBranchExists: historyBranchExists2 } = await import("./dist-YCNWHSLN.js");
|
|
161
154
|
const codemapConfig = loadCodemapConfig(projectDir2);
|
|
162
155
|
if (codemapConfig.autoImport) {
|
|
163
156
|
const branch = state.branch ?? getCurrentBranch(projectDir2);
|
|
164
157
|
const shadowBranch = getCodemapBranch(branch);
|
|
165
158
|
if (historyBranchExists2(projectDir2, shadowBranch)) {
|
|
166
|
-
const { importIndex } = await import("./dist-
|
|
159
|
+
const { importIndex } = await import("./dist-YFFG2ZD6.js");
|
|
167
160
|
await importIndex(projectDir2, branch);
|
|
168
161
|
}
|
|
169
162
|
}
|
|
@@ -231,6 +224,8 @@ import { join as join2 } from "path";
|
|
|
231
224
|
import * as os from "os";
|
|
232
225
|
import * as fs from "fs";
|
|
233
226
|
import * as path from "path";
|
|
227
|
+
import { spawn } from "child_process";
|
|
228
|
+
import { platform as platform2 } from "os";
|
|
234
229
|
import * as fs2 from "fs";
|
|
235
230
|
import * as path2 from "path";
|
|
236
231
|
function classifyNotification(input) {
|
|
@@ -306,9 +301,9 @@ var NotificationDeduplicator = class _NotificationDeduplicator {
|
|
|
306
301
|
}
|
|
307
302
|
};
|
|
308
303
|
function sendDesktopNotification(title, message) {
|
|
309
|
-
const
|
|
304
|
+
const platform3 = os.platform();
|
|
310
305
|
try {
|
|
311
|
-
if (
|
|
306
|
+
if (platform3 === "darwin") {
|
|
312
307
|
const escapedBody = message.replace(/["\\]/g, "\\$&");
|
|
313
308
|
const escapedTitle = title.replace(/["\\]/g, "\\$&");
|
|
314
309
|
const script = `display notification "${escapedBody}" with title "${escapedTitle}"`;
|
|
@@ -318,14 +313,14 @@ function sendDesktopNotification(title, message) {
|
|
|
318
313
|
});
|
|
319
314
|
return true;
|
|
320
315
|
}
|
|
321
|
-
if (
|
|
316
|
+
if (platform3 === "linux") {
|
|
322
317
|
execFileSync("notify-send", [title, message], {
|
|
323
318
|
timeout: 5e3,
|
|
324
319
|
stdio: "ignore"
|
|
325
320
|
});
|
|
326
321
|
return true;
|
|
327
322
|
}
|
|
328
|
-
if (
|
|
323
|
+
if (platform3 === "win32") {
|
|
329
324
|
const escapedTitle = title.replace(/'/g, "''");
|
|
330
325
|
const escapedBody = message.replace(/'/g, "''");
|
|
331
326
|
const ps = [
|
|
@@ -431,6 +426,78 @@ function writeTerminalNotification(title, message) {
|
|
|
431
426
|
[ulpi] ${title}: ${message}
|
|
432
427
|
`);
|
|
433
428
|
}
|
|
429
|
+
var MACOS_SOUNDS = {
|
|
430
|
+
success: "/System/Library/Sounds/Glass.aiff",
|
|
431
|
+
error: "/System/Library/Sounds/Sosumi.aiff",
|
|
432
|
+
warning: "/System/Library/Sounds/Basso.aiff",
|
|
433
|
+
complete: "/System/Library/Sounds/Hero.aiff"
|
|
434
|
+
};
|
|
435
|
+
var LINUX_SOUNDS = {
|
|
436
|
+
success: "/usr/share/sounds/freedesktop/stereo/complete.oga",
|
|
437
|
+
error: "/usr/share/sounds/freedesktop/stereo/dialog-error.oga",
|
|
438
|
+
warning: "/usr/share/sounds/freedesktop/stereo/dialog-warning.oga",
|
|
439
|
+
complete: "/usr/share/sounds/freedesktop/stereo/complete.oga"
|
|
440
|
+
};
|
|
441
|
+
var WIN_SOUNDS = {
|
|
442
|
+
success: "Asterisk",
|
|
443
|
+
error: "Hand",
|
|
444
|
+
warning: "Exclamation",
|
|
445
|
+
complete: "Asterisk"
|
|
446
|
+
};
|
|
447
|
+
function playSound(soundName) {
|
|
448
|
+
const os2 = platform2();
|
|
449
|
+
try {
|
|
450
|
+
switch (os2) {
|
|
451
|
+
case "darwin": {
|
|
452
|
+
const soundPath = MACOS_SOUNDS[soundName];
|
|
453
|
+
const proc = spawn("afplay", [soundPath], {
|
|
454
|
+
stdio: "ignore",
|
|
455
|
+
detached: true
|
|
456
|
+
});
|
|
457
|
+
proc.unref();
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
case "linux": {
|
|
461
|
+
const soundPath = LINUX_SOUNDS[soundName];
|
|
462
|
+
const proc = spawn("paplay", [soundPath], {
|
|
463
|
+
stdio: "ignore",
|
|
464
|
+
detached: true
|
|
465
|
+
});
|
|
466
|
+
proc.on("error", () => {
|
|
467
|
+
const fallback = spawn("aplay", ["-q", soundPath], {
|
|
468
|
+
stdio: "ignore",
|
|
469
|
+
detached: true
|
|
470
|
+
});
|
|
471
|
+
fallback.unref();
|
|
472
|
+
});
|
|
473
|
+
proc.unref();
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
case "win32": {
|
|
477
|
+
const systemSound = WIN_SOUNDS[soundName];
|
|
478
|
+
const proc = spawn(
|
|
479
|
+
"powershell",
|
|
480
|
+
[
|
|
481
|
+
"-NoProfile",
|
|
482
|
+
"-Command",
|
|
483
|
+
`[System.Media.SystemSounds]::${systemSound}.Play()`
|
|
484
|
+
],
|
|
485
|
+
{
|
|
486
|
+
stdio: "ignore",
|
|
487
|
+
detached: true,
|
|
488
|
+
windowsHide: true
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
proc.unref();
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
default:
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
434
501
|
var deduplicator = new NotificationDeduplicator();
|
|
435
502
|
function getEventConfig(classified, config) {
|
|
436
503
|
const key = classified;
|
|
@@ -517,6 +584,10 @@ async function routeToChannel(channel, title, message, timestamp, event) {
|
|
|
517
584
|
timestamp
|
|
518
585
|
});
|
|
519
586
|
}
|
|
587
|
+
case "sound": {
|
|
588
|
+
const soundName = channel.sound_name;
|
|
589
|
+
return playSound(soundName ?? "complete");
|
|
590
|
+
}
|
|
520
591
|
default:
|
|
521
592
|
return false;
|
|
522
593
|
}
|
|
@@ -808,453 +879,6 @@ async function dispatchToChannel(channel, title, message) {
|
|
|
808
879
|
}
|
|
809
880
|
}
|
|
810
881
|
|
|
811
|
-
// src/hooks/review-integration.ts
|
|
812
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync, readdirSync, statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
813
|
-
import { homedir } from "os";
|
|
814
|
-
import { join as join4 } from "path";
|
|
815
|
-
var FLAGS_BASE = REVIEW_FLAGS_DIR;
|
|
816
|
-
function flagsDir(sessionId) {
|
|
817
|
-
return sessionId ? join4(FLAGS_BASE, sessionId) : FLAGS_BASE;
|
|
818
|
-
}
|
|
819
|
-
function ensureFlagsDir(sessionId) {
|
|
820
|
-
const dir = flagsDir(sessionId);
|
|
821
|
-
if (!existsSync2(dir)) mkdirSync3(dir, { recursive: true });
|
|
822
|
-
}
|
|
823
|
-
function getReviewSettings() {
|
|
824
|
-
return loadUlpiSettings().review;
|
|
825
|
-
}
|
|
826
|
-
function isReviewEnabled(type) {
|
|
827
|
-
const review = getReviewSettings();
|
|
828
|
-
if (!review.enabled) return false;
|
|
829
|
-
if (type === "plan") return review.plan_review;
|
|
830
|
-
if (type === "code") return review.code_review;
|
|
831
|
-
return false;
|
|
832
|
-
}
|
|
833
|
-
async function extractPlanForReview(input) {
|
|
834
|
-
if (input.tool_input?.plan && typeof input.tool_input.plan === "string") {
|
|
835
|
-
return input.tool_input.plan;
|
|
836
|
-
}
|
|
837
|
-
if (input.tool_input?.planFilePath && typeof input.tool_input.planFilePath === "string") {
|
|
838
|
-
try {
|
|
839
|
-
return readFileSync(input.tool_input.planFilePath, "utf-8");
|
|
840
|
-
} catch {
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
if (input.transcript_path) {
|
|
844
|
-
const planFromTranscript = extractPlanFromTranscript(input.transcript_path);
|
|
845
|
-
if (planFromTranscript) return planFromTranscript;
|
|
846
|
-
}
|
|
847
|
-
const planDirs = [
|
|
848
|
-
input.cwd ? join4(input.cwd, ".claude", "plans") : null,
|
|
849
|
-
join4(homedir(), ".claude", "plans")
|
|
850
|
-
].filter(Boolean);
|
|
851
|
-
for (const dir of planDirs) {
|
|
852
|
-
const planFromDir = extractPlanFromDirectory(dir);
|
|
853
|
-
if (planFromDir) return planFromDir;
|
|
854
|
-
}
|
|
855
|
-
return null;
|
|
856
|
-
}
|
|
857
|
-
function extractPlanFromDirectory(plansDir) {
|
|
858
|
-
try {
|
|
859
|
-
if (!existsSync2(plansDir)) return null;
|
|
860
|
-
const files = readdirSync(plansDir);
|
|
861
|
-
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
862
|
-
if (mdFiles.length === 0) return null;
|
|
863
|
-
let latest = { file: mdFiles[0], mtime: 0 };
|
|
864
|
-
for (const f of mdFiles) {
|
|
865
|
-
const stat = statSync(join4(plansDir, f));
|
|
866
|
-
if (stat.mtimeMs > latest.mtime) {
|
|
867
|
-
latest = { file: f, mtime: stat.mtimeMs };
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
const content = readFileSync(join4(plansDir, latest.file), "utf-8");
|
|
871
|
-
if (content.trim()) {
|
|
872
|
-
console.error(`[ulpi] Reading plan from ${plansDir}/${latest.file}`);
|
|
873
|
-
return content;
|
|
874
|
-
}
|
|
875
|
-
} catch {
|
|
876
|
-
}
|
|
877
|
-
return null;
|
|
878
|
-
}
|
|
879
|
-
function extractPlanFromTranscript(transcriptPath) {
|
|
880
|
-
try {
|
|
881
|
-
const transcriptText = readFileSync(transcriptPath, "utf-8");
|
|
882
|
-
const lines = transcriptText.trim().split("\n").reverse();
|
|
883
|
-
for (const line of lines) {
|
|
884
|
-
try {
|
|
885
|
-
const entry = JSON.parse(line);
|
|
886
|
-
if (entry.role === "assistant" && typeof entry.content === "string") {
|
|
887
|
-
if (looksLikePlan(entry.content)) return entry.content;
|
|
888
|
-
}
|
|
889
|
-
if (Array.isArray(entry.content)) {
|
|
890
|
-
for (const block of entry.content) {
|
|
891
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
892
|
-
if (looksLikePlan(block.text)) return block.text;
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
} catch {
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
} catch {
|
|
900
|
-
}
|
|
901
|
-
return null;
|
|
902
|
-
}
|
|
903
|
-
function looksLikePlan(content) {
|
|
904
|
-
return content.includes("#") && content.length > 100;
|
|
905
|
-
}
|
|
906
|
-
function buildRichFeedback(decision, sections) {
|
|
907
|
-
const sectionTitleMap = /* @__PURE__ */ new Map();
|
|
908
|
-
for (const s of sections) {
|
|
909
|
-
sectionTitleMap.set(s.id, s.title);
|
|
910
|
-
}
|
|
911
|
-
const resolveSectionTitle = (sectionId) => {
|
|
912
|
-
if (!sectionId || sectionId === "global" || sectionId === "__global__") return "Global";
|
|
913
|
-
const byId = sectionTitleMap.get(sectionId);
|
|
914
|
-
if (byId) return byId;
|
|
915
|
-
const slugMatch = sections.find(
|
|
916
|
-
(s) => s.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") === sectionId
|
|
917
|
-
);
|
|
918
|
-
if (slugMatch) return slugMatch.title;
|
|
919
|
-
return sectionId;
|
|
920
|
-
};
|
|
921
|
-
const header = decision.behavior === "deny" ? "PLAN REVIEW FEEDBACK \u2014 Changes Requested" : "PLAN REVIEW FEEDBACK \u2014 Approved with Notes";
|
|
922
|
-
const parts = [header, ""];
|
|
923
|
-
const annotations = decision.annotations || [];
|
|
924
|
-
if (annotations.length > 0) {
|
|
925
|
-
parts.push("## Annotations");
|
|
926
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
927
|
-
for (const ann of annotations) {
|
|
928
|
-
const key = ann.sectionId || "__global__";
|
|
929
|
-
const group = grouped.get(key);
|
|
930
|
-
if (group) {
|
|
931
|
-
group.push(ann);
|
|
932
|
-
} else {
|
|
933
|
-
grouped.set(key, [ann]);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
for (const [key, anns] of grouped) {
|
|
937
|
-
for (const ann of anns) {
|
|
938
|
-
const sectionLabel = key === "__global__" ? "Global" : `Section: "${resolveSectionTitle(ann.sectionId)}"`;
|
|
939
|
-
const typeLabel = ann.type.toUpperCase().replace("_", " ");
|
|
940
|
-
parts.push(`### ${sectionLabel} (${typeLabel})`);
|
|
941
|
-
parts.push(ann.text);
|
|
942
|
-
if (ann.imagePaths && ann.imagePaths.length > 0) {
|
|
943
|
-
for (const imgPath of ann.imagePaths) {
|
|
944
|
-
parts.push(` - Attached image: ${imgPath}`);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
parts.push("");
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
const priorities = decision.priorities || [];
|
|
952
|
-
if (priorities.length > 0) {
|
|
953
|
-
parts.push("## Priorities");
|
|
954
|
-
for (const p of priorities) {
|
|
955
|
-
const title = resolveSectionTitle(p.sectionId);
|
|
956
|
-
const note = p.note ? ` (${p.note})` : "";
|
|
957
|
-
parts.push(`- "${title}" \u2192 ${p.priority.toUpperCase()}${note}`);
|
|
958
|
-
}
|
|
959
|
-
parts.push("");
|
|
960
|
-
}
|
|
961
|
-
const risks = decision.risks || [];
|
|
962
|
-
if (risks.length > 0) {
|
|
963
|
-
parts.push("## Risks");
|
|
964
|
-
for (const r of risks) {
|
|
965
|
-
const title = resolveSectionTitle(r.sectionId);
|
|
966
|
-
parts.push(`- "${title}" \u2192 ${r.level.toUpperCase()}: ${r.description}`);
|
|
967
|
-
}
|
|
968
|
-
parts.push("");
|
|
969
|
-
}
|
|
970
|
-
const instructions = decision.instructions || [];
|
|
971
|
-
if (instructions.length > 0) {
|
|
972
|
-
parts.push("## Instructions");
|
|
973
|
-
for (const inst of instructions) {
|
|
974
|
-
const title = resolveSectionTitle(inst.sectionId);
|
|
975
|
-
const priorityTag = inst.priority ? ` [${inst.priority.toUpperCase()}]` : "";
|
|
976
|
-
parts.push(`- "${title}": ${inst.instruction}${priorityTag}`);
|
|
977
|
-
}
|
|
978
|
-
parts.push("");
|
|
979
|
-
}
|
|
980
|
-
const inlineEdits = decision.inlineEdits || [];
|
|
981
|
-
if (inlineEdits.length > 0) {
|
|
982
|
-
parts.push("## Inline Edits");
|
|
983
|
-
for (const edit of inlineEdits) {
|
|
984
|
-
const title = resolveSectionTitle(edit.sectionId);
|
|
985
|
-
parts.push(`- Section "${title}": Reviewer edited the implementation approach`);
|
|
986
|
-
parts.push(` Original: "${truncate(edit.originalContent, 120)}"`);
|
|
987
|
-
parts.push(` Changed to: "${truncate(edit.editedContent, 120)}"`);
|
|
988
|
-
}
|
|
989
|
-
parts.push("");
|
|
990
|
-
}
|
|
991
|
-
const message = decision.message || decision.feedback;
|
|
992
|
-
if (message) {
|
|
993
|
-
parts.push("## Reviewer Message");
|
|
994
|
-
parts.push(message);
|
|
995
|
-
parts.push("");
|
|
996
|
-
}
|
|
997
|
-
if (decision.behavior === "deny") {
|
|
998
|
-
parts.push("ADDRESS ALL FEEDBACK BEFORE RE-SUBMITTING THE PLAN.");
|
|
999
|
-
}
|
|
1000
|
-
return parts.join("\n");
|
|
1001
|
-
}
|
|
1002
|
-
function truncate(text, maxLen) {
|
|
1003
|
-
const singleLine = text.replace(/\n/g, " ").trim();
|
|
1004
|
-
if (singleLine.length <= maxLen) return singleLine;
|
|
1005
|
-
return singleLine.slice(0, maxLen - 3) + "...";
|
|
1006
|
-
}
|
|
1007
|
-
async function runPlanReviewSession(plan, projectDir2, sessionId) {
|
|
1008
|
-
try {
|
|
1009
|
-
const discovered = await discoverUlpiServer();
|
|
1010
|
-
if (!discovered) {
|
|
1011
|
-
const settings2 = getReviewSettings();
|
|
1012
|
-
if (settings2.require_server) {
|
|
1013
|
-
console.error("[ulpi] No ULPI server running \u2014 blocking plan exit (require_server=true). Start the server with: ulpi ui");
|
|
1014
|
-
return { behavior: "deny", message: "Review server is required but not running. Start it with: ulpi ui" };
|
|
1015
|
-
}
|
|
1016
|
-
console.error("[ulpi] No ULPI server running \u2014 skipping plan review");
|
|
1017
|
-
return { behavior: "allow" };
|
|
1018
|
-
}
|
|
1019
|
-
const { port, secret: apiSecret } = discovered;
|
|
1020
|
-
appendEvent(sessionId, {
|
|
1021
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1022
|
-
event: "review_plan_started",
|
|
1023
|
-
hookEvent: "PermissionRequest",
|
|
1024
|
-
toolName: "ExitPlanMode",
|
|
1025
|
-
message: "Plan review session started"
|
|
1026
|
-
}, projectDir2);
|
|
1027
|
-
const registration = await registerWithServer(port, {
|
|
1028
|
-
type: "plan",
|
|
1029
|
-
plan,
|
|
1030
|
-
projectPath: projectDir2
|
|
1031
|
-
}, apiSecret);
|
|
1032
|
-
if (!registration) {
|
|
1033
|
-
console.error("[ulpi] Failed to register plan review session");
|
|
1034
|
-
return { behavior: "allow" };
|
|
1035
|
-
}
|
|
1036
|
-
console.error(`[ulpi] Plan review session: ${registration.sessionId}`);
|
|
1037
|
-
const apiHost = getApiHost();
|
|
1038
|
-
const planTokenParam = registration.token ? `&token=${encodeURIComponent(registration.token)}` : "";
|
|
1039
|
-
console.error(`[ulpi] Waiting for review decision at http://${apiHost}:${port}/review/plan?session=${registration.sessionId}${planTokenParam}`);
|
|
1040
|
-
const settings = getReviewSettings();
|
|
1041
|
-
const maxWaitMs = settings.review_timeout_seconds > 0 ? settings.review_timeout_seconds * 1e3 : void 0;
|
|
1042
|
-
const decision = await waitForServerDecision(port, registration.sessionId, 3e4, maxWaitMs, registration.token, apiSecret);
|
|
1043
|
-
if (!decision) {
|
|
1044
|
-
const behavior = settings.timeout_behavior ?? "allow";
|
|
1045
|
-
console.error(`[ulpi] Plan review timed out \u2014 ${behavior}`);
|
|
1046
|
-
return { behavior };
|
|
1047
|
-
}
|
|
1048
|
-
const planDecision = decision;
|
|
1049
|
-
appendEvent(sessionId, {
|
|
1050
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1051
|
-
event: "review_plan_decided",
|
|
1052
|
-
hookEvent: "PermissionRequest",
|
|
1053
|
-
toolName: "ExitPlanMode",
|
|
1054
|
-
message: `Plan review: ${planDecision.behavior}`
|
|
1055
|
-
}, projectDir2);
|
|
1056
|
-
const hasReviewData = (planDecision.annotations?.length ?? 0) > 0 || (planDecision.priorities?.length ?? 0) > 0 || (planDecision.risks?.length ?? 0) > 0 || (planDecision.instructions?.length ?? 0) > 0 || (planDecision.inlineEdits?.length ?? 0) > 0;
|
|
1057
|
-
let feedback;
|
|
1058
|
-
if (hasReviewData) {
|
|
1059
|
-
const blocks = parseMarkdownToBlocks(plan);
|
|
1060
|
-
const planSections = extractSections(blocks);
|
|
1061
|
-
feedback = buildRichFeedback(planDecision, planSections);
|
|
1062
|
-
} else {
|
|
1063
|
-
feedback = planDecision.feedback || planDecision.message || "";
|
|
1064
|
-
}
|
|
1065
|
-
return {
|
|
1066
|
-
behavior: planDecision.behavior,
|
|
1067
|
-
message: planDecision.message,
|
|
1068
|
-
feedback,
|
|
1069
|
-
clearContext: planDecision.clearContext
|
|
1070
|
-
};
|
|
1071
|
-
} catch (err) {
|
|
1072
|
-
console.error(`[ulpi] Plan review error: ${err instanceof Error ? err.message : err}`);
|
|
1073
|
-
return { behavior: "allow" };
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
async function runCodeReviewSession(diff, commitMessage, projectDir2, sessionId) {
|
|
1077
|
-
try {
|
|
1078
|
-
const discovered = await discoverUlpiServer();
|
|
1079
|
-
if (!discovered) {
|
|
1080
|
-
const settings2 = getReviewSettings();
|
|
1081
|
-
if (settings2.require_server) {
|
|
1082
|
-
console.error("[ulpi] No ULPI server running \u2014 blocking commit (require_server=true). Start the server with: ulpi ui");
|
|
1083
|
-
return { approved: false, message: "Review server is required but not running. Start it with: ulpi ui" };
|
|
1084
|
-
}
|
|
1085
|
-
console.error("[ulpi] No ULPI server running \u2014 skipping code review");
|
|
1086
|
-
return { approved: true };
|
|
1087
|
-
}
|
|
1088
|
-
const { port, secret: apiSecret } = discovered;
|
|
1089
|
-
appendEvent(sessionId, {
|
|
1090
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1091
|
-
event: "review_code_started",
|
|
1092
|
-
hookEvent: "PreToolUse",
|
|
1093
|
-
toolName: "Bash",
|
|
1094
|
-
command: "git commit",
|
|
1095
|
-
message: "Code review session started"
|
|
1096
|
-
}, projectDir2);
|
|
1097
|
-
const registration = await registerWithServer(port, {
|
|
1098
|
-
type: "code",
|
|
1099
|
-
diff,
|
|
1100
|
-
commitMessage,
|
|
1101
|
-
projectPath: projectDir2
|
|
1102
|
-
}, apiSecret);
|
|
1103
|
-
if (!registration) {
|
|
1104
|
-
console.error("[ulpi] Failed to register code review session");
|
|
1105
|
-
return { approved: true };
|
|
1106
|
-
}
|
|
1107
|
-
console.error(`[ulpi] Code review session: ${registration.sessionId}`);
|
|
1108
|
-
const codeApiHost = getApiHost();
|
|
1109
|
-
const codeTokenParam = registration.token ? `&token=${encodeURIComponent(registration.token)}` : "";
|
|
1110
|
-
console.error(`[ulpi] Waiting for review decision at http://${codeApiHost}:${port}/review/code?session=${registration.sessionId}${codeTokenParam}`);
|
|
1111
|
-
const settings = getReviewSettings();
|
|
1112
|
-
const maxWaitMs = settings.review_timeout_seconds > 0 ? settings.review_timeout_seconds * 1e3 : void 0;
|
|
1113
|
-
const decision = await waitForServerDecision(port, registration.sessionId, 3e4, maxWaitMs, registration.token, apiSecret);
|
|
1114
|
-
if (!decision) {
|
|
1115
|
-
const behavior = settings.timeout_behavior ?? "allow";
|
|
1116
|
-
console.error(`[ulpi] Code review timed out \u2014 ${behavior}`);
|
|
1117
|
-
return { approved: behavior === "allow" };
|
|
1118
|
-
}
|
|
1119
|
-
const codeDecision = decision;
|
|
1120
|
-
appendEvent(sessionId, {
|
|
1121
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1122
|
-
event: "review_code_decided",
|
|
1123
|
-
hookEvent: "PreToolUse",
|
|
1124
|
-
toolName: "Bash",
|
|
1125
|
-
command: "git commit",
|
|
1126
|
-
message: `Code review: ${codeDecision.approved ? "approved" : "changes requested"}`
|
|
1127
|
-
}, projectDir2);
|
|
1128
|
-
return {
|
|
1129
|
-
approved: codeDecision.approved,
|
|
1130
|
-
feedback: codeDecision.feedback,
|
|
1131
|
-
message: codeDecision.message
|
|
1132
|
-
};
|
|
1133
|
-
} catch (err) {
|
|
1134
|
-
console.error(`[ulpi] Code review error: ${err instanceof Error ? err.message : err}`);
|
|
1135
|
-
return { approved: true };
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
function writeClearContextFlag(sessionId) {
|
|
1139
|
-
ensureFlagsDir(sessionId);
|
|
1140
|
-
writeFileSync2(join4(flagsDir(sessionId), "clear-context.flag"), JSON.stringify({ timestamp: Date.now() }));
|
|
1141
|
-
}
|
|
1142
|
-
function readClearContextFlag(sessionId) {
|
|
1143
|
-
try {
|
|
1144
|
-
const path5 = join4(flagsDir(sessionId), "clear-context.flag");
|
|
1145
|
-
if (!existsSync2(path5)) return false;
|
|
1146
|
-
const data = JSON.parse(readFileSync(path5, "utf8"));
|
|
1147
|
-
unlinkSync2(path5);
|
|
1148
|
-
const age = Date.now() - (data.timestamp || 0);
|
|
1149
|
-
return age < 5 * 60 * 1e3;
|
|
1150
|
-
} catch {
|
|
1151
|
-
return false;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
function writeReviewFeedbackFlag(feedback, sessionId) {
|
|
1155
|
-
ensureFlagsDir(sessionId);
|
|
1156
|
-
writeFileSync2(join4(flagsDir(sessionId), "review-feedback.flag"), JSON.stringify({ timestamp: Date.now(), feedback }));
|
|
1157
|
-
}
|
|
1158
|
-
function readReviewFeedbackFlag(sessionId) {
|
|
1159
|
-
try {
|
|
1160
|
-
const path5 = join4(flagsDir(sessionId), "review-feedback.flag");
|
|
1161
|
-
if (!existsSync2(path5)) return null;
|
|
1162
|
-
const data = JSON.parse(readFileSync(path5, "utf8"));
|
|
1163
|
-
unlinkSync2(path5);
|
|
1164
|
-
const age = Date.now() - (data.timestamp || 0);
|
|
1165
|
-
if (age >= 5 * 60 * 1e3) return null;
|
|
1166
|
-
return data.feedback || null;
|
|
1167
|
-
} catch {
|
|
1168
|
-
return null;
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
function writeTeamDelegationFlag(planTitle, sections, sessionId) {
|
|
1172
|
-
ensureFlagsDir(sessionId);
|
|
1173
|
-
const flag = { timestamp: Date.now(), planTitle, sections };
|
|
1174
|
-
writeFileSync2(join4(flagsDir(sessionId), "team-delegation.flag"), JSON.stringify(flag));
|
|
1175
|
-
}
|
|
1176
|
-
function readTeamDelegationFlag(sessionId) {
|
|
1177
|
-
try {
|
|
1178
|
-
const path5 = join4(flagsDir(sessionId), "team-delegation.flag");
|
|
1179
|
-
if (!existsSync2(path5)) return null;
|
|
1180
|
-
const data = JSON.parse(readFileSync(path5, "utf8"));
|
|
1181
|
-
unlinkSync2(path5);
|
|
1182
|
-
const age = Date.now() - (data.timestamp || 0);
|
|
1183
|
-
if (age >= 5 * 60 * 1e3) return null;
|
|
1184
|
-
return data;
|
|
1185
|
-
} catch {
|
|
1186
|
-
return null;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
var AGENT_TYPE_KEYWORDS = [
|
|
1190
|
-
{ keywords: ["express", "node", "api", "endpoint", "middleware", "route", "server", "rest", "graphql"], agentType: "express-senior-engineer" },
|
|
1191
|
-
{ keywords: ["react", "component", "tailwind", "css", "ui", "frontend", "vite", "styled", "jsx", "tsx"], agentType: "react-vite-tailwind-engineer" },
|
|
1192
|
-
{ keywords: ["next.js", "nextjs", "app router", "rsc", "server component", "next"], agentType: "nextjs-senior-engineer" },
|
|
1193
|
-
{ keywords: ["laravel", "php", "eloquent", "artisan", "blade", "migration"], agentType: "laravel-senior-engineer" },
|
|
1194
|
-
{ keywords: ["docker", "container", "compose", "dockerfile", "kubernetes", "k8s"], agentType: "devops-docker-senior-engineer" },
|
|
1195
|
-
{ keywords: ["aws", "cdk", "cloudformation", "terraform", "lambda", "s3", "ec2", "iam"], agentType: "devops-aws-senior-engineer" },
|
|
1196
|
-
{ keywords: ["cli", "command", "terminal", "script", "commander", "inquirer", "chalk"], agentType: "nodejs-cli-senior-engineer" }
|
|
1197
|
-
];
|
|
1198
|
-
function inferAgentType(title, content) {
|
|
1199
|
-
const text = `${title} ${content}`.toLowerCase();
|
|
1200
|
-
let bestMatch = "general-purpose";
|
|
1201
|
-
let bestScore = 0;
|
|
1202
|
-
for (const entry of AGENT_TYPE_KEYWORDS) {
|
|
1203
|
-
let score = 0;
|
|
1204
|
-
for (const keyword of entry.keywords) {
|
|
1205
|
-
if (text.includes(keyword)) score++;
|
|
1206
|
-
}
|
|
1207
|
-
if (score > bestScore) {
|
|
1208
|
-
bestScore = score;
|
|
1209
|
-
bestMatch = entry.agentType;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
return bestMatch;
|
|
1213
|
-
}
|
|
1214
|
-
function buildDelegationSections(sections, blocks) {
|
|
1215
|
-
return sections.filter((s) => s.level >= 2 && s.title !== "Overview").map((section) => {
|
|
1216
|
-
const content = getSectionFullText(section, blocks);
|
|
1217
|
-
return {
|
|
1218
|
-
title: section.title,
|
|
1219
|
-
agentType: inferAgentType(section.title, content)
|
|
1220
|
-
};
|
|
1221
|
-
});
|
|
1222
|
-
}
|
|
1223
|
-
function buildDelegationContext(delegation) {
|
|
1224
|
-
const sectionLines = delegation.sections.map((s) => `- "${s.title}" \u2192 ${s.agentType}`).join("\n");
|
|
1225
|
-
return `TEAM DELEGATION INSTRUCTIONS:
|
|
1226
|
-
|
|
1227
|
-
The plan "${delegation.planTitle}" has been approved. You MUST now create a team of specialized agents to execute this plan in parallel using the Task tool.
|
|
1228
|
-
|
|
1229
|
-
As the team lead, you should:
|
|
1230
|
-
1. Analyze each plan section and confirm the recommended agent type (or adjust based on your understanding)
|
|
1231
|
-
2. Launch parallel Task agents for independent sections in a SINGLE message with multiple Task tool calls
|
|
1232
|
-
3. Use sequential execution only where sections have explicit dependencies
|
|
1233
|
-
4. Coordinate results after agents complete and verify integration
|
|
1234
|
-
|
|
1235
|
-
Recommended agent allocation:
|
|
1236
|
-
${sectionLines}
|
|
1237
|
-
|
|
1238
|
-
IMPORTANT: Launch independent agents in PARALLEL (single message, multiple Task tool calls) for maximum efficiency.`;
|
|
1239
|
-
}
|
|
1240
|
-
function collectPostReviewContexts(sessionId) {
|
|
1241
|
-
const contexts = [];
|
|
1242
|
-
if (readClearContextFlag(sessionId)) {
|
|
1243
|
-
contexts.push(
|
|
1244
|
-
"IMPORTANT: The plan reviewer approved this plan with a request to clear context before implementation. You MUST immediately run /compact before implementing. Do not ask the user \u2014 just do it now. After compaction completes, proceed with the approved plan."
|
|
1245
|
-
);
|
|
1246
|
-
}
|
|
1247
|
-
const reviewFeedback = readReviewFeedbackFlag(sessionId);
|
|
1248
|
-
if (reviewFeedback) {
|
|
1249
|
-
contexts.push(reviewFeedback);
|
|
1250
|
-
}
|
|
1251
|
-
const delegation = readTeamDelegationFlag(sessionId);
|
|
1252
|
-
if (delegation) {
|
|
1253
|
-
contexts.push(buildDelegationContext(delegation));
|
|
1254
|
-
}
|
|
1255
|
-
return contexts;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
882
|
// src/hooks/pre-tool.ts
|
|
1259
883
|
async function handlePreTool(ctx) {
|
|
1260
884
|
const { input, state, rules, projectDir: projectDir2, store } = ctx;
|
|
@@ -1324,32 +948,6 @@ async function handlePreTool(ctx) {
|
|
|
1324
948
|
process.stderr.write(message);
|
|
1325
949
|
return 2;
|
|
1326
950
|
}
|
|
1327
|
-
if (input.tool_name === "Bash" && isGitCommitCommand(input.tool_input?.command) && isReviewEnabled("code")) {
|
|
1328
|
-
try {
|
|
1329
|
-
const command2 = input.tool_input?.command;
|
|
1330
|
-
const { diff, commitMessage, allowEmpty } = captureCommitDiff({ command: command2, cwd: projectDir2 });
|
|
1331
|
-
if (allowEmpty && !diff.trim()) {
|
|
1332
|
-
console.error("[ulpi] Skipping code review: --allow-empty commit with no changes.");
|
|
1333
|
-
} else if (diff.trim()) {
|
|
1334
|
-
const reviewResult = await runCodeReviewSession(
|
|
1335
|
-
diff,
|
|
1336
|
-
commitMessage,
|
|
1337
|
-
projectDir2,
|
|
1338
|
-
input.session_id
|
|
1339
|
-
);
|
|
1340
|
-
if (!reviewResult.approved) {
|
|
1341
|
-
state.actionsBlocked++;
|
|
1342
|
-
state.consecutiveBlocks++;
|
|
1343
|
-
const message = reviewResult.feedback || reviewResult.message || "Code review: changes requested.";
|
|
1344
|
-
store.save(state);
|
|
1345
|
-
process.stderr.write(message);
|
|
1346
|
-
return 2;
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
} catch (err) {
|
|
1350
|
-
console.error(`[ulpi] Code review error: ${err instanceof Error ? err.message : err}`);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
951
|
state.consecutiveBlocks = 0;
|
|
1354
952
|
if (result.stdoutJson) {
|
|
1355
953
|
process.stdout.write(JSON.stringify(result.stdoutJson));
|
|
@@ -1377,15 +975,12 @@ async function handlePostTool(ctx) {
|
|
|
1377
975
|
}
|
|
1378
976
|
}
|
|
1379
977
|
try {
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
}
|
|
1387
|
-
};
|
|
1388
|
-
process.stdout.write(JSON.stringify(contextJson));
|
|
978
|
+
const { collectPostReviewContexts } = await import("./review-integration-5WHEJU2A.js");
|
|
979
|
+
const reviewCtx = collectPostReviewContexts(input.session_id);
|
|
980
|
+
if (reviewCtx.feedback) {
|
|
981
|
+
process.stderr.write(`[ulpi] Review feedback:
|
|
982
|
+
${reviewCtx.feedback}
|
|
983
|
+
`);
|
|
1389
984
|
}
|
|
1390
985
|
} catch {
|
|
1391
986
|
}
|
|
@@ -1473,7 +1068,7 @@ ${failureFeedback.join("\n\n")}`
|
|
|
1473
1068
|
}
|
|
1474
1069
|
}
|
|
1475
1070
|
try {
|
|
1476
|
-
const { isMemoryEnabled, loadMemoryConfig, appendMemoryEvent, toClassificationEvent } = await import("./dist-
|
|
1071
|
+
const { isMemoryEnabled, loadMemoryConfig, appendMemoryEvent, toClassificationEvent } = await import("./dist-JLU26AB6.js");
|
|
1477
1072
|
if (isMemoryEnabled(projectDir2)) {
|
|
1478
1073
|
const memConfig = loadMemoryConfig(projectDir2);
|
|
1479
1074
|
if (memConfig.captureMode === "continuous") {
|
|
@@ -1493,8 +1088,8 @@ ${failureFeedback.join("\n\n")}`
|
|
|
1493
1088
|
}
|
|
1494
1089
|
if (input.tool_name === "Bash" && /\bgit\s+push\b/.test(String(input.tool_input?.command ?? ""))) {
|
|
1495
1090
|
try {
|
|
1496
|
-
const { loadCodemapConfig, getCodemapStatus, exportIndex } = await import("./dist-
|
|
1497
|
-
const { getCurrentBranch } = await import("./dist-
|
|
1091
|
+
const { loadCodemapConfig, getCodemapStatus, exportIndex } = await import("./dist-YFFG2ZD6.js");
|
|
1092
|
+
const { getCurrentBranch } = await import("./dist-NUXMDXZ3.js");
|
|
1498
1093
|
const codemapConfig = loadCodemapConfig(projectDir2);
|
|
1499
1094
|
if (codemapConfig.autoExport) {
|
|
1500
1095
|
const branch = state.branch ?? getCurrentBranch(projectDir2);
|
|
@@ -1540,6 +1135,51 @@ async function captureOnCommit(projectDir2, state, input, sha) {
|
|
|
1540
1135
|
// src/hooks/permission.ts
|
|
1541
1136
|
async function handlePermission(ctx) {
|
|
1542
1137
|
const { input, state, rules, projectDir: projectDir2, store } = ctx;
|
|
1138
|
+
if (input.tool_name === "ExitPlanMode") {
|
|
1139
|
+
try {
|
|
1140
|
+
if (isReviewEnabled("plan", rules)) {
|
|
1141
|
+
const plan = extractPlanForReview(input);
|
|
1142
|
+
if (plan) {
|
|
1143
|
+
appendEvent(input.session_id, {
|
|
1144
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1145
|
+
event: "review_plan_started",
|
|
1146
|
+
hookEvent: input.hook_event_name,
|
|
1147
|
+
toolName: input.tool_name,
|
|
1148
|
+
message: "Plan review started"
|
|
1149
|
+
}, projectDir2);
|
|
1150
|
+
const reviewResult = await runPlanReviewSession(plan, projectDir2, input.session_id);
|
|
1151
|
+
if (reviewResult) {
|
|
1152
|
+
if (reviewResult.behavior === "deny") {
|
|
1153
|
+
appendEvent(input.session_id, {
|
|
1154
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1155
|
+
event: "review_plan_denied",
|
|
1156
|
+
hookEvent: input.hook_event_name,
|
|
1157
|
+
toolName: input.tool_name,
|
|
1158
|
+
message: reviewResult.feedback ?? "Plan review denied"
|
|
1159
|
+
}, projectDir2);
|
|
1160
|
+
state.actionsBlocked++;
|
|
1161
|
+
store.save(state);
|
|
1162
|
+
process.stderr.write(
|
|
1163
|
+
reviewResult.feedback ? `[ulpi] Plan review denied:
|
|
1164
|
+
${reviewResult.feedback}` : "[ulpi] Plan review denied by reviewer."
|
|
1165
|
+
);
|
|
1166
|
+
return 2;
|
|
1167
|
+
}
|
|
1168
|
+
appendEvent(input.session_id, {
|
|
1169
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1170
|
+
event: "review_plan_approved",
|
|
1171
|
+
hookEvent: input.hook_event_name,
|
|
1172
|
+
toolName: input.tool_name,
|
|
1173
|
+
message: "Plan review approved"
|
|
1174
|
+
}, projectDir2);
|
|
1175
|
+
state.planApproved = true;
|
|
1176
|
+
store.save(state);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1543
1183
|
const result = evaluateRules(input, rules, state, projectDir2);
|
|
1544
1184
|
const hookDecision = result.stdoutJson?.hookSpecificOutput?.decision?.behavior ?? result.stdoutJson?.hookSpecificOutput?.permissionDecision;
|
|
1545
1185
|
if (hookDecision) {
|
|
@@ -1597,60 +1237,6 @@ async function handlePermission(ctx) {
|
|
|
1597
1237
|
}
|
|
1598
1238
|
return 2;
|
|
1599
1239
|
}
|
|
1600
|
-
if (input.tool_name === "ExitPlanMode" && isReviewEnabled("plan")) {
|
|
1601
|
-
console.error(`[ulpi] ExitPlanMode intercepted \u2014 extracting plan (cwd: ${input.cwd})`);
|
|
1602
|
-
try {
|
|
1603
|
-
const plan = await extractPlanForReview(input);
|
|
1604
|
-
console.error(`[ulpi] Plan extraction result: ${plan ? `${plan.length} chars` : "null"}`);
|
|
1605
|
-
if (plan) {
|
|
1606
|
-
const result2 = await runPlanReviewSession(plan, projectDir2, input.session_id);
|
|
1607
|
-
if (result2.behavior === "allow") {
|
|
1608
|
-
state.planApproved = true;
|
|
1609
|
-
if (result2.clearContext) {
|
|
1610
|
-
writeClearContextFlag(input.session_id);
|
|
1611
|
-
}
|
|
1612
|
-
if (result2.feedback) {
|
|
1613
|
-
writeReviewFeedbackFlag(result2.feedback, input.session_id);
|
|
1614
|
-
}
|
|
1615
|
-
try {
|
|
1616
|
-
const blocks = parseMarkdownToBlocks(plan);
|
|
1617
|
-
const sections = extractSections(blocks);
|
|
1618
|
-
const title = extractTitle(blocks);
|
|
1619
|
-
const delegationSections = buildDelegationSections(sections, blocks);
|
|
1620
|
-
if (delegationSections.length > 0) {
|
|
1621
|
-
writeTeamDelegationFlag(title, delegationSections, input.session_id);
|
|
1622
|
-
}
|
|
1623
|
-
} catch {
|
|
1624
|
-
}
|
|
1625
|
-
const allowJson = {
|
|
1626
|
-
hookSpecificOutput: {
|
|
1627
|
-
hookEventName: "PermissionRequest",
|
|
1628
|
-
decision: { behavior: "allow" }
|
|
1629
|
-
}
|
|
1630
|
-
};
|
|
1631
|
-
store.save(state);
|
|
1632
|
-
process.stdout.write(JSON.stringify(allowJson));
|
|
1633
|
-
return 2;
|
|
1634
|
-
} else {
|
|
1635
|
-
const feedbackText = result2.feedback || result2.message || "Plan review: changes requested";
|
|
1636
|
-
writeReviewFeedbackFlag(feedbackText, input.session_id);
|
|
1637
|
-
const denyJson = {
|
|
1638
|
-
hookSpecificOutput: {
|
|
1639
|
-
hookEventName: "PermissionRequest",
|
|
1640
|
-
decision: { behavior: "deny", message: feedbackText }
|
|
1641
|
-
}
|
|
1642
|
-
};
|
|
1643
|
-
state.actionsBlocked++;
|
|
1644
|
-
store.save(state);
|
|
1645
|
-
process.stdout.write(JSON.stringify(denyJson));
|
|
1646
|
-
process.stderr.write(feedbackText);
|
|
1647
|
-
return 2;
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
} catch (err) {
|
|
1651
|
-
console.error(`[ulpi] Plan review error: ${err instanceof Error ? err.message : err}`);
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
1240
|
return 0;
|
|
1655
1241
|
}
|
|
1656
1242
|
|
|
@@ -1747,7 +1333,7 @@ ${warnings.map((w) => ` - ${w}`).join("\n")}`
|
|
|
1747
1333
|
}
|
|
1748
1334
|
|
|
1749
1335
|
// src/hooks/session-end.ts
|
|
1750
|
-
import { spawn } from "child_process";
|
|
1336
|
+
import { spawn as spawn2 } from "child_process";
|
|
1751
1337
|
async function captureSessionCommits(ctx) {
|
|
1752
1338
|
const { input, state, projectDir: projectDir2 } = ctx;
|
|
1753
1339
|
if (!state.headAtStart) return;
|
|
@@ -1835,7 +1421,7 @@ async function handleSessionEnd(ctx) {
|
|
|
1835
1421
|
} catch {
|
|
1836
1422
|
}
|
|
1837
1423
|
try {
|
|
1838
|
-
const memEngine = await import("./dist-
|
|
1424
|
+
const memEngine = await import("./dist-JLU26AB6.js");
|
|
1839
1425
|
if (memEngine.isMemoryEnabled(projectDir2)) {
|
|
1840
1426
|
const config = memEngine.loadMemoryConfig(projectDir2);
|
|
1841
1427
|
memEngine.finalizeCapture(input.session_id, state, projectDir2);
|
|
@@ -1843,7 +1429,7 @@ async function handleSessionEnd(ctx) {
|
|
|
1843
1429
|
try {
|
|
1844
1430
|
const args2 = ["memory", "classify", "-p", projectDir2, "--session", input.session_id];
|
|
1845
1431
|
if (config.autoExport) args2.push("--export");
|
|
1846
|
-
const child =
|
|
1432
|
+
const child = spawn2(process.execPath, [process.argv[1], ...args2], {
|
|
1847
1433
|
detached: true,
|
|
1848
1434
|
stdio: "ignore",
|
|
1849
1435
|
env: { ...process.env, ULPI_BG_CLASSIFY: "1" }
|
|
@@ -1865,7 +1451,7 @@ async function handleSessionEnd(ctx) {
|
|
|
1865
1451
|
try {
|
|
1866
1452
|
if (historyBranchExists(projectDir2)) {
|
|
1867
1453
|
const args2 = ["history", "backfill", "-p", projectDir2, "--limit", "50", "--branch-only"];
|
|
1868
|
-
const child =
|
|
1454
|
+
const child = spawn2(process.execPath, [process.argv[1], ...args2], {
|
|
1869
1455
|
detached: true,
|
|
1870
1456
|
stdio: "ignore"
|
|
1871
1457
|
});
|
|
@@ -1874,8 +1460,8 @@ async function handleSessionEnd(ctx) {
|
|
|
1874
1460
|
} catch {
|
|
1875
1461
|
}
|
|
1876
1462
|
try {
|
|
1877
|
-
const { loadCodemapConfig, getCodemapStatus, exportIndex } = await import("./dist-
|
|
1878
|
-
const { getCurrentBranch } = await import("./dist-
|
|
1463
|
+
const { loadCodemapConfig, getCodemapStatus, exportIndex } = await import("./dist-YFFG2ZD6.js");
|
|
1464
|
+
const { getCurrentBranch } = await import("./dist-NUXMDXZ3.js");
|
|
1879
1465
|
const codemapConfig = loadCodemapConfig(projectDir2);
|
|
1880
1466
|
if (codemapConfig.autoExport) {
|
|
1881
1467
|
const branch = state.branch ?? getCurrentBranch(projectDir2);
|
|
@@ -1888,10 +1474,51 @@ async function handleSessionEnd(ctx) {
|
|
|
1888
1474
|
}
|
|
1889
1475
|
}
|
|
1890
1476
|
|
|
1477
|
+
// src/hooks/normalize.ts
|
|
1478
|
+
var TOOL_NAME_MAP = {
|
|
1479
|
+
fs_read: "Read",
|
|
1480
|
+
read: "Read",
|
|
1481
|
+
fs_write: "Write",
|
|
1482
|
+
write: "Write",
|
|
1483
|
+
fs_edit: "Edit",
|
|
1484
|
+
edit: "Edit",
|
|
1485
|
+
execute_bash: "Bash",
|
|
1486
|
+
shell: "Bash",
|
|
1487
|
+
fs_glob: "Glob",
|
|
1488
|
+
glob: "Glob",
|
|
1489
|
+
fs_grep: "Grep",
|
|
1490
|
+
grep: "Grep",
|
|
1491
|
+
web_fetch: "WebFetch",
|
|
1492
|
+
web_search: "WebSearch"
|
|
1493
|
+
};
|
|
1494
|
+
var EVENT_NAME_MAP = {
|
|
1495
|
+
preToolUse: "PreToolUse",
|
|
1496
|
+
postToolUse: "PostToolUse",
|
|
1497
|
+
permissionRequest: "PermissionRequest",
|
|
1498
|
+
agentSpawn: "SessionStart",
|
|
1499
|
+
sessionStart: "SessionStart",
|
|
1500
|
+
sessionEnd: "SessionEnd",
|
|
1501
|
+
userPromptSubmit: "UserPromptSubmit",
|
|
1502
|
+
notification: "Notification",
|
|
1503
|
+
stop: "Stop"
|
|
1504
|
+
};
|
|
1505
|
+
function normalizeHookInput(raw) {
|
|
1506
|
+
if (typeof raw.hook_event_name === "string") {
|
|
1507
|
+
raw.hook_event_name = EVENT_NAME_MAP[raw.hook_event_name] ?? raw.hook_event_name;
|
|
1508
|
+
}
|
|
1509
|
+
if (typeof raw.tool_name === "string") {
|
|
1510
|
+
const lower = raw.tool_name.toLowerCase();
|
|
1511
|
+
raw.tool_name = TOOL_NAME_MAP[lower] ?? raw.tool_name;
|
|
1512
|
+
}
|
|
1513
|
+
return raw;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1891
1516
|
// src/hooks/handler.ts
|
|
1892
1517
|
function readStdinSync() {
|
|
1893
1518
|
const raw = fs3.readFileSync(0, "utf-8");
|
|
1894
|
-
|
|
1519
|
+
const parsed = JSON.parse(raw);
|
|
1520
|
+
normalizeHookInput(parsed);
|
|
1521
|
+
return parsed;
|
|
1895
1522
|
}
|
|
1896
1523
|
function findRulesPath(projectDir2) {
|
|
1897
1524
|
const projectRules = projectGuardsFile(projectDir2);
|
|
@@ -1940,6 +1567,98 @@ function validateInput(input) {
|
|
|
1940
1567
|
throw new Error("Missing or invalid hook_event_name");
|
|
1941
1568
|
}
|
|
1942
1569
|
}
|
|
1570
|
+
function emitToApi(event) {
|
|
1571
|
+
try {
|
|
1572
|
+
const lockData = JSON.parse(fs3.readFileSync(API_LOCK_FILE, "utf-8"));
|
|
1573
|
+
const port = lockData.port;
|
|
1574
|
+
if (!port) return;
|
|
1575
|
+
const body = JSON.stringify(event);
|
|
1576
|
+
const headers = {
|
|
1577
|
+
"Content-Type": "application/json",
|
|
1578
|
+
"Content-Length": String(Buffer.byteLength(body))
|
|
1579
|
+
};
|
|
1580
|
+
if (lockData.secret) {
|
|
1581
|
+
headers["X-Ulpi-Secret"] = lockData.secret;
|
|
1582
|
+
}
|
|
1583
|
+
const req = http.request({
|
|
1584
|
+
hostname: "127.0.0.1",
|
|
1585
|
+
port,
|
|
1586
|
+
path: "/api/events/emit",
|
|
1587
|
+
method: "POST",
|
|
1588
|
+
headers,
|
|
1589
|
+
timeout: 2e3
|
|
1590
|
+
});
|
|
1591
|
+
req.on("error", () => {
|
|
1592
|
+
});
|
|
1593
|
+
req.on("timeout", () => req.destroy());
|
|
1594
|
+
req.write(body);
|
|
1595
|
+
req.end();
|
|
1596
|
+
} catch {
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
function buildHookEvent(hookName, input, exitCode) {
|
|
1600
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1601
|
+
const sessionId = input.session_id;
|
|
1602
|
+
switch (hookName) {
|
|
1603
|
+
case "session-start":
|
|
1604
|
+
return {
|
|
1605
|
+
type: "hook:session-start",
|
|
1606
|
+
ts,
|
|
1607
|
+
sessionId,
|
|
1608
|
+
projectDir: input.cwd,
|
|
1609
|
+
cwd: input.cwd
|
|
1610
|
+
};
|
|
1611
|
+
case "session-end":
|
|
1612
|
+
return {
|
|
1613
|
+
type: "hook:session-end",
|
|
1614
|
+
ts,
|
|
1615
|
+
sessionId,
|
|
1616
|
+
durationMs: 0
|
|
1617
|
+
// Duration not available at hook level
|
|
1618
|
+
};
|
|
1619
|
+
case "pre-tool":
|
|
1620
|
+
return {
|
|
1621
|
+
type: "hook:pre-tool",
|
|
1622
|
+
ts,
|
|
1623
|
+
sessionId,
|
|
1624
|
+
toolName: input.tool_name ?? "unknown",
|
|
1625
|
+
filePath: input.tool_input?.file_path
|
|
1626
|
+
};
|
|
1627
|
+
case "post-tool":
|
|
1628
|
+
return {
|
|
1629
|
+
type: "hook:post-tool",
|
|
1630
|
+
ts,
|
|
1631
|
+
sessionId,
|
|
1632
|
+
toolName: input.tool_name ?? "unknown",
|
|
1633
|
+
filePath: input.tool_input?.file_path,
|
|
1634
|
+
durationMs: 0
|
|
1635
|
+
// Duration not available at hook level
|
|
1636
|
+
};
|
|
1637
|
+
case "permission":
|
|
1638
|
+
return {
|
|
1639
|
+
type: "hook:permission",
|
|
1640
|
+
ts,
|
|
1641
|
+
sessionId,
|
|
1642
|
+
toolName: input.tool_name ?? "unknown",
|
|
1643
|
+
decision: exitCode === 0 ? "allow" : "deny"
|
|
1644
|
+
};
|
|
1645
|
+
case "notification":
|
|
1646
|
+
return {
|
|
1647
|
+
type: "hook:notification",
|
|
1648
|
+
ts,
|
|
1649
|
+
sessionId,
|
|
1650
|
+
message: input.message ?? ""
|
|
1651
|
+
};
|
|
1652
|
+
case "stop":
|
|
1653
|
+
return {
|
|
1654
|
+
type: "hook:stop",
|
|
1655
|
+
ts,
|
|
1656
|
+
sessionId
|
|
1657
|
+
};
|
|
1658
|
+
default:
|
|
1659
|
+
return null;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1943
1662
|
async function handleHook(hookName) {
|
|
1944
1663
|
const input = readStdinSync();
|
|
1945
1664
|
validateInput(input);
|
|
@@ -1960,9 +1679,9 @@ async function handleHook(hookName) {
|
|
|
1960
1679
|
projectDir: projectDir2,
|
|
1961
1680
|
store
|
|
1962
1681
|
};
|
|
1963
|
-
if (hookName === "session-start") {
|
|
1682
|
+
if (hookName === "session-start" && !projectDir2.includes(".ulpi-worktrees")) {
|
|
1964
1683
|
try {
|
|
1965
|
-
|
|
1684
|
+
registerRepo(projectDir2, {
|
|
1966
1685
|
sessionId: input.session_id
|
|
1967
1686
|
});
|
|
1968
1687
|
} catch {
|
|
@@ -1992,6 +1711,10 @@ async function handleHook(hookName) {
|
|
|
1992
1711
|
await handleSessionEnd(ctx);
|
|
1993
1712
|
break;
|
|
1994
1713
|
}
|
|
1714
|
+
const hookEvent = buildHookEvent(hookName, input, exitCode);
|
|
1715
|
+
if (hookEvent) {
|
|
1716
|
+
emitToApi(hookEvent);
|
|
1717
|
+
}
|
|
1995
1718
|
process.exit(exitCode);
|
|
1996
1719
|
}
|
|
1997
1720
|
|
|
@@ -2010,7 +1733,7 @@ function parseGlobalFlags(args2) {
|
|
|
2010
1733
|
const projectIdx = args2.findIndex((arg) => arg === "--project" || arg === "-p");
|
|
2011
1734
|
if (projectIdx !== -1 && args2[projectIdx + 1]) {
|
|
2012
1735
|
const projectRef = args2[projectIdx + 1];
|
|
2013
|
-
const entry =
|
|
1736
|
+
const entry = getRepo(projectRef);
|
|
2014
1737
|
if (!entry) {
|
|
2015
1738
|
console.error(`Error: Unknown project: ${projectRef}`);
|
|
2016
1739
|
console.error("Run 'ulpi projects list' to see registered projects.");
|
|
@@ -2024,7 +1747,7 @@ function parseGlobalFlags(args2) {
|
|
|
2024
1747
|
if (isProjectDir) {
|
|
2025
1748
|
return { projectDir: cwd, remainingArgs: args2 };
|
|
2026
1749
|
}
|
|
2027
|
-
const defaultProject =
|
|
1750
|
+
const defaultProject = getDefaultRepo();
|
|
2028
1751
|
if (defaultProject) {
|
|
2029
1752
|
return { projectDir: defaultProject.path, remainingArgs: args2 };
|
|
2030
1753
|
}
|
|
@@ -2045,47 +1768,81 @@ async function main() {
|
|
|
2045
1768
|
switch (command) {
|
|
2046
1769
|
case "init": {
|
|
2047
1770
|
const initDir = rawArgs.some((a) => a === "--project" || a === "-p") ? projectDir : process.cwd();
|
|
2048
|
-
return (await import("./init-
|
|
1771
|
+
return (await import("./init-5FK3VKRT.js")).runInit([initDir, ...args.slice(1)]);
|
|
2049
1772
|
}
|
|
2050
1773
|
case "rules":
|
|
2051
|
-
return (await import("./rules-
|
|
1774
|
+
return (await import("./rules-Y4VSOY5Y.js")).runRules(args.slice(1), projectDir);
|
|
2052
1775
|
case "templates":
|
|
2053
|
-
return (await import("./templates-
|
|
1776
|
+
return (await import("./templates-CBRUJ66V.js")).runTemplates(args.slice(1), projectDir);
|
|
2054
1777
|
case "skills":
|
|
2055
|
-
return (await import("./skills-
|
|
1778
|
+
return (await import("./skills-QEYU2N27.js")).runSkills(args.slice(1), projectDir);
|
|
2056
1779
|
case "status":
|
|
2057
|
-
return (await import("./status-
|
|
1780
|
+
return (await import("./status-BHQYYGAL.js")).runStatus(args.slice(1), projectDir);
|
|
2058
1781
|
case "log":
|
|
2059
1782
|
return (await import("./log-TVTUXAYD.js")).runLog(args.slice(1), projectDir);
|
|
2060
1783
|
case "export":
|
|
2061
|
-
return (await import("./export-import-
|
|
1784
|
+
return (await import("./export-import-JFQH4KSJ.js")).runExport(args.slice(1), projectDir);
|
|
2062
1785
|
case "import":
|
|
2063
|
-
return (await import("./export-import-
|
|
1786
|
+
return (await import("./export-import-JFQH4KSJ.js")).runImport(args.slice(1), projectDir);
|
|
2064
1787
|
case "uninstall":
|
|
2065
|
-
return (await import("./uninstall-
|
|
1788
|
+
return (await import("./uninstall-ICUV6DDV.js")).runUninstall(args.slice(1), projectDir);
|
|
1789
|
+
case "start":
|
|
1790
|
+
return (await import("./start-JYOEL7AJ.js")).runStart(args.slice(1), projectDir);
|
|
2066
1791
|
case "ui":
|
|
2067
|
-
|
|
1792
|
+
console.log(chalk.yellow("'ulpi ui' is deprecated. Use 'ulpi start' instead."));
|
|
1793
|
+
return (await import("./start-JYOEL7AJ.js")).runStart(args.slice(1), projectDir);
|
|
2068
1794
|
case "update":
|
|
2069
|
-
return (await import("./update-
|
|
1795
|
+
return (await import("./update-7ZMAYRBH.js")).runUpdate(args.slice(1));
|
|
2070
1796
|
case "history":
|
|
2071
|
-
return (await import("./history-
|
|
1797
|
+
return (await import("./history-5NE46ZAH.js")).runHistory(args.slice(1), projectDir);
|
|
2072
1798
|
case "review":
|
|
2073
|
-
|
|
1799
|
+
console.error("The 'review' command has been removed in ULPI v2.");
|
|
1800
|
+
process.exit(1);
|
|
1801
|
+
return;
|
|
2074
1802
|
case "config":
|
|
2075
|
-
return (await import("./config-
|
|
1803
|
+
return (await import("./config-OOELBYTH.js")).runConfig(args.slice(1));
|
|
2076
1804
|
case "codemap":
|
|
2077
|
-
return (await import("./codemap-
|
|
1805
|
+
return (await import("./codemap-XNGMAF3F.js")).runCodemap(args.slice(1), projectDir);
|
|
2078
1806
|
case "memory":
|
|
2079
|
-
return (await import("./memory-
|
|
2080
|
-
case "
|
|
2081
|
-
return (await import("./
|
|
1807
|
+
return (await import("./memory-ZNAEAK3B.js")).runMemory(args.slice(1), projectDir);
|
|
1808
|
+
case "repos":
|
|
1809
|
+
return (await import("./repos-WWZXNN3P.js")).runRepos(args.slice(1));
|
|
2082
1810
|
case "ci":
|
|
2083
|
-
return (await import("./ci-
|
|
1811
|
+
return (await import("./ci-X3U2W4HC.js")).runCi(args.slice(1), projectDir);
|
|
2084
1812
|
case "auth":
|
|
2085
|
-
return (await import("./auth-
|
|
1813
|
+
return (await import("./auth-HDK7ECJL.js")).runAuth(args.slice(1));
|
|
1814
|
+
case "prd":
|
|
1815
|
+
return (await import("./prd-Q4J5NVAR.js")).runPrd(args.slice(1), projectDir);
|
|
1816
|
+
case "run":
|
|
1817
|
+
return (await import("./run-VPNXEIBY.js")).runRunCommand(args.slice(1), projectDir);
|
|
1818
|
+
case "job":
|
|
1819
|
+
return (await import("./job-HIDMAFW2.js")).runJobCommand(args.slice(1), projectDir);
|
|
1820
|
+
case "tui":
|
|
1821
|
+
return (await import("./tui-DP7736EX.js")).runTuiCommand(args.slice(1), projectDir);
|
|
1822
|
+
case "portal":
|
|
1823
|
+
return (await import("./portal-JYWVHXDU.js")).runPortalCommand(args.slice(1), projectDir);
|
|
1824
|
+
case "doctor":
|
|
1825
|
+
return (await import("./doctor-SI4LLLDZ.js")).runDoctorCommand(args.slice(1), projectDir);
|
|
1826
|
+
case "kiro":
|
|
1827
|
+
return (await import("./kiro-VMUHDFGK.js")).runKiro(args.slice(1), projectDir);
|
|
1828
|
+
case "codex":
|
|
1829
|
+
return (await import("./codex-MB5YTMRT.js")).runCodex(args.slice(1), projectDir);
|
|
1830
|
+
case "mcp":
|
|
1831
|
+
if (args[1] === "setup") {
|
|
1832
|
+
return (await import("./mcp-setup-OA7IB3H3.js")).runMcpSetup(projectDir);
|
|
1833
|
+
}
|
|
1834
|
+
if (args[1] === "codemap") {
|
|
1835
|
+
return (await import("./codemap-XNGMAF3F.js")).runCodemap(["serve"], projectDir);
|
|
1836
|
+
}
|
|
1837
|
+
if (args[1] === "memory") {
|
|
1838
|
+
return (await import("./memory-ZNAEAK3B.js")).runMemory(["serve"], projectDir);
|
|
1839
|
+
}
|
|
1840
|
+
return (await import("./mcp-PDUD7SGP.js")).runMcp(args.slice(1), projectDir);
|
|
1841
|
+
case "cloud":
|
|
1842
|
+
return (await import("./cloud-2F3NLVHN.js")).runCloud(args.slice(1), projectDir);
|
|
2086
1843
|
case "--version":
|
|
2087
1844
|
case "-v":
|
|
2088
|
-
console.log("0.1.
|
|
1845
|
+
console.log("0.1.6");
|
|
2089
1846
|
return;
|
|
2090
1847
|
case "--help":
|
|
2091
1848
|
case "-h":
|
|
@@ -2099,11 +1856,11 @@ async function main() {
|
|
|
2099
1856
|
}
|
|
2100
1857
|
function printUsage() {
|
|
2101
1858
|
console.log(`
|
|
2102
|
-
ULPI \u2014
|
|
1859
|
+
ULPI \u2014 Autonomous AI agent orchestration engine
|
|
2103
1860
|
|
|
2104
1861
|
Usage: ulpi <command> [options]
|
|
2105
1862
|
|
|
2106
|
-
Hook Handlers (invoked by
|
|
1863
|
+
Hook Handlers (invoked by AI agents):
|
|
2107
1864
|
session-start Initialize session state
|
|
2108
1865
|
pre-tool Evaluate rules before tool execution
|
|
2109
1866
|
post-tool Track state and run postconditions
|
|
@@ -2112,23 +1869,42 @@ Hook Handlers (invoked by Claude Code):
|
|
|
2112
1869
|
stop Final checks before stopping
|
|
2113
1870
|
session-end Cleanup and persist summary
|
|
2114
1871
|
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
1872
|
+
Core Commands:
|
|
1873
|
+
start Start ULPI daemon (API server + codemap watcher)
|
|
1874
|
+
run Start execution engine loop (agent + tracker)
|
|
1875
|
+
init Detect stack, generate guards, install hooks
|
|
1876
|
+
|
|
1877
|
+
Management Commands:
|
|
1878
|
+
repos Manage registered repos (list/add/remove/default)
|
|
2118
1879
|
rules Manage rules (list/add/enable/disable/validate)
|
|
2119
1880
|
templates Manage templates (list/save/apply/delete)
|
|
2120
1881
|
skills Manage skills (list/add/get/attach)
|
|
1882
|
+
config Manage settings and API keys
|
|
1883
|
+
prd PRD management (create/convert/show)
|
|
1884
|
+
job Manage execution jobs (create/list/status/cancel/logs)
|
|
1885
|
+
|
|
1886
|
+
Monitoring Commands:
|
|
2121
1887
|
status Show current session state
|
|
2122
1888
|
log View activity log
|
|
2123
|
-
export Export rules configuration
|
|
2124
|
-
import Import rules configuration
|
|
2125
|
-
uninstall Remove hooks
|
|
2126
|
-
ui Start web UI
|
|
2127
1889
|
history Shadow branch history (init/capture/list/show/enrich/backfill)
|
|
2128
|
-
|
|
2129
|
-
|
|
1890
|
+
tui Terminal UI client (connects to daemon)
|
|
1891
|
+
portal Start Portal dashboard in local mode (start/stop)
|
|
1892
|
+
doctor System health check (agents, MCP, hooks, intelligence)
|
|
1893
|
+
|
|
1894
|
+
Data & Intelligence:
|
|
2130
1895
|
codemap Semantic code indexing (init/search/status/reindex/watch)
|
|
2131
1896
|
memory Agent memory (init/search/remember/status/export/import/serve)
|
|
1897
|
+
mcp MCP management (gateway/list/add/remove/enable/disable/catalog/setup)
|
|
1898
|
+
cloud Connect to Cloud MCP Gateway (connect/status/disconnect)
|
|
1899
|
+
|
|
1900
|
+
Agent Integrations:
|
|
1901
|
+
kiro Manage Kiro CLI integration (install/uninstall/status)
|
|
1902
|
+
codex Manage Codex CLI integration (install/convert/uninstall/status)
|
|
1903
|
+
|
|
1904
|
+
Utility:
|
|
1905
|
+
export Export rules configuration
|
|
1906
|
+
import Import rules configuration
|
|
1907
|
+
uninstall Remove hooks
|
|
2132
1908
|
ci Run in CI/PR worker mode (used inside worker containers)
|
|
2133
1909
|
auth Manage Claude Code credentials for CI workers
|
|
2134
1910
|
update Check for and install updates
|