@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
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The mechanical half of splitting a drifted skill: find each skill whose
|
|
4
|
+
* SKILL.md the project edited, recover the text boop installed, list every
|
|
5
|
+
* edit, and write one plan per skill for an agent to decide. Nothing here
|
|
6
|
+
* decides where an edit belongs.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.SKILL_EDITS_DIR = void 0;
|
|
46
|
+
exports.writeSkillEditPlans = writeSkillEditPlans;
|
|
47
|
+
exports.diffLines = diffLines;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
51
|
+
const original_text_1 = require("./original-text");
|
|
52
|
+
const safe_io_1 = require("./safe-io");
|
|
53
|
+
const install_layout_1 = require("./install-layout");
|
|
54
|
+
const update_1 = require("./update");
|
|
55
|
+
/** Where plans are written, relative to the project. Everything in `.boop/` but config.json is gitignored. */
|
|
56
|
+
exports.SKILL_EDITS_DIR = path.join('.boop', 'skill-edits');
|
|
57
|
+
/**
|
|
58
|
+
* Find every skill edited in SKILL.md across the project's capabilities and
|
|
59
|
+
* write a plan for each to `.boop/skill-edits/<skill-name>.md`.
|
|
60
|
+
*
|
|
61
|
+
* A plan already written for the current project copy and boop copy is left
|
|
62
|
+
* alone, because it may hold decisions. One written for other text is backed
|
|
63
|
+
* up and rewritten. A skill boop has no stored hash for gets no plan.
|
|
64
|
+
*/
|
|
65
|
+
function writeSkillEditPlans(projectDir, manifest, options = {}) {
|
|
66
|
+
const outcomes = [];
|
|
67
|
+
for (const capability of manifest.capabilities) {
|
|
68
|
+
const plan = (0, update_1.planCapabilityUpdate)(projectDir, capability, manifest);
|
|
69
|
+
for (const action of plan?.actions ?? []) {
|
|
70
|
+
if (action.fileKind !== 'skill' || action.action !== 'skip')
|
|
71
|
+
continue;
|
|
72
|
+
const skill = path.basename(path.dirname(action.relativePath));
|
|
73
|
+
if (!action.storedHash) {
|
|
74
|
+
// Untracked; a copy identical to boop's holds nothing to split
|
|
75
|
+
if (action.currentHash && action.currentHash !== action.newHash) {
|
|
76
|
+
outcomes.push({ kind: 'hand-split', skill, relativePath: action.relativePath, upstreamPath: action.upstreamPath });
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (action.drift !== 'project-only' && action.drift !== 'both-changed')
|
|
81
|
+
continue;
|
|
82
|
+
outcomes.push(writePlan(projectDir, capability, skill, action, plan.packageDir, options));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return outcomes;
|
|
86
|
+
}
|
|
87
|
+
function writePlan(projectDir, capability, skill, action, packageDir, options) {
|
|
88
|
+
const planRelPath = path.join(exports.SKILL_EDITS_DIR, `${skill}.md`);
|
|
89
|
+
const planPath = path.join(projectDir, planRelPath);
|
|
90
|
+
const hashes = { installed: action.storedHash, project: action.currentHash, boop: action.newHash };
|
|
91
|
+
const existing = readPlanHashes(planPath);
|
|
92
|
+
if (existing && existing.project === hashes.project && existing.boop === hashes.boop) {
|
|
93
|
+
return { kind: 'kept', skill, planPath: planRelPath };
|
|
94
|
+
}
|
|
95
|
+
const projectText = fs.readFileSync(path.join(projectDir, action.relativePath), 'utf-8');
|
|
96
|
+
const boopText = fs.readFileSync(boopCopyPath(packageDir, action.relativePath), 'utf-8');
|
|
97
|
+
const original = (0, original_text_1.findOriginalText)(projectDir, action.relativePath.split(path.sep).join('/'), hashes.installed, options);
|
|
98
|
+
const baseText = original.found ? original.content : boopText;
|
|
99
|
+
const baseLines = baseText.split('\n');
|
|
100
|
+
const projectLines = projectText.split('\n');
|
|
101
|
+
const edits = diffLines(baseLines, projectLines);
|
|
102
|
+
const boopChanged = original.found ? action.drift === 'both-changed' : null;
|
|
103
|
+
const data = {
|
|
104
|
+
skill,
|
|
105
|
+
capability,
|
|
106
|
+
path: action.relativePath.split(path.sep).join('/'),
|
|
107
|
+
original: original.found ? sourceFields(original.source) : { source: 'none' },
|
|
108
|
+
comparedWithoutOriginal: !original.found,
|
|
109
|
+
boopChanged,
|
|
110
|
+
hashes,
|
|
111
|
+
decisions: Object.fromEntries(edits.map((_, i) => [editId(i), { decision: null, kind: null, rule: null, reason: null }])),
|
|
112
|
+
};
|
|
113
|
+
const body = original.found
|
|
114
|
+
? renderBody(data, edits, baseLines, projectLines, boopText.split('\n'))
|
|
115
|
+
: renderBody(data, edits, baseLines, projectLines, null);
|
|
116
|
+
fs.mkdirSync(path.dirname(planPath), { recursive: true });
|
|
117
|
+
const replacedStale = existing !== null;
|
|
118
|
+
if (replacedStale)
|
|
119
|
+
(0, safe_io_1.createBackup)(planPath);
|
|
120
|
+
fs.writeFileSync(planPath, gray_matter_1.default.stringify(body, data));
|
|
121
|
+
return {
|
|
122
|
+
kind: 'written',
|
|
123
|
+
skill,
|
|
124
|
+
planPath: planRelPath,
|
|
125
|
+
edits: edits.length,
|
|
126
|
+
boopChanged,
|
|
127
|
+
comparedWithoutOriginal: !original.found,
|
|
128
|
+
replacedStale,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** The hashes an existing plan was written against, or null when there is no readable plan. */
|
|
132
|
+
function readPlanHashes(planPath) {
|
|
133
|
+
if (!fs.existsSync(planPath))
|
|
134
|
+
return null;
|
|
135
|
+
try {
|
|
136
|
+
return (0, gray_matter_1.default)(fs.readFileSync(planPath, 'utf-8')).data.hashes ?? {};
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function boopCopyPath(packageDir, relativePath) {
|
|
143
|
+
// planCapabilityUpdate hashed this file, so its placement exists
|
|
144
|
+
return (0, install_layout_1.capabilityPlacementsFromDir)(packageDir).find(p => p.dest === relativePath).source;
|
|
145
|
+
}
|
|
146
|
+
function sourceFields(source) {
|
|
147
|
+
return source.kind === 'git'
|
|
148
|
+
? { source: 'git', commit: source.commit }
|
|
149
|
+
: { source: 'npm', package: source.package, version: source.version };
|
|
150
|
+
}
|
|
151
|
+
function editId(index) {
|
|
152
|
+
return `edit-${index + 1}`;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The edits that turn `base` into `project`: each maximal run of lines that
|
|
156
|
+
* are not part of a longest common subsequence of the two.
|
|
157
|
+
*/
|
|
158
|
+
function diffLines(base, project) {
|
|
159
|
+
const edits = [];
|
|
160
|
+
let baseAt = 0;
|
|
161
|
+
let projectAt = 0;
|
|
162
|
+
for (const [i, j] of [...matchLines(base, project), [base.length, project.length]]) {
|
|
163
|
+
if (i > baseAt || j > projectAt) {
|
|
164
|
+
edits.push({ baseStart: baseAt, baseEnd: i, projectStart: projectAt, projectEnd: j });
|
|
165
|
+
}
|
|
166
|
+
baseAt = i + 1;
|
|
167
|
+
projectAt = j + 1;
|
|
168
|
+
}
|
|
169
|
+
return edits;
|
|
170
|
+
}
|
|
171
|
+
/** Index pairs of the lines two texts share, in order: a longest common subsequence. */
|
|
172
|
+
function matchLines(a, b) {
|
|
173
|
+
// Most edits leave long runs at both ends untouched, which keeps the table small
|
|
174
|
+
let prefix = 0;
|
|
175
|
+
while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix])
|
|
176
|
+
prefix++;
|
|
177
|
+
let suffix = 0;
|
|
178
|
+
while (suffix < a.length - prefix && suffix < b.length - prefix && a[a.length - 1 - suffix] === b[b.length - 1 - suffix])
|
|
179
|
+
suffix++;
|
|
180
|
+
const n = a.length - prefix - suffix;
|
|
181
|
+
const m = b.length - prefix - suffix;
|
|
182
|
+
// lcs[x][y]: length of the common subsequence of a's middle from x and b's middle from y
|
|
183
|
+
const width = m + 1;
|
|
184
|
+
const lcs = new Uint32Array((n + 1) * (m + 1));
|
|
185
|
+
for (let x = n - 1; x >= 0; x--) {
|
|
186
|
+
for (let y = m - 1; y >= 0; y--) {
|
|
187
|
+
lcs[x * width + y] = a[prefix + x] === b[prefix + y]
|
|
188
|
+
? lcs[(x + 1) * width + y + 1] + 1
|
|
189
|
+
: Math.max(lcs[(x + 1) * width + y], lcs[x * width + y + 1]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const pairs = [];
|
|
193
|
+
for (let k = 0; k < prefix; k++)
|
|
194
|
+
pairs.push([k, k]);
|
|
195
|
+
let x = 0;
|
|
196
|
+
let y = 0;
|
|
197
|
+
while (x < n && y < m) {
|
|
198
|
+
if (a[prefix + x] === b[prefix + y]) {
|
|
199
|
+
pairs.push([prefix + x, prefix + y]);
|
|
200
|
+
x++;
|
|
201
|
+
y++;
|
|
202
|
+
}
|
|
203
|
+
else if (lcs[(x + 1) * width + y] >= lcs[x * width + y + 1]) {
|
|
204
|
+
x++;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
y++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (let k = suffix; k > 0; k--)
|
|
211
|
+
pairs.push([a.length - k, b.length - k]);
|
|
212
|
+
return pairs;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* What boop now ships in place of base lines [start, end): the smallest
|
|
216
|
+
* region around them bounded on each side by a line boop kept, so a change
|
|
217
|
+
* boop made just beside an edit is shown with it.
|
|
218
|
+
*/
|
|
219
|
+
function boopRegion(matches, upstreamLength, baseLength, start, end) {
|
|
220
|
+
let before = [-1, -1];
|
|
221
|
+
let after = [baseLength, upstreamLength];
|
|
222
|
+
for (const pair of matches) {
|
|
223
|
+
if (pair[0] < start)
|
|
224
|
+
before = pair;
|
|
225
|
+
else if (pair[0] >= end) {
|
|
226
|
+
after = pair;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { baseStart: before[0] + 1, baseEnd: after[0], upstreamStart: before[1] + 1, upstreamEnd: after[1] };
|
|
231
|
+
}
|
|
232
|
+
function renderBody(data, edits, base, project, upstream) {
|
|
233
|
+
const baseLabel = upstream ? 'The text boop installed' : 'boop ships now';
|
|
234
|
+
const out = [
|
|
235
|
+
`# Edits to ${data.path}`,
|
|
236
|
+
'',
|
|
237
|
+
upstream
|
|
238
|
+
? `This project's copy differs from the text boop installed in ${edits.length} place${edits.length === 1 ? '' : 's'}. ` +
|
|
239
|
+
`Each is shown below with boop's own changes to the same text, so you can see where boop has since covered an edit.`
|
|
240
|
+
: `boop could not find the text it installed, in this project's git history or in the published versions of its package, ` +
|
|
241
|
+
`so this plan compares the project's copy with what boop ships now. That comparison cannot tell this project's edits ` +
|
|
242
|
+
`from boop's own changes: every difference below needs a decision, including ones boop made.`,
|
|
243
|
+
'',
|
|
244
|
+
`Record a decision for every edit under \`decisions\` in the frontmatter, following the ${update_1.TRIAGE_SKILL} skill: ` +
|
|
245
|
+
'`keep` with a `rule` and a `kind` (`adds`, `narrows` or `overrides`), `offer` with a `rule`, or `drop` with a `reason`.',
|
|
246
|
+
];
|
|
247
|
+
const upstreamMatches = upstream ? matchLines(base, upstream) : [];
|
|
248
|
+
edits.forEach((edit, index) => {
|
|
249
|
+
out.push('', `## ${editId(index)}`, '', locationLine(edit, base, baseLabel));
|
|
250
|
+
out.push('', `**${baseLabel}:**`, '', ...textBlock(base.slice(edit.baseStart, edit.baseEnd), 'this project added the text below'));
|
|
251
|
+
out.push('', "**This project's copy:**", '', ...textBlock(project.slice(edit.projectStart, edit.projectEnd), 'this project removed the text above'));
|
|
252
|
+
if (upstream) {
|
|
253
|
+
const region = boopRegion(upstreamMatches, upstream.length, base.length, edit.baseStart, edit.baseEnd);
|
|
254
|
+
const before = base.slice(region.baseStart, region.baseEnd);
|
|
255
|
+
const after = upstream.slice(region.upstreamStart, region.upstreamEnd);
|
|
256
|
+
out.push('', '**boop now ships:**', '');
|
|
257
|
+
if (before.join('\n') === after.join('\n')) {
|
|
258
|
+
out.push('_boop has not changed this text._');
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
if (region.baseStart !== edit.baseStart || region.baseEnd !== edit.baseEnd) {
|
|
262
|
+
out.push(`_boop's change reaches past this edit. This is what boop now ships in place of ${lineRange(region.baseStart, region.baseEnd)} of the installed text:_`, '');
|
|
263
|
+
}
|
|
264
|
+
out.push(...textBlock(after, 'boop removed this text'));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
return out.join('\n') + '\n';
|
|
269
|
+
}
|
|
270
|
+
function locationLine(edit, base, baseLabel) {
|
|
271
|
+
const label = baseLabel.charAt(0).toLowerCase() + baseLabel.slice(1);
|
|
272
|
+
const where = edit.baseEnd > edit.baseStart
|
|
273
|
+
? `${capitalize(lineRange(edit.baseStart, edit.baseEnd))} of ${label}.`
|
|
274
|
+
: edit.baseStart === 0 ? `At the start of ${label}.` : `After line ${edit.baseStart} of ${label}.`;
|
|
275
|
+
const preceding = base.slice(0, edit.baseStart).reverse().find(line => line.trim() !== '');
|
|
276
|
+
return preceding ? `${where} It follows:\n\n> ${preceding.trim()}` : where;
|
|
277
|
+
}
|
|
278
|
+
/** Lines [start, end), numbered from 1 */
|
|
279
|
+
function lineRange(start, end) {
|
|
280
|
+
return end - start === 1 ? `line ${start + 1}` : `lines ${start + 1}–${end}`;
|
|
281
|
+
}
|
|
282
|
+
function capitalize(text) {
|
|
283
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
284
|
+
}
|
|
285
|
+
/** Lines in a code fence longer than any backtick run inside them, or a note when there are none. */
|
|
286
|
+
function textBlock(lines, whenEmpty) {
|
|
287
|
+
if (lines.every(line => line === '')) {
|
|
288
|
+
return lines.length === 0 ? [`_(none: ${whenEmpty})_`] : ['_(blank lines only)_'];
|
|
289
|
+
}
|
|
290
|
+
const longestRun = Math.max(0, ...lines.flatMap(line => (line.match(/`+/g) ?? []).map(run => run.length)));
|
|
291
|
+
const fence = '`'.repeat(Math.max(3, longestRun + 1));
|
|
292
|
+
return [fence, ...lines, fence];
|
|
293
|
+
}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Jest never recycles, and a long suite dies as that process's heap grows.
|
|
9
9
|
*
|
|
10
10
|
* `boop update` cannot simply fix those projects. Scripts are merged only when
|
|
11
|
-
* absent (see
|
|
12
|
-
* clobbering a script somebody customised would be
|
|
11
|
+
* absent (see lib/script-merge.ts, which both `boop add` and `boop update` use),
|
|
12
|
+
* which is the right default — clobbering a script somebody customised would be
|
|
13
|
+
* worse than leaving it stale.
|
|
13
14
|
* So the old command survives the update, and because a CLI `--runInBand` is the
|
|
14
15
|
* first and unconditional branch of Jest's `shouldRunInBand()`, it overrides the
|
|
15
16
|
* new config entirely. That is the state worth warning about: the project now has
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* Jest never recycles, and a long suite dies as that process's heap grows.
|
|
10
10
|
*
|
|
11
11
|
* `boop update` cannot simply fix those projects. Scripts are merged only when
|
|
12
|
-
* absent (see
|
|
13
|
-
* clobbering a script somebody customised would be
|
|
12
|
+
* absent (see lib/script-merge.ts, which both `boop add` and `boop update` use),
|
|
13
|
+
* which is the right default — clobbering a script somebody customised would be
|
|
14
|
+
* worse than leaving it stale.
|
|
14
15
|
* So the old command survives the update, and because a CLI `--runInBand` is the
|
|
15
16
|
* first and unconditional branch of Jest's `shouldRunInBand()`, it overrides the
|
|
16
17
|
* new config entirely. That is the state worth warning about: the project now has
|
package/dist/lib/update.d.ts
CHANGED
|
@@ -5,12 +5,19 @@
|
|
|
5
5
|
* local modifications, then selectively updates from the capability package.
|
|
6
6
|
*/
|
|
7
7
|
import { BoopManifest } from './project';
|
|
8
|
+
import { ScriptUpdateAction } from './script-merge';
|
|
8
9
|
export type FileState = 'match' | 'modified' | 'missing';
|
|
10
|
+
/**
|
|
11
|
+
* How a locally modified file stands against the base (what boop shipped last)
|
|
12
|
+
* and upstream (what boop ships now). Only 'both-changed' is a conflict.
|
|
13
|
+
*/
|
|
14
|
+
export type Drift = 'project-only' | 'both-changed' | 'already-upstream';
|
|
15
|
+
export type FileKind = 'skill' | 'reference' | 'code';
|
|
9
16
|
export interface FileUpdateAction {
|
|
10
17
|
/** Relative path from project root */
|
|
11
18
|
relativePath: string;
|
|
12
19
|
/** What will happen to this file */
|
|
13
|
-
action: 'overwrite' | 'skip' | 'add' | 'deprecate' | 'migrate';
|
|
20
|
+
action: 'overwrite' | 'skip' | 'add' | 'deprecate' | 'migrate' | 'record-hash';
|
|
14
21
|
/** Why this action was chosen */
|
|
15
22
|
reason: string;
|
|
16
23
|
/** Stored hash from config */
|
|
@@ -19,8 +26,23 @@ export interface FileUpdateAction {
|
|
|
19
26
|
currentHash?: string;
|
|
20
27
|
/** New hash from package */
|
|
21
28
|
newHash?: string;
|
|
22
|
-
/** For migrate actions: the old file path being replaced */
|
|
29
|
+
/** For migrate actions, and a folder file held back by an edited old one: the old file path being replaced */
|
|
23
30
|
oldPath?: string;
|
|
31
|
+
/** For an edited old single-file skill: the skill folder that replaces it */
|
|
32
|
+
replacedBy?: string;
|
|
33
|
+
/** For a file whose hash differs from its stored base: how it stands against upstream */
|
|
34
|
+
drift?: Drift;
|
|
35
|
+
/** What sort of file this is, from its destination path */
|
|
36
|
+
fileKind?: FileKind;
|
|
37
|
+
/** What whoever ran the update should do about this file, when the report lists it */
|
|
38
|
+
instruction?: string;
|
|
39
|
+
/** boop's current copy of the file, when the instruction points at it */
|
|
40
|
+
upstreamPath?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface FileInstruction {
|
|
43
|
+
relativePath: string;
|
|
44
|
+
instruction: string;
|
|
45
|
+
upstreamPath?: string;
|
|
24
46
|
}
|
|
25
47
|
export interface UpdatePlan {
|
|
26
48
|
capability: string;
|
|
@@ -32,6 +54,8 @@ export interface UpdatePlan {
|
|
|
32
54
|
newVersion: string;
|
|
33
55
|
/** Per-file actions */
|
|
34
56
|
actions: FileUpdateAction[];
|
|
57
|
+
/** What happens to each npm script the capability declares */
|
|
58
|
+
scriptActions: ScriptUpdateAction[];
|
|
35
59
|
/** Number of files unchanged in both package and locally */
|
|
36
60
|
unchangedCount: number;
|
|
37
61
|
/** True if already up to date */
|
|
@@ -50,9 +74,19 @@ export interface UpdateResult {
|
|
|
50
74
|
pruned: string[];
|
|
51
75
|
/** Files migrated from old flat format to new folder format */
|
|
52
76
|
migrated: string[];
|
|
77
|
+
/** npm scripts the capability declares that this update wrote into package.json */
|
|
78
|
+
scriptsAdded: ScriptUpdateAction[];
|
|
79
|
+
/** npm scripts left as the project has them, because it has its own command */
|
|
80
|
+
scriptsKept: ScriptUpdateAction[];
|
|
53
81
|
/** Count of files unchanged in both package and locally */
|
|
54
82
|
unchangedCount: number;
|
|
83
|
+
/** What to do about the files this update skipped or replaced, where the report says anything */
|
|
84
|
+
instructions: FileInstruction[];
|
|
55
85
|
}
|
|
86
|
+
/** The skill that splits a drifted SKILL.md — keep in step with the folder name boop-rm2l.8 gives it */
|
|
87
|
+
export declare const TRIAGE_SKILL = "boop-skills-edit-triage";
|
|
88
|
+
/** What to do about skills edited in SKILL.md, for a report that counts them */
|
|
89
|
+
export declare const SKILL_EDITS_INSTRUCTION = "Move each skill's edits into the PROJECT.md beside it: run `boop migrate-skill-edits` and follow the boop-skills-edit-triage skill.";
|
|
56
90
|
/**
|
|
57
91
|
* Compare a file's current hash against a stored hash.
|
|
58
92
|
*/
|
|
@@ -71,6 +105,15 @@ export declare function executeUpdatePlan(plan: UpdatePlan, options?: {
|
|
|
71
105
|
restore?: boolean;
|
|
72
106
|
prune?: boolean;
|
|
73
107
|
}): UpdateResult;
|
|
108
|
+
/**
|
|
109
|
+
* Count the skills a capability's project copy has edited in SKILL.md, and how
|
|
110
|
+
* many of those boop has changed since — from the same comparison `boop update`
|
|
111
|
+
* makes. Null when there are none, or the capability has no package to compare with.
|
|
112
|
+
*/
|
|
113
|
+
export declare function countSkillEdits(targetDir: string, capabilityName: string, projectManifest: BoopManifest): {
|
|
114
|
+
edited: number;
|
|
115
|
+
changesWaiting: number;
|
|
116
|
+
} | null;
|
|
74
117
|
/**
|
|
75
118
|
* Format an update result as a human-readable summary line.
|
|
76
119
|
*/
|