@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/rspack.js CHANGED
@@ -837,7 +837,8 @@ import { createDefu } from "defu";
837
837
  import {
838
838
  DEFAULT_PROVIDER_MODE,
839
839
  VALID_MODES,
840
- DEFAULT_INTENTS
840
+ DEFAULT_INTENTS,
841
+ isWorkflowConfig
841
842
  } from "@inspecto-dev/types";
842
843
 
843
844
  // src/utils/logger.ts
@@ -1085,13 +1086,28 @@ function resolveIntents(serverPrompts) {
1085
1086
  );
1086
1087
  continue;
1087
1088
  }
1088
- if (!item.aiIntent) {
1089
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
1090
- continue;
1089
+ if (item.kind === "workflow") {
1090
+ if (!item.prompt) {
1091
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
1092
+ continue;
1093
+ }
1094
+ result.push({
1095
+ kind: "workflow",
1096
+ id: item.id,
1097
+ label: item.label ?? item.id,
1098
+ prompt: item.prompt,
1099
+ confirm: item.confirm ?? false,
1100
+ enabled: item.enabled ?? true
1101
+ });
1102
+ } else {
1103
+ if (!item.aiIntent) {
1104
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
1105
+ continue;
1106
+ }
1107
+ result.push(
1108
+ baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
1109
+ );
1091
1110
  }
1092
- result.push(
1093
- baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item
1094
- );
1095
1111
  }
1096
1112
  }
1097
1113
  return result;
@@ -1111,26 +1127,61 @@ function resolveIntents(serverPrompts) {
1111
1127
  configLogger.warn('Intent object missing required "id" field, skipping.');
1112
1128
  continue;
1113
1129
  }
1114
- if (!item.aiIntent) {
1115
- configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
1116
- continue;
1117
- }
1118
- const existingIdx = merged.findIndex((i2) => i2.id === item.id);
1119
- if (existingIdx !== -1) {
1120
- if (item.enabled === false) {
1121
- merged.splice(existingIdx, 1);
1130
+ if (item.kind === "workflow") {
1131
+ if (!item.prompt) {
1132
+ configLogger.warn(`Workflow "${item.id}" missing required "prompt", skipping`);
1133
+ continue;
1134
+ }
1135
+ const wfConfig = {
1136
+ kind: "workflow",
1137
+ id: item.id,
1138
+ label: item.label ?? item.id,
1139
+ prompt: item.prompt,
1140
+ confirm: item.confirm ?? false,
1141
+ enabled: item.enabled ?? true
1142
+ };
1143
+ const existingIdx = merged.findIndex((i2) => i2.id === item.id);
1144
+ if (existingIdx !== -1) {
1145
+ if (item.enabled === false) {
1146
+ merged.splice(existingIdx, 1);
1147
+ } else {
1148
+ merged[existingIdx] = wfConfig;
1149
+ }
1122
1150
  } else {
1123
- merged[existingIdx] = { ...merged[existingIdx], ...item };
1151
+ if (item.enabled !== false) {
1152
+ merged.push(wfConfig);
1153
+ }
1124
1154
  }
1125
1155
  } else {
1126
- if (item.enabled !== false) {
1127
- merged.push(item);
1156
+ if (!item.aiIntent) {
1157
+ configLogger.warn(`Intent "${item.id}" is missing required "aiIntent".`);
1158
+ continue;
1159
+ }
1160
+ const existingIdx = merged.findIndex((i2) => i2.id === item.id);
1161
+ if (existingIdx !== -1) {
1162
+ if (item.enabled === false) {
1163
+ merged.splice(existingIdx, 1);
1164
+ } else {
1165
+ merged[existingIdx] = { ...merged[existingIdx], ...item };
1166
+ }
1167
+ } else {
1168
+ if (item.enabled !== false) {
1169
+ merged.push(item);
1170
+ }
1128
1171
  }
1129
1172
  }
1130
1173
  }
1131
1174
  }
1132
1175
  return merged;
1133
1176
  }
1177
+ function resolveWorkflowSlots(intents) {
1178
+ return intents.filter(isWorkflowConfig).filter((w3) => w3.enabled !== false).map((w3) => ({
1179
+ id: w3.id,
1180
+ label: w3.label ?? w3.id,
1181
+ prompt: w3.prompt,
1182
+ confirm: w3.confirm ?? false
1183
+ }));
1184
+ }
1134
1185
  var watchers = [];
