@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.
- package/all/copy-contents/.gitattributes +9 -0
- package/dist/cli/gate-commands.d.ts +22 -3
- package/dist/cli/gate-commands.d.ts.map +1 -1
- package/dist/cli/gate-commands.js +49 -1
- package/dist/cli/gate-commands.js.map +1 -1
- package/dist/cli/install-merge-driver-cmd.d.ts +21 -0
- package/dist/cli/install-merge-driver-cmd.d.ts.map +1 -0
- package/dist/cli/install-merge-driver-cmd.js +37 -0
- package/dist/cli/install-merge-driver-cmd.js.map +1 -0
- package/dist/cli/merge-learnings-cmd.d.ts +24 -0
- package/dist/cli/merge-learnings-cmd.d.ts.map +1 -0
- package/dist/cli/merge-learnings-cmd.js +82 -0
- package/dist/cli/merge-learnings-cmd.js.map +1 -0
- package/dist/core/learnings-budget-check.d.ts.map +1 -1
- package/dist/core/learnings-budget-check.js +18 -3
- package/dist/core/learnings-budget-check.js.map +1 -1
- package/dist/core/learnings-document.d.ts +46 -0
- package/dist/core/learnings-document.d.ts.map +1 -1
- package/dist/core/learnings-document.js +82 -4
- package/dist/core/learnings-document.js.map +1 -1
- package/dist/core/learnings-file-safety.d.ts.map +1 -1
- package/dist/core/learnings-file-safety.js +22 -0
- package/dist/core/learnings-file-safety.js.map +1 -1
- package/dist/core/learnings-merge-driver-config.d.ts +7 -0
- package/dist/core/learnings-merge-driver-config.d.ts.map +1 -0
- package/dist/core/learnings-merge-driver-config.js +58 -0
- package/dist/core/learnings-merge-driver-config.js.map +1 -0
- package/dist/core/learnings-merge-driver-install.d.ts +45 -0
- package/dist/core/learnings-merge-driver-install.d.ts.map +1 -0
- package/dist/core/learnings-merge-driver-install.js +161 -0
- package/dist/core/learnings-merge-driver-install.js.map +1 -0
- package/dist/core/learnings-merge-driver.d.ts +83 -0
- package/dist/core/learnings-merge-driver.d.ts.map +1 -0
- package/dist/core/learnings-merge-driver.js +100 -0
- package/dist/core/learnings-merge-driver.js.map +1 -0
- package/dist/core/learnings-merge.d.ts +23 -0
- package/dist/core/learnings-merge.d.ts.map +1 -0
- package/dist/core/learnings-merge.js +198 -0
- package/dist/core/learnings-merge.js.map +1 -0
- package/dist/core/learnings-writer.d.ts +6 -0
- package/dist/core/learnings-writer.d.ts.map +1 -1
- package/dist/core/learnings-writer.js +32 -65
- package/dist/core/learnings-writer.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +27 -4
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/storage.d.ts.map +1 -1
- package/dist/health/storage.js +7 -20
- package/dist/health/storage.js.map +1 -1
- package/dist/migrations/ensure-learnings-gitattributes.d.ts +43 -0
- package/dist/migrations/ensure-learnings-gitattributes.d.ts.map +1 -0
- package/dist/migrations/ensure-learnings-gitattributes.js +93 -0
- package/dist/migrations/ensure-learnings-gitattributes.js.map +1 -0
- package/dist/migrations/ensure-learnings-merge-driver.d.ts +46 -0
- package/dist/migrations/ensure-learnings-merge-driver.d.ts.map +1 -0
- package/dist/migrations/ensure-learnings-merge-driver.js +70 -0
- package/dist/migrations/ensure-learnings-merge-driver.js.map +1 -0
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +6 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/standards/storage.d.ts.map +1 -1
- package/dist/standards/storage.js +7 -20
- package/dist/standards/storage.js.map +1 -1
- package/dist/utils/atomic-file-write.d.ts +30 -0
- package/dist/utils/atomic-file-write.d.ts.map +1 -0
- package/dist/utils/atomic-file-write.js +61 -0
- package/dist/utils/atomic-file-write.js.map +1 -0
- package/dist/utils/directory-sync.d.ts.map +1 -0
- package/dist/utils/directory-sync.js.map +1 -0
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +1 -0
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
- package/plugins/lisa/skills/lisa-persist-learning/SKILL.md +1 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
- package/plugins/lisa-agy/skills/lisa-persist-learning/SKILL.md +1 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
- package/plugins/lisa-copilot/skills/lisa-persist-learning/SKILL.md +1 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
- package/plugins/lisa-cursor/skills/lisa-persist-learning/SKILL.md +1 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +12 -0
- package/plugins/src/base/skills/lisa-persist-learning/SKILL.md +1 -0
- package/dist/health/directory-sync.d.ts.map +0 -1
- package/dist/health/directory-sync.js.map +0 -1
- /package/dist/{health → utils}/directory-sync.d.ts +0 -0
- /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":"AAAA,mEAAmE;AACnE,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"learnings-file-safety.js","sourceRoot":"","sources":["../../src/core/learnings-file-safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|