@boopkit/cli 0.6.13 → 0.6.15
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/README.md +1 -1
- package/dist/capability-versions.json +16 -16
- package/dist/cli.js +7 -0
- package/dist/commands/init.js +2 -1
- package/dist/commands/migrate-skill-edits.d.ts +24 -0
- package/dist/commands/migrate-skill-edits.js +190 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +13 -0
- package/dist/commands/update.js +84 -7
- package/dist/lib/apply-skill-edits.d.ts +76 -0
- package/dist/lib/apply-skill-edits.js +307 -0
- package/dist/lib/execute.js +4 -12
- package/dist/lib/original-text.d.ts +43 -0
- package/dist/lib/original-text.js +165 -0
- package/dist/lib/scaffold.d.ts +53 -0
- package/dist/lib/scaffold.js +134 -1
- package/dist/lib/script-merge.d.ts +55 -0
- package/dist/lib/script-merge.js +144 -0
- package/dist/lib/skill-edits.d.ts +88 -0
- package/dist/lib/skill-edits.js +293 -0
- package/dist/lib/stale-scripts.d.ts +3 -2
- package/dist/lib/stale-scripts.js +3 -2
- package/dist/lib/update.d.ts +45 -2
- package/dist/lib/update.js +207 -44
- package/docs/skills.md +1 -0
- package/package.json +1 -1
- package/src/templates/skill-project/PROJECT.md +28 -0
- package/dist/commands/loop.js.orig-pre-maxbuffer +0 -1455
- package/dist/commands/status.test.d.ts +0 -1
- package/dist/commands/status.test.js +0 -118
- package/dist/lib/project.test.d.ts +0 -1
- package/dist/lib/project.test.js +0 -118
package/dist/lib/update.js
CHANGED
|
@@ -39,9 +39,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.SKILL_EDITS_INSTRUCTION = exports.TRIAGE_SKILL = void 0;
|
|
42
43
|
exports.compareFileHash = compareFileHash;
|
|
43
44
|
exports.planCapabilityUpdate = planCapabilityUpdate;
|
|
44
45
|
exports.executeUpdatePlan = executeUpdatePlan;
|
|
46
|
+
exports.countSkillEdits = countSkillEdits;
|
|
45
47
|
exports.formatUpdateSummary = formatUpdateSummary;
|
|
46
48
|
const fs = __importStar(require("fs"));
|
|
47
49
|
const path = __importStar(require("path"));
|
|
@@ -50,6 +52,11 @@ const capability_1 = require("./capability");
|
|
|
50
52
|
const project_1 = require("./project");
|
|
51
53
|
const safe_io_1 = require("./safe-io");
|
|
52
54
|
const install_layout_1 = require("./install-layout");
|
|
55
|
+
const script_merge_1 = require("./script-merge");
|
|
56
|
+
/** The skill that splits a drifted SKILL.md — keep in step with the folder name boop-rm2l.8 gives it */
|
|
57
|
+
exports.TRIAGE_SKILL = 'boop-skills-edit-triage';
|
|
58
|
+
/** What to do about skills edited in SKILL.md, for a report that counts them */
|
|
59
|
+
exports.SKILL_EDITS_INSTRUCTION = `Move each skill's edits into the PROJECT.md beside it: run \`boop migrate-skill-edits\` and follow the ${exports.TRIAGE_SKILL} skill.`;
|
|
53
60
|
/**
|
|
54
61
|
* Compare a file's current hash against a stored hash.
|
|
55
62
|
*/
|
|
@@ -81,6 +88,10 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
81
88
|
if (!boopkit?.capability)
|
|
82
89
|
return null;
|
|
83
90
|
const newVersion = pkgJson.version || '0.0.0';
|
|
91
|
+
// The capability as this package declares it: the npm scripts merged below,
|
|
92
|
+
// and the skill migrations applied further down.
|
|
93
|
+
const capJsonPath = path.join(pkgDir, 'capability.json');
|
|
94
|
+
const capJson = fs.existsSync(capJsonPath) ? JSON.parse(fs.readFileSync(capJsonPath, 'utf-8')) : {};
|
|
84
95
|
const meta = projectManifest.capabilityMeta?.[capabilityName];
|
|
85
96
|
const storedHashes = meta?.fileHashes || {};
|
|
86
97
|
const actions = [];
|
|
@@ -96,9 +107,22 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
96
107
|
const destPath = path.join(targetDir, relPath);
|
|
97
108
|
const storedHash = storedHashes[relPath];
|
|
98
109
|
const newHash = (0, scaffold_1.hashFile)(srcPath);
|
|
110
|
+
const fileKind = fileKindOf(relPath);
|
|
99
111
|
if (!storedHash) {
|
|
100
112
|
// New file — not in stored hashes
|
|
101
|
-
if (fs.existsSync(destPath)) {
|
|
113
|
+
if (fs.existsSync(destPath) && (0, scaffold_1.hashFile)(destPath) === newHash) {
|
|
114
|
+
// Untracked but already holds what boop ships, as a hand-split skill does — start tracking it
|
|
115
|
+
actions.push({
|
|
116
|
+
relativePath: relPath,
|
|
117
|
+
action: 'record-hash',
|
|
118
|
+
reason: 'Locally matches package',
|
|
119
|
+
currentHash: newHash,
|
|
120
|
+
newHash,
|
|
121
|
+
drift: 'already-upstream',
|
|
122
|
+
fileKind,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else if (fs.existsSync(destPath)) {
|
|
102
126
|
// File exists but wasn't tracked — treat as modified (skip)
|
|
103
127
|
actions.push({
|
|
104
128
|
relativePath: relPath,
|
|
@@ -106,6 +130,8 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
106
130
|
reason: 'File exists but has no stored hash (untracked)',
|
|
107
131
|
currentHash: (0, scaffold_1.hashFile)(destPath),
|
|
108
132
|
newHash,
|
|
133
|
+
fileKind,
|
|
134
|
+
...skipInstruction(targetDir, relPath, srcPath, fileKind),
|
|
109
135
|
});
|
|
110
136
|
}
|
|
111
137
|
else {
|
|
@@ -114,6 +140,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
114
140
|
action: 'add',
|
|
115
141
|
reason: 'New file from package',
|
|
116
142
|
newHash,
|
|
143
|
+
fileKind,
|
|
117
144
|
});
|
|
118
145
|
}
|
|
119
146
|
continue;
|
|
@@ -134,19 +161,40 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
134
161
|
storedHash,
|
|
135
162
|
currentHash: storedHash,
|
|
136
163
|
newHash,
|
|
164
|
+
fileKind,
|
|
165
|
+
instruction: fileKind === 'skill' ? offeredRulesInstruction(targetDir, relPath) : undefined,
|
|
137
166
|
});
|
|
138
167
|
break;
|
|
139
|
-
case 'modified':
|
|
140
|
-
|
|
168
|
+
case 'modified': {
|
|
169
|
+
const currentHash = (0, scaffold_1.hashFile)(destPath);
|
|
170
|
+
if (currentHash === newHash) {
|
|
171
|
+
// Already holds what boop ships — move the base forward, or it reads as modified forever
|
|
172
|
+
actions.push({
|
|
173
|
+
relativePath: relPath,
|
|
174
|
+
action: 'record-hash',
|
|
175
|
+
reason: 'Locally matches package',
|
|
176
|
+
storedHash,
|
|
177
|
+
currentHash,
|
|
178
|
+
newHash,
|
|
179
|
+
drift: 'already-upstream',
|
|
180
|
+
fileKind,
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
const drift = newHash === storedHash ? 'project-only' : 'both-changed';
|
|
141
185
|
actions.push({
|
|
142
186
|
relativePath: relPath,
|
|
143
187
|
action: 'skip',
|
|
144
188
|
reason: 'Locally modified',
|
|
145
189
|
storedHash,
|
|
146
|
-
currentHash
|
|
190
|
+
currentHash,
|
|
147
191
|
newHash,
|
|
192
|
+
drift,
|
|
193
|
+
fileKind,
|
|
194
|
+
...skipInstruction(targetDir, relPath, srcPath, fileKind, drift),
|
|
148
195
|
});
|
|
149
196
|
break;
|
|
197
|
+
}
|
|
150
198
|
case 'missing':
|
|
151
199
|
// File was deleted locally — respect user's deletion, skip by default
|
|
152
200
|
actions.push({
|
|
@@ -155,6 +203,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
155
203
|
reason: 'Locally deleted',
|
|
156
204
|
storedHash,
|
|
157
205
|
newHash,
|
|
206
|
+
fileKind,
|
|
158
207
|
});
|
|
159
208
|
break;
|
|
160
209
|
}
|
|
@@ -171,53 +220,59 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
171
220
|
}
|
|
172
221
|
}
|
|
173
222
|
// Apply skill migration logic: upgrade deprecate+add pairs to migrate actions
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// Change deprecate to skip for the old file
|
|
196
|
-
const oldAction = actions.find(a => a.relativePath === oldRelPath);
|
|
197
|
-
if (oldAction) {
|
|
198
|
-
oldAction.action = 'skip';
|
|
199
|
-
oldAction.reason = 'Locally modified (migration skipped)';
|
|
223
|
+
const migrations = capJson.skill_migrations;
|
|
224
|
+
if (migrations) {
|
|
225
|
+
for (const [newSkillName, oldFlatFilename] of Object.entries(migrations)) {
|
|
226
|
+
const oldRelPath = path.join('skills', oldFlatFilename);
|
|
227
|
+
const newPrefix = path.join('skills', newSkillName) + path.sep;
|
|
228
|
+
const oldStoredHash = storedHashes[oldRelPath];
|
|
229
|
+
if (!oldStoredHash)
|
|
230
|
+
continue;
|
|
231
|
+
const oldFilePath = path.join(targetDir, oldRelPath);
|
|
232
|
+
const oldState = compareFileHash(oldFilePath, oldStoredHash);
|
|
233
|
+
if (oldState === 'modified') {
|
|
234
|
+
// User modified the old file — keep it, and don't install the folder that replaces it.
|
|
235
|
+
// A folder file already installed has its own comparison, so only an install is held back.
|
|
236
|
+
for (const action of actions) {
|
|
237
|
+
if (action.action === 'add' && (action.relativePath.startsWith(newPrefix) || action.relativePath === path.join('skills', newSkillName))) {
|
|
238
|
+
action.action = 'skip';
|
|
239
|
+
action.reason = `Skipped: old skill file '${oldRelPath}' was locally modified`;
|
|
240
|
+
action.oldPath = oldRelPath;
|
|
241
|
+
action.instruction = `boop installs this once ${oldRelPath} is gone; see what to do about that file.`;
|
|
200
242
|
}
|
|
201
243
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
244
|
+
// Change deprecate to skip for the old file
|
|
245
|
+
const oldAction = actions.find(a => a.relativePath === oldRelPath);
|
|
246
|
+
if (oldAction) {
|
|
247
|
+
const projectFile = path.join('skills', newSkillName, 'PROJECT.md');
|
|
248
|
+
oldAction.action = 'skip';
|
|
249
|
+
oldAction.reason = 'Locally modified (migration skipped)';
|
|
250
|
+
oldAction.replacedBy = newSkillName;
|
|
251
|
+
oldAction.instruction = `This is the old single-file form of the ${newSkillName} skill, and this project edited it. ` +
|
|
252
|
+
`Move the rules this project needs from it into ${projectFile}, following the ${exports.TRIAGE_SKILL} skill, then delete it and run \`boop update\`.`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (oldState === 'match') {
|
|
256
|
+
// Old file unmodified — convert deprecate to migrate
|
|
257
|
+
const oldAction = actions.find(a => a.relativePath === oldRelPath);
|
|
258
|
+
if (oldAction) {
|
|
259
|
+
oldAction.action = 'migrate';
|
|
260
|
+
oldAction.reason = `Migrated to folder: skills/${newSkillName}/`;
|
|
261
|
+
}
|
|
262
|
+
// Tag new folder files with the old path they're replacing
|
|
263
|
+
for (const action of actions) {
|
|
264
|
+
if (action.relativePath.startsWith(newPrefix) || action.relativePath === path.join('skills', newSkillName)) {
|
|
265
|
+
action.oldPath = oldRelPath;
|
|
214
266
|
}
|
|
215
267
|
}
|
|
216
|
-
// oldState === 'missing': old file already gone, add new files normally
|
|
217
268
|
}
|
|
269
|
+
// oldState === 'missing': old file already gone, add new files normally
|
|
218
270
|
}
|
|
219
271
|
}
|
|
220
|
-
|
|
272
|
+
// A capability that ships a new command ships it as a file and a script; an
|
|
273
|
+
// update that delivered only the file left nothing able to run it (boop-ldou).
|
|
274
|
+
const scriptActions = (0, script_merge_1.planScriptUpdates)(capJson.scripts, (0, script_merge_1.readProjectScripts)(targetDir));
|
|
275
|
+
const upToDate = actions.length === 0 && !scriptActions.some(a => a.action === 'add');
|
|
221
276
|
return {
|
|
222
277
|
capability: capabilityName,
|
|
223
278
|
targetDir,
|
|
@@ -225,6 +280,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
225
280
|
currentVersion: meta?.version,
|
|
226
281
|
newVersion,
|
|
227
282
|
actions,
|
|
283
|
+
scriptActions,
|
|
228
284
|
unchangedCount,
|
|
229
285
|
upToDate,
|
|
230
286
|
};
|
|
@@ -244,7 +300,10 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
244
300
|
skippedDeleted: [],
|
|
245
301
|
pruned: [],
|
|
246
302
|
migrated: [],
|
|
303
|
+
scriptsAdded: [],
|
|
304
|
+
scriptsKept: [],
|
|
247
305
|
unchangedCount: plan.unchangedCount,
|
|
306
|
+
instructions: [],
|
|
248
307
|
};
|
|
249
308
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(plan.packageDir, 'package.json'), 'utf-8'));
|
|
250
309
|
const boopkit = pkgJson.boopkit;
|
|
@@ -264,9 +323,16 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
264
323
|
fs.copyFileSync(srcPath, destPath);
|
|
265
324
|
newHashes[action.relativePath] = (0, scaffold_1.hashFile)(destPath);
|
|
266
325
|
result.overwritten.push(action.relativePath);
|
|
326
|
+
if (action.instruction) {
|
|
327
|
+
result.instructions.push({ relativePath: action.relativePath, instruction: action.instruction });
|
|
328
|
+
}
|
|
267
329
|
}
|
|
268
330
|
break;
|
|
269
331
|
}
|
|
332
|
+
case 'record-hash':
|
|
333
|
+
newHashes[action.relativePath] = action.newHash;
|
|
334
|
+
result.unchangedCount++;
|
|
335
|
+
break;
|
|
270
336
|
case 'skip': {
|
|
271
337
|
const isLocallyDeleted = action.reason === 'Locally deleted';
|
|
272
338
|
if (isLocallyDeleted && options.restore) {
|
|
@@ -291,6 +357,10 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
291
357
|
newHashes[action.relativePath] = (0, scaffold_1.hashFile)(destPath);
|
|
292
358
|
result.overwritten.push(action.relativePath);
|
|
293
359
|
result.forcedOverwrite.push(action.relativePath);
|
|
360
|
+
const instruction = action.fileKind === 'skill' ? offeredRulesInstruction(plan.targetDir, action.relativePath) : undefined;
|
|
361
|
+
if (instruction) {
|
|
362
|
+
result.instructions.push({ relativePath: action.relativePath, instruction });
|
|
363
|
+
}
|
|
294
364
|
console.warn(`\x1b[33mWARN\x1b[0m Force-overwriting modified file: ${action.relativePath} (backup saved as ${action.relativePath}.bak)`);
|
|
295
365
|
}
|
|
296
366
|
}
|
|
@@ -299,6 +369,13 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
299
369
|
if (isLocallyDeleted) {
|
|
300
370
|
result.skippedDeleted.push(action.relativePath);
|
|
301
371
|
}
|
|
372
|
+
if (action.instruction) {
|
|
373
|
+
result.instructions.push({
|
|
374
|
+
relativePath: action.relativePath,
|
|
375
|
+
instruction: action.instruction,
|
|
376
|
+
upstreamPath: action.upstreamPath,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
302
379
|
// Keep existing hash unchanged
|
|
303
380
|
}
|
|
304
381
|
break;
|
|
@@ -341,6 +418,11 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
341
418
|
}
|
|
342
419
|
}
|
|
343
420
|
}
|
|
421
|
+
// Merge the capability's npm scripts, so a command it ships as file + script
|
|
422
|
+
// arrives whole. Additive only: a name the project already uses is left alone.
|
|
423
|
+
const scriptActions = plan.scriptActions ?? [];
|
|
424
|
+
result.scriptsAdded = (0, script_merge_1.applyScriptUpdates)(plan.targetDir, scriptActions);
|
|
425
|
+
result.scriptsKept = scriptActions.filter(a => a.action === 'keep');
|
|
344
426
|
// Update .boop/config.json
|
|
345
427
|
if (manifest) {
|
|
346
428
|
if (!manifest.capabilityMeta) {
|
|
@@ -355,6 +437,79 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
355
437
|
}
|
|
356
438
|
return result;
|
|
357
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* What to tell whoever ran the update about a file it skipped, or nothing.
|
|
442
|
+
* `drift` is absent for a file boop has no stored hash for.
|
|
443
|
+
*
|
|
444
|
+
* An edited SKILL.md always gets an instruction: any edit there is project
|
|
445
|
+
* content in boop's file. An edited reference page or code file is doing what
|
|
446
|
+
* those files are for, so it gets one only when boop has changed it too.
|
|
447
|
+
*/
|
|
448
|
+
function skipInstruction(targetDir, relPath, srcPath, fileKind, drift) {
|
|
449
|
+
const upstreamPath = displayPath(targetDir, srcPath);
|
|
450
|
+
if (!drift) {
|
|
451
|
+
const untracked = `boop has no record of installing this file, so it cannot tell this project's changes from its own. boop's current copy is at ${upstreamPath}`;
|
|
452
|
+
if (fileKind === 'skill') {
|
|
453
|
+
const projectFile = path.join(path.dirname(relPath), 'PROJECT.md');
|
|
454
|
+
return {
|
|
455
|
+
instruction: `${untracked}. Split it by hand: move this project's rules into ${projectFile}, following the ${exports.TRIAGE_SKILL} skill, then copy boop's copy into place, and the next update tracks it.`,
|
|
456
|
+
upstreamPath,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return { instruction: `${untracked}; compare the two by hand.`, upstreamPath };
|
|
460
|
+
}
|
|
461
|
+
if (fileKind === 'skill') {
|
|
462
|
+
const projectFile = path.join(path.dirname(relPath), 'PROJECT.md');
|
|
463
|
+
return {
|
|
464
|
+
instruction: `Move this project's edits into ${projectFile}: run \`boop migrate-skill-edits\` and follow the ${exports.TRIAGE_SKILL} skill.`,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (drift !== 'both-changed')
|
|
468
|
+
return {};
|
|
469
|
+
const advice = fileKind === 'reference'
|
|
470
|
+
? 'Blend in what applies to this project, following the boop-product-design-reference-integration skill.'
|
|
471
|
+
: 'Compare the two and take what applies.';
|
|
472
|
+
return { instruction: `boop's current copy is at ${upstreamPath}. ${advice}`, upstreamPath };
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* When an update replaces a SKILL.md whose PROJECT.md offers rules to boop,
|
|
476
|
+
* boop's new text may now cover them, and a rule left behind would duplicate it.
|
|
477
|
+
*/
|
|
478
|
+
function offeredRulesInstruction(targetDir, relPath) {
|
|
479
|
+
const projectFile = path.join(path.dirname(relPath), 'PROJECT.md');
|
|
480
|
+
if (!(0, scaffold_1.hasOfferedRules)(path.join(targetDir, projectFile)))
|
|
481
|
+
return undefined;
|
|
482
|
+
return `boop replaced this skill, and ${projectFile} offers rules to boop. Check whether boop's new text covers each rule under "Offered to boop", and delete any it does.`;
|
|
483
|
+
}
|
|
484
|
+
/** A path relative to the project when it lies inside it, as a package in node_modules does */
|
|
485
|
+
function displayPath(targetDir, filePath) {
|
|
486
|
+
const relative = path.relative(targetDir, filePath);
|
|
487
|
+
return relative.startsWith('..') || path.isAbsolute(relative) ? filePath : relative;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Count the skills a capability's project copy has edited in SKILL.md, and how
|
|
491
|
+
* many of those boop has changed since — from the same comparison `boop update`
|
|
492
|
+
* makes. Null when there are none, or the capability has no package to compare with.
|
|
493
|
+
*/
|
|
494
|
+
function countSkillEdits(targetDir, capabilityName, projectManifest) {
|
|
495
|
+
const plan = planCapabilityUpdate(targetDir, capabilityName, projectManifest);
|
|
496
|
+
const edited = (plan?.actions ?? []).filter(a => a.action === 'skip' && a.fileKind === 'skill' && (a.drift === 'project-only' || a.drift === 'both-changed'));
|
|
497
|
+
if (edited.length === 0)
|
|
498
|
+
return null;
|
|
499
|
+
return { edited: edited.length, changesWaiting: edited.filter(a => a.drift === 'both-changed').length };
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Classify a project-relative destination path: a skill's SKILL.md, a
|
|
503
|
+
* reference page, or anything else (code, views, config).
|
|
504
|
+
*/
|
|
505
|
+
function fileKindOf(relativePath) {
|
|
506
|
+
const segments = relativePath.split(path.sep);
|
|
507
|
+
if (segments.length === 3 && segments[0] === 'skills' && segments[2] === 'SKILL.md')
|
|
508
|
+
return 'skill';
|
|
509
|
+
if (relativePath.endsWith('.ref.md'))
|
|
510
|
+
return 'reference';
|
|
511
|
+
return 'code';
|
|
512
|
+
}
|
|
358
513
|
/**
|
|
359
514
|
* Resolve the source path a project-relative file was installed from — the
|
|
360
515
|
* placement list read backwards, so it cannot disagree with what was copied.
|
|
@@ -388,6 +543,14 @@ function formatUpdateSummary(result) {
|
|
|
388
543
|
if (result.added.length > 0) {
|
|
389
544
|
parts.push(`added ${result.added.length} new file${result.added.length === 1 ? '' : 's'}`);
|
|
390
545
|
}
|
|
546
|
+
const scriptsAdded = result.scriptsAdded?.length || 0;
|
|
547
|
+
if (scriptsAdded > 0) {
|
|
548
|
+
parts.push(`added ${scriptsAdded} script${scriptsAdded === 1 ? '' : 's'}`);
|
|
549
|
+
}
|
|
550
|
+
const scriptsKept = result.scriptsKept?.length || 0;
|
|
551
|
+
if (scriptsKept > 0) {
|
|
552
|
+
parts.push(`kept ${scriptsKept} customised script${scriptsKept === 1 ? '' : 's'}`);
|
|
553
|
+
}
|
|
391
554
|
if (result.pruned && result.pruned.length > 0) {
|
|
392
555
|
parts.push(`pruned ${result.pruned.length} deprecated file${result.pruned.length === 1 ? '' : 's'}`);
|
|
393
556
|
}
|
package/docs/skills.md
CHANGED
|
@@ -38,6 +38,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
38
38
|
- **boop-get-smart** — When there is a recurring fault in your own behaviour during a chat, run the five whys to find its root cause, and agree a change with the operator that makes it less likely.
|
|
39
39
|
- **boop-so-what** — When the operator asks for the so-what, or says some version of 'thanks for the essay', compress what you just produced into three sentences about what they should care about.
|
|
40
40
|
- **boop-skills-hyperlinking** — When you need to add, review, or repair a `## Related` section that points one skill at another, follow these rules for what earns a link, how to phrase it, which way it points, how many are too many, and how to write a path that survives `boop add`.
|
|
41
|
+
- **boop-skills-edit-triage** — When you need to move a project's edits out of a boop skill's SKILL.md — because `boop update` or `boop status` reports the skill as edited here, or `boop migrate-skill-edits` wrote a plan for it — decide whether each edit is kept in this project, offered to boop, or dropped, and rewrite each kept edit as a standalone rule for the PROJECT.md beside it. Not for reference pages, which are blended rather than split.
|
|
41
42
|
|
|
42
43
|
### `testing` — Jest testing setup
|
|
43
44
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# How this project applies this skill
|
|
2
|
+
|
|
3
|
+
This file belongs to the project.
|
|
4
|
+
`SKILL.md`, beside it, belongs to boop and is replaced on every `boop update`, so a rule written there is lost.
|
|
5
|
+
boop created this file and never tracks, updates or overwrites it, so write this project's rules here.
|
|
6
|
+
Where this file and `SKILL.md` conflict, this file wins.
|
|
7
|
+
|
|
8
|
+
Put each rule under the heading that describes it.
|
|
9
|
+
To decide between this file and `SKILL.md`, ask whether the rule would still be true in a brand-new boop project scaffolded tomorrow.
|
|
10
|
+
If it would not, it belongs here.
|
|
11
|
+
If it would, it belongs under "Offered to boop".
|
|
12
|
+
|
|
13
|
+
## Adds
|
|
14
|
+
|
|
15
|
+
Rules this project follows that the skill's guidance does not cover.
|
|
16
|
+
|
|
17
|
+
## Narrows
|
|
18
|
+
|
|
19
|
+
Where the skill offers a choice, the one this project always makes.
|
|
20
|
+
|
|
21
|
+
## Overrides
|
|
22
|
+
|
|
23
|
+
Where this project deliberately contradicts the skill's guidance, and why.
|
|
24
|
+
|
|
25
|
+
## Offered to boop
|
|
26
|
+
|
|
27
|
+
Rules that apply here and that this project thinks boop should adopt for every project.
|
|
28
|
+
They stay in effect here until boop ships them.
|