@bhimudev/gnanai 0.4.0

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.
Files changed (274) hide show
  1. package/README.md +270 -0
  2. package/dist/bin/cli.d.ts +3 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +188 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/commands/cleanup.d.ts +21 -0
  7. package/dist/commands/cleanup.d.ts.map +1 -0
  8. package/dist/commands/cleanup.js +380 -0
  9. package/dist/commands/cleanup.js.map +1 -0
  10. package/dist/commands/dispatch.d.ts +13 -0
  11. package/dist/commands/dispatch.d.ts.map +1 -0
  12. package/dist/commands/dispatch.js +85 -0
  13. package/dist/commands/dispatch.js.map +1 -0
  14. package/dist/commands/doctor.d.ts +2 -0
  15. package/dist/commands/doctor.d.ts.map +1 -0
  16. package/dist/commands/doctor.js +155 -0
  17. package/dist/commands/doctor.js.map +1 -0
  18. package/dist/commands/generate.d.ts +3 -0
  19. package/dist/commands/generate.d.ts.map +1 -0
  20. package/dist/commands/generate.js +167 -0
  21. package/dist/commands/generate.js.map +1 -0
  22. package/dist/commands/init.d.ts +10 -0
  23. package/dist/commands/init.d.ts.map +1 -0
  24. package/dist/commands/init.js +711 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/knowledge-sync.d.ts +69 -0
  27. package/dist/commands/knowledge-sync.d.ts.map +1 -0
  28. package/dist/commands/knowledge-sync.js +661 -0
  29. package/dist/commands/knowledge-sync.js.map +1 -0
  30. package/dist/commands/knowledge.d.ts +35 -0
  31. package/dist/commands/knowledge.d.ts.map +1 -0
  32. package/dist/commands/knowledge.js +254 -0
  33. package/dist/commands/knowledge.js.map +1 -0
  34. package/dist/commands/rollback.d.ts +13 -0
  35. package/dist/commands/rollback.d.ts.map +1 -0
  36. package/dist/commands/rollback.js +186 -0
  37. package/dist/commands/rollback.js.map +1 -0
  38. package/dist/commands/setup-config.d.ts +6 -0
  39. package/dist/commands/setup-config.d.ts.map +1 -0
  40. package/dist/commands/setup-config.js +663 -0
  41. package/dist/commands/setup-config.js.map +1 -0
  42. package/dist/commands/setup-project.d.ts +6 -0
  43. package/dist/commands/setup-project.d.ts.map +1 -0
  44. package/dist/commands/setup-project.js +361 -0
  45. package/dist/commands/setup-project.js.map +1 -0
  46. package/dist/commands/setup.d.ts +3 -0
  47. package/dist/commands/setup.d.ts.map +1 -0
  48. package/dist/commands/setup.js +293 -0
  49. package/dist/commands/setup.js.map +1 -0
  50. package/dist/commands/status.d.ts +51 -0
  51. package/dist/commands/status.d.ts.map +1 -0
  52. package/dist/commands/status.js +182 -0
  53. package/dist/commands/status.js.map +1 -0
  54. package/dist/commands/uninstall.d.ts +3 -0
  55. package/dist/commands/uninstall.d.ts.map +1 -0
  56. package/dist/commands/uninstall.js +173 -0
  57. package/dist/commands/uninstall.js.map +1 -0
  58. package/dist/commands/update.d.ts +10 -0
  59. package/dist/commands/update.d.ts.map +1 -0
  60. package/dist/commands/update.js +435 -0
  61. package/dist/commands/update.js.map +1 -0
  62. package/dist/commands/worktree.d.ts +30 -0
  63. package/dist/commands/worktree.d.ts.map +1 -0
  64. package/dist/commands/worktree.js +262 -0
  65. package/dist/commands/worktree.js.map +1 -0
  66. package/dist/generator/claude-cli.d.ts +24 -0
  67. package/dist/generator/claude-cli.d.ts.map +1 -0
  68. package/dist/generator/claude-cli.js +239 -0
  69. package/dist/generator/claude-cli.js.map +1 -0
  70. package/dist/generator/prompt-builder.d.ts +7 -0
  71. package/dist/generator/prompt-builder.d.ts.map +1 -0
  72. package/dist/generator/prompt-builder.js +144 -0
  73. package/dist/generator/prompt-builder.js.map +1 -0
  74. package/dist/index.d.ts +36 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +45 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/mcp/embeddings.d.ts +53 -0
  79. package/dist/mcp/embeddings.d.ts.map +1 -0
  80. package/dist/mcp/embeddings.js +68 -0
  81. package/dist/mcp/embeddings.js.map +1 -0
  82. package/dist/mcp/hybrid-search.d.ts +25 -0
  83. package/dist/mcp/hybrid-search.d.ts.map +1 -0
  84. package/dist/mcp/hybrid-search.js +72 -0
  85. package/dist/mcp/hybrid-search.js.map +1 -0
  86. package/dist/mcp/knowledge-server.d.ts +4 -0
  87. package/dist/mcp/knowledge-server.d.ts.map +1 -0
  88. package/dist/mcp/knowledge-server.js +294 -0
  89. package/dist/mcp/knowledge-server.js.map +1 -0
  90. package/dist/mcp/knowledge-utils.d.ts +65 -0
  91. package/dist/mcp/knowledge-utils.d.ts.map +1 -0
  92. package/dist/mcp/knowledge-utils.js +207 -0
  93. package/dist/mcp/knowledge-utils.js.map +1 -0
  94. package/dist/mcp/search-factory.d.ts +9 -0
  95. package/dist/mcp/search-factory.d.ts.map +1 -0
  96. package/dist/mcp/search-factory.js +23 -0
  97. package/dist/mcp/search-factory.js.map +1 -0
  98. package/dist/mcp/search-index.d.ts +45 -0
  99. package/dist/mcp/search-index.d.ts.map +1 -0
  100. package/dist/mcp/search-index.js +2 -0
  101. package/dist/mcp/search-index.js.map +1 -0
  102. package/dist/mcp/search-minisearch.d.ts +46 -0
  103. package/dist/mcp/search-minisearch.d.ts.map +1 -0
  104. package/dist/mcp/search-minisearch.js +99 -0
  105. package/dist/mcp/search-minisearch.js.map +1 -0
  106. package/dist/mcp/search-sqlite.d.ts +30 -0
  107. package/dist/mcp/search-sqlite.d.ts.map +1 -0
  108. package/dist/mcp/search-sqlite.js +188 -0
  109. package/dist/mcp/search-sqlite.js.map +1 -0
  110. package/dist/mcp/vector-store.d.ts +52 -0
  111. package/dist/mcp/vector-store.d.ts.map +1 -0
  112. package/dist/mcp/vector-store.js +183 -0
  113. package/dist/mcp/vector-store.js.map +1 -0
  114. package/dist/scaffold/copy-core-agents.d.ts +2 -0
  115. package/dist/scaffold/copy-core-agents.d.ts.map +1 -0
  116. package/dist/scaffold/copy-core-agents.js +90 -0
  117. package/dist/scaffold/copy-core-agents.js.map +1 -0
  118. package/dist/scaffold/create-claude-settings.d.ts +40 -0
  119. package/dist/scaffold/create-claude-settings.d.ts.map +1 -0
  120. package/dist/scaffold/create-claude-settings.js +422 -0
  121. package/dist/scaffold/create-claude-settings.js.map +1 -0
  122. package/dist/scaffold/create-config.d.ts +14 -0
  123. package/dist/scaffold/create-config.d.ts.map +1 -0
  124. package/dist/scaffold/create-config.js +199 -0
  125. package/dist/scaffold/create-config.js.map +1 -0
  126. package/dist/scaffold/create-project-description.d.ts +12 -0
  127. package/dist/scaffold/create-project-description.d.ts.map +1 -0
  128. package/dist/scaffold/create-project-description.js +104 -0
  129. package/dist/scaffold/create-project-description.js.map +1 -0
  130. package/dist/scaffold/create-structure.d.ts +2 -0
  131. package/dist/scaffold/create-structure.d.ts.map +1 -0
  132. package/dist/scaffold/create-structure.js +146 -0
  133. package/dist/scaffold/create-structure.js.map +1 -0
  134. package/dist/types/dependency-analysis.d.ts +11 -0
  135. package/dist/types/dependency-analysis.d.ts.map +1 -0
  136. package/dist/types/dependency-analysis.js +2 -0
  137. package/dist/types/dependency-analysis.js.map +1 -0
  138. package/dist/types/index.d.ts +526 -0
  139. package/dist/types/index.d.ts.map +1 -0
  140. package/dist/types/index.js +3 -0
  141. package/dist/types/index.js.map +1 -0
  142. package/dist/types/task.d.ts +25 -0
  143. package/dist/types/task.d.ts.map +1 -0
  144. package/dist/types/task.js +3 -0
  145. package/dist/types/task.js.map +1 -0
  146. package/dist/utils/analyze-files.d.ts +7 -0
  147. package/dist/utils/analyze-files.d.ts.map +1 -0
  148. package/dist/utils/analyze-files.js +27 -0
  149. package/dist/utils/analyze-files.js.map +1 -0
  150. package/dist/utils/backup.d.ts +102 -0
  151. package/dist/utils/backup.d.ts.map +1 -0
  152. package/dist/utils/backup.js +352 -0
  153. package/dist/utils/backup.js.map +1 -0
  154. package/dist/utils/ci-provider.d.ts +23 -0
  155. package/dist/utils/ci-provider.d.ts.map +1 -0
  156. package/dist/utils/ci-provider.js +525 -0
  157. package/dist/utils/ci-provider.js.map +1 -0
  158. package/dist/utils/ci-status.d.ts +57 -0
  159. package/dist/utils/ci-status.d.ts.map +1 -0
  160. package/dist/utils/ci-status.js +349 -0
  161. package/dist/utils/ci-status.js.map +1 -0
  162. package/dist/utils/dependency-analysis.d.ts +34 -0
  163. package/dist/utils/dependency-analysis.d.ts.map +1 -0
  164. package/dist/utils/dependency-analysis.js +298 -0
  165. package/dist/utils/dependency-analysis.js.map +1 -0
  166. package/dist/utils/detect-git.d.ts +57 -0
  167. package/dist/utils/detect-git.d.ts.map +1 -0
  168. package/dist/utils/detect-git.js +439 -0
  169. package/dist/utils/detect-git.js.map +1 -0
  170. package/dist/utils/detect-mcp.d.ts +32 -0
  171. package/dist/utils/detect-mcp.d.ts.map +1 -0
  172. package/dist/utils/detect-mcp.js +178 -0
  173. package/dist/utils/detect-mcp.js.map +1 -0
  174. package/dist/utils/detect-project.d.ts +3 -0
  175. package/dist/utils/detect-project.d.ts.map +1 -0
  176. package/dist/utils/detect-project.js +155 -0
  177. package/dist/utils/detect-project.js.map +1 -0
  178. package/dist/utils/file-comparison.d.ts +89 -0
  179. package/dist/utils/file-comparison.d.ts.map +1 -0
  180. package/dist/utils/file-comparison.js +301 -0
  181. package/dist/utils/file-comparison.js.map +1 -0
  182. package/dist/utils/file-merger.d.ts +74 -0
  183. package/dist/utils/file-merger.d.ts.map +1 -0
  184. package/dist/utils/file-merger.js +350 -0
  185. package/dist/utils/file-merger.js.map +1 -0
  186. package/dist/utils/logger.d.ts +26 -0
  187. package/dist/utils/logger.d.ts.map +1 -0
  188. package/dist/utils/logger.js +72 -0
  189. package/dist/utils/logger.js.map +1 -0
  190. package/dist/utils/managed-process.d.ts +109 -0
  191. package/dist/utils/managed-process.d.ts.map +1 -0
  192. package/dist/utils/managed-process.js +481 -0
  193. package/dist/utils/managed-process.js.map +1 -0
  194. package/dist/utils/merge-claude-settings.d.ts +65 -0
  195. package/dist/utils/merge-claude-settings.d.ts.map +1 -0
  196. package/dist/utils/merge-claude-settings.js +133 -0
  197. package/dist/utils/merge-claude-settings.js.map +1 -0
  198. package/dist/utils/migration.d.ts +74 -0
  199. package/dist/utils/migration.d.ts.map +1 -0
  200. package/dist/utils/migration.js +345 -0
  201. package/dist/utils/migration.js.map +1 -0
  202. package/dist/utils/process-health.d.ts +51 -0
  203. package/dist/utils/process-health.d.ts.map +1 -0
  204. package/dist/utils/process-health.js +123 -0
  205. package/dist/utils/process-health.js.map +1 -0
  206. package/dist/utils/process-registry.d.ts +20 -0
  207. package/dist/utils/process-registry.d.ts.map +1 -0
  208. package/dist/utils/process-registry.js +151 -0
  209. package/dist/utils/process-registry.js.map +1 -0
  210. package/dist/utils/process-tree.d.ts +51 -0
  211. package/dist/utils/process-tree.d.ts.map +1 -0
  212. package/dist/utils/process-tree.js +499 -0
  213. package/dist/utils/process-tree.js.map +1 -0
  214. package/dist/utils/repair-mcp-config.d.ts +15 -0
  215. package/dist/utils/repair-mcp-config.d.ts.map +1 -0
  216. package/dist/utils/repair-mcp-config.js +129 -0
  217. package/dist/utils/repair-mcp-config.js.map +1 -0
  218. package/dist/utils/task-lifecycle.d.ts +60 -0
  219. package/dist/utils/task-lifecycle.d.ts.map +1 -0
  220. package/dist/utils/task-lifecycle.js +310 -0
  221. package/dist/utils/task-lifecycle.js.map +1 -0
  222. package/dist/utils/update-agent-mcp.d.ts +7 -0
  223. package/dist/utils/update-agent-mcp.d.ts.map +1 -0
  224. package/dist/utils/update-agent-mcp.js +115 -0
  225. package/dist/utils/update-agent-mcp.js.map +1 -0
  226. package/dist/utils/update-agent-templates.d.ts +6 -0
  227. package/dist/utils/update-agent-templates.d.ts.map +1 -0
  228. package/dist/utils/update-agent-templates.js +56 -0
  229. package/dist/utils/update-agent-templates.js.map +1 -0
  230. package/dist/utils/update-config-ci.d.ts +7 -0
  231. package/dist/utils/update-config-ci.d.ts.map +1 -0
  232. package/dist/utils/update-config-ci.js +72 -0
  233. package/dist/utils/update-config-ci.js.map +1 -0
  234. package/dist/utils/update-config-git.d.ts +18 -0
  235. package/dist/utils/update-config-git.d.ts.map +1 -0
  236. package/dist/utils/update-config-git.js +146 -0
  237. package/dist/utils/update-config-git.js.map +1 -0
  238. package/dist/utils/update-config-mcp.d.ts +7 -0
  239. package/dist/utils/update-config-mcp.d.ts.map +1 -0
  240. package/dist/utils/update-config-mcp.js +98 -0
  241. package/dist/utils/update-config-mcp.js.map +1 -0
  242. package/dist/utils/validate-config.d.ts +3 -0
  243. package/dist/utils/validate-config.d.ts.map +1 -0
  244. package/dist/utils/validate-config.js +109 -0
  245. package/dist/utils/validate-config.js.map +1 -0
  246. package/dist/utils/version-tracker.d.ts +130 -0
  247. package/dist/utils/version-tracker.d.ts.map +1 -0
  248. package/dist/utils/version-tracker.js +298 -0
  249. package/dist/utils/version-tracker.js.map +1 -0
  250. package/dist/utils/worktree.d.ts +68 -0
  251. package/dist/utils/worktree.d.ts.map +1 -0
  252. package/dist/utils/worktree.js +446 -0
  253. package/dist/utils/worktree.js.map +1 -0
  254. package/package.json +77 -0
  255. package/templates/ARCHAI_README.md +329 -0
  256. package/templates/CLAUDE.md +67 -0
  257. package/templates/PROMPTS.md +506 -0
  258. package/templates/core-agents/boss-agent.md +671 -0
  259. package/templates/core-agents/cleanup-agent.md +145 -0
  260. package/templates/core-agents/code-reviewer.md +175 -0
  261. package/templates/core-agents/critical-reviewer.md +117 -0
  262. package/templates/core-agents/deep-analyst.md +216 -0
  263. package/templates/core-agents/finalization-agent.md +252 -0
  264. package/templates/core-agents/git-coordinator.md +240 -0
  265. package/templates/core-agents/implementation-agent.md +151 -0
  266. package/templates/core-agents/maestro-agent.md +413 -0
  267. package/templates/core-agents/maestro-headless-agent.md +422 -0
  268. package/templates/core-agents/plan-validator.md +198 -0
  269. package/templates/core-agents/quick-fix.md +56 -0
  270. package/templates/core-agents/routing-templates.md +338 -0
  271. package/templates/core-agents/task-orchestrator.md +143 -0
  272. package/templates/core-agents/task-prep.md +202 -0
  273. package/templates/core-agents/tdd-designer.md +143 -0
  274. package/templates/specialist-meta.md +275 -0
