@codyswann/lisa 4.84.6 → 4.84.7

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.
Files changed (90) hide show
  1. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  2. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  3. package/dist/core/upstream-evidence-manifest.js +7 -3
  4. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  5. package/package.json +4 -4
  6. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa/hooks/block-host-name-leak.mjs +25 -13
  9. package/plugins/lisa/hooks/host-name-command-context.mjs +16 -4
  10. package/plugins/lisa-agy/hooks/block-host-name-leak.mjs +25 -13
  11. package/plugins/lisa-agy/hooks/host-name-command-context.mjs +16 -4
  12. package/plugins/lisa-agy/plugin.json +1 -1
  13. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  16. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  20. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/hooks/block-host-name-leak.mjs +25 -13
  22. package/plugins/lisa-copilot/hooks/host-name-command-context.mjs +16 -4
  23. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cursor/hooks/block-host-name-leak.mjs +25 -13
  25. package/plugins/lisa-cursor/hooks/host-name-command-context.mjs +16 -4
  26. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  28. package/plugins/lisa-devin/hooks/block-host-name-leak.mjs +25 -13
  29. package/plugins/lisa-devin/hooks/host-name-command-context.mjs +16 -4
  30. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-expo-agy/plugin.json +1 -1
  33. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  47. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  54. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  58. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  61. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  65. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-rails-agy/plugin.json +1 -1
  68. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  75. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  79. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  82. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  86. package/plugins/src/base/hooks/block-host-name-leak.mjs +25 -13
  87. package/plugins/src/base/hooks/host-name-command-context.mjs +16 -4
  88. package/scripts/lib/host-name-expanded-command-cases.mjs +286 -0
  89. package/scripts/lib/host-name-field-occurrence-cases.mjs +60 -0
  90. package/scripts/verify-host-name-target-scope.mjs +27 -1
package/package.json CHANGED
@@ -189,7 +189,7 @@
189
189
  "qs": "^6.16.0"
190
190
  },
191
191
  "name": "@codyswann/lisa",
192
- "version": "4.84.6",
192
+ "version": "4.84.7",
193
193
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
194
194
  "main": "dist/index.js",
195
195
  "exports": {
@@ -347,7 +347,7 @@
347
347
  "test": "tests"
348
348
  },
349
349
  "types": "./dist/index.d.ts",
350
- "lisaReleaseCommit": "26506db550db7f036466d2978c8a6448fa1f7f89",
351
- "gitHead": "26506db550db7f036466d2978c8a6448fa1f7f89",
352
- "lisaReleaseTag": "v4.84.6"
350
+ "lisaReleaseCommit": "c4b4854246e19e87038c9a31bc480085e1e004bf",
351
+ "gitHead": "c4b4854246e19e87038c9a31bc480085e1e004bf",
352
+ "lisaReleaseTag": "v4.84.7"
353
353
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -287,8 +287,16 @@ export function publishableText(tokens, cwd, noun) {
287
287
  const value = valueAt(tokens, index);
288
288
  // On `gh api`, `-F` is `--field`: `key=@path` or `key=value`. Read as a
289
289
  // bare path, `body=@file` named no file and published unchecked (#4462).
290
- if (noun === "api" && /^[\w.[\]-]+=/u.test(value))
291
- return [fieldSource(value, cwd)];
290
+ if (
291
+ (noun === "api" || (noun === undefined && bare === "-F")) &&
292
+ /^[\w.[\]-]+=/u.test(value)
293
+ ) {
294
+ const field = fieldSource(value, cwd);
295
+ if (noun === "api") return [field];
296
+ // An unresolved noun may become api or issue/pr. Inspect both possible
297
+ // meanings of -F without evaluating the expansion or certifying routing.
298
+ return [field, { origin: value, text: readIfPresent(value, cwd) }];
299
+ }
292
300
  const file = value.replace(/^@/u, "");
293
301
  return [{ origin: file, text: readIfPresent(file, cwd) }];
294
302
  }