1135
1186
  function watchConfig(onReload, cwd = process.cwd(), gitRoot) {
1136
1187
  if (isWatching) return;
@@ -1360,6 +1411,10 @@ function assertPathWithinIdeOpenScope(file, projectRoot) {
1360
1411
  }
1361
1412
  }
1362
1413
 
1414
+ // src/server/annotation-dispatch.ts
1415
+ import { exec } from "child_process";
1416
+ import { promisify } from "util";
1417
+
1363
1418
  // src/server/session-store.ts
1364
1419
  var DEFAULT_STATUS = "pending";
1365
1420
  function createAnnotationSessionStore(options = {}) {
@@ -1367,8 +1422,12 @@ function createAnnotationSessionStore(options = {}) {
1367
1422
  const listeners = /* @__PURE__ */ new Set();
1368
1423
  const now = options.now ?? (() => Date.now());
1369
1424
  const createId = options.createId ?? createRandomId;
1370
- function findNewestMatchingSession(statuses) {
1371
- return [...sessions.values()].filter((session) => statuses ? statuses.has(session.status) : true).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
1425
+ function findNewestMatchingSession(statuses, source) {
1426
+ return [...sessions.values()].filter((session) => {
1427
+ if (statuses && !statuses.has(session.status)) return false;
1428
+ if (source && session.source !== source) return false;
1429
+ return true;
1430
+ }).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
1372
1431
  }
1373
1432
  function updateSessionStatus(id, status) {
1374
1433
  const session = sessions.get(id);
@@ -1428,7 +1487,7 @@ function createAnnotationSessionStore(options = {}) {
1428
1487
  },
1429
1488
  async claimNextSession(options2 = {}) {
1430
1489
  const statuses = normalizeStatuses(DEFAULT_STATUS);
1431
- const existingSession = findNewestMatchingSession(statuses);
1490
+ const existingSession = findNewestMatchingSession(statuses, options2.source);
1432
1491
  if (existingSession) {
1433
1492
  return {
1434
1493
  session: claimSession(existingSession.id, statuses),
@@ -1543,6 +1602,7 @@ function cloneValue(value) {
1543
1602
  }
1544
1603
 
1545
1604
  // src/server/annotation-dispatch.ts
1605
+ var execAsync = promisify(exec);
1546
1606
  var AnnotationDispatchError = class extends Error {
1547
1607
  constructor(message, errorCode) {
1548
1608
  super(message);
@@ -1554,9 +1614,14 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
1554
1614
  try {
1555
1615
  validateAnnotationDispatchRequest(req, state);
1556
1616
  const batch = normalizeAnnotationBatch(req);
1557
- const prompt = buildAnnotationBatchPrompt(batch);
1617
+ let prompt = buildAnnotationBatchPrompt(batch);
1618
+ if (req.source === "workflow") {
1619
+ prompt = await appendProjectMetadata(prompt, state);
1620
+ }
1558
1621
  const deliveryMode = normalizeDeliveryMode(req.deliveryMode);
1559
1622
  const session = store.createSession({
1623
+ source: req.source || "annotation",
1624
+ ...req.workflowId ? { workflowId: req.workflowId } : {},
1560
1625
  instruction: batch.instruction,
1561
1626
  annotations: toSessionAnnotations(batch.annotations),
1562
1627
  deliveryMode,
@@ -1582,6 +1647,33 @@ async function dispatchAnnotationsToAi(req, state, store = annotationSessionStor
1582
1647
  };
1583
1648
  }
1584
1649
  }
1650
+ async function appendProjectMetadata(prompt, state) {
1651
+ const lines = ["\n## Project"];
1652
+ lines.push(`- Root: ${state.projectRoot}`);
1653
+ try {
1654
+ const options = {
1655
+ cwd: state.projectRoot,
1656
+ encoding: "utf-8",
1657
+ timeout: 2e3
1658
+ };
1659
+ const { stdout: branchStdout } = await execAsync("git branch --show-current", options);
1660
+ const branch = branchStdout.trim();
1661
+ lines.push(`- Branch: ${branch}`);
1662
+ const { stdout: statusStdout } = await execAsync("git status --porcelain", options);
1663
+ const statusRaw = statusStdout.trim();
1664
+ const entries = statusRaw ? statusRaw.split("\n") : [];
1665
+ const staged = entries.filter((l) => l[0] !== " " && l[0] !== "?").length;
1666
+ const unstaged = entries.filter((l) => l[1] !== " " && l[1] !== "?").length;
1667
+ const untracked = entries.filter((l) => l[0] === "?").length;
1668
+ lines.push(`- Status: ${staged} staged, ${unstaged} unstaged, ${untracked} untracked`);
1669
+ } catch (err) {
1670
+ console.warn("[inspecto] Failed to get git status for workflow:", err);
1671
+ lines.push("- Git: unavailable or check timeout");
1672
+ }
1673
+ return `${prompt}
1674
+
1675
+ ${lines.join("\n")}`;
1676
+ }
1585
1677
  function normalizeDeliveryMode(input) {
1586
1678
  return input === "agent" ? "agent" : "ide";
1587
1679
  }
@@ -1618,7 +1710,7 @@ function toSessionSummary(session) {
1618
1710
  };
1619
1711
  }
1620
1712
  function validateAnnotationDispatchRequest(req, state) {
1621
- if (!req.annotations.length) {
1713
+ if (!req.annotations.length && req.source !== "workflow") {
1622
1714
  throw new AnnotationDispatchError("At least one annotation is required.", "INVALID_REQUEST");
1623
1715
  }
1624
1716
  for (const annotation of req.annotations) {
@@ -1720,6 +1812,7 @@ function getAnnotationDispatchErrorCode(error) {
1720
1812
  }
1721
1813
 
1722
1814
  // src/server/client-config.ts
1815
+ import { isAiIntentConfig } from "@inspecto-dev/types";
1723
1816
  async function buildClientConfig(serverState2) {
1724
1817
  const userConfig = loadUserConfigSync(false, serverState2.cwd, serverState2.configRoot);
1725
1818
  const promptsConfig = await loadPromptsConfig(false, serverState2.cwd, serverState2.configRoot);
@@ -1731,11 +1824,13 @@ async function buildClientConfig(serverState2) {
1731
1824
  const { scheme: _scheme, ...rest } = serverState2.ideInfo;
1732
1825
  info = rest;
1733
1826
  }
1827
+ const allIntents = resolveIntents(promptsConfig);
1734
1828
  return {
1735
1829
  ...info,
1736
- prompts: resolveIntents(promptsConfig),
1830
+ prompts: allIntents.filter(isAiIntentConfig),
1831
+ workflows: resolveWorkflowSlots(allIntents),
1737
1832
  hotKeys: userConfig["inspector.hotKey"] ?? "alt",
1738
- annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "both",
1833
+ annotateDeliveryMode: userConfig["annotate.deliveryMode"] ?? "agent",
1739
1834
  includeSnippet: userConfig["prompt.includeSnippet"] ?? false,
1740
1835
  runtimeContext: {
1741
1836
  enabled: true,
@@ -1832,41 +1927,60 @@ import fs4 from "fs";
1832
1927
  import path7 from "path";
1833
1928
  import { execSync } from "child_process";
1834
1929
  var serverLogger3 = createLogger("inspecto:server", { logLevel: getGlobalLogLevel() });
1835
- function resolveProjectRoot() {
1836
- const cwd = process.cwd();
1837
- let gitRoot;
1930
+ function resolveGitRoot(_cwd) {
1838
1931
  try {
1839
- gitRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
1932
+ const output = execSync("git rev-parse --show-toplevel", { encoding: "utf-8" });
1933
+ return typeof output === "string" ? output.trim() : null;
1840
1934
  } catch (e) {
1841
1935
  serverLogger3.warn("Failed to resolve git root via git rev-parse:", e);
1842
- gitRoot = cwd;
1843
- }
1844
- const visited = /* @__PURE__ */ new Set();
1845
- const search = (start, stop) => {
1846
- let current = start;
1847
- while (!visited.has(current)) {
1848
- visited.add(current);
1849
- if (fs4.existsSync(path7.join(current, ".inspecto"))) return current;
1850
- if (current === stop) break;
1851
- const parent = path7.dirname(current);
1852
- if (parent === current) break;
1853
- current = parent;
1854
- }
1855
1936
  return null;
1856
- };
1857
- const cwdMatch = search(cwd, path7.parse(cwd).root);
1858
- if (cwdMatch) return cwdMatch;
1859
- const repoMatch = search(gitRoot, path7.parse(gitRoot).root);
1860
- if (repoMatch) return repoMatch;
1861
- return gitRoot;
1937
+ }
1938
+ }
1939
+ function findNearestAncestorWith(start, predicate) {
1940
+ let current = start;
1941
+ while (true) {
1942
+ if (predicate(current)) return current;
1943
+ const parent = path7.dirname(current);
1944
+ if (parent === current) break;
1945
+ current = parent;
1946
+ }
1947
+ return null;
1948
+ }
1949
+ function resolveWorkspaceRoot() {
1950
+ const cwd = process.cwd();
1951
+ const inspectoRoot = findNearestAncestorWith(
1952
+ cwd,
1953
+ (dir) => fs4.existsSync(path7.join(dir, ".inspecto"))
1954
+ );
1955
+ if (inspectoRoot) return inspectoRoot;
1956
+ const packageRoot = findNearestAncestorWith(
1957
+ cwd,
1958
+ (dir) => fs4.existsSync(path7.join(dir, "package.json"))
1959
+ );
1960
+ if (packageRoot) return packageRoot;
1961
+ return resolveGitRoot(cwd) ?? cwd;
1962
+ }
1963
+ function resolveProjectRoot() {
1964
+ const cwd = process.cwd();
1965
+ const packageRoot = findNearestAncestorWith(
1966
+ cwd,
1967
+ (dir) => fs4.existsSync(path7.join(dir, "package.json"))
1968
+ );
1969
+ if (packageRoot) return packageRoot;
1970
+ const inspectoRoot = findNearestAncestorWith(
1971
+ cwd,
1972
+ (dir) => fs4.existsSync(path7.join(dir, ".inspecto"))
1973
+ );
1974
+ if (inspectoRoot) return inspectoRoot;
1975
+ return resolveGitRoot(cwd) ?? cwd;
1862
1976
  }
1863
1977
 
1864
1978
  // src/server/server-url.ts
1865
1979
  function resolveServerHost(cwd, configRoot) {
1980
+ if (process.env["VITEST"]) return "127.0.0.1";
1866
1981
  const userConfig = loadUserConfigSync(false, cwd, configRoot);
1867
1982
  const configuredHost = userConfig["server.host"]?.trim();
1868
1983
  if (configuredHost) return configuredHost;
1869
- if (process.env["VITEST"]) return "127.0.0.1";
1870
1984
  return "127.0.0.1";
1871
1985
  }
1872
1986
  function resolvePublicServerUrl(args) {
@@ -1931,7 +2045,7 @@ async function startServer() {
1931
2045
  return serverState.port;
1932
2046
  }
1933
2047
  serverState.projectRoot = resolveProjectRoot();
1934
- serverState.configRoot = serverState.projectRoot;
2048
+ serverState.configRoot = resolveWorkspaceRoot();
1935
2049
  serverState.cwd = process.cwd();
1936
2050
  const serverHost = resolveServerHost(serverState.cwd, serverState.configRoot);
1937
2051
  portfinder.basePort = 5678;
@@ -2344,10 +2458,23 @@ data: ${JSON.stringify({ ok: true })}
2344
2458
  }
2345
2459
  async function dispatchToAi(req) {
2346
2460
  const { location, snippet, prompt } = req;
2347
- const formattedPrompt = prompt ?? `Please help me with this code from \`${location.file}\` (line ${location.line}):
2461
+ if (prompt?.trim()) {
2462
+ const runtime2 = resolvePromptDispatchRuntime(serverState);
2463
+ return dispatchPromptThroughIde(runtime2, {
2464
+ prompt: prompt.trim()
2465
+ });
2466
+ }
2467
+ if (!location) {
2468
+ return {
2469
+ success: false,
2470
+ error: "Source location is required when prompt is omitted.",
2471
+ errorCode: "INVALID_REQUEST"
2472
+ };
2473
+ }
2474
+ const formattedPrompt = `Please help me with this code from \`${location.file}\` (line ${location.line}):
2348
2475
 
2349
2476
  \`\`\`
2350
- ${snippet}
2477
+ ${snippet ?? ""}
2351
2478
  \`\`\`
2352
2479
  `;
2353
2480
  const runtime = resolvePromptDispatchRuntime(serverState);
@@ -2356,7 +2483,7 @@ ${snippet}
2356
2483
  filePath: location.file,
2357
2484
  line: location.line,
2358
2485
  column: location.column,
2359
- snippet
2486
+ ...snippet !== void 0 ? { snippet } : {}
2360
2487
  });
2361
2488
  }
2362
2489
  function getBatchDispatchStatusCode(errorCode, success) {