@mohammadhprp/system-prompt 0.12.6 → 0.13.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/package.json +1 -1
- package/src/cli.js +4 -4
- package/src/doctor.js +19 -4
- package/src/installer.js +156 -35
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ import { intro, outro, confirm, multiselect, spinner, isCancel } from '@clack/pr
|
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import { categories } from './catalog.js';
|
|
5
|
-
import { getPackageVersion, install, loadLockFile } from './installer.js';
|
|
5
|
+
import { getPackageVersion, install, loadLockFile, lockToSelections } from './installer.js';
|
|
6
6
|
import { doctor } from './doctor.js';
|
|
7
7
|
|
|
8
8
|
const CATEGORY_FLAGS = new Set(Object.keys(categories));
|
|
@@ -71,7 +71,7 @@ function buildSummary(selections) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function computeDiff(oldLock, selections) {
|
|
74
|
-
const oldSels = oldLock
|
|
74
|
+
const oldSels = lockToSelections(oldLock);
|
|
75
75
|
const added = {};
|
|
76
76
|
const removed = {};
|
|
77
77
|
const kept = {};
|
|
@@ -161,7 +161,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
161
161
|
agentType,
|
|
162
162
|
selections,
|
|
163
163
|
includeAgentsMd: args.includeAgentsMd,
|
|
164
|
-
oldSelections: oldLock
|
|
164
|
+
oldSelections: oldLock ? lockToSelections(oldLock) : undefined,
|
|
165
165
|
oldLock,
|
|
166
166
|
force: args.force,
|
|
167
167
|
dryRun: args.dryRun,
|
|
@@ -315,7 +315,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
315
315
|
agentType,
|
|
316
316
|
selections,
|
|
317
317
|
includeAgentsMd,
|
|
318
|
-
oldSelections: oldLock
|
|
318
|
+
oldSelections: oldLock ? lockToSelections(oldLock) : undefined,
|
|
319
319
|
oldLock,
|
|
320
320
|
force: args.force,
|
|
321
321
|
dryRun: args.dryRun,
|
package/src/doctor.js
CHANGED
|
@@ -3,7 +3,7 @@ import { access, readFile } from 'node:fs/promises';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
import { categories } from './catalog.js';
|
|
6
|
-
import { loadLockFile } from './installer.js';
|
|
6
|
+
import { loadLockFile, lockToSelections } from './installer.js';
|
|
7
7
|
|
|
8
8
|
async function exists(path) {
|
|
9
9
|
try {
|
|
@@ -31,17 +31,32 @@ export async function inspectInstallation(targetDir) {
|
|
|
31
31
|
return { targetDir: absTarget, issues };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
for (const [path,
|
|
34
|
+
for (const [path, entry] of Object.entries(lock.generated || {})) {
|
|
35
35
|
try {
|
|
36
36
|
const actual = createHash('sha256').update(await readFile(resolve(absTarget, path))).digest('hex');
|
|
37
|
-
if (actual !==
|
|
37
|
+
if (actual !== entry.computedHash) issues.push(`Modified managed file: ${path}`);
|
|
38
38
|
} catch (error) {
|
|
39
39
|
if (error.code === 'ENOENT') issues.push(`Missing managed file: ${path}`);
|
|
40
40
|
else throw error;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
for (const [category,
|
|
44
|
+
for (const [category, entries] of Object.entries(lock)) {
|
|
45
|
+
if (!categories[category]) continue;
|
|
46
|
+
for (const [id, entry] of Object.entries(entries)) {
|
|
47
|
+
for (const [path, expected] of Object.entries(entry.files || {})) {
|
|
48
|
+
try {
|
|
49
|
+
const actual = createHash('sha256').update(await readFile(resolve(absTarget, path))).digest('hex');
|
|
50
|
+
if (actual !== expected) issues.push(`Modified managed file: ${path}`);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code === 'ENOENT') issues.push(`Missing managed file: ${path}`);
|
|
53
|
+
else throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [category, ids] of Object.entries(lockToSelections(lock))) {
|
|
45
60
|
const config = categories[category];
|
|
46
61
|
for (const id of ids) {
|
|
47
62
|
const item = config.items.find(entry => entry.id === id);
|
package/src/installer.js
CHANGED
|
@@ -86,6 +86,47 @@ package-lock.json
|
|
|
86
86
|
bun.lock
|
|
87
87
|
`;
|
|
88
88
|
|
|
89
|
+
export const LOCK_VERSION = 1;
|
|
90
|
+
export const LOCK_CATEGORIES = ['skills', 'agents', 'commands', 'mcps', 'plugins', 'styles', 'modes', 'memory', 'standards', 'templates'];
|
|
91
|
+
|
|
92
|
+
const FILE_BASED = new Set(['agents', 'commands', 'memory', 'modes', 'standards', 'templates']);
|
|
93
|
+
|
|
94
|
+
export function lockToSelections(lock) {
|
|
95
|
+
const selections = {};
|
|
96
|
+
if (!lock) return selections;
|
|
97
|
+
for (const category of LOCK_CATEGORIES) {
|
|
98
|
+
const ids = Object.keys(lock[category] || {});
|
|
99
|
+
if (ids.length) selections[category] = ids;
|
|
100
|
+
}
|
|
101
|
+
return selections;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function itemSourcePath(category, id) {
|
|
105
|
+
const config = categories[category];
|
|
106
|
+
if (FILE_BASED.has(category)) return `${config.sourceDir}/${id}.md`;
|
|
107
|
+
return `${config.sourceDir}/${id}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildComputedHash(files, fallbackId) {
|
|
111
|
+
const keys = Object.keys(files).sort();
|
|
112
|
+
if (keys.length === 0) return hash(fallbackId);
|
|
113
|
+
if (keys.length === 1) return files[keys[0]];
|
|
114
|
+
return hash(keys.map(path => `${path}:${files[path]}`).join('\n'));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getExpectedHash(oldLock, relativePath) {
|
|
118
|
+
const generated = oldLock?.generated?.[relativePath]?.computedHash;
|
|
119
|
+
if (generated) return generated;
|
|
120
|
+
for (const category of LOCK_CATEGORIES) {
|
|
121
|
+
const entries = oldLock?.[category];
|
|
122
|
+
if (!entries) continue;
|
|
123
|
+
for (const entry of Object.values(entries)) {
|
|
124
|
+
const value = entry?.files?.[relativePath];
|
|
125
|
+
if (value) return value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
89
130
|
export async function getPackageVersion() {
|
|
90
131
|
try {
|
|
91
132
|
const pkg = JSON.parse(await readFile(resolve(packageRoot, 'package.json'), 'utf-8'));
|
|
@@ -114,26 +155,63 @@ function validateLock(lock) {
|
|
|
114
155
|
if (!lock || typeof lock !== 'object' || Array.isArray(lock)) {
|
|
115
156
|
throw new Error('Invalid system-prompt lock: expected an object');
|
|
116
157
|
}
|
|
117
|
-
if (
|
|
118
|
-
throw new Error('Invalid system-prompt lock:
|
|
158
|
+
if (lock.selections !== undefined || lock.managedFiles !== undefined) {
|
|
159
|
+
throw new Error('Invalid system-prompt lock: legacy lock format no longer supported');
|
|
119
160
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
161
|
+
if (lock.version !== LOCK_VERSION) {
|
|
162
|
+
throw new Error('Invalid system-prompt lock: unsupported version');
|
|
163
|
+
}
|
|
164
|
+
if (typeof lock.agentType !== 'string' || typeof lock.installedAt !== 'string') {
|
|
165
|
+
throw new Error('Invalid system-prompt lock: missing installation metadata');
|
|
166
|
+
}
|
|
167
|
+
if (typeof lock.includeAgentsMd !== 'boolean') {
|
|
168
|
+
throw new Error('Invalid system-prompt lock: includeAgentsMd must be a boolean');
|
|
169
|
+
}
|
|
170
|
+
const allowed = new Set(['version', 'agentType', 'installedAt', 'includeAgentsMd', 'generated', ...LOCK_CATEGORIES]);
|
|
171
|
+
for (const key of Object.keys(lock)) {
|
|
172
|
+
if (!allowed.has(key)) {
|
|
173
|
+
throw new Error(`Invalid system-prompt lock: unknown key ${key}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const category of LOCK_CATEGORIES) {
|
|
177
|
+
const entries = lock[category];
|
|
178
|
+
if (entries === undefined) continue;
|
|
179
|
+
if (!entries || typeof entries !== 'object' || Array.isArray(entries)) {
|
|
123
180
|
throw new Error(`Invalid system-prompt lock: invalid ${category} selection`);
|
|
124
181
|
}
|
|
182
|
+
const config = categories[category];
|
|
125
183
|
const knownIds = new Set(config.items.map(item => item.id));
|
|
126
|
-
|
|
127
|
-
|
|
184
|
+
for (const [id, entry] of Object.entries(entries)) {
|
|
185
|
+
if (!knownIds.has(id)) {
|
|
186
|
+
throw new Error(`Invalid system-prompt lock: unknown ${category} item`);
|
|
187
|
+
}
|
|
188
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
189
|
+
throw new Error(`Invalid system-prompt lock: invalid ${category} entry`);
|
|
190
|
+
}
|
|
191
|
+
if (typeof entry.source !== 'string' || typeof entry.sourceType !== 'string' || typeof entry.itemPath !== 'string') {
|
|
192
|
+
throw new Error(`Invalid system-prompt lock: invalid ${category} entry source`);
|
|
193
|
+
}
|
|
194
|
+
if (!/^[a-f0-9]{64}$/.test(entry.computedHash || '')) {
|
|
195
|
+
throw new Error(`Invalid system-prompt lock: invalid ${category} entry hash`);
|
|
196
|
+
}
|
|
197
|
+
if (!entry.files || typeof entry.files !== 'object' || Array.isArray(entry.files)) {
|
|
198
|
+
throw new Error(`Invalid system-prompt lock: invalid ${category} entry files`);
|
|
199
|
+
}
|
|
200
|
+
for (const [path, checksum] of Object.entries(entry.files)) {
|
|
201
|
+
if (path.startsWith('/') || path.split('/').includes('..') || !/^[a-f0-9]{64}$/.test(checksum)) {
|
|
202
|
+
throw new Error('Invalid system-prompt lock: unsafe managed file entry');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
128
205
|
}
|
|
129
206
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
207
|
+
const generated = lock.generated;
|
|
208
|
+
if (generated !== undefined) {
|
|
209
|
+
if (!generated || typeof generated !== 'object' || Array.isArray(generated)) {
|
|
210
|
+
throw new Error('Invalid system-prompt lock: generated must be an object');
|
|
133
211
|
}
|
|
134
|
-
for (const [path,
|
|
135
|
-
if (path.startsWith('/') || path.split('/').includes('..') || !/^[a-f0-9]{64}$/.test(
|
|
136
|
-
throw new Error('Invalid system-prompt lock: unsafe
|
|
212
|
+
for (const [path, entry] of Object.entries(generated)) {
|
|
213
|
+
if (path.startsWith('/') || path.split('/').includes('..') || !/^[a-f0-9]{64}$/.test(entry?.computedHash || '')) {
|
|
214
|
+
throw new Error('Invalid system-prompt lock: unsafe generated file entry');
|
|
137
215
|
}
|
|
138
216
|
}
|
|
139
217
|
}
|
|
@@ -152,7 +230,7 @@ async function canWrite(destFile, relativePath, oldLock, force) {
|
|
|
152
230
|
if (force) return true;
|
|
153
231
|
try {
|
|
154
232
|
const existing = await readFile(destFile);
|
|
155
|
-
const previousHash = oldLock
|
|
233
|
+
const previousHash = getExpectedHash(oldLock, relativePath);
|
|
156
234
|
return Boolean(previousHash && previousHash === hash(existing));
|
|
157
235
|
} catch (error) {
|
|
158
236
|
if (error.code === 'ENOENT') return true;
|
|
@@ -160,7 +238,7 @@ async function canWrite(destFile, relativePath, oldLock, force) {
|
|
|
160
238
|
}
|
|
161
239
|
}
|
|
162
240
|
|
|
163
|
-
async function writeManagedFile(destFile, content, relativePath, options) {
|
|
241
|
+
async function writeManagedFile(destFile, content, relativePath, options, owner) {
|
|
164
242
|
await assertSafeDestination(options.targetDir, destFile);
|
|
165
243
|
if (!options.allowExistingMerge && !(await canWrite(destFile, relativePath, options.oldLock, options.force))) {
|
|
166
244
|
console.warn(` ⚠ Preserving existing file: ${relativePath}`);
|
|
@@ -169,10 +247,11 @@ async function writeManagedFile(destFile, content, relativePath, options) {
|
|
|
169
247
|
if (!options.dryRun) await mkdir(dirname(destFile), { recursive: true });
|
|
170
248
|
if (!options.dryRun) await writeFile(destFile, content);
|
|
171
249
|
options.managedFiles[relativePath] = hash(content);
|
|
250
|
+
if (owner) options.fileOwners[relativePath] = owner;
|
|
172
251
|
return true;
|
|
173
252
|
}
|
|
174
253
|
|
|
175
|
-
async function copyDir(src, dest, relativeDir, options) {
|
|
254
|
+
async function copyDir(src, dest, relativeDir, options, owner) {
|
|
176
255
|
if (!options.dryRun) await mkdir(dest, { recursive: true });
|
|
177
256
|
const entries = await readdir(src, { withFileTypes: true });
|
|
178
257
|
|
|
@@ -183,10 +262,10 @@ async function copyDir(src, dest, relativeDir, options) {
|
|
|
183
262
|
const relativePath = `${relativeDir}/${entry.name}`;
|
|
184
263
|
|
|
185
264
|
if (entry.isDirectory()) {
|
|
186
|
-
await copyDir(srcPath, destPath, relativePath, options);
|
|
265
|
+
await copyDir(srcPath, destPath, relativePath, options, owner);
|
|
187
266
|
} else if (entry.isFile()) {
|
|
188
267
|
const content = await readFile(srcPath);
|
|
189
|
-
await writeManagedFile(destPath, content, relativePath, options);
|
|
268
|
+
await writeManagedFile(destPath, content, relativePath, options, owner);
|
|
190
269
|
}
|
|
191
270
|
}
|
|
192
271
|
}
|
|
@@ -241,7 +320,7 @@ async function copySelectedDirs(targetDir, category, selectedIds, options) {
|
|
|
241
320
|
|
|
242
321
|
try {
|
|
243
322
|
await stat(srcPath);
|
|
244
|
-
await copyDir(srcPath, destPath, `${relativeDir}/${id}`, options);
|
|
323
|
+
await copyDir(srcPath, destPath, `${relativeDir}/${id}`, options, { category, id });
|
|
245
324
|
} catch (error) {
|
|
246
325
|
if (sourceMissing(error)) {
|
|
247
326
|
console.warn(` ⚠ Source not found: ${catConfig.sourceDir}/${id}`);
|
|
@@ -268,7 +347,7 @@ async function copySelectedFiles(targetDir, category, selectedIds, options) {
|
|
|
268
347
|
await assertSafeDestination(targetDir, destFile);
|
|
269
348
|
try {
|
|
270
349
|
const content = await readFile(srcFile);
|
|
271
|
-
await writeManagedFile(destFile, content, `${relativeDir}/${id}.md`, options);
|
|
350
|
+
await writeManagedFile(destFile, content, `${relativeDir}/${id}.md`, options, { category, id });
|
|
272
351
|
} catch (error) {
|
|
273
352
|
if (sourceMissing(error)) {
|
|
274
353
|
console.warn(` ⚠ Source not found: ${catConfig.sourceDir}/${id}.md`);
|
|
@@ -283,7 +362,6 @@ async function deleteSelectedItems(absTarget, category, ids, oldLock, force, dry
|
|
|
283
362
|
const catConfig = categories[category];
|
|
284
363
|
if (!catConfig || !ids?.length) return;
|
|
285
364
|
|
|
286
|
-
const FILE_BASED = new Set(['agents', 'commands', 'memory', 'modes', 'standards', 'templates']);
|
|
287
365
|
if (!FILE_BASED.has(category) && category !== 'skills' && category !== 'styles') return;
|
|
288
366
|
|
|
289
367
|
const relativeDir = targetSubdir(catConfig.sourceDir);
|
|
@@ -294,8 +372,8 @@ async function deleteSelectedItems(absTarget, category, ids, oldLock, force, dry
|
|
|
294
372
|
const destPath = resolve(absTarget, relativePath);
|
|
295
373
|
await assertSafeDestination(absTarget, destPath);
|
|
296
374
|
if (!force && oldLock) {
|
|
297
|
-
const
|
|
298
|
-
|
|
375
|
+
const oldFiles = oldLock?.[category]?.[id]?.files || {};
|
|
376
|
+
const managedEntries = Object.entries(oldFiles);
|
|
299
377
|
if (managedEntries.length === 0) {
|
|
300
378
|
console.warn(` ⚠ Preserving unmanaged item: ${relativePath}`);
|
|
301
379
|
continue;
|
|
@@ -363,7 +441,7 @@ async function mergeJsonFile(destFile, generated, relativePath, options, previou
|
|
|
363
441
|
return writeManagedFile(destFile, Buffer.from(JSON.stringify(merged, null, 4)), relativePath, {
|
|
364
442
|
...options,
|
|
365
443
|
allowExistingMerge: !options.oldLock,
|
|
366
|
-
});
|
|
444
|
+
}, { generated: true });
|
|
367
445
|
}
|
|
368
446
|
|
|
369
447
|
async function mergeGitignore(destFile, options) {
|
|
@@ -378,7 +456,7 @@ async function mergeGitignore(destFile, options) {
|
|
|
378
456
|
return writeManagedFile(destFile, Buffer.from(`${[...lines].join('\n')}\n`), '.gitignore', {
|
|
379
457
|
...options,
|
|
380
458
|
allowExistingMerge: !options.oldLock,
|
|
381
|
-
});
|
|
459
|
+
}, { generated: true });
|
|
382
460
|
}
|
|
383
461
|
|
|
384
462
|
export function validateSelections(selections) {
|
|
@@ -405,8 +483,27 @@ export async function install({ targetDir, agentType, selections, includeAgentsM
|
|
|
405
483
|
oldLock,
|
|
406
484
|
force,
|
|
407
485
|
dryRun,
|
|
408
|
-
managedFiles: {
|
|
486
|
+
managedFiles: {},
|
|
487
|
+
fileOwners: {},
|
|
409
488
|
};
|
|
489
|
+
if (oldLock) {
|
|
490
|
+
for (const category of LOCK_CATEGORIES) {
|
|
491
|
+
for (const [id, entry] of Object.entries(oldLock[category] || {})) {
|
|
492
|
+
for (const [path, checksum] of Object.entries(entry.files || {})) {
|
|
493
|
+
if (!(path in options.managedFiles)) {
|
|
494
|
+
options.managedFiles[path] = checksum;
|
|
495
|
+
options.fileOwners[path] = { category, id };
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const [path, entry] of Object.entries(oldLock.generated || {})) {
|
|
501
|
+
if (!(path in options.managedFiles)) {
|
|
502
|
+
options.managedFiles[path] = entry.computedHash;
|
|
503
|
+
options.fileOwners[path] = { generated: true };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
410
507
|
|
|
411
508
|
// Delete items that are no longer selected.
|
|
412
509
|
if (oldSelections) {
|
|
@@ -428,7 +525,7 @@ export async function install({ targetDir, agentType, selections, includeAgentsM
|
|
|
428
525
|
await Promise.all(tasks);
|
|
429
526
|
|
|
430
527
|
if (writeAgentsMd) {
|
|
431
|
-
await writeManagedFile(resolve(absTarget, 'AGENTS.md'), Buffer.from(AGENTS_MD), 'AGENTS.md', options);
|
|
528
|
+
await writeManagedFile(resolve(absTarget, 'AGENTS.md'), Buffer.from(AGENTS_MD), 'AGENTS.md', options, { generated: true });
|
|
432
529
|
}
|
|
433
530
|
|
|
434
531
|
if (agentType === 'opencode') {
|
|
@@ -437,14 +534,15 @@ export async function install({ targetDir, agentType, selections, includeAgentsM
|
|
|
437
534
|
let previousOpenCodeConfig;
|
|
438
535
|
let previousTuiConfig;
|
|
439
536
|
if (oldLock) {
|
|
537
|
+
const oldSelections = lockToSelections(oldLock);
|
|
440
538
|
let previousMcpEntries = {};
|
|
441
|
-
if (
|
|
539
|
+
if (oldSelections.mcps?.length) previousMcpEntries = await loadMcpConfigs(oldSelections.mcps);
|
|
442
540
|
previousOpenCodeConfig = JSON.parse(generateOpenCodeConfig({
|
|
443
|
-
selections:
|
|
541
|
+
selections: oldSelections,
|
|
444
542
|
mcpEntries: previousMcpEntries,
|
|
445
543
|
includeAgentsMd: oldLock.includeAgentsMd ?? true,
|
|
446
544
|
}));
|
|
447
|
-
previousTuiConfig = JSON.parse(generateTuiConfig({ selections:
|
|
545
|
+
previousTuiConfig = JSON.parse(generateTuiConfig({ selections: oldSelections }));
|
|
448
546
|
}
|
|
449
547
|
const configJson = generateOpenCodeConfig({ selections, mcpEntries, includeAgentsMd });
|
|
450
548
|
await mergeJsonFile(resolve(absTarget, 'opencode.json'), JSON.parse(configJson), 'opencode.json', options, previousOpenCodeConfig);
|
|
@@ -458,16 +556,38 @@ export async function install({ targetDir, agentType, selections, includeAgentsM
|
|
|
458
556
|
}
|
|
459
557
|
|
|
460
558
|
if (dryRun) return absTarget;
|
|
461
|
-
const
|
|
559
|
+
const pkgVersion = await getPackageVersion();
|
|
560
|
+
const source = `system-prompt@${pkgVersion}`;
|
|
462
561
|
const lockData = {
|
|
463
|
-
version,
|
|
562
|
+
version: LOCK_VERSION,
|
|
464
563
|
agentType,
|
|
465
|
-
targetDir,
|
|
466
564
|
installedAt: new Date().toISOString(),
|
|
467
|
-
selections: Object.fromEntries(Object.entries(selections).map(([k, v]) => [k, [...v]])),
|
|
468
565
|
includeAgentsMd,
|
|
469
|
-
managedFiles: options.managedFiles,
|
|
470
566
|
};
|
|
567
|
+
for (const category of LOCK_CATEGORIES) {
|
|
568
|
+
const entries = {};
|
|
569
|
+
for (const id of selections[category] || []) {
|
|
570
|
+
if (await isRemoved(category, id)) continue;
|
|
571
|
+
const files = {};
|
|
572
|
+
for (const [path, checksum] of Object.entries(options.managedFiles)) {
|
|
573
|
+
const owner = options.fileOwners[path];
|
|
574
|
+
if (owner?.category === category && owner?.id === id) files[path] = checksum;
|
|
575
|
+
}
|
|
576
|
+
entries[id] = {
|
|
577
|
+
source,
|
|
578
|
+
sourceType: 'bundled',
|
|
579
|
+
itemPath: itemSourcePath(category, id),
|
|
580
|
+
computedHash: buildComputedHash(files, id),
|
|
581
|
+
files,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
lockData[category] = entries;
|
|
585
|
+
}
|
|
586
|
+
const generated = {};
|
|
587
|
+
for (const [path, checksum] of Object.entries(options.managedFiles)) {
|
|
588
|
+
if (options.fileOwners[path]?.generated) generated[path] = { computedHash: checksum };
|
|
589
|
+
}
|
|
590
|
+
lockData.generated = generated;
|
|
471
591
|
await writeFile(resolve(absTarget, 'system-prompt-lock.json'), JSON.stringify(lockData, null, 2));
|
|
472
592
|
return absTarget;
|
|
473
593
|
}
|
|
@@ -548,4 +668,5 @@ async function writeMergedEnv(absTarget, examples, options) {
|
|
|
548
668
|
const content = Buffer.from(`${existingContent}${separator}${additions.join('\n')}\n`);
|
|
549
669
|
if (!options.dryRun) await writeFile(envPath, content);
|
|
550
670
|
options.managedFiles['.env'] = hash(content);
|
|
671
|
+
options.fileOwners['.env'] = { generated: true };
|
|
551
672
|
}
|