@nalvietnam/avatar-cli 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/ai.ts
7
7
  import { promises as fs4 } from "fs";
8
- import { join as join5 } from "path";
8
+ import { join as join6 } from "path";
9
9
  import { confirm } from "@inquirer/prompts";
10
10
 
11
11
  // src/lib/filesystem-helpers.ts
@@ -42,12 +42,44 @@ async function writeJsonAtomic(path, data, mode) {
42
42
  `, mode);
43
43
  }
44
44
 
45
+ // src/lib/resolve-avatar-workspace-root-from-cwd.ts
46
+ import { existsSync, readFileSync } from "fs";
47
+ import { dirname as dirname2, join as join2 } from "path";
48
+ var MAX_WALKUP_LEVELS = 5;
49
+ function isAvatarWorkspace(dir) {
50
+ const hasClaudeDir = existsSync(join2(dir, ".claude"));
51
+ const hasClaudeMd = existsSync(join2(dir, "CLAUDE.md"));
52
+ if (!hasClaudeDir || !hasClaudeMd) return false;
53
+ const hasSrcDir = existsSync(join2(dir, "src"));
54
+ const gitmodulesPath = join2(dir, ".gitmodules");
55
+ if (hasSrcDir) return true;
56
+ if (existsSync(gitmodulesPath)) {
57
+ try {
58
+ const content = readFileSync(gitmodulesPath, "utf8");
59
+ return content.includes("path = src") || content.includes("path = ./src");
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ function resolveAvatarWorkspaceRootFromCwd(startDir) {
67
+ let current = startDir;
68
+ for (let i = 0; i < MAX_WALKUP_LEVELS; i++) {
69
+ if (isAvatarWorkspace(current)) return current;
70
+ const parent = dirname2(current);
71
+ if (parent === current) return null;
72
+ current = parent;
73
+ }
74
+ return null;
75
+ }
76
+
45
77
  // src/lib/audit-log-appender.ts
46
78
  import { promises as fs2 } from "fs";
47
79
 
48
80
  // src/lib/user-config-store.ts
49
81
  import { homedir } from "os";
50
- import { join as join2 } from "path";
82
+ import { join as join3 } from "path";
51
83
 
52
84
  // src/types/config-schema.ts
53
85
  import { z } from "zod";
@@ -80,11 +112,11 @@ var projectSettingsSchema = z.object({
80
112
  var initModeSchema = z.enum(["internal", "client", "library"]);
81
113
 
82
114
  // src/lib/user-config-store.ts
83
- var AVATAR_HOME = join2(homedir(), ".avatar");
84
- var USER_CONFIG_PATH = join2(AVATAR_HOME, "config.json");
85
- var USER_STATE_PATH = join2(AVATAR_HOME, "state.json");
86
- var AUDIT_LOG_PATH = join2(AVATAR_HOME, "audit.log");
87
- var BACKUPS_DIR = join2(AVATAR_HOME, "backups");
115
+ var AVATAR_HOME = join3(homedir(), ".avatar");
116
+ var USER_CONFIG_PATH = join3(AVATAR_HOME, "config.json");
117
+ var USER_STATE_PATH = join3(AVATAR_HOME, "state.json");
118
+ var AUDIT_LOG_PATH = join3(AVATAR_HOME, "audit.log");
119
+ var BACKUPS_DIR = join3(AVATAR_HOME, "backups");
88
120
  var SECRET_FILE_MODE = 384;
89
121
  async function ensureAvatarHome() {
90
122
  await ensureDir(AVATAR_HOME);
@@ -348,18 +380,18 @@ function installClaudeCodeViaNpm() {
348
380
  }
349
381
 
350
382
  // src/lib/prompt-ai-provider-choice.ts
351
- import { readFileSync } from "fs";
383
+ import { readFileSync as readFileSync2 } from "fs";
352
384
  import { homedir as homedir2 } from "os";
353
- import { join as join3 } from "path";
385
+ import { join as join4 } from "path";
354
386
  import { select } from "@inquirer/prompts";
355
387
  function getGlobalSettingsPath() {
356
- return join3(homedir2(), ".claude", "settings.json");
388
+ return join4(homedir2(), ".claude", "settings.json");
357
389
  }
358
390
  function detectGlobalClaudeSettings() {
359
391
  const path = getGlobalSettingsPath();
360
392
  let raw;
361
393
  try {
362
- raw = readFileSync(path, "utf8");
394
+ raw = readFileSync2(path, "utf8");
363
395
  } catch {
364
396
  return { exists: false, hasBaseUrl: false, hasToken: false };
365
397
  }
@@ -497,10 +529,10 @@ async function setupLLMLiteApiKeyAndModel() {
497
529
 
498
530
  // src/lib/write-claude-settings-json-per-project.ts
499
531
  import { promises as fs3 } from "fs";
500
- import { join as join4 } from "path";
532
+ import { join as join5 } from "path";
501
533
  var SECRET_FILE_MODE2 = 384;
502
534
  function getClaudeSettingsPath(workspacePath) {
503
- return join4(workspacePath, ".claude", "settings.json");
535
+ return join5(workspacePath, ".claude", "settings.json");
504
536
  }
505
537
  async function readExistingSettings(path) {
506
538
  if (!await pathExists(path)) return {};
@@ -752,15 +784,23 @@ async function testAiProviderByDetectedMode(settings) {
752
784
  // src/commands/ai.ts
753
785
  async function ensureWorkspaceCwd() {
754
786
  const cwd = process.cwd();
755
- const claudeDir = join5(cwd, ".claude");
756
- if (!await pathExists(claudeDir)) {
757
- log.error("Kh\xF4ng th\u1EA5y .claude/ trong th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i. Ch\u1EA1y l\u1EC7nh n\xE0y trong workspace Avatar.");
787
+ const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);
788
+ if (!workspaceRoot) {
789
+ log.error(
790
+ `Kh\xF4ng t\xECm th\u1EA5y Avatar workspace t\u1EEB th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i.
791
+ Avatar workspace c\u1EA7n c\xF3: .claude/ + CLAUDE.md + src/ (ho\u1EB7c .gitmodules).
792
+ B\u1EA1n \u0111ang \u1EDF: ${cwd}
793
+ Cd v\xE0o workspace dir (vd /path/to/<project>-workspace) r\u1ED3i ch\u1EA1y l\u1EA1i.`
794
+ );
758
795
  process.exit(1);
759
796
  }
760
- return cwd;
797
+ if (workspaceRoot !== cwd) {
798
+ log.dim(`Detected workspace root: ${workspaceRoot}`);
799
+ }
800
+ return workspaceRoot;
761
801
  }
762
802
  async function readWorkspaceSettings(workspacePath) {
763
- const settingsPath = join5(workspacePath, ".claude", "settings.json");
803
+ const settingsPath = join6(workspacePath, ".claude", "settings.json");
764
804
  if (!await pathExists(settingsPath)) return {};
765
805
  try {
766
806
  return await readJson(settingsPath);
@@ -798,7 +838,7 @@ async function runAiTest() {
798
838
  }
799
839
  async function runAiReset(opts) {
800
840
  const workspacePath = await ensureWorkspaceCwd();
801
- const settingsPath = join5(workspacePath, ".claude", "settings.json");
841
+ const settingsPath = join6(workspacePath, ".claude", "settings.json");
802
842
  const settings = await readWorkspaceSettings(workspacePath);
803
843
  if (!opts.yes) {
804
844
  const ok = await confirm({
@@ -848,25 +888,25 @@ import { input as input2 } from "@inquirer/prompts";
848
888
 
849
889
  // src/lib/execute-commit-with-target-selection.ts
850
890
  import { spawnSync as spawnSync5 } from "child_process";
851
- import { existsSync } from "fs";
852
- import { join as join6 } from "path";
891
+ import { existsSync as existsSync2 } from "fs";
892
+ import { join as join7 } from "path";
853
893
  function assertAvatarWorkspaceRoot(cwd) {
854
- const srcGit = join6(cwd, "src", ".git");
855
- const workspaceGit = join6(cwd, ".git");
856
- const claudeDir = join6(cwd, ".claude");
857
- if (!existsSync(workspaceGit)) {
894
+ const srcGit = join7(cwd, "src", ".git");
895
+ const workspaceGit = join7(cwd, ".git");
896
+ const claudeDir = join7(cwd, ".claude");
897
+ if (!existsSync2(workspaceGit)) {
858
898
  throw new Error(
859
899
  `Kh\xF4ng ph\u1EA3i workspace root: ${cwd}
860
900
  Ch\u1EA1y 'avatar commit' trong workspace dir (c\xF3 .git v\xE0 .claude/).`
861
901
  );
862
902
  }
863
- if (!existsSync(claudeDir)) {
903
+ if (!existsSync2(claudeDir)) {
864
904
  throw new Error(
865
905
  `Kh\xF4ng th\u1EA5y .claude/ trong ${cwd}.
866
906
  Ch\u1EA1y 'avatar commit' trong Avatar workspace, kh\xF4ng ph\u1EA3i project b\xECnh th\u01B0\u1EDDng.`
867
907
  );
868
908
  }
869
- if (!existsSync(srcGit)) {
909
+ if (!existsSync2(srcGit)) {
870
910
  throw new Error(
871
911
  `Kh\xF4ng th\u1EA5y src/.git trong ${cwd}.
872
912
  Workspace thi\u1EBFu submodule src/. Ch\u1EA1y 'avatar init' l\u1EA1i?`
@@ -890,7 +930,7 @@ function isDirty(cwd) {
890
930
  return status.length > 0;
891
931
  }
892
932
  async function commitSrc(workspaceRoot, opts) {
893
- const srcPath = join6(workspaceRoot, "src");
933
+ const srcPath = join7(workspaceRoot, "src");
894
934
  if (!isDirty(srcPath)) {
895
935
  log.dim("src/: nothing to commit (clean)");
896
936
  return {};
@@ -930,7 +970,7 @@ async function commitWorkspace(workspaceRoot, opts) {
930
970
  }
931
971
  function warnIfOtherTargetDirty(workspaceRoot, requestedTarget) {
932
972
  if (requestedTarget === "all") return;
933
- const srcDirty = isDirty(join6(workspaceRoot, "src"));
973
+ const srcDirty = isDirty(join7(workspaceRoot, "src"));
934
974
  const workspaceDirty = isDirty(workspaceRoot);
935
975
  if (requestedTarget === "src" && workspaceDirty) {
936
976
  log.warn(
@@ -1006,23 +1046,23 @@ async function runCommit(target, opts) {
1006
1046
  // src/commands/doctor.ts
1007
1047
  import { spawnSync as spawnSync6 } from "child_process";
1008
1048
  import { promises as fs6 } from "fs";
1009
- import { join as join10 } from "path";
1049
+ import { join as join11 } from "path";
1010
1050
  import boxen from "boxen";
1011
1051
 
1012
1052
  // src/lib/git-operations.ts
1013
- import { join as join7 } from "path";
1053
+ import { join as join8 } from "path";
1014
1054
  import { simpleGit } from "simple-git";
1015
1055
  function git(cwd = process.cwd()) {
1016
1056
  return simpleGit({ baseDir: cwd, binary: "git" });
1017
1057
  }
1018
1058
  async function isGitRepo(cwd = process.cwd()) {
1019
- return await pathExists(join7(cwd, ".git"));
1059
+ return await pathExists(join8(cwd, ".git"));
1020
1060
  }
1021
1061
  async function addSubmodule(repoUrl, destPath, cwd = process.cwd()) {
1022
1062
  await git(cwd).subModule(["add", repoUrl, destPath]);
1023
1063
  }
1024
1064
  async function checkoutTagInSubmodule(submodulePath, tag, cwd = process.cwd()) {
1025
- const submoduleCwd = join7(cwd, submodulePath);
1065
+ const submoduleCwd = join8(cwd, submodulePath);
1026
1066
  await git(submoduleCwd).fetch(["--tags"]);
1027
1067
  await git(submoduleCwd).checkout(tag);
1028
1068
  }
@@ -1041,11 +1081,11 @@ async function currentCommitSha(cwd = process.cwd()) {
1041
1081
 
1042
1082
  // src/lib/project-tree-scaffolder.ts
1043
1083
  import { promises as fs5 } from "fs";
1044
- import { join as join9 } from "path";
1084
+ import { join as join10 } from "path";
1045
1085
 
1046
1086
  // src/lib/template-bundle-loader.ts
1047
- import { existsSync as existsSync2 } from "fs";
1048
- import { dirname as dirname2, join as join8 } from "path";
1087
+ import { existsSync as existsSync3 } from "fs";
1088
+ import { dirname as dirname3, join as join9 } from "path";
1049
1089
  import { fileURLToPath } from "url";
1050
1090
 
1051
1091
  // src/lib/mustache-template-engine.ts
@@ -1059,15 +1099,15 @@ function renderTemplate(source, variables) {
1059
1099
  }
1060
1100
 
1061
1101
  // src/lib/template-bundle-loader.ts
1062
- var HERE = dirname2(fileURLToPath(import.meta.url));
1102
+ var HERE = dirname3(fileURLToPath(import.meta.url));
1063
1103
  var PACKAGE_ROOT = findPackageRoot(HERE);
1064
- var TEMPLATES_ROOT = join8(PACKAGE_ROOT, "src", "templates");
1065
- var HOOKS_ROOT = join8(PACKAGE_ROOT, "src", "hooks");
1104
+ var TEMPLATES_ROOT = join9(PACKAGE_ROOT, "src", "templates");
1105
+ var HOOKS_ROOT = join9(PACKAGE_ROOT, "src", "hooks");
1066
1106
  function findPackageRoot(startDir) {
1067
1107
  let dir = startDir;
1068
1108
  while (true) {
1069
- if (existsSync2(join8(dir, "package.json"))) return dir;
1070
- const parent = dirname2(dir);
1109
+ if (existsSync3(join9(dir, "package.json"))) return dir;
1110
+ const parent = dirname3(dir);
1071
1111
  if (parent === dir) {
1072
1112
  throw new Error(`Cannot locate package root from ${startDir}`);
1073
1113
  }
@@ -1075,14 +1115,14 @@ function findPackageRoot(startDir) {
1075
1115
  }
1076
1116
  }
1077
1117
  async function loadTemplate(name) {
1078
- return await readText(join8(TEMPLATES_ROOT, `${name}.tpl`));
1118
+ return await readText(join9(TEMPLATES_ROOT, `${name}.tpl`));
1079
1119
  }
1080
1120
  async function renderTemplateByName(name, variables) {
1081
1121
  const source = await loadTemplate(name);
1082
1122
  return renderTemplate(source, variables);
1083
1123
  }
1084
1124
  async function loadHook(name) {
1085
- return await readText(join8(HOOKS_ROOT, `${name}.sh.tpl`));
1125
+ return await readText(join9(HOOKS_ROOT, `${name}.sh.tpl`));
1086
1126
  }
1087
1127
 
1088
1128
  // src/lib/project-tree-scaffolder.ts
@@ -1116,12 +1156,12 @@ var PROJECT_KNOWLEDGE_TEMPLATES = [
1116
1156
  "project/gotchas.md"
1117
1157
  ];
1118
1158
  async function createClaudeDirTree(projectRoot) {
1119
- const claudeRoot = join9(projectRoot, ".claude");
1159
+ const claudeRoot = join10(projectRoot, ".claude");
1120
1160
  await ensureDir(claudeRoot);
1121
1161
  for (const sub of CLAUDE_SUBDIRS) {
1122
- const dir = join9(claudeRoot, sub);
1162
+ const dir = join10(claudeRoot, sub);
1123
1163
  await ensureDir(dir);
1124
- await writeTextAtomic(join9(dir, ".gitkeep"), "");
1164
+ await writeTextAtomic(join10(dir, ".gitkeep"), "");
1125
1165
  }
1126
1166
  }
1127
1167
  async function writeProjectKnowledgeFiles(projectRoot, vars) {
@@ -1152,7 +1192,7 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
1152
1192
  for (const tpl of PROJECT_KNOWLEDGE_TEMPLATES) {
1153
1193
  const content = await renderTemplateByName(tpl, baseVars);
1154
1194
  const relative4 = tpl.replace(/^project\//, "");
1155
- const outPath = join9(projectRoot, ".claude", "project", relative4);
1195
+ const outPath = join10(projectRoot, ".claude", "project", relative4);
1156
1196
  const backup = await writeWithBackup(outPath, content);
1157
1197
  if (backup) backups.push(backup);
1158
1198
  }
@@ -1160,14 +1200,14 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
1160
1200
  }
1161
1201
  async function writeRootClaudeMd(projectRoot, vars) {
1162
1202
  const content = await renderTemplateByName("CLAUDE.md", vars);
1163
- return await writeWithBackup(join9(projectRoot, "CLAUDE.md"), content);
1203
+ return await writeWithBackup(join10(projectRoot, "CLAUDE.md"), content);
1164
1204
  }
1165
1205
  async function writeProjectSettings(projectRoot, vars) {
1166
1206
  const content = await renderTemplateByName("settings.json", vars);
1167
- return await writeWithBackup(join9(projectRoot, ".claude", "settings.json"), content);
1207
+ return await writeWithBackup(join10(projectRoot, ".claude", "settings.json"), content);
1168
1208
  }
1169
1209
  async function appendGitignoreEntries(projectRoot) {
1170
- const path = join9(projectRoot, ".gitignore");
1210
+ const path = join10(projectRoot, ".gitignore");
1171
1211
  const tpl = await renderTemplateByName("gitignore", {});
1172
1212
  const marker = "# Avatar \u2014 git-ignored entries injected on `avatar init`";
1173
1213
  let existing = "";
@@ -1181,9 +1221,9 @@ ${tpl}`);
1181
1221
  }
