@codyswann/lisa 2.297.4 → 2.297.6

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 (137) hide show
  1. package/all/copy-contents/.gitattributes +9 -0
  2. package/dist/cli/gate-commands.d.ts +22 -3
  3. package/dist/cli/gate-commands.d.ts.map +1 -1
  4. package/dist/cli/gate-commands.js +49 -1
  5. package/dist/cli/gate-commands.js.map +1 -1
  6. package/dist/cli/install-merge-driver-cmd.d.ts +21 -0
  7. package/dist/cli/install-merge-driver-cmd.d.ts.map +1 -0
  8. package/dist/cli/install-merge-driver-cmd.js +37 -0
  9. package/dist/cli/install-merge-driver-cmd.js.map +1 -0
  10. package/dist/cli/merge-learnings-cmd.d.ts +24 -0
  11. package/dist/cli/merge-learnings-cmd.d.ts.map +1 -0
  12. package/dist/cli/merge-learnings-cmd.js +82 -0
  13. package/dist/cli/merge-learnings-cmd.js.map +1 -0
  14. package/dist/core/learnings-budget-check.d.ts.map +1 -1
  15. package/dist/core/learnings-budget-check.js +18 -3
  16. package/dist/core/learnings-budget-check.js.map +1 -1
  17. package/dist/core/learnings-document.d.ts +46 -0
  18. package/dist/core/learnings-document.d.ts.map +1 -1
  19. package/dist/core/learnings-document.js +82 -4
  20. package/dist/core/learnings-document.js.map +1 -1
  21. package/dist/core/learnings-file-safety.d.ts.map +1 -1
  22. package/dist/core/learnings-file-safety.js +22 -0
  23. package/dist/core/learnings-file-safety.js.map +1 -1
  24. package/dist/core/learnings-merge-driver-config.d.ts +7 -0
  25. package/dist/core/learnings-merge-driver-config.d.ts.map +1 -0
  26. package/dist/core/learnings-merge-driver-config.js +58 -0
  27. package/dist/core/learnings-merge-driver-config.js.map +1 -0
  28. package/dist/core/learnings-merge-driver-install.d.ts +45 -0
  29. package/dist/core/learnings-merge-driver-install.d.ts.map +1 -0
  30. package/dist/core/learnings-merge-driver-install.js +161 -0
  31. package/dist/core/learnings-merge-driver-install.js.map +1 -0
  32. package/dist/core/learnings-merge-driver.d.ts +83 -0
  33. package/dist/core/learnings-merge-driver.d.ts.map +1 -0
  34. package/dist/core/learnings-merge-driver.js +100 -0
  35. package/dist/core/learnings-merge-driver.js.map +1 -0
  36. package/dist/core/learnings-merge.d.ts +23 -0
  37. package/dist/core/learnings-merge.d.ts.map +1 -0
  38. package/dist/core/learnings-merge.js +198 -0
  39. package/dist/core/learnings-merge.js.map +1 -0
  40. package/dist/core/learnings-writer.d.ts +6 -0
  41. package/dist/core/learnings-writer.d.ts.map +1 -1
  42. package/dist/core/learnings-writer.js +32 -65
  43. package/dist/core/learnings-writer.js.map +1 -1
  44. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  45. package/dist/core/upstream-evidence-manifest.js +27 -4
  46. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  47. package/dist/health/storage.d.ts.map +1 -1
  48. package/dist/health/storage.js +7 -20
  49. package/dist/health/storage.js.map +1 -1
  50. package/dist/migrations/ensure-learnings-gitattributes.d.ts +43 -0
  51. package/dist/migrations/ensure-learnings-gitattributes.d.ts.map +1 -0
  52. package/dist/migrations/ensure-learnings-gitattributes.js +93 -0
  53. package/dist/migrations/ensure-learnings-gitattributes.js.map +1 -0
  54. package/dist/migrations/ensure-learnings-merge-driver.d.ts +46 -0
  55. package/dist/migrations/ensure-learnings-merge-driver.d.ts.map +1 -0
  56. package/dist/migrations/ensure-learnings-merge-driver.js +70 -0
  57. package/dist/migrations/ensure-learnings-merge-driver.js.map +1 -0
  58. package/dist/migrations/index.d.ts +2 -0
  59. package/dist/migrations/index.d.ts.map +1 -1
  60. package/dist/migrations/index.js +6 -0
  61. package/dist/migrations/index.js.map +1 -1
  62. package/dist/standards/storage.d.ts.map +1 -1
  63. package/dist/standards/storage.js +7 -20
  64. package/dist/standards/storage.js.map +1 -1
  65. package/dist/utils/atomic-file-write.d.ts +30 -0
  66. package/dist/utils/atomic-file-write.d.ts.map +1 -0
  67. package/dist/utils/atomic-file-write.js +61 -0
  68. package/dist/utils/atomic-file-write.js.map +1 -0
  69. package/dist/utils/directory-sync.d.ts.map +1 -0
  70. package/dist/utils/directory-sync.js.map +1 -0
  71. package/package.json +1 -1
  72. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
  75. package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +1 -0
  76. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
  77. package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +1 -0
  78. package/plugins/lisa-agy/plugin.json +1 -1
  79. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
  80. package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +1 -0
  81. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  84. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
  88. package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +1 -0
  89. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
  91. package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +1 -0
  92. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-expo-agy/plugin.json +1 -1
  95. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  100. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  105. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  109. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  110. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  114. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  115. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  119. package/plugins/lisa-rails-agy/plugin.json +1 -1
  120. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  124. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  125. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  129. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  130. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  132. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
  133. package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +1 -0
  134. package/dist/health/directory-sync.d.ts.map +0 -1
  135. package/dist/health/directory-sync.js.map +0 -1
  136. /package/dist/{health → utils}/directory-sync.d.ts +0 -0
  137. /package/dist/{health → utils}/directory-sync.js +0 -0
