@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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
  3. package/dist/{auth-ECQ3IB4E.js → auth-HDK7ECJL.js} +2 -1
  4. package/dist/{chunk-3SBPZRB5.js → chunk-3BCW6ABU.js} +402 -142
  5. package/dist/{chunk-JGBXM5NC.js → chunk-3WB5CXH4.js} +180 -5
  6. package/dist/{chunk-2HEE5OKX.js → chunk-4UCJIAOU.js} +2 -2
  7. package/dist/chunk-4XTHZVDS.js +109 -0
  8. package/dist/chunk-4ZPOZULQ.js +6522 -0
  9. package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
  10. package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
  11. package/dist/chunk-76D3BYJD.js +221 -0
  12. package/dist/{chunk-ZLYRPD7I.js → chunk-AWOSRA5F.js} +1 -1
  13. package/dist/{chunk-PDR55ZNW.js → chunk-BFEKZZHM.js} +274 -57
  14. package/dist/chunk-C7CLUQI6.js +1286 -0
  15. package/dist/{chunk-7AL4DOEJ.js → chunk-E3B5NROU.js} +7 -7
  16. package/dist/chunk-EJ7TW77N.js +1418 -0
  17. package/dist/{chunk-5J6NLQUN.js → chunk-IV6MWETF.js} +383 -168
  18. package/dist/chunk-IZPJHSPX.js +1478 -0
  19. package/dist/chunk-JLHNLM3C.js +228 -0
  20. package/dist/{chunk-BZL5H4YQ.js → chunk-KYYI23AQ.js} +2 -2
  21. package/dist/{chunk-2CLNOKPA.js → chunk-RSFJ6QSR.js} +18 -0
  22. package/dist/chunk-S6ANCSYO.js +1271 -0
  23. package/dist/chunk-SEU7WWNQ.js +1251 -0
  24. package/dist/chunk-SNQ7NAIS.js +453 -0
  25. package/dist/{ulpi-RMMCUAGP-JCJ273T6.js → chunk-TSLDGT5O.js} +73 -35
  26. package/dist/{chunk-SPOI23SB.js → chunk-UXHCHOWQ.js} +83 -62
  27. package/dist/chunk-V2H5D6Y3.js +146 -0
  28. package/dist/{chunk-QJ5GSMEC.js → chunk-VVEDXI7E.js} +2 -1
  29. package/dist/chunk-VXH5Y4FO.js +6761 -0
  30. package/dist/chunk-WED4LM5N.js +322 -0
  31. package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
  32. package/dist/chunk-Z53CAR7G.js +298 -0
  33. package/dist/ci-X3U2W4HC.js +854 -0
  34. package/dist/cloud-2F3NLVHN.js +274 -0
  35. package/dist/{codemap-RKSD4MIE.js → codemap-XNGMAF3F.js} +37 -37
  36. package/dist/codex-MB5YTMRT.js +132 -0
  37. package/dist/{config-EGAXXCGL.js → config-OOELBYTH.js} +1 -1
  38. package/dist/dist-2BJYR5EI.js +59 -0
  39. package/dist/dist-2K7IEVTA.js +43 -0
  40. package/dist/dist-3EIQTZHT.js +1380 -0
  41. package/dist/{dist-YA2BWZB2.js → dist-4U5L2X2C.js} +2 -2
  42. package/dist/{dist-UKMCJBB2.js → dist-54KAMNLO.js} +16 -15
  43. package/dist/dist-6M4MZWZW.js +58 -0
  44. package/dist/dist-6X576SU2.js +27 -0
  45. package/dist/dist-7QOEYLFX.js +103 -0
  46. package/dist/dist-AYBGHEDY.js +2541 -0
  47. package/dist/dist-EK45QNEM.js +45 -0
  48. package/dist/{dist-CS2VKNYS.js → dist-FKFEJRPX.js} +16 -15
  49. package/dist/dist-GTEJUBBT.js +66 -0
  50. package/dist/dist-HA74OKJZ.js +40 -0
  51. package/dist/dist-HU5RZAON.js +48 -0
  52. package/dist/dist-IYE3OBRB.js +374 -0
  53. package/dist/{dist-GJYT2OQV.js → dist-JLU26AB6.js} +12 -9
  54. package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
  55. package/dist/dist-NUEMFZFL.js +33 -0
  56. package/dist/{dist-RKOGLK7R.js → dist-NUXMDXZ3.js} +31 -3
  57. package/dist/{dist-QAU3LGJN.js → dist-YCNWHSLN.js} +15 -5
  58. package/dist/{dist-CB5D5LMO.js → dist-YFFG2ZD6.js} +9 -16
  59. package/dist/dist-ZG4OKCSR.js +15 -0
  60. package/dist/doctor-SI4LLLDZ.js +345 -0
  61. package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
  62. package/dist/{history-3MOBX4MA.js → history-5NE46ZAH.js} +7 -7
  63. package/dist/hooks-installer-UN5JZLDQ.js +19 -0
  64. package/dist/index.js +395 -619
  65. package/dist/{init-6CH4HV5T.js → init-5FK3VKRT.js} +79 -13
  66. package/dist/job-HIDMAFW2.js +376 -0
  67. package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
  68. package/dist/kiro-VMUHDFGK.js +153 -0
  69. package/dist/{launchd-LF2QMSKZ.js → launchd-6AWT54HR.js} +9 -17
  70. package/dist/mcp-PDUD7SGP.js +249 -0
  71. package/dist/mcp-installer-PQU3XOGO.js +259 -0
  72. package/dist/mcp-setup-OA7IB3H3.js +263 -0
  73. package/dist/{memory-Y6OZTXJ2.js → memory-ZNAEAK3B.js} +17 -17
  74. package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
  75. package/dist/{openai-E7G2YAHU-UYY4ZWON.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
  76. package/dist/portal-JYWVHXDU.js +210 -0
  77. package/dist/prd-Q4J5NVAR.js +408 -0
  78. package/dist/repos-WWZXNN3P.js +271 -0
  79. package/dist/review-integration-5WHEJU2A.js +14 -0
  80. package/dist/{rules-E427DKYJ.js → rules-Y4VSOY5Y.js} +3 -3
  81. package/dist/run-VPNXEIBY.js +687 -0
  82. package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
  83. package/dist/server-KKSETHDV-XSSLEENT.js +20 -0
  84. package/dist/{skills-CX73O3IV.js → skills-QEYU2N27.js} +4 -2
  85. package/dist/start-JYOEL7AJ.js +303 -0
  86. package/dist/{status-4DFHDJMN.js → status-BHQYYGAL.js} +2 -2
  87. package/dist/{templates-U7T6MARD.js → templates-CBRUJ66V.js} +4 -3
  88. package/dist/tui-DP7736EX.js +61 -0
  89. package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
  90. package/dist/{uninstall-6SW35IK4.js → uninstall-ICUV6DDV.js} +3 -3
  91. package/dist/{update-M6IBJNYP.js → update-7ZMAYRBH.js} +3 -3
  92. package/dist/{version-checker-Q6YTYAGP.js → version-checker-4ZFMZA7Y.js} +2 -2
  93. package/package.json +39 -31
  94. package/dist/chunk-2MZER6ND.js +0 -415
  95. package/dist/chunk-2VYFVYJL.js +0 -4273
  96. package/dist/chunk-6OCEY7JY.js +0 -422
  97. package/dist/chunk-7LXY5UVC.js +0 -330
  98. package/dist/chunk-B55DDP24.js +0 -136
  99. package/dist/chunk-JWUUVXIV.js +0 -13694
  100. package/dist/chunk-MIAQVCFW.js +0 -39
  101. package/dist/chunk-YM2HV4IA.js +0 -505
  102. package/dist/ci-STSL2LSP.js +0 -370
  103. package/dist/mcp-installer-NQCGKQ23.js +0 -124
  104. package/dist/projects-ATHDD3D6.js +0 -271
  105. package/dist/review-ADUPV3PN.js +0 -152
  106. package/dist/server-USLHY6GH-AEOJC5ST.js +0 -18
  107. package/dist/server-X5P6WH2M-7K2RY34N.js +0 -11
  108. package/dist/skills/ulpi-generate-guardian/SKILL.md +0 -750
  109. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +0 -849
  110. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +0 -591
  111. package/dist/ui-OWXZ3YSR.js +0 -167
  112. 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
- discoverUlpiServer,
4
- registerWithServer,
5
- waitForServerDecision
6
- } from "./chunk-2MZER6ND.js";
3
+ getDefaultRepo,
4
+ getRepo,
5
+ registerRepo
6
+ } from "./chunk-UXHCHOWQ.js";
7
7
  import {
8
- captureCommitDiff,
9
- extractSections,
10
- extractTitle,
11
- getSectionFullText,
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-6OCEY7JY.js";
15
+ } from "./chunk-4ZPOZULQ.js";
27
16
  import {
28
17
  evaluateRules,
29
18
  loadRulesSync,
30
19
  matchesFilePattern
31
- } from "./chunk-SIAQVRKG.js";
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-JGBXM5NC.js";
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-YM2HV4IA.js";
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-7LXY5UVC.js";
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-Q6YTYAGP.js").then((m) => m.checkForUpdates()).catch(() => {
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-GJYT2OQV.js");
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-CB5D5LMO.js");
159
- const { getCodemapBranch, getCurrentBranch } = await import("./dist-RKOGLK7R.js");
160
- const { historyBranchExists: historyBranchExists2 } = await import("./dist-QAU3LGJN.js");
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-CB5D5LMO.js");
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 platform2 = os.platform();
304
+ const platform3 = os.platform();
310
305
  try {
311
- if (platform2 === "darwin") {
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 (platform2 === "linux") {
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 (platform2 === "win32") {
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 contexts = collectPostReviewContexts(input.session_id);
1381
- if (contexts.length > 0) {
1382
- const contextJson = {
1383
- hookSpecificOutput: {
1384
- hookEventName: "PostToolUse",
1385
- additionalContext: contexts.join("\n\n---\n\n")
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-GJYT2OQV.js");
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-CB5D5LMO.js");
1497
- const { getCurrentBranch } = await import("./dist-RKOGLK7R.js");
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-GJYT2OQV.js");
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 = spawn(process.execPath, [process.argv[1], ...args2], {
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 = spawn(process.execPath, [process.argv[1], ...args2], {
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-CB5D5LMO.js");
1878
- const { getCurrentBranch } = await import("./dist-RKOGLK7R.js");
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
- return JSON.parse(raw);
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
- registerProject(projectDir2, {
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 = getProject(projectRef);
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 = getDefaultProject();
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-6CH4HV5T.js")).runInit([initDir, ...args.slice(1)]);
1771
+ return (await import("./init-5FK3VKRT.js")).runInit([initDir, ...args.slice(1)]);
2049
1772
  }
2050
1773
  case "rules":
2051
- return (await import("./rules-E427DKYJ.js")).runRules(args.slice(1), projectDir);
1774
+ return (await import("./rules-Y4VSOY5Y.js")).runRules(args.slice(1), projectDir);
2052
1775
  case "templates":
2053
- return (await import("./templates-U7T6MARD.js")).runTemplates(args.slice(1), projectDir);
1776
+ return (await import("./templates-CBRUJ66V.js")).runTemplates(args.slice(1), projectDir);
2054
1777
  case "skills":
2055
- return (await import("./skills-CX73O3IV.js")).runSkills(args.slice(1), projectDir);
1778
+ return (await import("./skills-QEYU2N27.js")).runSkills(args.slice(1), projectDir);
2056
1779
  case "status":
2057
- return (await import("./status-4DFHDJMN.js")).runStatus(args.slice(1), projectDir);
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-4A5MWLIA.js")).runExport(args.slice(1), projectDir);
1784
+ return (await import("./export-import-JFQH4KSJ.js")).runExport(args.slice(1), projectDir);
2062
1785
  case "import":
2063
- return (await import("./export-import-4A5MWLIA.js")).runImport(args.slice(1), projectDir);
1786
+ return (await import("./export-import-JFQH4KSJ.js")).runImport(args.slice(1), projectDir);
2064
1787
  case "uninstall":
2065
- return (await import("./uninstall-6SW35IK4.js")).runUninstall(args.slice(1), projectDir);
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
- return (await import("./ui-OWXZ3YSR.js")).runUI(args.slice(1), projectDir);
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-M6IBJNYP.js")).runUpdate(args.slice(1));
1795
+ return (await import("./update-7ZMAYRBH.js")).runUpdate(args.slice(1));
2070
1796
  case "history":
2071
- return (await import("./history-3MOBX4MA.js")).runHistory(args.slice(1), projectDir);
1797
+ return (await import("./history-5NE46ZAH.js")).runHistory(args.slice(1), projectDir);
2072
1798
  case "review":
2073
- return (await import("./review-ADUPV3PN.js")).runReview(args.slice(1), projectDir);
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-EGAXXCGL.js")).runConfig(args.slice(1));
1803
+ return (await import("./config-OOELBYTH.js")).runConfig(args.slice(1));
2076
1804
  case "codemap":
2077
- return (await import("./codemap-RKSD4MIE.js")).runCodemap(args.slice(1), projectDir);
1805
+ return (await import("./codemap-XNGMAF3F.js")).runCodemap(args.slice(1), projectDir);
2078
1806
  case "memory":
2079
- return (await import("./memory-Y6OZTXJ2.js")).runMemory(args.slice(1), projectDir);
2080
- case "projects":
2081
- return (await import("./projects-ATHDD3D6.js")).runProjects(args.slice(1));
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-STSL2LSP.js")).runCi(args.slice(1), projectDir);
1811
+ return (await import("./ci-X3U2W4HC.js")).runCi(args.slice(1), projectDir);
2084
1812
  case "auth":
2085
- return (await import("./auth-ECQ3IB4E.js")).runAuth(args.slice(1));
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.4");
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 Rules engine for AI coding agents
1859
+ ULPI \u2014 Autonomous AI agent orchestration engine
2103
1860
 
2104
1861
  Usage: ulpi <command> [options]
2105
1862
 
2106
- Hook Handlers (invoked by Claude Code):
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
- CLI Commands:
2116
- init [--no-ai] [--model=<id>] Detect stack, generate guards, install hooks
2117
- projects Manage registered projects (list/add/remove/default)
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
- review Plan and code review management (list/show/config/migrate)
2129
- config Manage settings and API keys
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