1182
1222
  async function installGitHook(gitDir, hookName) {
1183
1223
  const content = await loadHook(hookName);
1184
- const hooksDir = join9(gitDir, "hooks");
1224
+ const hooksDir = join10(gitDir, "hooks");
1185
1225
  await ensureDir(hooksDir);
1186
- const dest = join9(hooksDir, hookName);
1226
+ const dest = join10(hooksDir, hookName);
1187
1227
  await writeTextAtomic(dest, content, 493);
1188
1228
  }
1189
1229
 
@@ -1241,7 +1281,7 @@ async function runChecks(cwd) {
1241
1281
  detail: gitRepo ? cwd : "Kh\xF4ng ph\u1EA3i git repo (c\u1EA7n cho 'avatar init')",
1242
1282
  fixable: false
1243
1283
  });
1244
- const packPath = join10(cwd, ".claude", "pack");
1284
+ const packPath = join11(cwd, ".claude", "pack");
1245
1285
  const hasPack = await pathExists(packPath);
1246
1286
  checks.push({
1247
1287
  name: "team-ai-pack submodule",
@@ -1249,7 +1289,7 @@ async function runChecks(cwd) {
1249
1289
  detail: hasPack ? packPath : "Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",
1250
1290
  fixable: false
1251
1291
  });
1252
- const claudeMdPath = join10(cwd, "CLAUDE.md");
1292
+ const claudeMdPath = join11(cwd, "CLAUDE.md");
1253
1293
  const hasClaudeMd = await pathExists(claudeMdPath);
1254
1294
  checks.push({
1255
1295
  name: "CLAUDE.md",
@@ -1257,7 +1297,7 @@ async function runChecks(cwd) {
1257
1297
  detail: hasClaudeMd ? "t\u1ED3n t\u1EA1i \u1EDF project root" : "thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",
1258
1298
  fixable: false
1259
1299
  });
1260
- const hookPath = join10(cwd, ".git", "hooks", "post-merge");
1300
+ const hookPath = join11(cwd, ".git", "hooks", "post-merge");
1261
1301
  const hasHook = await pathExists(hookPath);
1262
1302
  if (gitRepo && hasPack) {
1263
1303
  checks.push({
@@ -1266,11 +1306,11 @@ async function runChecks(cwd) {
1266
1306
  detail: hasHook ? "installed" : "missing \u2014 fixable",
1267
1307
  fixable: !hasHook,
1268
1308
  fix: hasHook ? void 0 : async () => {
1269
- await installGitHook(join10(cwd, ".git"), "post-merge");
1309
+ await installGitHook(join11(cwd, ".git"), "post-merge");
1270
1310
  }
1271
1311
  });
1272
1312
  }
1273
- const gitignorePath = join10(cwd, ".gitignore");
1313
+ const gitignorePath = join11(cwd, ".gitignore");
1274
1314
  if (gitRepo) {
1275
1315
  let gitignoreOk = false;
1276
1316
  if (await pathExists(gitignorePath)) {
@@ -1332,7 +1372,7 @@ async function applyFixes(checks) {
1332
1372
  }
1333
1373
 
1334
1374
  // src/commands/init.ts
1335
- import { basename, join as join17, relative as relative2, resolve } from "path";
1375
+ import { basename, join as join18, relative as relative2, resolve } from "path";
1336
1376
  import { confirm as confirm3, input as input5, select as select8 } from "@inquirer/prompts";
1337
1377
  import boxen4 from "boxen";
1338
1378
 
@@ -1365,7 +1405,7 @@ async function promptRetryOrSkip(args) {
1365
1405
  }
1366
1406
 
1367
1407
  // src/lib/team-pack-submodule-manager.ts
1368
- import { join as join11 } from "path";
1408
+ import { join as join12 } from "path";
1369
1409
 
1370
1410
  // src/lib/check-team-pack-access-with-retry-loop.ts
1371
1411
  import { spawnSync as spawnSync7 } from "child_process";
@@ -1530,7 +1570,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1530
1570
  }
1531
1571
  let target = tag ?? null;
1532
1572
  if (!target) {
1533
- target = await latestTag(join11(projectRoot, TEAM_PACK_RELATIVE_PATH));
1573
+ target = await latestTag(join12(projectRoot, TEAM_PACK_RELATIVE_PATH));
1534
1574
  }
1535
1575
  if (target) {
1536
1576
  await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
@@ -1538,7 +1578,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1538
1578
  return { pinnedTag: target };
1539
1579
  }
1540
1580
  async function readPinnedPackVersion(projectRoot) {
1541
- const submoduleRoot = join11(projectRoot, TEAM_PACK_RELATIVE_PATH);
1581
+ const submoduleRoot = join12(projectRoot, TEAM_PACK_RELATIVE_PATH);
1542
1582
  const tag = await latestTag(submoduleRoot);
1543
1583
  if (tag) return tag;
1544
1584
  const sha = await currentCommitSha(submoduleRoot);
@@ -2225,11 +2265,11 @@ import { select as select7 } from "@inquirer/prompts";
2225
2265
  import { simpleGit as simpleGit3 } from "simple-git";
2226
2266
 
2227
2267
  // src/lib/check-folder-has-git.ts
2228
- import { existsSync as existsSync3, statSync } from "fs";
2229
- import { join as join12 } from "path";
2268
+ import { existsSync as existsSync4, statSync } from "fs";
2269
+ import { join as join13 } from "path";
2230
2270
  function checkFolderHasGit(folderPath) {
2231
- const gitPath = join12(folderPath, ".git");
2232
- if (!existsSync3(gitPath)) return false;
2271
+ const gitPath = join13(folderPath, ".git");
2272
+ if (!existsSync4(gitPath)) return false;
2233
2273
  const stat = statSync(gitPath);
2234
2274
  return stat.isDirectory() || stat.isFile();
2235
2275
  }
@@ -2259,8 +2299,8 @@ async function createInitialGitCommit(folderPath) {
2259
2299
  }
2260
2300
 
2261
2301
  // src/lib/detect-folder-tech-stack.ts
2262
- import { existsSync as existsSync4 } from "fs";
2263
- import { join as join13 } from "path";
2302
+ import { existsSync as existsSync5 } from "fs";
2303
+ import { join as join14 } from "path";
2264
2304
  var SIGNATURES = {
2265
2305
  node: ["package.json"],
2266
2306
  python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
@@ -2272,7 +2312,7 @@ var SIGNATURES = {
2272
2312
  function detectFolderTechStack(folderPath) {
2273
2313
  const matched = [];
2274
2314
  for (const [stack, files] of Object.entries(SIGNATURES)) {
2275
- if (files.some((f) => existsSync4(join13(folderPath, f)))) {
2315
+ if (files.some((f) => existsSync5(join14(folderPath, f)))) {
2276
2316
  matched.push(stack);
2277
2317
  }
2278
2318
  }
@@ -2280,26 +2320,26 @@ function detectFolderTechStack(folderPath) {
2280
2320
  }
2281
2321
 
2282
2322
  // src/lib/gitignore-template-loader.ts
2283
- import { readFileSync as readFileSync2 } from "fs";
2284
- import { dirname as dirname3, join as join14 } from "path";
2323
+ import { readFileSync as readFileSync3 } from "fs";
2324
+ import { dirname as dirname4, join as join15 } from "path";
2285
2325
  import { fileURLToPath as fileURLToPath2 } from "url";
2286
- var __dirname = dirname3(fileURLToPath2(import.meta.url));
2326
+ var __dirname = dirname4(fileURLToPath2(import.meta.url));
2287
2327
  var CANDIDATE_DIRS = [
2288
2328
  // Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
2289
- join14(__dirname, "templates", "gitignore"),
2329
+ join15(__dirname, "templates", "gitignore"),
2290
2330
  // Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
2291
- join14(__dirname, "..", "templates", "gitignore"),
2331
+ join15(__dirname, "..", "templates", "gitignore"),
2292
2332
  // Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
2293
- join14(__dirname, "..", "..", "src", "templates", "gitignore"),
2333
+ join15(__dirname, "..", "..", "src", "templates", "gitignore"),
2294
2334
  // npm-installed alt: __dirname = .../dist/ → package_root/src/templates
2295
- join14(__dirname, "..", "src", "templates", "gitignore")
2335
+ join15(__dirname, "..", "src", "templates", "gitignore")
2296
2336
  ];
2297
2337
  var AVATAR_MARKER_START = "# === avatar ===";
2298
2338
  var AVATAR_MARKER_END = "# === /avatar ===";
2299
2339
  function readTemplate(stack) {
2300
2340
  for (const dir of CANDIDATE_DIRS) {
2301
2341
  try {
2302
- return readFileSync2(join14(dir, `${stack}.txt`), "utf8");
2342
+ return readFileSync3(join15(dir, `${stack}.txt`), "utf8");
2303
2343
  } catch {
2304
2344
  }
2305
2345
  }
@@ -2313,15 +2353,15 @@ ${readTemplate(s).trim()}`);
2313
2353
  }
2314
2354
 
2315
2355
  // src/lib/write-or-merge-gitignore.ts
2316
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync } from "fs";
2317
- import { join as join15 } from "path";
2356
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync } from "fs";
2357
+ import { join as join16 } from "path";
2318
2358
  function writeOrMergeGitignore(folderPath, avatarBlock) {
2319
- const path = join15(folderPath, ".gitignore");
2320
- if (!existsSync5(path)) {
2359
+ const path = join16(folderPath, ".gitignore");
2360
+ if (!existsSync6(path)) {
2321
2361
  writeFileSync(path, avatarBlock, "utf8");
2322
2362
  return;
2323
2363
  }
2324
- const existing = readFileSync3(path, "utf8");
2364
+ const existing = readFileSync4(path, "utf8");
2325
2365
  const startIdx = existing.indexOf(AVATAR_MARKER_START);
2326
2366
  const endIdx = existing.indexOf(AVATAR_MARKER_END);
2327
2367
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
@@ -2493,7 +2533,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
2493
2533
 
2494
2534
  // src/commands/init-conflict-detection-helpers.ts
2495
2535
  import { readdir } from "fs/promises";
2496
- import { join as join16 } from "path";
2536
+ import { join as join17 } from "path";
2497
2537
  async function isEmptyOrMissing(path) {
2498
2538
  if (!await pathExists(path)) return true;
2499
2539
  try {
@@ -2506,7 +2546,7 @@ async function isEmptyOrMissing(path) {
2506
2546
  }
2507
2547
  async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
2508
2548
  for (let i = 2; i < maxAttempts; i++) {
2509
- const candidate = join16(parent, `${desiredName}-${i}`);
2549
+ const candidate = join17(parent, `${desiredName}-${i}`);
2510
2550
  if (await isEmptyOrMissing(candidate)) return candidate;
2511
2551
  }
2512
2552
  return null;
@@ -2898,7 +2938,7 @@ async function runInitFromScratch(opts, ownerEmail) {
2898
2938
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2899
2939
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2900
2940
  const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
2901
- const srcPath = join17(workspacePath, "src");
2941
+ const srcPath = join18(workspacePath, "src");
2902
2942
  await ensureDir(workspacePath);
2903
2943
  await ensureDir(srcPath);
2904
2944
  await safeBootstrapGitInFolder(srcPath, { autoYes: true });
@@ -3034,10 +3074,10 @@ async function finalizeWorkspaceScaffold(args) {
3034
3074
  await writeRootClaudeMd(args.workspacePath, vars);
3035
3075
  await writeProjectSettings(args.workspacePath, vars);
3036
3076
  await appendGitignoreEntries(args.workspacePath);
3037
- await ensureDir(join17(args.workspacePath, "notes"));
3038
- await ensureDir(join17(args.workspacePath, "scripts"));
3039
- await installGitHook(join17(args.workspacePath, ".git"), "post-merge");
3040
- await installGitHook(join17(args.workspacePath, ".git", "modules", "src"), "pre-push");
3077
+ await ensureDir(join18(args.workspacePath, "notes"));
3078
+ await ensureDir(join18(args.workspacePath, "scripts"));
3079
+ await installGitHook(join18(args.workspacePath, ".git"), "post-merge");
3080
+ await installGitHook(join18(args.workspacePath, ".git", "modules", "src"), "pre-push");
3041
3081
  log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
3042
3082
  await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
3043
3083
  await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
@@ -3137,7 +3177,7 @@ async function maybeCreateWorkspaceRemote(args) {
3137
3177
  }
3138
3178
  }
3139
3179
  async function resolveWorkspacePath(parent, desiredName, force) {
3140
- const desired = join17(parent, desiredName);
3180
+ const desired = join18(parent, desiredName);
3141
3181
  if (await isEmptyOrMissing(desired)) return desired;
3142
3182
  log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
3143
3183
  while (true) {
@@ -3168,7 +3208,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
3168
3208
  message: "T\xEAn workspace m\u1EDBi:",
3169
3209
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
3170
3210
  });
3171
- const newPath = join17(parent, newName.trim());
3211
+ const newPath = join18(parent, newName.trim());
3172
3212
  if (await isEmptyOrMissing(newPath)) return newPath;
3173
3213
  log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
3174
3214
  }
@@ -3262,15 +3302,15 @@ function registerSecretsCommand(program2) {
3262
3302
 
3263
3303
  // src/commands/status.ts
3264
3304
  import { promises as fs8 } from "fs";
3265
- import { join as join19 } from "path";
3305
+ import { join as join20 } from "path";
3266
3306
  import boxen5 from "boxen";
3267
3307
 
3268
3308
  // src/lib/pack-backup-manager.ts
3269
3309
  import { promises as fs7 } from "fs";
3270
- import { join as join18 } from "path";
3310
+ import { join as join19 } from "path";
3271
3311
  var BACKUP_DIR_NAME = "_backup";
3272
3312
  async function listBackups(projectRoot) {
3273
- const dir = join18(projectRoot, ".claude", BACKUP_DIR_NAME);
3313
+ const dir = join19(projectRoot, ".claude", BACKUP_DIR_NAME);
3274
3314
  if (!await pathExists(dir)) return [];
3275
3315
  const entries = await fs7.readdir(dir, { withFileTypes: true });
3276
3316
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
@@ -3296,7 +3336,7 @@ function registerStatusCommand(program2) {
3296
3336
  }
3297
3337
  async function gatherStatus(cwd) {
3298
3338
  const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
3299
- const claudeRoot = join19(cwd, ".claude");
3339
+ const claudeRoot = join20(cwd, ".claude");
3300
3340
  const hasAvatar = await pathExists(claudeRoot);
3301
3341
  if (!hasAvatar) {
3302
3342
  return {
@@ -3309,8 +3349,8 @@ async function gatherStatus(cwd) {
3309
3349
  hasAvatar: false
3310
3350
  };
3311
3351
  }
3312
- const packVersion = await isGitRepo(join19(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3313
- const pendingDir = join19(claudeRoot, "_pending");
3352
+ const packVersion = await isGitRepo(join20(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3353
+ const pendingDir = join20(claudeRoot, "_pending");
3314
3354
  const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
3315
3355
  const backupCount = (await listBackups(cwd)).length;
3316
3356
  const techStackSummary = await readTechStackFirstLine(claudeRoot);
@@ -3325,7 +3365,7 @@ async function gatherStatus(cwd) {
3325
3365
  };
3326
3366
  }
3327
3367
  async function readTechStackFirstLine(claudeRoot) {
3328
- const techStackPath = join19(claudeRoot, "project", "tech-stack.md");
3368
+ const techStackPath = join20(claudeRoot, "project", "tech-stack.md");
3329
3369
  if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
3330
3370
  const content = await readText(techStackPath);
3331
3371
  const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
@@ -3366,27 +3406,27 @@ import boxen6 from "boxen";
3366
3406
  // src/lib/create-uninstall-backup-snapshot.ts
3367
3407
  import { cp, mkdir, writeFile } from "fs/promises";
3368
3408
  import { homedir as homedir3 } from "os";
3369
- import { basename as basename2, join as join20 } from "path";
3370
- var UNINSTALL_BACKUPS_DIR = join20(homedir3(), ".avatar", "uninstall-backups");
3409
+ import { basename as basename2, join as join21 } from "path";
3410
+ var UNINSTALL_BACKUPS_DIR = join21(homedir3(), ".avatar", "uninstall-backups");
3371
3411
  async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
3372
3412
  const projectName = basename2(projectRoot);
3373
3413
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3374
- const backupDir = join20(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3414
+ const backupDir = join21(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3375
3415
  await mkdir(backupDir, { recursive: true, mode: 448 });
3376
3416
  if (artifacts.claudeDir) {
3377
- await cp(artifacts.claudeDir, join20(backupDir, ".claude"), { recursive: true });
3417
+ await cp(artifacts.claudeDir, join21(backupDir, ".claude"), { recursive: true });
3378
3418
  }
3379
3419
  if (artifacts.claudeMd) {
3380
- await cp(artifacts.claudeMd, join20(backupDir, "CLAUDE.md"));
3420
+ await cp(artifacts.claudeMd, join21(backupDir, "CLAUDE.md"));
3381
3421
  }
3382
3422
  if (artifacts.postMergeHook || artifacts.prePushHook) {
3383
- const hooksBackupDir = join20(backupDir, "hooks");
3423
+ const hooksBackupDir = join21(backupDir, "hooks");
3384
3424
  await mkdir(hooksBackupDir, { recursive: true });
3385
3425
  if (artifacts.postMergeHook) {
3386
- await cp(artifacts.postMergeHook, join20(hooksBackupDir, "post-merge"));
3426
+ await cp(artifacts.postMergeHook, join21(hooksBackupDir, "post-merge"));
3387
3427
  }
3388
3428
  if (artifacts.prePushHook) {
3389
- await cp(artifacts.prePushHook, join20(hooksBackupDir, "pre-push"));
3429
+ await cp(artifacts.prePushHook, join21(hooksBackupDir, "pre-push"));
3390
3430
  }
3391
3431
  }
3392
3432
  const manifest = {
@@ -3401,27 +3441,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
3401
3441
  prePushHook: !!artifacts.prePushHook
3402
3442
  }
3403
3443
  };
3404
- await writeFile(join20(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3444
+ await writeFile(join21(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3405
3445
  return backupDir;
3406
3446
  }
3407
3447
 
3408
3448
  // src/lib/detect-avatar-project-artifacts.ts
3409
- import { existsSync as existsSync6 } from "fs";
3410
- import { join as join21 } from "path";
3449
+ import { existsSync as existsSync7 } from "fs";
3450
+ import { join as join22 } from "path";
3411
3451
  function existsOrNull(path) {
3412
- return existsSync6(path) ? path : null;
3452
+ return existsSync7(path) ? path : null;
3413
3453
  }
3414
3454
  function detectAvatarProjectArtifacts(projectRoot) {
3415
- const claudeDir = existsOrNull(join21(projectRoot, ".claude"));
3416
- const claudeMd = existsOrNull(join21(projectRoot, "CLAUDE.md"));
3417
- const postMergeHook = existsOrNull(join21(projectRoot, ".git", "hooks", "post-merge"));
3455
+ const claudeDir = existsOrNull(join22(projectRoot, ".claude"));
3456
+ const claudeMd = existsOrNull(join22(projectRoot, "CLAUDE.md"));
3457
+ const postMergeHook = existsOrNull(join22(projectRoot, ".git", "hooks", "post-merge"));
3418
3458
  const prePushHook = existsOrNull(
3419
- join21(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3459
+ join22(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3420
3460
  );
3421
- const gitignorePath = existsOrNull(join21(projectRoot, ".gitignore"));
3422
- const gitmodulesPath = existsOrNull(join21(projectRoot, ".gitmodules"));
3423
- const notesDir = existsOrNull(join21(projectRoot, "notes"));
3424
- const scriptsDir = existsOrNull(join21(projectRoot, "scripts"));
3461
+ const gitignorePath = existsOrNull(join22(projectRoot, ".gitignore"));
3462
+ const gitmodulesPath = existsOrNull(join22(projectRoot, ".gitmodules"));
3463
+ const notesDir = existsOrNull(join22(projectRoot, "notes"));
3464
+ const scriptsDir = existsOrNull(join22(projectRoot, "scripts"));
3425
3465
  const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
3426
3466
  return {
3427
3467
  hasAnyArtifact,
@@ -3442,11 +3482,11 @@ async function executeUninstallDeletion(artifacts, flags) {
3442
3482
  if (artifacts.claudeDir) {
3443
3483
  if (flags.keepSubmodule) {
3444
3484
  const { readdir: readdir2 } = await import("fs/promises");
3445
- const { join: join22 } = await import("path");
3485
+ const { join: join23 } = await import("path");
3446
3486
  const entries = await readdir2(artifacts.claudeDir);
3447
3487
  for (const entry of entries) {
3448
3488
  if (entry === "pack") continue;
3449
- await rm(join22(artifacts.claudeDir, entry), { recursive: true, force: true });
3489
+ await rm(join23(artifacts.claudeDir, entry), { recursive: true, force: true });
3450
3490
  }
3451
3491
  } else {
3452
3492
  await rm(artifacts.claudeDir, { recursive: true, force: true });
@@ -3515,7 +3555,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
3515
3555
  }
3516
3556
 
3517
3557
  // src/commands/uninstall.ts
3518
- var CLI_VERSION = "1.3.0";
3558
+ var CLI_VERSION = "1.3.1";
3519
3559
  function registerUninstallCommand(program2) {
3520
3560
  program2.command("uninstall").description("G\u1EE1 Avatar kh\u1ECFi project \u2014 backup t\u1EF1 \u0111\u1ED9ng (M11)").option("--yes", "Skip confirm prompt").option("--no-backup", "Kh\xF4ng t\u1EA1o backup tr\u01B0\u1EDBc khi x\xF3a (nguy hi\u1EC3m)").option("--keep-submodule", "Gi\u1EEF submodule .claude/pack/").option("--keep-hooks", "Gi\u1EEF git hooks post-merge, pre-push").option("--dry-run", "Hi\u1EC3n th\u1ECB danh s\xE1ch s\u1EBD x\xF3a, kh\xF4ng th\u1EF1c thi").action(async (opts) => {
3521
3561
  try {
@@ -3597,7 +3637,7 @@ function printUninstallSuccessBox(backupPath) {
3597
3637
  }
3598
3638
 
3599
3639
  // src/index.ts
3600
- var CLI_VERSION2 = "1.3.0";
3640
+ var CLI_VERSION2 = "1.3.1";
3601
3641
  var program = new Command();
3602
3642
  program.name("avatar").description("AI harness CLI for NAL Vietnam engineering").version(CLI_VERSION2, "-v, --version", "Hi\u1EC3n th\u1ECB phi\xEAn b\u1EA3n Avatar CLI").addHelpText(
3603
3643
  "beforeAll",