facult 2.13.2 → 2.13.3

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 CHANGED
@@ -100,6 +100,11 @@ fclt self-update
100
100
  fclt self-update --version 2.12.0
101
101
  ```
102
102
 
103
+ `self-update` follows the active install mode. It updates release-script binaries
104
+ directly, npm/Bun global installs through their package manager, and
105
+ mise-managed npm installs with `mise use -g --pin npm:facult@<version>`, then
106
+ verifies the active `fclt --version`.
107
+
103
108
  ## Quick start
104
109
 
105
110
  ### 1. Inspect existing AI state
package/docs/reference.md CHANGED
@@ -108,6 +108,10 @@ fclt audit [--non-interactive]
108
108
  fclt self-update
109
109
  ```
110
110
 
111
+ `self-update` detects release-script, npm/Bun, and mise-managed npm installs.
112
+ For mise installs it updates the global `npm:facult` pin and verifies the
113
+ resolved `fclt` version through mise.
114
+
111
115
  Use `--strict-source-trust` when installing or updating remote capability from catalogs.
112
116
 
113
117
  ## Root Selection
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "facult",
3
- "version": "2.13.2",
3
+ "version": "2.13.3",
4
4
  "description": "Manage canonical AI capabilities, sync surfaces, and evolution state.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,6 +19,7 @@ type InstallMethod =
19
19
  | "script-bin"
20
20
  | "release-script"
21
21
  | "npm-binary-cache"
22
+ | "mise-npm"
22
23
  | "unknown";
23
24
 
