@codyswann/lisa 4.9.2 → 4.9.4

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 (83) hide show
  1. package/all/copy-overwrite/scripts/lisa-gates.mjs +2 -2
  2. package/dist/cli/{doctor-learnings-merge-driver.d.ts → doctor-merge-drivers.d.ts} +6 -6
  3. package/dist/cli/doctor-merge-drivers.d.ts.map +1 -0
  4. package/dist/cli/doctor-merge-drivers.js +163 -0
  5. package/dist/cli/doctor-merge-drivers.js.map +1 -0
  6. package/dist/cli/doctor.js +2 -2
  7. package/dist/cli/doctor.js.map +1 -1
  8. package/dist/core/gitattributes-merge-drivers.d.ts +28 -0
  9. package/dist/core/gitattributes-merge-drivers.d.ts.map +1 -0
  10. package/dist/core/gitattributes-merge-drivers.js +62 -0
  11. package/dist/core/gitattributes-merge-drivers.js.map +1 -0
  12. package/dist/core/learnings-merge-driver-install.d.ts +0 -13
  13. package/dist/core/learnings-merge-driver-install.d.ts.map +1 -1
  14. package/dist/core/learnings-merge-driver-install.js +0 -16
  15. package/dist/core/learnings-merge-driver-install.js.map +1 -1
  16. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  17. package/dist/core/lisa-owned-hash-ledger.js +2 -0
  18. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  19. package/dist/core/merge-driver-registration.d.ts +26 -0
  20. package/dist/core/merge-driver-registration.d.ts.map +1 -0
  21. package/dist/core/merge-driver-registration.js +38 -0
  22. package/dist/core/merge-driver-registration.js.map +1 -0
  23. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  24. package/dist/core/upstream-evidence-manifest.js +15 -3
  25. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  26. package/package.json +3 -2
  27. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa-agy/plugin.json +1 -1
  30. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  33. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-expo-agy/plugin.json +1 -1
  40. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  45. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  50. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  55. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  60. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-rails-agy/plugin.json +1 -1
  65. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  70. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  75. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  77. package/scripts/check-merge-driver-registration.mjs +142 -0
  78. package/scripts/install-generated-artifact-merge-driver.mjs +200 -0
  79. package/scripts/lib/gitattributes-merge-drivers.mjs +52 -0
  80. package/scripts/merge-generated-artifact.mjs +519 -0
  81. package/dist/cli/doctor-learnings-merge-driver.d.ts.map +0 -1
  82. package/dist/cli/doctor-learnings-merge-driver.js +0 -73
  83. package/dist/cli/doctor-learnings-merge-driver.js.map +0 -1
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Register the `lisa-generated-artifact` merge driver in this checkout's local
4
+ * git config (issue CodySwannGT/lisa#3084).
5
+ *
6
+ * ## Why this exists separately from the learnings driver's registration
7
+ *
8
+ * Lisa already registers a merge driver — `lisa-learnings` — from the
9
+ * `EnsureLearningsMergeDriver` migration, which runs on every `lisa apply`, and
10
+ * `lisa apply` runs from a HOST project's `postinstall`. That path covers every
11
+ * consumer and covers **Lisa's own repository not at all**: Lisa's `postinstall`
12
+ * is `install-claude-plugins.sh` plus a `tsc` fallback, with no apply, because
13
+ * apply is self-restricted in the source repo. Measured — `git config --get
14
+ * merge.lisa-learnings.driver` exits 1 in the main checkout and in agent
15
+ * worktrees alike.
16
+ *
17
+ * That asymmetry is exactly backwards for #3084: the two artifacts this driver
18
+ * serves exist **only** in the Lisa repository, which is the one place the
19
+ * existing registration mechanism never reaches. So this registration runs from
20
+ * Lisa's own `postinstall` instead, where `bun install` in a fresh clone or a
21
+ * fresh agent worktree fires it with no separate operator step.
22
+ *
23
+ * ## Why it is inert everywhere else
24
+ *
25
+ * Lisa's `postinstall` also runs inside `node_modules/@codyswann/lisa` when a
26
+ * consumer installs the package, where blindly writing a driver command would
27
+ * litter the consumer's git config with a path that does not exist there. The
28
+ * roster is therefore read from the checkout's own `.gitattributes`: unless a
29
+ * tracked path in THIS working tree is mapped to the driver, there is nothing
30
+ * to register and the script says so and exits 0.
31
+ *
32
+ * ## Why the command is repo-relative
33
+ *
34
+ * `git config --local` writes to `.git/config`, which every linked worktree of
35
+ * a repository SHARES. An absolute path would pin every worktree's merges to
36
+ * whichever checkout happened to register last — including one later deleted.
37
+ * Git runs a merge driver with its cwd at the top level of the working tree
38
+ * being merged (measured, including when `git merge` is invoked from a
39
+ * subdirectory), so `./scripts/...` resolves per-worktree and stays correct.
40
+ *
41
+ * Exit codes:
42
+ * 0 — registered, already correct, or nothing mapped here to register.
43
+ * 1 — a git-config write genuinely failed. Reporting success there would tell
44
+ * an operator the artifacts are protected when they are not.
45
+ *
46
+ * @module scripts/install-generated-artifact-merge-driver
47
+ */
48
+ import { existsSync, readFileSync } from "node:fs";
49
+ import path from "node:path";
50
+ import process from "node:process";
51
+ import { execFileSync } from "node:child_process";
52
+
53
+ import { invokedAsScript } from "./lib/invoked-as-script.mjs";
54
+
55
+ /** Merge-driver name shared by `.gitattributes` and local git config. */
56
+ export const GENERATED_ARTIFACT_DRIVER = "lisa-generated-artifact";
57
+
58
+ /** Human-readable driver name recorded alongside the command. */
59
+ const DRIVER_DESCRIPTION =
60
+ "Reconstruct a generated artifact from both sides of a merge";
61
+
62
+ /** Repo-relative driver entry point. */
63
+ const DRIVER_SCRIPT = "scripts/merge-generated-artifact.mjs";
64
+
65
+ /** The command git will run, with git's own `%O %A %B %P` placeholders. */
66
+ export const DRIVER_COMMAND = `node ./${DRIVER_SCRIPT} --base %O --ours %A --theirs %B --path %P`;
67
+
68
+ /**
69
+ * Run a fixed git command, never through a shell.
70
+ * @param {readonly string[]} args - Literal git arguments
71
+ * @param {string} cwd - Working directory
72
+ * @returns {{ok: true, stdout: string} | {ok: false}} Trimmed stdout, or a failure
73
+ */
74
+ function git(args, cwd) {
75
+ try {
76
+ return {
77
+ ok: true,
78
+ stdout: execFileSync("git", [...args], {
79
+ cwd,
80
+ encoding: "utf8",
81
+ stdio: ["ignore", "pipe", "ignore"],
82
+ }).trim(),
83
+ };
84
+ } catch {
85
+ return { ok: false };
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Paths this working tree maps to the driver, confirmed by git.
91
+ *
92
+ * Two steps rather than one, and both are needed. `.gitattributes` supplies the
93
+ * CANDIDATES cheaply — asking `git check-attr` about every tracked file would
94
+ * be several thousand git invocations inside a `postinstall`. `git check-attr`
95
+ * then supplies the ANSWER, because whether a pattern actually resolves to this
96
+ * driver depends on pattern precedence and on every attributes source git
97
+ * consults, which reading one file cannot tell you.
98
+ * @param {string} root - Working-tree top level
99
+ * @returns {string[]} Paths git confirms are mapped to this driver
100
+ */
101
+ function mappedPaths(root) {
102
+ const patterns = [];
103
+ try {
104
+ for (const line of readFileSync(
105
+ path.join(root, ".gitattributes"),
106
+ "utf8"
107
+ ).split("\n")) {
108
+ const trimmed = line.trim();
109
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
110
+ const [pattern, ...attributes] = trimmed.split(/\s+/u);
111
+ if (attributes.includes(`merge=${GENERATED_ARTIFACT_DRIVER}`))
112
+ patterns.push(pattern);
113
+ }
114
+ } catch {
115
+ return [];
116
+ }
117
+ if (patterns.length === 0) return [];
118
+ const attr = git(["check-attr", "merge", "--", ...patterns], root);
119
+ if (!attr.ok) return [];
120
+ return attr.stdout
121
+ .split("\n")
122
+ .filter(line => line.endsWith(`merge: ${GENERATED_ARTIFACT_DRIVER}`))
123
+ .map(line => line.slice(0, line.indexOf(": merge:")));
124
+ }
125
+
126
+ /**
127
+ * Register the driver for one checkout.
128
+ * @param {string} cwd - Directory to register from
129
+ * @param {(message: string) => void} log - Sink for the outcome line
130
+ * @returns {number} Process exit code
131
+ */
132
+ export function installGeneratedArtifactMergeDriver(cwd, log) {
133
+ const toplevel = git(["rev-parse", "--show-toplevel"], cwd);
134
+ if (!toplevel.ok || toplevel.stdout === "") {
135
+ log(
136
+ `${GENERATED_ARTIFACT_DRIVER}: not a git working tree — nothing to register`
137
+ );
138
+ return 0;
139
+ }
140
+ const root = toplevel.stdout;
141
+ if (!existsSync(path.join(root, DRIVER_SCRIPT))) {
142
+ log(
143
+ `${GENERATED_ARTIFACT_DRIVER}: ${DRIVER_SCRIPT} is not in this working tree — nothing to register`
144
+ );
145
+ return 0;
146
+ }
147
+ const attributed = mappedPaths(root);
148
+ if (attributed.length === 0) {
149
+ log(
150
+ `${GENERATED_ARTIFACT_DRIVER}: nothing in this working tree maps to the driver — nothing to register`
151
+ );
152
+ return 0;
153
+ }
154
+ const current = git(
155
+ ["config", "--local", "--get", `merge.${GENERATED_ARTIFACT_DRIVER}.driver`],
156
+ root
157
+ );
158
+ if (current.ok && current.stdout === DRIVER_COMMAND) {
159
+ log(
160
+ `${GENERATED_ARTIFACT_DRIVER}: already registered for ${attributed.length} path(s)`
161
+ );
162
+ return 0;
163
+ }
164
+ git(
165
+ [
166
+ "config",
167
+ "--local",
168
+ `merge.${GENERATED_ARTIFACT_DRIVER}.name`,
169
+ DRIVER_DESCRIPTION,
170
+ ],
171
+ root
172
+ );
173
+ const written = git(
174
+ [
175
+ "config",
176
+ "--local",
177
+ `merge.${GENERATED_ARTIFACT_DRIVER}.driver`,
178
+ DRIVER_COMMAND,
179
+ ],
180
+ root
181
+ );
182
+ if (!written.ok) {
183
+ log(
184
+ `${GENERATED_ARTIFACT_DRIVER}: could not write local git config — merge driver NOT registered; generated artifacts will conflict on every merge`
185
+ );
186
+ return 1;
187
+ }
188
+ log(
189
+ `${GENERATED_ARTIFACT_DRIVER}: registered for ${attributed.length} path(s)`
190
+ );
191
+ return 0;
192
+ }
193
+
194
+ if (invokedAsScript(import.meta.url)) {
195
+ process.exit(
196
+ installGeneratedArtifactMergeDriver(process.cwd(), message =>
197
+ process.stdout.write(`${message}\n`)
198
+ )
199
+ );
200
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The roster of merge drivers a repository's `.gitattributes` asks for.
3
+ *
4
+ * Every consumer of this list exists because a merge driver has two halves in
5
+ * two places and only one of them is committed: `.gitattributes` names WHICH
6
+ * driver a path uses, while `merge.<name>.driver` — the command — is
7
+ * machine-local, because git refuses to run a command a repository supplies.
8
+ * A mapping whose driver is unregistered is not an error anywhere: git falls
9
+ * back to its built-in text merge and says nothing. Declared, and inert.
10
+ *
11
+ * The roster is DERIVED rather than hardcoded on purpose. Lisa's first driver
12
+ * (`lisa-learnings`) had a registration check that named it, so Lisa's second
13
+ * driver would have been uncovered until somebody remembered to widen it. A
14
+ * derived roster covers a new driver the day its mapping lands.
15
+ *
16
+ * @module scripts/lib/gitattributes-merge-drivers
17
+ */
18
+
19
+ /**
20
+ * Compare driver names for a stable, locale-independent roster order.
21
+ * @param {string} left - First driver name
22
+ * @param {string} right - Second driver name
23
+ * @returns {number} Negative, zero, or positive per the usual comparator contract
24
+ */
25
+ function compareNames(left, right) {
26
+ return left.localeCompare(right, "en");
27
+ }
28
+
29
+ /** Built-in merge strategies, which need no registration. */
30
+ const BUILT_IN = new Set(["text", "binary", "union", "ours"]);
31
+
32
+ /**
33
+ * Driver names named by a `.gitattributes` file's `merge=<name>` attributes.
34
+ *
35
+ * Built-in strategies (`text`, `binary`, `union`, `ours`) are excluded: they
36
+ * need no registration, so reporting them unregistered would be noise that
37
+ * teaches an operator to ignore the check.
38
+ * @param {string} contents - Whole `.gitattributes` file contents
39
+ * @returns {string[]} Sorted, de-duplicated custom driver names
40
+ */
41
+ export function mergeDriversIn(contents) {
42
+ const named = contents
43
+ .split("\n")
44
+ .map(line => line.trim())
45
+ .filter(line => line !== "" && !line.startsWith("#"))
46
+ // The first field is the path pattern; every field after it is an attribute.
47
+ .flatMap(line => line.split(/\s+/u).slice(1))
48
+ .filter(attribute => attribute.startsWith("merge="))
49
+ .map(attribute => attribute.slice("merge=".length))
50
+ .filter(name => name !== "" && !BUILT_IN.has(name));
51
+ return [...new Set(named)].sort(compareNames);
52
+ }