@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.
package/dist/astro.js CHANGED
@@ -130,7 +130,8 @@ import { createDefu } from "defu";
130
130
  import {
131
131
  DEFAULT_PROVIDER_MODE,
132
132
  VALID_MODES,
133
- DEFAULT_INTENTS
133
+ DEFAULT_INTENTS,
134
+ isWorkflowConfig
134
135
  } from "@inspecto-dev/types";
135
136
 
136
137
  // src/utils/logger.ts
@@ -378,13 +379,28 @@ function resolveIntents(serverPrompts) {
378
379
  );
379
380
  continue;
380
381
  }
381
- if (!item.aiIntent) {
382
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
383
- continue;
382
+ if (item.kind === "workflow") {
383
+ if (!item.prompt) {
384
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
385
+ continue;
386
+ }
387
+ result.push({
388
+ kind: "workflow",
389
+ id: item.id,
390
+ label: item.label ?? item.id,
391
+ prompt: item.prompt,
392
+ confirm: item.confirm ?? false,
393
+ enabled: item.enabled ?? true
394
+ });
395
+ } else {
396
+ if (!item.aiIntent) {
397
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
398
+ continue;
399
+ }
400
+ result.push(
401
+ baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
402
+ );
384
403
  }
385
- result.push(
386
- baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
387
- );
388
404
  }
389
405
  }
390
406
  return result;
@@ -404,26 +420,61 @@ function resolveIntents(serverPrompts) {
404
420
  configLogger.warn('Intent object missing required "id" field, skipping.');
405
421
  continue;
406
422
  }
407
- if (!item.aiIntent) {
408
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
409
- continue;
410
- }
411
- const existingIdx = merged.findIndex((i2) => i2.id === item.id);
412
- if (existingIdx !== -1) {
413
- if (item.enabled === false) {
414
- merged.splice(existingIdx, 1);
423
+ if (item.kind === "workflow") {
424
+ if (!item.prompt) {
425
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
426
+ continue;
427
+ }
428
+ const wfConfig = {
429
+ kind: "workflow",
430
+ id: item.id,
431
+ label: item.label ?? item.id,
432
+ prompt: item.prompt,
433
+ confirm: item.confirm ?? false,
434
+ enabled: item.enabled ?? true
435
+ };
436
+ const existingIdx = merged.findIndex((i2) => i2.id === item.id);
437
+ if (existingIdx !== -1) {
438
+ if (item.enabled === false) {
439
+ merged.splice(existingIdx, 1);
440
+ } else {
441
+ merged[existingIdx] = wfConfig;
442
+ }
415
443
  } else {
416
- merged[existingIdx] = { ...merged[existingIdx], ...item };
444
+ if (item.enabled !== false) {
445
+ merged.push(wfConfig);
446
+ }
417
447
  }
418
448
  } else {
419
- if (item.enabled !== false) {
420
- merged.push(item);
449
+ if (!item.aiIntent) {
450
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
451
+ continue;
452
+ }
453
+ const existingIdx = merged.findIndex((i2) => i2.id === item.id);
454
+ if (existingIdx !== -1) {
455
+ if (item.enabled === false) {
456
+ merged.splice(existingIdx, 1);
457
+ } else {
458
+ merged[existingIdx] = { ...merged[existingIdx], ...item };
459
+ }
460
+ } else {
461
+ if (item.enabled !== false) {
462
+ merged.push(item);
463
+ }
421
464
  }
422
465
  }
423
466
  }
424
467
  }
425
468
  return merged;
426
469
  }
470
+ function resolveWorkflowSlots(intents) {
471
+ return intents.filter(isWorkflowConfig).filter((w3) => w3.enabled !== false).map((w3) => ({
472
+ id: w3.id,
473
+ label: w3.label ?? w3.id,
474
+ prompt: w3.prompt,
475
+ confirm: w3.confirm ?? false
476
+ }));
477
+ }
427
478
  var watchers = [];
428
479
  function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
