@codyswann/lisa 2.217.7 → 2.218.0
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/dist/cli/apply.d.ts.map +1 -1
- package/dist/cli/apply.js +18 -1
- package/dist/cli/apply.js.map +1 -1
- package/dist/core/config.d.ts +11 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +13 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/learnings-contract.d.ts +38 -0
- package/dist/core/learnings-contract.d.ts.map +1 -0
- package/dist/core/learnings-contract.js +36 -0
- package/dist/core/learnings-contract.js.map +1 -0
- package/dist/core/learnings-entry.d.ts +9 -0
- package/dist/core/learnings-entry.d.ts.map +1 -0
- package/dist/core/learnings-entry.js +233 -0
- package/dist/core/learnings-entry.js.map +1 -0
- package/dist/core/learnings-file-safety.d.ts +23 -0
- package/dist/core/learnings-file-safety.d.ts.map +1 -0
- package/dist/core/learnings-file-safety.js +84 -0
- package/dist/core/learnings-file-safety.js.map +1 -0
- package/dist/core/learnings-lock.d.ts +8 -0
- package/dist/core/learnings-lock.d.ts.map +1 -0
- package/dist/core/learnings-lock.js +227 -0
- package/dist/core/learnings-lock.js.map +1 -0
- package/dist/core/learnings-writer.d.ts +22 -0
- package/dist/core/learnings-writer.d.ts.map +1 -0
- package/dist/core/learnings-writer.js +128 -0
- package/dist/core/learnings-writer.js.map +1 -0
- package/dist/core/learnings.d.ts +4 -0
- package/dist/core/learnings.d.ts.map +1 -0
- package/dist/core/learnings.js +4 -0
- package/dist/core/learnings.js.map +1 -0
- package/dist/core/lisa.d.ts +17 -1
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +39 -3
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/project-config.d.ts +43 -2
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +121 -11
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/self-apply.d.ts +10 -0
- package/dist/core/self-apply.d.ts.map +1 -0
- package/dist/core/self-apply.js +29 -0
- package/dist/core/self-apply.js.map +1 -0
- package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.js +7 -0
- package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
- package/dist/strategies/package-lisa.d.ts +24 -4
- package/dist/strategies/package-lisa.d.ts.map +1 -1
- package/dist/strategies/package-lisa.js +148 -6
- package/dist/strategies/package-lisa.js.map +1 -1
- package/dist/sync/registry.d.ts +0 -12
- package/dist/sync/registry.d.ts.map +1 -1
- package/dist/sync/registry.js +18 -0
- package/dist/sync/registry.js.map +1 -1
- package/package.json +2 -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-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa/rules/eager/config-resolution.md +9 -0
- package/plugins/lisa/rules/reference/config-resolution.md +2 -0
- package/plugins/lisa/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-debrief-apply/SKILL.md +3 -3
- 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/rules/eager/config-resolution.md +9 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +2 -0
- package/plugins/lisa-copilot/skills/lisa-debrief-apply/SKILL.md +3 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +2 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +9 -0
- package/plugins/lisa-cursor/skills/lisa-debrief-apply/SKILL.md +3 -3
- 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/rules/eager/config-resolution.md +9 -0
- package/plugins/src/base/rules/reference/config-resolution.md +2 -0
- package/plugins/src/base/skills/lisa-debrief-apply/SKILL.md +3 -3
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/** Cross-process serialization for project learnings writes. */
|
|
2
|
+
import { link, lstat, readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
const MAX_LOCK_ATTEMPTS = 200;
|
|
4
|
+
const LOCK_RETRY_DELAY_MS = 10;
|
|
5
|
+
const STALE_LOCK_MS = 30_000;
|
|
6
|
+
/**
|
|
7
|
+
* Serialize same-target writers across processes with an exclusive hard link.
|
|
8
|
+
* @param target - Absolute learnings target
|
|
9
|
+
* @param operation - Complete read, validate, and atomic-write transaction
|
|
10
|
+
* @returns Operation result
|
|
11
|
+
*/
|
|
12
|
+
export async function withLearningTargetLock(target, operation) {
|
|
13
|
+
const lockPath = `${target}.lock`;
|
|
14
|
+
const owner = {
|
|
15
|
+
token: crypto.randomUUID(),
|
|
16
|
+
pid: process.pid,
|
|
17
|
+
createdAt: Date.now(),
|
|
18
|
+
};
|
|
19
|
+
const lease = await acquireLock(lockPath, owner, 0);
|
|
20
|
+
try {
|
|
21
|
+
return await operation();
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await releaseLock(lockPath, lease);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Publish complete owner metadata atomically via a hard link.
|
|
29
|
+
* @param lockPath - Shared lock path
|
|
30
|
+
* @param owner - Unique owner metadata
|
|
31
|
+
* @param attempt - Current retry count
|
|
32
|
+
* @returns Acquired lock lease
|
|
33
|
+
*/
|
|
34
|
+
async function acquireLock(lockPath, owner, attempt) {
|
|
35
|
+
const ownerPath = `${lockPath}.${owner.token}.owner`;
|
|
36
|
+
await writeFile(ownerPath, JSON.stringify(owner), {
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
flag: "wx",
|
|
39
|
+
});
|
|
40
|
+
const acquired = await publishOwnerLink(ownerPath, lockPath);
|
|
41
|
+
if (acquired) {
|
|
42
|
+
return { owner, ownerPath };
|
|
43
|
+
}
|
|
44
|
+
await removeFileIfPresent(ownerPath);
|
|
45
|
+
if (attempt >= MAX_LOCK_ATTEMPTS) {
|
|
46
|
+
throw new Error(`Timed out waiting for learnings lock: ${lockPath}`);
|
|
47
|
+
}
|
|
48
|
+
await reclaimStaleLock(lockPath);
|
|
49
|
+
await delay(LOCK_RETRY_DELAY_MS);
|
|
50
|
+
return acquireLock(lockPath, owner, attempt + 1);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Try to hard-link fully written owner metadata into the lock path.
|
|
54
|
+
* @param ownerPath - Fully written owner metadata file
|
|
55
|
+
* @param lockPath - Destination lock path
|
|
56
|
+
* @returns Whether publication acquired the lock
|
|
57
|
+
*/
|
|
58
|
+
async function publishOwnerLink(ownerPath, lockPath) {
|
|
59
|
+
try {
|
|
60
|
+
await link(ownerPath, lockPath);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error.code === "EEXIST" ||
|
|
65
|
+
error.code === "ENOENT") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Remove only the lock inode still linked to this lease's owner file.
|
|
73
|
+
* @param lockPath - Shared lock path
|
|
74
|
+
* @param lease - Owner lease retaining the same inode
|
|
75
|
+
*/
|
|
76
|
+
async function releaseLock(lockPath, lease) {
|
|
77
|
+
try {
|
|
78
|
+
if (await sameFile(lockPath, lease.ownerPath)) {
|
|
79
|
+
await unlink(lockPath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await removeFileIfPresent(lease.ownerPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reclaim an expired lock only when its declared process is no longer live.
|
|
88
|
+
* @param lockPath - Shared lock path
|
|
89
|
+
*/
|
|
90
|
+
async function reclaimStaleLock(lockPath) {
|
|
91
|
+
const before = await statFile(lockPath);
|
|
92
|
+
if (before === undefined || !before.isFile()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const owner = await readLockOwner(lockPath);
|
|
96
|
+
const timestamp = owner?.createdAt ?? Number(before.mtimeMs);
|
|
97
|
+
if (Date.now() - timestamp <= STALE_LOCK_MS ||
|
|
98
|
+
(owner !== undefined && isProcessLive(owner.pid))) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const retainedOwnerPath = owner === undefined ? undefined : `${lockPath}.${owner.token}.owner`;
|
|
102
|
+
const quarantine = retainedOwnerPath !== undefined &&
|
|
103
|
+
(await sameFile(lockPath, retainedOwnerPath))
|
|
104
|
+
? retainedOwnerPath
|
|
105
|
+
: `${lockPath}.${crypto.randomUUID()}.stale`;
|
|
106
|
+
const linked = quarantine === retainedOwnerPath
|
|
107
|
+
? true
|
|
108
|
+
: await publishOwnerLink(lockPath, quarantine);
|
|
109
|
+
if (!linked) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
if (await sameFile(lockPath, quarantine)) {
|
|
114
|
+
await removeFileIfPresent(lockPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await removeFileIfPresent(quarantine);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Read bounded owner metadata; partial or special lock files are unowned.
|
|
123
|
+
* @param lockPath - Shared lock path
|
|
124
|
+
* @returns Parsed owner or undefined
|
|
125
|
+
*/
|
|
126
|
+
async function readLockOwner(lockPath) {
|
|
127
|
+
const metadata = await statFile(lockPath);
|
|
128
|
+
if (metadata === undefined || !metadata.isFile() || metadata.size > 512) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(await readFile(lockPath, "utf8"));
|
|
133
|
+
return isLockOwner(parsed) ? parsed : undefined;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Compare two regular-file paths by device and inode.
|
|
141
|
+
* @param left - First file path
|
|
142
|
+
* @param right - Second file path
|
|
143
|
+
* @returns Whether both paths retain the same regular-file inode
|
|
144
|
+
*/
|
|
145
|
+
async function sameFile(left, right) {
|
|
146
|
+
const [leftStat, rightStat] = await Promise.all([
|
|
147
|
+
statFile(left),
|
|
148
|
+
statFile(right),
|
|
149
|
+
]);
|
|
150
|
+
return (leftStat !== undefined &&
|
|
151
|
+
rightStat !== undefined &&
|
|
152
|
+
leftStat.isFile() &&
|
|
153
|
+
rightStat.isFile() &&
|
|
154
|
+
leftStat.dev === rightStat.dev &&
|
|
155
|
+
leftStat.ino === rightStat.ino);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Read file metadata without treating an absent path as an error.
|
|
159
|
+
* @param filePath - Filesystem path
|
|
160
|
+
* @returns File metadata or undefined
|
|
161
|
+
*/
|
|
162
|
+
async function statFile(filePath) {
|
|
163
|
+
try {
|
|
164
|
+
return await lstat(filePath);
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (error.code === "ENOENT") {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Remove one path without recursive deletion.
|
|
175
|
+
* @param filePath - Regular file or hard-link path
|
|
176
|
+
*/
|
|
177
|
+
async function removeFileIfPresent(filePath) {
|
|
178
|
+
try {
|
|
179
|
+
await unlink(filePath);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (error.code !== "ENOENT") {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Narrow parsed lock metadata to the exact ownership shape.
|
|
189
|
+
* @param value - Parsed metadata
|
|
190
|
+
* @returns Whether the metadata is a lock owner
|
|
191
|
+
*/
|
|
192
|
+
function isLockOwner(value) {
|
|
193
|
+
if (value === null || typeof value !== "object") {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const owner = value;
|
|
197
|
+
return (typeof owner.token === "string" &&
|
|
198
|
+
typeof owner.pid === "number" &&
|
|
199
|
+
Number.isSafeInteger(owner.pid) &&
|
|
200
|
+
owner.pid > 0 &&
|
|
201
|
+
typeof owner.createdAt === "number" &&
|
|
202
|
+
Number.isSafeInteger(owner.createdAt));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Treat permission-denied PID probes as live and missing PIDs as dead.
|
|
206
|
+
* @param pid - Declared owner process id
|
|
207
|
+
* @returns Whether the process may still be alive
|
|
208
|
+
*/
|
|
209
|
+
function isProcessLive(pid) {
|
|
210
|
+
try {
|
|
211
|
+
process.kill(pid, 0);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return error.code !== "ESRCH";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Await a short retry delay without blocking the event loop.
|
|
220
|
+
* @param milliseconds - Delay duration
|
|
221
|
+
*/
|
|
222
|
+
async function delay(milliseconds) {
|
|
223
|
+
await new Promise(resolve => {
|
|
224
|
+
setTimeout(resolve, milliseconds);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=learnings-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-lock.js","sourceRoot":"","sources":["../../src/core/learnings-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type LearningEntry } from "./learnings-contract.js";
|
|
2
|
+
/**
|
|
3
|
+
* Persist one already-selected learning without generating or truncating it.
|
|
4
|
+
* @param projectRoot - Absolute path to the host project root
|
|
5
|
+
* @param candidate - Untrusted seven-field learning entry
|
|
6
|
+
* @returns Absolute path to the persisted learnings file
|
|
7
|
+
*/
|
|
8
|
+
export declare function persistLearningEntry(projectRoot: string, candidate: unknown): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Render the complete file in one stable, machine-readable Markdown form.
|
|
11
|
+
* @param entries - Validated entries to serialize
|
|
12
|
+
* @returns Canonical Markdown plus JSONL document
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderLearningsFile(entries: readonly LearningEntry[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Parse and revalidate an existing file before it participates in a write.
|
|
17
|
+
* @param content - Existing learnings document
|
|
18
|
+
* @returns Revalidated entries from the document
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseLearningsFile(content: string): LearningEntry[];
|
|
21
|
+
export { validateLearningEntry } from "./learnings-entry.js";
|
|
22
|
+
//# sourceMappingURL=learnings-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-writer.d.ts","sourceRoot":"","sources":["../../src/core/learnings-writer.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAqBjC;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAsBnE;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** Deterministic persistence for project-local learnings. */
|
|
2
|
+
import * as fse from "fs-extra";
|
|
3
|
+
import { rename, writeFile } from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { LEARNINGS_CONTRACT, estimateLearningTokens, } from "./learnings-contract.js";
|
|
6
|
+
import { validateLearningEntry } from "./learnings-entry.js";
|
|
7
|
+
import { assertSafeLearningParents, readExistingLearnings, resolveSafeLearningTarget, } from "./learnings-file-safety.js";
|
|
8
|
+
import { withLearningTargetLock } from "./learnings-lock.js";
|
|
9
|
+
import { readProjectConfig, resolveProjectLearningsFile, } from "./project-config.js";
|
|
10
|
+
const FILE_HEADER = `# Project Learnings
|
|
11
|
+
|
|
12
|
+
<!-- lisa-learnings-contract:v${LEARNINGS_CONTRACT.version} -->
|
|
13
|
+
|
|
14
|
+
`;
|
|
15
|
+
const JSON_FENCE_START = "```jsonl\n";
|
|
16
|
+
const JSON_FENCE_END = "\n```\n";
|
|
17
|
+
/**
|
|
18
|
+
* Persist one already-selected learning without generating or truncating it.
|
|
19
|
+
* @param projectRoot - Absolute path to the host project root
|
|
20
|
+
* @param candidate - Untrusted seven-field learning entry
|
|
21
|
+
* @returns Absolute path to the persisted learnings file
|
|
22
|
+
*/
|
|
23
|
+
export async function persistLearningEntry(projectRoot, candidate) {
|
|
24
|
+
const entry = validateLearningEntry(candidate);
|
|
25
|
+
const initialDocument = buildNextDocument([], entry);
|
|
26
|
+
const config = await readProjectConfig(projectRoot);
|
|
27
|
+
const relativeFile = resolveProjectLearningsFile(config);
|
|
28
|
+
const { root, target } = resolveSafeLearningTarget(projectRoot, relativeFile);
|
|
29
|
+
await assertSafeLearningParents(root, path.dirname(target));
|
|
30
|
+
await fse.ensureDir(path.dirname(target));
|
|
31
|
+
return withLearningTargetLock(target, async () => {
|
|
32
|
+
await assertSafeLearningParents(root, path.dirname(target));
|
|
33
|
+
const existing = await readExistingLearnings(target);
|
|
34
|
+
const entries = existing === undefined ? [] : parseLearningsFile(existing);
|
|
35
|
+
const rendered = existing === undefined
|
|
36
|
+
? initialDocument
|
|
37
|
+
: buildNextDocument(entries, entry);
|
|
38
|
+
const temporary = path.join(path.dirname(target), `.${path.basename(target)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
39
|
+
try {
|
|
40
|
+
await writeFile(temporary, rendered, { encoding: "utf8", flag: "wx" });
|
|
41
|
+
await assertSafeLearningParents(root, path.dirname(target));
|
|
42
|
+
await rename(temporary, target);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await fse.remove(temporary);
|
|
46
|
+
}
|
|
47
|
+
return target;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render the complete file in one stable, machine-readable Markdown form.
|
|
52
|
+
* @param entries - Validated entries to serialize
|
|
53
|
+
* @returns Canonical Markdown plus JSONL document
|
|
54
|
+
*/
|
|
55
|
+
export function renderLearningsFile(entries) {
|
|
56
|
+
const lines = entries.map(entry => JSON.stringify(entry)).join("\n");
|
|
57
|
+
return `${FILE_HEADER}${JSON_FENCE_START}${lines}${JSON_FENCE_END}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parse and revalidate an existing file before it participates in a write.
|
|
61
|
+
* @param content - Existing learnings document
|
|
62
|
+
* @returns Revalidated entries from the document
|
|
63
|
+
*/
|
|
64
|
+
export function parseLearningsFile(content) {
|
|
65
|
+
if (estimateLearningTokens(content) > LEARNINGS_CONTRACT.maxTokens) {
|
|
66
|
+
throw new Error(`Project learnings payload exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens}`);
|
|
67
|
+
}
|
|
68
|
+
if (!content.startsWith(`${FILE_HEADER}${JSON_FENCE_START}`) ||
|
|
69
|
+
!content.endsWith(JSON_FENCE_END)) {
|
|
70
|
+
throw new Error("Invalid project learnings file format");
|
|
71
|
+
}
|
|
72
|
+
const jsonStart = FILE_HEADER.length + JSON_FENCE_START.length;
|
|
73
|
+
const jsonEnd = content.length - JSON_FENCE_END.length;
|
|
74
|
+
const entries = parseJsonLines(content.slice(jsonStart, jsonEnd)).map(validateLearningEntry);
|
|
75
|
+
if (new Set(entries.map(entry => entry.id)).size !== entries.length) {
|
|
76
|
+
throw new Error("Invalid project learnings payload: duplicate ids");
|
|
77
|
+
}
|
|
78
|
+
assertDocumentBudget(content, entries.length, "Project learnings payload");
|
|
79
|
+
return entries;
|
|
80
|
+
}
|
|
81
|
+
export { validateLearningEntry } from "./learnings-entry.js";
|
|
82
|
+
/**
|
|
83
|
+
* Build and budget-check the next canonical document.
|
|
84
|
+
* @param entries - Existing validated entries
|
|
85
|
+
* @param entry - New validated entry
|
|
86
|
+
* @returns Next canonical document
|
|
87
|
+
*/
|
|
88
|
+
function buildNextDocument(entries, entry) {
|
|
89
|
+
if (entries.some(current => current.id === entry.id)) {
|
|
90
|
+
throw new Error(`Duplicate learning id: ${entry.id}`);
|
|
91
|
+
}
|
|
92
|
+
const nextEntries = [...entries, entry].sort((left, right) => left.id.localeCompare(right.id));
|
|
93
|
+
const rendered = renderLearningsFile(nextEntries);
|
|
94
|
+
assertDocumentBudget(rendered, nextEntries.length, "Learnings file");
|
|
95
|
+
return rendered;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Enforce the shared entry-count and model-agnostic token upper bounds.
|
|
99
|
+
* @param content - Canonical document
|
|
100
|
+
* @param entryCount - Number of entries in the document
|
|
101
|
+
* @param context - Error-message prefix
|
|
102
|
+
*/
|
|
103
|
+
function assertDocumentBudget(content, entryCount, context) {
|
|
104
|
+
if (entryCount > LEARNINGS_CONTRACT.maxEntries) {
|
|
105
|
+
throw new Error(`${context} exceeds maxEntries ${LEARNINGS_CONTRACT.maxEntries}`);
|
|
106
|
+
}
|
|
107
|
+
const estimatedTokens = estimateLearningTokens(content);
|
|
108
|
+
if (estimatedTokens > LEARNINGS_CONTRACT.maxTokens) {
|
|
109
|
+
throw new Error(`${context} exceeds maxTokens ${LEARNINGS_CONTRACT.maxTokens} (measured ${estimatedTokens})`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse the strict JSONL payload with a stable, user-facing error.
|
|
114
|
+
* @param payload - JSONL payload
|
|
115
|
+
* @returns Parsed unknown entry values
|
|
116
|
+
*/
|
|
117
|
+
function parseJsonLines(payload) {
|
|
118
|
+
if (payload === "") {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return payload.split("\n").map(line => JSON.parse(line));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
throw new Error("Invalid project learnings JSONL payload");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=learnings-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-writer.js","sourceRoot":"","sources":["../../src/core/learnings-writer.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,GAAG;;gCAEY,kBAAkB,CAAC,OAAO;;CAEzD,CAAC;AACF,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,WAAmB,EACnB,SAAkB;IAElB,MAAM,KAAK,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,iBAAiB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACzD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,yBAAyB,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAC9E,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1C,OAAO,sBAAsB,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC3E,MAAM,QAAQ,GACZ,QAAQ,KAAK,SAAS;YACpB,CAAC,CAAC,eAAe;YACjB,CAAC,CAAC,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EACpB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,MAAM,CACtE,CAAC;QACF,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACvE,MAAM,yBAAyB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5D,MAAM,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAClC,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAiC;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrE,OAAO,GAAG,WAAW,GAAG,gBAAgB,GAAG,KAAK,GAAG,cAAc,EAAE,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,IAAI,sBAAsB,CAAC,OAAO,CAAC,GAAG,kBAAkB,CAAC,SAAS,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CACb,+CAA+C,kBAAkB,CAAC,SAAS,EAAE,CAC9E,CAAC;IACJ,CAAC;IACD,IACE,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,gBAAgB,EAAE,CAAC;QACxD,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,EACjC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC;IACvD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CACnE,qBAAqB,CACtB,CAAC;IACF,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAC3E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,OAAiC,EACjC,KAAoB;IAEpB,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,WAAW,GAAG,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAC3D,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAChC,CAAC;IACF,MAAM,QAAQ,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAClD,oBAAoB,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IACrE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;GAKG;AACH,SAAS,oBAAoB,CAC3B,OAAe,EACf,UAAkB,EAClB,OAAe;IAEf,IAAI,UAAU,GAAG,kBAAkB,CAAC,UAAU,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,uBAAuB,kBAAkB,CAAC,UAAU,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,MAAM,eAAe,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,eAAe,GAAG,kBAAkB,CAAC,SAAS,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,sBAAsB,kBAAkB,CAAC,SAAS,cAAc,eAAe,GAAG,CAC7F,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * from "./learnings-contract.js";
|
|
2
|
+
export * from "./learnings-writer.js";
|
|
3
|
+
export { DEFAULT_PROJECT_RULES_FILE, PROJECT_LEARNINGS_FILENAME, resolveProjectLearningsFile, resolveProjectRulesFile, type ProjectConfig, } from "./project-config.js";
|
|
4
|
+
//# sourceMappingURL=learnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings.d.ts","sourceRoot":"","sources":["../../src/core/learnings.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings.js","sourceRoot":"","sources":["../../src/core/learnings.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,2BAA2B,EAC3B,uBAAuB,GAExB,MAAM,qBAAqB,CAAC"}
|
package/dist/core/lisa.d.ts
CHANGED
|
@@ -78,6 +78,12 @@ export declare class Lisa {
|
|
|
78
78
|
private copyOverwriteOwnership;
|
|
79
79
|
private readonly separator;
|
|
80
80
|
private readonly lisaignoreSuffix;
|
|
81
|
+
/**
|
|
82
|
+
* True when the destination is the Lisa source repo itself. A self-apply
|
|
83
|
+
* skips deletions, template application, and postinstall injection while
|
|
84
|
+
* still applying package.lisa.json dependency governance. See {@link isLisaSourceRepo}.
|
|
85
|
+
*/
|
|
86
|
+
private selfApply;
|
|
81
87
|
/**
|
|
82
88
|
* Initialize Lisa orchestrator
|
|
83
89
|
* @param config - Configuration for the apply operation
|
|
@@ -88,7 +94,9 @@ export declare class Lisa {
|
|
|
88
94
|
* Postinstall applies are intentionally narrow: they refresh governance
|
|
89
95
|
* templates that must track dependency updates, but they must not regenerate
|
|
90
96
|
* project-scoped agent surfaces. Agent trees are large, committed outputs and
|
|
91
|
-
* are updated through an explicit `lisa apply` or cross-pollinate run.
|
|
97
|
+
* are updated through an explicit `lisa apply` or cross-pollinate run. A
|
|
98
|
+
* self-apply against the Lisa source repo also skips emit — those agent
|
|
99
|
+
* surfaces are built from `plugins/src`, not regenerated by apply.
|
|
92
100
|
* @returns True when the current apply should avoid agent-surface writes.
|
|
93
101
|
*/
|
|
94
102
|
private shouldSkipAgentEmitDuringPostinstall;
|
|
@@ -100,6 +108,14 @@ export declare class Lisa {
|
|
|
100
108
|
* Detect and confirm project types
|
|
101
109
|
*/
|
|
102
110
|
private detectTypes;
|
|
111
|
+
/**
|
|
112
|
+
* Detect whether this apply targets the Lisa source repo itself and, if so,
|
|
113
|
+
* announce the narrowed behavior. A self-apply skips deletions, template
|
|
114
|
+
* application, and agent-surface emit, but still applies package.lisa.json
|
|
115
|
+
* dependency governance so Lisa's own deps track the floors it forces
|
|
116
|
+
* downstream. See {@link isLisaSourceRepo}.
|
|
117
|
+
*/
|
|
118
|
+
private detectSelfApply;
|
|
103
119
|
/**
|
|
104
120
|
* Process all configurations
|
|
105
121
|
*/
|
package/dist/core/lisa.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lisa.d.ts","sourceRoot":"","sources":["../../src/core/lisa.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAyBnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"lisa.d.ts","sourceRoot":"","sources":["../../src/core/lisa.ts"],"names":[],"mappings":"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"}
|
package/dist/core/lisa.js
CHANGED
|
@@ -18,6 +18,7 @@ import { installOpencodeMcpConfig, resolveOpencodeConfigPath, } from "../opencod
|
|
|
18
18
|
import { discoverAndInstallAgents as installOpencodeAgents } from "../opencode/agent-installer.js";
|
|
19
19
|
import { discoverAndInstallCommands as installOpencodeCommands } from "../opencode/command-installer.js";
|
|
20
20
|
import { installClaudeMd } from "../claude/claude-md-installer.js";
|
|
21
|
+
import { isLisaSourceRepo } from "./self-apply.js";
|
|
21
22
|
import { DestinationNotDirectoryError, DestinationNotFoundError, UserAbortedError, } from "../errors/index.js";
|
|
22
23
|
import { listFilesRecursive } from "../utils/file-operations.js";
|
|
23
24
|
import { loadIgnorePatterns, } from "../utils/ignore-patterns.js";
|
|
@@ -92,6 +93,12 @@ export class Lisa {
|
|
|
92
93
|
copyOverwriteOwnership = new Map();
|
|
93
94
|
separator = "========================================";
|
|
94
95
|
lisaignoreSuffix = "(.lisaignore)";
|
|
96
|
+
/**
|
|
97
|
+
* True when the destination is the Lisa source repo itself. A self-apply
|
|
98
|
+
* skips deletions, template application, and postinstall injection while
|
|
99
|
+
* still applying package.lisa.json dependency governance. See {@link isLisaSourceRepo}.
|
|
100
|
+
*/
|
|
101
|
+
selfApply = false;
|
|
95
102
|
/**
|
|
96
103
|
* Initialize Lisa orchestrator
|
|
97
104
|
* @param config - Configuration for the apply operation
|
|
@@ -105,11 +112,13 @@ export class Lisa {
|
|
|
105
112
|
* Postinstall applies are intentionally narrow: they refresh governance
|
|
106
113
|
* templates that must track dependency updates, but they must not regenerate
|
|
107
114
|
* project-scoped agent surfaces. Agent trees are large, committed outputs and
|
|
108
|
-
* are updated through an explicit `lisa apply` or cross-pollinate run.
|
|
115
|
+
* are updated through an explicit `lisa apply` or cross-pollinate run. A
|
|
116
|
+
* self-apply against the Lisa source repo also skips emit — those agent
|
|
117
|
+
* surfaces are built from `plugins/src`, not regenerated by apply.
|
|
109
118
|
* @returns True when the current apply should avoid agent-surface writes.
|
|
110
119
|
*/
|
|
111
120
|
shouldSkipAgentEmitDuringPostinstall() {
|
|
112
|
-
return this.config.skipGitCheck;
|
|
121
|
+
return this.config.skipGitCheck || this.selfApply;
|
|
113
122
|
}
|
|
114
123
|
/**
|
|
115
124
|
* Initialize services
|
|
@@ -131,6 +140,21 @@ export class Lisa {
|
|
|
131
140
|
...(await prompter.confirmProjectTypes(this.detectedTypes)),
|
|
132
141
|
];
|
|
133
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Detect whether this apply targets the Lisa source repo itself and, if so,
|
|
145
|
+
* announce the narrowed behavior. A self-apply skips deletions, template
|
|
146
|
+
* application, and agent-surface emit, but still applies package.lisa.json
|
|
147
|
+
* dependency governance so Lisa's own deps track the floors it forces
|
|
148
|
+
* downstream. See {@link isLisaSourceRepo}.
|
|
149
|
+
*/
|
|
150
|
+
async detectSelfApply() {
|
|
151
|
+
this.selfApply = await isLisaSourceRepo(this.config.destDir);
|
|
152
|
+
if (this.selfApply) {
|
|
153
|
+
this.deps.logger.info(pc.yellow("Detected Lisa source repo — self-apply mode: applying only " +
|
|
154
|
+
"package.lisa.json dependency governance (skipping template " +
|
|
155
|
+
"application, deletions, postinstall injection, and agent emit)."));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
134
158
|
/**
|
|
135
159
|
* Process all configurations
|
|
136
160
|
*/
|
|
@@ -539,13 +563,19 @@ export class Lisa {
|
|
|
539
563
|
this.printHeader();
|
|
540
564
|
await this.initServices();
|
|
541
565
|
await this.detectTypes();
|
|
566
|
+
await this.detectSelfApply();
|
|
542
567
|
await this.loadPendingDeletions();
|
|
543
568
|
await this.loadCreateOnlyOwnership();
|
|
544
569
|
await this.loadCopyOverwriteOwnership();
|
|
545
570
|
await this.runMigrationsBeforeStrategies();
|
|
546
571
|
const prePackageJsonHash = hashFile(path.join(this.config.destDir, "package.json"));
|
|
547
572
|
await this.processConfigurations();
|
|
548
|
-
|
|
573
|
+
if (this.selfApply) {
|
|
574
|
+
this.deps.logger.info(pc.gray("Self-apply (Lisa source repo): skipped deletions.json processing"));
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
await this.processDeletions();
|
|
578
|
+
}
|
|
549
579
|
await this.processMigrations();
|
|
550
580
|
await this.processCodexEmit();
|
|
551
581
|
await this.processClaudeEmit();
|
|
@@ -950,6 +980,12 @@ export class Lisa {
|
|
|
950
980
|
async processProjectType(type) {
|
|
951
981
|
const { logger, strategyRegistry } = this.deps;
|
|
952
982
|
for (const strategy of COPY_STRATEGIES) {
|
|
983
|
+
// Self-apply (Lisa source repo) applies ONLY dependency governance via the
|
|
984
|
+
// package-lisa strategy — every other strategy would overwrite Lisa's own
|
|
985
|
+
// hand-authored source with the templates it ships.
|
|
986
|
+
if (this.selfApply && strategy !== "package-lisa") {
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
953
989
|
const srcDir = path.join(this.config.lisaDir, type, strategy);
|
|
954
990
|
if (await fse.pathExists(srcDir)) {
|
|
955
991
|
logger.info(`Processing ${type}/${strategy}...`);
|