@codyswann/lisa 3.70.0 → 3.70.2

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 (85) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +224 -7
  2. package/dist/cli/prompts.d.ts +52 -1
  3. package/dist/cli/prompts.d.ts.map +1 -1
  4. package/dist/cli/prompts.js +57 -2
  5. package/dist/cli/prompts.js.map +1 -1
  6. package/dist/codex/scripts/block-no-verify.sh +190 -3
  7. package/dist/configs/vitest/scratch-global-setup.d.ts +32 -0
  8. package/dist/configs/vitest/scratch-global-setup.d.ts.map +1 -1
  9. package/dist/configs/vitest/scratch-global-setup.js +48 -0
  10. package/dist/configs/vitest/scratch-global-setup.js.map +1 -1
  11. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  12. package/dist/core/lisa-owned-hash-ledger.js +2 -0
  13. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  14. package/dist/core/lisa.d.ts.map +1 -1
  15. package/dist/core/lisa.js +4 -0
  16. package/dist/core/lisa.js.map +1 -1
  17. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  18. package/dist/core/upstream-evidence-manifest.js +7 -3
  19. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  20. package/dist/opencode/hooks-installer.d.ts.map +1 -1
  21. package/dist/opencode/hooks-installer.js +14 -0
  22. package/dist/opencode/hooks-installer.js.map +1 -1
  23. package/dist/strategies/copy-overwrite.d.ts.map +1 -1
  24. package/dist/strategies/copy-overwrite.js +30 -11
  25. package/dist/strategies/copy-overwrite.js.map +1 -1
  26. package/dist/strategies/strategy.interface.d.ts +15 -0
  27. package/dist/strategies/strategy.interface.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa/hooks/block-no-verify.agy.sh +200 -6
  32. package/plugins/lisa/hooks/block-no-verify.sh +224 -7
  33. package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +200 -6
  34. package/plugins/lisa-agy/plugin.json +1 -1
  35. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  38. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-copilot/hooks/block-no-verify.sh +224 -7
  42. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-cursor/hooks/block-no-verify.sh +224 -7
  44. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-expo-agy/plugin.json +1 -1
  47. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  52. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  57. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  62. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  67. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-rails-agy/plugin.json +1 -1
  72. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  77. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-typescript-cursor/.claude-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/src/base/hooks/block-no-verify.agy.sh +200 -6
  85. package/plugins/src/base/hooks/block-no-verify.sh +224 -7
@@ -12,7 +12,9 @@
12
12
  # 2. HUSKY=0 / HUSKY_SKIP_HOOKS=... — disables husky-managed git hooks;
13
13
  # 3. core.hooksPath pointed anywhere but an allowlisted in-repo hooks dir;
14
14
  # 4. --config-env=core.hooksPath=VAR, in both the one- and two-token spellings;
15
- # 5. GIT_CONFIG_KEY_<n>=core.hooksPath env-var-style command-scope config.
15
+ # 5. GIT_CONFIG_KEY_<n>=core.hooksPath env-var-style command-scope config;
16
+ # 6. the short `-n`, as a real argv token of a `git commit` — bare, or bundled
17
+ # into a short-option cluster such as `-nm "msg"`.
16
18
  #
17
19
  # The line below is what lets `lisa apply` tell a downstream copy of this guard
18
20
  # that is BEHIND from one that is AHEAD. Byte comparison cannot: both look like
@@ -23,15 +25,31 @@
23
25
  # Add a name here in the same commit that closes a vector. A hardening that
24
26
  # forgets to is invisible to refresh, and shows up as an unexplained diff at
25
27
  # review time instead of a named capability.
26
- # lisa-guard-capabilities: no-verify-abbrev, husky-env, hookspath-allowlist, config-env, git-config-key
28
+ # lisa-guard-capabilities: no-verify-abbrev, husky-env, hookspath-allowlist, config-env, git-config-key, no-verify-short
27
29
  #
28
30
  # Shell-token matching avoids false positives from issue bodies, heredocs, and