@@ -0,0 +1,661 @@
1
+ import { execFileSync } from 'child_process';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import chalk from 'chalk';
6
+ import { logger } from '../utils/logger.js';
7
+ import { validateGroupName, getGroupPath, getGroupJsonPath } from './knowledge.js';
8
+ const GROUPS_BASE = path.join(os.homedir(), '.archai', 'knowledge', 'groups');
9
+ const CACHE_FILES = ['.index.db', '.index.db-wal', '.index.db-shm', '.vectors.json'];
10
+ const GITIGNORE_ENTRIES = ['.index.db', '.index.db-wal', '.index.db-shm', '.vectors.json'];
11
+ // --- Git Utilities ---
12
+ /**
13
+ * Execute a git command in the given directory.
14
+ * Uses execFileSync (no shell) for cross-platform safety.
15
+ */
16
+ export function execGit(args, cwd, extraEnv) {
17
+ const result = execFileSync('git', args, {
18
+ cwd,
19
+ encoding: 'utf-8',
20
+ stdio: ['pipe', 'pipe', 'pipe'],
21
+ timeout: 30000,
22
+ env: extraEnv ? { ...process.env, ...extraEnv } : undefined,
23
+ });
24
+ return result.trim();
25
+ }
26
+ /**
27
+ * Check if a directory is a git repository.
28
+ */
29
+ export function isGitRepo(dirPath) {
30
+ try {
31
+ execGit(['rev-parse', '--is-inside-work-tree'], dirPath);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Ensure the .gitignore in a group directory contains all required entries.
40
+ */
41
+ export async function ensureGitignore(groupPath) {
42
+ const gitignorePath = path.join(groupPath, '.gitignore');
43
+ let content = '';
44
+ if (await fs.pathExists(gitignorePath)) {
45
+ content = await fs.readFile(gitignorePath, 'utf-8');
46
+ }
47
+ const lines = content.split('\n').map(l => l.trim());
48
+ const missing = GITIGNORE_ENTRIES.filter(entry => !lines.includes(entry));
49
+ if (missing.length > 0) {
50
+ const suffix = (content.endsWith('\n') || content === '') ? '' : '\n';
51
+ const addition = missing.join('\n') + '\n';
52
+ await fs.writeFile(gitignorePath, content + suffix + addition);
53
+ }
54
+ }
55
+ /**
56
+ * Ensure git user config is set for commits in the given repo.
57
+ * Sets local defaults if global config is missing.
58
+ */
59
+ function ensureGitUser(cwd) {
60
+ try {
61
+ execGit(['config', 'user.name'], cwd);
62
+ }
63
+ catch {
64
+ execGit(['config', 'user.name', 'archai-sync'], cwd);
65
+ }
66
+ try {
67
+ execGit(['config', 'user.email'], cwd);
68
+ }
69
+ catch {
70
+ execGit(['config', 'user.email', 'archai@local'], cwd);
71
+ }
72
+ }
73
+ // --- Conflict Resolution ---
74
+ /**
75
+ * Extract one side of a git merge conflict from file content.
76
+ * Returns the content between the conflict markers for the specified side,
77
+ * or null if no valid conflict markers are found.
78
+ */
79
+ export function extractConflictSide(content, side) {
80
+ const lines = content.split('\n');
81
+ let startIdx = -1;
82
+ let separatorIdx = -1;
83
+ let endIdx = -1;
84
+ for (let i = 0; i < lines.length; i++) {
85
+ if (lines[i].startsWith('<<<<<<<')) {
86
+ startIdx = i;
87
+ }
88
+ else if (lines[i].startsWith('=======') && startIdx >= 0) {
89
+ separatorIdx = i;
90
+ }
91
+ else if (lines[i].startsWith('>>>>>>>') && separatorIdx >= 0) {
92
+ endIdx = i;
93
+ break;
94
+ }
95
+ }
96
+ if (startIdx < 0 || separatorIdx < 0 || endIdx < 0) {
97
+ return null;
98
+ }
99
+ if (side === 'ours') {
100
+ return lines.slice(startIdx + 1, separatorIdx).join('\n');
101
+ }
102
+ else {
103
+ return lines.slice(separatorIdx + 1, endIdx).join('\n');
104
+ }
105
+ }
106
+ /**
107
+ * Auto-resolve group.json merge conflicts by union-merging the members array.
108
+ * Returns true if conflict was resolved, false if it could not be auto-resolved.
109
+ */
110
+ export async function resolveGroupJsonConflict(groupPath) {
111
+ const conflictPath = path.join(groupPath, 'group.json');
112
+ let content;
113
+ try {
114
+ content = await fs.readFile(conflictPath, 'utf-8');
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ if (!content.includes('<<<<<<<'))
120
+ return false;
121
+ const ours = extractConflictSide(content, 'ours');
122
+ const theirs = extractConflictSide(content, 'theirs');
123
+ if (!ours || !theirs)
124
+ return false;
125
+ try {
126
+ const oursJson = JSON.parse(ours);
127
+ const theirsJson = JSON.parse(theirs);
128
+ // Union merge members, deduplicate by normalized path
129
+ const allMembers = new Map();
130
+ for (const m of [...oursJson.members, ...theirsJson.members]) {
131
+ allMembers.set(path.resolve(m), m);
132
+ }
133
+ const resolved = {
134
+ ...theirsJson,
135
+ members: [...allMembers.values()],
136
+ remote: oursJson.remote || theirsJson.remote,
137
+ };
138
+ // Atomic write
139
+ const tmpPath = conflictPath + '.tmp.' + Date.now();
140
+ await fs.writeFile(tmpPath, JSON.stringify(resolved, null, 2) + '\n');
141
+ await fs.rename(tmpPath, conflictPath);
142
+ // Stage the resolution
143
+ execGit(['add', 'group.json'], groupPath);
144
+ return true;
145
+ }
146
+ catch {
147
+ return false;
148
+ }
149
+ }
150
+ // --- Index Invalidation ---
151
+ /**
152
+ * Delete search index and vector store cache files.
153
+ * These are rebuilt automatically by the MCP server on next startup.
154
+ */
155
+ export async function invalidateSearchIndices(groupPath) {
156
+ for (const file of CACHE_FILES) {
157
+ const filePath = path.join(groupPath, file);
158
+ try {
159
+ await fs.remove(filePath);
160
+ }
161
+ catch {
162
+ // ignore -- file may not exist
163
+ }
164
+ }
165
+ }
166
+ // --- Sync Status ---
167
+ /**
168
+ * Get the git sync status of a knowledge group.
169
+ */
170
+ export function getGitSyncStatus(groupPath) {
171
+ if (!isGitRepo(groupPath))
172
+ return { status: 'no-git' };
173
+ try {
174
+ execGit(['remote', 'get-url', 'origin'], groupPath);
175
+ }
176
+ catch {
177
+ return { status: 'no-remote' };
178
+ }
179
+ // Check for uncommitted changes
180
+ const statusOutput = execGit(['status', '--porcelain'], groupPath);
181
+ const dirty = statusOutput.length > 0;
182
+ // Fetch to compare (non-destructive)
183
+ try {
184
+ execGit(['fetch', 'origin', '--quiet'], groupPath);
185
+ }
186
+ catch {
187
+ // Offline -- can only report local state
188
+ return dirty ? { status: 'dirty', dirty: true } : { status: 'clean' };
189
+ }
190
+ // Count ahead/behind
191
+ try {
192
+ const counts = execGit(['rev-list', '--left-right', '--count', 'HEAD...origin/main'], groupPath);
193
+ const parts = counts.split(/\s+/);
194
+ const ahead = parseInt(parts[0], 10) || 0;
195
+ const behind = parseInt(parts[1], 10) || 0;
196
+ if (dirty)
197
+ return { status: 'dirty', dirty: true, ahead, behind };
198
+ if (ahead > 0 && behind > 0)
199
+ return { status: 'diverged', ahead, behind };
200
+ if (ahead > 0)
201
+ return { status: 'ahead', ahead, behind: 0 };
202
+ if (behind > 0)
203
+ return { status: 'behind', ahead: 0, behind };
204
+ return { status: 'clean', ahead: 0, behind: 0 };
205
+ }
206
+ catch {
207
+ // No commits on remote yet or other issue
208
+ return dirty ? { status: 'dirty', dirty: true } : { status: 'clean' };
209
+ }
210
+ }
211
+ // --- Core Sync Operation ---
212
+ /**
213
+ * Perform a full sync operation on a knowledge group:
214
+ * stage -> commit -> pull --rebase -> push -> invalidate indices.
215
+ */
216
+ export async function syncGroup(groupPath, name) {
217
+ // 1. Check remote is configured
218
+ try {
219
+ execGit(['remote', 'get-url', 'origin'], groupPath);
220
+ }
221
+ catch {
222
+ return {
223
+ success: false, pushed: false, pulled: false,
224
+ error: 'No remote configured for group "' + name + '".',
225
+ };
226
+ }
227
+ // 2. Stage all changes
228
+ execGit(['add', '-A'], groupPath);
229
+ // 3. Commit if there are staged changes
230
+ try {
231
+ execGit(['diff', '--cached', '--quiet'], groupPath);
232
+ // No error = no changes staged
233
+ }
234
+ catch {
235
+ // Exit code 1 = there are changes
236
+ ensureGitUser(groupPath);
237
+ const timestamp = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
238
+ execGit(['commit', '-m', 'knowledge sync: ' + timestamp], groupPath);
239
+ }
240
+ // 4. Pull with rebase
241
+ let pulled = false;
242
+ try {
243
+ execGit(['pull', '--rebase', 'origin', 'main'], groupPath);
244
+ pulled = true;
245
+ }
246
+ catch (err) {
247
+ const errMsg = getErrorMessage(err);
248
+ if (isNetworkError(errMsg)) {
249
+ return {
250
+ success: false, pushed: false, pulled: false,
251
+ error: 'Sync failed: could not reach remote. Local changes preserved.',
252
+ };
253
+ }
254
+ if (isConflictError(errMsg)) {
255
+ // Try auto-resolve group.json conflicts
256
+ const resolved = await resolveGroupJsonConflict(groupPath);
257
+ if (resolved) {
258
+ try {
259
+ execGit(['rebase', '--continue'], groupPath, {
260
+ GIT_EDITOR: 'true',
261
+ GIT_SEQUENCE_EDITOR: 'true',
262
+ });
263
+ pulled = true;
264
+ }
265
+ catch {
266
+ try {
267
+ execGit(['rebase', '--abort'], groupPath);
268
+ }
269
+ catch { /* ignore */ }
270
+ return {
271
+ success: false, pushed: false, pulled: false,
272
+ error: 'Failed to continue rebase after conflict resolution.',
273
+ };
274
+ }
275
+ }
276
+ else {
277
+ // Identify conflicting files
278
+ let conflictFiles = [];
279
+ try {
280
+ const conflictOutput = execGit(['diff', '--name-only', '--diff-filter=U'], groupPath);
281
+ conflictFiles = conflictOutput.split('\n').filter(Boolean);
282
+ }
283
+ catch { /* ignore */ }
284
+ try {
285
+ execGit(['rebase', '--abort'], groupPath);
286
+ }
287
+ catch { /* ignore */ }
288
+ return {
289
+ success: false, pushed: false, pulled: false,
290
+ conflictFiles,
291
+ error: 'Merge conflicts could not be auto-resolved.',
292
+ };
293
+ }
294
+ }
295
+ else {
296
+ return {
297
+ success: false, pushed: false, pulled: false,
298
+ error: 'Pull failed: ' + errMsg,
299
+ };
300
+ }
301
+ }
302
+ // 5. Push
303
+ let pushed = false;
304
+ try {
305
+ execGit(['push', 'origin', 'main'], groupPath);
306
+ pushed = true;
307
+ }
308
+ catch (err) {
309
+ const errMsg = getErrorMessage(err);
310
+ if (isNetworkError(errMsg)) {
311
+ return {
312
+ success: false, pushed: false, pulled,
313
+ error: 'Sync failed: could not reach remote. Local changes preserved.',
314
+ };
315
+ }
316
+ return {
317
+ success: false, pushed: false, pulled,
318
+ error: 'Push failed: ' + errMsg,
319
+ };
320
+ }
321
+ // 6. Invalidate search indices
322
+ await invalidateSearchIndices(groupPath);
323
+ return { success: true, pushed, pulled };
324
+ }
325
+ function getErrorMessage(err) {
326
+ if (err instanceof Error) {
327
+ // execFileSync errors include stderr in the message or as a property
328
+ const execErr = err;
329
+ if (execErr.stderr) {
330
+ return typeof execErr.stderr === 'string' ? execErr.stderr : execErr.stderr.toString();
331
+ }
332
+ return err.message;
333
+ }
334
+ return String(err);
335
+ }
336
+ function isNetworkError(msg) {
337
+ const patterns = [
338
+ 'Could not resolve host',
339
+ 'Connection refused',
340
+ 'fatal: unable to access',
341
+ 'Could not read from remote',
342
+ 'Network is unreachable',
343
+ 'Connection timed out',
344
+ 'No route to host',
345
+ ];
346
+ return patterns.some(p => msg.includes(p));
347
+ }
348
+ function isConflictError(msg) {
349
+ return msg.includes('CONFLICT') || msg.includes('conflict') || msg.includes('could not apply');
350
+ }
351
+ // --- CLI Commands ---
352
+ /**
353
+ * Add a git remote to a knowledge group for team sharing.
354
+ * Initializes git repo if needed, commits existing content, and pushes.
355
+ */
356
+ export async function knowledgeRemote(name, url) {
357
+ const validation = validateGroupName(name);
358
+ if (!validation.valid) {
359
+ logger.error(validation.error);
360
+ process.exitCode = 1;
361
+ return;
362
+ }
363
+ const groupPath = getGroupPath(name);
364
+ const jsonPath = getGroupJsonPath(name);
365
+ if (!(await fs.pathExists(jsonPath))) {
366
+ logger.error('Knowledge group "' + name + '" does not exist.');
367
+ logger.info('Create it first: archai knowledge create ' + name);
368
+ process.exitCode = 1;
369
+ return;
370
+ }
371
+ // Initialize git repo if not already
372
+ if (!isGitRepo(groupPath)) {
373
+ execGit(['init'], groupPath);
374
+ execGit(['branch', '-M', 'main'], groupPath);
375
+ logger.info('Initialized git repository.');
376
+ }
377
+ // Ensure .gitignore is correct
378
+ await ensureGitignore(groupPath);
379
+ // Add remote (check if already exists)
380
+ try {
381
+ const existingUrl = execGit(['remote', 'get-url', 'origin'], groupPath);
382
+ if (existingUrl === url) {
383
+ logger.info('Remote "origin" already set to ' + url);
384
+ }
385
+ else {
386
+ execGit(['remote', 'set-url', 'origin', url], groupPath);
387
+ logger.info('Updated remote "origin" to ' + url);
388
+ }
389
+ }
390
+ catch {
391
+ execGit(['remote', 'add', 'origin', url], groupPath);
392
+ }
393
+ // Update group.json with remote URL
394
+ const raw = await fs.readFile(jsonPath, 'utf-8');
395
+ const groupData = JSON.parse(raw);
396
+ if (groupData.remote !== url) {
397
+ groupData.remote = url;
398
+ const tmpPath = jsonPath + '.tmp.' + Date.now();
399
+ await fs.writeFile(tmpPath, JSON.stringify(groupData, null, 2) + '\n');
400
+ await fs.rename(tmpPath, jsonPath);
401
+ }
402
+ // Ensure git user is configured
403
+ ensureGitUser(groupPath);
404
+ // Initial commit of existing entries
405
+ execGit(['add', '-A'], groupPath);
406
+ try {
407
+ execGit(['diff', '--cached', '--quiet'], groupPath);
408
+ // No changes to commit
409
+ }
410
+ catch {
411
+ execGit(['commit', '-m', 'Initial knowledge base'], groupPath);
412
+ }
413
+ // Push to remote
414
+ try {
415
+ execGit(['push', '-u', 'origin', 'main'], groupPath);
416
+ logger.success('Remote configured and knowledge pushed to ' + url);
417
+ }
418
+ catch (err) {
419
+ const errMsg = getErrorMessage(err);
420
+ if (isNetworkError(errMsg)) {
421
+ logger.warn('Remote configured but push failed: could not reach remote.');
422
+ }
423
+ else {
424
+ logger.warn('Remote configured but push failed: ' + errMsg);
425
+ }
426
+ logger.info('Run "archai knowledge sync ' + name + '" to push later.');
427
+ }
428
+ }
429
+ /**
430
+ * Clone an existing knowledge group from a git remote.
431
+ */
432
+ export async function knowledgeCreateFrom(name, url) {
433
+ const validation = validateGroupName(name);
434
+ if (!validation.valid) {
435
+ logger.error(validation.error);
436
+ process.exitCode = 1;
437
+ return;
438
+ }
439
+ const groupPath = getGroupPath(name);
440
+ if (await fs.pathExists(groupPath)) {
441
+ logger.error('Knowledge group "' + name + '" already exists at ' + groupPath);
442
+ process.exitCode = 1;
443
+ return;
444
+ }
445
+ // Ensure parent directory exists
446
+ await fs.ensureDir(path.dirname(groupPath));
447
+ // Clone
448
+ try {
449
+ execFileSync('git', ['clone', url, groupPath], {
450
+ encoding: 'utf-8',
451
+ stdio: ['pipe', 'pipe', 'pipe'],
452
+ timeout: 60000,
453
+ });
454
+ }
455
+ catch (err) {
456
+ const errMsg = getErrorMessage(err);
457
+ logger.error('Failed to clone from ' + url + ': ' + errMsg);
458
+ process.exitCode = 1;
459
+ return;
460
+ }
461
+ // Verify cloned repo has a valid group.json
462
+ const jsonPath = getGroupJsonPath(name);
463
+ if (!(await fs.pathExists(jsonPath))) {
464
+ logger.error('Cloned repository does not contain a valid knowledge group (no group.json found).');
465
+ await fs.remove(groupPath);
466
+ process.exitCode = 1;
467
+ return;
468
+ }
469
+ try {
470
+ const raw = await fs.readFile(jsonPath, 'utf-8');
471
+ JSON.parse(raw);
472
+ }
473
+ catch {
474
+ logger.error('Cloned repository has an invalid group.json.');
475
+ await fs.remove(groupPath);
476
+ process.exitCode = 1;
477
+ return;
478
+ }
479
+ logger.success('Cloned knowledge group "' + name + '" from ' + url);
480
+ logger.info('Path: ' + groupPath);
481
+ console.log('');
482
+ console.log(chalk.gray(' Next steps:'));
483
+ console.log(chalk.gray(' cd ~/your-project && archai knowledge join ' + name));
484
+ console.log(chalk.gray(' archai knowledge sync ' + name + ' Sync with team'));
485
+ console.log('');
486
+ }
487
+ /**
488
+ * Sync one or all knowledge groups with their git remotes.
489
+ */
490
+ export async function knowledgeSync(name) {
491
+ if (name) {
492
+ const validation = validateGroupName(name);
493
+ if (!validation.valid) {
494
+ logger.error(validation.error);
495
+ process.exitCode = 1;
496
+ return;
497
+ }
498
+ const groupPath = getGroupPath(name);
499
+ const jsonPath = getGroupJsonPath(name);
500
+ if (!(await fs.pathExists(jsonPath))) {
501
+ logger.error('Knowledge group "' + name + '" does not exist.');
502
+ process.exitCode = 1;
503
+ return;
504
+ }
505
+ if (!isGitRepo(groupPath)) {
506
+ logger.warn('Group "' + name + '" is not a git repository. Set up a remote first:');
507
+ logger.info(' archai knowledge remote ' + name + ' <url>');
508
+ return;
509
+ }
510
+ const result = await syncGroup(groupPath, name);
511
+ displaySyncResult(name, result);
512
+ return;
513
+ }
514
+ // Sync all groups with remotes
515
+ if (!(await fs.pathExists(GROUPS_BASE))) {
516
+ logger.info('No knowledge groups found.');
517
+ return;
518
+ }
519
+ const entries = await fs.readdir(GROUPS_BASE, { withFileTypes: true });
520
+ let syncedCount = 0;
521
+ let skippedCount = 0;
522
+ for (const entry of entries) {
523
+ if (!entry.isDirectory())
524
+ continue;
525
+ const jsonPath = getGroupJsonPath(entry.name);
526
+ if (!(await fs.pathExists(jsonPath)))
527
+ continue;
528
+ const groupPath = getGroupPath(entry.name);
529
+ if (!isGitRepo(groupPath)) {
530
+ skippedCount++;
531
+ continue;
532
+ }
533
+ try {
534
+ execGit(['remote', 'get-url', 'origin'], groupPath);
535
+ }
536
+ catch {
537
+ skippedCount++;
538
+ continue;
539
+ }
540
+ const result = await syncGroup(groupPath, entry.name);
541
+ displaySyncResult(entry.name, result);
542
+ syncedCount++;
543
+ }
544
+ if (syncedCount === 0 && skippedCount > 0) {
545
+ logger.info('No groups with remotes found. ' + skippedCount + ' local-only group(s) skipped.');
546
+ }
547
+ else if (syncedCount === 0) {
548
+ logger.info('No knowledge groups found to sync.');
549
+ }
550
+ }
551
+ /**
552
+ * Display detailed information about a knowledge group.
553
+ */
554
+ export async function knowledgeInfo(name) {
555
+ const validation = validateGroupName(name);
556
+ if (!validation.valid) {
557
+ logger.error(validation.error);
558
+ process.exitCode = 1;
559
+ return;
560
+ }
561
+ const groupPath = getGroupPath(name);
562
+ const jsonPath = getGroupJsonPath(name);
563
+ if (!(await fs.pathExists(jsonPath))) {
564
+ logger.error('Knowledge group "' + name + '" does not exist.');
565
+ process.exitCode = 1;
566
+ return;
567
+ }
568
+ const raw = await fs.readFile(jsonPath, 'utf-8');
569
+ const groupData = JSON.parse(raw);
570
+ // Count entries per category
571
+ const categories = ['decisions', 'patterns', 'constraints', 'learnings', 'context'];
572
+ const categoryCounts = {};
573
+ let totalEntries = 0;
574
+ for (const cat of categories) {
575
+ const catDir = path.join(groupPath, cat);
576
+ let count = 0;
577
+ if (await fs.pathExists(catDir)) {
578
+ const files = await fs.readdir(catDir);
579
+ count = files.filter(f => f.endsWith('.md') && !f.startsWith('.')).length;
580
+ }
581
+ categoryCounts[cat] = count;
582
+ totalEntries += count;
583
+ }
584
+ // Get sync status
585
+ const syncStatus = getGitSyncStatus(groupPath);
586
+ // Get last commit date (proxy for last sync time)
587
+ let lastSyncTime = 'never';
588
+ if (isGitRepo(groupPath)) {
589
+ try {
590
+ lastSyncTime = execGit(['log', '-1', '--format=%ci'], groupPath);
591
+ }
592
+ catch {
593
+ lastSyncTime = 'no commits';
594
+ }
595
+ }
596
+ // Display
597
+ console.log('');
598
+ logger.section('Knowledge Group: ' + name);
599
+ console.log('');
600
+ console.log(' ' + chalk.gray('Name: ') + chalk.white(groupData.name));
601
+ console.log(' ' + chalk.gray('Created: ') + chalk.white(groupData.created));
602
+ console.log(' ' + chalk.gray('Members: ') + chalk.white(String(groupData.members.length)));
603
+ if (groupData.members.length > 0) {
604
+ for (const member of groupData.members) {
605
+ console.log(' ' + chalk.gray(' ') + chalk.gray(member));
606
+ }
607
+ }
608
+ console.log(' ' + chalk.gray('Remote: ') + (groupData.remote ? chalk.cyan(groupData.remote) : chalk.gray('not configured')));
609
+ console.log(' ' + chalk.gray('Last sync: ') + chalk.white(lastSyncTime));
610
+ console.log('');
611
+ // Entries by category
612
+ console.log(' ' + chalk.gray('Entries: ') + chalk.white(String(totalEntries) + ' total'));
613
+ for (const cat of categories) {
614
+ if (categoryCounts[cat] > 0) {
615
+ console.log(' ' + chalk.gray(' ') + chalk.gray(cat + ': ') + chalk.white(String(categoryCounts[cat])));
616
+ }
617
+ }
618
+ console.log('');
619
+ // Sync status
620
+ const statusLabel = formatSyncStatus(syncStatus);
621
+ console.log(' ' + chalk.gray('Sync: ') + statusLabel);
622
+ console.log('');
623
+ }
624
+ function formatSyncStatus(status) {
625
+ switch (status.status) {
626
+ case 'no-git':
627
+ return chalk.gray('not a git repo');
628
+ case 'no-remote':
629
+ return chalk.gray('no remote configured');
630
+ case 'clean':
631
+ return chalk.green('up to date');
632
+ case 'dirty':
633
+ return chalk.yellow('uncommitted changes');
634
+ case 'ahead':
635
+ return chalk.yellow('ahead by ' + status.ahead + ' commit(s)');
636
+ case 'behind':
637
+ return chalk.yellow('behind by ' + status.behind + ' commit(s)');
638
+ case 'diverged':
639
+ return chalk.red('diverged (ahead ' + status.ahead + ', behind ' + status.behind + ')');
640
+ default:
641
+ return chalk.gray('unknown');
642
+ }
643
+ }
644
+ function displaySyncResult(name, result) {
645
+ if (result.success) {
646
+ logger.success('Synced "' + name + '"');
647
+ }
648
+ else {
649
+ if (result.conflictFiles && result.conflictFiles.length > 0) {
650
+ logger.warn('Sync of "' + name + '" failed: ' + result.error);
651
+ logger.info('Conflicting files:');
652
+ for (const f of result.conflictFiles) {
653
+ console.log(' ' + chalk.yellow(f));
654
+ }
655
+ }
656
+ else {
657
+ logger.warn('Sync of "' + name + '": ' + result.error);
658
+ }
659
+ }
660
+ }
661
+ //# sourceMappingURL=knowledge-sync.js.map