@codyswann/lisa 3.64.1 → 3.64.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 (70) hide show
  1. package/cdk/package-lisa/package.lisa.json +12 -4
  2. package/dist/core/lisa.d.ts.map +1 -1
  3. package/dist/core/lisa.js +8 -1
  4. package/dist/core/lisa.js.map +1 -1
  5. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  6. package/dist/core/upstream-evidence-manifest.js +15 -7
  7. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  8. package/dist/strategies/package-lisa-types.d.ts +32 -1
  9. package/dist/strategies/package-lisa-types.d.ts.map +1 -1
  10. package/dist/strategies/package-lisa.d.ts +17 -0
  11. package/dist/strategies/package-lisa.d.ts.map +1 -1
  12. package/dist/strategies/package-lisa.js +342 -13
  13. package/dist/strategies/package-lisa.js.map +1 -1
  14. package/expo/package-lisa/package.lisa.json +12 -2
  15. package/harper-fabric/package-lisa/package.lisa.json +38 -8
  16. package/nestjs/package-lisa/package.lisa.json +11 -3
  17. package/package.json +1 -1
  18. package/phaser/package-lisa/package.lisa.json +38 -8
  19. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  21. package/plugins/lisa-agy/plugin.json +1 -1
  22. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  25. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-agy/plugin.json +1 -1
  32. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  47. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  52. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails-agy/plugin.json +1 -1
  57. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  62. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  67. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  69. package/typescript/package-lisa/package.lisa.json +38 -9
  70. package/ui/index.html +5 -2
@@ -42,6 +42,29 @@ export interface MergeSection {
42
42
  export interface RemoveSection {
43
43
  [key: string]: string[];
44
44
  }
45
+ /**
46
+ * Adopt behavior: values Lisa itself previously wrote into a key it has since
47
+ * handed back to the host.
48
+ *
49
+ * A `force` key that a host must be able to EXTEND cannot stay forced — every
50
+ * apply would delete the extension. Moving it to `defaults` protects the
51
+ * extension but freezes every host that never customised it on whatever literal
52
+ * their last apply left behind, because `defaults` never overwrites.
53
+ *
54
+ * `adopt` is the bridge. Each entry lists the values Lisa is known to have
55
+ * written into that key itself. A host sitting on one of them has provably NOT
56
+ * customised it, so Lisa discards it and lets `defaults` install the current
57
+ * value; any other value is the host's own and is kept. Applied between force
58
+ * and defaults, so the default it clears the way for lands in the same apply.
59
+ *
60
+ * Each entry maps a package.json section (e.g. "scripts") to a map of key to
61
+ * the Lisa-authored values recognised for that key. The list is cumulative:
62
+ * whenever a governed value changes, the value being replaced stays in the list
63
+ * so a host that skipped a release is still recognised rather than warned at.
64
+ */
65
+ export interface AdoptSection {
66
+ [key: string]: Record<string, string[]>;
67
+ }
45
68
  /**
46
69
  * Template structure for package.lisa.json files
47
70
  * @remarks
@@ -49,6 +72,8 @@ export interface RemoveSection {
49
72
  * - `defaults`: Sections where project's values take precedence if they exist
50
73
  * - `merge`: Array sections that are concatenated and deduplicated
51
74
  * - `remove`: Section keys Lisa deletes from the project (retired keys)
75
+ * - `adopt`: Section keys whose Lisa-authored values are reclaimed so
76
+ * `defaults` can install the current one
52
77
  *
53
78
  * When multiple package.lisa.json files are loaded from the inheritance chain (all → typescript → specific),
54
79
  * they are merged with child types overriding parent types in each section.
@@ -74,6 +99,9 @@ export interface RemoveSection {
74
99
  * },
75
100
  * "remove": {
76
101
  * "scripts": ["knip"]
102
+ * },
103
+ * "adopt": {
104
+ * "scripts": { "lint": ["eslint . --quiet"] }
77
105
  * }
78
106
  * }
79
107
  * ```
@@ -87,9 +115,11 @@ export interface PackageLisaTemplate {
87
115
  merge?: Record<string, unknown[]>;
88
116
  /** Section keys Lisa deletes from the project (retired keys) */
89
117
  remove?: Record<string, string[]>;
118
+ /** Section keys whose Lisa-authored values are reclaimed before defaults */
119
+ adopt?: Record<string, Record<string, string[]>>;
90
120
  }