29
31
  # commit-message prose while still catching quoted real argv values such as
30
32
  # `git -c "core.hooksPath=/dev/null"`.
31
33
  #
32
- # The short `-n` form is intentionally NOT matched (see block-no-verify.agy.sh):
33
- # grep cannot distinguish a real -n option from -n in commit-message prose or an
34
- # unrelated piped command, and -n is far more common than --no-verify.
34
+ # The short `-n` form used to be excluded here, and the stated reason was that
35
+ # "grep cannot distinguish a real -n option from -n in commit-message prose or
36
+ # an unrelated piped command". That was true of the matcher it was written for.
37
+ # It is not true of this one: the command is TOKENIZED, not grepped, so `-m "fix
38
+ # the -n flag"` is one token holding the message and `grep -n` is a separate
39
+ # command — the two cases the comment called inseparable are separated by the
40
+ # same machinery `--config-env` and `GIT_CONFIG_KEY_<n>` already depend on.
41
+ #
42
+ # A rationale that outlives the implementation it describes is worse than none:
43
+ # it reads as a considered decision and stops the next reader from re-examining
44
+ # it. Measured against real git, `git commit -n`, `-nm msg`, and `-anm msg` all
45
+ # skip pre-commit exactly as completely as `--no-verify`, and `-nm` is the more
46
+ # likely spelling in practice because it reads as an ordinary message flag.
47
+ #
48
+ # What the old comment got right is the part about `-n` being common, and that
49
+ # concern is answered by SCOPE rather than by giving up: `-n` is `--dry-run` for
50
+ # `push`, `--no-stat` for `merge`, and a line count for head/tail/sort/grep, so
51
+ # the match is confined to the argv of a `git commit` invocation and nothing
52
+ # else. See `git_commit_skips_verification` below.
35
53
  set -euo pipefail
36
54
 
37
55
  input="$(cat)"
@@ -168,6 +186,203 @@ def disables_verification(token):
168
186
  and NO_VERIFY.startswith(token)
169
187
  )
170
188
 
