@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.
@@ -137,7 +137,8 @@ import { createDefu } from "defu";
137
137
  import {
138
138
  DEFAULT_PROVIDER_MODE,
139
139
  VALID_MODES,
140
- DEFAULT_INTENTS
140
+ DEFAULT_INTENTS,
141
+ isWorkflowConfig
141
142
  } from "@inspecto-dev/types";
142
143
 
143
144
  // src/utils/logger.ts
@@ -371,13 +372,28 @@ function resolveIntents(serverPrompts) {
371
372
  );
372
373
  continue;
373
374
  }
374
- if (!item.aiIntent) {
375
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
376
- continue;
375
+ if (item.kind === "workflow") {
376
+ if (!item.prompt) {
377
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
378
+ continue;
379
+ }
380
+ result.push({
381
+ kind: "workflow",
382
+ id: item.id,
383
+ label: item.label ?? item.id,
384
+ prompt: item.prompt,
385
+ confirm: item.confirm ?? false,
386
+ enabled: item.enabled ?? true
387
+ });
388
+ } else {
389
+ if (!item.aiIntent) {
390
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
391
+ continue;
392
+ }
393
+ result.push(
394
+ baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
395
+ );
377
396
  }
378
- result.push(
379
- baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
380
- );
381
397
  }
382
398
  }
383
399
  return result;
@@ -397,26 +413,61 @@ function resolveIntents(serverPrompts) {
397
413
  configLogger.warn('Intent object missing required "id" field, skipping.');
398
414
  continue;
399
415
  }
400
- if (!item.aiIntent) {
401
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
402
- continue;
403
- }
404
- const existingIdx = merged.findIndex((i) => i.id === item.id);
405
- if (existingIdx !== -1) {
406
- if (item.enabled === false) {
407
- merged.splice(existingIdx, 1);
416
+ if (item.kind === "workflow") {
417
+ if (!item.prompt) {
418
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
419
+ continue;
420
+ }
421
+ const wfConfig = {
422
+ kind: "workflow",
423
+ id: item.id,
424
+ label: item.label ?? item.id,
425
+ prompt: item.prompt,
426
+ confirm: item.confirm ?? false,
427
+ enabled: item.enabled ?? true
428
+ };
429
+ const existingIdx = merged.findIndex((i) => i.id === item.id);
430
+ if (existingIdx !== -1) {
431
+ if (item.enabled === false) {
432
+ merged.splice(existingIdx, 1);
433
+ } else {
434
+ merged[existingIdx] = wfConfig;
435
+ }
408
436
  } else {
409
- merged[existingIdx] = { ...merged[existingIdx], ...item };
437
+ if (item.enabled !== false) {
438
+ merged.push(wfConfig);
439
+ }
410
440
  }
411
441
  } else {
412
- if (item.enabled !== false) {
413
- merged.push(item);
442
+ if (!item.aiIntent) {
443
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
444
+ continue;
445
+ }
446
+ const existingIdx = merged.findIndex((i) => i.id === item.id);
447
+ if (existingIdx !== -1) {
448
+ if (item.enabled === false) {
449
+ merged.splice(existingIdx, 1);
450
+ } else {
451
+ merged[existingIdx] = { ...merged[existingIdx], ...item };
452
+ }
453
+ } else {
454
+ if (item.enabled !== false) {
455
+ merged.push(item);
456
+ }
414
457
  }
415
458
  }
416
459
  }
417
460
  }
418
461
  return merged;
419
462
  }
463
+ function resolveWorkflowSlots(intents) {
464
+ return intents.filter(isWorkflowConfig).filter((w) => w.enabled !== false).map((w) => ({
465
+ id: w.id,
466
+ label: w.label ?? w.id,
467
+ prompt: w.prompt,
468
+ confirm: w.confirm ?? false
469
+ }));
470
+ }
420
471
  var watchers = [];
421
472
  function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
422
473
  if (isWatching) return;
