@house365/comate 0.7.0 → 0.8.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/bin/init-run.mjs +9 -0
- package/bin/init.mjs +82 -1
- package/bin/postinstall.mjs +21 -1
- package/package.json +1 -1
package/bin/init-run.mjs
CHANGED
|
@@ -11,10 +11,12 @@ import {
|
|
|
11
11
|
SKILLS_TIMEOUT_MS,
|
|
12
12
|
npmInvocation,
|
|
13
13
|
openSpecState,
|
|
14
|
+
pruneDeprecatedSkills,
|
|
14
15
|
run,
|
|
15
16
|
scaffoldDocs,
|
|
16
17
|
skillsInvocation,
|
|
17
18
|
superpowersInstalled,
|
|
19
|
+
userSkillsRoot,
|
|
18
20
|
} from './init.mjs';
|
|
19
21
|
|
|
20
22
|
// `comate init` — no flags. Skills go user-level (~/.agents/skills), matching how
|
|
@@ -133,6 +135,13 @@ export async function init(pkgRoot) {
|
|
|
133
135
|
results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason, r.output]);
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
// Prune comate skill dirs renamed/dropped in past releases. `skills add` above
|
|
139
|
+
// only adds/overwrites, so /comate-hotfix etc. would otherwise linger as dead
|
|
140
|
+
// slash commands. The postinstall hook does this too, but --ignore-scripts
|
|
141
|
+
// users never run it — init is their only path, so it must clean up here.
|
|
142
|
+
const pruned = pruneDeprecatedSkills(userSkillsRoot(home));
|
|
143
|
+
if (pruned.length) results.push(['清理废弃 skill', 'installed', pruned.join('、')]);
|
|
144
|
+
|
|
136
145
|
// 4. openspec/ project structure. Not optional: comate's open and archive
|
|
137
146
|
// phases read it, so an install without it cannot actually run. An existing
|
|
138
147
|
// one is left alone, which covers projects that already keep it elsewhere.
|
package/bin/init.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// ponytail: no SKILLS_AGENT_MAP here. The skills CLI detects the running agent
|
|
10
10
|
// on its own, so translating platform ids to agent names would be dead code.
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
|
-
import { existsSync, mkdirSync, readdirSync, copyFileSync, statSync } from 'node:fs';
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, copyFileSync, statSync, lstatSync, rmSync } from 'node:fs';
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
14
|
import { dirname, join } from 'node:path';
|
|
15
15
|
|
|
@@ -211,6 +211,83 @@ function scaffoldDocs(pkgRoot, cwd = process.cwd()) {
|
|
|
211
211
|
return { ok: true, created, skipped };
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// comate skill dirs that later releases renamed or dropped. `skills add` only
|
|
215
|
+
// adds/overwrites, never removes, so without an explicit prune these linger in
|
|
216
|
+
// ~/.agents/skills as dead slash commands (e.g. /comate-hotfix after it became
|
|
217
|
+
// /comate-fix). ONLY these exact names are ever deleted: the dir also holds
|
|
218
|
+
// unrelated third-party skills (lark-*, openspec-*), so a prefix sweep or a
|
|
219
|
+
// diff-against-current strategy could wipe a user's own work. Add a line here
|
|
220
|
+
// whenever a comate skill is renamed or removed.
|
|
221
|
+
const DEPRECATED_SKILLS = ['comate-hotfix', 'comate-tweak', 'comate-intake'];
|
|
222
|
+
|
|
223
|
+
function userSkillsRoot(home = process.env.HOME ?? process.env.USERPROFILE ?? '') {
|
|
224
|
+
return join(home, '.agents', 'skills');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// lstat, not stat: only a real directory is a comate-installed skill. A symlink
|
|
228
|
+
// is a developer's deliberate dev setup pointing elsewhere — never remove it.
|
|
229
|
+
function isRealDir(p) {
|
|
230
|
+
try {
|
|
231
|
+
return lstatSync(p).isDirectory();
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Delete only the exact DEPRECATED_SKILLS dirs present under skillsRoot. Every
|
|
238
|
+
// dependency is injectable so tests never touch a real ~/.agents/skills. Never
|
|
239
|
+
// throws — a permission error or race on one dir must not abort an install.
|
|
240
|
+
// Returns the names actually removed (for the caller to report).
|
|
241
|
+
function pruneDeprecatedSkills(
|
|
242
|
+
skillsRoot,
|
|
243
|
+
{
|
|
244
|
+
names = DEPRECATED_SKILLS,
|
|
245
|
+
exists = existsSync,
|
|
246
|
+
isDir = isRealDir,
|
|
247
|
+
rm = (p) => rmSync(p, { recursive: true, force: true }),
|
|
248
|
+
} = {},
|
|
249
|
+
) {
|
|
250
|
+
const removed = [];
|
|
251
|
+
for (const name of names) {
|
|
252
|
+
const dir = join(skillsRoot, name);
|
|
253
|
+
if (!exists(dir) || !isDir(dir)) continue;
|
|
254
|
+
try {
|
|
255
|
+
rm(dir);
|
|
256
|
+
removed.push(name);
|
|
257
|
+
} catch {
|
|
258
|
+
// A stuck dir (locked, permission) is not worth failing the install over.
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return removed;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// True only when the pinned `skills` dependency resolves locally. A postinstall
|
|
265
|
+
// must never reach the network, so this gates syncComateSkills: if resolution
|
|
266
|
+
// fails we skip rather than let skillsInvocation fall back to `npx skills@latest`.
|
|
267
|
+
function skillsResolvesLocally(pkgRoot) {
|
|
268
|
+
try {
|
|
269
|
+
createRequire(join(pkgRoot, 'package.json')).resolve('skills/package.json');
|
|
270
|
+
return true;
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Copy comate's own skills into ~/.agents/skills via the bundled skills CLI —
|
|
277
|
+
// the exact command `comate init` runs, so the layout is identical. Local-only:
|
|
278
|
+
// skips (never goes to network) when the skills dep can't be resolved, leaving
|
|
279
|
+
// `comate init` as the fallback. Returns { ok, skipped }.
|
|
280
|
+
function syncComateSkills(pkgRoot, { home = process.env.HOME ?? process.env.USERPROFILE ?? '', runner = run } = {}) {
|
|
281
|
+
if (!skillsResolvesLocally(pkgRoot)) return { ok: false, skipped: 'no-local-cli' };
|
|
282
|
+
const r = runner(
|
|
283
|
+
skillsInvocation(pkgRoot, ['add', pkgRoot, '--skill', '*', '-g', '-y']),
|
|
284
|
+
SKILLS_TIMEOUT_MS,
|
|
285
|
+
{ quiet: true },
|
|
286
|
+
);
|
|
287
|
+
const landed = existsSync(join(home, '.agents', 'skills', 'comate', 'SKILL.md'));
|
|
288
|
+
return { ok: r.ok && landed, skipped: null };
|
|
289
|
+
}
|
|
290
|
+
|
|
214
291
|
export {
|
|
215
292
|
MIN_OPENSPEC,
|
|
216
293
|
NPM_TIMEOUT_MS,
|
|
@@ -228,4 +305,8 @@ export {
|
|
|
228
305
|
scaffoldSourceDir,
|
|
229
306
|
copyTreeSkipExisting,
|
|
230
307
|
scaffoldDocs,
|
|
308
|
+
DEPRECATED_SKILLS,
|
|
309
|
+
userSkillsRoot,
|
|
310
|
+
pruneDeprecatedSkills,
|
|
311
|
+
syncComateSkills,
|
|
231
312
|
};
|
package/bin/postinstall.mjs
CHANGED
|
@@ -14,13 +14,33 @@ if (isCI() || isDevInstall()) {
|
|
|
14
14
|
|
|
15
15
|
const { banner, pkgVersion } = await import('./init-run.mjs');
|
|
16
16
|
|
|
17
|
+
// Best-effort: refresh the user-level skills so `npm i -g @latest` alone updates
|
|
18
|
+
// them, and prune dirs renamed/dropped in past releases. All guarded — a failure
|
|
19
|
+
// here must never break the npm install, and `comate init` remains the fallback
|
|
20
|
+
// for --ignore-scripts users (who never reach this hook) and per-project setup.
|
|
21
|
+
const sync = await syncSkillsQuietly();
|
|
22
|
+
|
|
17
23
|
console.log(banner(pkgVersion(pkgRoot)));
|
|
18
24
|
console.log(` 🎉 Congratulations! comate 已成功安装。\n`);
|
|
25
|
+
if (sync.synced) console.log(' ✓ 已把 skills 同步到 ~/.agents/skills');
|
|
26
|
+
if (sync.removed.length) console.log(` ✓ 已清理废弃 skill:${sync.removed.join('、')}`);
|
|
19
27
|
console.log(' 下一步:');
|
|
20
|
-
console.log(' 在你的项目目录下运行 comate init
|
|
28
|
+
console.log(' 在你的项目目录下运行 comate init 完成项目初始化(openspec/、docs/)');
|
|
21
29
|
console.log(' 或运行 comate doctor 健康检查');
|
|
22
30
|
console.log(' 更多帮助 comate --help\n');
|
|
23
31
|
|
|
32
|
+
async function syncSkillsQuietly() {
|
|
33
|
+
try {
|
|
34
|
+
const { syncComateSkills, pruneDeprecatedSkills, userSkillsRoot } = await import('./init.mjs');
|
|
35
|
+
const { ok } = syncComateSkills(pkgRoot);
|
|
36
|
+
const removed = pruneDeprecatedSkills(userSkillsRoot());
|
|
37
|
+
return { synced: ok, removed };
|
|
38
|
+
} catch {
|
|
39
|
+
// Any failure (no skills CLI resolvable, fs error) is silent: init covers it.
|
|
40
|
+
return { synced: false, removed: [] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
function isCI() {
|
|
25
45
|
return Boolean(process.env.CI || process.env.GITHUB_ACTIONS);
|
|
26
46
|
}
|