@rogatio/cli 5.17.0 → 5.19.0

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.
@@ -1844,9 +1844,6 @@ var CREATABLE_RULE_FIELDS = /* @__PURE__ */ new Set([
1844
1844
  ...ACTION_PAYLOAD_FIELDS,
1845
1845
  "redactSensitiveInLogs"
1846
1846
  ]);
1847
- function isBodyRuleType(type) {
1848
- return type === "request-body" || type === "response-body";
1849
- }
1850
1847
  function setValueAtPath(root, path, value) {
1851
1848
  const segments = decodePointer(path);
1852
1849
  if (!segments || segments.length === 0) return false;
@@ -2003,6 +2000,7 @@ function ownString(record, ...keys) {
2003
2000
  }
2004
2001
  return void 0;
2005
2002
  }
2003
+ var RULE_PATH_PATTERN = /^\/groups\/(0|[1-9][0-9]*)\/rules\/(0|[1-9][0-9]*)(?:\/|$)/;
2006
2004
  function applyRuleProposalAction(rule, ruleProposal) {
2007
2005
  const action = ruleProposal.action;
2008
2006
  switch (ruleProposal.kind) {
@@ -2126,6 +2124,7 @@ var EditorControllerImpl = class {
2126
2124
  testRequestId = 0;
2127
2125
  aiAssistPanel = null;
2128
2126
  aiAssistInFlight = false;
2127
+ aiRepairTargets = [];
2129
2128
  constructor(options, initial, extensions) {
2130
2129
  this.root = options.root;
2131
2130
  this.document = options.root.ownerDocument;
@@ -2975,6 +2974,8 @@ var EditorControllerImpl = class {
2975
2974
  this.render();
2976
2975
  }
2977
2976
  applyAIProposal(proposal) {
2977
+ const repairs = [...this.aiRepairTargets];
2978
+ this.aiRepairTargets = [];
2978
2979
  for (const ruleProposal of proposal.rules) {
2979
2980
  let group = this.groupById(ruleProposal.groupId);
2980
2981
  if (!group) {
@@ -2988,7 +2989,19 @@ var EditorControllerImpl = class {
2988
2989
  group = this.groupById(groupId);
2989
2990
  }
2990
2991
  if (!group) return;
2991
- const ruleId = this.nextId("rule-ai");
2992
+ const rules = group.rules;
2993
+ const repair = repairs.find(
2994
+ (target) => target.groupId === ruleProposal.groupId && rules.some((existing) => String(existing.id) === target.ruleId)
2995
+ );
2996
+ let repairIndex = -1;
2997
+ let ruleId = this.nextId("rule-ai");
2998
+ if (repair) {
2999
+ repairs.splice(repairs.indexOf(repair), 1);
3000
+ ruleId = repair.ruleId;
3001
+ repairIndex = rules.findIndex(
3002
+ (existing) => String(existing.id) === repair.ruleId
3003
+ );
3004
+ }
2992
3005
  const rule = {
2993
3006
  id: ruleId,
2994
3007
  name: ruleProposal.name,
@@ -3001,20 +3014,56 @@ var EditorControllerImpl = class {
3001
3014
  rule.method = ruleProposal.method;
3002
3015
  }
3003
3016
  applyRuleProposalAction(rule, ruleProposal);
3004
- group.rules.push(rule);
3017
+ if (repairIndex >= 0) {
3018
+ rules[repairIndex] = rule;
3019
+ } else {
3020
+ rules.push(rule);
3021
+ }
3005
3022
  this.markChanged();
3006
3023
  const groupIdStr = String(group.id);
3007
3024
  this.focusRequest = pointer(
3008
3025
  "groups",
3009
3026
  this.groupIndex(groupIdStr),
3010
3027
  "rules",
3011
- group.rules.length - 1,
3028
+ repairIndex >= 0 ? repairIndex : rules.length - 1,
3012
3029
  "name"
3013
3030
  );
3014
3031
  }
3015
3032
  this.statusMessage = `Applied ${proposal.rules.length} rule${proposal.rules.length === 1 ? "" : "s"} from AI.`;
3016
3033
  this.render();
3017
3034
  }
3035
+ /**
3036
+ * Rules carrying validation errors, in stable path order. A fix proposal
3037
+ * repairs these rules in place (keeping their ids) instead of adding new ones.
3038
+ */
3039
+ repairTargetsFrom(diagnostics) {
3040
+ const seen = /* @__PURE__ */ new Set();
3041
+ const positions = [];
3042
+ for (const diagnostic2 of diagnostics) {
3043
+ const match = RULE_PATH_PATTERN.exec(diagnostic2.path);
3044
+ if (!match) continue;
3045
+ const groupIndex = Number(match[1]);
3046
+ const ruleIndex = Number(match[2]);
3047
+ if (!Number.isSafeInteger(groupIndex) || !Number.isSafeInteger(ruleIndex)) {
3048
+ continue;
3049
+ }
3050
+ const key = `${groupIndex}/${ruleIndex}`;
3051
+ if (seen.has(key)) continue;
3052
+ seen.add(key);
3053
+ positions.push({ groupIndex, ruleIndex });
3054
+ }
3055
+ positions.sort(
3056
+ (left, right) => left.groupIndex - right.groupIndex || left.ruleIndex - right.ruleIndex
3057
+ );
3058
+ const targets = [];
3059
+ for (const position of positions) {
3060
+ const group = this.draft.groups[position.groupIndex];
3061
+ const rule = group?.rules[position.ruleIndex];
3062
+ if (!group || !rule) continue;
3063
+ targets.push({ groupId: String(group.id), ruleId: String(rule.id) });
3064
+ }
3065
+ return targets;
3066
+ }
3018
3067
  async runAIAssist(prompt) {
3019
3068
  const handler = this.options.aiAssist;
3020
3069
  const panel = this.aiAssistPanel;
@@ -3033,6 +3082,7 @@ var EditorControllerImpl = class {
3033
3082
  diagnostics: diagnostics.length > 0 ? diagnostics : void 0
3034
3083
  }
3035
3084
  };
3085
+ this.aiRepairTargets = request.kind === "fix" && diagnostics.length > 0 ? this.repairTargetsFrom(diagnostics) : [];
3036
3086
  panel.startStreaming("assistant");
3037
3087
  try {
3038
3088
  const result = handler(request);
@@ -3937,25 +3987,23 @@ var EditorControllerImpl = class {
3937
3987
  this.renderField(matcherGrid, "Method", `${rulePath}/method`, method);
3938
3988
  matcherFields.append(matcherGrid);
3939
3989
  card.append(matcherFields);
3940
- if (!isBodyRuleType(rule.type)) {
3941
- const redactFieldset = this.document.createElement("fieldset");
3942
- const redactLegend = this.document.createElement("legend");
3943
- redactLegend.textContent = "Match logging";
3944
- redactFieldset.append(redactLegend);
3945
- const redactGrid = this.document.createElement("div");
3946
- redactGrid.dataset.editorFields = "true";
3947
- const redactCheckbox = this.document.createElement("input");
3948
- redactCheckbox.type = "checkbox";
3949
- redactCheckbox.checked = rule.redactSensitiveInLogs === true;
3950
- this.renderField(
3951
- redactGrid,
3952
- "Redact sensitive fields in logs",
3953
- `${rulePath}/redactSensitiveInLogs`,
3954
- redactCheckbox
3955
- );
3956
- redactFieldset.append(redactGrid);
3957
- card.append(redactFieldset);
3958
- }
3990
+ const redactFieldset = this.document.createElement("fieldset");
3991
+ const redactLegend = this.document.createElement("legend");
3992
+ redactLegend.textContent = "Match logging";
3993
+ redactFieldset.append(redactLegend);
3994
+ const redactGrid = this.document.createElement("div");
3995
+ redactGrid.dataset.editorFields = "true";
3996
+ const redactCheckbox = this.document.createElement("input");
3997
+ redactCheckbox.type = "checkbox";
3998
+ redactCheckbox.checked = rule.redactSensitiveInLogs === true;
3999
+ this.renderField(
4000
+ redactGrid,
4001
+ "Redact sensitive fields in logs",
4002
+ `${rulePath}/redactSensitiveInLogs`,
4003
+ redactCheckbox
4004
+ );
4005
+ redactFieldset.append(redactGrid);
4006
+ card.append(redactFieldset);
3959
4007
  if (this.extensions.length > 0) {
3960
4008
  const currentType = this.extensions.find(
3961
4009
  (e) => e.matches(rule)
@@ -20878,6 +20878,115 @@ function mergeProposalIntoProject(project, proposal) {
20878
20878
  }
20879
20879
  return { ...project, groups };
20880
20880
  }
20881
+ var RULE_PATH_PATTERN = /^\/groups\/(0|[1-9][0-9]*)\/rules\/(0|[1-9][0-9]*)(?:\/|$)/;
20882
+ function repairTargetsFromDiagnostics(diagnostics) {
20883
+ if (!Array.isArray(diagnostics)) return [];
20884
+ const seen = /* @__PURE__ */ new Set();
20885
+ const targets = [];
20886
+ for (const diagnostic of diagnostics) {
20887
+ if (!isRecord(diagnostic) || typeof diagnostic.path !== "string") continue;
20888
+ const match = RULE_PATH_PATTERN.exec(diagnostic.path);
20889
+ if (!match) continue;
20890
+ const groupIndex = Number(match[1]);
20891
+ const ruleIndex = Number(match[2]);
20892
+ if (!Number.isSafeInteger(groupIndex) || !Number.isSafeInteger(ruleIndex)) {
20893
+ continue;
20894
+ }
20895
+ const key = `${groupIndex}/${ruleIndex}`;
20896
+ if (seen.has(key)) continue;
20897
+ seen.add(key);
20898
+ targets.push({ groupIndex, ruleIndex });
20899
+ }
20900
+ targets.sort(
20901
+ (left, right) => left.groupIndex - right.groupIndex || left.ruleIndex - right.ruleIndex
20902
+ );
20903
+ return targets;
20904
+ }
20905
+ var RULE_KINDS = /* @__PURE__ */ new Set([
20906
+ "redirect",
20907
+ "query",
20908
+ "header",
20909
+ "response-body",
20910
+ "request-body"
20911
+ ]);
20912
+ function stringArray(value) {
20913
+ if (!Array.isArray(value)) return void 0;
20914
+ const items = value.filter(
20915
+ (item) => typeof item === "string"
20916
+ );
20917
+ return items.length === value.length ? items : void 0;
20918
+ }
20919
+ function parseProposalRule(value) {
20920
+ if (!isRecord(value)) return null;
20921
+ const kind = value.kind;
20922
+ if (typeof kind !== "string" || !RULE_KINDS.has(kind) || typeof value.groupId !== "string" || typeof value.name !== "string" || typeof value.urlRegex !== "string" || !Object.hasOwn(value, "action")) {
20923
+ return null;
20924
+ }
20925
+ return {
20926
+ kind,
20927
+ groupId: value.groupId,
20928
+ name: value.name,
20929
+ urlRegex: value.urlRegex,
20930
+ origins: stringArray(value.origins),
20931
+ resourceTypes: stringArray(value.resourceTypes),
20932
+ priority: typeof value.priority === "number" ? value.priority : void 0,
20933
+ method: typeof value.method === "string" ? value.method : void 0,
20934
+ action: value.action
20935
+ };
20936
+ }
20937
+ function repairProposalIntoProject(project, diagnostics, proposal) {
20938
+ const groups = Array.isArray(project.groups) ? project.groups.map((group) => {
20939
+ if (!isRecord(group)) {
20940
+ return { id: "invalid", name: "invalid", origins: [], rules: [] };
20941
+ }
20942
+ return {
20943
+ ...group,
20944
+ rules: Array.isArray(group.rules) ? [...group.rules] : []
20945
+ };
20946
+ }) : [];
20947
+ const queues = /* @__PURE__ */ new Map();
20948
+ for (const target of repairTargetsFromDiagnostics(diagnostics)) {
20949
+ const group = groups[target.groupIndex];
20950
+ if (!group) continue;
20951
+ const rules = group.rules;
20952
+ if (target.ruleIndex >= rules.length) continue;
20953
+ if (!isRecord(rules[target.ruleIndex])) continue;
20954
+ const queue = queues.get(target.groupIndex) ?? [];
20955
+ queue.push(target.ruleIndex);
20956
+ queues.set(target.groupIndex, queue);
20957
+ }
20958
+ let seq = 0;
20959
+ const newId = () => `ai-${Date.now()}-${seq++}`;
20960
+ const proposed = Array.isArray(proposal.rules) ? proposal.rules : [];
20961
+ for (const entry of proposed) {
20962
+ const ruleProposal = parseProposalRule(entry);
20963
+ if (!ruleProposal) continue;
20964
+ const groupIndex = groups.findIndex(
20965
+ (candidate) => candidate.id === ruleProposal.groupId
20966
+ );
20967
+ const queue = groupIndex >= 0 ? queues.get(groupIndex) : void 0;
20968
+ const targetIndex = queue?.shift();
20969
+ if (groupIndex >= 0 && targetIndex !== void 0) {
20970
+ const rules = groups[groupIndex].rules;
20971
+ const existing = rules[targetIndex];
20972
+ const keptId = typeof existing.id === "string" ? existing.id : newId();
20973
+ rules[targetIndex] = ruleFromProposal(ruleProposal, keptId);
20974
+ continue;
20975
+ }
20976
+ let group = groupIndex >= 0 ? groups[groupIndex] : void 0;
20977
+ if (!group) {
20978
+ group = {
20979
+ id: ruleProposal.groupId,
20980
+ name: "AI Group",
20981
+ origins: [],
20982
+ rules: []
20983
+ };
20984
+ groups.push(group);
20985
+ }
20986
+ group.rules.push(ruleFromProposal(ruleProposal, newId()));
20987
+ }
20988
+ return { ...project, groups };
20989
+ }
20881
20990
  function parseProposal(content) {
20882
20991
  try {
20883
20992
  const parsed = JSON.parse(content);
@@ -20943,6 +21052,11 @@ async function runStream(client, options) {
20943
21052
  async function runAIAssist(request, config, validate, dryRun, client) {
20944
21053
  const aiClient = client ?? createAIClient(config);
20945
21054
  const systemPrompt = buildSystemPrompt(request.context.project);
21055
+ const merge = (project, proposal) => request.kind === "fix" ? repairProposalIntoProject(
21056
+ project,
21057
+ request.context.diagnostics,
21058
+ proposal
21059
+ ) : mergeProposalIntoProject(project, proposal);
20946
21060
  let currentProposal = null;
20947
21061
  let attempts = 0;
20948
21062
  while (attempts < MAX_FIX_ATTEMPTS) {
@@ -20950,9 +21064,7 @@ async function runAIAssist(request, config, validate, dryRun, client) {
20950
21064
  const isFixAttempt = attempts > 1 || request.kind === "fix";
20951
21065
  const validationErrors = (() => {
20952
21066
  if (!isFixAttempt || !currentProposal) return [];
20953
- return validate(
20954
- mergeProposalIntoProject(request.context.project, currentProposal)
20955
- );
21067
+ return validate(merge(request.context.project, currentProposal));
20956
21068
  })();
20957
21069
  const messages = buildMessages(
20958
21070
  systemPrompt,
@@ -20983,10 +21095,7 @@ async function runAIAssist(request, config, validate, dryRun, client) {
20983
21095
  throw new Error("AI returned invalid proposal format");
20984
21096
  }
20985
21097
  currentProposal = proposal;
20986
- const testProject = mergeProposalIntoProject(
20987
- request.context.project,
20988
- proposal
20989
- );
21098
+ const testProject = merge(request.context.project, proposal);
20990
21099
  const diagnostics = validate(testProject);
20991
21100
  if (diagnostics.length === 0) {
20992
21101
  if (dryRun && request.context.dryRunCases && request.context.dryRunCases.length > 0) {
@@ -23526,36 +23635,78 @@ Options:
23526
23635
  }
23527
23636
 
23528
23637
  // packages/cli/src/commands/ai.ts
23638
+ var pendingInput = "";
23639
+ function readLine() {
23640
+ return new Promise((resolve9) => {
23641
+ const take = () => {
23642
+ const newline = pendingInput.search(/[\r\n]/u);
23643
+ if (newline === -1) return false;
23644
+ const line = pendingInput.slice(0, newline);
23645
+ pendingInput = pendingInput.slice(newline + 1);
23646
+ resolve9(line);
23647
+ return true;
23648
+ };
23649
+ if (take()) return;
23650
+ const onData = (chunk) => {
23651
+ pendingInput += chunk.toString();
23652
+ if (!take()) return;
23653
+ process.stdin.off("data", onData);
23654
+ process.stdin.pause();
23655
+ };
23656
+ process.stdin.on("data", onData);
23657
+ process.stdin.resume();
23658
+ });
23659
+ }
23529
23660
  async function promptInput(prompt) {
23530
23661
  process.stdout.write(prompt);
23531
- for await (const chunk of process.stdin) {
23532
- return chunk.toString().trim();
23533
- }
23534
- return "";
23662
+ return (await readLine()).trim();
23535
23663
  }
23536
23664
  async function promptSecret(prompt) {
23537
23665
  process.stdout.write(prompt);
23538
23666
  process.stdin.setRawMode(true);
23539
- let input = "";
23540
- for await (const chunk of process.stdin) {
23541
- const char = chunk.toString();
23542
- if (char === "\n" || char === "\r") {
23543
- process.stdout.write("\n");
23544
- break;
23545
- } else if (char === "") {
23546
- process.exit(1);
23547
- } else if (char === "\x7F" || char === "\b") {
23548
- if (input.length > 0) {
23549
- input = input.slice(0, -1);
23550
- process.stdout.write("\b \b");
23551
- }
23552
- } else {
23553
- input += char;
23554
- process.stdout.write("*");
23555
- }
23667
+ try {
23668
+ return await new Promise((resolve9) => {
23669
+ let input = "";
23670
+ let settled = false;
23671
+ const finish = () => {
23672
+ settled = true;
23673
+ process.stdin.off("data", onData);
23674
+ process.stdin.pause();
23675
+ resolve9(input.trim());
23676
+ };
23677
+ const consume = (text) => {
23678
+ if (settled) return;
23679
+ for (let index = 0; index < text.length; index += 1) {
23680
+ const char = text[index];
23681
+ if (char === "\n" || char === "\r") {
23682
+ process.stdout.write("\n");
23683
+ pendingInput = text.slice(index + 1);
23684
+ finish();
23685
+ return;
23686
+ }
23687
+ if (char === "") {
23688
+ process.exit(1);
23689
+ } else if (char === "\x7F" || char === "\b") {
23690
+ if (input.length > 0) {
23691
+ input = input.slice(0, -1);
23692
+ process.stdout.write("\b \b");
23693
+ }
23694
+ } else {
23695
+ input += char;
23696
+ process.stdout.write("*");
23697
+ }
23698
+ }
23699
+ };
23700
+ const onData = (chunk) => consume(chunk.toString());
23701
+ consume(pendingInput);
23702
+ if (settled) return;
23703
+ pendingInput = "";
23704
+ process.stdin.on("data", onData);
23705
+ process.stdin.resume();
23706
+ });
23707
+ } finally {
23708
+ process.stdin.setRawMode(false);
23556
23709
  }
23557
- process.stdin.setRawMode(false);
23558
- return input.trim();
23559
23710
  }
23560
23711
  async function aiCommand(args) {
23561
23712
  if (args.includes("--help") || args.includes("-h")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogatio/cli",
3
- "version": "5.17.0",
3
+ "version": "5.19.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Local-first browser request and response rules — editor host, file verification, test runner, and runtime dispatch.",