@@ -3,6 +3,7 @@ import * as fse from "fs-extra";
3
3
  import { lstat, open, realpath } from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { LEARNINGS_CONTRACT } from "./learnings-contract.js";
6
+ import { conflictMarkerError, findConflictMarkerInBytes, } from "./learnings-document.js";
6
7
  /**
7
8
  * Resolve the configured target without permitting project-root escape.
8
9
  * @param projectRoot - Host project root
@@ -37,6 +38,12 @@ export async function readExistingLearnings(target) {
37
38
  throw new Error("Unsafe project learnings path: target changed during open");
38
39
  }
39
40
  if (opened.size > LEARNINGS_CONTRACT.maxTokens) {
41
+ // Diagnose conflict corruption before reporting size. A conflicted
42
+ // merge roughly doubles the ledger, so this branch is exactly where a
43
+ // real corruption lands — reporting "exceeds maxTokens" here would
44
+ // send the writer's caller off shortening entries instead of
45
+ // recompacting the duplicated block.
46
+ await assertNoConflictMarkerPrefix(handle);
40
47
  throw new Error(`Project learnings payload exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens}`);
41
48
  }
42
49
  return handle.readFile({ encoding: "utf8" });
@@ -52,6 +59,21 @@ export async function readExistingLearnings(target) {
52
59
  throw error;
53
60
  }
54
61
  }
62
+ /**
63
+ * Throw the shared conflict diagnosis when an over-budget file is corrupted.
64
+ *
65
+ * Reads only a bounded prefix from the already-verified handle, so an oversized
66
+ * ledger is diagnosed without ever being fully loaded.
67
+ * @param handle - Open handle to the verified regular file
68
+ */
69
+ async function assertNoConflictMarkerPrefix(handle) {
70
+ const buffer = Buffer.allocUnsafe(LEARNINGS_CONTRACT.maxTokens + 1);
71
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
72
+ const conflictLine = findConflictMarkerInBytes(buffer.subarray(0, bytesRead));
73
+ if (conflictLine !== undefined) {
74
+ throw conflictMarkerError(conflictLine);
75
+ }
76
+ }
55
77
  /**
56
78
  * Reject parent-directory symlinks that resolve outside the project root.
57
79
  * @param root - Resolved project root
@@ -1 +1 @@
1
- {"version":3,"file":"learnings-file-safety.js","sourceRoot":"","sources":["../../src/core/learnings-file-safety.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"learnings-file-safety.js","sourceRoot":"","sources":["../../src/core/learnings-file-safety.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAmB,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,WAAmB,EACnB,YAAoB;IAEpB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAc;IAEd,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,6DAA6D,CAC9D,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACnC,IACE,CAAC,MAAM,CAAC,MAAM,EAAE;gBAChB,MAAM,CAAC,GAAG,KAAK,UAAU,CAAC,GAAG;gBAC7B,MAAM,CAAC,GAAG,KAAK,UAAU,CAAC,GAAG,EAC7B,CAAC;gBACD,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;YACJ,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,GAAG,kBAAkB,CAAC,SAAS,EAAE,CAAC;gBAC/C,mEAAmE;gBACnE,sEAAsE;gBACtE,mEAAmE;gBACnE,6DAA6D;gBAC7D,qCAAqC;gBACrC,MAAM,4BAA4B,CAAC,MAAM,CAAC,CAAC;gBAC3C,MAAM,IAAI,KAAK,CACb,+CAA+C,kBAAkB,CAAC,SAAS,EAAE,CAC9E,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,4BAA4B,CAAC,MAAkB;IAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;IACpE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACrE,MAAM,YAAY,GAAG,yBAAyB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9E,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAAY,EACZ,MAAc;IAEd,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1C,IACE,UAAU,KAAK,QAAQ;QACvB,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAChD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,oBAAoB,CAAC,SAAiB;IACnD,IAAI,MAAM,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether this project accepts the merge-driver registration.
3
+ * @param projectRoot - Host project directory
4
+ * @returns False only when `learnings.mergeDriver` is explicitly `false`
5
+ */
6
+ export declare function isLearningsMergeDriverEnabled(projectRoot: string): Promise<boolean>;
7
+ //# sourceMappingURL=learnings-merge-driver-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge-driver-config.d.ts","sourceRoot":"","sources":["../../src/core/learnings-merge-driver-config.ts"],"names":[],"mappings":"AA6CA;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAQlB"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Host opt-out for the project-learnings union merge driver.
3
+ *
4
+ * `lisa apply` registers the driver automatically, and the TypeScript stack
5
+ * wires `lisa apply` into `postinstall` — so an ordinary `npm install` writes
6
+ * `merge.lisa-learnings.driver` into `.git/config` with no separate human step.
7
+ * That is not an escalation over `npm install` itself, which already runs
8
+ * arbitrary code, but it does persist an executable hook that fires on ordinary
9
+ * git operations for as long as the clone exists, outside npm's lifecycle. A
10
+ * host that would rather not carry that hook needs a way to say so.
11
+ *
12
+ * This reads `.lisa.config.json` directly rather than extending the validated
13
+ * `ProjectConfig` schema: `project-config.ts` sits exactly at its 300-line lint
14
+ * cap, and forcing an extraction of its cross-cutting learnings validators to
15
+ * make room would be a far larger change than this flag warrants. Keeping the
16
+ * flag beside the feature it governs also means the driver's opt-out is
17
+ * readable in one place.
18
+ *
19
+ * Opt-out only, and strictly `=== false`: absent, malformed, or non-boolean
20
+ * values all leave the driver enabled, matching the zero-configuration default
21
+ * every other Lisa guardrail uses. A malformed config must not silently disable
22
+ * a data-integrity guard.
23
+ * @module core/learnings-merge-driver-config
24
+ */
25
+ import { readFile } from "node:fs/promises";
26
+ import * as path from "node:path";
27
+ const CONFIG_FILE = ".lisa.config.json";
28
+ /**
29
+ * Read one inert own property without invoking an accessor.
30
+ * @param candidate - Value to inspect
31
+ * @param key - Property name to read
32
+ * @returns The own data value, when present
33
+ */
34
+ function readOwn(candidate, key) {
35
+ if (candidate === null || typeof candidate !== "object") {
36
+ return undefined;
37
+ }
38
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
39
+ return descriptor !== undefined && "value" in descriptor
40
+ ? descriptor.value
41
+ : undefined;
42
+ }
43
+ /**
44
+ * Whether this project accepts the merge-driver registration.
45
+ * @param projectRoot - Host project directory
46
+ * @returns False only when `learnings.mergeDriver` is explicitly `false`
47
+ */
48
+ export async function isLearningsMergeDriverEnabled(projectRoot) {
49
+ try {
50
+ const raw = await readFile(path.join(projectRoot, CONFIG_FILE), "utf8");
51
+ const learnings = readOwn(JSON.parse(raw), "learnings");
52
+ return readOwn(learnings, "mergeDriver") !== false;
53
+ }
54
+ catch {
55
+ return true;
56
+ }
57
+ }
58
+ //# sourceMappingURL=learnings-merge-driver-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge-driver-config.js","sourceRoot":"","sources":["../../src/core/learnings-merge-driver-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC;;;;;GAKG;AACH,SAAS,OAAO,CAAC,SAAkB,EAAE,GAAW;IAC9C,IAAI,SAAS,KAAK,IAAI,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QACxD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACnE,OAAO,UAAU,KAAK,SAAS,IAAI,OAAO,IAAI,UAAU;QACtD,CAAC,CAAC,UAAU,CAAC,KAAK;QAClB,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QACxE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,EAAE,WAAW,CAAC,CAAC;QACnE,OAAO,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,KAAK,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Outcome of one registration attempt.
3
+ *
4
+ * `skipped` and `failed` are deliberately distinct: a directory that is not a
5
+ * git repository is a benign, expected non-event, while a git-config write that
6
+ * genuinely failed must not be reported as success — an operator who ran
7
+ * `lisa install-merge-driver` and saw exit 0 would believe the ledger is
8
+ * protected when it is not.
9
+ */
10
+ export interface MergeDriverInstallResult {
11
+ /** Registered now, already correct, benignly inapplicable, or a real failure. */
12
+ readonly kind: "installed" | "unchanged" | "skipped" | "failed";
13
+ /** Single-line, operator-facing explanation. */
14
+ readonly detail: string;
15
+ }
16
+ /** Injectable collaborators for {@link installLearningsMergeDriver}. */
17
+ export interface MergeDriverInstallDependencies {
18
+ /**
19
+ * How to invoke the Lisa CLI. Defaults to this process's own runtime and
20
+ * entry script, which is what makes the registration self-locating: whichever
21
+ * Lisa is running the install is the Lisa the driver will call.
22
+ */
23
+ readonly invocation?: string;
24
+ }
25
+ /**
26
+ * Whether this directory is a git working tree the driver can be registered in.
27
+ *
28
+ * Strictly read-only, so callers that must not mutate — a migration's
29
+ * `applies()` check and its dry-run path — can ask the question without
30
+ * writing anything.
31
+ * @param projectRoot - Project directory to inspect
32
+ * @returns True when the directory is inside a git working tree
33
+ */
34
+ export declare function canInstallLearningsMergeDriver(projectRoot: string): Promise<boolean>;
35
+ /**
36
+ * Register the union merge driver in one project's local git config.
37
+ *
38
+ * Idempotent and safe outside a git repository: a non-repository directory is
39
+ * skipped rather than failing an apply.
40
+ * @param projectRoot - Project directory to register the driver in
41
+ * @param dependencies - Injectable collaborators for tests
42
+ * @returns Structured registration outcome
43
+ */
44
+ export declare function installLearningsMergeDriver(projectRoot: string, dependencies?: MergeDriverInstallDependencies): Promise<MergeDriverInstallResult>;
45
+ //# sourceMappingURL=learnings-merge-driver-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge-driver-install.d.ts","sourceRoot":"","sources":["../../src/core/learnings-merge-driver-install.ts"],"names":[],"mappings":"AAiCA;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAwB;IACvC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;IAChE,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAuFD;;;;;;;;GAQG;AACH,wBAAsB,8BAA8B,CAClD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,8BAAmC,GAChD,OAAO,CAAC,wBAAwB,CAAC,CAkDnC"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Machine-local registration of the project-learnings union merge driver.
3
+ *
4
+ * Git never reads a driver command from the repository — that would let a
5
+ * cloned repository execute arbitrary code on `git merge` — so the command must
6
+ * be written into local git config on each machine. This module is the single
7
+ * implementation behind both registration paths: the `EnsureLearningsMergeDriver`
8
+ * migration that runs on every `lisa apply`, and the explicit
9
+ * `lisa install-merge-driver` command an operator can re-run by hand.
10
+ * @module core/learnings-merge-driver-install
11
+ */
12
+ import { LEARNINGS_MERGE_DRIVER_DESCRIPTION, LEARNINGS_MERGE_DRIVER_NAME, buildLearningsMergeDriverCommand, } from "./learnings-merge-driver.js";
13
+ const GIT_COMMAND_ENV = {
14
+ PATH: "/usr/bin:/bin:/usr/sbin:/sbin",
15
+ };
16
+ const DRIVER_KEY = `merge.${LEARNINGS_MERGE_DRIVER_NAME}.driver`;
17
+ const NAME_KEY = `merge.${LEARNINGS_MERGE_DRIVER_NAME}.name`;
18
+ const NOT_REGISTERED = "merge driver not registered";
19
+ const NO_REPO = "not-a-repository";
20
+ /**
21
+ * Run a fixed git command with arguments, never through a shell.
22
+ *
23
+ * `execFile` (not `exec`) keeps every argument a literal — the driver command
24
+ * contains `%` placeholders and may contain spaces, and shell quoting is an
25
+ * avoidable failure mode. `child_process` is imported dynamically to match the
26
+ * house pattern that keeps the static command-path lint satisfied.
27
+ * @param args - Literal git arguments
28
+ * @param cwd - Working directory to run in
29
+ * @returns Trimmed stdout, or undefined when the command exits non-zero
30
+ */
31
+ async function tryGit(args, cwd) {
32
+ const { execFile } = await import("node:child_process");
33
+ const { promisify } = await import("node:util");
34
+ const run = promisify(execFile);
35
+ try {
36
+ const { stdout } = await run("git", [...args], {
37
+ cwd,
38
+ env: GIT_COMMAND_ENV,
39
+ });
40
+ return { ok: true, stdout: stdout.trim() };
41
+ }
42
+ catch (error) {
43
+ const code = typeof error === "object" && error !== null && "code" in error
44
+ ? error.code
45
+ : undefined;
46
+ return { ok: false, missingGit: code === "ENOENT" };
47
+ }
48
+ }
49
+ /**
50
+ * Probe whether this directory is a registrable git working tree.
51
+ *
52
+ * Distinguishes a missing git binary from a real non-repository. The pinned
53
+ * PATH is deliberate hardening, but on a host where git lives outside it, an
54
+ * undifferentiated failure would tell the operator "not a git repository" about
55
+ * a directory that plainly is one.
56
+ * @param projectRoot - Project directory to inspect
57
+ * @returns Closed probe outcome
58
+ */
59
+ async function probeRepository(projectRoot) {
60
+ const result = await tryGit(["rev-parse", "--is-inside-work-tree"], projectRoot);
61
+ if (!result.ok) {
62
+ return result.missingGit ? "git-unavailable" : NO_REPO;
63
+ }
64
+ return result.stdout === "true" ? "repository" : NO_REPO;
65
+ }
66
+ /**
67
+ * Quote one path for the POSIX shell that git runs a merge driver through.
68
+ *
69
+ * Single quotes, not double: git executes `merge.<name>.driver` via `sh`, where
70
+ * `$`, a backtick, and a backslash stay special inside double quotes. A path
71
+ * containing `$` would silently expand to nothing and the driver would fail to
72
+ * launch; a backtick would run a command substitution. Inside single quotes the
73
+ * shell treats every byte literally, so only an embedded single quote needs
74
+ * handling — closed, escaped, and reopened as `'\''`.
75
+ * @param value - Raw filesystem path
76
+ * @returns Shell-safe single-quoted token
77
+ */
78
+ function shellQuote(value) {
79
+ return `'${value.replaceAll("'", "'\\''")}'`;
80
+ }
81
+ /**
82
+ * Resolve how to re-invoke the currently running Lisa CLI.
83
+ *
84
+ * Uses `process.execPath` as the runtime rather than a hardcoded `node`, so a
85
+ * checkout running under Bun registers a Bun-executable command and a published
86
+ * package running under Node registers a Node one. Returns undefined when the
87
+ * entry script is unknown: registering a command that can never launch is worse
88
+ * than leaving git on its default driver, which at least fails visibly.
89
+ * @returns Shell-safe `<runtime> <entry script>` invocation, when resolvable
90
+ */
91
+ function resolveLisaInvocation() {
92
+ const entry = process.argv[1];
93
+ if (entry === undefined || entry === "") {
94
+ return undefined;
95
+ }
96
+ return `${shellQuote(process.execPath)} ${shellQuote(entry)}`;
97
+ }
98
+ /**
99
+ * Whether this directory is a git working tree the driver can be registered in.
100
+ *
101
+ * Strictly read-only, so callers that must not mutate — a migration's
102
+ * `applies()` check and its dry-run path — can ask the question without
103
+ * writing anything.
104
+ * @param projectRoot - Project directory to inspect
105
+ * @returns True when the directory is inside a git working tree
106
+ */
107
+ export async function canInstallLearningsMergeDriver(projectRoot) {
108
+ return (await probeRepository(projectRoot)) === "repository";
109
+ }
110
+ /**
111
+ * Register the union merge driver in one project's local git config.
112
+ *
113
+ * Idempotent and safe outside a git repository: a non-repository directory is
114
+ * skipped rather than failing an apply.
115
+ * @param projectRoot - Project directory to register the driver in
116
+ * @param dependencies - Injectable collaborators for tests
117
+ * @returns Structured registration outcome
118
+ */
119
+ export async function installLearningsMergeDriver(projectRoot, dependencies = {}) {
120
+ const probe = await probeRepository(projectRoot);
121
+ if (probe === "git-unavailable") {
122
+ return {
123
+ kind: "failed",
124
+ detail: `git executable not found — ${NOT_REGISTERED} (install git, then re-run \`lisa install-merge-driver\`)`,
125
+ };
126
+ }
127
+ if (probe === NO_REPO) {
128
+ return {
129
+ kind: "skipped",
130
+ detail: "not a git repository — nothing to register",
131
+ };
132
+ }
133
+ const invocation = dependencies.invocation ?? resolveLisaInvocation();
134
+ if (invocation === undefined) {
135
+ return {
136
+ kind: "failed",
137
+ detail: `could not resolve the Lisa entry point — ${NOT_REGISTERED}`,
138
+ };
139
+ }
140
+ const desired = buildLearningsMergeDriverCommand(invocation);
141
+ const current = await tryGit(["config", "--local", "--get", DRIVER_KEY], projectRoot);
142
+ if (current.ok && current.stdout === desired) {
143
+ return {
144
+ kind: "unchanged",
145
+ detail: `${LEARNINGS_MERGE_DRIVER_NAME} merge driver already registered`,
146
+ };
147
+ }
148
+ await tryGit(["config", "--local", NAME_KEY, LEARNINGS_MERGE_DRIVER_DESCRIPTION], projectRoot);
149
+ const set = await tryGit(["config", "--local", DRIVER_KEY, desired], projectRoot);
150
+ if (!set.ok) {
151
+ return {
152
+ kind: "failed",
153
+ detail: `could not write local git config — ${NOT_REGISTERED}`,
154
+ };
155
+ }
156
+ return {
157
+ kind: "installed",
158
+ detail: `registered the ${LEARNINGS_MERGE_DRIVER_NAME} merge driver`,
159
+ };
160
+ }
161
+ //# sourceMappingURL=learnings-merge-driver-install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge-driver-install.js","sourceRoot":"","sources":["../../src/core/learnings-merge-driver-install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Registration contract for the project-learnings union merge driver.
3
+ *
4
+ * A git merge driver has two halves that live in different places:
5
+ *
6
+ * 1. **The attribute** (`.gitattributes`) says WHICH driver a path uses. It is
7
+ * a committed file, so Lisa ships it to the repository and to every host
8
+ * project through the template pipeline.
9
+ * 2. **The driver command** (`merge.<name>.driver`) says HOW to run it. Git
10
+ * deliberately keeps this in machine-local config and never reads it from
11
+ * the repository, because a committed driver command would let a cloned
12
+ * repository execute arbitrary code on `git merge`.
13
+ *
14
+ * So the attribute alone is inert. Until the command is registered, git falls
15
+ * back to its built-in text merge — which is exactly today's behavior, so an
16
+ * unregistered checkout is degraded, never broken. That fallback is asserted in
17
+ * `tests/unit/core/learnings-merge-driver.test.ts`.
18
+ *
19
+ * Registration is AUTOMATIC, not operator-initiated. `EnsureLearningsMergeDriver`
20
+ * runs on every `lisa apply`, and the TypeScript stack's `package.lisa.json`
21
+ * wires `lisa apply` into `postinstall` — so an ordinary `npm install` in a host
22
+ * project writes `merge.lisa-learnings.driver` into `.git/config` with no
23
+ * separate human step, and against Lisa's own repository too.
24
+ *
25
+ * That is worth stating plainly because of what it implies: the registration
26
+ * persists an executable hook in local git config that fires on ordinary git
27
+ * operations, outside npm's lifecycle. It is not an escalation over `npm
28
+ * install` itself (which already runs arbitrary code), but it does outlive it.
29
+ * Hosts that would rather not carry that hook set `learnings.mergeDriver: false`
30
+ * in `.lisa.config.json`; `lisa install-merge-driver` remains available to
31
+ * register it by hand in a clone that apply has not touched.
32
+ * @module core/learnings-merge-driver
33
+ */
34
+ /** Git merge-driver name shared by `.gitattributes` and the local git config. */
35
+ export declare const LEARNINGS_MERGE_DRIVER_NAME = "lisa-learnings";
36
+ /** Human-readable driver name recorded alongside the command. */
37
+ export declare const LEARNINGS_MERGE_DRIVER_DESCRIPTION = "Union project learnings by entry id";
38
+ /** Marker opening the Lisa-managed block in a host `.gitattributes`. */
39
+ export declare const GITATTRIBUTES_BEGIN_MARKER = "# BEGIN: AI GUARDRAILS";
40
+ /** Marker closing the Lisa-managed block in a host `.gitattributes`. */
41
+ export declare const GITATTRIBUTES_END_MARKER = "# END: AI GUARDRAILS";
42
+ /**
43
+ * Build the `merge.<name>.driver` command string.
44
+ *
45
+ * Git expands `%O` (merge base), `%A` (our version, and the file the driver
46
+ * MUST overwrite with the result), `%B` (their version), and `%P` (the real
47
+ * pathname, used only for diagnostics). The driver reports success by exiting
48
+ * zero; any non-zero exit tells git to record a conflict.
49
+ *
50
+ * ⚠️ DO NOT WRAP THE PLACEHOLDERS IN QUOTES. Git runs this string through
51
+ * `sh -c` and substitutes `%O/%A/%B/%P` already shell-quoted (git's own
52
+ * `sq_quote`), so bare `--path %P` is correct AND safe for a pathname
53
+ * containing spaces or metacharacters. Adding quotes — `--path "%P"` — looks
54
+ * like a fix for filenames with spaces and is a remote code execution hole: the
55
+ * value arrives pre-quoted, so the added double quotes re-expose `$(...)`,
56
+ * backticks and `$` inside a filename to the shell. A security review
57
+ * demonstrated a working RCE for exactly that "fix". If a path with spaces
58
+ * appears broken, the bug is elsewhere.
59
+ *
60
+ * The `invocation` argument is a different matter: it is OUR string, not git's,
61
+ * so it must be shell-quoted by the caller before it gets here (see
62
+ * `learnings-merge-driver-install.ts`).
63
+ * @param invocation - How to invoke the Lisa CLI, already shell-quoted
64
+ * @returns Fully expanded git driver command
65
+ */
66
+ export declare function buildLearningsMergeDriverCommand(invocation: string): string;
67
+ /**
68
+ * Build the `.gitattributes` line binding one ledger path to the driver.
69
+ * @param ledgerPath - Project-relative learnings file path
70
+ * @returns Single `.gitattributes` line, without a trailing newline
71
+ */
72
+ export declare function buildLearningsAttributeLine(ledgerPath: string): string;
73
+ /**
74
+ * Render the complete Lisa-managed `.gitattributes` block.
75
+ *
76
+ * Wrapped in the same guardrail markers the `.gitignore` template uses, so the
77
+ * `copy-contents` strategy replaces exactly this block on re-apply and leaves
78
+ * every host-authored rule outside it untouched.
79
+ * @param ledgerPath - Project-relative learnings file path
80
+ * @returns Managed block, newline-terminated
81
+ */
82
+ export declare function renderLearningsGitattributesBlock(ledgerPath: string): string;
83
+ //# sourceMappingURL=learnings-merge-driver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge-driver.d.ts","sourceRoot":"","sources":["../../src/core/learnings-merge-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,iFAAiF;AACjF,eAAO,MAAM,2BAA2B,mBAAmB,CAAC;AAE5D,iEAAiE;AACjE,eAAO,MAAM,kCAAkC,wCACR,CAAC;AAExC,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,2BAA2B,CAAC;AAEnE,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gCAAgC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAa5E"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Registration contract for the project-learnings union merge driver.
3
+ *
4
+ * A git merge driver has two halves that live in different places:
5
+ *
6
+ * 1. **The attribute** (`.gitattributes`) says WHICH driver a path uses. It is
7
+ * a committed file, so Lisa ships it to the repository and to every host
8
+ * project through the template pipeline.
9
+ * 2. **The driver command** (`merge.<name>.driver`) says HOW to run it. Git
10
+ * deliberately keeps this in machine-local config and never reads it from
11
+ * the repository, because a committed driver command would let a cloned
12
+ * repository execute arbitrary code on `git merge`.
13
+ *
14
+ * So the attribute alone is inert. Until the command is registered, git falls
15
+ * back to its built-in text merge — which is exactly today's behavior, so an
16
+ * unregistered checkout is degraded, never broken. That fallback is asserted in
17
+ * `tests/unit/core/learnings-merge-driver.test.ts`.
18
+ *
19
+ * Registration is AUTOMATIC, not operator-initiated. `EnsureLearningsMergeDriver`
20
+ * runs on every `lisa apply`, and the TypeScript stack's `package.lisa.json`
21
+ * wires `lisa apply` into `postinstall` — so an ordinary `npm install` in a host
22
+ * project writes `merge.lisa-learnings.driver` into `.git/config` with no
23
+ * separate human step, and against Lisa's own repository too.
24
+ *
25
+ * That is worth stating plainly because of what it implies: the registration
26
+ * persists an executable hook in local git config that fires on ordinary git
27
+ * operations, outside npm's lifecycle. It is not an escalation over `npm
28
+ * install` itself (which already runs arbitrary code), but it does outlive it.
29
+ * Hosts that would rather not carry that hook set `learnings.mergeDriver: false`
30
+ * in `.lisa.config.json`; `lisa install-merge-driver` remains available to
31
+ * register it by hand in a clone that apply has not touched.
32
+ * @module core/learnings-merge-driver
33
+ */
34
+ /** Git merge-driver name shared by `.gitattributes` and the local git config. */
35
+ export const LEARNINGS_MERGE_DRIVER_NAME = "lisa-learnings";
36
+ /** Human-readable driver name recorded alongside the command. */
37
+ export const LEARNINGS_MERGE_DRIVER_DESCRIPTION = "Union project learnings by entry id";
38
+ /** Marker opening the Lisa-managed block in a host `.gitattributes`. */
39
+ export const GITATTRIBUTES_BEGIN_MARKER = "# BEGIN: AI GUARDRAILS";
40
+ /** Marker closing the Lisa-managed block in a host `.gitattributes`. */
41
+ export const GITATTRIBUTES_END_MARKER = "# END: AI GUARDRAILS";
42
+ /**
43
+ * Build the `merge.<name>.driver` command string.
44
+ *
45
+ * Git expands `%O` (merge base), `%A` (our version, and the file the driver
46
+ * MUST overwrite with the result), `%B` (their version), and `%P` (the real
47
+ * pathname, used only for diagnostics). The driver reports success by exiting
48
+ * zero; any non-zero exit tells git to record a conflict.
49
+ *
50
+ * ⚠️ DO NOT WRAP THE PLACEHOLDERS IN QUOTES. Git runs this string through
51
+ * `sh -c` and substitutes `%O/%A/%B/%P` already shell-quoted (git's own
52
+ * `sq_quote`), so bare `--path %P` is correct AND safe for a pathname
53
+ * containing spaces or metacharacters. Adding quotes — `--path "%P"` — looks
54
+ * like a fix for filenames with spaces and is a remote code execution hole: the
55
+ * value arrives pre-quoted, so the added double quotes re-expose `$(...)`,
56
+ * backticks and `$` inside a filename to the shell. A security review
57
+ * demonstrated a working RCE for exactly that "fix". If a path with spaces
58
+ * appears broken, the bug is elsewhere.
59
+ *
60
+ * The `invocation` argument is a different matter: it is OUR string, not git's,
61
+ * so it must be shell-quoted by the caller before it gets here (see
62
+ * `learnings-merge-driver-install.ts`).
63
+ * @param invocation - How to invoke the Lisa CLI, already shell-quoted
64
+ * @returns Fully expanded git driver command
65
+ */
66
+ export function buildLearningsMergeDriverCommand(invocation) {
67
+ return `${invocation} merge-learnings --base %O --ours %A --theirs %B --path %P`;
68
+ }
69
+ /**
70
+ * Build the `.gitattributes` line binding one ledger path to the driver.
71
+ * @param ledgerPath - Project-relative learnings file path
72
+ * @returns Single `.gitattributes` line, without a trailing newline
73
+ */
74
+ export function buildLearningsAttributeLine(ledgerPath) {
75
+ return `${ledgerPath} merge=${LEARNINGS_MERGE_DRIVER_NAME}`;
76
+ }
77
+ /**
78
+ * Render the complete Lisa-managed `.gitattributes` block.
79
+ *
80
+ * Wrapped in the same guardrail markers the `.gitignore` template uses, so the
81
+ * `copy-contents` strategy replaces exactly this block on re-apply and leaves
82
+ * every host-authored rule outside it untouched.
83
+ * @param ledgerPath - Project-relative learnings file path
84
+ * @returns Managed block, newline-terminated
85
+ */
86
+ export function renderLearningsGitattributesBlock(ledgerPath) {
87
+ return [
88
+ GITATTRIBUTES_BEGIN_MARKER,
89
+ "",
90
+ "# The project learnings ledger is written by concurrent learner passes that",
91
+ "# each run on their own branch. Git's default line merge corrupts it with",
92
+ "# conflict markers; this driver unions entries by id instead. The driver",
93
+ "# command is machine-local — run `lisa install-merge-driver` to register it.",
94
+ buildLearningsAttributeLine(ledgerPath),
95
+ "",
96
+ GITATTRIBUTES_END_MARKER,
97
+ "",
98
+ ].join("\n");
99
+ }
100
+ //# sourceMappingURL=learnings-merge-driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge-driver.js","sourceRoot":"","sources":["../../src/core/learnings-merge-driver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,2BAA2B,GAAG,gBAAgB,CAAC;AAE5D,iEAAiE;AACjE,MAAM,CAAC,MAAM,kCAAkC,GAC7C,qCAAqC,CAAC;AAExC,wEAAwE;AACxE,MAAM,CAAC,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAEnE,wEAAwE;AACxE,MAAM,CAAC,MAAM,wBAAwB,GAAG,sBAAsB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,gCAAgC,CAAC,UAAkB;IACjE,OAAO,GAAG,UAAU,4DAA4D,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,UAAkB;IAC5D,OAAO,GAAG,UAAU,UAAU,2BAA2B,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iCAAiC,CAAC,UAAkB;IAClE,OAAO;QACL,0BAA0B;QAC1B,EAAE;QACF,6EAA6E;QAC7E,2EAA2E;QAC3E,0EAA0E;QAC1E,8EAA8E;QAC9E,2BAA2B,CAAC,UAAU,CAAC;QACvC,EAAE;QACF,wBAAwB;QACxB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,23 @@
1
+ /** A clean union that satisfies every budget. */
2
+ export interface LearningsMergeMerged {
3
+ readonly kind: "merged";
4
+ /** Canonical merged document, ready to publish. */
5
+ readonly content: string;
6
+ }
7
+ /** The merge cannot be completed without losing information. */
8
+ export interface LearningsMergeConflict {
9
+ readonly kind: "conflict";
10
+ /** Single-line, operator-facing explanation. */
11
+ readonly reason: string;
12
+ }
13
+ /** Closed outcome of one ledger merge. */
14
+ export type LearningsMergeResult = LearningsMergeMerged | LearningsMergeConflict;
15
+ /**
16
+ * Merge two divergent ledger versions against their common ancestor.
17
+ * @param base - Merge-base document, or undefined when the file is new on both sides
18
+ * @param ours - Current-branch document
19
+ * @param theirs - Incoming-branch document
20
+ * @returns Canonical merged document, or a conflict with a reason
21
+ */
22
+ export declare function mergeLearningsDocuments(base: string | undefined, ours: string, theirs: string): LearningsMergeResult;
23
+ //# sourceMappingURL=learnings-merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learnings-merge.d.ts","sourceRoot":"","sources":["../../src/core/learnings-merge.ts"],"names":[],"mappings":"AAoDA,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,gEAAgE;AAChE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,0CAA0C;AAC1C,MAAM,MAAM,oBAAoB,GAC5B,oBAAoB,GACpB,sBAAsB,CAAC;AAE3B;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,oBAAoB,CAgBtB"}