189
+
190
+ # `git commit -n` is the short spelling of --no-verify and skips pre-commit and
191
+ # commit-msg identically. Matching it needs one thing the `--no-verify` scan
192
+ # above does not: SCOPE. `-n` means --dry-run to `git push`, --no-stat to `git
193
+ # merge`, and a line count to head/tail/sort/grep, so a token-anywhere match
194
+ # would refuse ordinary commands all day. Only `git commit` reads it as a hook
195
+ # bypass, so only that invocation's argv is scanned.
196
+ #
197
+ # The tokenizer below is a SECOND pass, deliberately not shared with the one the
198
+ # checks above use. `shlex.split` leaves `|` and `;` glued to their neighbours,
199
+ # which those checks paper over by stripping the punctuation off each token —
200
+ # fine when the question is "does this token look like a bypass anywhere in the
201
+ # line", fatal when the question is "where does this command end", because
202
+ # `git commit -m x && grep -n foo` would then read as one long git invocation
203
+ # and refuse the grep. `punctuation_chars=True` emits the operators as their own
204
+ # tokens, so the invocation's boundary is a token the scan can stop at.
205
+ COMMAND_SEPARATORS = {
206
+ ";", "|", "||", "&", "&&", "(", ")", "<", ">", ">>", "<<", "&|",
207
+ }
208
+
209
+ # git's own options that take a SEPARATE value token, which therefore must be
210
+ # skipped when looking for the subcommand: `git -c core.hooksPath=x commit` and
211
+ # `git -C /repo commit` both reach `commit`.
212
+ GIT_GLOBAL_SEPARATE_VALUE = {
213
+ "-c", "-C", "--config-env", "--git-dir", "--work-tree",
214
+ "--namespace", "--exec-path", "--super-prefix",
215
+ }
216
+
217
+ # `git commit` long options whose value can be a separate token. Listed so the
218
+ # VALUE is never mistaken for a flag — `git commit --author "A -n B" -m x`
219
+ # must stay allowed. Only genuinely value-taking options belong here: adding a
220
+ # boolean one by mistake would swallow the token after it, and `git commit
221
+ # --amend -n` would go unnoticed.
222
+ COMMIT_LONG_SEPARATE_VALUE = {
223
+ "--message", "--file", "--author", "--date", "--template", "--cleanup",
224
+ "--reuse-message", "--reedit-message", "--fixup", "--squash",
225
+ "--pathspec-from-file", "--trailer",
226
+ }
227
+
228
+ # Short `git commit` options that REQUIRE a value: `-m`/`-F`/`-c`/`-C`/`-t`.
229
+ # In a cluster the value is whatever follows them in the same token, or the next
230
+ # token when nothing does — which is why `-mn` is the message "n" and not a
231
+ # bypass, and `-nm msg` IS one.
232
+ COMMIT_SHORT_REQUIRED_VALUE = set("mFcCt")
233
+
234
+ # Short options taking an OPTIONAL value, which git only ever reads attached:
235
+ # `-uno` is --untracked-files=no, `-Skeyid` is --gpg-sign=keyid. The cluster
236
+ # ends at them either way, so `-un` is untracked-files "n" rather than a bypass.
237
+ COMMIT_SHORT_OPTIONAL_VALUE = set("uS")
238
+
239
+
240
+ def line_boundaries_as_separators(text):
241
+ """Turn newlines into command separators, keeping continuations intact.
242
+
243
+ A newline ends a command exactly as `;` does, but shlex treats it as plain
244
+ whitespace — so `git commit -m x` on one line and `grep -n foo` on the next
245
+ read as ONE invocation and the grep gets refused. That is the same
246
+ false-positive class the short-form match exists to avoid, arriving through
247
+ the boundary rather than through the token.
248
+
249
+ Backslash-newline is joined first, because there the newline is NOT a
250
+ boundary: `git commit \\` + newline + `-nm x` is one command, and turning
251
+ that newline into a separator would hide a real bypass.
252
+
253
+ Args:
254
+ text: The command line, heredoc payloads already stripped.
255
+
256
+ Returns:
257
+ The same command with line breaks spelled as separators.
258
+ """
259
+ joined = text.replace("\r\n", "\n").replace("\\\n", " ")
260
+ return joined.replace("\n", " ; ")
261
+
262
+
263
+ def shell_tokens(text):
264
+ """Tokenize a command with shell operators kept as their own tokens.
265
+
266
+ Args:
267
+ text: The command line, heredoc payloads already stripped.
268
+
269
+ Returns:
270
+ The token list, with `;`, `|`, `&&`, `(` and friends standing alone.
271
+ """
272
+ lexer = shlex.shlex(
273
+ line_boundaries_as_separators(text), posix=True, punctuation_chars=True
274
+ )
275
+ lexer.whitespace_split = True
276
+ # shlex treats `#` as a comment introducer by default and would silently
277
+ # truncate the rest of the line; `shlex.split` disables it, and so must this.
278
+ lexer.commenters = ""
279
+ return list(lexer)
280
+
281
+
282
+ def cluster_skips_verification(cluster):
283
+ """Read a short-option cluster the way git's own parser reads it.
284
+
285
+ Args:
286
+ cluster: A single token beginning with one `-`, e.g. `-nm` or `-mn`.
287
+
288
+ Returns:
289
+ A pair (bypasses, consumes_next_token). `bypasses` is True when a real
290
+ `-n` option is present; `consumes_next_token` is True when the cluster
291
+ ends in a value-taking option whose value is the following token.
292
+ """
293
+ body = cluster[1:]
294
+ for offset, letter in enumerate(body):
295
+ if letter == "n":
296
+ return (True, False)
297
+ if letter in COMMIT_SHORT_REQUIRED_VALUE:
298
+ # Everything after this letter is the value. It is a separate token
299
+ # only when nothing is attached.
300
+ return (False, offset == len(body) - 1)
301
+ if letter in COMMIT_SHORT_OPTIONAL_VALUE:
302
+ return (False, False)
303
+ return (False, False)
304
+
305
+
306
+ def commit_bypasses_verification(argv):
307
+ """Whether a `git commit` invocation's argv carries a real short `-n`.
308
+
309
+ Args:
310
+ argv: Tokens following the `commit` subcommand, to the end of the line.
311
+
312
+ Returns:
313
+ True if git would read one of them as --no-verify.
314
+ """
315
+ index = 0
316
+ while index < len(argv):
317
+ token = argv[index]
318
+ index += 1
319
+ # `--` ends the options; everything after it is a pathspec, and a file
320
+ # legitimately named `-n` is not a bypass.
321
+ if token in COMMAND_SEPARATORS or token == "--":
322
+ return False
323
+ if token.startswith("--"):
324
+ if token in COMMIT_LONG_SEPARATE_VALUE:
325
+ index += 1
326
+ continue
327
+ # A bare `-` is git's stdin placeholder, not an option cluster.
328
+ if not token.startswith("-") or token == "-":
329
+ continue
330
+ bypasses, consumes_next = cluster_skips_verification(token)
331
+ if bypasses:
332
+ return True
333
+ if consumes_next:
334
+ index += 1
335
+ return False
336
+
337
+
338
+ def subcommand_after_git(tokens, start):
339
+ """Find the subcommand a `git` token introduces, and its argv.
340
+
341
+ Args:
342
+ tokens: The full operator-aware token list.
343
+ start: Index just past the `git` token.
344
+
345
+ Returns:
346
+ A pair (subcommand, argv), or None when the invocation names none.
347
+ """
348
+ index = start
349
+ while index < len(tokens):
350
+ token = tokens[index]
351
+ if token in COMMAND_SEPARATORS:
352
+ return None
353
+ if not token.startswith("-"):
354
+ return (token, tokens[index + 1:])
355
+ index += 2 if token in GIT_GLOBAL_SEPARATE_VALUE else 1
356
+ return None
357
+
358
+
359
+ def git_commit_skips_verification(text):
360
+ """Whether the command runs `git commit` with the short `-n` bypass.
361
+
362
+ Args:
363
+ text: The command line, heredoc payloads already stripped.
364
+
365
+ Returns:
366
+ True if any `git commit` invocation on the line skips verification.
367
+ """
368
+ try:
369
+ scoped_tokens = shell_tokens(text)
370
+ except ValueError:
371
+ return False
372
+ for index, token in enumerate(scoped_tokens):
373
+ # `/usr/bin/git` and a bare `git` are the same program; an env prefix
374
+ # (`HUSKY=1 git commit -n`) simply sits in an earlier token.
375
+ if token != "git" and not token.endswith("/git"):
376
+ continue
377
+ found = subcommand_after_git(scoped_tokens, index + 1)
378
+ if found and found[0] == "commit" and commit_bypasses_verification(found[1]):
379
+ return True
380
+ return False
381
+
382
+
383
+ if git_commit_skips_verification(strip_heredocs(command)):
384
+ sys.exit(1)
385
+
171
386
  for i, token in enumerate(normalized_tokens):
