arcane-cli 0.15.8 → 0.16.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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ const require = __createRequire(import.meta.url);
6
6
  import { Command } from "commander";
7
7
  import { createRequire as createRequire2 } from "module";
8
8
  import { fileURLToPath } from "url";
9
- import { dirname as dirname3, join as join10 } from "path";
9
+ import { dirname as dirname3, join as join11 } from "path";
10
10
 
11
11
  // src/commands/init.ts
12
12
  import { select as select2, confirm } from "@inquirer/prompts";
@@ -305,13 +305,13 @@ var COMPONENTS = [
305
305
  // Spell prompts — installed to .github/prompts/ (VS Code Copilot convention)
306
306
  {
307
307
  name: "spell-prompts",
308
- description: "Full Arcane spell prompt library (33 spells)",
308
+ description: "Full Arcane spell prompt library (34 spells)",
309
309
  files: [
310
310
  ".github/prompts/spell-arcane-version.prompt.md",
311
311
  ".github/prompts/spell-architect.prompt.md",
312
312
  ".github/prompts/spell-brainstorm.prompt.md",
313
313
  ".github/prompts/spell-scope.prompt.md",
314
- ".github/prompts/spell-bootstrap-business.prompt.md",
314
+ ".github/prompts/spell-summon-venture.prompt.md",
315
315
  ".github/prompts/spell-bug.prompt.md",
316
316
  ".github/prompts/spell-bump.prompt.md",
317
317
  ".github/prompts/spell-check-drift.prompt.md",
@@ -338,6 +338,7 @@ var COMPONENTS = [
338
338
  ".github/prompts/spell-create-pull-request.prompt.md",
339
339
  ".github/prompts/spell-document.prompt.md",
340
340
  ".github/prompts/spell-feedback.prompt.md",
341
+ ".github/prompts/spell-manifest.prompt.md",
341
342
  ".github/prompts/spell-save-idea.prompt.md",
342
343
  ".github/prompts/spell-status.prompt.md",
343
344
  ".github/prompts/spell-review-batch.prompt.md"
@@ -346,8 +347,12 @@ var COMPONENTS = [
346
347
  // Templates
347
348
  {
348
349
  name: "venture-template",
349
- description: "Starter template for a new business venture folder",
350
- files: [".arcane/templates/venture-template/overview.md"]
350
+ description: "Starter template for a new business venture folder \u2014 overview plus idea/todo books",
351
+ files: [
352
+ ".arcane/templates/venture-template/overview.md",
353
+ ".arcane/templates/venture-template/IDEAS.md",
354
+ ".arcane/templates/venture-template/TODO.md"
355
+ ]
351
356
  },
352
357
  // Session continuity — files required for spell-close-session / spell-open-session
353
358
  {
@@ -378,13 +383,13 @@ var COMPONENTS = [
378
383
  // Claude commands
379
384
  {
380
385
  name: "claude-commands",
381
- description: "Claude Code slash command wrappers for all 33 Arcane spells",
386
+ description: "Claude Code slash command wrappers for all 34 Arcane spells",
382
387
  files: [
383
388
  ".claude/commands/spell-arcane-version.md",
384
389
  ".claude/commands/spell-architect.md",
385
390
  ".claude/commands/spell-brainstorm.md",
386
391
  ".claude/commands/spell-scope.md",
387
- ".claude/commands/spell-bootstrap-business.md",
392
+ ".claude/commands/spell-summon-venture.md",
388
393
  ".claude/commands/spell-bug.md",
389
394
  ".claude/commands/spell-bump.md",
390
395
  ".claude/commands/spell-check-drift.md",
@@ -411,6 +416,7 @@ var COMPONENTS = [
411
416
  ".claude/commands/spell-create-pull-request.md",
412
417
  ".claude/commands/spell-document.md",
413
418
  ".claude/commands/spell-feedback.md",
419
+ ".claude/commands/spell-manifest.md",
414
420
  ".claude/commands/spell-save-idea.md",
415
421
  ".claude/commands/spell-status.md",
416
422
  ".claude/commands/spell-review-batch.md"
@@ -1824,11 +1830,21 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
1824
1830
  printDryRun(`Would initialize with profile "${profile}" \u2014 ${fileCount} files`);
1825
1831
  return;
1826
1832
  }
1833
+ let role;
1834
+ if (!options.profile) {
1835
+ console.log();
1836
+ const isHub = await confirm({
1837
+ message: "Will this repo manage other ventures as a hub? (idea books, spell-manifest promotion, venture registry)",
1838
+ default: false
1839
+ });
1840
+ role = isHub ? "hub" : "consumer";
1841
+ }
1827
1842
  const manifest = {
1828
1843
  version: packageVersion,
1829
1844
  profile,
1830
1845
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1831
- components: installedComponents
1846
+ components: installedComponents,
1847
+ ...role ? { role } : {}
1832
1848
  };
1833
1849
  await writeManifest(targetDir, manifest);
1834
1850
  printSuccess(`Initialized with profile "${profile}" \u2014 ${fileCount} files installed`);
@@ -1946,7 +1962,87 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
1946
1962
  }
1947
1963
 
1948
1964
  // src/commands/update.ts
1965
+ import { join as join7 } from "path";
1966
+
1967
+ // src/modules/hub.ts
1968
+ import { confirm as confirm2 } from "@inquirer/prompts";
1969
+ import { readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
1949
1970
  import { join as join6 } from "path";
1971
+ var MANIFEST_RETROFITS = [
1972
+ {
1973
+ field: "role",
1974
+ needsRetrofit: (m) => m.role === void 0,
1975
+ ask: async () => {
1976
+ const isHub = await confirm2({
1977
+ message: "Will this repo manage other ventures as a hub? (idea books, spell-manifest promotion, venture registry)",
1978
+ default: false
1979
+ });
1980
+ return { role: isHub ? "hub" : "consumer" };
1981
+ }
1982
+ }
1983
+ ];
1984
+ async function runManifestRetrofits(manifest) {
1985
+ const applicable = MANIFEST_RETROFITS.filter((r) => r.needsRetrofit(manifest));
1986
+ if (applicable.length === 0) return {};
1987
+ console.log();
1988
+ console.log(
1989
+ `This install predates ${applicable.length} manifest field${applicable.length === 1 ? "" : "s"} -- a couple of quick questions:`
1990
+ );
1991
+ let patch = {};
1992
+ for (const retrofit of applicable) {
1993
+ const answer = await retrofit.ask();
1994
+ patch = { ...patch, ...answer };
1995
+ }
1996
+ return patch;
1997
+ }
1998
+ async function offerRegistryScaffold(targetDir, businessRoot) {
1999
+ const registryPath = join6(targetDir, businessRoot, "registry.json");
2000
+ if (await fileExists(registryPath)) return;
2001
+ const businessRootPath = join6(targetDir, businessRoot);
2002
+ let ventureDirs;
2003
+ try {
2004
+ const entries = await readdir3(businessRootPath, { withFileTypes: true });
2005
+ ventureDirs = entries.filter((e) => e.isDirectory() && e.name !== "_template").map((e) => e.name);
2006
+ } catch {
2007
+ return;
2008
+ }
2009
+ if (ventureDirs.length === 0) return;
2010
+ console.log();
2011
+ const scaffold = await confirm2({
2012
+ message: `Found ${ventureDirs.length} folder${ventureDirs.length === 1 ? "" : "s"} under ${businessRoot}/ -- scaffold ${businessRoot}/registry.json from them?`,
2013
+ default: true
2014
+ });
2015
+ if (!scaffold) return;
2016
+ const ventures = {};
2017
+ for (const slug of ventureDirs) {
2018
+ const include = await confirm2({ message: ` Include "${slug}"?`, default: true });
2019
+ if (!include) continue;
2020
+ ventures[slug] = {
2021
+ name: slug,
2022
+ aliases: [],
2023
+ status: "active",
2024
+ visibility: "private",
2025
+ ownership: "llc",
2026
+ tracking: "none",
2027
+ repos: []
2028
+ };
2029
+ }
2030
+ if (Object.keys(ventures).length === 0) {
2031
+ console.log(" No ventures selected -- registry not created.");
2032
+ return;
2033
+ }
2034
+ const registry = {
2035
+ _comment: "Hub-owned venture registry. Private. Never installed or modified by spell update. Seeded by the retrofit wizard -- review and enrich (aliases, ownership, tracking, clones) before relying on it.",
2036
+ updated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
2037
+ ventures
2038
+ };
2039
+ await writeFile5(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
2040
+ console.log(
2041
+ ` \u2713 Scaffolded ${businessRoot}/registry.json with ${Object.keys(ventures).length} venture(s).`
2042
+ );
2043
+ }
2044
+
2045
+ // src/commands/update.ts
1950
2046
  async function runUpdate(options, targetDir, assetsDir, packageVersion) {
1951
2047
  let manifest;
1952
2048
  try {
@@ -2013,8 +2109,8 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
2013
2109
  }
2014
2110
  const updatedFiles = [];
2015
2111
  for (const file of component.files) {
2016
- const srcPath = join6(assetsDir, file);
2017
- const preserveExisting = component.skipExisting && await fileExists(join6(targetDir, file));
2112
+ const srcPath = join7(assetsDir, file);
2113
+ const preserveExisting = component.skipExisting && await fileExists(join7(targetDir, file));
2018
2114
  if (preserveExisting) {
2019
2115
  console.log(` ${options.dryRun ? "[dry-run] Would preserve" : "Preserved"}: ${file}`);
2020
2116
  } else if (options.dryRun) {
@@ -2027,7 +2123,7 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
2027
2123
  updatedFiles.push(file);
2028
2124
  }
2029
2125
  for (const dir of component.directories ?? []) {
2030
- const srcDirPath = join6(assetsDir, dir);
2126
+ const srcDirPath = join7(assetsDir, dir);
2031
2127
  if (options.dryRun) {
2032
2128
  console.log(` [dry-run] Would update directory: ${dir}/`);
2033
2129
  } else {
@@ -2047,19 +2143,34 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
2047
2143
  `
2048
2144
  [dry-run] Would update ${fileCount} files.`
2049
2145
  );
2146
+ const applicableRetrofits = MANIFEST_RETROFITS.filter((r) => r.needsRetrofit(manifest));
2147
+ if (applicableRetrofits.length > 0) {
2148
+ console.log(
2149
+ `[dry-run] Would ask ${applicableRetrofits.length} manifest retrofit question${applicableRetrofits.length === 1 ? "" : "s"}: ${applicableRetrofits.map((r) => r.field).join(", ")}.`
2150
+ );
2151
+ }
2050
2152
  return;
2051
2153
  }
2052
- const updated = { ...manifest, version: packageVersion, components: updatedComponents };
2154
+ const retrofitPatch = await runManifestRetrofits(manifest);
2155
+ const updated = {
2156
+ ...manifest,
2157
+ version: packageVersion,
2158
+ components: updatedComponents,
2159
+ ...retrofitPatch
2160
+ };
2053
2161
  await writeManifest(targetDir, updated);
2054
2162
  console.log(
2055
2163
  `
2056
2164
  \u2713 Updated ${fileCount} files.`
2057
2165
  );
2166
+ if (manifest.role !== "hub" && updated.role === "hub") {
2167
+ await offerRegistryScaffold(targetDir, updated.business_root ?? "ventures");
2168
+ }
2058
2169
  }
2059
2170
 
2060
2171
  // src/modules/version-check.ts
2061
- import { readFile as readFile4, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
2062
- import { dirname, join as join7 } from "path";
2172
+ import { readFile as readFile4, writeFile as writeFile6, mkdir as mkdir4 } from "fs/promises";
2173
+ import { dirname, join as join8 } from "path";
2063
2174
  import { homedir as homedir2 } from "os";
2064
2175
  import { createRequire } from "module";
2065
2176
  var require2 = createRequire(import.meta.url);
@@ -2073,7 +2184,7 @@ function getFeedUrl() {
2073
2184
  }
2074
2185
  return url;
2075
2186
  }
2076
- var DEFAULT_CACHE_PATH = join7(homedir2(), ".arcane", "version-cache.json");
2187
+ var DEFAULT_CACHE_PATH = join8(homedir2(), ".arcane", "version-cache.json");
2077
2188
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2078
2189
  async function readCache(cachePath) {
2079
2190
  try {
@@ -2090,7 +2201,7 @@ async function readCache(cachePath) {
2090
2201
  async function writeCache(cachePath, latest) {
2091
2202
  await mkdir4(dirname(cachePath), { recursive: true });
2092
2203
  const entry = { latest, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
2093
- await writeFile5(cachePath, JSON.stringify(entry, null, 2), "utf-8");
2204
+ await writeFile6(cachePath, JSON.stringify(entry, null, 2), "utf-8");
2094
2205
  }
2095
2206
  async function getLatestVersion(feedUrl) {
2096
2207
  const normalizedBase = feedUrl.endsWith("/") ? feedUrl : feedUrl + "/";
@@ -2203,8 +2314,8 @@ async function runStatus(targetDir, packageVersion) {
2203
2314
 
2204
2315
  // src/commands/uninstall.ts
2205
2316
  import { rm as rm2 } from "fs/promises";
2206
- import { join as join8 } from "path";
2207
- import { confirm as confirm2 } from "@inquirer/prompts";
2317
+ import { join as join9 } from "path";
2318
+ import { confirm as confirm3 } from "@inquirer/prompts";
2208
2319
  async function runUninstall(options, targetDir) {
2209
2320
  let manifest;
2210
2321
  try {
@@ -2234,7 +2345,7 @@ async function runUninstall(options, targetDir) {
2234
2345
  return;
2235
2346
  }
2236
2347
  if (!options.yes) {
2237
- const confirmed = await confirm2({
2348
+ const confirmed = await confirm3({
2238
2349
  message: `This will remove ${totalFiles} files and .arcane.json. Continue?`,
2239
2350
  default: false
2240
2351
  });
@@ -2246,7 +2357,7 @@ async function runUninstall(options, targetDir) {
2246
2357
  let removed = 0;
2247
2358
  for (const component of manifest.components) {
2248
2359
  for (const file of component.files) {
2249
- const filePath = join8(targetDir, file);
2360
+ const filePath = join9(targetDir, file);
2250
2361
  try {
2251
2362
  await rm2(filePath, { force: true });
2252
2363
  removed++;
@@ -2254,16 +2365,16 @@ async function runUninstall(options, targetDir) {
2254
2365
  }
2255
2366
  }
2256
2367
  }
2257
- await rm2(join8(targetDir, ".arcane.json"), { force: true });
2368
+ await rm2(join9(targetDir, ".arcane.json"), { force: true });
2258
2369
  const agentDirs = [
2259
2370
  ".github/agents",
2260
2371
  ".arcane/agents",
2261
2372
  ".arcane/generated"
2262
2373
  ];
2263
2374
  for (const dir of agentDirs) {
2264
- await rm2(join8(targetDir, dir), { recursive: true, force: true });
2375
+ await rm2(join9(targetDir, dir), { recursive: true, force: true });
2265
2376
  }
2266
- await rm2(join8(targetDir, ".arcane/agents.yaml"), { force: true });
2377
+ await rm2(join9(targetDir, ".arcane/agents.yaml"), { force: true });
2267
2378
  const markerFiles = [
2268
2379
  "CLAUDE.md",
2269
2380
  "AGENTS.md",
@@ -2278,7 +2389,7 @@ async function runUninstall(options, targetDir) {
2278
2389
 
2279
2390
  // src/commands/doctor.ts
2280
2391
  import { access as access3, readFile as readFile5, mkdir as mkdir5, copyFile as fsCopyFile2 } from "fs/promises";
2281
- import { join as join9, dirname as dirname2 } from "path";
2392
+ import { join as join10, dirname as dirname2 } from "path";
2282
2393
  import { execFile as execFile2 } from "child_process";
2283
2394
  import { promisify as promisify2 } from "util";
2284
2395
 
@@ -2366,13 +2477,13 @@ async function checkVSCodeExtension(id, label) {
2366
2477
  const name = `VS Code extension: ${label}`;
2367
2478
  const home = process.env["HOME"] ?? "";
2368
2479
  const extDirs = [
2369
- join9(home, ".vscode", "extensions"),
2370
- join9(home, ".vscode-insiders", "extensions")
2480
+ join10(home, ".vscode", "extensions"),
2481
+ join10(home, ".vscode-insiders", "extensions")
2371
2482
  ];
2372
2483
  for (const dir of extDirs) {
2373
2484
  try {
2374
- const { readdir: readdir3 } = await import("fs/promises");
2375
- const entries = await readdir3(dir);
2485
+ const { readdir: readdir4 } = await import("fs/promises");
2486
+ const entries = await readdir4(dir);
2376
2487
  const prefix = id.toLowerCase() + "-";
2377
2488
  const found = entries.some(
2378
2489
  (e) => e.toLowerCase() === id.toLowerCase() || e.toLowerCase().startsWith(prefix)
@@ -2401,11 +2512,11 @@ async function checkVSCodeExtension(id, label) {
2401
2512
  }
2402
2513
  async function checkArcaneManifest(targetDir) {
2403
2514
  const name = "Arcane manifest (.arcane.json)";
2404
- const manifestPath2 = join9(targetDir, ".arcane.json");
2515
+ const manifestPath2 = join10(targetDir, ".arcane.json");
2405
2516
  try {
2406
2517
  await access3(manifestPath2);
2407
2518
  } catch {
2408
- const selfHostedManifestPath = join9(targetDir, "src", "assets", ".arcane.json");
2519
+ const selfHostedManifestPath = join10(targetDir, "src", "assets", ".arcane.json");
2409
2520
  try {
2410
2521
  const raw = await readFile5(selfHostedManifestPath, "utf8");
2411
2522
  const manifest = JSON.parse(raw);
@@ -2474,7 +2585,7 @@ async function checkSessionContinuity(targetDir) {
2474
2585
  const results = [];
2475
2586
  for (const file of SESSION_CONTINUITY_FILES) {
2476
2587
  const name = `Session file: ${file}`;
2477
- const filePath = join9(targetDir, file);
2588
+ const filePath = join10(targetDir, file);
2478
2589
  try {
2479
2590
  await access3(filePath);
2480
2591
  results.push({ name, passed: true, message: "present" });
@@ -2492,11 +2603,11 @@ async function checkSessionContinuity(targetDir) {
2492
2603
  async function fixSessionContinuity(targetDir, assetsDir) {
2493
2604
  const created = [];
2494
2605
  for (const file of SESSION_CONTINUITY_FILES) {
2495
- const destPath = join9(targetDir, file);
2606
+ const destPath = join10(targetDir, file);
2496
2607
  try {
2497
2608
  await access3(destPath);
2498
2609
  } catch {
2499
- const srcPath = join9(assetsDir, file);
2610
+ const srcPath = join10(assetsDir, file);
2500
2611
  await mkdir5(dirname2(destPath), { recursive: true });
2501
2612
  await fsCopyFile2(srcPath, destPath);
2502
2613
  created.push(file);
@@ -2558,7 +2669,7 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
2558
2669
  import { realpathSync } from "fs";
2559
2670
  var require3 = createRequire2(import.meta.url);
2560
2671
  var pkg = require3("../package.json");
2561
- var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join10(dirname3(fileURLToPath(import.meta.url)), "assets");
2672
+ var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join11(dirname3(fileURLToPath(import.meta.url)), "assets");
2562
2673
  var program = new Command();
2563
2674
  program.name("spell").description("Arcane framework CLI \u2014 scaffold and manage governance files").version(pkg.version).action(async () => {
2564
2675
  await printWelcome(pkg.version, process.cwd());