24
25
  interface InstallState {
@@ -113,21 +114,27 @@ export function detectInstallMethod(
113
114
  if (envMethod === "script-dev" || envMethod === "script-bin") {
114
115
  return envMethod;
115
116
  }
116
- if (envMethod === "npm-binary-cache") {
117
- return "npm-binary-cache";
117
+ if (envMethod === "npm-binary-cache" || envMethod === "mise-npm") {
118
+ return envMethod;
119
+ }
120
+
121
+ const exec = context.executablePath ?? process.execPath;
122
+ const home = context.homeDir ?? homedir();
123
+ if (looksLikeMiseNpmFacultExecutable(exec)) {
124
+ return "mise-npm";
118
125
  }
126
+
119
127
  const raw = state?.method?.trim();
120
128
  if (
121
129
  raw === "script-dev" ||
122
130
  raw === "script-bin" ||
123
131
  raw === "release-script" ||
124
- raw === "npm-binary-cache"
132
+ raw === "npm-binary-cache" ||
133
+ raw === "mise-npm"
125
134
  ) {
126
135
  return raw;
127
136
  }
128
137
 
129
- const exec = context.executablePath ?? process.execPath;
130
- const home = context.homeDir ?? homedir();
131
138
  const facultBins = [
132
139
  join(preferredGlobalFacultStateDir(home), "bin"),
133
140
  join(legacyExternalFacultStateDir(home), "bin"),
@@ -145,6 +152,68 @@ export function detectInstallMethod(
145
152
  return "unknown";
146
153
  }
147
154
 
155
+ async function detectActiveInstallMethod(
156
+ state: InstallState | null
157
+ ): Promise<InstallMethod> {
158
+ const method = detectInstallMethod(state);
159
+ if (method !== "npm-binary-cache" && method !== "unknown") {
160
+ return method;
161
+ }
162
+ if (await activeFcltUsesMiseNpmFacult()) {
163
+ return "mise-npm";
164
+ }
165
+ return method;
166
+ }
167
+
168
+ function looksLikeMiseNpmFacultExecutable(executablePath: string): boolean {
169
+ const normalized = executablePath.split("\\").join(sep);
170
+ return (
171
+ normalized.includes(`${sep}mise${sep}installs${sep}npm-facult${sep}`) &&
172
+ CLI_BASENAME_PATTERN.test(basename(normalized))
173
+ );
174
+ }
175
+
176
+ export function looksLikeMiseShim(
177
+ pathValue: string | null | undefined
178
+ ): boolean {
179
+ if (!pathValue) {
180
+ return false;
181
+ }
182
+ const normalized = pathValue.split("\\").join(sep);
183
+ return (
184
+ normalized.includes(`${sep}mise${sep}shims${sep}`) &&
185
+ CLI_BASENAME_PATTERN.test(basename(normalized))
186
+ );
187
+ }
188
+
189
+ async function activeFcltUsesMiseNpmFacult(): Promise<boolean> {
190
+ if (!Bun.which("mise")) {
191
+ return false;
192
+ }
193
+ const fcltPath = Bun.which("fclt");
194
+ if (looksLikeMiseNpmFacultExecutable(fcltPath ?? "")) {
195
+ return true;
196
+ }
197
+ if (!looksLikeMiseShim(fcltPath)) {
198
+ return false;
199
+ }
200
+ const proc = Bun.spawn({
201
+ cmd: ["mise", "which", "fclt"],
202
+ stdin: "ignore",
203
+ stdout: "pipe",
204
+ stderr: "ignore",
205
+ env: process.env,
206
+ });
207
+ const [stdout, code] = await Promise.all([
208
+ new Response(proc.stdout).text(),
209
+ proc.exited,
210
+ ]);
211
+ if (code !== 0) {
212
+ return false;
213
+ }
214
+ return looksLikeMiseNpmFacultExecutable(stdout.trim());
215
+ }
216
+
148
217
  function resolvePlatformTarget(): {
149
218
  platform: string;
150
219
  arch: string;
@@ -284,14 +353,17 @@ async function selfUpdateBinary(args: {
284
353
  console.log(`Path: ${binaryPath}`);
285
354
  }
286
355
 
287
- type PackageManager = "npm" | "bun";
356
+ type PackageManager = "npm" | "bun" | "mise";
288
357
 
289
358
  function chooseGlobalPackageManager(preferred?: string): PackageManager {
290
359
  const forced = process.env.FACULT_INSTALL_PM?.trim();
291
- if (forced === "npm" || forced === "bun") {
360
+ if (forced === "npm" || forced === "bun" || forced === "mise") {
292
361
  return forced;
293
362
  }
294
363
 
364
+ if (preferred === "mise" && Bun.which("mise")) {
365
+ return "mise";
366
+ }
295
367
  if (preferred === "npm" && Bun.which("npm")) {
296
368
  return "npm";
297
369
  }
@@ -305,25 +377,58 @@ function chooseGlobalPackageManager(preferred?: string): PackageManager {
305
377
  if (Bun.which("bun")) {
306
378
  return "bun";
307
379
  }
380
+ if (Bun.which("mise")) {
381
+ return "mise";
382
+ }
308
383
  return "npm";
309
384
  }
310
385
 
386
+ export function buildPackageManagerUpdateCommand(args: {
387
+ packageManager: PackageManager;
388
+ version: string;
389
+ }): string[] {
390
+ const installSpec = `${PACKAGE_NAME}@${args.version}`;
391
+ if (args.packageManager === "npm") {
392
+ return ["npm", "install", "-g", installSpec];
393
+ }
394
+ if (args.packageManager === "bun") {
395
+ return ["bun", "add", "-g", installSpec];
396
+ }
397
+ return ["mise", "use", "-g", "--pin", `npm:${PACKAGE_NAME}@${args.version}`];
398
+ }
399
+
400
+ async function resolvePackageTargetVersion(requestedVersion?: string): Promise<{
401
+ version: string;
402
+ resolvedFromLatest: boolean;
403
+ }> {
404
+ if (requestedVersion && requestedVersion !== "latest") {
405
+ return {
406
+ version: stripTagPrefix(requestedVersion),
407
+ resolvedFromLatest: false,
408
+ };
409
+ }
410
+ const tag = await resolveLatestTag();
411
+ return { version: stripTagPrefix(tag), resolvedFromLatest: true };
412
+ }
413
+
311
414
  async function selfUpdateViaPackageManager(args: {
312
415
  requestedVersion?: string;
313
416
  dryRun: boolean;
314
417
  preferredPackageManager?: string;
418
+ method?: InstallMethod;
315
419
  }) {
316
- const pm = chooseGlobalPackageManager(args.preferredPackageManager);
317
- const targetVersion =
318
- args.requestedVersion && args.requestedVersion !== "latest"
319
- ? stripTagPrefix(args.requestedVersion)
320
- : "latest";
321
-
322
- const installSpec = `${PACKAGE_NAME}@${targetVersion}`;
323
- const cmd =
324
- pm === "npm"
325
- ? ["npm", "install", "-g", installSpec]
326
- : ["bun", "add", "-g", installSpec];
420
+ const pm = chooseGlobalPackageManager(
421
+ args.method === "mise-npm" ? "mise" : args.preferredPackageManager
422
+ );
423
+ const target =
424
+ args.dryRun &&
425
+ (!args.requestedVersion || args.requestedVersion === "latest")
426
+ ? { version: "latest", resolvedFromLatest: false }
427
+ : await resolvePackageTargetVersion(args.requestedVersion);
428
+ const cmd = buildPackageManagerUpdateCommand({
429
+ packageManager: pm,
430
+ version: target.version,
431
+ });
327
432
 
328
433
  if (args.dryRun) {
329
434
  console.log(`[dry-run] Would run: ${cmd.join(" ")}`);
@@ -341,7 +446,68 @@ async function selfUpdateViaPackageManager(args: {
341
446
  if (code !== 0) {
342
447
  throw new Error(`Self-update failed via ${pm} (exit ${code}).`);
343
448
  }
344
- console.log(`Updated fclt via ${pm}: ${installSpec}`);
449
+ if (pm === "mise") {
450
+ await runBestEffort(["mise", "reshim", `npm:${PACKAGE_NAME}`]);
451
+ }
452
+ await assertActiveFcltVersion(target.version, pm);
453
+ console.log(`Updated fclt via ${pm}: ${PACKAGE_NAME}@${target.version}`);
454
+ if (target.resolvedFromLatest) {
455
+ console.log(`Resolved latest release to ${target.version}`);
456
+ }
457
+ }
458
+
459
+ async function runBestEffort(cmd: string[]): Promise<void> {
460
+ const proc = Bun.spawn({
461
+ cmd,
462
+ stdin: "ignore",
463
+ stdout: "ignore",
464
+ stderr: "ignore",
465
+ env: process.env,
466
+ });
467
+ await proc.exited;
468
+ }
469
+
470
+ async function assertActiveFcltVersion(
471
+ expectedVersion: string,
472
+ packageManager: PackageManager
473
+ ): Promise<void> {
474
+ const cmd =
475
+ packageManager === "mise"
476
+ ? [
477
+ "mise",
478
+ "exec",
479
+ `npm:${PACKAGE_NAME}@${expectedVersion}`,
480
+ "--",
481
+ "fclt",
482
+ "--version",
483
+ ]
484
+ : ["fclt", "--version"];
485
+ const proc = Bun.spawn({
486
+ cmd,
487
+ stdin: "ignore",
488
+ stdout: "pipe",
489
+ stderr: "pipe",
490
+ env: process.env,
491
+ });
492
+ const [stdout, stderr, code] = await Promise.all([
493
+ new Response(proc.stdout).text(),
494
+ new Response(proc.stderr).text(),
495
+ proc.exited,
496
+ ]);
497
+ const actual = stdout.trim();
498
+ if (code !== 0) {
499
+ throw new Error(
500
+ `Updated package, but could not verify active fclt version: ${stderr.trim()}`
501
+ );
502
+ }
503
+ if (actual !== expectedVersion) {
504
+ throw new Error(
505
+ [
506
+ `Updated package to ${expectedVersion}, but active fclt is still ${actual}.`,
507
+ "Your PATH may be resolving an older shim. Run `mise which fclt` or `which fclt` to inspect it.",
508
+ ].join("\n")
509
+ );
510
+ }
345
511
  }
346
512
 
347
513
  async function fetchReleaseBinaryWithRetry(url: string): Promise<ArrayBuffer> {
@@ -424,7 +590,7 @@ export async function selfUpdateCommand(argv: string[]) {
424
590
 
425
591
  const home = homedir();
426
592
  const state = await loadInstallState(home);
427
- const method = detectInstallMethod(state);
593
+ const method = await detectActiveInstallMethod(state);
428
594
 
429
595
  try {
430
596
  if (method === "script-dev") {
@@ -434,9 +600,10 @@ export async function selfUpdateCommand(argv: string[]) {
434
600
  );
435
601
  return;
436
602
  }
437
- if (method === "npm-binary-cache") {
603
+ if (method === "npm-binary-cache" || method === "mise-npm") {
438
604
  await selfUpdateViaPackageManager({
439
605
  ...parsed,
606
+ method,
440
607
  preferredPackageManager:
441
608
  process.env.FACULT_INSTALL_PM?.trim() || state?.packageManager,
442
609
  });