172
387
  if disables_verification(token):
173
388
  sys.exit(1)
@@ -238,8 +453,10 @@ sys.exit(0)
238
453
  PY
239
454
  then
240
455
  cat >&2 <<'EOF'
241
- Blocked: this command bypasses pre-commit/pre-push hooks (--no-verify, HUSKY=0,
242
- or core.hooksPath disabling). Fix the underlying issue (security audit, lint,
456
+ Blocked: this command bypasses pre-commit/pre-push hooks (--no-verify or its
457
+ short form -n, HUSKY=0, or core.hooksPath disabling). `git commit -n` and a
458
+ cluster such as `-nm "msg"` skip pre-commit exactly as completely as the long
459
+ flag. Fix the underlying issue (security audit, lint,
243
460
  typecheck, tests, formatting) instead. If a fix is genuinely impossible, ask the
244
461
  user to make the risk-acceptance decision and add a specific documented ignore;
245
462
  never bypass the hook.
@@ -7,6 +7,16 @@ export type OverwriteDecision = "yes" | "no" | "diff";
7
7
  * Interface for user prompts
8
8
  */
9
9
  export interface IPrompter {
10
+ /**
11
+ * Whether this run has no operator decision available to it — neither a live
12
+ * prompt to answer nor a `--yes` given in advance.
13
+ *
14
+ * The apply needs to tell those apart, and the prompter is the only thing
15
+ * that knows. A `--yes` run is *attended*: the operator decided before
16
+ * starting. A run with no TTY and no flag is not — there is nobody to ask,
17
+ * and answering on their behalf is what #3026 was.
18
+ */
19
+ readonly unattended: boolean;
10
20
  /**
11
21
  * Prompt for file overwrite decision
12
22
  * @param relativePath Path to the conflicting file
@@ -30,17 +40,52 @@ export interface IPrompter {
30
40
  * Interactive prompter using {@link https://github.com/enquirer/enquirer inquirer}/prompts
31
41
  */
32
42
  export declare class InteractivePrompter implements IPrompter {
43
+ /** A human is at the keyboard and answers every question. */
44
+ readonly unattended = false;
33
45
  promptOverwrite(relativePath: string): Promise<OverwriteDecision>;
34
46
  confirmProjectTypes(detected: readonly ProjectType[]): Promise<readonly ProjectType[]>;
35
47
  confirmDirtyGit(status: string): Promise<boolean>;
36
48
  }
37
49
  /**
38
- * Auto-accepting prompter for non-interactive mode
50
+ * Auto-accepting prompter for `--yes`
51
+ *
52
+ * Reached only when the operator passed the flag. That flag *is* the decision —
53
+ * "replace what differs, do not ask me" — so this prompter is attended even
54
+ * though nothing is interactive about it. What it must never cover is a run
55
+ * where no such flag was given; see {@link UnattendedPrompter}.
39
56
  *
40
57
  * Note: confirmDirtyGit always prompts interactively even in auto-accept mode,
41
58
  * as running Lisa on a dirty working directory requires explicit user consent.
42
59
  */
43
60
  export declare class AutoAcceptPrompter implements IPrompter {
61
+ /** The operator decided in advance by passing `--yes`. */
62
+ readonly unattended = false;
63
+ promptOverwrite(_relativePath: string): Promise<OverwriteDecision>;
64
+ confirmProjectTypes(detected: readonly ProjectType[]): Promise<readonly ProjectType[]>;
65
+ confirmDirtyGit(status: string): Promise<boolean>;
66
+ }
67
+ /**
68
+ * Prompter for a run with no operator on either end — no TTY to ask, and no
69
+ * `--yes` given in advance.
70
+ *
71
+ * This is the common case, not the exotic one: every agent-driven, scripted,
72
+ * and CI `lisa apply` lands here. It used to be answered by
73
+ * {@link AutoAcceptPrompter}, which says "yes" to every overwrite. So a bare
74
+ * `lisa apply` replaced host-customised `knip.json`, `eslint.config.ts`, and
75
+ * `tsconfig.json` and reported the result under "Overwritten: N files
76
+ * (approved or Lisa-owned)" — nobody approved them, and Lisa owns none of
77
+ * them. The absence of a terminal was being read as consent from an operator
78
+ * who was never there (#3026).
79
+ *
80
+ * It answers "no" so that nothing is silently replaced even if a caller builds
81
+ * a strategy context by hand. The apply's own routing does the rest: an
82
+ * unattended run takes the same branch the postinstall does, which reports the
83
+ * file as out of date and still honours `--refresh-templates`, the flag that
84
+ * means "take upstream's version of these".
85
+ */
86
+ export declare class UnattendedPrompter implements IPrompter {
87
+ /** There is nobody to ask and no answer was given in advance. */
88
+ readonly unattended = true;
44
89
  promptOverwrite(_relativePath: string): Promise<OverwriteDecision>;
45
90
  confirmProjectTypes(detected: readonly ProjectType[]): Promise<readonly ProjectType[]>;
46
91
  confirmDirtyGit(status: string): Promise<boolean>;
@@ -52,6 +97,12 @@ export declare class AutoAcceptPrompter implements IPrompter {
52
97
  export declare function isInteractive(): boolean;
53
98
  /**
54
99
  * Create appropriate prompter based on mode and TTY
100
+ *
101
+ * The three cases are three different sources of authority, and collapsing any
102
+ * two of them loses the distinction that matters. `--yes` is consent given in
103
+ * advance. A TTY is consent obtainable on demand. Neither one is a run with no
104
+ * operator at all, which gets a prompter that declines rather than one that
105
+ * invents an approval (#3026).
55
106
  * @param yesMode Non-interactive mode flag
56
107
  * @returns Prompter instance
57
108
  */
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAarD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAElE;;;;OAIG;IACH,mBAAmB,CACjB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;IAEnC;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnD;AAED;;GAEG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IAC7C,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAWjE,mBAAmB,CACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAY5B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUxD;AAED;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAC5C,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIlE,mBAAmB,CACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAI5B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAwBxD;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAK1D"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAarD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAE7B;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAElE;;;;OAIG;IACH,mBAAmB,CACjB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;IAEnC;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnD;AAED;;GAEG;AACH,qBAAa,mBAAoB,YAAW,SAAS;IACnD,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,SAAS;IAEtB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAWjE,mBAAmB,CACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAY5B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUxD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAClD,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,SAAS;IAEtB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIlE,mBAAmB,CACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAI5B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAwBxD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IAClD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,QAAQ;IAErB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIlE,mBAAmB,CACvB,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC;IAI5B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUxD;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAQ1D"}
@@ -11,6 +11,8 @@ const DIRTY_GIT_CONFIRM_MESSAGE = "Your git working directory has uncommitted ch
11
11
  * Interactive prompter using {@link https://github.com/enquirer/enquirer inquirer}/prompts
12
12
  */
13
13
  export class InteractivePrompter {
14
+ /** A human is at the keyboard and answers every question. */
15
+ unattended = false;
14
16
  async promptOverwrite(relativePath) {
15
17
  return select({
16
18
  message: `File differs: ${relativePath}\nOverwrite?`,
@@ -40,12 +42,19 @@ export class InteractivePrompter {
40
42
  }
41
43
  }
42
44
  /**
43
- * Auto-accepting prompter for non-interactive mode
45
+ * Auto-accepting prompter for `--yes`
46
+ *
47
+ * Reached only when the operator passed the flag. That flag *is* the decision —
48
+ * "replace what differs, do not ask me" — so this prompter is attended even
49
+ * though nothing is interactive about it. What it must never cover is a run
50
+ * where no such flag was given; see {@link UnattendedPrompter}.
44
51
  *
45
52
  * Note: confirmDirtyGit always prompts interactively even in auto-accept mode,
46
53
  * as running Lisa on a dirty working directory requires explicit user consent.
47
54
  */
48
55
  export class AutoAcceptPrompter {
56
+ /** The operator decided in advance by passing `--yes`. */
57
+ unattended = false;
49
58
  async promptOverwrite(_relativePath) {
50
59
  return "yes";
51
60
  }
@@ -73,6 +82,43 @@ export class AutoAcceptPrompter {
73
82
  });
74
83
  }
75
84
  }
85
+ /**
86
+ * Prompter for a run with no operator on either end — no TTY to ask, and no
87
+ * `--yes` given in advance.
88
+ *
89
+ * This is the common case, not the exotic one: every agent-driven, scripted,
90
+ * and CI `lisa apply` lands here. It used to be answered by
91
+ * {@link AutoAcceptPrompter}, which says "yes" to every overwrite. So a bare
92
+ * `lisa apply` replaced host-customised `knip.json`, `eslint.config.ts`, and
93
+ * `tsconfig.json` and reported the result under "Overwritten: N files
94
+ * (approved or Lisa-owned)" — nobody approved them, and Lisa owns none of
95
+ * them. The absence of a terminal was being read as consent from an operator
96
+ * who was never there (#3026).
97
+ *
98
+ * It answers "no" so that nothing is silently replaced even if a caller builds
99
+ * a strategy context by hand. The apply's own routing does the rest: an
100
+ * unattended run takes the same branch the postinstall does, which reports the
101
+ * file as out of date and still honours `--refresh-templates`, the flag that
102
+ * means "take upstream's version of these".
103
+ */
104
+ export class UnattendedPrompter {
105
+ /** There is nobody to ask and no answer was given in advance. */
106
+ unattended = true;
107
+ async promptOverwrite(_relativePath) {
108
+ return "no";
109
+ }
110
+ async confirmProjectTypes(detected) {
111
+ return detected;
112
+ }
113
+ async confirmDirtyGit(status) {
114
+ console.log(UNCOMMITTED_CHANGES_HEADER);
115
+ console.log(status);
116
+ console.log("");
117
+ console.log("Cannot proceed: working directory has uncommitted changes and no TTY available for confirmation.");
118
+ console.log("Please commit or stash your changes before running Lisa.");
119
+ return false;
120
+ }
121
+ }
76
122
  /**
77
123
  * Check if running in interactive mode (TTY available)
78
124
  * @returns True if running in TTY mode
@@ -82,13 +128,22 @@ export function isInteractive() {
82
128
  }
83
129
  /**
84
130
  * Create appropriate prompter based on mode and TTY
131
+ *
132
+ * The three cases are three different sources of authority, and collapsing any
133
+ * two of them loses the distinction that matters. `--yes` is consent given in
134
+ * advance. A TTY is consent obtainable on demand. Neither one is a run with no
135
+ * operator at all, which gets a prompter that declines rather than one that
136
+ * invents an approval (#3026).
85
137
  * @param yesMode Non-interactive mode flag
86
138
  * @returns Prompter instance
87
139
  */
88
140
  export function createPrompter(yesMode) {
89
- if (yesMode || !isInteractive()) {
141
+ if (yesMode) {
90
142
  return new AutoAcceptPrompter();
91
143
  }
144
+ if (!isInteractive()) {
145
+ return new UnattendedPrompter();
146
+ }
92
147
  return new InteractivePrompter();
93
148
  }
94
149
  //# sourceMappingURL=prompts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGpD;;GAEG;AACH,MAAM,0BAA0B,GAAG,iCAAiC,CAAC;AAErE;;GAEG;AACH,MAAM,yBAAyB,GAC7B,iFAAiF,CAAC;AAmCpF;;GAEG;AACH,MAAM,OAAO,mBAAmB;IAC9B,KAAK,CAAC,eAAe,CAAC,YAAoB;QACxC,OAAO,MAAM,CAAC;YACZ,OAAO,EAAE,iBAAiB,YAAY,cAAc;YACpD,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAc,EAAE;gBAClD,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,IAAa,EAAE;gBAC3C,EAAE,IAAI,EAAE,yBAAyB,EAAE,KAAK,EAAE,MAAe,EAAE;aAC5D;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,QAAgC;QAEhC,MAAM,YAAY,GAChB,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC;QAEhE,MAAM,OAAO,CAAC;YACZ,OAAO,EAAE,2BAA2B,YAAY,8BAA8B;YAC9E,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,MAAc;QAClC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,OAAO,OAAO,CAAC;YACb,OAAO,EAAE,yBAAyB;YAClC,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,kBAAkB;IAC7B,KAAK,CAAC,eAAe,CAAC,aAAqB;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,QAAgC;QAEhC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,MAAc;QAClC,wDAAwD;QACxD,qEAAqE;QACrE,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACrB,8DAA8D;YAC9D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CACT,kGAAkG,CACnG,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;YACxE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,OAAO,OAAO,CAAC;YACb,OAAO,EAAE,yBAAyB;YAClC,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,IAAI,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;QAChC,OAAO,IAAI,kBAAkB,EAAE,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,mBAAmB,EAAE,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"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"}