429
480
  if (isWatching) return;
@@ -653,6 +704,10 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
653
704
  }
654
705
  }
655
706
 
707
+ // src/server/annotation-dispatch.ts
708
+ import { exec } from "child_process";
709
+ import { promisify } from "util";
710
+
656
711
  // src/server/session-store.ts
657
712
  var DEFAULT_STATUS = "pending";
658
713
  function createAnnotationSessionStore(options = {}) {
@@ -660,8 +715,12 @@ function createAnnotationSessionStore(options = {}) {
660
715
  const listeners = /* @__PURE__ */ new Set();
661
716
  const now = options.now ?? (() => Date.now());
662
717
  const createId = options.createId ?? createRandomId;
663
- function findNewestMatchingSession(statuses) {
664
- return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
718
+ function findNewestMatchingSession(statuses, source) {
719
+ return [...sessions.values()].filter((session) => {
720
+ if (statuses && !statuses.has(session.status)) return false;
721
+ if (source && session.source !== source) return false;
722
+ return true;
723
+ }).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
665
724
  }
666
725
  function updateSessionStatus(id, status) {
667
726
  const session = sessions.get(id);
@@ -721,7 +780,7 @@ function createAnnotationSessionStore(options = {}) {
721
780
  },
722
781
  async claimNextSession(options2 = {}) {
723
782
  const statuses = normalizeStatuses(DEFAULT_STATUS);
724
- const existingSession = findNewestMatchingSession(statuses);
783
+ const existingSession = findNewestMatchingSession(statuses, options2.source);
725
784
  if (existingSession) {
726
785
  return {
727
786
  session: claimSession(existingSession.id, statuses),
@@ -836,6 +895,7 @@ function cloneValue(value) {
836
895
  }
837
896
 
838
897
  // src/server/annotation-dispatch.ts
898
+ var execAsync = promisify(exec);
839
899
  var AnnotationDispatchError = class extends Error {
840
900
  constructor(message, errorCode) {
841
901
  super(message);
@@ -847,9 +907,14 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
847
907
  try {
848
908
  validateAnnotationDispatchRequest(req, state);
849
909
  const batch = normalizeAnnotationBatch(req);
850
- const prompt = buildAnnotationBatchPrompt(batch);
910
+ let prompt = buildAnnotationBatchPrompt(batch);
911
+ if (req.source === "workflow") {
912
+ prompt = await appendProjectMetadata(prompt, state);
913
+ }
851
914
  const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
852
915
  const session = store.createSession({
916
+ source: req.source || "annotation",
917
+ ...req.workflowId ? { workflowId: req.workflowId } : {},
853
918
  instruction: batch.instruction,
854
919
  annotations: toSessionAnnotations(batch.annotations),
855
920
  deliveryMode,
@@ -875,6 +940,33 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
875
940
  };
876
941
  }
877
942
  }
943
+ async function appendProjectMetadata(prompt, state) {
944
+ const lines = ["\n## Project"];
945
+ lines.push(`- Root: ${state.projectRoot}`);
946
+ try {
947
+ const options = {
948
+ cwd: state.projectRoot,
949
+ encoding: "utf-8",
950
+ timeout: 2e3
951
+ };
952
+ const { stdout: branchStdout } = await execAsync("git branch --show-current", options);
953
+ const branch = branchStdout.trim();
954
+ lines.push(`- Branch: ${branch}`);
955
+ const { stdout: statusStdout } = await execAsync("git status --porcelain", options);
956
+ const statusRaw = statusStdout.trim();
957
+ const entries = statusRaw ? statusRaw.split("\n") : [];
958
+ const staged = entries.filter((l) => l[0] !== " " && l[0] !== "?").length;
959
+ const unstaged = entries.filter((l) => l[1] !== " " && l[1] !== "?").length;
960
+ const untracked = entries.filter((l) => l[0] === "?").length;
961
+ lines.push(`- Status: ${staged} staged, ${unstaged} unstaged, ${untracked} untracked`);
962
+ } catch (err) {
963
+ console.warn("[inspecto] Failed to get git status for workflow:", err);
964
+ lines.push("- Git: unavailable or check timeout");
965
+ }
966
+ return `${prompt}
967
+
968
+ ${lines.join("\n")}`;
969
+ }
878
970
  function normalizeDeliveryMode(input) {
879
971
  return input === "agent" ? "agent" : "ide";
880
972
  }
@@ -911,7 +1003,7 @@ function toSessionSummary(session) {
911
1003
  };
912
1004
  }
913
1005
  function validateAnnotationDispatchRequest(req, state) {
914
- if (!req.annotations.length) {
1006
+ if (!req.annotations.length && req.source !== "workflow") {
915
1007
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
916
1008
  }
917
1009
  for (const annotation of req.annotations) {
@@ -1013,6 +1105,7 @@ function getAnnotationDispatchErrorCode(error) {
1013
1105
  }
1014
1106
 
1015
1107
  // src/server/client-config.ts
1108
+ import { isAiIntentConfig } from "@inspecto-dev/types";
1016
1109
  async function buildClientConfig(serverState2) {
1017
1110
  const userConfig = loadUserConfigSync(false, serverState2.cwd, serverState2.configRoot);
1018
1111
  const promptsConfig = await loadPromptsConfig(false, serverState2.cwd, serverState2.configRoot);
@@ -1024,11 +1117,13 @@ async function buildClientConfig(serverState2) {
1024
1117
  const { scheme: _scheme, ...rest } = serverState2.ideInfo;
1025
1118
  info = rest;
1026
1119
  }
1120
+ const allIntents = resolveIntents(promptsConfig);
1027
1121
  return {
1028
1122
  ...info,
1029
- prompts: resolveIntents(promptsConfig),
1123
+ prompts: allIntents.filter(isAiIntentConfig),
1124
+ workflows: resolveWorkflowSlots(allIntents),
1030
1125
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
1031
- annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
1126
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "agent",
1032
1127
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
1033
1128
  runtimeContext: {
1034
1129
  enabled: true,
@@ -1125,41 +1220,60 @@ import fs4 from "fs";
1125
1220
  import path4 from "path";
1126
1221
  import { execSync } from "child_process";
1127
1222
  var serverLogger3 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1128
- function resolveProjectRoot() {
1129
- const cwd = process.cwd();
1130
- let gitRoot;
1223
+ function resolveGitRoot(_cwd) {
1131
1224
  try {
1132
- gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
1225
+ const output = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" });
1226
+ return typeof output === "string" ? output.trim() : null;
1133
1227
  } catch (e) {
1134
1228
  serverLogger3.warn("Failed to resolve git root via git rev-parse:", e);
1135
- gitRoot = cwd;
1136
- }
1137
- const visited = /* @__PURE__ */ new Set();
1138
- const search = (start, stop) => {
1139
- let current = start;
1140
- while (!visited.has(current)) {
1141
- visited.add(current);
1142
- if (fs4.existsSync(path4.join(current, ".inspecto"))) return current;
1143
- if (current === stop) break;
1144
- const parent = path4.dirname(current);
1145
- if (parent === current) break;
1146
- current = parent;
1147
- }
1148
1229
  return null;
1149
- };
1150
- const cwdMatch = search(cwd, path4.parse(cwd).root);
1151
- if (cwdMatch) return cwdMatch;
1152
- const repoMatch = search(gitRoot, path4.parse(gitRoot).root);
1153
- if (repoMatch) return repoMatch;
1154
- return gitRoot;
1230
+ }
1231
+ }
1232
+ function findNearestAncestorWith(start, predicate) {
1233
+ let current = start;
1234
+ while (true) {
1235
+ if (predicate(current)) return current;
1236
+ const parent = path4.dirname(current);
1237
+ if (parent === current) break;
1238
+ current = parent;
1239
+ }
1240
+ return null;
1241
+ }
1242
+ function resolveWorkspaceRoot() {
1243
+ const cwd = process.cwd();
1244
+ const inspectoRoot = findNearestAncestorWith(
1245
+ cwd,
1246
+ (dir) => fs4.existsSync(path4.join(dir, ".inspecto"))
1247
+ );
1248
+ if (inspectoRoot) return inspectoRoot;
1249
+ const packageRoot = findNearestAncestorWith(
1250
+ cwd,
1251
+ (dir) => fs4.existsSync(path4.join(dir, "package.json"))
1252
+ );
1253
+ if (packageRoot) return packageRoot;
1254
+ return resolveGitRoot(cwd) ?? cwd;
1255
+ }
1256
+ function resolveProjectRoot() {
1257
+ const cwd = process.cwd();
1258
+ const packageRoot = findNearestAncestorWith(
1259
+ cwd,
1260
+ (dir) => fs4.existsSync(path4.join(dir, "package.json"))
1261
+ );
1262
+ if (packageRoot) return packageRoot;
1263
+ const inspectoRoot = findNearestAncestorWith(
1264
+ cwd,
1265
+ (dir) => fs4.existsSync(path4.join(dir, ".inspecto"))
1266
+ );
1267
+ if (inspectoRoot) return inspectoRoot;
1268
+ return resolveGitRoot(cwd) ?? cwd;
1155
1269
  }
1156
1270
 
1157
1271
  // src/server/server-url.ts
1158
1272
  function resolveServerHost(cwd, configRoot) {
1273
+ if (process.env["VITEST"]) return "127.0.0.1";
1159
1274
  const userConfig = loadUserConfigSync(false, cwd, configRoot);
1160
1275
  const configuredHost = userConfig["server.host"]?.trim();
1161
1276
  if (configuredHost) return configuredHost;
1162
- if (process.env["VITEST"]) return "127.0.0.1";
1163
1277
  return "127.0.0.1";
1164
1278
  }
1165
1279
  function resolvePublicServerUrl(args) {
@@ -1224,7 +1338,7 @@ async function startServer() {
1224
1338
  return serverState.port;
1225
1339
  }
1226
1340
  serverState.projectRoot = resolveProjectRoot();
1227
- serverState.configRoot = serverState.projectRoot;
1341
+ serverState.configRoot = resolveWorkspaceRoot();
1228
1342
  serverState.cwd = process.cwd();
1229
1343
  const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
1230
1344
  portfinder.basePort = 5678;
@@ -1637,10 +1751,23 @@ data: ${JSON.stringify({ ok: true })}
1637
1751
  }
1638
1752
  async function dispatchToAi(req) {
1639
1753
  const { location, snippet, prompt } = req;
1640
- const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
1754
+ if (prompt?.trim()) {
1755
+ const runtime2 = resolvePromptDispatchRuntime(serverState);
1756
+ return dispatchPromptThroughIde(runtime2, {
1757
+ prompt: prompt.trim()
1758
+ });
1759
+ }
1760
+ if (!location) {
1761
+ return {
1762
+ success: false,
1763
+ error: "Source location is required when prompt is omitted.",
1764
+ errorCode: "INVALID_REQUEST"
1765
+ };
1766
+ }
1767
+ const formattedPrompt = `Please help me with this code from \`${location.file}\` (line ${location.line}):
1641
1768
 
1642
1769
  \`\`\`
1643
- ${snippet}
1770
+ ${snippet ?? ""}
1644
1771
  \`\`\`
1645
1772
  `;
1646
1773
  const runtime = resolvePromptDispatchRuntime(serverState);
@@ -1649,7 +1776,7 @@ ${snippet}
1649
1776
  filePath: location.file,
1650
1777
  line: location.line,
1651
1778
  column: location.column,
1652
- snippet
1779
+ ...snippet !== void 0 ? { snippet } : {}
1653
1780
  });
1654
1781
  }
1655
1782
  function getBatchDispatchStatusCode(errorCode, success) {