@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.
- package/README.md +270 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +188 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/commands/cleanup.d.ts +21 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +380 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/dispatch.d.ts +13 -0
- package/dist/commands/dispatch.d.ts.map +1 -0
- package/dist/commands/dispatch.js +85 -0
- package/dist/commands/dispatch.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +155 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +167 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +711 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/knowledge-sync.d.ts +69 -0
- package/dist/commands/knowledge-sync.d.ts.map +1 -0
- package/dist/commands/knowledge-sync.js +661 -0
- package/dist/commands/knowledge-sync.js.map +1 -0
- package/dist/commands/knowledge.d.ts +35 -0
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/commands/knowledge.js +254 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/rollback.d.ts +13 -0
- package/dist/commands/rollback.d.ts.map +1 -0
- package/dist/commands/rollback.js +186 -0
- package/dist/commands/rollback.js.map +1 -0
- package/dist/commands/setup-config.d.ts +6 -0
- package/dist/commands/setup-config.d.ts.map +1 -0
- package/dist/commands/setup-config.js +663 -0
- package/dist/commands/setup-config.js.map +1 -0
- package/dist/commands/setup-project.d.ts +6 -0
- package/dist/commands/setup-project.d.ts.map +1 -0
- package/dist/commands/setup-project.js +361 -0
- package/dist/commands/setup-project.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +293 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +51 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +182 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/uninstall.d.ts +3 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +173 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +435 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/worktree.d.ts +30 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +262 -0
- package/dist/commands/worktree.js.map +1 -0
- package/dist/generator/claude-cli.d.ts +24 -0
- package/dist/generator/claude-cli.d.ts.map +1 -0
- package/dist/generator/claude-cli.js +239 -0
- package/dist/generator/claude-cli.js.map +1 -0
- package/dist/generator/prompt-builder.d.ts +7 -0
- package/dist/generator/prompt-builder.d.ts.map +1 -0
- package/dist/generator/prompt-builder.js +144 -0
- package/dist/generator/prompt-builder.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/embeddings.d.ts +53 -0
- package/dist/mcp/embeddings.d.ts.map +1 -0
- package/dist/mcp/embeddings.js +68 -0
- package/dist/mcp/embeddings.js.map +1 -0
- package/dist/mcp/hybrid-search.d.ts +25 -0
- package/dist/mcp/hybrid-search.d.ts.map +1 -0
- package/dist/mcp/hybrid-search.js +72 -0
- package/dist/mcp/hybrid-search.js.map +1 -0
- package/dist/mcp/knowledge-server.d.ts +4 -0
- package/dist/mcp/knowledge-server.d.ts.map +1 -0
- package/dist/mcp/knowledge-server.js +294 -0
- package/dist/mcp/knowledge-server.js.map +1 -0
- package/dist/mcp/knowledge-utils.d.ts +65 -0
- package/dist/mcp/knowledge-utils.d.ts.map +1 -0
- package/dist/mcp/knowledge-utils.js +207 -0
- package/dist/mcp/knowledge-utils.js.map +1 -0
- package/dist/mcp/search-factory.d.ts +9 -0
- package/dist/mcp/search-factory.d.ts.map +1 -0
- package/dist/mcp/search-factory.js +23 -0
- package/dist/mcp/search-factory.js.map +1 -0
- package/dist/mcp/search-index.d.ts +45 -0
- package/dist/mcp/search-index.d.ts.map +1 -0
- package/dist/mcp/search-index.js +2 -0
- package/dist/mcp/search-index.js.map +1 -0
- package/dist/mcp/search-minisearch.d.ts +46 -0
- package/dist/mcp/search-minisearch.d.ts.map +1 -0
- package/dist/mcp/search-minisearch.js +99 -0
- package/dist/mcp/search-minisearch.js.map +1 -0
- package/dist/mcp/search-sqlite.d.ts +30 -0
- package/dist/mcp/search-sqlite.d.ts.map +1 -0
- package/dist/mcp/search-sqlite.js +188 -0
- package/dist/mcp/search-sqlite.js.map +1 -0
- package/dist/mcp/vector-store.d.ts +52 -0
- package/dist/mcp/vector-store.d.ts.map +1 -0
- package/dist/mcp/vector-store.js +183 -0
- package/dist/mcp/vector-store.js.map +1 -0
- package/dist/scaffold/copy-core-agents.d.ts +2 -0
- package/dist/scaffold/copy-core-agents.d.ts.map +1 -0
- package/dist/scaffold/copy-core-agents.js +90 -0
- package/dist/scaffold/copy-core-agents.js.map +1 -0
- package/dist/scaffold/create-claude-settings.d.ts +40 -0
- package/dist/scaffold/create-claude-settings.d.ts.map +1 -0
- package/dist/scaffold/create-claude-settings.js +422 -0
- package/dist/scaffold/create-claude-settings.js.map +1 -0
- package/dist/scaffold/create-config.d.ts +14 -0
- package/dist/scaffold/create-config.d.ts.map +1 -0
- package/dist/scaffold/create-config.js +199 -0
- package/dist/scaffold/create-config.js.map +1 -0
- package/dist/scaffold/create-project-description.d.ts +12 -0
- package/dist/scaffold/create-project-description.d.ts.map +1 -0
- package/dist/scaffold/create-project-description.js +104 -0
- package/dist/scaffold/create-project-description.js.map +1 -0
- package/dist/scaffold/create-structure.d.ts +2 -0
- package/dist/scaffold/create-structure.d.ts.map +1 -0
- package/dist/scaffold/create-structure.js +146 -0
- package/dist/scaffold/create-structure.js.map +1 -0
- package/dist/types/dependency-analysis.d.ts +11 -0
- package/dist/types/dependency-analysis.d.ts.map +1 -0
- package/dist/types/dependency-analysis.js +2 -0
- package/dist/types/dependency-analysis.js.map +1 -0
- package/dist/types/index.d.ts +526 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/task.d.ts +25 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +3 -0
- package/dist/types/task.js.map +1 -0
- package/dist/utils/analyze-files.d.ts +7 -0
- package/dist/utils/analyze-files.d.ts.map +1 -0
- package/dist/utils/analyze-files.js +27 -0
- package/dist/utils/analyze-files.js.map +1 -0
- package/dist/utils/backup.d.ts +102 -0
- package/dist/utils/backup.d.ts.map +1 -0
- package/dist/utils/backup.js +352 -0
- package/dist/utils/backup.js.map +1 -0
- package/dist/utils/ci-provider.d.ts +23 -0
- package/dist/utils/ci-provider.d.ts.map +1 -0
- package/dist/utils/ci-provider.js +525 -0
- package/dist/utils/ci-provider.js.map +1 -0
- package/dist/utils/ci-status.d.ts +57 -0
- package/dist/utils/ci-status.d.ts.map +1 -0
- package/dist/utils/ci-status.js +349 -0
- package/dist/utils/ci-status.js.map +1 -0
- package/dist/utils/dependency-analysis.d.ts +34 -0
- package/dist/utils/dependency-analysis.d.ts.map +1 -0
- package/dist/utils/dependency-analysis.js +298 -0
- package/dist/utils/dependency-analysis.js.map +1 -0
- package/dist/utils/detect-git.d.ts +57 -0
- package/dist/utils/detect-git.d.ts.map +1 -0
- package/dist/utils/detect-git.js +439 -0
- package/dist/utils/detect-git.js.map +1 -0
- package/dist/utils/detect-mcp.d.ts +32 -0
- package/dist/utils/detect-mcp.d.ts.map +1 -0
- package/dist/utils/detect-mcp.js +178 -0
- package/dist/utils/detect-mcp.js.map +1 -0
- package/dist/utils/detect-project.d.ts +3 -0
- package/dist/utils/detect-project.d.ts.map +1 -0
- package/dist/utils/detect-project.js +155 -0
- package/dist/utils/detect-project.js.map +1 -0
- package/dist/utils/file-comparison.d.ts +89 -0
- package/dist/utils/file-comparison.d.ts.map +1 -0
- package/dist/utils/file-comparison.js +301 -0
- package/dist/utils/file-comparison.js.map +1 -0
- package/dist/utils/file-merger.d.ts +74 -0
- package/dist/utils/file-merger.d.ts.map +1 -0
- package/dist/utils/file-merger.js +350 -0
- package/dist/utils/file-merger.js.map +1 -0
- package/dist/utils/logger.d.ts +26 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/managed-process.d.ts +109 -0
- package/dist/utils/managed-process.d.ts.map +1 -0
- package/dist/utils/managed-process.js +481 -0
- package/dist/utils/managed-process.js.map +1 -0
- package/dist/utils/merge-claude-settings.d.ts +65 -0
- package/dist/utils/merge-claude-settings.d.ts.map +1 -0
- package/dist/utils/merge-claude-settings.js +133 -0
- package/dist/utils/merge-claude-settings.js.map +1 -0
- package/dist/utils/migration.d.ts +74 -0
- package/dist/utils/migration.d.ts.map +1 -0
- package/dist/utils/migration.js +345 -0
- package/dist/utils/migration.js.map +1 -0
- package/dist/utils/process-health.d.ts +51 -0
- package/dist/utils/process-health.d.ts.map +1 -0
- package/dist/utils/process-health.js +123 -0
- package/dist/utils/process-health.js.map +1 -0
- package/dist/utils/process-registry.d.ts +20 -0
- package/dist/utils/process-registry.d.ts.map +1 -0
- package/dist/utils/process-registry.js +151 -0
- package/dist/utils/process-registry.js.map +1 -0
- package/dist/utils/process-tree.d.ts +51 -0
- package/dist/utils/process-tree.d.ts.map +1 -0
- package/dist/utils/process-tree.js +499 -0
- package/dist/utils/process-tree.js.map +1 -0
- package/dist/utils/repair-mcp-config.d.ts +15 -0
- package/dist/utils/repair-mcp-config.d.ts.map +1 -0
- package/dist/utils/repair-mcp-config.js +129 -0
- package/dist/utils/repair-mcp-config.js.map +1 -0
- package/dist/utils/task-lifecycle.d.ts +60 -0
- package/dist/utils/task-lifecycle.d.ts.map +1 -0
- package/dist/utils/task-lifecycle.js +310 -0
- package/dist/utils/task-lifecycle.js.map +1 -0
- package/dist/utils/update-agent-mcp.d.ts +7 -0
- package/dist/utils/update-agent-mcp.d.ts.map +1 -0
- package/dist/utils/update-agent-mcp.js +115 -0
- package/dist/utils/update-agent-mcp.js.map +1 -0
- package/dist/utils/update-agent-templates.d.ts +6 -0
- package/dist/utils/update-agent-templates.d.ts.map +1 -0
- package/dist/utils/update-agent-templates.js +56 -0
- package/dist/utils/update-agent-templates.js.map +1 -0
- package/dist/utils/update-config-ci.d.ts +7 -0
- package/dist/utils/update-config-ci.d.ts.map +1 -0
- package/dist/utils/update-config-ci.js +72 -0
- package/dist/utils/update-config-ci.js.map +1 -0
- package/dist/utils/update-config-git.d.ts +18 -0
- package/dist/utils/update-config-git.d.ts.map +1 -0
- package/dist/utils/update-config-git.js +146 -0
- package/dist/utils/update-config-git.js.map +1 -0
- package/dist/utils/update-config-mcp.d.ts +7 -0
- package/dist/utils/update-config-mcp.d.ts.map +1 -0
- package/dist/utils/update-config-mcp.js +98 -0
- package/dist/utils/update-config-mcp.js.map +1 -0
- package/dist/utils/validate-config.d.ts +3 -0
- package/dist/utils/validate-config.d.ts.map +1 -0
- package/dist/utils/validate-config.js +109 -0
- package/dist/utils/validate-config.js.map +1 -0
- package/dist/utils/version-tracker.d.ts +130 -0
- package/dist/utils/version-tracker.d.ts.map +1 -0
- package/dist/utils/version-tracker.js +298 -0
- package/dist/utils/version-tracker.js.map +1 -0
- package/dist/utils/worktree.d.ts +68 -0
- package/dist/utils/worktree.d.ts.map +1 -0
- package/dist/utils/worktree.js +446 -0
- package/dist/utils/worktree.js.map +1 -0
- package/package.json +77 -0
- package/templates/ARCHAI_README.md +329 -0
- package/templates/CLAUDE.md +67 -0
- package/templates/PROMPTS.md +506 -0
- package/templates/core-agents/boss-agent.md +671 -0
- package/templates/core-agents/cleanup-agent.md +145 -0
- package/templates/core-agents/code-reviewer.md +175 -0
- package/templates/core-agents/critical-reviewer.md +117 -0
- package/templates/core-agents/deep-analyst.md +216 -0
- package/templates/core-agents/finalization-agent.md +252 -0
- package/templates/core-agents/git-coordinator.md +240 -0
- package/templates/core-agents/implementation-agent.md +151 -0
- package/templates/core-agents/maestro-agent.md +413 -0
- package/templates/core-agents/maestro-headless-agent.md +422 -0
- package/templates/core-agents/plan-validator.md +198 -0
- package/templates/core-agents/quick-fix.md +56 -0
- package/templates/core-agents/routing-templates.md +338 -0
- package/templates/core-agents/task-orchestrator.md +143 -0
- package/templates/core-agents/task-prep.md +202 -0
- package/templates/core-agents/tdd-designer.md +143 -0
- 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
|