@@ -414,20 +422,24 @@ export async function evaluate(command, projectDir) {
414
422
  // protection, and name the unread input instead of claiming the text clean.
415
423
  if (
416
424
  context.cwd === undefined &&
417
- [...context.flags].some(([flag, item]) => {
425
+ [
426
+ ...context.flags,
427
+ ...(context.noun === "api" || context.noun === undefined
428
+ ? context.fieldOccurrences
429
+ : []),
430
+ ].some(([flag, item]) => {
418
431
  const field =
419
- context.noun === "api" && FIELD_FLAGS.has(flag)
432
+ (context.noun === "api" || context.noun === undefined) &&
433
+ FIELD_FLAGS.has(flag)
420
434
  ? /^([^=]+)=@(.+)$/u.exec(item.value)
421
435
  : null;
422
- const file =
423
- field !== null
424
- ? PROSE_FIELDS.has(field[1])
425
- ? field[2]
426
- : undefined
427
- : FILE_FLAGS.has(flag)
428
- ? item.value.replace(/^@/, "")
429
- : undefined;
430
- return file !== undefined && !path.isAbsolute(file);
436
+ const files = [
437
+ field !== null && PROSE_FIELDS.has(field[1]) ? field[2] : undefined,
438
+ FILE_FLAGS.has(flag) && (context.noun !== "api" || field === null)
439
+ ? item.value.replace(/^@/, "")
440
+ : undefined,
441
+ ];
442
+ return files.some(file => file !== undefined && !path.isAbsolute(file));
431
443
  })
432
444
  ) {
433
445
  return {
@@ -103,6 +103,7 @@ function words(command) {
103
103
  */
104
104
  function argumentsOf(input) {
105
105
  const flags = new Map(),
106
+ fieldOccurrences = [],
106
107
  positional = [],
107
108
  tokens = [];
108
109
  // Any expansion can add/replace routing arguments after shell word splitting.
@@ -139,23 +140,34 @@ function argumentsOf(input) {
139
140
  flag === "-R" ? "--repo" : flag === "-X" ? "--method" : flag,
140
141
  value
141
142
  );
143
+ // API fields are additive even though routing options use last values.
144
+ if (FIELD_FLAGS.has(flag)) fieldOccurrences.push([flag, value]);
142
145
  tokens.push(value.value);
143
146
  } else if (!word.value.startsWith("-")) positional.push(word);
144
147
  else if (!word.literal) certain = false;
145
148
  }
146
- const noun = positional[0]?.value,
147
- verb = positional[1]?.value;
149
+ const nounWord = positional[0],
150
+ verbWord = positional[1];
151
+ const noun = nounWord?.literal ? nounWord.value : undefined,
152
+ verb = verbWord?.value;
153
+ // Command-name expansions can supply several argv words. A following
154
+ // read-looking literal does not prove that the expanded command is a read.
155
+ const uncertainName =
156
+ nounWord?.literal === false ||
157
+ (["issue", "pr"].includes(noun) && verbWord?.literal === false);
148
158
  const method = flags.get("--method");
149
159
  const writes =
150
- noun === "api"
160
+ uncertainName ||
161
+ (noun === "api"
151
162
  ? method
152
163
  ? !method.literal || METHODS.has(method.value.toUpperCase())
153
164
  : [...FIELD_FLAGS].some(flag => flags.has(flag)) ||
154
165
  Boolean(flags.get("--input")?.value)
155
166
  : ["issue", "pr"].includes(noun) &&
156
- ["create", "comment", "edit"].includes(verb);
167
+ ["create", "comment", "edit"].includes(verb));
157
168
  return {
158
169
  flags,
170
+ fieldOccurrences,
159
171
  positional,
160
172
  tokens: ["gh", ...tokens],
161
173
  noun,
@@ -287,8 +287,16 @@ export function publishableText(tokens, cwd, noun) {
287
287
  const value = valueAt(tokens, index);
288
288
  // On `gh api`, `-F` is `--field`: `key=@path` or `key=value`. Read as a
289
289
  // bare path, `body=@file` named no file and published unchecked (#4462).
290
- if (noun === "api" && /^[\w.[\]-]+=/u.test(value))
291
- return [fieldSource(value, cwd)];
290
+ if (
291
+ (noun === "api" || (noun === undefined && bare === "-F")) &&
292
+ /^[\w.[\]-]+=/u.test(value)
293
+ ) {
294
+ const field = fieldSource(value, cwd);
295
+ if (noun === "api") return [field];
296
+ // An unresolved noun may become api or issue/pr. Inspect both possible
297
+ // meanings of -F without evaluating the expansion or certifying routing.
298
+ return [field, { origin: value, text: readIfPresent(value, cwd) }];
299
+ }
292
300
  const file = value.replace(/^@/u, "");
293
301
  return [{ origin: file, text: readIfPresent(file, cwd) }];
294
302
  }
@@ -414,20 +422,24 @@ export async function evaluate(command, projectDir) {
414
422
  // protection, and name the unread input instead of claiming the text clean.
415
423
  if (
416
424
  context.cwd === undefined &&
417
- [...context.flags].some(([flag, item]) => {
425
+ [
426
+ ...context.flags,
427
+ ...(context.noun === "api" || context.noun === undefined
428
+ ? context.fieldOccurrences
429
+ : []),
430
+ ].some(([flag, item]) => {
418
431
  const field =
419
- context.noun === "api" && FIELD_FLAGS.has(flag)
432
+ (context.noun === "api" || context.noun === undefined) &&
433
+ FIELD_FLAGS.has(flag)
420
434
  ? /^([^=]+)=@(.+)$/u.exec(item.value)
421
435
  : null;
422
- const file =
423
- field !== null
424
- ? PROSE_FIELDS.has(field[1])
425
- ? field[2]
426
- : undefined
427
- : FILE_FLAGS.has(flag)
428
- ? item.value.replace(/^@/, "")
429
- : undefined;
430
- return file !== undefined && !path.isAbsolute(file);
436
+ const files = [
437
+ field !== null && PROSE_FIELDS.has(field[1]) ? field[2] : undefined,
438
+ FILE_FLAGS.has(flag) && (context.noun !== "api" || field === null)
439
+ ? item.value.replace(/^@/, "")
440
+ : undefined,
441
+ ];
442
+ return files.some(file => file !== undefined && !path.isAbsolute(file));
431
443
  })
432
444
  ) {
433
445
  return {
@@ -103,6 +103,7 @@ function words(command) {
103
103
  */
104
104
  function argumentsOf(input) {
105
105
  const flags = new Map(),
106
+ fieldOccurrences = [],
106
107
  positional = [],
107
108
  tokens = [];
108
109
  // Any expansion can add/replace routing arguments after shell word splitting.
@@ -139,23 +140,34 @@ function argumentsOf(input) {
139
140
  flag === "-R" ? "--repo" : flag === "-X" ? "--method" : flag,
140
141
  value
141
142
  );
143
+ // API fields are additive even though routing options use last values.
144
+ if (FIELD_FLAGS.has(flag)) fieldOccurrences.push([flag, value]);
142
145
  tokens.push(value.value);
143
146
  } else if (!word.value.startsWith("-")) positional.push(word);
144
147
  else if (!word.literal) certain = false;
145
148
  }
146
- const noun = positional[0]?.value,
147
- verb = positional[1]?.value;
149
+ const nounWord = positional[0],
150
+ verbWord = positional[1];
151
+ const noun = nounWord?.literal ? nounWord.value : undefined,
152
+ verb = verbWord?.value;
153
+ // Command-name expansions can supply several argv words. A following
154
+ // read-looking literal does not prove that the expanded command is a read.
155
+ const uncertainName =
156
+ nounWord?.literal === false ||
157
+ (["issue", "pr"].includes(noun) && verbWord?.literal === false);
148
158
  const method = flags.get("--method");
149
159
  const writes =
150
- noun === "api"
160
+ uncertainName ||
161
+ (noun === "api"
151
162
  ? method
152
163
  ? !method.literal || METHODS.has(method.value.toUpperCase())
153
164
  : [...FIELD_FLAGS].some(flag => flags.has(flag)) ||
154
165
  Boolean(flags.get("--input")?.value)
155
166
  : ["issue", "pr"].includes(noun) &&
156
- ["create", "comment", "edit"].includes(verb);
167
+ ["create", "comment", "edit"].includes(verb));
157
168
  return {
158
169
  flags,
170
+ fieldOccurrences,
159
171
  positional,
160
172
  tokens: ["gh", ...tokens],
161
173
  noun,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -287,8 +287,16 @@ export function publishableText(tokens, cwd, noun) {
287
287
  const value = valueAt(tokens, index);
288
288
  // On `gh api`, `-F` is `--field`: `key=@path` or `key=value`. Read as a
289
289
  // bare path, `body=@file` named no file and published unchecked (#4462).
290
- if (noun === "api" && /^[\w.[\]-]+=/u.test(value))
291
- return [fieldSource(value, cwd)];
290
+ if (
291
+ (noun === "api" || (noun === undefined && bare === "-F")) &&
292
+ /^[\w.[\]-]+=/u.test(value)
293
+ ) {
294
+ const field = fieldSource(value, cwd);
295
+ if (noun === "api") return [field];
296
+ // An unresolved noun may become api or issue/pr. Inspect both possible
297
+ // meanings of -F without evaluating the expansion or certifying routing.
298
+ return [field, { origin: value, text: readIfPresent(value, cwd) }];
299
+ }
292
300
  const file = value.replace(/^@/u, "");
293
301
  return [{ origin: file, text: readIfPresent(file, cwd) }];
294
302
  }
@@ -414,20 +422,24 @@ export async function evaluate(command, projectDir) {
414
422
  // protection, and name the unread input instead of claiming the text clean.
415
423
  if (
416
424
  context.cwd === undefined &&
417
- [...context.flags].some(([flag, item]) => {
425
+ [
426
+ ...context.flags,
427
+ ...(context.noun === "api" || context.noun === undefined
428
+ ? context.fieldOccurrences
429
+ : []),
430
+ ].some(([flag, item]) => {
418
431
  const field =
419
- context.noun === "api" && FIELD_FLAGS.has(flag)
432
+ (context.noun === "api" || context.noun === undefined) &&
433
+ FIELD_FLAGS.has(flag)
420
434
  ? /^([^=]+)=@(.+)$/u.exec(item.value)
421
435
  : null;
422
- const file =
423
- field !== null
424
- ? PROSE_FIELDS.has(field[1])
425
- ? field[2]
426
- : undefined
427
- : FILE_FLAGS.has(flag)
428
- ? item.value.replace(/^@/, "")
429
- : undefined;
430
- return file !== undefined && !path.isAbsolute(file);
436
+ const files = [
437
+ field !== null && PROSE_FIELDS.has(field[1]) ? field[2] : undefined,
438
+ FILE_FLAGS.has(flag) && (context.noun !== "api" || field === null)
439
+ ? item.value.replace(/^@/, "")
440
+ : undefined,
441
+ ];
442
+ return files.some(file => file !== undefined && !path.isAbsolute(file));
431
443
  })
432
444
  ) {
433
445
  return {
@@ -103,6 +103,7 @@ function words(command) {
103
103
  */
104
104
  function argumentsOf(input) {
105
105
  const flags = new Map(),
106
+ fieldOccurrences = [],
106
107
  positional = [],
107
108
  tokens = [];
108
109
  // Any expansion can add/replace routing arguments after shell word splitting.
@@ -139,23 +140,34 @@ function argumentsOf(input) {
139
140
  flag === "-R" ? "--repo" : flag === "-X" ? "--method" : flag,
140
141
  value
141
142
  );
143
+ // API fields are additive even though routing options use last values.
144
+ if (FIELD_FLAGS.has(flag)) fieldOccurrences.push([flag, value]);
142
145
  tokens.push(value.value);
143
146
  } else if (!word.value.startsWith("-")) positional.push(word);
144
147
  else if (!word.literal) certain = false;
145
148
  }
146
- const noun = positional[0]?.value,
147
- verb = positional[1]?.value;
149
+ const nounWord = positional[0],
150
+ verbWord = positional[1];
151
+ const noun = nounWord?.literal ? nounWord.value : undefined,
152
+ verb = verbWord?.value;
153
+ // Command-name expansions can supply several argv words. A following
154
+ // read-looking literal does not prove that the expanded command is a read.
155
+ const uncertainName =
156
+ nounWord?.literal === false ||
157
+ (["issue", "pr"].includes(noun) && verbWord?.literal === false);
148
158
  const method = flags.get("--method");
149
159
  const writes =
150
- noun === "api"
160
+ uncertainName ||
161
+ (noun === "api"
151
162
  ? method
152
163
  ? !method.literal || METHODS.has(method.value.toUpperCase())
153
164
  : [...FIELD_FLAGS].some(flag => flags.has(flag)) ||
154
165
  Boolean(flags.get("--input")?.value)
155
166
  : ["issue", "pr"].includes(noun) &&
156
- ["create", "comment", "edit"].includes(verb);
167
+ ["create", "comment", "edit"].includes(verb));
157
168
  return {
158
169
  flags,
170
+ fieldOccurrences,
159
171
  positional,
160
172
  tokens: ["gh", ...tokens],
161
173
  noun,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -287,8 +287,16 @@ export function publishableText(tokens, cwd, noun) {
287
287
  const value = valueAt(tokens, index);
288
288
  // On `gh api`, `-F` is `--field`: `key=@path` or `key=value`. Read as a
289
289
  // bare path, `body=@file` named no file and published unchecked (#4462).
290
- if (noun === "api" && /^[\w.[\]-]+=/u.test(value))
291
- return [fieldSource(value, cwd)];
290
+ if (
291
+ (noun === "api" || (noun === undefined && bare === "-F")) &&
292
+ /^[\w.[\]-]+=/u.test(value)
293
+ ) {
294
+ const field = fieldSource(value, cwd);
295
+ if (noun === "api") return [field];
296
+ // An unresolved noun may become api or issue/pr. Inspect both possible
297
+ // meanings of -F without evaluating the expansion or certifying routing.
298
+ return [field, { origin: value, text: readIfPresent(value, cwd) }];
299
+ }
292
300
  const file = value.replace(/^@/u, "");
293
301
  return [{ origin: file, text: readIfPresent(file, cwd) }];
294
302
  }
@@ -414,20 +422,24 @@ export async function evaluate(command, projectDir) {
414
422
  // protection, and name the unread input instead of claiming the text clean.
415
423
  if (
416
424
  context.cwd === undefined &&
417
- [...context.flags].some(([flag, item]) => {
425
+ [
426
+ ...context.flags,
427
+ ...(context.noun === "api" || context.noun === undefined
428
+ ? context.fieldOccurrences
429
+ : []),
430
+ ].some(([flag, item]) => {
418
431
  const field =
419
- context.noun === "api" && FIELD_FLAGS.has(flag)
432
+ (context.noun === "api" || context.noun === undefined) &&
433
+ FIELD_FLAGS.has(flag)
420
434
  ? /^([^=]+)=@(.+)$/u.exec(item.value)
421
435
  : null;
422
- const file =
423
- field !== null
424
- ? PROSE_FIELDS.has(field[1])
425
- ? field[2]
426
- : undefined
427
- : FILE_FLAGS.has(flag)
428
- ? item.value.replace(/^@/, "")
429
- : undefined;
430
- return file !== undefined && !path.isAbsolute(file);
436
+ const files = [
437
+ field !== null && PROSE_FIELDS.has(field[1]) ? field[2] : undefined,
438
+ FILE_FLAGS.has(flag) && (context.noun !== "api" || field === null)
439
+ ? item.value.replace(/^@/, "")
440
+ : undefined,
441
+ ];
442
+ return files.some(file => file !== undefined && !path.isAbsolute(file));
431
443
  })
432
444
  ) {
433
445
  return {
@@ -103,6 +103,7 @@ function words(command) {
103
103
  */
104
104
  function argumentsOf(input) {
105
105
  const flags = new Map(),
106
+ fieldOccurrences = [],
106
107
  positional = [],
107
108
  tokens = [];
108
109
  // Any expansion can add/replace routing arguments after shell word splitting.
@@ -139,23 +140,34 @@ function argumentsOf(input) {
139
140
  flag === "-R" ? "--repo" : flag === "-X" ? "--method" : flag,
140
141
  value
141
142
  );
143
+ // API fields are additive even though routing options use last values.
144
+ if (FIELD_FLAGS.has(flag)) fieldOccurrences.push([flag, value]);
142
145
  tokens.push(value.value);
143
146
  } else if (!word.value.startsWith("-")) positional.push(word);
144
147
  else if (!word.literal) certain = false;
145
148
  }
146
- const noun = positional[0]?.value,
147
- verb = positional[1]?.value;
149
+ const nounWord = positional[0],
150
+ verbWord = positional[1];
151
+ const noun = nounWord?.literal ? nounWord.value : undefined,
152
+ verb = verbWord?.value;
153
+ // Command-name expansions can supply several argv words. A following
154
+ // read-looking literal does not prove that the expanded command is a read.
155
+ const uncertainName =
156
+ nounWord?.literal === false ||
157
+ (["issue", "pr"].includes(noun) && verbWord?.literal === false);
148
158
  const method = flags.get("--method");
149
159
  const writes =
150
- noun === "api"
160
+ uncertainName ||
161
+ (noun === "api"
151
162
  ? method
152
163
  ? !method.literal || METHODS.has(method.value.toUpperCase())
153
164
  : [...FIELD_FLAGS].some(flag => flags.has(flag)) ||
154
165
  Boolean(flags.get("--input")?.value)
155
166
  : ["issue", "pr"].includes(noun) &&
156
- ["create", "comment", "edit"].includes(verb);
167
+ ["create", "comment", "edit"].includes(verb));
157
168
  return {
158
169
  flags,
170
+ fieldOccurrences,
159
171
  positional,
160
172
  tokens: ["gh", ...tokens],
161
173
  noun,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.84.6",
3
+ "version": "4.84.7",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -287,8 +287,16 @@ export function publishableText(tokens, cwd, noun) {
287
287
  const value = valueAt(tokens, index);
288
288
  // On `gh api`, `-F` is `--field`: `key=@path` or `key=value`. Read as a
289
289
  // bare path, `body=@file` named no file and published unchecked (#4462).
290
- if (noun === "api" && /^[\w.[\]-]+=/u.test(value))
291
- return [fieldSource(value, cwd)];
290
+ if (
291
+ (noun === "api" || (noun === undefined && bare === "-F")) &&
292
+ /^[\w.[\]-]+=/u.test(value)
293
+ ) {
294
+ const field = fieldSource(value, cwd);
295
+ if (noun === "api") return [field];
296
+ // An unresolved noun may become api or issue/pr. Inspect both possible
297
+ // meanings of -F without evaluating the expansion or certifying routing.
298
+ return [field, { origin: value, text: readIfPresent(value, cwd) }];
299
+ }
292
300
  const file = value.replace(/^@/u, "");
293
301
  return [{ origin: file, text: readIfPresent(file, cwd) }];
294
302
  }
@@ -414,20 +422,24 @@ export async function evaluate(command, projectDir) {
414
422
  // protection, and name the unread input instead of claiming the text clean.
415
423
  if (
416
424
  context.cwd === undefined &&
417
- [...context.flags].some(([flag, item]) => {
425
+ [
426
+ ...context.flags,
427
+ ...(context.noun === "api" || context.noun === undefined
428
+ ? context.fieldOccurrences
429
+ : []),
430
+ ].some(([flag, item]) => {
418
431
  const field =
419
- context.noun === "api" && FIELD_FLAGS.has(flag)
432
+ (context.noun === "api" || context.noun === undefined) &&
433
+ FIELD_FLAGS.has(flag)
420
434
  ? /^([^=]+)=@(.+)$/u.exec(item.value)
421
435
  : null;
422
- const file =
423
- field !== null
424
- ? PROSE_FIELDS.has(field[1])
425
- ? field[2]
426
- : undefined
427
- : FILE_FLAGS.has(flag)
428
- ? item.value.replace(/^@/, "")
429
- : undefined;
430
- return file !== undefined && !path.isAbsolute(file);
436
+ const files = [
437
+ field !== null && PROSE_FIELDS.has(field[1]) ? field[2] : undefined,
438
+ FILE_FLAGS.has(flag) && (context.noun !== "api" || field === null)
439
+ ? item.value.replace(/^@/, "")
440
+ : undefined,
441
+ ];
442
+ return files.some(file => file !== undefined && !path.isAbsolute(file));
431
443
  })
432
444
  ) {
433
445
  return {