@flydocs/cli 0.6.0-alpha.30 → 0.6.0-alpha.32

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/cli.js CHANGED
@@ -15,7 +15,7 @@ var CLI_VERSION, CLI_NAME, PACKAGE_NAME, POSTHOG_API_KEY;
15
15
  var init_constants = __esm({
16
16
  "src/lib/constants.ts"() {
17
17
  "use strict";
18
- CLI_VERSION = "0.6.0-alpha.30";
18
+ CLI_VERSION = "0.6.0-alpha.32";
19
19
  CLI_NAME = "flydocs";
20
20
  PACKAGE_NAME = "@flydocs/cli";
21
21
  POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
@@ -440,9 +440,9 @@ async function replaceOwnedSkills(templateDir, targetDir, _tier) {
440
440
  }
441
441
  }
442
442
  async function copyCursorRules(targetDir) {
443
- const { mkdir: mkdir12 } = await import("fs/promises");
443
+ const { mkdir: mkdir15 } = await import("fs/promises");
444
444
  const rulesDir = join4(targetDir, ".cursor", "rules");
445
- await mkdir12(rulesDir, { recursive: true });
445
+ await mkdir15(rulesDir, { recursive: true });
446
446
  const workflowRule = join4(
447
447
  targetDir,
448
448
  ".claude",
@@ -1492,10 +1492,20 @@ import { join as join11 } from "path";
1492
1492
  async function ensureGitignore(targetDir) {
1493
1493
  const gitignorePath = join11(targetDir, ".gitignore");
1494
1494
  if (await pathExists(gitignorePath)) {
1495
- const content = await readFile6(gitignorePath, "utf-8");
1495
+ let content = await readFile6(gitignorePath, "utf-8");
1496
+ let modified = false;
1496
1497
  if (!content.includes("# FlyDocs")) {
1497
- const section = "\n# FlyDocs\n" + FLYDOCS_GITIGNORE_ENTRIES.join("\n") + "\n";
1498
- await appendFile(gitignorePath, section, "utf-8");
1498
+ const section = "\n# FlyDocs \u2014 local state\n" + FLYDOCS_GITIGNORE_ENTRIES.join("\n") + "\n";
1499
+ content += section;
1500
+ modified = true;
1501
+ }
1502
+ if (!content.includes("# FlyDocs \u2014 server-managed")) {
1503
+ const section = "\n# FlyDocs \u2014 server-managed (rebuilt by flydocs sync)\n" + SERVER_MANAGED_GITIGNORE_ENTRIES.join("\n") + "\n";
1504
+ content += section;
1505
+ modified = true;
1506
+ }
1507
+ if (modified) {
1508
+ await writeFile4(gitignorePath, content, "utf-8");
1499
1509
  printStatus("Added FlyDocs entries to .gitignore");
1500
1510
  }
1501
1511
  } else {
@@ -1507,33 +1517,55 @@ async function migrateGitignore(targetDir) {
1507
1517
  const gitignorePath = join11(targetDir, ".gitignore");
1508
1518
  if (!await pathExists(gitignorePath)) return;
1509
1519
  let content = await readFile6(gitignorePath, "utf-8");
1510
- for (const entry of FLYDOCS_GITIGNORE_ENTRIES) {
1520
+ let modified = false;
1521
+ content = migrateEntriesToSection(
1522
+ content,
1523
+ FLYDOCS_GITIGNORE_ENTRIES,
1524
+ "# FlyDocs"
1525
+ );
1526
+ if (!content.includes("# FlyDocs \u2014 server-managed")) {
1527
+ const section = "\n# FlyDocs \u2014 server-managed (rebuilt by flydocs sync)\n" + SERVER_MANAGED_GITIGNORE_ENTRIES.join("\n") + "\n";
1528
+ content += section;
1529
+ modified = true;
1530
+ } else {
1531
+ const before = content;
1532
+ content = migrateEntriesToSection(
1533
+ content,
1534
+ SERVER_MANAGED_GITIGNORE_ENTRIES,
1535
+ "# FlyDocs \u2014 server-managed"
1536
+ );
1537
+ if (content !== before) modified = true;
1538
+ }
1539
+ if (!modified) {
1540
+ const original = await readFile6(gitignorePath, "utf-8");
1541
+ if (content !== original) modified = true;
1542
+ }
1543
+ if (modified) {
1544
+ await writeFile4(gitignorePath, content, "utf-8");
1545
+ printStatus("Updated .gitignore with new FlyDocs entries");
1546
+ }
1547
+ }
1548
+ function migrateEntriesToSection(content, entries, sectionMarker) {
1549
+ for (const entry of entries) {
1511
1550
  if (content.includes(entry)) continue;
1512
- if (content.includes("# FlyDocs")) {
1513
- const lines = content.split("\n");
1514
- const flyDocsIdx = lines.findIndex((l) => l.trim() === "# FlyDocs");
1515
- if (flyDocsIdx !== -1) {
1516
- let insertIdx = flyDocsIdx + 1;
1517
- while (insertIdx < lines.length && lines[insertIdx].trim() !== "" && !lines[insertIdx].startsWith("#")) {
1518
- insertIdx++;
1519
- }
1520
- lines.splice(insertIdx, 0, entry);
1521
- content = lines.join("\n");
1522
- await writeFile4(gitignorePath, content, "utf-8");
1523
- } else {
1524
- content += `
1525
- ${entry}
1526
- `;
1527
- await writeFile4(gitignorePath, content, "utf-8");
1551
+ const lines = content.split("\n");
1552
+ const sectionIdx = lines.findIndex(
1553
+ (l) => l.trim().startsWith(sectionMarker)
1554
+ );
1555
+ if (sectionIdx !== -1) {
1556
+ let insertIdx = sectionIdx + 1;
1557
+ while (insertIdx < lines.length && lines[insertIdx].trim() !== "" && !lines[insertIdx].startsWith("#")) {
1558
+ insertIdx++;
1528
1559
  }
1560
+ lines.splice(insertIdx, 0, entry);
1561
+ content = lines.join("\n");
1529
1562
  } else {
1530
1563
  content += `
1531
1564
  ${entry}
1532
1565
  `;
1533
- await writeFile4(gitignorePath, content, "utf-8");
1534
1566
  }
1535
- printStatus(`Added ${entry} to .gitignore`);
1536
1567
  }
1568
+ return content;
1537
1569
  }
1538
1570
  async function ensurePlatformIgnores(targetDir) {
1539
1571
  for (const filename of PLATFORM_IGNORE_FILES) {
@@ -1546,7 +1578,7 @@ async function ensurePlatformIgnores(targetDir) {
1546
1578
  printStatus(`Added FlyDocs exclusions to ${filename}`);
1547
1579
  }
1548
1580
  }
1549
- var PLATFORM_IGNORE_FILES, FLYDOCS_DEPLOY_EXCLUSIONS, FLYDOCS_GITIGNORE_ENTRIES, FULL_GITIGNORE_TEMPLATE;
1581
+ var PLATFORM_IGNORE_FILES, FLYDOCS_DEPLOY_EXCLUSIONS, FLYDOCS_GITIGNORE_ENTRIES, SERVER_MANAGED_GITIGNORE_ENTRIES, FULL_GITIGNORE_TEMPLATE;
1550
1582
  var init_gitignore = __esm({
1551
1583
  "src/lib/gitignore.ts"() {
1552
1584
  "use strict";
@@ -1582,12 +1614,23 @@ var init_gitignore = __esm({
1582
1614
  ".flydocs/session/",
1583
1615
  "flydocs/context/graph.json"
1584
1616
  ];
1617
+ SERVER_MANAGED_GITIGNORE_ENTRIES = [
1618
+ ".claude/skills/",
1619
+ ".claude/commands/",
1620
+ ".claude/hooks/",
1621
+ ".claude/agents/",
1622
+ ".claude/settings.json",
1623
+ ".claude/CLAUDE.md",
1624
+ ".cursor/rules/",
1625
+ ".cursor/commands/",
1626
+ "AGENTS.md"
1627
+ ];
1585
1628
  FULL_GITIGNORE_TEMPLATE = `# Environment
1586
1629
  .env
1587
1630
  .env.local
1588
1631
  .env.*.local
1589
1632
 
1590
- # FlyDocs
1633
+ # FlyDocs \u2014 local state
1591
1634
  .flydocs/session.json
1592
1635
  .flydocs/logs/
1593
1636
  .flydocs/backup-*/
@@ -1597,6 +1640,17 @@ var init_gitignore = __esm({
1597
1640
  .flydocs/session/
1598
1641
  flydocs/context/graph.json
1599
1642
 
1643
+ # FlyDocs \u2014 server-managed (rebuilt by flydocs sync)
1644
+ .claude/skills/
1645
+ .claude/commands/
1646
+ .claude/hooks/
1647
+ .claude/agents/
1648
+ .claude/settings.json
1649
+ .claude/CLAUDE.md
1650
+ .cursor/rules/
1651
+ .cursor/commands/
1652
+ AGENTS.md
1653
+
1600
1654
  # Dependencies
1601
1655
  node_modules/
1602
1656
 
@@ -2973,6 +3027,65 @@ async function fetchTemplates(apiKey, sinceVersion, workspaceId) {
2973
3027
  }
2974
3028
  return await response.json();
2975
3029
  }
3030
+ async function fetchArtifacts(apiKey, sinceVersion) {
3031
+ const baseUrl = resolveRelayUrl();
3032
+ const response = await fetch(`${baseUrl}/artifacts?since=${sinceVersion}`, {
3033
+ method: "GET",
3034
+ headers: headers(apiKey),
3035
+ signal: AbortSignal.timeout(3e4)
3036
+ });
3037
+ if (!response.ok) {
3038
+ const body = await response.text().catch(() => "");
3039
+ throw new RelayError(
3040
+ `artifacts fetch failed (${response.status})`,
3041
+ response.status,
3042
+ body
3043
+ );
3044
+ }
3045
+ return await response.json();
3046
+ }
3047
+ async function fetchAgentConfigs(apiKey, workspaceId) {
3048
+ const baseUrl = resolveRelayUrl();
3049
+ const response = await fetch(`${baseUrl}/artifacts/agent-configs`, {
3050
+ method: "GET",
3051
+ headers: headers(apiKey, workspaceId),
3052
+ signal: AbortSignal.timeout(15e3)
3053
+ });
3054
+ if (!response.ok) {
3055
+ const body = await response.text().catch(() => "");
3056
+ throw new RelayError(
3057
+ `agent-configs fetch failed (${response.status})`,
3058
+ response.status,
3059
+ body
3060
+ );
3061
+ }
3062
+ return await response.json();
3063
+ }
3064
+ async function triggerScan(apiKey, repoName, options) {
3065
+ const baseUrl = resolveRelayUrl();
3066
+ const body = {
3067
+ repoName,
3068
+ options: {
3069
+ regenerate: options.regenerate ?? false,
3070
+ includeIdeConfigs: options.includeIdeConfigs ?? false
3071
+ }
3072
+ };
3073
+ const response = await fetch(`${baseUrl}/ai/generate-context`, {
3074
+ method: "POST",
3075
+ headers: headers(apiKey, options.workspaceId),
3076
+ body: JSON.stringify(body),
3077
+ signal: AbortSignal.timeout(6e4)
3078
+ });
3079
+ if (!response.ok) {
3080
+ const responseBody = await response.text().catch(() => "");
3081
+ throw new RelayError(
3082
+ `ai/generate-context failed (${response.status})`,
3083
+ response.status,
3084
+ responseBody
3085
+ );
3086
+ }
3087
+ return await response.json();
3088
+ }
2976
3089
  var DEFAULT_RELAY_URL, RelayError;
2977
3090
  var init_relay_client = __esm({
2978
3091
  "src/lib/relay-client.ts"() {
@@ -2989,6 +3102,90 @@ var init_relay_client = __esm({
2989
3102
  }
2990
3103
  });
2991
3104
 
3105
+ // src/lib/artifacts.ts
3106
+ import { mkdir as mkdir9, writeFile as writeFile11 } from "fs/promises";
3107
+ import { join as join18, dirname as dirname3 } from "path";
3108
+ async function syncArtifacts(targetDir, apiKey, workspaceId, currentVersion) {
3109
+ const response = await fetchArtifacts(apiKey, currentVersion);
3110
+ if (response.artifacts.length === 0) {
3111
+ return { written: 0, version: currentVersion };
3112
+ }
3113
+ let written = 0;
3114
+ for (const artifact of response.artifacts) {
3115
+ await writeArtifact(targetDir, artifact);
3116
+ written++;
3117
+ }
3118
+ printStatus(
3119
+ `Synced ${written} artifact(s) (v${currentVersion} \u2192 v${response.version})`
3120
+ );
3121
+ return { written, version: response.version };
3122
+ }
3123
+ async function syncAgentConfigs(targetDir, apiKey, workspaceId) {
3124
+ const response = await fetchAgentConfigs(apiKey, workspaceId);
3125
+ let written = 0;
3126
+ for (const file of response.files) {
3127
+ const filePath = join18(targetDir, file.path);
3128
+ await mkdir9(dirname3(filePath), { recursive: true });
3129
+ await writeFile11(filePath, file.content, "utf-8");
3130
+ written++;
3131
+ }
3132
+ if (written > 0) {
3133
+ printStatus(`Wrote ${written} agent config(s) (CLAUDE.md, AGENTS.md)`);
3134
+ }
3135
+ return written;
3136
+ }
3137
+ async function writeArtifact(targetDir, artifact) {
3138
+ const filePath = join18(targetDir, artifact.path);
3139
+ await mkdir9(dirname3(filePath), { recursive: true });
3140
+ await writeFile11(filePath, artifact.content, "utf-8");
3141
+ }
3142
+ async function syncAllArtifacts(targetDir, apiKey, workspaceId, currentArtifactVersion) {
3143
+ const changes = [];
3144
+ let artifactVersion = currentArtifactVersion;
3145
+ try {
3146
+ const result = await syncArtifacts(
3147
+ targetDir,
3148
+ apiKey,
3149
+ workspaceId,
3150
+ currentArtifactVersion
3151
+ );
3152
+ if (result.written > 0) {
3153
+ changes.push(`Synced ${result.written} artifact(s)`);
3154
+ artifactVersion = result.version;
3155
+ }
3156
+ } catch (err) {
3157
+ if (err instanceof RelayError) {
3158
+ printWarning(
3159
+ `Could not fetch artifacts (${err.status}). Will retry on next sync.`
3160
+ );
3161
+ } else {
3162
+ printWarning("Artifact sync failed. Will retry on next sync.");
3163
+ }
3164
+ }
3165
+ try {
3166
+ const configCount = await syncAgentConfigs(targetDir, apiKey, workspaceId);
3167
+ if (configCount > 0) {
3168
+ changes.push(`Wrote ${configCount} agent config(s)`);
3169
+ }
3170
+ } catch (err) {
3171
+ if (err instanceof RelayError) {
3172
+ printWarning(
3173
+ `Could not fetch agent configs (${err.status}). Will retry on next sync.`
3174
+ );
3175
+ } else {
3176
+ printWarning("Agent config sync failed. Will retry on next sync.");
3177
+ }
3178
+ }
3179
+ return { artifactVersion, changes };
3180
+ }
3181
+ var init_artifacts = __esm({
3182
+ "src/lib/artifacts.ts"() {
3183
+ "use strict";
3184
+ init_ui();
3185
+ init_relay_client();
3186
+ }
3187
+ });
3188
+
2992
3189
  // src/commands/cleanup.ts
2993
3190
  var cleanup_exports = {};
2994
3191
  __export(cleanup_exports, {
@@ -2998,11 +3195,11 @@ __export(cleanup_exports, {
2998
3195
  });
2999
3196
  import { defineCommand as defineCommand2 } from "citty";
3000
3197
  import pc7 from "picocolors";
3001
- import { join as join18 } from "path";
3002
- import { readFile as readFile13, writeFile as writeFile11, rm as rm4 } from "fs/promises";
3198
+ import { join as join19 } from "path";
3199
+ import { readFile as readFile13, writeFile as writeFile12, rm as rm4 } from "fs/promises";
3003
3200
  async function scanArtifacts(targetDir) {
3004
3201
  const actions = [];
3005
- const localMePath = join18(targetDir, ".flydocs", "me.json");
3202
+ const localMePath = join19(targetDir, ".flydocs", "me.json");
3006
3203
  if (await pathExists(localMePath) && await pathExists(globalMePath())) {
3007
3204
  actions.push({
3008
3205
  description: "Remove .flydocs/me.json (migrated to ~/.flydocs/me.json)",
@@ -3010,7 +3207,7 @@ async function scanArtifacts(targetDir) {
3010
3207
  type: "file"
3011
3208
  });
3012
3209
  }
3013
- const validationCachePath = join18(
3210
+ const validationCachePath = join19(
3014
3211
  targetDir,
3015
3212
  ".flydocs",
3016
3213
  "validation-cache.json"
@@ -3024,7 +3221,7 @@ async function scanArtifacts(targetDir) {
3024
3221
  }
3025
3222
  const hasGlobalCreds = await pathExists(credentialsPath());
3026
3223
  for (const envFile of [".env", ".env.local"]) {
3027
- const envPath = join18(targetDir, envFile);
3224
+ const envPath = join19(targetDir, envFile);
3028
3225
  if (!await pathExists(envPath)) continue;
3029
3226
  const content = await readFile13(envPath, "utf-8");
3030
3227
  const lines = content.split("\n");
@@ -3049,7 +3246,7 @@ async function scanArtifacts(targetDir) {
3049
3246
  try {
3050
3247
  const config = await readAnyConfig(targetDir);
3051
3248
  const rawContent = await readFile13(
3052
- join18(targetDir, ".flydocs", "config.json"),
3249
+ join19(targetDir, ".flydocs", "config.json"),
3053
3250
  "utf-8"
3054
3251
  );
3055
3252
  const raw = JSON.parse(rawContent);
@@ -3058,7 +3255,7 @@ async function scanArtifacts(targetDir) {
3058
3255
  if (field in raw) {
3059
3256
  actions.push({
3060
3257
  description: `Remove ghost field "${field}" from config.json`,
3061
- path: join18(targetDir, ".flydocs", "config.json"),
3258
+ path: join19(targetDir, ".flydocs", "config.json"),
3062
3259
  type: "field"
3063
3260
  });
3064
3261
  }
@@ -3077,7 +3274,7 @@ async function scanArtifacts(targetDir) {
3077
3274
  if (field in raw) {
3078
3275
  actions.push({
3079
3276
  description: `Remove v1 field "${field}" from config.json (server-owned in v2)`,
3080
- path: join18(targetDir, ".flydocs", "config.json"),
3277
+ path: join19(targetDir, ".flydocs", "config.json"),
3081
3278
  type: "field"
3082
3279
  });
3083
3280
  }
@@ -3112,11 +3309,11 @@ async function executeCleanup(targetDir, actions) {
3112
3309
  if (!hasContent) {
3113
3310
  await rm4(envPath, { force: true });
3114
3311
  } else {
3115
- await writeFile11(envPath, filtered.join("\n"), "utf-8");
3312
+ await writeFile12(envPath, filtered.join("\n"), "utf-8");
3116
3313
  }
3117
3314
  }
3118
3315
  if (fieldRemovals.length > 0) {
3119
- const configPath = join18(targetDir, ".flydocs", "config.json");
3316
+ const configPath = join19(targetDir, ".flydocs", "config.json");
3120
3317
  const content = await readFile13(configPath, "utf-8");
3121
3318
  const config = JSON.parse(content);
3122
3319
  for (const action of fieldRemovals) {
@@ -3125,7 +3322,7 @@ async function executeCleanup(targetDir, actions) {
3125
3322
  delete config[match[1]];
3126
3323
  }
3127
3324
  }
3128
- await writeFile11(
3325
+ await writeFile12(
3129
3326
  configPath,
3130
3327
  JSON.stringify(config, null, 2) + "\n",
3131
3328
  "utf-8"
@@ -3163,7 +3360,7 @@ var init_cleanup = __esm({
3163
3360
  console.log(` ${pc7.bold("FlyDocs Cleanup")}`);
3164
3361
  console.log();
3165
3362
  const hasConfig = await pathExists(
3166
- join18(targetDir, ".flydocs", "config.json")
3363
+ join19(targetDir, ".flydocs", "config.json")
3167
3364
  );
3168
3365
  if (!hasConfig) {
3169
3366
  printError("No .flydocs/config.json found. Run flydocs init first.");
@@ -3206,33 +3403,106 @@ var init_cleanup = __esm({
3206
3403
  }
3207
3404
  });
3208
3405
 
3406
+ // src/lib/ide-archive.ts
3407
+ import { readFile as readFile14, writeFile as writeFile13, mkdir as mkdir10, readdir as readdir4 } from "fs/promises";
3408
+ import { join as join20, basename } from "path";
3409
+ async function archiveIdeConfigs(targetDir) {
3410
+ const archived = [];
3411
+ for (const cfg of SINGLE_FILE_CONFIGS) {
3412
+ const absSource = join20(targetDir, cfg.source);
3413
+ if (!await pathExists(absSource)) continue;
3414
+ const content = await readFile14(absSource, "utf-8");
3415
+ const absDest = join20(targetDir, cfg.archive);
3416
+ await mkdir10(join20(absDest, ".."), { recursive: true });
3417
+ await writeFile13(absDest, content, "utf-8");
3418
+ archived.push({
3419
+ sourcePath: cfg.source,
3420
+ archivePath: cfg.archive,
3421
+ content
3422
+ });
3423
+ printStatus(`Archived ${cfg.source}`);
3424
+ }
3425
+ const cursorRulesDir = join20(targetDir, ".cursor", "rules");
3426
+ if (await pathExists(cursorRulesDir)) {
3427
+ let entries;
3428
+ try {
3429
+ entries = await readdir4(cursorRulesDir);
3430
+ } catch {
3431
+ entries = [];
3432
+ }
3433
+ const mdFiles = entries.filter((f) => f.endsWith(".md"));
3434
+ if (mdFiles.length > 0) {
3435
+ const archiveSubdir = join20(
3436
+ targetDir,
3437
+ "flydocs",
3438
+ "knowledge",
3439
+ "archived",
3440
+ "cursor-rules"
3441
+ );
3442
+ await mkdir10(archiveSubdir, { recursive: true });
3443
+ for (const file of mdFiles) {
3444
+ const absSource = join20(cursorRulesDir, file);
3445
+ const content = await readFile14(absSource, "utf-8");
3446
+ const archivePath = `flydocs/knowledge/archived/cursor-rules/${basename(file)}`;
3447
+ const absDest = join20(targetDir, archivePath);
3448
+ await writeFile13(absDest, content, "utf-8");
3449
+ archived.push({
3450
+ sourcePath: `.cursor/rules/${file}`,
3451
+ archivePath,
3452
+ content
3453
+ });
3454
+ printStatus(`Archived .cursor/rules/${file}`);
3455
+ }
3456
+ }
3457
+ }
3458
+ return archived;
3459
+ }
3460
+ var SINGLE_FILE_CONFIGS;
3461
+ var init_ide_archive = __esm({
3462
+ "src/lib/ide-archive.ts"() {
3463
+ "use strict";
3464
+ init_fs_ops();
3465
+ init_ui();
3466
+ SINGLE_FILE_CONFIGS = [
3467
+ {
3468
+ source: ".cursorrules",
3469
+ archive: "flydocs/knowledge/archived/cursorrules.md"
3470
+ },
3471
+ {
3472
+ source: ".claude/CLAUDE.md",
3473
+ archive: "flydocs/knowledge/archived/claude-md.md"
3474
+ }
3475
+ ];
3476
+ }
3477
+ });
3478
+
3209
3479
  // src/lib/workspace.ts
3210
- import { readFile as readFile14, writeFile as writeFile12, readdir as readdir4, stat as stat2 } from "fs/promises";
3211
- import { join as join19, dirname as dirname3, relative, resolve as resolve3 } from "path";
3480
+ import { readFile as readFile15, writeFile as writeFile14, readdir as readdir5, stat as stat2 } from "fs/promises";
3481
+ import { join as join21, dirname as dirname4, relative, resolve as resolve3 } from "path";
3212
3482
  async function readWorkspaceFile(dir) {
3213
- const filePath = join19(dir, WORKSPACE_FILENAME);
3483
+ const filePath = join21(dir, WORKSPACE_FILENAME);
3214
3484
  if (!await pathExists(filePath)) return null;
3215
- const content = await readFile14(filePath, "utf-8");
3485
+ const content = await readFile15(filePath, "utf-8");
3216
3486
  const parsed = JSON.parse(content);
3217
3487
  validateWorkspaceFile(parsed);
3218
3488
  return parsed;
3219
3489
  }
3220
3490
  async function writeWorkspaceFile(dir, workspace) {
3221
- const filePath = join19(dir, WORKSPACE_FILENAME);
3491
+ const filePath = join21(dir, WORKSPACE_FILENAME);
3222
3492
  const content = JSON.stringify(workspace, null, 2) + "\n";
3223
- await writeFile12(filePath, content, "utf-8");
3493
+ await writeFile14(filePath, content, "utf-8");
3224
3494
  }
3225
3495
  async function detectSiblingRepos(parentDir) {
3226
3496
  const repos = {};
3227
- const entries = await readdir4(parentDir);
3497
+ const entries = await readdir5(parentDir);
3228
3498
  for (const entry of entries) {
3229
3499
  if (entry.startsWith(".")) continue;
3230
- const entryPath = join19(parentDir, entry);
3500
+ const entryPath = join21(parentDir, entry);
3231
3501
  const entryStat = await stat2(entryPath).catch(() => null);
3232
3502
  if (!entryStat?.isDirectory()) continue;
3233
- const hasGit = await pathExists(join19(entryPath, ".git"));
3503
+ const hasGit = await pathExists(join21(entryPath, ".git"));
3234
3504
  const hasFlydocs = await pathExists(
3235
- join19(entryPath, ".flydocs", "config.json")
3505
+ join21(entryPath, ".flydocs", "config.json")
3236
3506
  );
3237
3507
  if (hasGit || hasFlydocs) {
3238
3508
  repos[entry] = { path: `./${entry}` };
@@ -3247,10 +3517,10 @@ async function readSiblingDescriptors(workspaceDir, repos) {
3247
3517
  const descriptors = [];
3248
3518
  for (const [name, entry] of Object.entries(repos)) {
3249
3519
  const repoDir = resolve3(workspaceDir, entry.path);
3250
- const serviceJsonPath = join19(repoDir, "flydocs", "context", "service.json");
3520
+ const serviceJsonPath = join21(repoDir, "flydocs", "context", "service.json");
3251
3521
  if (!await pathExists(serviceJsonPath)) continue;
3252
3522
  try {
3253
- const content = await readFile14(serviceJsonPath, "utf-8");
3523
+ const content = await readFile15(serviceJsonPath, "utf-8");
3254
3524
  const descriptor = JSON.parse(content);
3255
3525
  descriptors.push({ name, path: entry.path, descriptor });
3256
3526
  } catch {
@@ -3353,23 +3623,217 @@ var init_workspace = __esm({
3353
3623
  }
3354
3624
  });
3355
3625
 
3626
+ // src/commands/scan.ts
3627
+ var scan_exports = {};
3628
+ __export(scan_exports, {
3629
+ default: () => scan_default,
3630
+ runScan: () => runScan
3631
+ });
3632
+ import { defineCommand as defineCommand3 } from "citty";
3633
+ import { spinner } from "@clack/prompts";
3634
+ import pc8 from "picocolors";
3635
+ import { join as join22 } from "path";
3636
+ import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
3637
+ async function resolveRepoName(targetDir, repoArg) {
3638
+ if (repoArg) return repoArg;
3639
+ try {
3640
+ const config = await readAnyConfig(targetDir);
3641
+ if (!isConfigV2(config) && config.sourceRepo) {
3642
+ return config.sourceRepo;
3643
+ }
3644
+ } catch {
3645
+ }
3646
+ try {
3647
+ const { execFile: execFile2 } = await import("child_process");
3648
+ const { promisify: promisify2 } = await import("util");
3649
+ const execFileAsync2 = promisify2(execFile2);
3650
+ const { stdout } = await execFileAsync2(
3651
+ "git",
3652
+ ["-C", targetDir, "remote", "get-url", "origin"],
3653
+ { timeout: 5e3 }
3654
+ );
3655
+ const url = stdout.trim();
3656
+ const match = url.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
3657
+ if (match) return match[1];
3658
+ } catch {
3659
+ }
3660
+ return null;
3661
+ }
3662
+ async function writeScanResults(targetDir, response) {
3663
+ const written = [];
3664
+ const contextDir = join22(targetDir, "flydocs", "context");
3665
+ await mkdir11(contextDir, { recursive: true });
3666
+ if (response.projectMd) {
3667
+ const projectMdPath = join22(contextDir, "project.md");
3668
+ await writeFile15(projectMdPath, response.projectMd, "utf-8");
3669
+ written.push("flydocs/context/project.md");
3670
+ }
3671
+ if (response.serviceJson) {
3672
+ const serviceJsonPath = join22(contextDir, "service.json");
3673
+ await writeFile15(
3674
+ serviceJsonPath,
3675
+ JSON.stringify(response.serviceJson, null, 2) + "\n",
3676
+ "utf-8"
3677
+ );
3678
+ written.push("flydocs/context/service.json");
3679
+ }
3680
+ return written;
3681
+ }
3682
+ async function runScan(apiKey, workspaceId, targetDir, repoName, regenerate) {
3683
+ const s = spinner();
3684
+ s.start("Running AI scan (this may take 5-20 seconds)...");
3685
+ try {
3686
+ const response = await triggerScan(apiKey, repoName, {
3687
+ workspaceId,
3688
+ regenerate
3689
+ });
3690
+ if (!response.success) {
3691
+ s.stop("Scan returned unsuccessful", 1);
3692
+ return { success: false, written: [] };
3693
+ }
3694
+ s.stop("Scan complete");
3695
+ const written = await writeScanResults(targetDir, response);
3696
+ return { success: true, written };
3697
+ } catch (err) {
3698
+ if (err instanceof RelayError) {
3699
+ if (err.status === 403) {
3700
+ s.stop("Permission denied", 1);
3701
+ printError(
3702
+ "Admin role required. Only workspace admins can trigger AI scans."
3703
+ );
3704
+ } else {
3705
+ s.stop("Scan failed", 1);
3706
+ printError(`Server error: ${err.message}`);
3707
+ }
3708
+ } else {
3709
+ s.stop("Scan failed", 1);
3710
+ printError("Could not reach server. Check your network and try again.");
3711
+ }
3712
+ return { success: false, written: [] };
3713
+ }
3714
+ }
3715
+ var scan_default;
3716
+ var init_scan = __esm({
3717
+ "src/commands/scan.ts"() {
3718
+ "use strict";
3719
+ init_ui();
3720
+ init_global_config();
3721
+ init_config();
3722
+ init_types();
3723
+ init_relay_client();
3724
+ scan_default = defineCommand3({
3725
+ meta: {
3726
+ name: "scan",
3727
+ description: "Trigger server-side AI scan to generate project context"
3728
+ },
3729
+ args: {
3730
+ force: {
3731
+ type: "boolean",
3732
+ description: "Regenerate context even if it already exists",
3733
+ default: false
3734
+ },
3735
+ path: {
3736
+ type: "string",
3737
+ description: "Path to project directory"
3738
+ },
3739
+ repo: {
3740
+ type: "string",
3741
+ description: "Override repo name (owner/repo format)"
3742
+ }
3743
+ },
3744
+ async run({ args }) {
3745
+ const targetDir = args.path ?? process.cwd();
3746
+ console.log();
3747
+ printInfo("FlyDocs AI Scan");
3748
+ console.log();
3749
+ const resolved = await resolveApiKey(void 0, targetDir);
3750
+ if (!resolved) {
3751
+ printError(
3752
+ "No API key found. Run `flydocs auth` or `flydocs init` first."
3753
+ );
3754
+ process.exit(1);
3755
+ }
3756
+ const apiKey = resolved.key;
3757
+ const cred = await readGlobalCredential();
3758
+ const workspaceId = cred?.workspaceId;
3759
+ if (!workspaceId) {
3760
+ printError(
3761
+ "No workspace ID found. Run `flydocs init` to set up your workspace."
3762
+ );
3763
+ process.exit(1);
3764
+ }
3765
+ printInfo("Checking permissions...");
3766
+ try {
3767
+ const configResponse = await fetchConfigV2(apiKey, { workspaceId });
3768
+ if (configResponse.identity?.role !== "admin") {
3769
+ printError(
3770
+ "Admin role required. Only workspace admins can trigger AI scans."
3771
+ );
3772
+ console.log(
3773
+ ` ${pc8.dim("Your role: " + (configResponse.identity?.role ?? "unknown"))}`
3774
+ );
3775
+ console.log(
3776
+ ` ${pc8.dim("Ask a workspace admin to run this command or promote your role.")}`
3777
+ );
3778
+ process.exit(1);
3779
+ }
3780
+ } catch (err) {
3781
+ if (err instanceof RelayError) {
3782
+ printError(`Could not verify permissions: ${err.message}`);
3783
+ } else {
3784
+ printError("Could not reach server. Check your network.");
3785
+ }
3786
+ process.exit(1);
3787
+ }
3788
+ const repoName = await resolveRepoName(targetDir, args.repo);
3789
+ if (!repoName) {
3790
+ printError(
3791
+ "Could not determine repo name. Use --repo owner/name to specify it."
3792
+ );
3793
+ process.exit(1);
3794
+ }
3795
+ printInfo(`Repo: ${pc8.bold(repoName)}`);
3796
+ const { success, written } = await runScan(
3797
+ apiKey,
3798
+ workspaceId,
3799
+ targetDir,
3800
+ repoName,
3801
+ args.force
3802
+ );
3803
+ if (success) {
3804
+ console.log();
3805
+ for (const file of written) {
3806
+ printStatus(`Wrote ${file}`);
3807
+ }
3808
+ if (written.length === 0) {
3809
+ printWarning("Scan completed but no context files were returned.");
3810
+ }
3811
+ console.log();
3812
+ } else {
3813
+ process.exit(1);
3814
+ }
3815
+ }
3816
+ });
3817
+ }
3818
+ });
3819
+
3356
3820
  // src/commands/init.ts
3357
3821
  var init_exports = {};
3358
3822
  __export(init_exports, {
3359
3823
  default: () => init_default
3360
3824
  });
3361
- import { defineCommand as defineCommand3 } from "citty";
3825
+ import { defineCommand as defineCommand4 } from "citty";
3362
3826
  import { text as text2, confirm as confirm3, select as select2, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
3363
- import pc8 from "picocolors";
3364
- import { join as join20 } from "path";
3365
- import { mkdir as mkdir9, writeFile as writeFile13 } from "fs/promises";
3827
+ import pc9 from "picocolors";
3828
+ import { join as join23 } from "path";
3829
+ import { mkdir as mkdir12, writeFile as writeFile16 } from "fs/promises";
3366
3830
  import { execFile } from "child_process";
3367
3831
  import { promisify } from "util";
3368
3832
  async function resolveAndValidateKey(keyArg, targetDir) {
3369
3833
  let resolved = await resolveApiKey(keyArg, targetDir);
3370
3834
  if (!resolved) {
3371
3835
  console.log(
3372
- ` ${pc8.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3836
+ ` ${pc9.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
3373
3837
  );
3374
3838
  console.log();
3375
3839
  const keyInput = await text2({
@@ -3399,13 +3863,13 @@ async function resolveAndValidateKey(keyArg, targetDir) {
3399
3863
  printError("Invalid API key. Check your key and try again.");
3400
3864
  process.exit(1);
3401
3865
  }
3402
- printStatus(`Authenticated with ${pc8.bold(result.org)}`);
3866
+ printStatus(`Authenticated with ${pc9.bold(result.org)}`);
3403
3867
  } catch {
3404
3868
  printError(
3405
3869
  "Could not reach FlyDocs API. Check your network and try again."
3406
3870
  );
3407
3871
  console.log(
3408
- ` ${pc8.dim("flydocs init requires server access. For offline use, run flydocs install instead.")}`
3872
+ ` ${pc9.dim("flydocs init requires server access. For offline use, run flydocs install instead.")}`
3409
3873
  );
3410
3874
  process.exit(1);
3411
3875
  }
@@ -3416,7 +3880,7 @@ async function resolveAndValidateKey(keyArg, targetDir) {
3416
3880
  process.exit(1);
3417
3881
  } else if (workspaces.length === 1) {
3418
3882
  workspaceId = workspaces[0].id;
3419
- printStatus(`Workspace: ${pc8.bold(workspaces[0].name)}`);
3883
+ printStatus(`Workspace: ${pc9.bold(workspaces[0].name)}`);
3420
3884
  } else {
3421
3885
  const choice = await select2({
3422
3886
  message: "Select workspace",
@@ -3442,7 +3906,7 @@ async function resolveAndValidateKey(keyArg, targetDir) {
3442
3906
  }
3443
3907
  async function pullServerConfig(apiKey, targetDir, workspaceId, forceIncludeContext = false) {
3444
3908
  printInfo("Pulling config from server...");
3445
- const isFirstInit = forceIncludeContext || !await pathExists(join20(targetDir, ".flydocs", "config.json"));
3909
+ const isFirstInit = forceIncludeContext || !await pathExists(join23(targetDir, ".flydocs", "config.json"));
3446
3910
  try {
3447
3911
  const response = await fetchConfigV2(apiKey, {
3448
3912
  includeContext: isFirstInit,
@@ -3458,7 +3922,7 @@ async function pullServerConfig(apiKey, targetDir, workspaceId, forceIncludeCont
3458
3922
  printError(`Server error: ${err.message}`);
3459
3923
  if (err.status === 401) {
3460
3924
  console.log(
3461
- ` ${pc8.dim("Your API key may be revoked. Run flydocs auth with a new key.")}`
3925
+ ` ${pc9.dim("Your API key may be revoked. Run flydocs auth with a new key.")}`
3462
3926
  );
3463
3927
  }
3464
3928
  } else {
@@ -3467,10 +3931,11 @@ async function pullServerConfig(apiKey, targetDir, workspaceId, forceIncludeCont
3467
3931
  process.exit(1);
3468
3932
  }
3469
3933
  }
3470
- async function initSingleRepo(targetDir, apiKey, serverResponse) {
3934
+ async function initSingleRepo(targetDir, apiKey, serverResponse, options = {}) {
3471
3935
  const actions = [];
3472
3936
  const skipped = [];
3473
- await mkdir9(join20(targetDir, ".flydocs"), { recursive: true });
3937
+ const { childRepoMode = false } = options;
3938
+ await mkdir12(join23(targetDir, ".flydocs"), { recursive: true });
3474
3939
  const configWithHash = applyConfigHash(serverResponse.config);
3475
3940
  await writeConfig(targetDir, configWithHash);
3476
3941
  actions.push("Wrote .flydocs/config.json (from server)");
@@ -3483,18 +3948,18 @@ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3483
3948
  actions.push("Wrote ~/.flydocs/me.json");
3484
3949
  }
3485
3950
  if (serverResponse.context) {
3486
- const contextDir = join20(targetDir, "flydocs", "context");
3487
- await mkdir9(contextDir, { recursive: true });
3488
- const projectMdPath = join20(contextDir, "project.md");
3951
+ const contextDir = join23(targetDir, "flydocs", "context");
3952
+ await mkdir12(contextDir, { recursive: true });
3953
+ const projectMdPath = join23(contextDir, "project.md");
3489
3954
  if (!await pathExists(projectMdPath)) {
3490
- await writeFile13(projectMdPath, serverResponse.context.projectMd, "utf-8");
3955
+ await writeFile16(projectMdPath, serverResponse.context.projectMd, "utf-8");
3491
3956
  actions.push("Wrote flydocs/context/project.md");
3492
3957
  } else {
3493
3958
  skipped.push("flydocs/context/project.md (already exists)");
3494
3959
  }
3495
- const serviceJsonPath = join20(contextDir, "service.json");
3960
+ const serviceJsonPath = join23(contextDir, "service.json");
3496
3961
  if (serverResponse.context.serviceJson && !await pathExists(serviceJsonPath)) {
3497
- await writeFile13(
3962
+ await writeFile16(
3498
3963
  serviceJsonPath,
3499
3964
  JSON.stringify(serverResponse.context.serviceJson, null, 2) + "\n",
3500
3965
  "utf-8"
@@ -3504,6 +3969,27 @@ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3504
3969
  skipped.push("flydocs/context/service.json (already exists)");
3505
3970
  }
3506
3971
  }
3972
+ if (childRepoMode) {
3973
+ const cleaned = await cleanServerManagedFiles(targetDir);
3974
+ if (cleaned > 0) {
3975
+ actions.push(
3976
+ `Removed ${cleaned} server-managed file(s) (moved to workspace root)`
3977
+ );
3978
+ }
3979
+ } else {
3980
+ const archivedConfigs = await archiveIdeConfigs(targetDir);
3981
+ if (archivedConfigs.length > 0) {
3982
+ actions.push(`Archived ${archivedConfigs.length} existing IDE config(s)`);
3983
+ }
3984
+ const { changes: artifactChanges } = await syncAllArtifacts(
3985
+ targetDir,
3986
+ apiKey,
3987
+ serverResponse.workspaceId,
3988
+ 0
3989
+ // fresh init — fetch all artifacts
3990
+ );
3991
+ actions.push(...artifactChanges);
3992
+ }
3507
3993
  await ensureGitignore(targetDir);
3508
3994
  await ensurePlatformIgnores(targetDir);
3509
3995
  actions.push("Updated ignore files");
@@ -3512,10 +3998,32 @@ async function initSingleRepo(targetDir, apiKey, serverResponse) {
3512
3998
  await executeCleanup(targetDir, artifacts);
3513
3999
  actions.push(`Cleaned ${artifacts.length} legacy artifact(s)`);
3514
4000
  }
3515
- return { actions, skipped };
4001
+ return {
4002
+ actions,
4003
+ skipped
4004
+ };
4005
+ }
4006
+ async function cleanServerManagedFiles(targetDir) {
4007
+ const { rm: rm7 } = await import("fs/promises");
4008
+ let cleaned = 0;
4009
+ for (const dir of SERVER_MANAGED_DIRS) {
4010
+ const fullPath = join23(targetDir, dir);
4011
+ if (await pathExists(fullPath)) {
4012
+ await rm7(fullPath, { recursive: true, force: true });
4013
+ cleaned++;
4014
+ }
4015
+ }
4016
+ for (const file of SERVER_MANAGED_FILES) {
4017
+ const fullPath = join23(targetDir, file);
4018
+ if (await pathExists(fullPath)) {
4019
+ await rm7(fullPath, { force: true });
4020
+ cleaned++;
4021
+ }
4022
+ }
4023
+ return cleaned;
3516
4024
  }
3517
4025
  async function checkGitFreshness(repoDir) {
3518
- const hasGit = await pathExists(join20(repoDir, ".git"));
4026
+ const hasGit = await pathExists(join23(repoDir, ".git"));
3519
4027
  if (!hasGit) return;
3520
4028
  try {
3521
4029
  await execFileAsync("git", ["-C", repoDir, "fetch", "--quiet"], {
@@ -3544,7 +4052,7 @@ async function checkGitFreshness(repoDir) {
3544
4052
  }
3545
4053
  }
3546
4054
  async function isEmptyOrNonRepo(dir) {
3547
- const hasGit = await pathExists(join20(dir, ".git"));
4055
+ const hasGit = await pathExists(join23(dir, ".git"));
3548
4056
  if (hasGit) return false;
3549
4057
  const siblings = await detectSiblingRepos(dir);
3550
4058
  if (Object.keys(siblings).length >= 2) return false;
@@ -3557,29 +4065,29 @@ async function runCloneAndInit(targetDir, keyArg, repos, apiKey, workspaceId) {
3557
4065
  if (repos.length === 1) {
3558
4066
  const repo = repos[0];
3559
4067
  const shortName = repoShortName(repo.name);
3560
- printInfo(`Workspace has 1 repo: ${pc8.bold(shortName)}`);
4068
+ printInfo(`Workspace has 1 repo: ${pc9.bold(shortName)}`);
3561
4069
  const shouldClone = await confirm3({
3562
- message: `Clone ${shortName} and initialize?`
4070
+ message: `Clone ${shortName} into this directory and initialize?`
3563
4071
  });
3564
4072
  if (isCancel4(shouldClone) || !shouldClone) {
3565
4073
  cancel3("Init cancelled.");
3566
4074
  process.exit(0);
3567
4075
  }
3568
- const cloneDir = join20(targetDir, shortName);
3569
4076
  printInfo(`Cloning ${shortName}...`);
3570
- await execFileAsync("git", ["clone", repo.cloneUrl, cloneDir], {
4077
+ await execFileAsync("git", ["clone", repo.cloneUrl, "."], {
4078
+ cwd: targetDir,
3571
4079
  timeout: 6e4
3572
4080
  });
3573
4081
  printStatus(`Cloned ${shortName}`);
3574
4082
  const serverResponse = await pullServerConfig(
3575
4083
  apiKey,
3576
- cloneDir,
4084
+ targetDir,
3577
4085
  workspaceId,
3578
4086
  true
3579
4087
  // always include context on fresh clone
3580
4088
  );
3581
4089
  const { actions, skipped } = await initSingleRepo(
3582
- cloneDir,
4090
+ targetDir,
3583
4091
  apiKey,
3584
4092
  serverResponse
3585
4093
  );
@@ -3595,7 +4103,7 @@ async function runCloneAndInit(targetDir, keyArg, repos, apiKey, workspaceId) {
3595
4103
  const repoNames = repos.map((r) => repoShortName(r.name));
3596
4104
  printInfo(`Workspace has ${repos.length} repos:`);
3597
4105
  for (const name of repoNames) {
3598
- console.log(` ${pc8.cyan(name)}`);
4106
+ console.log(` ${pc9.cyan(name)}`);
3599
4107
  }
3600
4108
  console.log();
3601
4109
  const shouldClone = await confirm3({
@@ -3614,13 +4122,13 @@ async function runCloneAndInit(targetDir, keyArg, repos, apiKey, workspaceId) {
3614
4122
  for (let i = 0; i < repos.length; i++) {
3615
4123
  const repo = repos[i];
3616
4124
  const shortName = repoNames[i];
3617
- const cloneDir = join20(targetDir, shortName);
4125
+ const cloneDir = join23(targetDir, shortName);
3618
4126
  console.log();
3619
- if (await pathExists(join20(cloneDir, ".git"))) {
3620
- printInfo(`${pc8.bold(shortName)} already cloned, initializing...`);
4127
+ if (await pathExists(join23(cloneDir, ".git"))) {
4128
+ printInfo(`${pc9.bold(shortName)} already cloned, initializing...`);
3621
4129
  await checkGitFreshness(cloneDir);
3622
4130
  } else {
3623
- printInfo(`Cloning ${pc8.bold(shortName)}...`);
4131
+ printInfo(`Cloning ${pc9.bold(shortName)}...`);
3624
4132
  await execFileAsync("git", ["clone", repo.cloneUrl, cloneDir], {
3625
4133
  timeout: 6e4
3626
4134
  });
@@ -3659,12 +4167,12 @@ async function runCloneAndInit(targetDir, keyArg, repos, apiKey, workspaceId) {
3659
4167
  );
3660
4168
  await writeWorkspaceFile(targetDir, workspaceFile);
3661
4169
  allActions.push(`.flydocs-workspace.json (${repos.length} repos)`);
3662
- const contextDir = join20(targetDir, "flydocs", "context");
3663
- const workspaceMdPath = join20(contextDir, "workspace.md");
4170
+ const contextDir = join23(targetDir, "flydocs", "context");
4171
+ const workspaceMdPath = join23(contextDir, "workspace.md");
3664
4172
  if (!await pathExists(workspaceMdPath)) {
3665
- await mkdir9(contextDir, { recursive: true });
4173
+ await mkdir12(contextDir, { recursive: true });
3666
4174
  const content = firstResponse.context?.workspaceMd ?? await generateWorkspaceMd(targetDir, workspaceFile);
3667
- await writeFile13(workspaceMdPath, content, "utf-8");
4175
+ await writeFile16(workspaceMdPath, content, "utf-8");
3668
4176
  const source = firstResponse.context?.workspaceMd ? "from server" : "generated locally";
3669
4177
  allActions.push(`Wrote flydocs/context/workspace.md (${source})`);
3670
4178
  }
@@ -3679,7 +4187,7 @@ async function runCloneAndInit(targetDir, keyArg, repos, apiKey, workspaceId) {
3679
4187
  }
3680
4188
  }
3681
4189
  async function isParentDirectory(dir) {
3682
- const hasGit = await pathExists(join20(dir, ".git"));
4190
+ const hasGit = await pathExists(join23(dir, ".git"));
3683
4191
  if (hasGit) return false;
3684
4192
  const repos = await detectSiblingRepos(dir);
3685
4193
  return Object.keys(repos).length >= 2;
@@ -3689,7 +4197,7 @@ async function runMultiRepoInit(parentDir, keyArg) {
3689
4197
  const repoNames = Object.keys(repos).sort();
3690
4198
  printInfo(`Detected ${repoNames.length} repos:`);
3691
4199
  for (const name of repoNames) {
3692
- console.log(` ${pc8.cyan(name)}`);
4200
+ console.log(` ${pc9.cyan(name)}`);
3693
4201
  }
3694
4202
  console.log();
3695
4203
  const shouldContinue = await confirm3({
@@ -3699,7 +4207,7 @@ async function runMultiRepoInit(parentDir, keyArg) {
3699
4207
  cancel3("Init cancelled.");
3700
4208
  process.exit(0);
3701
4209
  }
3702
- const firstRepoDir = join20(parentDir, repoNames[0]);
4210
+ const firstRepoDir = join23(parentDir, repoNames[0]);
3703
4211
  const { apiKey, workspaceId } = await resolveAndValidateKey(
3704
4212
  keyArg,
3705
4213
  firstRepoDir
@@ -3710,17 +4218,38 @@ async function runMultiRepoInit(parentDir, keyArg) {
3710
4218
  const allSkipped = [];
3711
4219
  let allWarnings = [];
3712
4220
  let firstResponse;
4221
+ const workspaceResponse = await pullServerConfig(
4222
+ apiKey,
4223
+ firstRepoDir,
4224
+ workspaceId
4225
+ );
4226
+ firstResponse = workspaceResponse;
4227
+ const archivedConfigs = await archiveIdeConfigs(parentDir);
4228
+ if (archivedConfigs.length > 0) {
4229
+ allActions.push(
4230
+ `Archived ${archivedConfigs.length} existing IDE config(s) at workspace root`
4231
+ );
4232
+ }
4233
+ const { changes: artifactChanges } = await syncAllArtifacts(
4234
+ parentDir,
4235
+ apiKey,
4236
+ workspaceId,
4237
+ 0
4238
+ );
4239
+ allActions.push(...artifactChanges);
4240
+ await ensureGitignore(parentDir);
4241
+ allActions.push("Updated workspace root .gitignore");
3713
4242
  for (const repoName of repoNames) {
3714
- const repoDir = join20(parentDir, repoName);
4243
+ const repoDir = join23(parentDir, repoName);
3715
4244
  console.log();
3716
- printInfo(`Initializing ${pc8.bold(repoName)}...`);
4245
+ printInfo(`Initializing ${pc9.bold(repoName)}...`);
3717
4246
  await checkGitFreshness(repoDir);
3718
4247
  const serverResponse = await pullServerConfig(apiKey, repoDir, workspaceId);
3719
- if (!firstResponse) firstResponse = serverResponse;
3720
4248
  const { actions, skipped } = await initSingleRepo(
3721
4249
  repoDir,
3722
4250
  apiKey,
3723
- serverResponse
4251
+ serverResponse,
4252
+ { childRepoMode: true }
3724
4253
  );
3725
4254
  for (const action of actions) {
3726
4255
  allActions.push(`[${repoName}] ${action}`);
@@ -3743,15 +4272,15 @@ async function runMultiRepoInit(parentDir, keyArg) {
3743
4272
  await writeWorkspaceFile(parentDir, workspaceFile);
3744
4273
  allActions.push(`.flydocs-workspace.json (${repoNames.length} repos)`);
3745
4274
  }
3746
- const contextDir = join20(parentDir, "flydocs", "context");
3747
- const workspaceMdPath = join20(contextDir, "workspace.md");
4275
+ const contextDir = join23(parentDir, "flydocs", "context");
4276
+ const workspaceMdPath = join23(contextDir, "workspace.md");
3748
4277
  if (await pathExists(workspaceMdPath)) {
3749
4278
  allSkipped.push("flydocs/context/workspace.md (already exists)");
3750
4279
  } else {
3751
- await mkdir9(contextDir, { recursive: true });
4280
+ await mkdir12(contextDir, { recursive: true });
3752
4281
  const workspaceJson = await readWorkspaceFile(parentDir) ?? buildWorkspaceFile(firstResponse.workspaceId, repos);
3753
4282
  const content = firstResponse.context?.workspaceMd ?? await generateWorkspaceMd(parentDir, workspaceJson);
3754
- await writeFile13(workspaceMdPath, content, "utf-8");
4283
+ await writeFile16(workspaceMdPath, content, "utf-8");
3755
4284
  const source = firstResponse.context?.workspaceMd ? "from server" : "generated locally";
3756
4285
  allActions.push(`Wrote flydocs/context/workspace.md (${source})`);
3757
4286
  }
@@ -3768,35 +4297,35 @@ function printInitReport(actions, skipped, warnings, hasContext) {
3768
4297
  console.log();
3769
4298
  const lines = [];
3770
4299
  for (const action of actions) {
3771
- lines.push(`${pc8.green("+")} ${action}`);
4300
+ lines.push(`${pc9.green("+")} ${action}`);
3772
4301
  }
3773
4302
  for (const skip of skipped) {
3774
- lines.push(`${pc8.dim("-")} ${skip}`);
4303
+ lines.push(`${pc9.dim("-")} ${skip}`);
3775
4304
  }
3776
4305
  if (!hasContext) {
3777
4306
  lines.push("");
3778
4307
  lines.push(
3779
- `${pc8.yellow("!")} No generated context yet. An admin can generate it from the portal.`
4308
+ `${pc9.yellow("!")} No generated context yet. An admin can generate it from the portal.`
3780
4309
  );
3781
4310
  }
3782
4311
  if (warnings.length > 0) {
3783
4312
  lines.push("");
3784
4313
  for (const warning of warnings) {
3785
- lines.push(`${pc8.yellow("!")} ${warning}`);
4314
+ lines.push(`${pc9.yellow("!")} ${warning}`);
3786
4315
  }
3787
4316
  }
3788
4317
  printCompletionBox("FlyDocs Initialized", lines);
3789
4318
  console.log();
3790
4319
  console.log(` Next steps:`);
3791
4320
  console.log(
3792
- ` ${pc8.cyan("flydocs sync")} Pull latest config and templates`
4321
+ ` ${pc9.cyan("flydocs sync")} Pull latest config and templates`
3793
4322
  );
3794
4323
  console.log(
3795
- ` ${pc8.cyan("cursor .")} ${pc8.dim("or")} ${pc8.cyan("code .")} Open your IDE, then use ${pc8.bold("/start-session")}`
4324
+ ` ${pc9.cyan("cursor .")} ${pc9.dim("or")} ${pc9.cyan("code .")} Open your IDE, then use ${pc9.bold("/start-session")}`
3796
4325
  );
3797
4326
  console.log();
3798
4327
  }
3799
- var execFileAsync, init_default;
4328
+ var SERVER_MANAGED_DIRS, SERVER_MANAGED_FILES, execFileAsync, init_default;
3800
4329
  var init_init = __esm({
3801
4330
  "src/commands/init.ts"() {
3802
4331
  "use strict";
@@ -3808,10 +4337,26 @@ var init_init = __esm({
3808
4337
  init_gitignore();
3809
4338
  init_fs_ops();
3810
4339
  init_relay_client();
4340
+ init_artifacts();
3811
4341
  init_cleanup();
4342
+ init_ide_archive();
3812
4343
  init_workspace();
4344
+ init_scan();
4345
+ SERVER_MANAGED_DIRS = [
4346
+ ".claude/skills",
4347
+ ".claude/commands",
4348
+ ".claude/hooks",
4349
+ ".claude/agents",
4350
+ ".cursor/rules",
4351
+ ".cursor/commands"
4352
+ ];
4353
+ SERVER_MANAGED_FILES = [
4354
+ ".claude/settings.json",
4355
+ ".claude/CLAUDE.md",
4356
+ "AGENTS.md"
4357
+ ];
3813
4358
  execFileAsync = promisify(execFile);
3814
- init_default = defineCommand3({
4359
+ init_default = defineCommand4({
3815
4360
  meta: {
3816
4361
  name: "init",
3817
4362
  description: "Initialize FlyDocs in this project (unified setup)"
@@ -3824,12 +4369,17 @@ var init_init = __esm({
3824
4369
  path: {
3825
4370
  type: "string",
3826
4371
  description: "Path to project directory"
4372
+ },
4373
+ scan: {
4374
+ type: "boolean",
4375
+ description: "Run AI scan after init to generate project context",
4376
+ default: false
3827
4377
  }
3828
4378
  },
3829
4379
  async run({ args }) {
3830
4380
  const targetDir = args.path ?? process.cwd();
3831
4381
  console.log();
3832
- console.log(` ${pc8.bold("FlyDocs Init")}`);
4382
+ console.log(` ${pc9.bold("FlyDocs Init")}`);
3833
4383
  console.log();
3834
4384
  if (await isParentDirectory(targetDir)) {
3835
4385
  await runMultiRepoInit(targetDir, args.key);
@@ -3876,7 +4426,7 @@ var init_init = __esm({
3876
4426
  actions.unshift("Stored credential globally (~/.flydocs/credentials)");
3877
4427
  const topology = serverResponse.config.topology;
3878
4428
  if (topology?.type === 4 && topology.label === "sibling-repos") {
3879
- const parentDir = join20(targetDir, "..");
4429
+ const parentDir = join23(targetDir, "..");
3880
4430
  const existing = await readWorkspaceFile(parentDir);
3881
4431
  if (existing) {
3882
4432
  skipped.push(".flydocs-workspace.json (already exists)");
@@ -3900,6 +4450,98 @@ var init_init = __esm({
3900
4450
  serverResponse.warnings,
3901
4451
  !!serverResponse.context
3902
4452
  );
4453
+ const shouldAutoScan = !args.scan && serverResponse.contextVersion === 0 && !serverResponse.context?.projectMd;
4454
+ if (shouldAutoScan) {
4455
+ const isAdmin = serverResponse.identity?.role === "admin";
4456
+ if (isAdmin) {
4457
+ console.log();
4458
+ printInfo(
4459
+ "No generated context found. AI scanning creates project.md and service.json."
4460
+ );
4461
+ const shouldScan = await confirm3({
4462
+ message: "Run AI scan now?"
4463
+ });
4464
+ if (!isCancel4(shouldScan) && shouldScan) {
4465
+ let repoName = null;
4466
+ try {
4467
+ const { stdout } = await execFileAsync(
4468
+ "git",
4469
+ ["-C", targetDir, "remote", "get-url", "origin"],
4470
+ { timeout: 5e3 }
4471
+ );
4472
+ const url = stdout.trim();
4473
+ const match = url.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
4474
+ if (match) repoName = match[1];
4475
+ } catch {
4476
+ }
4477
+ if (repoName) {
4478
+ const { success, written } = await runScan(
4479
+ apiKey,
4480
+ workspaceId,
4481
+ targetDir,
4482
+ repoName,
4483
+ false
4484
+ );
4485
+ if (success) {
4486
+ for (const file of written) {
4487
+ printStatus(`Wrote ${file}`);
4488
+ }
4489
+ }
4490
+ } else {
4491
+ printWarning(
4492
+ "Could not determine repo name from git remote. Run `flydocs scan --repo owner/name` manually."
4493
+ );
4494
+ }
4495
+ }
4496
+ } else {
4497
+ console.log();
4498
+ printInfo(
4499
+ "No generated context yet. Ask a workspace admin to generate it from the portal, then run `flydocs sync`."
4500
+ );
4501
+ }
4502
+ }
4503
+ if (args.scan) {
4504
+ const isAdmin = serverResponse.identity?.role === "admin";
4505
+ if (!isAdmin) {
4506
+ printWarning(
4507
+ "Skipping scan: admin role required. Only workspace admins can trigger AI scans."
4508
+ );
4509
+ } else {
4510
+ let repoName = null;
4511
+ try {
4512
+ const { stdout } = await execFileAsync(
4513
+ "git",
4514
+ ["-C", targetDir, "remote", "get-url", "origin"],
4515
+ { timeout: 5e3 }
4516
+ );
4517
+ const url = stdout.trim();
4518
+ const match = url.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
4519
+ if (match) repoName = match[1];
4520
+ } catch {
4521
+ }
4522
+ if (!repoName) {
4523
+ printWarning(
4524
+ "Skipping scan: could not determine repo name from git remote."
4525
+ );
4526
+ } else {
4527
+ console.log();
4528
+ printInfo(`Running AI scan for ${pc9.bold(repoName)}...`);
4529
+ const { success, written } = await runScan(
4530
+ apiKey,
4531
+ workspaceId,
4532
+ targetDir,
4533
+ repoName,
4534
+ false
4535
+ // not a forced regeneration on init
4536
+ );
4537
+ if (success) {
4538
+ for (const file of written) {
4539
+ printStatus(`Wrote ${file}`);
4540
+ }
4541
+ }
4542
+ }
4543
+ }
4544
+ }
3903
4545
  }
3904
4546
  });
3905
4547
  }
@@ -3910,11 +4552,11 @@ var update_exports = {};
3910
4552
  __export(update_exports, {
3911
4553
  default: () => update_default
3912
4554
  });
3913
- import { defineCommand as defineCommand4 } from "citty";
3914
- import { resolve as resolve4, join as join21 } from "path";
3915
- import { mkdir as mkdir10, cp as cp2, readFile as readFile15, readdir as readdir5, rm as rm5 } from "fs/promises";
4555
+ import { defineCommand as defineCommand5 } from "citty";
4556
+ import { resolve as resolve4, join as join24 } from "path";
4557
+ import { mkdir as mkdir13, cp as cp2, readFile as readFile16, readdir as readdir6, rm as rm5 } from "fs/promises";
3916
4558
  import { select as select3, text as text3, confirm as confirm4, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
3917
- import pc9 from "picocolors";
4559
+ import pc10 from "picocolors";
3918
4560
  var update_default;
3919
4561
  var init_update = __esm({
3920
4562
  "src/commands/update.ts"() {
@@ -3933,7 +4575,7 @@ var init_update = __esm({
3933
4575
  init_update_check();
3934
4576
  init_telemetry();
3935
4577
  init_integrity();
3936
- update_default = defineCommand4({
4578
+ update_default = defineCommand5({
3937
4579
  meta: {
3938
4580
  name: "update",
3939
4581
  description: "Update an existing FlyDocs installation"
@@ -4020,9 +4662,9 @@ var init_update = __esm({
4020
4662
  }
4021
4663
  targetDir = resolve4(targetDir);
4022
4664
  process.chdir(targetDir);
4023
- const hasVersion = await pathExists(join21(targetDir, ".flydocs", "version"));
4665
+ const hasVersion = await pathExists(join24(targetDir, ".flydocs", "version"));
4024
4666
  const hasConfig = await pathExists(
4025
- join21(targetDir, ".flydocs", "config.json")
4667
+ join24(targetDir, ".flydocs", "config.json")
4026
4668
  );
4027
4669
  if (!hasVersion && !hasConfig) {
4028
4670
  printError(`Not a FlyDocs project: ${targetDir}`);
@@ -4033,8 +4675,8 @@ var init_update = __esm({
4033
4675
  console.log();
4034
4676
  let currentVersion = "0.1.0";
4035
4677
  if (hasVersion) {
4036
- const vContent = await readFile15(
4037
- join21(targetDir, ".flydocs", "version"),
4678
+ const vContent = await readFile16(
4679
+ join24(targetDir, ".flydocs", "version"),
4038
4680
  "utf-8"
4039
4681
  );
4040
4682
  currentVersion = vContent.trim();
@@ -4068,10 +4710,10 @@ var init_update = __esm({
4068
4710
  });
4069
4711
  console.log(`Updating: v${currentVersion} \u2192 v${version}`);
4070
4712
  console.log();
4071
- const changelogPath = join21(templateDir, "CHANGELOG.md");
4713
+ const changelogPath = join24(templateDir, "CHANGELOG.md");
4072
4714
  const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
4073
4715
  if (whatsNew.length > 0) {
4074
- console.log(pc9.cyan("What's new:"));
4716
+ console.log(pc10.cyan("What's new:"));
4075
4717
  console.log();
4076
4718
  for (const entry of whatsNew) {
4077
4719
  console.log(` ${entry}`);
@@ -4080,23 +4722,23 @@ var init_update = __esm({
4080
4722
  }
4081
4723
  const now = /* @__PURE__ */ new Date();
4082
4724
  const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
4083
- const backupDir = join21(targetDir, ".flydocs", `backup-${ts}`);
4084
- await mkdir10(backupDir, { recursive: true });
4725
+ const backupDir = join24(targetDir, ".flydocs", `backup-${ts}`);
4726
+ await mkdir13(backupDir, { recursive: true });
4085
4727
  if (hasConfig) {
4086
4728
  await cp2(
4087
- join21(targetDir, ".flydocs", "config.json"),
4088
- join21(backupDir, "config.json")
4729
+ join24(targetDir, ".flydocs", "config.json"),
4730
+ join24(backupDir, "config.json")
4089
4731
  );
4090
4732
  printStatus(`Config backed up to .flydocs/backup-${ts}/`);
4091
4733
  }
4092
4734
  try {
4093
- const flydocsDir = join21(targetDir, ".flydocs");
4094
- const entries = await readdir5(flydocsDir);
4735
+ const flydocsDir = join24(targetDir, ".flydocs");
4736
+ const entries = await readdir6(flydocsDir);
4095
4737
  const backups = entries.filter((e) => e.startsWith("backup-")).sort();
4096
4738
  if (backups.length > 3) {
4097
4739
  const toRemove = backups.slice(0, backups.length - 3);
4098
4740
  for (const old of toRemove) {
4099
- await rm5(join21(flydocsDir, old), { recursive: true, force: true });
4741
+ await rm5(join24(flydocsDir, old), { recursive: true, force: true });
4100
4742
  }
4101
4743
  }
4102
4744
  } catch {
@@ -4135,17 +4777,17 @@ var init_update = __esm({
4135
4777
  await ensureDirectories(targetDir, effectiveTier);
4136
4778
  console.log("Replacing framework directories...");
4137
4779
  await replaceDirectory(
4138
- join21(templateDir, ".flydocs", "templates"),
4139
- join21(targetDir, ".flydocs", "templates")
4780
+ join24(templateDir, ".flydocs", "templates"),
4781
+ join24(targetDir, ".flydocs", "templates")
4140
4782
  );
4141
4783
  printStatus(".flydocs/templates");
4142
4784
  await replaceDirectory(
4143
- join21(templateDir, ".claude", "hooks"),
4144
- join21(targetDir, ".claude", "hooks")
4785
+ join24(templateDir, ".claude", "hooks"),
4786
+ join24(targetDir, ".claude", "hooks")
4145
4787
  );
4146
4788
  printStatus(".claude/hooks");
4147
4789
  const hasExistingAgents = await pathExists(
4148
- join21(targetDir, ".claude", "agents")
4790
+ join24(targetDir, ".claude", "agents")
4149
4791
  );
4150
4792
  let installAgents;
4151
4793
  if (args.yes) {
@@ -4154,7 +4796,7 @@ var init_update = __esm({
4154
4796
  installAgents = true;
4155
4797
  } else {
4156
4798
  console.log();
4157
- console.log(` ${pc9.bold(pc9.yellow("Sub-Agents (Recommended)"))}`);
4799
+ console.log(` ${pc10.bold(pc10.yellow("Sub-Agents (Recommended)"))}`);
4158
4800
  console.log();
4159
4801
  console.log(
4160
4802
  " Sub-agents are specialized roles (PM, implementation, review,"
@@ -4177,20 +4819,20 @@ var init_update = __esm({
4177
4819
  }
4178
4820
  }
4179
4821
  if (installAgents) {
4180
- const claudeAgentsSrc = join21(templateDir, ".claude", "agents");
4822
+ const claudeAgentsSrc = join24(templateDir, ".claude", "agents");
4181
4823
  if (await pathExists(claudeAgentsSrc)) {
4182
- await mkdir10(join21(targetDir, ".claude", "agents"), { recursive: true });
4824
+ await mkdir13(join24(targetDir, ".claude", "agents"), { recursive: true });
4183
4825
  await copyDirectoryContents(
4184
4826
  claudeAgentsSrc,
4185
- join21(targetDir, ".claude", "agents")
4827
+ join24(targetDir, ".claude", "agents")
4186
4828
  );
4187
4829
  }
4188
- const cursorAgentsSrc = join21(templateDir, ".cursor", "agents");
4830
+ const cursorAgentsSrc = join24(templateDir, ".cursor", "agents");
4189
4831
  if (await pathExists(cursorAgentsSrc)) {
4190
- await mkdir10(join21(targetDir, ".cursor", "agents"), { recursive: true });
4832
+ await mkdir13(join24(targetDir, ".cursor", "agents"), { recursive: true });
4191
4833
  await copyDirectoryContents(
4192
4834
  cursorAgentsSrc,
4193
- join21(targetDir, ".cursor", "agents")
4835
+ join24(targetDir, ".cursor", "agents")
4194
4836
  );
4195
4837
  }
4196
4838
  printStatus(
@@ -4204,58 +4846,58 @@ var init_update = __esm({
4204
4846
  console.log();
4205
4847
  console.log("Replacing framework files...");
4206
4848
  await copyFile(
4207
- join21(templateDir, ".claude", "CLAUDE.md"),
4208
- join21(targetDir, ".claude", "CLAUDE.md")
4849
+ join24(templateDir, ".claude", "CLAUDE.md"),
4850
+ join24(targetDir, ".claude", "CLAUDE.md")
4209
4851
  );
4210
4852
  await copyFile(
4211
- join21(templateDir, ".claude", "settings.json"),
4212
- join21(targetDir, ".claude", "settings.json")
4853
+ join24(templateDir, ".claude", "settings.json"),
4854
+ join24(targetDir, ".claude", "settings.json")
4213
4855
  );
4214
4856
  printStatus(".claude/CLAUDE.md, settings.json");
4215
4857
  await copyDirectoryContents(
4216
- join21(templateDir, ".claude", "commands"),
4217
- join21(targetDir, ".claude", "commands")
4858
+ join24(templateDir, ".claude", "commands"),
4859
+ join24(targetDir, ".claude", "commands")
4218
4860
  );
4219
4861
  await copyDirectoryContents(
4220
- join21(templateDir, ".claude", "commands"),
4221
- join21(targetDir, ".cursor", "commands")
4862
+ join24(templateDir, ".claude", "commands"),
4863
+ join24(targetDir, ".cursor", "commands")
4222
4864
  );
4223
4865
  printStatus(".claude/commands, .cursor/commands");
4224
- const skillsReadmeSrc = join21(templateDir, ".claude", "skills", "README.md");
4866
+ const skillsReadmeSrc = join24(templateDir, ".claude", "skills", "README.md");
4225
4867
  if (await pathExists(skillsReadmeSrc)) {
4226
4868
  await copyFile(
4227
4869
  skillsReadmeSrc,
4228
- join21(targetDir, ".claude", "skills", "README.md")
4870
+ join24(targetDir, ".claude", "skills", "README.md")
4229
4871
  );
4230
4872
  }
4231
4873
  printStatus(".claude/skills/README.md");
4232
4874
  await copyFile(
4233
- join21(templateDir, ".cursor", "hooks.json"),
4234
- join21(targetDir, ".cursor", "hooks.json")
4875
+ join24(templateDir, ".cursor", "hooks.json"),
4876
+ join24(targetDir, ".cursor", "hooks.json")
4235
4877
  );
4236
4878
  printStatus(".cursor/hooks.json");
4237
4879
  await copyFile(
4238
- join21(templateDir, "AGENTS.md"),
4239
- join21(targetDir, "AGENTS.md")
4880
+ join24(templateDir, "AGENTS.md"),
4881
+ join24(targetDir, "AGENTS.md")
4240
4882
  );
4241
4883
  printStatus("AGENTS.md");
4242
- const envExampleSrc = join21(templateDir, ".env.example");
4884
+ const envExampleSrc = join24(templateDir, ".env.example");
4243
4885
  if (await pathExists(envExampleSrc)) {
4244
- await copyFile(envExampleSrc, join21(targetDir, ".env.example"));
4886
+ await copyFile(envExampleSrc, join24(targetDir, ".env.example"));
4245
4887
  printStatus(".env.example");
4246
4888
  }
4247
- const knowledgeTemplatesDir = join21(
4889
+ const knowledgeTemplatesDir = join24(
4248
4890
  targetDir,
4249
4891
  "flydocs",
4250
4892
  "knowledge",
4251
4893
  "templates"
4252
4894
  );
4253
4895
  if (!await pathExists(knowledgeTemplatesDir)) {
4254
- await mkdir10(knowledgeTemplatesDir, { recursive: true });
4896
+ await mkdir13(knowledgeTemplatesDir, { recursive: true });
4255
4897
  }
4256
4898
  for (const tmpl of ["decision.md", "feature.md", "note.md"]) {
4257
- const src = join21(templateDir, "flydocs", "knowledge", "templates", tmpl);
4258
- const dest = join21(knowledgeTemplatesDir, tmpl);
4899
+ const src = join24(templateDir, "flydocs", "knowledge", "templates", tmpl);
4900
+ const dest = join24(knowledgeTemplatesDir, tmpl);
4259
4901
  if (await pathExists(src) && !await pathExists(dest)) {
4260
4902
  await copyFile(src, dest);
4261
4903
  }
@@ -4282,18 +4924,18 @@ var init_update = __esm({
4282
4924
  printWarning("Config merge failed \u2014 config.json preserved as-is");
4283
4925
  }
4284
4926
  await copyFile(
4285
- join21(templateDir, ".flydocs", "version"),
4286
- join21(targetDir, ".flydocs", "version")
4927
+ join24(templateDir, ".flydocs", "version"),
4928
+ join24(targetDir, ".flydocs", "version")
4287
4929
  );
4288
4930
  printStatus(`.flydocs/version \u2192 ${version}`);
4289
- const clSrc = join21(templateDir, "CHANGELOG.md");
4931
+ const clSrc = join24(templateDir, "CHANGELOG.md");
4290
4932
  if (await pathExists(clSrc)) {
4291
- await copyFile(clSrc, join21(targetDir, ".flydocs", "CHANGELOG.md"));
4933
+ await copyFile(clSrc, join24(targetDir, ".flydocs", "CHANGELOG.md"));
4292
4934
  printStatus(".flydocs/CHANGELOG.md");
4293
4935
  }
4294
- const mfSrc = join21(templateDir, "manifest.json");
4936
+ const mfSrc = join24(templateDir, "manifest.json");
4295
4937
  if (await pathExists(mfSrc)) {
4296
- await copyFile(mfSrc, join21(targetDir, ".flydocs", "manifest.json"));
4938
+ await copyFile(mfSrc, join24(targetDir, ".flydocs", "manifest.json"));
4297
4939
  printStatus(".flydocs/manifest.json");
4298
4940
  }
4299
4941
  await generateIntegrity(targetDir, version);
@@ -4355,22 +4997,22 @@ var uninstall_exports = {};
4355
4997
  __export(uninstall_exports, {
4356
4998
  default: () => uninstall_default
4357
4999
  });
4358
- import { defineCommand as defineCommand5 } from "citty";
4359
- import { resolve as resolve5, join as join22 } from "path";
4360
- import { readdir as readdir6, rm as rm6, rename as rename2 } from "fs/promises";
5000
+ import { defineCommand as defineCommand6 } from "citty";
5001
+ import { resolve as resolve5, join as join25 } from "path";
5002
+ import { readdir as readdir7, rm as rm6, rename as rename2 } from "fs/promises";
4361
5003
  import { confirm as confirm5, select as select4, isCancel as isCancel6, cancel as cancel5 } from "@clack/prompts";
4362
- import pc10 from "picocolors";
5004
+ import pc11 from "picocolors";
4363
5005
  async function removeOwnedSkills(targetDir) {
4364
- const skillsDir = join22(targetDir, ".claude", "skills");
5006
+ const skillsDir = join25(targetDir, ".claude", "skills");
4365
5007
  const removed = [];
4366
5008
  if (!await pathExists(skillsDir)) {
4367
5009
  return removed;
4368
5010
  }
4369
5011
  try {
4370
- const entries = await readdir6(skillsDir);
5012
+ const entries = await readdir7(skillsDir);
4371
5013
  for (const entry of entries) {
4372
5014
  if (entry.startsWith(OWNED_SKILL_PREFIX)) {
4373
- await rm6(join22(skillsDir, entry), { recursive: true, force: true });
5015
+ await rm6(join25(skillsDir, entry), { recursive: true, force: true });
4374
5016
  removed.push(`.claude/skills/${entry}`);
4375
5017
  }
4376
5018
  }
@@ -4379,16 +5021,16 @@ async function removeOwnedSkills(targetDir) {
4379
5021
  return removed;
4380
5022
  }
4381
5023
  async function removeOwnedCursorRules(targetDir) {
4382
- const rulesDir = join22(targetDir, ".cursor", "rules");
5024
+ const rulesDir = join25(targetDir, ".cursor", "rules");
4383
5025
  const removed = [];
4384
5026
  if (!await pathExists(rulesDir)) {
4385
5027
  return removed;
4386
5028
  }
4387
5029
  try {
4388
- const entries = await readdir6(rulesDir);
5030
+ const entries = await readdir7(rulesDir);
4389
5031
  for (const entry of entries) {
4390
5032
  if (entry.startsWith(OWNED_RULE_PREFIX) && entry.endsWith(".mdc")) {
4391
- await rm6(join22(rulesDir, entry), { force: true });
5033
+ await rm6(join25(rulesDir, entry), { force: true });
4392
5034
  removed.push(`.cursor/rules/${entry}`);
4393
5035
  }
4394
5036
  }
@@ -4398,7 +5040,7 @@ async function removeOwnedCursorRules(targetDir) {
4398
5040
  }
4399
5041
  async function isEmptyDir(dirPath) {
4400
5042
  try {
4401
- const entries = await readdir6(dirPath);
5043
+ const entries = await readdir7(dirPath);
4402
5044
  return entries.length === 0;
4403
5045
  } catch {
4404
5046
  return false;
@@ -4407,7 +5049,7 @@ async function isEmptyDir(dirPath) {
4407
5049
  async function cleanupEmptyParents(targetDir, dirs) {
4408
5050
  const cleaned = [];
4409
5051
  for (const dir of dirs) {
4410
- const fullPath = join22(targetDir, dir);
5052
+ const fullPath = join25(targetDir, dir);
4411
5053
  if (await pathExists(fullPath) && await isEmptyDir(fullPath)) {
4412
5054
  await rm6(fullPath, { recursive: true, force: true });
4413
5055
  cleaned.push(dir);
@@ -4439,7 +5081,7 @@ var init_uninstall = __esm({
4439
5081
  ];
4440
5082
  OWNED_SKILL_PREFIX = "flydocs-";
4441
5083
  OWNED_RULE_PREFIX = "flydocs-";
4442
- uninstall_default = defineCommand5({
5084
+ uninstall_default = defineCommand6({
4443
5085
  meta: {
4444
5086
  name: "uninstall",
4445
5087
  description: "Remove FlyDocs from a project directory"
@@ -4486,8 +5128,8 @@ var init_uninstall = __esm({
4486
5128
  process.exit(1);
4487
5129
  }
4488
5130
  targetDir = resolve5(targetDir);
4489
- const hasFlydocs = await pathExists(join22(targetDir, ".flydocs"));
4490
- const hasAgentsMd = await pathExists(join22(targetDir, "AGENTS.md"));
5131
+ const hasFlydocs = await pathExists(join25(targetDir, ".flydocs"));
5132
+ const hasAgentsMd = await pathExists(join25(targetDir, "AGENTS.md"));
4491
5133
  if (!hasFlydocs && !hasAgentsMd) {
4492
5134
  printError(`Not a FlyDocs project: ${targetDir}`);
4493
5135
  printInfo("No .flydocs/ directory or AGENTS.md found.");
@@ -4499,7 +5141,7 @@ var init_uninstall = __esm({
4499
5141
  const removeAll = forceAll || args.all;
4500
5142
  const skipPrompts = forceAll || args.yes;
4501
5143
  let contentAction = "preserve";
4502
- const hasUserContent = await pathExists(join22(targetDir, "flydocs"));
5144
+ const hasUserContent = await pathExists(join25(targetDir, "flydocs"));
4503
5145
  if (hasUserContent) {
4504
5146
  if (removeAll) {
4505
5147
  contentAction = "remove";
@@ -4533,34 +5175,34 @@ var init_uninstall = __esm({
4533
5175
  }
4534
5176
  if (!skipPrompts) {
4535
5177
  console.log();
4536
- console.log(pc10.bold("The following will be removed:"));
5178
+ console.log(pc11.bold("The following will be removed:"));
4537
5179
  console.log();
4538
5180
  console.log(" Framework files:");
4539
5181
  for (const [path] of ALWAYS_REMOVED) {
4540
- console.log(` ${pc10.dim(path)}`);
5182
+ console.log(` ${pc11.dim(path)}`);
4541
5183
  }
4542
- console.log(` ${pc10.dim(".claude/skills/flydocs-*")}`);
4543
- console.log(` ${pc10.dim(".cursor/rules/flydocs-*.mdc")}`);
5184
+ console.log(` ${pc11.dim(".claude/skills/flydocs-*")}`);
5185
+ console.log(` ${pc11.dim(".cursor/rules/flydocs-*.mdc")}`);
4544
5186
  if (hasUserContent) {
4545
5187
  if (contentAction === "archive") {
4546
5188
  console.log();
4547
5189
  console.log(
4548
- ` User content: ${pc10.yellow("flydocs/ -> flydocs-archive/")}`
5190
+ ` User content: ${pc11.yellow("flydocs/ -> flydocs-archive/")}`
4549
5191
  );
4550
5192
  } else if (contentAction === "remove") {
4551
5193
  console.log();
4552
- console.log(` User content: ${pc10.red("flydocs/ (deleted)")}`);
5194
+ console.log(` User content: ${pc11.red("flydocs/ (deleted)")}`);
4553
5195
  } else {
4554
5196
  console.log();
4555
- console.log(` User content: ${pc10.green("flydocs/ (preserved)")}`);
5197
+ console.log(` User content: ${pc11.green("flydocs/ (preserved)")}`);
4556
5198
  }
4557
5199
  }
4558
5200
  console.log();
4559
- console.log(pc10.bold("Preserved:"));
5201
+ console.log(pc11.bold("Preserved:"));
4560
5202
  console.log(
4561
- ` ${pc10.dim(".claude/skills/ (non-flydocs community skills)")}`
5203
+ ` ${pc11.dim(".claude/skills/ (non-flydocs community skills)")}`
4562
5204
  );
4563
- console.log(` ${pc10.dim(".env, .env.local")}`);
5205
+ console.log(` ${pc11.dim(".env, .env.local")}`);
4564
5206
  console.log();
4565
5207
  const shouldContinue = await confirm5({
4566
5208
  message: "Proceed with uninstall?"
@@ -4582,7 +5224,7 @@ var init_uninstall = __esm({
4582
5224
  const removedRules = await removeOwnedCursorRules(targetDir);
4583
5225
  result.removed.push(...removedRules);
4584
5226
  for (const [relativePath, type] of ALWAYS_REMOVED) {
4585
- const fullPath = join22(targetDir, relativePath);
5227
+ const fullPath = join25(targetDir, relativePath);
4586
5228
  if (!await pathExists(fullPath)) {
4587
5229
  result.skipped.push(relativePath);
4588
5230
  continue;
@@ -4600,9 +5242,9 @@ var init_uninstall = __esm({
4600
5242
  }
4601
5243
  }
4602
5244
  if (hasUserContent) {
4603
- const flydocsPath = join22(targetDir, "flydocs");
5245
+ const flydocsPath = join25(targetDir, "flydocs");
4604
5246
  if (contentAction === "archive") {
4605
- const archivePath = join22(targetDir, "flydocs-archive");
5247
+ const archivePath = join25(targetDir, "flydocs-archive");
4606
5248
  if (await pathExists(archivePath)) {
4607
5249
  await rm6(archivePath, { recursive: true, force: true });
4608
5250
  }
@@ -4628,17 +5270,17 @@ var init_uninstall = __esm({
4628
5270
  result.restored = originals.map((f) => f.relativePath);
4629
5271
  }
4630
5272
  console.log();
4631
- console.log(pc10.bold("Uninstall Summary"));
5273
+ console.log(pc11.bold("Uninstall Summary"));
4632
5274
  console.log();
4633
5275
  if (result.removed.length > 0) {
4634
- console.log(` ${pc10.green("Removed")} (${result.removed.length}):`);
5276
+ console.log(` ${pc11.green("Removed")} (${result.removed.length}):`);
4635
5277
  for (const item of result.removed) {
4636
5278
  printStatus(item);
4637
5279
  }
4638
5280
  }
4639
5281
  if (result.archived.length > 0) {
4640
5282
  console.log();
4641
- console.log(` ${pc10.yellow("Archived")} (${result.archived.length}):`);
5283
+ console.log(` ${pc11.yellow("Archived")} (${result.archived.length}):`);
4642
5284
  for (const item of result.archived) {
4643
5285
  printInfo(item);
4644
5286
  }
@@ -4646,7 +5288,7 @@ var init_uninstall = __esm({
4646
5288
  if (result.restored.length > 0) {
4647
5289
  console.log();
4648
5290
  console.log(
4649
- ` ${pc10.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
5291
+ ` ${pc11.green("Restored")} (${result.restored.length} pre-FlyDocs original(s)):`
4650
5292
  );
4651
5293
  for (const item of result.restored) {
4652
5294
  printInfo(item);
@@ -4655,16 +5297,16 @@ var init_uninstall = __esm({
4655
5297
  if (result.skipped.length > 0) {
4656
5298
  console.log();
4657
5299
  console.log(
4658
- ` ${pc10.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
5300
+ ` ${pc11.dim("Skipped")} (${result.skipped.length} \u2014 not found):`
4659
5301
  );
4660
5302
  for (const item of result.skipped) {
4661
- console.log(` ${pc10.dim(item)}`);
5303
+ console.log(` ${pc11.dim(item)}`);
4662
5304
  }
4663
5305
  }
4664
5306
  console.log();
4665
5307
  printStatus("FlyDocs has been removed from this project.");
4666
5308
  console.log();
4667
- printInfo(`To reinstall: ${pc10.cyan("npx @flydocs/cli install --here")}`);
5309
+ printInfo(`To reinstall: ${pc11.cyan("npx @flydocs/cli install --here")}`);
4668
5310
  console.log();
4669
5311
  }
4670
5312
  });
@@ -4676,28 +5318,28 @@ var setup_exports = {};
4676
5318
  __export(setup_exports, {
4677
5319
  default: () => setup_default
4678
5320
  });
4679
- import { defineCommand as defineCommand6 } from "citty";
4680
- import pc11 from "picocolors";
5321
+ import { defineCommand as defineCommand7 } from "citty";
5322
+ import pc12 from "picocolors";
4681
5323
  var setup_default;
4682
5324
  var init_setup = __esm({
4683
5325
  "src/commands/setup.ts"() {
4684
5326
  "use strict";
4685
- setup_default = defineCommand6({
5327
+ setup_default = defineCommand7({
4686
5328
  meta: {
4687
5329
  name: "setup",
4688
5330
  description: "Configure FlyDocs settings for this project"
4689
5331
  },
4690
5332
  run() {
4691
5333
  console.log();
4692
- console.log(` ${pc11.bold("FlyDocs Setup")}`);
5334
+ console.log(` ${pc12.bold("FlyDocs Setup")}`);
4693
5335
  console.log();
4694
5336
  console.log(` Setup runs inside your IDE as an interactive AI command.`);
4695
5337
  console.log();
4696
5338
  console.log(
4697
- ` ${pc11.cyan("Claude Code:")} Type ${pc11.bold("/flydocs-setup")} in chat`
5339
+ ` ${pc12.cyan("Claude Code:")} Type ${pc12.bold("/flydocs-setup")} in chat`
4698
5340
  );
4699
5341
  console.log(
4700
- ` ${pc11.cyan("Cursor:")} Type ${pc11.bold("/flydocs-setup")} in chat`
5342
+ ` ${pc12.cyan("Cursor:")} Type ${pc12.bold("/flydocs-setup")} in chat`
4701
5343
  );
4702
5344
  console.log();
4703
5345
  console.log(` This configures your project context, detects your stack,`);
@@ -4713,14 +5355,14 @@ var skills_exports = {};
4713
5355
  __export(skills_exports, {
4714
5356
  default: () => skills_default
4715
5357
  });
4716
- import { defineCommand as defineCommand7 } from "citty";
4717
- import pc12 from "picocolors";
5358
+ import { defineCommand as defineCommand8 } from "citty";
5359
+ import pc13 from "picocolors";
4718
5360
  var list, search, add, remove, skills_default;
4719
5361
  var init_skills2 = __esm({
4720
5362
  "src/commands/skills.ts"() {
4721
5363
  "use strict";
4722
5364
  init_skill_manager();
4723
- list = defineCommand7({
5365
+ list = defineCommand8({
4724
5366
  meta: {
4725
5367
  name: "list",
4726
5368
  description: "List installed skills"
@@ -4736,26 +5378,26 @@ var init_skills2 = __esm({
4736
5378
  console.log(`${total} skill(s) installed:`);
4737
5379
  if (result.platform.length > 0) {
4738
5380
  console.log();
4739
- console.log(pc12.bold("Platform"));
5381
+ console.log(pc13.bold("Platform"));
4740
5382
  for (const skill of result.platform) {
4741
5383
  console.log(
4742
- ` ${skill.name} ${pc12.dim(`(${skill.triggers} triggers)`)}`
5384
+ ` ${skill.name} ${pc13.dim(`(${skill.triggers} triggers)`)}`
4743
5385
  );
4744
5386
  }
4745
5387
  }
4746
5388
  if (result.community.length > 0) {
4747
5389
  console.log();
4748
- console.log(pc12.bold("Community"));
5390
+ console.log(pc13.bold("Community"));
4749
5391
  for (const skill of result.community) {
4750
5392
  console.log(
4751
- ` ${skill.name} ${pc12.dim(`(${skill.triggers} triggers)`)}`
5393
+ ` ${skill.name} ${pc13.dim(`(${skill.triggers} triggers)`)}`
4752
5394
  );
4753
5395
  }
4754
5396
  }
4755
5397
  console.log();
4756
5398
  }
4757
5399
  });
4758
- search = defineCommand7({
5400
+ search = defineCommand8({
4759
5401
  meta: {
4760
5402
  name: "search",
4761
5403
  description: "Search community skills"
@@ -4771,24 +5413,24 @@ var init_skills2 = __esm({
4771
5413
  const results = await searchCatalog(args.keyword);
4772
5414
  if (results.length === 0) {
4773
5415
  console.log(`No skills found for "${args.keyword}".`);
4774
- console.log(` Browse the catalog at: ${pc12.cyan("https://skills.sh/")}`);
5416
+ console.log(` Browse the catalog at: ${pc13.cyan("https://skills.sh/")}`);
4775
5417
  return;
4776
5418
  }
4777
5419
  console.log();
4778
5420
  console.log(`${results.length} skill(s) matching "${args.keyword}":`);
4779
5421
  console.log();
4780
5422
  for (const skill of results) {
4781
- console.log(` ${pc12.bold(skill.name)}`);
5423
+ console.log(` ${pc13.bold(skill.name)}`);
4782
5424
  console.log(` ${skill.description}`);
4783
- console.log(` ${pc12.dim(skill.repo)}`);
5425
+ console.log(` ${pc13.dim(skill.repo)}`);
4784
5426
  if (skill.tags.length > 0) {
4785
- console.log(` ${pc12.dim(skill.tags.join(", "))}`);
5427
+ console.log(` ${pc13.dim(skill.tags.join(", "))}`);
4786
5428
  }
4787
5429
  console.log();
4788
5430
  }
4789
5431
  }
4790
5432
  });
4791
- add = defineCommand7({
5433
+ add = defineCommand8({
4792
5434
  meta: {
4793
5435
  name: "add",
4794
5436
  description: "Install a community skill"
@@ -4804,7 +5446,7 @@ var init_skills2 = __esm({
4804
5446
  await addSkill(process.cwd(), args.source);
4805
5447
  }
4806
5448
  });
4807
- remove = defineCommand7({
5449
+ remove = defineCommand8({
4808
5450
  meta: {
4809
5451
  name: "remove",
4810
5452
  description: "Remove an installed community skill"
@@ -4820,7 +5462,7 @@ var init_skills2 = __esm({
4820
5462
  await removeSkill(process.cwd(), args.name);
4821
5463
  }
4822
5464
  });
4823
- skills_default = defineCommand7({
5465
+ skills_default = defineCommand8({
4824
5466
  meta: {
4825
5467
  name: "skills",
4826
5468
  description: "Manage FlyDocs skills (list, search, add, remove)"
@@ -4840,10 +5482,10 @@ var connect_exports = {};
4840
5482
  __export(connect_exports, {
4841
5483
  default: () => connect_default
4842
5484
  });
4843
- import { defineCommand as defineCommand8 } from "citty";
5485
+ import { defineCommand as defineCommand9 } from "citty";
4844
5486
  import { text as text4, confirm as confirm6, isCancel as isCancel7, cancel as cancel6 } from "@clack/prompts";
4845
- import pc13 from "picocolors";
4846
- import { join as join23 } from "path";
5487
+ import pc14 from "picocolors";
5488
+ import { join as join26 } from "path";
4847
5489
  var connect_default;
4848
5490
  var init_connect = __esm({
4849
5491
  "src/commands/connect.ts"() {
@@ -4853,7 +5495,7 @@ var init_connect = __esm({
4853
5495
  init_template();
4854
5496
  init_ui();
4855
5497
  init_api_key();
4856
- connect_default = defineCommand8({
5498
+ connect_default = defineCommand9({
4857
5499
  meta: {
4858
5500
  name: "connect",
4859
5501
  description: "Connect FlyDocs to a cloud provider"
@@ -4878,11 +5520,11 @@ var init_connect = __esm({
4878
5520
  },
4879
5521
  async run({ args }) {
4880
5522
  const targetDir = args.path ?? process.cwd();
4881
- const configPath = join23(targetDir, ".flydocs", "config.json");
5523
+ const configPath = join26(targetDir, ".flydocs", "config.json");
4882
5524
  if (!await pathExists(configPath)) {
4883
5525
  printError("Not a FlyDocs project (.flydocs/config.json not found).");
4884
5526
  console.log(
4885
- ` Run ${pc13.cyan("flydocs")} first to install FlyDocs in this project.`
5527
+ ` Run ${pc14.cyan("flydocs")} first to install FlyDocs in this project.`
4886
5528
  );
4887
5529
  process.exit(1);
4888
5530
  }
@@ -4899,10 +5541,10 @@ var init_connect = __esm({
4899
5541
  }
4900
5542
  }
4901
5543
  console.log();
4902
- console.log(` ${pc13.bold("Connect to FlyDocs Cloud")}`);
5544
+ console.log(` ${pc14.bold("Connect to FlyDocs Cloud")}`);
4903
5545
  console.log();
4904
5546
  console.log(
4905
- ` ${pc13.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
5547
+ ` ${pc14.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
4906
5548
  );
4907
5549
  console.log();
4908
5550
  let apiKey = args.key ?? "";
@@ -4940,7 +5582,7 @@ var init_connect = __esm({
4940
5582
  console.log(` Check your key and try again.`);
4941
5583
  process.exit(1);
4942
5584
  }
4943
- printStatus(`Connected to ${pc13.bold(result.org)}`);
5585
+ printStatus(`Connected to ${pc14.bold(result.org)}`);
4944
5586
  } catch {
4945
5587
  printError(
4946
5588
  "Could not reach relay API. Check your network and try again."
@@ -4948,7 +5590,7 @@ var init_connect = __esm({
4948
5590
  process.exit(1);
4949
5591
  }
4950
5592
  const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
4951
- printStatus(`API key stored in ${pc13.dim(envFile)}`);
5593
+ printStatus(`API key stored in ${pc14.dim(envFile)}`);
4952
5594
  } else {
4953
5595
  try {
4954
5596
  const result = await validateLinearKey(apiKey);
@@ -4958,7 +5600,7 @@ var init_connect = __esm({
4958
5600
  process.exit(1);
4959
5601
  }
4960
5602
  printStatus(
4961
- `Authenticated as ${pc13.bold(result.name)} (${result.email})`
5603
+ `Authenticated as ${pc14.bold(result.name)} (${result.email})`
4962
5604
  );
4963
5605
  } catch {
4964
5606
  printError("Invalid API key or network error.");
@@ -4966,7 +5608,7 @@ var init_connect = __esm({
4966
5608
  process.exit(1);
4967
5609
  }
4968
5610
  const envFile = await storeEnvKey(targetDir, "LINEAR_API_KEY", apiKey);
4969
- printStatus(`API key stored in ${pc13.dim(envFile)}`);
5611
+ printStatus(`API key stored in ${pc14.dim(envFile)}`);
4970
5612
  }
4971
5613
  const wasLocal = config.tier === "local";
4972
5614
  config.tier = "cloud";
@@ -4982,14 +5624,14 @@ var init_connect = __esm({
4982
5624
  }
4983
5625
  console.log();
4984
5626
  console.log(
4985
- ` ${pc13.bold("Connected!")} Your project is now on the cloud tier.`
5627
+ ` ${pc14.bold("Connected!")} Your project is now on the cloud tier.`
4986
5628
  );
4987
5629
  console.log();
4988
5630
  console.log(` Next steps:`);
4989
5631
  console.log(
4990
- ` 1. Run ${pc13.cyan("/flydocs-setup")} in your IDE to configure your project`
5632
+ ` 1. Run ${pc14.cyan("/flydocs-setup")} in your IDE to configure your project`
4991
5633
  );
4992
- console.log(` 2. Run ${pc13.cyan("/start-session")} to begin working`);
5634
+ console.log(` 2. Run ${pc14.cyan("/start-session")} to begin working`);
4993
5635
  console.log();
4994
5636
  }
4995
5637
  });
@@ -5001,9 +5643,9 @@ var auth_exports = {};
5001
5643
  __export(auth_exports, {
5002
5644
  default: () => auth_default
5003
5645
  });
5004
- import { defineCommand as defineCommand9 } from "citty";
5646
+ import { defineCommand as defineCommand10 } from "citty";
5005
5647
  import { text as text5, confirm as confirm7, isCancel as isCancel8, cancel as cancel7 } from "@clack/prompts";
5006
- import pc14 from "picocolors";
5648
+ import pc15 from "picocolors";
5007
5649
  var auth_default;
5008
5650
  var init_auth = __esm({
5009
5651
  "src/commands/auth.ts"() {
@@ -5011,7 +5653,7 @@ var init_auth = __esm({
5011
5653
  init_ui();
5012
5654
  init_api_key();
5013
5655
  init_global_config();
5014
- auth_default = defineCommand9({
5656
+ auth_default = defineCommand10({
5015
5657
  meta: {
5016
5658
  name: "auth",
5017
5659
  description: "Store API key globally (~/.flydocs/credentials)"
@@ -5025,13 +5667,13 @@ var init_auth = __esm({
5025
5667
  },
5026
5668
  async run({ args }) {
5027
5669
  console.log();
5028
- console.log(` ${pc14.bold("FlyDocs Authentication")}`);
5670
+ console.log(` ${pc15.bold("FlyDocs Authentication")}`);
5029
5671
  console.log();
5030
5672
  let apiKey = args.key ?? "";
5031
5673
  const existing = await readGlobalCredential();
5032
5674
  if (existing?.apiKey && !apiKey) {
5033
5675
  printInfo(
5034
- `Existing key found: ${pc14.dim(existing.apiKey.slice(0, 8) + "...")}`
5676
+ `Existing key found: ${pc15.dim(existing.apiKey.slice(0, 8) + "...")}`
5035
5677
  );
5036
5678
  const replace = await confirm7({
5037
5679
  message: "Replace with a new key?"
@@ -5043,7 +5685,7 @@ var init_auth = __esm({
5043
5685
  }
5044
5686
  if (!apiKey) {
5045
5687
  console.log(
5046
- ` ${pc14.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
5688
+ ` ${pc15.dim("Get your API key (fdk_...) from your FlyDocs dashboard")}`
5047
5689
  );
5048
5690
  console.log();
5049
5691
  const keyInput = await text5({
@@ -5079,7 +5721,7 @@ var init_auth = __esm({
5079
5721
  printError("Invalid API key. Check your key and try again.");
5080
5722
  process.exit(1);
5081
5723
  }
5082
- printStatus(`Authenticated with ${pc14.bold(result.org)}`);
5724
+ printStatus(`Authenticated with ${pc15.bold(result.org)}`);
5083
5725
  } catch {
5084
5726
  printError(
5085
5727
  "Could not reach FlyDocs API. Check your network and try again."
@@ -5094,10 +5736,10 @@ var init_auth = __esm({
5094
5736
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5095
5737
  lastValidated: (/* @__PURE__ */ new Date()).toISOString()
5096
5738
  });
5097
- printStatus(`Key stored at ${pc14.dim(credentialsPath())}`);
5739
+ printStatus(`Key stored at ${pc15.dim(credentialsPath())}`);
5098
5740
  await checkCredentialPermissions();
5099
5741
  console.log();
5100
- console.log(` ${pc14.bold("Authenticated!")} Key stored globally.`);
5742
+ console.log(` ${pc15.bold("Authenticated!")} Key stored globally.`);
5101
5743
  console.log(` All FlyDocs projects on this machine will use this key.`);
5102
5744
  console.log();
5103
5745
  }
@@ -5110,10 +5752,21 @@ var sync_exports = {};
5110
5752
  __export(sync_exports, {
5111
5753
  default: () => sync_default
5112
5754
  });
5113
- import { defineCommand as defineCommand10 } from "citty";
5114
- import pc15 from "picocolors";
5115
- import { join as join24 } from "path";
5116
- import { mkdir as mkdir11, writeFile as writeFile14 } from "fs/promises";
5755
+ import { defineCommand as defineCommand11 } from "citty";
5756
+ import pc16 from "picocolors";
5757
+ import { join as join27, resolve as resolve6 } from "path";
5758
+ import { mkdir as mkdir14, writeFile as writeFile17 } from "fs/promises";
5759
+ async function resolveArtifactWriteRoot(targetDir, config) {
5760
+ const topology = config.topology;
5761
+ if (topology && topology.type === 4 && topology.label === "sibling-repos") {
5762
+ const parentDir = join27(targetDir, "..");
5763
+ const workspaceFile = await readWorkspaceFile(parentDir);
5764
+ if (workspaceFile) {
5765
+ return resolve6(parentDir);
5766
+ }
5767
+ }
5768
+ return targetDir;
5769
+ }
5117
5770
  var sync_default;
5118
5771
  var init_sync = __esm({
5119
5772
  "src/commands/sync.ts"() {
@@ -5124,8 +5777,11 @@ var init_sync = __esm({
5124
5777
  init_config_integrity();
5125
5778
  init_fs_ops();
5126
5779
  init_types();
5780
+ init_gitignore();
5781
+ init_artifacts();
5782
+ init_workspace();
5127
5783
  init_relay_client();
5128
- sync_default = defineCommand10({
5784
+ sync_default = defineCommand11({
5129
5785
  meta: {
5130
5786
  name: "sync",
5131
5787
  description: "Pull latest config and templates from server"
@@ -5158,10 +5814,12 @@ var init_sync = __esm({
5158
5814
  process.exit(1);
5159
5815
  }
5160
5816
  let currentTemplateVersion = 0;
5817
+ let currentArtifactVersion = 0;
5161
5818
  try {
5162
5819
  const currentConfig = await readAnyConfig(targetDir);
5163
5820
  if (isConfigV2(currentConfig)) {
5164
5821
  currentTemplateVersion = currentConfig.configVersion ?? 0;
5822
+ currentArtifactVersion = currentConfig.artifactVersion ?? 0;
5165
5823
  }
5166
5824
  } catch {
5167
5825
  }
@@ -5176,14 +5834,14 @@ var init_sync = __esm({
5176
5834
  } else {
5177
5835
  printWarning("Server unreachable, using cached config.");
5178
5836
  }
5179
- console.log(` ${pc15.dim("Config may be stale. Retry when connected.")}`);
5837
+ console.log(` ${pc16.dim("Config may be stale. Retry when connected.")}`);
5180
5838
  return;
5181
5839
  }
5182
5840
  if (!serverResponse.valid) {
5183
5841
  printWarning("Server returned invalid config. Keeping current config.");
5184
5842
  return;
5185
5843
  }
5186
- await mkdir11(join24(targetDir, ".flydocs"), { recursive: true });
5844
+ await mkdir14(join27(targetDir, ".flydocs"), { recursive: true });
5187
5845
  const configWithHash = applyConfigHash(serverResponse.config);
5188
5846
  await writeConfig(targetDir, configWithHash);
5189
5847
  changes.push("Updated .flydocs/config.json");
@@ -5196,21 +5854,21 @@ var init_sync = __esm({
5196
5854
  workspaceId
5197
5855
  );
5198
5856
  if (templatesResponse.templates.length > 0) {
5199
- const templatesDir = join24(
5857
+ const templatesDir = join27(
5200
5858
  targetDir,
5201
5859
  ".claude",
5202
5860
  "skills",
5203
5861
  "flydocs-workflow",
5204
5862
  "templates"
5205
5863
  );
5206
- await mkdir11(templatesDir, { recursive: true });
5864
+ await mkdir14(templatesDir, { recursive: true });
5207
5865
  for (const template of templatesResponse.templates) {
5208
5866
  const filename = `${template.type}.md`;
5209
5867
  const subdir = template.category === "issue" ? "issues" : template.category === "pr" ? "pr" : template.category === "comment" ? "." : ".";
5210
- const templateDir = join24(templatesDir, subdir);
5211
- await mkdir11(templateDir, { recursive: true });
5212
- await writeFile14(
5213
- join24(templateDir, filename),
5868
+ const templateDir = join27(templatesDir, subdir);
5869
+ await mkdir14(templateDir, { recursive: true });
5870
+ await writeFile17(
5871
+ join27(templateDir, filename),
5214
5872
  template.content,
5215
5873
  "utf-8"
5216
5874
  );
@@ -5227,6 +5885,24 @@ var init_sync = __esm({
5227
5885
  }
5228
5886
  }
5229
5887
  }
5888
+ const serverArtifactVersion = serverResponse.artifactVersion ?? 0;
5889
+ if (serverArtifactVersion > currentArtifactVersion) {
5890
+ const artifactWriteRoot = await resolveArtifactWriteRoot(
5891
+ targetDir,
5892
+ serverResponse.config
5893
+ );
5894
+ const { changes: artifactChanges } = await syncAllArtifacts(
5895
+ artifactWriteRoot,
5896
+ apiKey,
5897
+ workspaceId,
5898
+ currentArtifactVersion
5899
+ );
5900
+ changes.push(...artifactChanges);
5901
+ if (artifactWriteRoot !== targetDir) {
5902
+ changes.push(`Artifacts written to workspace root`);
5903
+ }
5904
+ }
5905
+ await migrateGitignore(targetDir);
5230
5906
  if (changes.length === 0) {
5231
5907
  printStatus("Already up to date.");
5232
5908
  } else {
@@ -5245,15 +5921,15 @@ var upgrade_exports = {};
5245
5921
  __export(upgrade_exports, {
5246
5922
  default: () => upgrade_default
5247
5923
  });
5248
- import { defineCommand as defineCommand11 } from "citty";
5249
- import pc16 from "picocolors";
5924
+ import { defineCommand as defineCommand12 } from "citty";
5925
+ import pc17 from "picocolors";
5250
5926
  var upgrade_default;
5251
5927
  var init_upgrade = __esm({
5252
5928
  "src/commands/upgrade.ts"() {
5253
5929
  "use strict";
5254
5930
  init_config();
5255
5931
  init_fs_ops();
5256
- upgrade_default = defineCommand11({
5932
+ upgrade_default = defineCommand12({
5257
5933
  meta: {
5258
5934
  name: "upgrade",
5259
5935
  description: "Learn about FlyDocs Cloud tier and upgrade from local"
@@ -5282,34 +5958,34 @@ var init_upgrade = __esm({
5282
5958
  console.log();
5283
5959
  if (currentTier === "cloud") {
5284
5960
  console.log(
5285
- ` ${pc16.green("\u2713")} You're already on the ${pc16.bold("cloud")} tier.`
5961
+ ` ${pc17.green("\u2713")} You're already on the ${pc17.bold("cloud")} tier.`
5286
5962
  );
5287
5963
  console.log();
5288
5964
  console.log(` Your issues sync with your provider via the relay API.`);
5289
5965
  console.log(
5290
- ` Run ${pc16.cyan("flydocs connect")} to update your connection settings.`
5966
+ ` Run ${pc17.cyan("flydocs connect")} to update your connection settings.`
5291
5967
  );
5292
5968
  console.log();
5293
5969
  return;
5294
5970
  }
5295
- console.log(` ${pc16.bold("FlyDocs Cloud Tier")}`);
5971
+ console.log(` ${pc17.bold("FlyDocs Cloud Tier")}`);
5296
5972
  console.log();
5297
- console.log(` You're currently on the ${pc16.yellow("local")} tier.`);
5973
+ console.log(` You're currently on the ${pc17.yellow("local")} tier.`);
5298
5974
  console.log(` Upgrade to cloud for:`);
5299
5975
  console.log();
5300
- console.log(` ${pc16.cyan("\u2192")} Issue sync with Linear, Jira, and more`);
5301
- console.log(` ${pc16.cyan("\u2192")} Project milestones and cycle management`);
5302
- console.log(` ${pc16.cyan("\u2192")} Team assignment and priority tracking`);
5303
- console.log(` ${pc16.cyan("\u2192")} Project health updates and dashboards`);
5304
- console.log(` ${pc16.cyan("\u2192")} Cross-project issue linking`);
5976
+ console.log(` ${pc17.cyan("\u2192")} Issue sync with Linear, Jira, and more`);
5977
+ console.log(` ${pc17.cyan("\u2192")} Project milestones and cycle management`);
5978
+ console.log(` ${pc17.cyan("\u2192")} Team assignment and priority tracking`);
5979
+ console.log(` ${pc17.cyan("\u2192")} Project health updates and dashboards`);
5980
+ console.log(` ${pc17.cyan("\u2192")} Cross-project issue linking`);
5305
5981
  console.log();
5306
- console.log(` ${pc16.bold("How to upgrade:")}`);
5982
+ console.log(` ${pc17.bold("How to upgrade:")}`);
5307
5983
  console.log();
5308
- console.log(` Option 1: Run ${pc16.cyan("/flydocs-upgrade")} in your IDE`);
5984
+ console.log(` Option 1: Run ${pc17.cyan("/flydocs-upgrade")} in your IDE`);
5309
5985
  console.log(` Guided migration with issue transfer`);
5310
5986
  console.log();
5311
5987
  console.log(
5312
- ` Option 2: Run ${pc16.cyan("flydocs connect")} from terminal`
5988
+ ` Option 2: Run ${pc17.cyan("flydocs connect")} from terminal`
5313
5989
  );
5314
5990
  console.log(` Quick tier swap (no issue migration)`);
5315
5991
  console.log();
@@ -5323,23 +5999,23 @@ var self_update_exports = {};
5323
5999
  __export(self_update_exports, {
5324
6000
  default: () => self_update_default
5325
6001
  });
5326
- import { defineCommand as defineCommand12 } from "citty";
6002
+ import { defineCommand as defineCommand13 } from "citty";
5327
6003
  import { execSync } from "child_process";
5328
- import pc17 from "picocolors";
6004
+ import pc18 from "picocolors";
5329
6005
  var self_update_default;
5330
6006
  var init_self_update = __esm({
5331
6007
  "src/commands/self-update.ts"() {
5332
6008
  "use strict";
5333
6009
  init_constants();
5334
6010
  init_ui();
5335
- self_update_default = defineCommand12({
6011
+ self_update_default = defineCommand13({
5336
6012
  meta: {
5337
6013
  name: "self-update",
5338
6014
  description: "Update FlyDocs CLI to the latest version"
5339
6015
  },
5340
6016
  async run() {
5341
6017
  console.log();
5342
- console.log(` Updating ${pc17.cyan(PACKAGE_NAME)}...`);
6018
+ console.log(` Updating ${pc18.cyan(PACKAGE_NAME)}...`);
5343
6019
  console.log();
5344
6020
  try {
5345
6021
  execSync(`npm install -g ${PACKAGE_NAME}@beta`, {
@@ -5364,15 +6040,15 @@ var telemetry_exports = {};
5364
6040
  __export(telemetry_exports, {
5365
6041
  default: () => telemetry_default
5366
6042
  });
5367
- import { defineCommand as defineCommand13 } from "citty";
5368
- import pc18 from "picocolors";
6043
+ import { defineCommand as defineCommand14 } from "citty";
6044
+ import pc19 from "picocolors";
5369
6045
  var enable, disable, status, telemetry_default;
5370
6046
  var init_telemetry2 = __esm({
5371
6047
  "src/commands/telemetry.ts"() {
5372
6048
  "use strict";
5373
6049
  init_telemetry();
5374
6050
  init_ui();
5375
- enable = defineCommand13({
6051
+ enable = defineCommand14({
5376
6052
  meta: {
5377
6053
  name: "enable",
5378
6054
  description: "Enable anonymous usage analytics"
@@ -5387,7 +6063,7 @@ var init_telemetry2 = __esm({
5387
6063
  }
5388
6064
  }
5389
6065
  });
5390
- disable = defineCommand13({
6066
+ disable = defineCommand14({
5391
6067
  meta: {
5392
6068
  name: "disable",
5393
6069
  description: "Disable anonymous usage analytics"
@@ -5405,7 +6081,7 @@ var init_telemetry2 = __esm({
5405
6081
  }
5406
6082
  }
5407
6083
  });
5408
- status = defineCommand13({
6084
+ status = defineCommand14({
5409
6085
  meta: {
5410
6086
  name: "status",
5411
6087
  description: "Show current telemetry status"
@@ -5413,37 +6089,37 @@ var init_telemetry2 = __esm({
5413
6089
  async run() {
5414
6090
  const info = await getStatus();
5415
6091
  console.log();
5416
- console.log(pc18.bold("Telemetry Status"));
6092
+ console.log(pc19.bold("Telemetry Status"));
5417
6093
  console.log();
5418
6094
  const effectivelyEnabled = info.enabled && !info.envOverride;
5419
6095
  console.log(
5420
- ` Enabled: ${effectivelyEnabled ? pc18.green("yes") : pc18.yellow("no")}`
6096
+ ` Enabled: ${effectivelyEnabled ? pc19.green("yes") : pc19.yellow("no")}`
5421
6097
  );
5422
6098
  if (info.envOverride) {
5423
6099
  console.log(
5424
- ` Env override: ${pc18.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
6100
+ ` Env override: ${pc19.yellow("FLYDOCS_TELEMETRY=0 (disabled via env)")}`
5425
6101
  );
5426
6102
  }
5427
6103
  if (info.anonymousId) {
5428
- console.log(` Anonymous ID: ${pc18.dim(info.anonymousId)}`);
6104
+ console.log(` Anonymous ID: ${pc19.dim(info.anonymousId)}`);
5429
6105
  } else {
5430
6106
  console.log(
5431
- ` Anonymous ID: ${pc18.dim("(not yet created \u2014 generated on first run)")}`
6107
+ ` Anonymous ID: ${pc19.dim("(not yet created \u2014 generated on first run)")}`
5432
6108
  );
5433
6109
  }
5434
6110
  console.log();
5435
6111
  console.log(
5436
- pc18.dim(
6112
+ pc19.dim(
5437
6113
  " FlyDocs collects anonymous usage analytics to improve the CLI."
5438
6114
  )
5439
6115
  );
5440
6116
  console.log(
5441
- pc18.dim(" No personal data, file contents, or code is ever collected.")
6117
+ pc19.dim(" No personal data, file contents, or code is ever collected.")
5442
6118
  );
5443
6119
  console.log();
5444
6120
  }
5445
6121
  });
5446
- telemetry_default = defineCommand13({
6122
+ telemetry_default = defineCommand14({
5447
6123
  meta: {
5448
6124
  name: "telemetry",
5449
6125
  description: "Manage anonymous usage analytics (enable, disable, status)"
@@ -5459,7 +6135,7 @@ var init_telemetry2 = __esm({
5459
6135
 
5460
6136
  // src/cli.ts
5461
6137
  init_constants();
5462
- import { defineCommand as defineCommand14, runMain } from "citty";
6138
+ import { defineCommand as defineCommand15, runMain } from "citty";
5463
6139
  var SUB_COMMANDS = /* @__PURE__ */ new Set([
5464
6140
  "install",
5465
6141
  "init",
@@ -5470,6 +6146,7 @@ var SUB_COMMANDS = /* @__PURE__ */ new Set([
5470
6146
  "connect",
5471
6147
  "auth",
5472
6148
  "sync",
6149
+ "scan",
5473
6150
  "cleanup",
5474
6151
  "upgrade",
5475
6152
  "self-update",
@@ -5483,7 +6160,7 @@ var firstPositional = userArgs.find((a) => !a.startsWith("-"));
5483
6160
  if (!hasMetaFlag && (!firstPositional || !SUB_COMMANDS.has(firstPositional))) {
5484
6161
  process.argv.splice(2, 0, "install");
5485
6162
  }
5486
- var main = defineCommand14({
6163
+ var main = defineCommand15({
5487
6164
  meta: {
5488
6165
  name: CLI_NAME,
5489
6166
  version: CLI_VERSION,
@@ -5499,6 +6176,7 @@ var main = defineCommand14({
5499
6176
  connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
5500
6177
  auth: () => Promise.resolve().then(() => (init_auth(), auth_exports)).then((m) => m.default),
5501
6178
  sync: () => Promise.resolve().then(() => (init_sync(), sync_exports)).then((m) => m.default),
6179
+ scan: () => Promise.resolve().then(() => (init_scan(), scan_exports)).then((m) => m.default),
5502
6180
  cleanup: () => Promise.resolve().then(() => (init_cleanup(), cleanup_exports)).then((m) => m.default),
5503
6181
  upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
5504
6182
  "self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default),