@inspecto-dev/plugin 0.3.10 → 0.3.11

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.
@@ -169,7 +169,8 @@ import { createDefu } from "defu";
169
169
  import {
170
170
  DEFAULT_PROVIDER_MODE,
171
171
  VALID_MODES,
172
- DEFAULT_INTENTS
172
+ DEFAULT_INTENTS,
173
+ isWorkflowConfig
173
174
  } from "@inspecto-dev/types";
174
175
 
175
176
  // src/utils/logger.ts
@@ -403,13 +404,28 @@ function resolveIntents(serverPrompts) {
403
404
  );
404
405
  continue;
405
406
  }
406
- if (!item.aiIntent) {
407
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
408
- continue;
407
+ if (item.kind === "workflow") {
408
+ if (!item.prompt) {
409
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
410
+ continue;
411
+ }
412
+ result.push({
413
+ kind: "workflow",
414
+ id: item.id,
415
+ label: item.label ?? item.id,
416
+ prompt: item.prompt,
417
+ confirm: item.confirm ?? false,
418
+ enabled: item.enabled ?? true
419
+ });
420
+ } else {
421
+ if (!item.aiIntent) {
422
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
423
+ continue;
424
+ }
425
+ result.push(
426
+ baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
427
+ );
409
428
  }
410
- result.push(
411
- baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
412
- );
413
429
  }
414
430
  }
415
431
  return result;
@@ -429,26 +445,61 @@ function resolveIntents(serverPrompts) {
429
445
  configLogger.warn('Intent object missing required "id" field, skipping.');
430
446
  continue;
431
447
  }
432
- if (!item.aiIntent) {
433
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
434
- continue;
435
- }
436
- const existingIdx = merged.findIndex((i) => i.id === item.id);
437
- if (existingIdx !== -1) {
438
- if (item.enabled === false) {
439
- merged.splice(existingIdx, 1);
448
+ if (item.kind === "workflow") {
449
+ if (!item.prompt) {
450
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
451
+ continue;
452
+ }
453
+ const wfConfig = {
454
+ kind: "workflow",
455
+ id: item.id,
456
+ label: item.label ?? item.id,
457
+ prompt: item.prompt,
458
+ confirm: item.confirm ?? false,
459
+ enabled: item.enabled ?? true
460
+ };
461
+ const existingIdx = merged.findIndex((i) => i.id === item.id);
462
+ if (existingIdx !== -1) {
463
+ if (item.enabled === false) {
464
+ merged.splice(existingIdx, 1);
465
+ } else {
466
+ merged[existingIdx] = wfConfig;
467
+ }
440
468
  } else {
441
- merged[existingIdx] = { ...merged[existingIdx], ...item };
469
+ if (item.enabled !== false) {
470
+ merged.push(wfConfig);
471
+ }
442
472
  }
443
473
  } else {
444
- if (item.enabled !== false) {
445
- merged.push(item);
474
+ if (!item.aiIntent) {
475
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
476
+ continue;
477
+ }
478
+ const existingIdx = merged.findIndex((i) => i.id === item.id);
479
+ if (existingIdx !== -1) {
480
+ if (item.enabled === false) {
481
+ merged.splice(existingIdx, 1);
482
+ } else {
483
+ merged[existingIdx] = { ...merged[existingIdx], ...item };
484
+ }
485
+ } else {
486
+ if (item.enabled !== false) {
487
+ merged.push(item);
488
+ }
446
489
  }
447
490
  }
448
491
  }
449
492
  }
450
493
  return merged;
451
494
  }
495
+ function resolveWorkflowSlots(intents) {
496
+ return intents.filter(isWorkflowConfig).filter((w) => w.enabled !== false).map((w) => ({
497
+ id: w.id,
498
+ label: w.label ?? w.id,
499
+ prompt: w.prompt,
500
+ confirm: w.confirm ?? false
501
+ }));
502
+ }
452
503
  var watchers = [];
453
504
  function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