91
121
  /**
92
- * Merged template with resolved force/defaults/merge/remove sections
122
+ * Merged template with resolved force/defaults/merge/remove/adopt sections
93
123
  * ready to be applied to a project's package.json
94
124
  */
95
125
  export interface ResolvedPackageLisaTemplate extends PackageLisaTemplate {
@@ -97,5 +127,6 @@ export interface ResolvedPackageLisaTemplate extends PackageLisaTemplate {
97
127
  defaults: Record<string, unknown>;
98
128
  merge: Record<string, unknown[]>;
99
129
  remove: Record<string, string[]>;
130
+ adopt: Record<string, Record<string, string[]>>;
100
131
  }
101
132
  //# sourceMappingURL=package-lisa-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"package-lisa-types.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAElC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAClC"}
1
+ {"version":3,"file":"package-lisa-types.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAElC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAElC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CACjD"}
@@ -10,8 +10,15 @@ import type { ICopyStrategy, StrategyContext } from "./strategy.interface.js";
10
10
  *
11
11
  * Behavior is defined in package.lisa.json:
12
12
  * - force: Lisa's values completely replace project's values
13
+ * - adopt: Lisa reclaims a key still holding a value Lisa itself wrote
13
14
  * - defaults: Project's values preserved; Lisa's used only if missing
14
15
  * - merge: Arrays concatenated and deduplicated
16
+ * - remove: Retired keys deleted from their section
17
+ *
18
+ * A governed script CI invokes is shipped as a pair — `lint:lisa` forced, and
19
+ * `lint` merely defaulted to invoke it — so the host owns the composition point
20
+ * and anything chained onto it survives an apply. See the reserved-base section
21
+ * of `wiki/documentation/specs/package-lisa-json.md`.
15
22
  *
16
23
  * Inheritance chain: all → typescript → specific types (expo, nestjs, cdk, npm-package)
17
24
  * Child types override parent values in each section.
@@ -25,7 +32,15 @@ import type { ICopyStrategy, StrategyContext } from "./strategy.interface.js";
25
32
  * - Keeps project's package.json 100% clean (no Lisa artifacts)
26
33
  */
27
34
  export declare class PackageLisaStrategy implements ICopyStrategy {
35
+ private readonly readApplyingVersion;
28
36
  readonly name: "package-lisa";
37
+ /**
38
+ * Build a strategy, optionally stating the version performing the apply.
39
+ * @param readApplyingVersion - Reports the Lisa version performing the apply.
40
+ * Injected rather than read inline so a spec can state a version outright
41
+ * instead of deriving its expectation from the code under test.
42
+ */
43
+ constructor(readApplyingVersion?: () => string);
29
44
  private readonly PACKAGE_JSON;
30
45
  private readonly TSCONFIG_JSON;
31
46
  private readonly APP_JSON;
@@ -223,6 +238,8 @@ export declare class PackageLisaStrategy implements ICopyStrategy {
223
238
  * @param projectJson - Current project's package.json
224
239
  * @param template - Merged package.lisa.json template
225
240
  * @param fileName - Basename used in error messages
241
+ * @param restricted - True when the template was reduced to security pins,
242
+ * which is the postinstall path and Lisa's own repository
226
243
  * @returns Modified package.json plus operator-visible notes
227
244
  * @private
228
245
  */
@@ -1 +1 @@
1
- {"version":3,"file":"package-lisa.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE1E,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AA6B9E;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IACvD,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IAExC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0C;IAC5E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAGhC;IAEF;;;;;;;OAOG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,aAAa,EAAE,SAAS,WAAW,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,gBAAgB,UAAQ,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAmBnC;;;;;;;;;;;;;OAaG;IACG,KAAK,CACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC;IA8D/B;;;;;;;;;OASG;YACW,iBAAiB;IAmB/B;;;;;;;;;OASG;YACW,iBAAiB;IAwC/B;;;;;;;;OAQG;YACW,gBAAgB;IAkC9B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,sBAAsB;IAmB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,wBAAwB;IAsChC;;;;;;OAMG;YACW,kBAAkB;IA0DhC;;;;;;OAMG;YACW,+BAA+B;IAkB7C;;;;;;OAMG;YACW,wBAAwB;IAgBtC;;;;;;OAMG;YACW,8BAA8B;IAmB5C;;;;;;OAMG;YACW,gBAAgB;IAW9B;;;;;;;;;OASG;YACW,qBAAqB;IAyCnC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAgB3B;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAetB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,aAAa;IA6BrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;CA2B1B"}
1
+ {"version":3,"file":"package-lisa.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE1E,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AA8B9E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IAUrD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IATtC,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IAExC;;;;;OAKG;gBAEgB,mBAAmB,GAAE,MAAM,MAA0B;IAGxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0C;IAC5E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAGhC;IAEF;;;;;;;OAOG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,aAAa,EAAE,SAAS,WAAW,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,gBAAgB,UAAQ,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAmBnC;;;;;;;;;;;;;OAaG;IACG,KAAK,CACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC;IA8D/B;;;;;;;;;OASG;YACW,iBAAiB;IAmB/B;;;;;;;;;OASG;YACW,iBAAiB;IAwC/B;;;;;;;;OAQG;YACW,gBAAgB;IAkC9B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,wBAAwB;IAsChC;;;;;;OAMG;YACW,kBAAkB;IA0DhC;;;;;;OAMG;YACW,+BAA+B;IAkB7C;;;;;;OAMG;YACW,wBAAwB;IAgBtC;;;;;;OAMG;YACW,8BAA8B;IAmB5C;;;;;;OAMG;YACW,gBAAgB;IAW9B;;;;;;;;;OASG;YACW,qBAAqB;IA0CnC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAgB3B;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,aAAa;IAkDrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;CA2B1B"}
@@ -8,6 +8,7 @@ import { ensureParentDir } from "../utils/file-operations.js";
8
8
  import { readJson, writeJson, deepMerge, readJsonOrNull, } from "../utils/json-utils.js";
9
9
  import { JsonMergeError } from "../errors/index.js";
10
10
  import { LISA_PACKAGE_NAME } from "../core/self-apply.js";
11
+ import { getPackageVersion } from "../cli/version.js";
11
12
  /**
12
13
  * @file package-lisa.ts
13
14
  * @description Package.lisa.json strategy for governance-driven package.json management
@@ -18,8 +19,15 @@ import { LISA_PACKAGE_NAME } from "../core/self-apply.js";
18
19
  *
19
20
  * Behavior is defined in package.lisa.json:
20
21
  * - force: Lisa's values completely replace project's values
22
+ * - adopt: Lisa reclaims a key still holding a value Lisa itself wrote
21
23
  * - defaults: Project's values preserved; Lisa's used only if missing
22
24
  * - merge: Arrays concatenated and deduplicated
25
+ * - remove: Retired keys deleted from their section
26
+ *
27
+ * A governed script CI invokes is shipped as a pair — `lint:lisa` forced, and
28
+ * `lint` merely defaulted to invoke it — so the host owns the composition point
29
+ * and anything chained onto it survives an apply. See the reserved-base section
30
+ * of `wiki/documentation/specs/package-lisa-json.md`.
23
31
  *
24
32
  * Inheritance chain: all → typescript → specific types (expo, nestjs, cdk, npm-package)
25
33
  * Child types override parent values in each section.
@@ -33,7 +41,17 @@ import { LISA_PACKAGE_NAME } from "../core/self-apply.js";
33
41
  * - Keeps project's package.json 100% clean (no Lisa artifacts)
34
42
  */
35
43
  export class PackageLisaStrategy {
44
+ readApplyingVersion;
36
45
  name = "package-lisa";
46
+ /**
47
+ * Build a strategy, optionally stating the version performing the apply.
48
+ * @param readApplyingVersion - Reports the Lisa version performing the apply.
49
+ * Injected rather than read inline so a spec can state a version outright
50
+ * instead of deriving its expectation from the code under test.
51
+ */
52
+ constructor(readApplyingVersion = getPackageVersion) {
53
+ this.readApplyingVersion = readApplyingVersion;
54
+ }
37
55
  PACKAGE_JSON = "package.json";
38
56
  TSCONFIG_JSON = "tsconfig.json";
39
57
  APP_JSON = "app.json";
@@ -52,10 +70,9 @@ export class PackageLisaStrategy {
52
70
  */
53
71
  async planPackageJson(projectJson, detectedTypes, lisaDir, securityPinsOnly = false) {
54
72
  const merged = await this.loadAndMergeTemplates(lisaDir, detectedTypes);
55
- const effective = securityPinsOnly || projectJson.name === LISA_PACKAGE_NAME
56
- ? this.restrictToSecurityPins(merged)
57
- : merged;
58
- const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON);
73
+ const restricted = securityPinsOnly || projectJson.name === LISA_PACKAGE_NAME;
74
+ const effective = restricted ? this.restrictToSecurityPins(merged) : merged;
75
+ const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON, restricted);
59
76
  const result = planSelfReferencingOverrideNormalization(forced.packageJson, this.PACKAGE_JSON);
60
77
  assertManifestIsInstallable(result.packageJson, this.PACKAGE_JSON);
61
78
  return result.packageJson;
@@ -190,10 +207,9 @@ export class PackageLisaStrategy {
190
207
  // Get detected project types by analyzing the project structure
191
208
  const detectedTypes = await this.detectProjectTypes(projectDir);
192
209
  const merged = await this.loadAndMergeTemplates(lisaDir, detectedTypes);
193
- const effective = securityPinsOnly || projectJson.name === LISA_PACKAGE_NAME
194
- ? this.restrictToSecurityPins(merged)
195
- : merged;
196
- const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON);
210
+ const restricted = securityPinsOnly || projectJson.name === LISA_PACKAGE_NAME;
211
+ const effective = restricted ? this.restrictToSecurityPins(merged) : merged;
212
+ const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON, restricted);
197
213
  const plan = planSelfReferencingOverrideNormalization(forced.packageJson, this.PACKAGE_JSON);
198
214
  assertManifestIsInstallable(plan.packageJson, this.PACKAGE_JSON);
199
215
  return { ...plan, notes: [...forced.notes, ...plan.notes] };
@@ -235,7 +251,10 @@ export class PackageLisaStrategy {
235
251
  }
236
252
  this.includeBackingDirectDeps(template, force);
237
253
  const remove = Object.fromEntries(OVERRIDE_SECTIONS.filter(section => template.remove[section] !== undefined).map(section => [section, template.remove[section]]));
238
- return { force, defaults: {}, merge: {}, remove };
254
+ // `adopt` reclaims a key so `defaults` can rewrite it, and `defaults` is
255
+ // dropped here — carrying it alone would delete a host script and put
256
+ // nothing back.
257
+ return { force, defaults: {}, merge: {}, remove, adopt: {} };
239
258
  }
240
259
  /**
241
260
  * For every direct-dependency override in the restricted overrides/resolutions,
@@ -410,6 +429,7 @@ export class PackageLisaStrategy {
410
429
  defaults: {},
411
430
  merge: {},
412
431
  remove: {},
432
+ adopt: {},
413
433
  };
414
434
  // Expand types to include parents (e.g., expo includes typescript)
415
435
  const allTypes = this.expandTypeHierarchy(detectedTypes);
@@ -467,6 +487,7 @@ export class PackageLisaStrategy {
467
487
  defaults: deepMerge(parent.defaults, child.defaults || {}),
468
488
  merge: this.mergeMergeSections(parent.merge, child.merge || {}),
469
489
  remove: this.mergeMergeSections(parent.remove, child.remove || {}),
490
+ adopt: mergeAdoptSections(parent.adopt, child.adopt || {}),
470
491
  };
471
492
  }
472
493
  /**
@@ -503,21 +524,40 @@ export class PackageLisaStrategy {
503
524
  * @param projectJson - Current project's package.json
504
525
  * @param template - Merged package.lisa.json template
505
526
  * @param fileName - Basename used in error messages
527
+ * @param restricted - True when the template was reduced to security pins,
528
+ * which is the postinstall path and Lisa's own repository
506
529
  * @returns Modified package.json plus operator-visible notes
507
530
  * @private
508
531
  */
509
- applyTemplate(projectJson, template, fileName) {
532
+ applyTemplate(projectJson, template, fileName, restricted = false) {
510
533
  // Phase 1: Apply force (Lisa's values completely replace project's), then
511
534
  // restore any dependency pin the host had raised ABOVE Lisa's floor.
512
535
  const afterForce = preserveHigherHostPins(projectJson, deepMerge(projectJson, template.force), fileName);
536
+ // Phase 1.5: Reclaim keys still carrying a value Lisa itself wrote, so the
537
+ // defaults phase can install the current one. A host value Lisa does not
538
+ // recognise as its own is left alone — that is the whole point.
539
+ const afterAdopt = applyAdoptSections(afterForce.packageJson, template);
513
540
  // Phase 2: Apply defaults (project's values preserved, Lisa provides fallback)
514
- const afterDefaults = deepMerge(template.defaults, afterForce.packageJson);
541
+ const afterDefaults = deepMerge(template.defaults, afterAdopt);
515
542
  // Phase 3: Apply merge (concatenate and deduplicate arrays)
516
543
  const afterMerge = this.applyMergeSections(afterDefaults, template.merge);
517
544
  // Phase 4: Apply remove (delete retired keys from their sections)
545
+ const afterRemove = this.applyRemoveSections(afterMerge, template.remove);
546
+ // Phase 5: Make the host's pin name the version that wrote these templates.
547
+ // A restricted apply is the postinstall path, where the installed package
548
+ // IS the applying version, so there is nothing to reconcile — and rewriting
549
+ // the host's manifest from inside their `install` is not this phase's to do.
550
+ const pinned = restricted
551
+ ? { packageJson: afterRemove, notes: [] }
552
+ : alignLisaPin(afterRemove, this.readApplyingVersion());
553
+ // Phase 6: Say out loud what the host lost, and which gates nothing runs.
518
554
  return {
519
- packageJson: this.applyRemoveSections(afterMerge, template.remove),
520
- notes: afterForce.notes,
555
+ packageJson: pinned.packageJson,
556
+ notes: [
557
+ ...afterForce.notes,
558
+ ...pinned.notes,
559
+ ...describeScriptChanges(projectJson, pinned.packageJson, template),
560
+ ],
521
561
  };
522
562
  }
523
563
  /**
@@ -598,6 +638,295 @@ export class PackageLisaStrategy {
598
638
  return result;
599
639
  }
600
640
  }
641
+ /**
642
+ * Dependency sections a host may declare `@codyswann/lisa` in, most-specific
643
+ * first: a runtime dependency is the unusual choice, so finding one there means
644
+ * the host meant it.
645
+ */
646
+ const LISA_PIN_SECTIONS = ["dependencies", "devDependencies"];
647
+ /** Where a pin goes on a host that does not have one yet. */
648
+ const DEFAULT_LISA_PIN_SECTION = "devDependencies";
649
+ /**
650
+ * Specs that name a LOCATION rather than a registry version.
651
+ * @remarks
652
+ * `file:` / `link:` / `portal:` / `workspace:` and the git forms all mean
653
+ * somebody is developing against a checkout instead of a release. Replacing one
654
+ * with a version number breaks that setup, so the apply reports the skew rather
655
+ * than resolving it — which is the branch the second acceptance scenario is
656
+ * about.
657
+ */
658
+ const NON_REGISTRY_SPEC = /^(?:file|link|portal|workspace|git|git\+[a-z]+|github|https?|npm):/i;
659
+ /**
660
+ * Does this spec point at a location rather than name a registry version?
661
+ * @param spec - The version spec the host declared
662
+ * @returns True when the spec resolves outside the registry
663
+ */
664
+ function isNonRegistrySpec(spec) {
665
+ return NON_REGISTRY_SPEC.test(spec);
666
+ }
667
+ /**
668
+ * Where the host declares its Lisa pin, and what it currently says.
669
+ * @param packageJson - The manifest as the merge phases left it
670
+ * @returns The section to write into and the spec already there, if any
671
+ */
672
+ function locateLisaPin(packageJson) {
673
+ const declared = LISA_PIN_SECTIONS.map(section => ({
674
+ section,
675
+ current: asRecord(packageJson[section])[LISA_PACKAGE_NAME],
676
+ })).find(found => typeof found.current === "string");
677
+ return declared === undefined
678
+ ? { section: DEFAULT_LISA_PIN_SECTION, current: undefined }
679
+ : { section: declared.section, current: declared.current };
680
+ }
681
+ /**
682
+ * Make the host's `@codyswann/lisa` pin name the version doing the applying.
683
+ * @remarks
684
+ * An apply writes templates that call into the package's own API, so the
685
+ * applied version and the INSTALLED version are two halves of one thing. When
686
+ * they drift, a config file calls an export the installed package does not have
687
+ * and every run of the tool that loads it dies at config load — while the apply
688
+ * itself reports success, and `postinstall`'s `[ -d dist/configs ] || tsc ||
689
+ * true` swallows the only local signal. The failure then surfaces at the next
690
+ * lint run, detached from the apply that caused it (#2953).
691
+ *
692
+ * A range is rewritten as readily as an exact pin, and deliberately so: a caret
693
+ * range ADMITS the applying version without requiring it, so a lockfile still
694
+ * resolving an older build produces exactly the skew this closes.
695
+ *
696
+ * Lisa applying to its own repository never reaches here: that path is
697
+ * restricted to security pins, and a package cannot depend on itself.
698
+ * @param packageJson - The manifest as the merge phases left it
699
+ * @param applyingVersion - Version of the Lisa performing this apply
700
+ * @returns The manifest with the pin aligned, plus operator-visible notes
701
+ */
702
+ function alignLisaPin(packageJson, applyingVersion) {
703
+ const { section, current } = locateLisaPin(packageJson);
704
+ if (current === applyingVersion)
705
+ return { packageJson, notes: [] };
706
+ if (current !== undefined && isNonRegistrySpec(current)) {
707
+ return {
708
+ packageJson,
709
+ notes: [
710
+ `Left ${LISA_PACKAGE_NAME} at "${current}", which points at a local copy rather than a release, but this apply is ${applyingVersion}. If that copy is older, the files just written may call something it does not have and every lint run will fail before it checks anything.`,
711
+ ],
712
+ };
713
+ }
714
+ const note = current === undefined
715
+ ? `Added ${LISA_PACKAGE_NAME} ${applyingVersion} to ${section}. The files this apply just wrote come from ${applyingVersion} and call into it, so install it before your next lint run.`
716
+ : `Pinned ${LISA_PACKAGE_NAME} to ${applyingVersion}; it was ${current}. The files this apply just wrote come from ${applyingVersion} and call into it, so the two have to be the same version — install it before your next lint run.`;
717
+ return {
718
+ packageJson: {
719
+ ...packageJson,
720
+ [section]: {
721
+ ...asRecord(packageJson[section]),
722
+ [LISA_PACKAGE_NAME]: applyingVersion,
723
+ },
724
+ },
725
+ notes: [note],
726
+ };
727
+ }
728
+ /** The package.json section whose overwrites are reported to the operator. */
729
+ const SCRIPTS_SECTION = "scripts";
730
+ /**
731
+ * Suffix naming the Lisa-owned half of a split script.
732
+ *
733
+ * A governed gate is shipped as a PAIR: `lint:lisa` carries Lisa's own command
734
+ * and stays in `force`, so a host can neither delete nor weaken it; `lint` is
735
+ * only a `defaults` entry invoking it, so the host owns the composition point
736
+ * and anything chained onto it survives every apply.
737
+ */
738
+ const RESERVED_BASE_SUFFIX = ":lisa";
739
+ /** How much of a script value an operator note quotes before eliding. */
740
+ const NOTE_VALUE_BUDGET = 140;
741
+ /**
742
+ * Merge two `adopt` sections, taking the UNION of the recognised values.
743
+ * @remarks
744
+ * Union, not child-overrides-parent as `force` and `defaults` use. Every entry
745
+ * is a value Lisa is known to have written, and a host may have taken any of
746
+ * them from any layer of the chain it has passed through. Dropping the parent's
747
+ * list would stop recognising a value Lisa really did author, and the cost of
748
+ * that is not cosmetic: the host gets warned that it customised something it
749
+ * never touched, and stops tracking the template.
750
+ * @param parent - Parent template's adopt section
751
+ * @param child - Child template's adopt section
752
+ * @returns Per-section, per-key union of the two, order-preserving and deduped
753
+ */
754
+ function mergeAdoptSections(parent, child) {
755
+ const sections = new Set([...Object.keys(parent), ...Object.keys(child)]);
756
+ return Object.fromEntries(Array.from(sections).map(section => {
757
+ const parentKeys = parent[section] ?? {};
758
+ const childKeys = child[section] ?? {};
759
+ const keys = new Set([
760
+ ...Object.keys(parentKeys),
761
+ ...Object.keys(childKeys),
762
+ ]);
763
+ return [
764
+ section,
765
+ Object.fromEntries(Array.from(keys).map(key => [
766
+ key,
767
+ Array.from(new Set([...(parentKeys[key] ?? []), ...(childKeys[key] ?? [])])),
768
+ ])),
769
+ ];
770
+ }));
771
+ }
772
+ /**
773
+ * Drop every key still carrying a value Lisa itself wrote.
774
+ * @remarks
775
+ * The deletion is what lets the `defaults` phase, which never overwrites, reach
776
+ * a key Lisa used to force. Nothing else is touched: a value absent from the
777
+ * adopt list is the host's own work by definition, and keeping it is the entire
778
+ * behaviour this exists to provide.
779
+ * @param packageJson - The document as the force phase left it
780
+ * @param template - Resolved template carrying the adopt section
781
+ * @returns The document with Lisa-authored values cleared
782
+ */
783
+ function applyAdoptSections(packageJson, template) {
784
+ return Object.entries(template.adopt).reduce((document, [sectionName, recognised]) => {
785
+ const section = document[sectionName];
786
+ if (section === null ||
787
+ typeof section !== "object" ||
788
+ Array.isArray(section)) {
789
+ return document;
790
+ }
791
+ // A key the template also FORCES already holds Lisa's current value.
792
+ // Clearing it would delete what force just wrote and leave the key to
793
+ // whatever `defaults` happens to carry — so force wins, and adopt is a
794
+ // no-op there. Adopt only has meaning for a key Lisa has handed back.
795
+ const forcedHere = asRecord(asRecord(template.force)[sectionName]);
796
+ const entries = Object.entries(section);
797
+ const kept = entries.filter(([key, value]) => typeof value !== "string" ||
798
+ key in forcedHere ||
799
+ !(recognised[key] ?? []).includes(value));
800
+ if (kept.length === entries.length) {
801
+ return document;
802
+ }
803
+ return { ...document, [sectionName]: Object.fromEntries(kept) };
804
+ }, packageJson);
805
+ }
806
+ /**
807
+ * Quote a script value for an operator note without flooding the terminal.
808
+ * @param value - The script value being quoted
809
+ * @returns The value, elided past the note budget
810
+ */
811
+ function quoteScript(value) {
812
+ return value.length <= NOTE_VALUE_BUDGET
813
+ ? `"${value}"`
814
+ : `"${value.slice(0, NOTE_VALUE_BUDGET)}…"`;
815
+ }
816
+ /**
817
+ * Report what an apply did to the host's scripts, and what it left inert.
818
+ * @remarks
819
+ * The defect this answers was invisible rather than wrong-looking. One script
820
+ * value changed inside a `package.json` diff dominated by key reordering, and
821
+ * nothing said so, so five chained CI gates became dead code while the Lint
822
+ * check kept reporting green.
823
+ *
824
+ * Every key of the host's `scripts` is walked. Deliberately not a curated list
825
+ * of interesting names: the review that nearly shipped this defect compared a
826
+ * GUESSED subset and concluded "ordering only". A subset is not a method.
827
+ * @param projectJson - The host manifest as it was before the apply
828
+ * @param packageJson - The manifest the apply is about to write
829
+ * @param template - Resolved template, for the reserved-base pairing
830
+ * @returns Operator-readable lines, empty when nothing was lost
831
+ */
832
+ function describeScriptChanges(projectJson, packageJson, template) {
833
+ const before = asRecord(projectJson[SCRIPTS_SECTION]);
834
+ const after = asRecord(packageJson[SCRIPTS_SECTION]);
835
+ const adopted = asRecord(template.adopt[SCRIPTS_SECTION]);
836
+ return [
837
+ ...describeOverwrittenScripts(before, after, adopted),
838
+ ...describeUnrunGates(after, template),
839
+ ];
840
+ }
841
+ /**
842
+ * Was this host value one Lisa itself wrote into that key?
843
+ * @param adopted - The resolved adopt list for the scripts section
844
+ * @param name - Script name being reported on
845
+ * @param hostValue - The value the host carried before the apply
846
+ * @returns True when the value is Lisa's own rather than the host's work
847
+ */
848
+ function isLisaAuthored(adopted, name, hostValue) {
849
+ const recognised = adopted[name];
850
+ return Array.isArray(recognised) && recognised.includes(hostValue);
851
+ }
852
+ /**
853
+ * Name every host script value this apply replaced or deleted.
854
+ * @remarks
855
+ * A value on the `adopt` list is Lisa's own, so replacing it discards nothing
856
+ * of the host's and must not be reported as a loss. That is not cosmetic: the
857
+ * split hands six gate names back at once, so loss-shaped wording there puts
858
+ * six false alarms in front of every operator on their first upgrade and a
859
+ * REAL loss stops standing out — the precise failure this change exists to
860
+ * end. Those keys get a handover line instead, because the operator does need
861
+ * to learn that the composition point is now theirs to extend.
862
+ * @param before - The host's scripts before the apply
863
+ * @param after - The scripts the apply is about to write
864
+ * @param adopted - Resolved adopt list, naming the values Lisa authored
865
+ * @returns One line per script whose host value did not survive
866
+ */
867
+ function describeOverwrittenScripts(before, after, adopted) {
868
+ return Object.entries(before).flatMap(([name, hostValue]) => {
869
+ if (typeof hostValue !== "string")
870
+ return [];
871
+ const applied = after[name];
872
+ if (applied === hostValue)
873
+ return [];
874
+ if (applied === undefined) {
875
+ return [
876
+ `Removed scripts.${name}; it ran ${quoteScript(hostValue)}. Nothing in your project runs that any more.`,
877
+ ];
878
+ }
879
+ if (typeof applied !== "string")
880
+ return [];
881
+ const base = `${name}${RESERVED_BASE_SUFFIX}`;
882
+ // The handover wording names the reserved base, so it is only truthful
883
+ // when the value being written actually invokes one.
884
+ if (isLisaAuthored(adopted, name, hostValue) && applied.includes(base)) {
885
+ return [
886
+ `Moved Lisa's ${name} checks into scripts.${base}; scripts.${name} now calls it, so anything you add there survives the next apply.`,
887
+ ];
888
+ }
889
+ return [
890
+ `Replaced scripts.${name}: it ran ${quoteScript(hostValue)} and now runs ${quoteScript(applied)}.`,
891
+ ];
892
+ });
893
+ }
894
+ /**
895
+ * Name every Lisa gate the host's own composition point does not run.
896
+ * @remarks
897
+ * `lint:lisa` being force-installed proves the gate EXISTS; it proves nothing
898
+ * about whether anything invokes it, and CI invokes `lint`. A host is free to
899
+ * decline a gate, but declining it silently is the failure mode this whole
900
+ * change is about, so the apply says which gate went unrun.
901
+ *
902
+ * A composition point that inlines Lisa's current base verbatim — the shape
903
+ * every host was left in before the split existed — does run the gate, so it is
904
+ * not warned about. It is nudged instead: an inlined copy stops tracking the
905
+ * template the next time the base changes.
906
+ * @param after - The scripts the apply is about to write
907
+ * @param template - Resolved template carrying the forced reserved bases
908
+ * @returns One line per gate nothing invokes, plus migration nudges
909
+ */
910
+ function describeUnrunGates(after, template) {
911
+ const forcedScripts = asRecord(asRecord(template.force)[SCRIPTS_SECTION]);
912
+ return Object.keys(forcedScripts).flatMap(base => {
913
+ if (!base.endsWith(RESERVED_BASE_SUFFIX))
914
+ return [];
915
+ const composed = base.slice(0, -RESERVED_BASE_SUFFIX.length);
916
+ const hostValue = after[composed];
917
+ if (typeof hostValue !== "string" || hostValue.includes(base))
918
+ return [];
919
+ const baseValue = forcedScripts[base];
920
+ if (typeof baseValue === "string" && hostValue.includes(baseValue)) {
921
+ return [
922
+ `Kept your scripts.${composed}. It spells out Lisa's ${composed} checks instead of calling ${base}, so it will not pick up changes to them; run ${base} from it to stay current.`,
923
+ ];
924
+ }
925
+ return [
926
+ `Kept your scripts.${composed}, but nothing invokes ${base}, so Lisa's ${composed} checks do not run. Add ${base} to scripts.${composed} to turn them back on.`,
927
+ ];
928
+ });
929
+ }
601
930
  /** package.json sections whose keys are treated as direct dependencies. */
602
931
  const DIRECT_DEPENDENCY_SECTIONS = [
603
932
  "dependencies",