total-recall-brain 3.43.0 → 3.43.1
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/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/runtime/installation.cjs +71 -30
- package/src/cli/release.mjs +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "total-recall-brain",
|
|
3
|
-
"version": "3.43.
|
|
3
|
+
"version": "3.43.1",
|
|
4
4
|
"description": "The Vibe Coding Brain & Autonomous Software Factory: memory, rules, secrets, and 33 visual plugins automatically managed across all your devices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-ssss",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "tr-plugin-ssss",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@gregiteen/ssss-cli": "0.14.4"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
3
3
|
"id": "ssss",
|
|
4
4
|
"name": "SSSS",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.2",
|
|
6
6
|
"description": "Canonical SSSS distribution, scaffolding and validation for Total Recall installations.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"use_cases": ["software-development", "state-conformance"],
|
|
@@ -99,16 +99,25 @@ function inspectScaffold(dir, source) {
|
|
|
99
99
|
return { ok: true, path: dir, files: Object.keys(expected.files).length };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
const realOrNull = file => { try { return fs.realpathSync(file); } catch { return null; } };
|
|
103
|
+
/** A path with its folders resolved and its last part kept, so a link is compared as itself. */
|
|
104
|
+
const entryOf = file => path.join(realOrNull(path.dirname(file)) ?? path.dirname(file), path.basename(file));
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* True for a project's link to the machine's shared skill. A link at the shared location itself
|
|
108
|
+
* is not one: that location is what has to hold the skill.
|
|
109
|
+
*/
|
|
110
|
+
function linksToSharedSkill(target, sharedBrainDir) {
|
|
111
|
+
if (!sharedBrainDir) return false;
|
|
112
|
+
const shared = path.join(path.dirname(sharedBrainDir), 'ssss');
|
|
113
|
+
if (entryOf(shared) === entryOf(target)) return false;
|
|
114
|
+
const resolved = realOrNull(target);
|
|
115
|
+
return resolved !== null && resolved === realOrNull(shared);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
/** A link that reaches a complete, current skill is left alone, like a linked plugin. */
|
|
119
|
+
function inspectSkill(target, source) {
|
|
120
|
+
const dir = stat(target)?.isSymbolicLink() ? fs.realpathSync(target) : target;
|
|
112
121
|
requireDirectory(dir);
|
|
113
122
|
const wrapper = fs.readFileSync(regularPath(dir, 'SKILL.md'), 'utf8');
|
|
114
123
|
if (!/^name:\s*['"]?ssss['"]?\s*$/m.test(wrapper) || !wrapper.includes('core/SKILL.md')) {
|
|
@@ -164,31 +173,63 @@ function writeAsset(root, relative, content) {
|
|
|
164
173
|
fs.writeFileSync(file, content, { flag: 'wx' });
|
|
165
174
|
}
|
|
166
175
|
|
|
176
|
+
const NAMES_THIS_SKILL = /^name:\s*['"]?ssss['"]?\s*$/m;
|
|
177
|
+
|
|
178
|
+
/** Fill a staged skill: any files already there stay, and the portable core is always replaced. */
|
|
179
|
+
function completeSkill(stage, source) {
|
|
180
|
+
fs.rmSync(path.join(stage, 'core'), { recursive: true, force: true });
|
|
181
|
+
for (const relative of Object.keys(source.skill)) {
|
|
182
|
+
if (!stat(regularPath(stage, relative))) {
|
|
183
|
+
writeAsset(stage, relative, fs.readFileSync(regularPath(source.skillRoot, relative)));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const wrapper = path.join(stage, 'SKILL.md');
|
|
187
|
+
if (!fs.readFileSync(wrapper, 'utf8').includes('core/SKILL.md')) fs.appendFileSync(wrapper, ROUTE);
|
|
188
|
+
inspectSkill(stage, source);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* An older layout linked the skill in, and the link is now dangling or stale. Only the link is
|
|
193
|
+
* replaced: nothing is written through it, so whatever it pointed at stays exactly as it was.
|
|
194
|
+
* A wrapper with this skill's name carries over, because it can hold repository guidance.
|
|
195
|
+
*/
|
|
196
|
+
function replaceLink(target, source) {
|
|
197
|
+
const pointsAt = fs.readlinkSync(target);
|
|
198
|
+
let wrapper = null;
|
|
199
|
+
try {
|
|
200
|
+
const text = fs.readFileSync(path.join(target, 'SKILL.md'), 'utf8');
|
|
201
|
+
if (NAMES_THIS_SKILL.test(text)) wrapper = text;
|
|
202
|
+
} catch { /* a dangling link or a folder with no entry has no wrapper to keep */ }
|
|
203
|
+
publishDirectory(target, stage => {
|
|
204
|
+
if (wrapper) writeAsset(stage, 'SKILL.md', wrapper);
|
|
205
|
+
completeSkill(stage, source);
|
|
206
|
+
}, true);
|
|
207
|
+
return { path: target, target: pointsAt };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** @returns {{ path: string, target: string } | undefined} the link it replaced, if it replaced one */
|
|
167
211
|
function installSkill(target, source, sharedBrainDir) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
212
|
+
if (stat(target)?.isSymbolicLink()) {
|
|
213
|
+
if (!linksToSharedSkill(target, sharedBrainDir)) return replaceLink(target, source);
|
|
214
|
+
// The shared skill is repaired where it lives. A project does not take a private copy.
|
|
215
|
+
inspectSkill(target, source);
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
checkRoot(target);
|
|
219
|
+
const exists = Boolean(stat(target));
|
|
172
220
|
// Stage the complete skill so a failed source read cannot leave a partial skill.
|
|
173
221
|
if (exists) {
|
|
174
|
-
const wrapper = regularPath(
|
|
175
|
-
if (stat(wrapper) &&
|
|
176
|
-
throw new Error('Existing skill directory belongs to another skill: ' +
|
|
222
|
+
const wrapper = regularPath(target, 'SKILL.md');
|
|
223
|
+
if (stat(wrapper) && !NAMES_THIS_SKILL.test(fs.readFileSync(wrapper, 'utf8'))) {
|
|
224
|
+
throw new Error('Existing skill directory belongs to another skill: ' + target);
|
|
177
225
|
}
|
|
178
|
-
listFiles(
|
|
226
|
+
listFiles(target);
|
|
179
227
|
}
|
|
180
|
-
publishDirectory(
|
|
181
|
-
if (exists) fs.cpSync(
|
|
182
|
-
|
|
183
|
-
for (const relative of Object.keys(source.skill)) {
|
|
184
|
-
if (!stat(regularPath(stage, relative))) {
|
|
185
|
-
writeAsset(stage, relative, fs.readFileSync(regularPath(source.skillRoot, relative)));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
const wrapper = path.join(stage, 'SKILL.md');
|
|
189
|
-
if (!fs.readFileSync(wrapper, 'utf8').includes('core/SKILL.md')) fs.appendFileSync(wrapper, ROUTE);
|
|
190
|
-
inspectSkill(stage, source);
|
|
228
|
+
publishDirectory(target, stage => {
|
|
229
|
+
if (exists) fs.cpSync(target, stage, { recursive: true });
|
|
230
|
+
completeSkill(stage, source);
|
|
191
231
|
}, exists);
|
|
232
|
+
return undefined;
|
|
192
233
|
}
|
|
193
234
|
|
|
194
235
|
function hasUpdaterDrift(dir, source, receipt) {
|
|
@@ -247,7 +288,7 @@ function inspectInstalled(options, source) {
|
|
|
247
288
|
catch (error) { return { ok: false, path: target, error: error.message }; }
|
|
248
289
|
};
|
|
249
290
|
const observed = {
|
|
250
|
-
skill: check(skill, () => inspectSkill(skill, source
|
|
291
|
+
skill: check(skill, () => inspectSkill(skill, source)),
|
|
251
292
|
scaffold: check(scaffold, () => {
|
|
252
293
|
checkRoot(options.brainDir);
|
|
253
294
|
checkRoot(path.dirname(scaffold));
|
|
@@ -269,10 +310,10 @@ function ensureInstallation(options) {
|
|
|
269
310
|
checkRoot(options.brainDir);
|
|
270
311
|
const current = inspectInstalled(options, source);
|
|
271
312
|
const recoveredFrom = current.scaffold.ok ? undefined : installScaffold(scaffold, source, options.repair === true);
|
|
272
|
-
|
|
313
|
+
const replacedLink = current.skill.ok ? undefined : installSkill(skill, source, options.sharedBrainDir);
|
|
273
314
|
const installed = inspectInstalled(options, source);
|
|
274
315
|
if (!installed.ok) throw new Error([installed.skill.error, installed.scaffold.error].filter(Boolean).join('; '));
|
|
275
|
-
return recoveredFrom ? {
|
|
316
|
+
return { ...installed, ...(recoveredFrom ? { recoveredFrom } : {}), ...(replacedLink ? { replacedLink } : {}) };
|
|
276
317
|
}
|
|
277
318
|
|
|
278
319
|
async function maintain({ brainDir, entries, apply, run }) {
|
package/src/cli/release.mjs
CHANGED
|
@@ -243,7 +243,11 @@ export function consumerState(root, pkg, version = null) {
|
|
|
243
243
|
/* keep looking */
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
|
-
|
|
246
|
+
// What a consumer runs is what counts, and the version named is the question. A pin can lag on
|
|
247
|
+
// purpose, because `update` installs plugin copies with --no-save. Comparing against the pin
|
|
248
|
+
// would fail those and pass a consumer whose pin and install both lag.
|
|
249
|
+
const expected = version || want;
|
|
250
|
+
const satisfied = Boolean(expected && installed === expected);
|
|
247
251
|
return {
|
|
248
252
|
root,
|
|
249
253
|
declared,
|
|
@@ -251,6 +255,7 @@ export function consumerState(root, pkg, version = null) {
|
|
|
251
255
|
range,
|
|
252
256
|
installed,
|
|
253
257
|
satisfied,
|
|
258
|
+
pinBehind: Boolean(satisfied && want && want !== installed),
|
|
254
259
|
reason: !installed ? 'not-installed' : satisfied ? 'ok' : 'installed-mismatch',
|
|
255
260
|
};
|
|
256
261
|
}
|
|
@@ -279,6 +284,7 @@ export function verifyConsumers(pkg, version, opts = {}) {
|
|
|
279
284
|
consumers: relevant.length,
|
|
280
285
|
failing: failing.length,
|
|
281
286
|
failures: failing.map((s) => ({ root: s.root, declared: s.declared, installed: s.installed, reason: s.reason })),
|
|
287
|
+
pin_behind: relevant.filter((s) => s.pinBehind).map((s) => ({ root: s.root, declared: s.declared, installed: s.installed })),
|
|
282
288
|
};
|
|
283
289
|
}
|
|
284
290
|
|
|
@@ -362,6 +368,7 @@ export async function run(argv = process.argv, deps = {}) {
|
|
|
362
368
|
return fail(`${pkg}@${version}: ${consumers.failing}/${consumers.consumers} consumers not satisfied. First: ${consumers.failures[0].root} (${consumers.failures[0].installed ?? 'not installed'}).`);
|
|
363
369
|
}
|
|
364
370
|
log(` ✓ ${pkg}@${version} verified: registry + ${consumers.consumers}/${consumers.consumers} consumers`);
|
|
371
|
+
if (consumers.pin_behind.length) log(` ${consumers.pin_behind.length} run it with an older pin in package.json, such as ${consumers.pin_behind[0].root}`);
|
|
365
372
|
continue;
|
|
366
373
|
}
|
|
367
374
|
|