454
505
  if (isWatching) return;
@@ -678,6 +729,10 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
678
729
  }
679
730
  }
680
731
 
732
+ // src/server/annotation-dispatch.ts
733
+ import { exec } from "child_process";
734
+ import { promisify } from "util";
735
+
681
736
  // src/server/session-store.ts
682
737
  var DEFAULT_STATUS = "pending";
683
738
  function createAnnotationSessionStore(options = {}) {
@@ -685,8 +740,12 @@ function createAnnotationSessionStore(options = {}) {
685
740
  const listeners = /* @__PURE__ */ new Set();
686
741
  const now = options.now ?? (() => Date.now());
687
742
  const createId = options.createId ?? createRandomId;
688
- function findNewestMatchingSession(statuses) {
689
- return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
743
+ function findNewestMatchingSession(statuses, source) {
744
+ return [...sessions.values()].filter((session) => {
745
+ if (statuses && !statuses.has(session.status)) return false;
746
+ if (source && session.source !== source) return false;
747
+ return true;
748
+ }).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
690
749
  }
691
750
  function updateSessionStatus(id, status) {
692
751
  const session = sessions.get(id);
@@ -746,7 +805,7 @@ function createAnnotationSessionStore(options = {}) {
746
805
  },
747
806
  async claimNextSession(options2 = {}) {
748
807
  const statuses = normalizeStatuses(DEFAULT_STATUS);
749
- const existingSession = findNewestMatchingSession(statuses);
808
+ const existingSession = findNewestMatchingSession(statuses, options2.source);
750
809
  if (existingSession) {
751
810
  return {
752
811
  session: claimSession(existingSession.id, statuses),
@@ -861,6 +920,7 @@ function cloneValue(value) {
861
920
  }
862
921
 
863
922
  // src/server/annotation-dispatch.ts
923
+ var execAsync = promisify(exec);
864
924
  var AnnotationDispatchError = class extends Error {
865
925
  constructor(message, errorCode) {
866
926
  super(message);
@@ -872,9 +932,14 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
872
932
  try {
873
933
  validateAnnotationDispatchRequest(req, state);
874
934
  const batch = normalizeAnnotationBatch(req);
875
- const prompt = buildAnnotationBatchPrompt(batch);
935
+ let prompt = buildAnnotationBatchPrompt(batch);
936
+ if (req.source === "workflow") {
937
+ prompt = await appendProjectMetadata(prompt, state);
938
+ }
876
939
  const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
877
940
  const session = store.createSession({
941
+ source: req.source || "annotation",
942
+ ...req.workflowId ? { workflowId: req.workflowId } : {},
878
943
  instruction: batch.instruction,
879
944
  annotations: toSessionAnnotations(batch.annotations),
880
945
  deliveryMode,
@@ -900,6 +965,33 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
900
965
  };
901
966
  }
902
967
  }
968
+ async function appendProjectMetadata(prompt, state) {
969
+ const lines = ["\n## Project"];
970
+ lines.push(`- Root: ${state.projectRoot}`);
971
+ try {
972
+ const options = {
973
+ cwd: state.projectRoot,
974
+ encoding: "utf-8",
975
+ timeout: 2e3
976
+ };
977
+ const { stdout: branchStdout } = await execAsync("git branch --show-current", options);
978
+ const branch = branchStdout.trim();
979
+ lines.push(`- Branch: ${branch}`);
980
+ const { stdout: statusStdout } = await execAsync("git status --porcelain", options);
981
+ const statusRaw = statusStdout.trim();
982
+ const entries = statusRaw ? statusRaw.split("\n") : [];
983
+ const staged = entries.filter((l) => l[0] !== " " && l[0] !== "?").length;
984
+ const unstaged = entries.filter((l) => l[1] !== " " && l[1] !== "?").length;
985
+ const untracked = entries.filter((l) => l[0] === "?").length;
986
+ lines.push(`- Status: ${staged} staged, ${unstaged} unstaged, ${untracked} untracked`);
987
+ } catch (err) {
988
+ console.warn("[inspecto] Failed to get git status for workflow:", err);
989
+ lines.push("- Git: unavailable or check timeout");
990
+ }
991
+ return `${prompt}
992
+
993
+ ${lines.join("\n")}`;
994
+ }
903
995
  function normalizeDeliveryMode(input) {
904
996
  return input === "agent" ? "agent" : "ide";
905
997
  }
@@ -936,7 +1028,7 @@ function toSessionSummary(session) {
936
1028
  };
937
1029
  }
938
1030
  function validateAnnotationDispatchRequest(req, state) {
939
- if (!req.annotations.length) {
1031
+ if (!req.annotations.length && req.source !== "workflow") {
940
1032
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
941
1033
  }
942
1034
  for (const annotation of req.annotations) {
@@ -1038,6 +1130,7 @@ function getAnnotationDispatchErrorCode(error) {
1038
1130
  }
1039
1131
 
1040
1132
  // src/server/client-config.ts
1133
+ import { isAiIntentConfig } from "@inspecto-dev/types";
1041
1134
  async function buildClientConfig(serverState2) {
1042
1135
  const userConfig = loadUserConfigSync(false, serverState2.cwd, serverState2.configRoot);
1043
1136
  const promptsConfig = await loadPromptsConfig(false, serverState2.cwd, serverState2.configRoot);
@@ -1049,11 +1142,13 @@ async function buildClientConfig(serverState2) {
1049
1142
  const { scheme: _scheme, ...rest } = serverState2.ideInfo;
1050
1143
  info = rest;
1051
1144
  }
1145
+ const allIntents = resolveIntents(promptsConfig);
1052
1146
  return {
1053
1147
  ...info,
1054
- prompts: resolveIntents(promptsConfig),
1148
+ prompts: allIntents.filter(isAiIntentConfig),
1149
+ workflows: resolveWorkflowSlots(allIntents),
1055
1150
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
1056
- annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
1151
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "agent",
1057
1152
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
1058
1153
  runtimeContext: {
1059
1154
  enabled: true,
@@ -1150,41 +1245,60 @@ import fs4 from "fs";
1150
1245
  import path5 from "path";
1151
1246
  import { execSync } from "child_process";
1152
1247
  var serverLogger3 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1153
- function resolveProjectRoot() {
1154
- const cwd = process.cwd();
1155
- let gitRoot;
1248
+ function resolveGitRoot(_cwd) {
1156
1249
  try {
1157
- gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
1250
+ const output = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" });
1251
+ return typeof output === "string" ? output.trim() : null;
1158
1252
  } catch (e) {
1159
1253
  serverLogger3.warn("Failed to resolve git root via git rev-parse:", e);
1160
- gitRoot = cwd;
1161
- }
1162
- const visited = /* @__PURE__ */ new Set();
1163
- const search = (start, stop) => {
1164
- let current = start;
1165
- while (!visited.has(current)) {
1166
- visited.add(current);
1167
- if (fs4.existsSync(path5.join(current, ".inspecto"))) return current;
1168
- if (current === stop) break;
1169
- const parent = path5.dirname(current);
1170
- if (parent === current) break;
1171
- current = parent;
1172
- }
1173
1254
  return null;
1174
- };
1175
- const cwdMatch = search(cwd, path5.parse(cwd).root);
1176
- if (cwdMatch) return cwdMatch;
1177
- const repoMatch = search(gitRoot, path5.parse(gitRoot).root);
1178
- if (repoMatch) return repoMatch;
1179
- return gitRoot;
1255
+ }
1256
+ }
1257
+ function findNearestAncestorWith(start, predicate) {
1258
+ let current = start;
1259
+ while (true) {
1260
+ if (predicate(current)) return current;
1261
+ const parent = path5.dirname(current);
1262
+ if (parent === current) break;
1263
+ current = parent;
1264
+ }
1265
+ return null;
1266
+ }
1267
+ function resolveWorkspaceRoot() {
1268
+ const cwd = process.cwd();
1269
+ const inspectoRoot = findNearestAncestorWith(
1270
+ cwd,
1271
+ (dir) => fs4.existsSync(path5.join(dir, ".inspecto"))
1272
+ );
1273
+ if (inspectoRoot) return inspectoRoot;
1274
+ const packageRoot = findNearestAncestorWith(
1275
+ cwd,
1276
+ (dir) => fs4.existsSync(path5.join(dir, "package.json"))
1277
+ );
1278
+ if (packageRoot) return packageRoot;
1279
+ return resolveGitRoot(cwd) ?? cwd;
1280
+ }
1281
+ function resolveProjectRoot() {
1282
+ const cwd = process.cwd();
1283
+ const packageRoot = findNearestAncestorWith(
1284
+ cwd,
1285
+ (dir) => fs4.existsSync(path5.join(dir, "package.json"))
1286
+ );
1287
+ if (packageRoot) return packageRoot;
1288
+ const inspectoRoot = findNearestAncestorWith(
1289
+ cwd,
1290
+ (dir) => fs4.existsSync(path5.join(dir, ".inspecto"))
1291
+ );
1292
+ if (inspectoRoot) return inspectoRoot;
1293
+ return resolveGitRoot(cwd) ?? cwd;
1180
1294
  }
1181
1295
 
1182
1296
  // src/server/server-url.ts
1183
1297
  function resolveServerHost(cwd, configRoot) {
1298
+ if (process.env["VITEST"]) return "127.0.0.1";
1184
1299
  const userConfig = loadUserConfigSync(false, cwd, configRoot);
1185
1300
  const configuredHost = userConfig["server.host"]?.trim();
1186
1301
  if (configuredHost) return configuredHost;
1187
- if (process.env["VITEST"]) return "127.0.0.1";
1188
1302
  return "127.0.0.1";
1189
1303
  }
1190
1304
 
@@ -1240,7 +1354,7 @@ async function startServer() {
1240
1354
  return serverState.port;
1241
1355
  }
1242
1356
  serverState.projectRoot = resolveProjectRoot();
1243
- serverState.configRoot = serverState.projectRoot;
1357
+ serverState.configRoot = resolveWorkspaceRoot();
1244
1358
  serverState.cwd = process.cwd();
1245
1359
  const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
1246
1360
  portfinder.basePort = 5678;
@@ -1653,10 +1767,23 @@ data: ${JSON.stringify({ ok: true })}
1653
1767
  }
1654
1768
  async function dispatchToAi(req) {
1655
1769
  const { location, snippet, prompt } = req;
1656
- const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1770
+ if (prompt?.trim()) {
1771
+ const runtime2 = resolvePromptDispatchRuntime(serverState);
1772
+ return dispatchPromptThroughIde(runtime2, {
1773
+ prompt: prompt.trim()
1774
+ });
1775
+ }
1776
+ if (!location) {
1777
+ return {
1778
+ success: false,
1779
+ error: "Source location is required when prompt is omitted.",
1780
+ errorCode: "INVALID_REQUEST"
1781
+ };
1782
+ }
1783
+ const formattedPrompt = `Please help me with this code from \`${location.file}\` (line ${location.line}):
1657
1784
 
1658
1785
  \`\`\`
1659
- ${snippet}
1786
+ ${snippet ?? ""}
1660
1787
  \`\`\`
1661
1788
  `;
1662
1789
  const runtime = resolvePromptDispatchRuntime(serverState);
@@ -1665,7 +1792,7 @@ ${snippet}
1665
1792
  filePath: location.file,
1666
1793
  line: location.line,
1667
1794
  column: location.column,
1668
- snippet
1795
+ ...snippet !== void 0 ? { snippet } : {}
1669
1796
  });
1670
1797
  }
1671
1798
  function getBatchDispatchStatusCode(errorCode, success) {