@@ -646,6 +697,10 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
646
697
  }
647
698
  }
648
699
 
700
+ // src/server/annotation-dispatch.ts
701
+ import { exec } from "child_process";
702
+ import { promisify } from "util";
703
+
649
704
  // src/server/session-store.ts
650
705
  var DEFAULT_STATUS = "pending";
651
706
  function createAnnotationSessionStore(options = {}) {
@@ -653,8 +708,12 @@ function createAnnotationSessionStore(options = {}) {
653
708
  const listeners = /* @__PURE__ */ new Set();
654
709
  const now = options.now ?? (() => Date.now());
655
710
  const createId = options.createId ?? createRandomId;
656
- function findNewestMatchingSession(statuses) {
657
- return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
711
+ function findNewestMatchingSession(statuses, source) {
712
+ return [...sessions.values()].filter((session) => {
713
+ if (statuses && !statuses.has(session.status)) return false;
714
+ if (source && session.source !== source) return false;
715
+ return true;
716
+ }).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
658
717
  }
659
718
  function updateSessionStatus(id, status) {
660
719
  const session = sessions.get(id);
@@ -714,7 +773,7 @@ function createAnnotationSessionStore(options = {}) {
714
773
  },
715
774
  async claimNextSession(options2 = {}) {
716
775
  const statuses = normalizeStatuses(DEFAULT_STATUS);
717
- const existingSession = findNewestMatchingSession(statuses);
776
+ const existingSession = findNewestMatchingSession(statuses, options2.source);
718
777
  if (existingSession) {
719
778
  return {
720
779
  session: claimSession(existingSession.id, statuses),
@@ -829,6 +888,7 @@ function cloneValue(value) {
829
888
  }
830
889
 
831
890
  // src/server/annotation-dispatch.ts
891
+ var execAsync = promisify(exec);
832
892
  var AnnotationDispatchError = class extends Error {
833
893
  constructor(message, errorCode) {
834
894
  super(message);
@@ -840,9 +900,14 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
840
900
  try {
841
901
  validateAnnotationDispatchRequest(req, state);
842
902
  const batch = normalizeAnnotationBatch(req);
843
- const prompt = buildAnnotationBatchPrompt(batch);
903
+ let prompt = buildAnnotationBatchPrompt(batch);
904
+ if (req.source === "workflow") {
905
+ prompt = await appendProjectMetadata(prompt, state);
906
+ }
844
907
  const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
845
908
  const session = store.createSession({
909
+ source: req.source || "annotation",
910
+ ...req.workflowId ? { workflowId: req.workflowId } : {},
846
911
  instruction: batch.instruction,
847
912
  annotations: toSessionAnnotations(batch.annotations),
848
913
  deliveryMode,
@@ -868,6 +933,33 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
868
933
  };
869
934
  }
870
935
  }
936
+ async function appendProjectMetadata(prompt, state) {
937
+ const lines = ["\n## Project"];
938
+ lines.push(`- Root: ${state.projectRoot}`);
939
+ try {
940
+ const options = {
941
+ cwd: state.projectRoot,
942
+ encoding: "utf-8",
943
+ timeout: 2e3
944
+ };
945
+ const { stdout: branchStdout } = await execAsync("git branch --show-current", options);
946
+ const branch = branchStdout.trim();
947
+ lines.push(`- Branch: ${branch}`);
948
+ const { stdout: statusStdout } = await execAsync("git status --porcelain", options);
949
+ const statusRaw = statusStdout.trim();
950
+ const entries = statusRaw ? statusRaw.split("\n") : [];
951
+ const staged = entries.filter((l) => l[0] !== " " && l[0] !== "?").length;
952
+ const unstaged = entries.filter((l) => l[1] !== " " && l[1] !== "?").length;
953
+ const untracked = entries.filter((l) => l[0] === "?").length;
954
+ lines.push(`- Status: ${staged} staged, ${unstaged} unstaged, ${untracked} untracked`);
955
+ } catch (err) {
956
+ console.warn("[inspecto] Failed to get git status for workflow:", err);
957
+ lines.push("- Git: unavailable or check timeout");
958
+ }
959
+ return `${prompt}
960
+
961
+ ${lines.join("\n")}`;
962
+ }
871
963
  function normalizeDeliveryMode(input) {
872
964
  return input === "agent" ? "agent" : "ide";
873
965
  }
@@ -904,7 +996,7 @@ function toSessionSummary(session) {
904
996
  };
905
997
  }
906
998
  function validateAnnotationDispatchRequest(req, state) {
907
- if (!req.annotations.length) {
999
+ if (!req.annotations.length && req.source !== "workflow") {
908
1000
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
909
1001
  }
910
1002
  for (const annotation of req.annotations) {
@@ -1006,6 +1098,7 @@ function getAnnotationDispatchErrorCode(error) {
1006
1098
  }
1007
1099
 
1008
1100
  // src/server/client-config.ts
1101
+ import { isAiIntentConfig } from "@inspecto-dev/types";
1009
1102
  async function buildClientConfig(serverState2) {
1010
1103
  const userConfig = loadUserConfigSync(false, serverState2.cwd, serverState2.configRoot);
1011
1104
  const promptsConfig = await loadPromptsConfig(false, serverState2.cwd, serverState2.configRoot);
@@ -1017,11 +1110,13 @@ async function buildClientConfig(serverState2) {
1017
1110
  const { scheme: _scheme, ...rest } = serverState2.ideInfo;
1018
1111
  info = rest;
1019
1112
  }
1113
+ const allIntents = resolveIntents(promptsConfig);
1020
1114
  return {
1021
1115
  ...info,
1022
- prompts: resolveIntents(promptsConfig),
1116
+ prompts: allIntents.filter(isAiIntentConfig),
1117
+ workflows: resolveWorkflowSlots(allIntents),
1023
1118
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
1024
- annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
1119
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "agent",
1025
1120
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
1026
1121
  runtimeContext: {
1027
1122
  enabled: true,
@@ -1118,41 +1213,60 @@ import fs4 from "fs";
1118
1213
  import path5 from "path";
1119
1214
  import { execSync } from "child_process";
1120
1215
  var serverLogger3 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1121
- function resolveProjectRoot() {
1122
- const cwd = process.cwd();
1123
- let gitRoot;
1216
+ function resolveGitRoot(_cwd) {
1124
1217
  try {
1125
- gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
1218
+ const output = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" });
1219
+ return typeof output === "string" ? output.trim() : null;
1126
1220
  } catch (e) {
1127
1221
  serverLogger3.warn("Failed to resolve git root via git rev-parse:", e);
1128
- gitRoot = cwd;
1129
- }
1130
- const visited = /* @__PURE__ */ new Set();
1131
- const search = (start, stop) => {
1132
- let current = start;
1133
- while (!visited.has(current)) {
1134
- visited.add(current);
1135
- if (fs4.existsSync(path5.join(current, ".inspecto"))) return current;
1136
- if (current === stop) break;
1137
- const parent = path5.dirname(current);
1138
- if (parent === current) break;
1139
- current = parent;
1140
- }
1141
1222
  return null;
1142
- };
1143
- const cwdMatch = search(cwd, path5.parse(cwd).root);
1144
- if (cwdMatch) return cwdMatch;
1145
- const repoMatch = search(gitRoot, path5.parse(gitRoot).root);
1146
- if (repoMatch) return repoMatch;
1147
- return gitRoot;
1223
+ }
1224
+ }
1225
+ function findNearestAncestorWith(start, predicate) {
1226
+ let current = start;
1227
+ while (true) {
1228
+ if (predicate(current)) return current;
1229
+ const parent = path5.dirname(current);
1230
+ if (parent === current) break;
1231
+ current = parent;
1232
+ }
1233
+ return null;
1234
+ }
1235
+ function resolveWorkspaceRoot() {
1236
+ const cwd = process.cwd();
1237
+ const inspectoRoot = findNearestAncestorWith(
1238
+ cwd,
1239
+ (dir) => fs4.existsSync(path5.join(dir, ".inspecto"))
1240
+ );
1241
+ if (inspectoRoot) return inspectoRoot;
1242
+ const packageRoot = findNearestAncestorWith(
1243
+ cwd,
1244
+ (dir) => fs4.existsSync(path5.join(dir, "package.json"))
1245
+ );
1246
+ if (packageRoot) return packageRoot;
1247
+ return resolveGitRoot(cwd) ?? cwd;
1248
+ }
1249
+ function resolveProjectRoot() {
1250
+ const cwd = process.cwd();
1251
+ const packageRoot = findNearestAncestorWith(
1252
+ cwd,
1253
+ (dir) => fs4.existsSync(path5.join(dir, "package.json"))
1254
+ );
1255
+ if (packageRoot) return packageRoot;
1256
+ const inspectoRoot = findNearestAncestorWith(
1257
+ cwd,
1258
+ (dir) => fs4.existsSync(path5.join(dir, ".inspecto"))
1259
+ );
1260
+ if (inspectoRoot) return inspectoRoot;
1261
+ return resolveGitRoot(cwd) ?? cwd;
1148
1262
  }
1149
1263
 
1150
1264
  // src/server/server-url.ts
1151
1265
  function resolveServerHost(cwd, configRoot) {
1266
+ if (process.env["VITEST"]) return "127.0.0.1";
1152
1267
  const userConfig = loadUserConfigSync(false, cwd, configRoot);
1153
1268
  const configuredHost = userConfig["server.host"]?.trim();
1154
1269
  if (configuredHost) return configuredHost;
1155
- if (process.env["VITEST"]) return "127.0.0.1";
1156
1270
  return "127.0.0.1";
1157
1271
  }
1158
1272
 
@@ -1208,7 +1322,7 @@ async function startServer() {
1208
1322
  return serverState.port;
1209
1323
  }
1210
1324
  serverState.projectRoot = resolveProjectRoot();
1211
- serverState.configRoot = serverState.projectRoot;
1325
+ serverState.configRoot = resolveWorkspaceRoot();
1212
1326
  serverState.cwd = process.cwd();
1213
1327
  const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
1214
1328
  portfinder.basePort = 5678;
@@ -1621,10 +1735,23 @@ data: ${JSON.stringify({ ok: true })}
1621
1735
  }
1622
1736
  async function dispatchToAi(req) {
1623
1737
  const { location, snippet, prompt } = req;
1624
- const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1738
+ if (prompt?.trim()) {
1739
+ const runtime2 = resolvePromptDispatchRuntime(serverState);
1740
+ return dispatchPromptThroughIde(runtime2, {
1741
+ prompt: prompt.trim()
1742
+ });
1743
+ }
1744
+ if (!location) {
1745
+ return {
1746
+ success: false,
1747
+ error: "Source location is required when prompt is omitted.",
1748
+ errorCode: "INVALID_REQUEST"
1749
+ };
1750
+ }
1751
+ const formattedPrompt = `Please help me with this code from \`${location.file}\` (line ${location.line}):
1625
1752
 
1626
1753
  \`\`\`
1627
- ${snippet}
1754
+ ${snippet ?? ""}
1628
1755
  \`\`\`
1629
1756
  `;
1630
1757
  const runtime = resolvePromptDispatchRuntime(serverState);
@@ -1633,7 +1760,7 @@ ${snippet}
1633
1760
  filePath: location.file,
1634
1761
  line: location.line,
1635
1762
  column: location.column,
1636
- snippet
1763
+ ...snippet !== void 0 ? { snippet } : {}
1637
1764
  });
1638
1765
  }
1639
1766
  function getBatchDispatchStatusCode(errorCode, success) {