facult 2.13.2 → 2.13.4

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.4",
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,82 @@ 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 (await miseHasCurrentFacultTool()) {
198
+ return true;
199
+ }
200
+ if (!looksLikeMiseShim(fcltPath)) {
201
+ return false;
202
+ }
203
+ const proc = Bun.spawn({
204
+ cmd: ["mise", "which", "fclt"],
205
+ stdin: "ignore",
206
+ stdout: "pipe",
207
+ stderr: "ignore",
208
+ env: process.env,
209
+ });
210
+ const [stdout, code] = await Promise.all([
211
+ new Response(proc.stdout).text(),
212
+ proc.exited,
213
+ ]);
214
+ if (code !== 0) {
215
+ return false;
216
+ }
217
+ return looksLikeMiseNpmFacultExecutable(stdout.trim());
218
+ }
219
+
220
+ async function miseHasCurrentFacultTool(): Promise<boolean> {
221
+ const proc = Bun.spawn({
222
+ cmd: ["mise", "current", `npm:${PACKAGE_NAME}`],
223
+ stdin: "ignore",
224
+ stdout: "ignore",
225
+ stderr: "ignore",
226
+ env: process.env,
227
+ });
228
+ return (await proc.exited) === 0;
229
+ }
230
+
148
231
  function resolvePlatformTarget(): {
149
232
  platform: string;
150
233
  arch: string;
@@ -284,14 +367,17 @@ async function selfUpdateBinary(args: {
284
367
  console.log(`Path: ${binaryPath}`);
285
368
  }
286
369
 
287
- type PackageManager = "npm" | "bun";
370
+ type PackageManager = "npm" | "bun" | "mise";
288
371
 
289
372
  function chooseGlobalPackageManager(preferred?: string): PackageManager {
290
373
  const forced = process.env.FACULT_INSTALL_PM?.trim();
291
- if (forced === "npm" || forced === "bun") {
374
+ if (forced === "npm" || forced === "bun" || forced === "mise") {
292
375
  return forced;
293
376
  }
294
377
 
378
+ if (preferred === "mise" && Bun.which("mise")) {
379
+ return "mise";
380
+ }
295
381
  if (preferred === "npm" && Bun.which("npm")) {
296
382
  return "npm";
297
383
  }
@@ -305,25 +391,58 @@ function chooseGlobalPackageManager(preferred?: string): PackageManager {
305
391
  if (Bun.which("bun")) {
306
392
  return "bun";
307
393
  }
394
+ if (Bun.which("mise")) {
395
+ return "mise";
396
+ }
308
397
  return "npm";
309
398
  }
310
399
 
400
+ export function buildPackageManagerUpdateCommand(args: {
401
+ packageManager: PackageManager;
402
+ version: string;
403
+ }): string[] {
404
+ const installSpec = `${PACKAGE_NAME}@${args.version}`;
405
+ if (args.packageManager === "npm") {
406
+ return ["npm", "install", "-g", installSpec];
407
+ }
408
+ if (args.packageManager === "bun") {
409
+ return ["bun", "add", "-g", installSpec];
410
+ }
411
+ return ["mise", "use", "-g", "--pin", `npm:${PACKAGE_NAME}@${args.version}`];
412
+ }
413
+
414
+ async function resolvePackageTargetVersion(requestedVersion?: string): Promise<{
415
+ version: string;
416
+ resolvedFromLatest: boolean;
417
+ }> {
418
+ if (requestedVersion && requestedVersion !== "latest") {
419
+ return {
420
+ version: stripTagPrefix(requestedVersion),
421
+ resolvedFromLatest: false,
422
+ };
423
+ }
424
+ const tag = await resolveLatestTag();
425
+ return { version: stripTagPrefix(tag), resolvedFromLatest: true };
426
+ }
427
+
311
428
  async function selfUpdateViaPackageManager(args: {
312
429
  requestedVersion?: string;
313
430
  dryRun: boolean;
314
431
  preferredPackageManager?: string;
432
+ method?: InstallMethod;
315
433
  }) {
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];
434
+ const pm = chooseGlobalPackageManager(
435
+ args.method === "mise-npm" ? "mise" : args.preferredPackageManager
436
+ );
437
+ const target =
438
+ args.dryRun &&
439
+ (!args.requestedVersion || args.requestedVersion === "latest")
440
+ ? { version: "latest", resolvedFromLatest: false }
441
+ : await resolvePackageTargetVersion(args.requestedVersion);
442
+ const cmd = buildPackageManagerUpdateCommand({
443
+ packageManager: pm,
444
+ version: target.version,
445
+ });
327
446
 
328
447
  if (args.dryRun) {
329
448
  console.log(`[dry-run] Would run: ${cmd.join(" ")}`);
@@ -341,7 +460,68 @@ async function selfUpdateViaPackageManager(args: {
341
460
  if (code !== 0) {
342
461
  throw new Error(`Self-update failed via ${pm} (exit ${code}).`);
343
462
  }
344
- console.log(`Updated fclt via ${pm}: ${installSpec}`);
463
+ if (pm === "mise") {
464
+ await runBestEffort(["mise", "reshim", `npm:${PACKAGE_NAME}`]);
465
+ }
466
+ await assertActiveFcltVersion(target.version, pm);
467
+ console.log(`Updated fclt via ${pm}: ${PACKAGE_NAME}@${target.version}`);
468
+ if (target.resolvedFromLatest) {
469
+ console.log(`Resolved latest release to ${target.version}`);
470
+ }
471
+ }
472
+
473
+ async function runBestEffort(cmd: string[]): Promise<void> {
474
+ const proc = Bun.spawn({
475
+ cmd,
476
+ stdin: "ignore",
477
+ stdout: "ignore",
478
+ stderr: "ignore",
479
+ env: process.env,
480
+ });
481
+ await proc.exited;
482
+ }
483
+
484
+ async function assertActiveFcltVersion(
485
+ expectedVersion: string,
486
+ packageManager: PackageManager
487
+ ): Promise<void> {
488
+ const cmd =
489
+ packageManager === "mise"
490
+ ? [
491
+ "mise",
492
+ "exec",
493
+ `npm:${PACKAGE_NAME}@${expectedVersion}`,
494
+ "--",
495
+ "fclt",
496
+ "--version",
497
+ ]
498
+ : ["fclt", "--version"];
499
+ const proc = Bun.spawn({
500
+ cmd,
501
+ stdin: "ignore",
502
+ stdout: "pipe",
503
+ stderr: "pipe",
504
+ env: process.env,
505
+ });
506
+ const [stdout, stderr, code] = await Promise.all([
507
+ new Response(proc.stdout).text(),
508
+ new Response(proc.stderr).text(),
509
+ proc.exited,
510
+ ]);
511
+ const actual = stdout.trim();
512
+ if (code !== 0) {
513
+ throw new Error(
514
+ `Updated package, but could not verify active fclt version: ${stderr.trim()}`
515
+ );
516
+ }
517
+ if (actual !== expectedVersion) {
518
+ throw new Error(
519
+ [
520
+ `Updated package to ${expectedVersion}, but active fclt is still ${actual}.`,
521
+ "Your PATH may be resolving an older shim. Run `mise which fclt` or `which fclt` to inspect it.",
522
+ ].join("\n")
523
+ );
524
+ }
345
525
  }
346
526
 
347
527
  async function fetchReleaseBinaryWithRetry(url: string): Promise<ArrayBuffer> {
@@ -424,7 +604,7 @@ export async function selfUpdateCommand(argv: string[]) {
424
604
 
425
605
  const home = homedir();
426
606
  const state = await loadInstallState(home);
427
- const method = detectInstallMethod(state);
607
+ const method = await detectActiveInstallMethod(state);
428
608
 
429
609
  try {
430
610
  if (method === "script-dev") {
@@ -434,9 +614,10 @@ export async function selfUpdateCommand(argv: string[]) {
434
614
  );
435
615
  return;
436
616
  }
437
- if (method === "npm-binary-cache") {
617
+ if (method === "npm-binary-cache" || method === "mise-npm") {
438
618
  await selfUpdateViaPackageManager({
439
619
  ...parsed,
620
+ method,
440
621
  preferredPackageManager:
441
622
  process.env.FACULT_INSTALL_PM?.trim() || state?.packageManager,
442
623
  });