@clubmatto/ai-kit 0.0.7 → 0.0.8
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 +4 -3
- package/dist/src/cmd/sync.js +112 -78
- package/dist/src/detection/detect.js +11 -8
- package/dist/src/detection/language-detectors.js +0 -2
- package/dist/src/detection/scanner.js +11 -41
- package/dist/src/manifest.js +16 -2
- package/dist/src/output.js +21 -10
- package/dist/src/plan.js +91 -0
- package/dist/src/template.js +6 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -83,11 +83,12 @@ ai-kit sync
|
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
# Bump version in package.json first
|
|
86
|
-
git add package.json
|
|
86
|
+
git add ai-kit/package.json
|
|
87
|
+
git commit -m "release: bump version to <version>"
|
|
87
88
|
|
|
88
|
-
# Create git tag
|
|
89
|
+
# Create git tag and push both (tag triggers automated release)
|
|
89
90
|
git tag ai-kit/v<version>
|
|
90
|
-
git push origin
|
|
91
|
+
git push origin main --follow-tags
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
## License
|
package/dist/src/cmd/sync.js
CHANGED
|
@@ -9,6 +9,7 @@ const template_1 = require("../template");
|
|
|
9
9
|
const output_1 = require("../output");
|
|
10
10
|
const detect_1 = require("../detection/detect");
|
|
11
11
|
const language_detectors_1 = require("../detection/language-detectors");
|
|
12
|
+
const plan_1 = require("../plan");
|
|
12
13
|
const rootDir = (0, path_1.join)(__dirname, "..", "..", "..");
|
|
13
14
|
const defaultSourceDirs = {
|
|
14
15
|
rules: (0, path_1.join)(rootDir, "src", "rules"),
|
|
@@ -17,41 +18,31 @@ const defaultSourceDirs = {
|
|
|
17
18
|
commands: (0, path_1.join)(rootDir, "src", "commands"),
|
|
18
19
|
};
|
|
19
20
|
async function sync(cwd, version, options, logger = output_1.log, sourceDirs = defaultSourceDirs) {
|
|
20
|
-
const manifest = (0, manifest_1.readManifest)(cwd);
|
|
21
21
|
logger.logo(version);
|
|
22
|
-
if (manifest && manifest.version === version) {
|
|
23
|
-
logger.success(`Already at latest version (${version})`);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
22
|
logger.welcome();
|
|
27
|
-
const counts = await doSync(cwd, version, options, logger, sourceDirs);
|
|
28
|
-
logger.summary(counts);
|
|
29
|
-
}
|
|
30
|
-
function writeItem(aiDir, file) {
|
|
31
|
-
const targetDir = (0, path_1.join)(aiDir, file.type);
|
|
32
|
-
if (!(0, fs_1.existsSync)(targetDir)) {
|
|
33
|
-
(0, fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
const targetPath = (0, path_1.join)(targetDir, file.name);
|
|
36
|
-
const parentDir = (0, path_1.dirname)(targetPath);
|
|
37
|
-
if (!(0, fs_1.existsSync)(parentDir)) {
|
|
38
|
-
(0, fs_1.mkdirSync)(parentDir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
(0, fs_1.writeFileSync)(targetPath, (0, template_1.processTemplate)(file.content));
|
|
41
|
-
}
|
|
42
|
-
async function doSync(cwd, version, options, logger, sourceDirs) {
|
|
43
23
|
const aiDir = (0, path_1.join)(cwd, ".agents");
|
|
44
24
|
if (!(0, fs_1.existsSync)(aiDir)) {
|
|
45
25
|
(0, fs_1.mkdirSync)(aiDir, { recursive: true });
|
|
46
26
|
}
|
|
27
|
+
const manifest = (0, manifest_1.readManifest)(cwd);
|
|
28
|
+
const desired = buildDesiredFiles(sourceDirs, cwd, options);
|
|
29
|
+
const actions = (0, plan_1.diffDesired)(desired, manifest, cwd);
|
|
30
|
+
const changes = executeActions(actions, cwd, logger);
|
|
31
|
+
const newFiles = {};
|
|
32
|
+
for (const [relPath, df] of desired) {
|
|
33
|
+
newFiles[relPath] = { sourceHash: df.identity };
|
|
34
|
+
}
|
|
35
|
+
(0, manifest_1.writeManifest)(cwd, { version, files: newFiles });
|
|
36
|
+
logger.summary(changes);
|
|
37
|
+
}
|
|
38
|
+
function buildDesiredFiles(sourceDirs, cwd, options) {
|
|
39
|
+
const desired = new Map();
|
|
47
40
|
const contentFiles = (0, reader_1.readContent)(sourceDirs.rules, sourceDirs.skills);
|
|
48
41
|
const rootFiles = (0, reader_1.readConfigs)(sourceDirs.agents);
|
|
49
|
-
// Detect languages and determine project type
|
|
50
42
|
const detectionResult = (0, detect_1.detectLanguages)(cwd);
|
|
51
43
|
let languages = detectionResult.languages;
|
|
52
44
|
let isMonorepo = detectionResult.isMonorepo;
|
|
53
45
|
const primaryLanguage = detectionResult.primaryLanguage;
|
|
54
|
-
// Apply overrides from options
|
|
55
46
|
if (options.allRules) {
|
|
56
47
|
languages = language_detectors_1.detectors.map((d) => d.name);
|
|
57
48
|
isMonorepo = true;
|
|
@@ -66,84 +57,127 @@ async function doSync(cwd, version, options, logger, sourceDirs) {
|
|
|
66
57
|
languages = options.languages;
|
|
67
58
|
isMonorepo = languages.length > 1;
|
|
68
59
|
}
|
|
69
|
-
// If no languages detected and no overrides, fall back to all rules (monorepo)
|
|
70
60
|
if (languages.length === 0) {
|
|
71
|
-
languages = language_detectors_1.detectors.map((d) => d.name);
|
|
72
61
|
isMonorepo = true;
|
|
73
62
|
}
|
|
74
63
|
const agentsFile = (0, reader_1.readAgents)(sourceDirs.agents, isMonorepo, primaryLanguage);
|
|
75
|
-
// Filter rules based on detected languages
|
|
76
64
|
const ruleFilesToInclude = options.allRules
|
|
77
65
|
? (0, detect_1.getAllRuleFiles)()
|
|
78
66
|
: (0, detect_1.getRuleFilesForLanguages)(languages);
|
|
79
67
|
const rules = contentFiles.filter((f) => {
|
|
80
68
|
if (f.type !== "rules")
|
|
81
69
|
return false;
|
|
82
|
-
|
|
83
|
-
if (!(0, detect_1.isLanguageSpecificRule)(f.name)) {
|
|
70
|
+
if (!(0, detect_1.isLanguageSpecificRule)(f.name))
|
|
84
71
|
return true;
|
|
85
|
-
}
|
|
86
|
-
// For language-specific rules, check if they're in the include list
|
|
87
72
|
return ruleFilesToInclude.includes(f.name);
|
|
88
73
|
});
|
|
89
|
-
const stats = { rules: 0, skills: 0, commands: 0 };
|
|
90
|
-
const installedRootFiles = [];
|
|
91
74
|
if (!options.skipOpencode) {
|
|
92
75
|
const commandConfig = (0, reader_1.getCommandConfig)(sourceDirs.commands);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (rootFiles.length > 0) {
|
|
101
|
-
logger.section("configs");
|
|
102
|
-
for (const file of rootFiles) {
|
|
103
|
-
let content = file.content;
|
|
104
|
-
if (file.name === "opencode.json" &&
|
|
105
|
-
Object.keys(commandConfig).length > 0) {
|
|
106
|
-
const config = JSON.parse(content);
|
|
107
|
-
config.command = commandConfig;
|
|
108
|
-
content = JSON.stringify(config, null, 2) + "\n";
|
|
109
|
-
}
|
|
110
|
-
const targetPath = (0, path_1.join)(cwd, file.name);
|
|
111
|
-
(0, fs_1.writeFileSync)(targetPath, content);
|
|
112
|
-
logger.success(`${file.name}`);
|
|
113
|
-
installedRootFiles.push(file.name);
|
|
76
|
+
for (const file of rootFiles) {
|
|
77
|
+
let content = file.content;
|
|
78
|
+
if (file.name === "opencode.json" &&
|
|
79
|
+
Object.keys(commandConfig).length > 0) {
|
|
80
|
+
const config = JSON.parse(content);
|
|
81
|
+
config.command = commandConfig;
|
|
82
|
+
content = JSON.stringify(config, null, 2) + "\n";
|
|
114
83
|
}
|
|
84
|
+
desired.set(file.name, {
|
|
85
|
+
path: file.name,
|
|
86
|
+
identity: (0, manifest_1.hashContent)(content),
|
|
87
|
+
content,
|
|
88
|
+
category: file.name === "opencode.json"
|
|
89
|
+
? "opencode-json"
|
|
90
|
+
: "static",
|
|
91
|
+
});
|
|
115
92
|
}
|
|
116
93
|
}
|
|
117
94
|
if (agentsFile) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
95
|
+
desired.set(agentsFile.name, {
|
|
96
|
+
path: agentsFile.name,
|
|
97
|
+
identity: (0, manifest_1.hashContent)(agentsFile.content),
|
|
98
|
+
content: (0, template_1.processTemplate)(agentsFile.content),
|
|
99
|
+
category: "agents-md",
|
|
100
|
+
});
|
|
121
101
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
102
|
+
for (const file of rules) {
|
|
103
|
+
const relPath = (0, path_1.join)(".agents", file.type, file.name);
|
|
104
|
+
desired.set(relPath, {
|
|
105
|
+
path: relPath,
|
|
106
|
+
identity: (0, manifest_1.hashContent)(file.content),
|
|
107
|
+
content: (0, template_1.processTemplate)(file.content),
|
|
108
|
+
category: "static",
|
|
109
|
+
});
|
|
129
110
|
}
|
|
130
111
|
const skills = contentFiles.filter((f) => f.type === "skills");
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
112
|
+
for (const file of skills) {
|
|
113
|
+
const relPath = (0, path_1.join)(".agents", file.type, file.name);
|
|
114
|
+
desired.set(relPath, {
|
|
115
|
+
path: relPath,
|
|
116
|
+
identity: (0, manifest_1.hashContent)(file.content),
|
|
117
|
+
content: (0, template_1.processTemplate)(file.content),
|
|
118
|
+
category: "static",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return desired;
|
|
122
|
+
}
|
|
123
|
+
function executeActions(actions, cwd, logger) {
|
|
124
|
+
const changes = (0, plan_1.emptySyncChanges)();
|
|
125
|
+
for (const action of actions) {
|
|
126
|
+
const targetPath = (0, path_1.join)(cwd, action.relPath);
|
|
127
|
+
switch (action.action) {
|
|
128
|
+
case "add": {
|
|
129
|
+
const dir = (0, path_1.dirname)(targetPath);
|
|
130
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
131
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
132
|
+
(0, fs_1.writeFileSync)(targetPath, action.content);
|
|
133
|
+
changes.added++;
|
|
134
|
+
logger.success(`+ ${action.relPath}`);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "update": {
|
|
138
|
+
const dir = (0, path_1.dirname)(targetPath);
|
|
139
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
140
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
141
|
+
(0, fs_1.writeFileSync)(targetPath, action.content);
|
|
142
|
+
changes.updated++;
|
|
143
|
+
logger.success(`~ ${action.relPath}`);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "skip":
|
|
147
|
+
changes.skipped++;
|
|
148
|
+
break;
|
|
149
|
+
case "merge": {
|
|
150
|
+
const currentContent = (0, fs_1.readFileSync)(targetPath, "utf-8");
|
|
151
|
+
const merged = (0, plan_1.mergeOpencodeJson)(action.content, currentContent);
|
|
152
|
+
(0, fs_1.writeFileSync)(targetPath, merged);
|
|
153
|
+
changes.merged++;
|
|
154
|
+
logger.success(`M ${action.relPath}`);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "backup": {
|
|
158
|
+
const currentContent = (0, fs_1.readFileSync)(targetPath, "utf-8");
|
|
159
|
+
const backupDir = (0, path_1.join)(cwd, ".agents");
|
|
160
|
+
if (!(0, fs_1.existsSync)(backupDir))
|
|
161
|
+
(0, fs_1.mkdirSync)(backupDir, { recursive: true });
|
|
162
|
+
const backupPath = (0, path_1.join)(backupDir, `${action.relPath}.bak.${Date.now()}`);
|
|
163
|
+
(0, fs_1.writeFileSync)(backupPath, currentContent);
|
|
164
|
+
(0, fs_1.writeFileSync)(targetPath, action.content);
|
|
165
|
+
changes.backedUp++;
|
|
166
|
+
logger.success(`! ${action.relPath}`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "remove": {
|
|
170
|
+
(0, fs_1.rmSync)(targetPath, { force: true });
|
|
171
|
+
changes.removed++;
|
|
172
|
+
logger.success(`- ${action.relPath}`);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "warn": {
|
|
176
|
+
changes.warned++;
|
|
177
|
+
logger.warn(`${action.relPath} (modified — skipped)`);
|
|
178
|
+
break;
|
|
140
179
|
}
|
|
141
180
|
}
|
|
142
181
|
}
|
|
143
|
-
|
|
144
|
-
version,
|
|
145
|
-
installedAt: new Date().toISOString(),
|
|
146
|
-
rootFiles: installedRootFiles,
|
|
147
|
-
});
|
|
148
|
-
return stats;
|
|
182
|
+
return changes;
|
|
149
183
|
}
|
|
@@ -6,20 +6,23 @@ exports.getAllRuleFiles = getAllRuleFiles;
|
|
|
6
6
|
exports.isLanguageSpecificRule = isLanguageSpecificRule;
|
|
7
7
|
const language_detectors_1 = require("./language-detectors");
|
|
8
8
|
const scanner_1 = require("./scanner");
|
|
9
|
+
function hasMatchingFile(files, names) {
|
|
10
|
+
return names.some((name) => files.some((f) => f === name || f.endsWith("/" + name)));
|
|
11
|
+
}
|
|
12
|
+
function hasMatchingExtension(files, extensions) {
|
|
13
|
+
return extensions.some((ext) => files.some((f) => f.endsWith(ext)));
|
|
14
|
+
}
|
|
9
15
|
function detectLanguages(cwd) {
|
|
16
|
+
const { allFiles, rootFiles } = (0, scanner_1.scanTree)(cwd);
|
|
10
17
|
const detected = new Set();
|
|
11
18
|
for (const detector of language_detectors_1.detectors) {
|
|
12
|
-
|
|
19
|
+
const hasConfigAtRoot = hasMatchingFile(rootFiles, detector.configFiles);
|
|
20
|
+
const hasConfigInTree = hasMatchingFile(allFiles, detector.configFiles);
|
|
21
|
+
const hasSource = hasMatchingExtension(allFiles, detector.extensions);
|
|
22
|
+
if (hasConfigAtRoot || hasConfigInTree || hasSource) {
|
|
13
23
|
detected.add(detector.name);
|
|
14
24
|
}
|
|
15
25
|
}
|
|
16
|
-
if (detected.size === 0) {
|
|
17
|
-
for (const detector of language_detectors_1.detectors) {
|
|
18
|
-
if ((0, scanner_1.hasAnySourceFile)(cwd, detector.extensions, 2)) {
|
|
19
|
-
detected.add(detector.name);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
26
|
const languages = Array.from(detected);
|
|
24
27
|
const isMonorepo = languages.length > 1;
|
|
25
28
|
const primaryLanguage = languages.length > 0 ? languages[0] : undefined;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectors = void 0;
|
|
4
|
-
//TODO this is a good first implementation
|
|
5
|
-
//but we clearly want each detector to come with a detect function
|
|
6
4
|
exports.detectors = [
|
|
7
5
|
{
|
|
8
6
|
name: "typescript",
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
const fs_1 = require("fs");
|
|
6
|
-
const path_1 = require("path");
|
|
3
|
+
exports.scanTree = scanTree;
|
|
4
|
+
const fdir_1 = require("fdir");
|
|
7
5
|
const IGNORE_DIRS = [
|
|
8
6
|
"node_modules",
|
|
9
7
|
".git",
|
|
@@ -13,41 +11,13 @@ const IGNORE_DIRS = [
|
|
|
13
11
|
".next",
|
|
14
12
|
".nuxt",
|
|
15
13
|
];
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return scanForExtensions(cwd, extensions, maxDepth, 0);
|
|
26
|
-
}
|
|
27
|
-
function scanForExtensions(dir, extensions, maxDepth, currentDepth) {
|
|
28
|
-
if (currentDepth > maxDepth) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
const entries = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
33
|
-
for (const entry of entries) {
|
|
34
|
-
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
35
|
-
if (entry.isDirectory()) {
|
|
36
|
-
if (!IGNORE_DIRS.includes(entry.name) && !entry.name.startsWith(".")) {
|
|
37
|
-
if (scanForExtensions(fullPath, extensions, maxDepth, currentDepth + 1)) {
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
else if (entry.isFile()) {
|
|
43
|
-
if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
// If we can't read the directory, skip it
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
14
|
+
function scanTree(cwd, maxDepth = 4) {
|
|
15
|
+
const allFiles = new fdir_1.fdir()
|
|
16
|
+
.withRelativePaths()
|
|
17
|
+
.withMaxDepth(maxDepth)
|
|
18
|
+
.exclude((name) => IGNORE_DIRS.includes(name) || name.startsWith("."))
|
|
19
|
+
.crawl(cwd)
|
|
20
|
+
.sync();
|
|
21
|
+
const rootFiles = allFiles.filter((f) => !f.includes("/"));
|
|
22
|
+
return { allFiles, rootFiles };
|
|
53
23
|
}
|
package/dist/src/manifest.js
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hashContent = hashContent;
|
|
3
4
|
exports.readManifest = readManifest;
|
|
4
5
|
exports.writeManifest = writeManifest;
|
|
5
6
|
const fs_1 = require("fs");
|
|
6
7
|
const path_1 = require("path");
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
7
9
|
const AI_DIR = ".agents";
|
|
8
10
|
const MANIFEST_FILE = ".ai-kit";
|
|
9
11
|
function getManifestPath(cwd) {
|
|
10
12
|
return (0, path_1.join)(cwd, AI_DIR, MANIFEST_FILE);
|
|
11
13
|
}
|
|
14
|
+
function hashContent(content) {
|
|
15
|
+
return (0, crypto_1.createHash)("sha256").update(content, "utf-8").digest("hex");
|
|
16
|
+
}
|
|
12
17
|
function readManifest(cwd) {
|
|
13
18
|
const path = getManifestPath(cwd);
|
|
14
19
|
if (!(0, fs_1.existsSync)(path))
|
|
15
20
|
return null;
|
|
16
|
-
|
|
21
|
+
try {
|
|
22
|
+
const data = JSON.parse((0, fs_1.readFileSync)(path, "utf-8"));
|
|
23
|
+
if (!data.files) {
|
|
24
|
+
return { version: data.version || "0.0.0", files: {} };
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
17
31
|
}
|
|
18
32
|
function writeManifest(cwd, manifest) {
|
|
19
33
|
const dir = (0, path_1.join)(cwd, AI_DIR);
|
|
20
34
|
if (!(0, fs_1.existsSync)(dir)) {
|
|
21
35
|
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
22
36
|
}
|
|
23
|
-
(0, fs_1.writeFileSync)(getManifestPath(cwd), JSON.stringify(manifest, null, 2));
|
|
37
|
+
(0, fs_1.writeFileSync)(getManifestPath(cwd), JSON.stringify(manifest, null, 2) + "\n");
|
|
24
38
|
}
|
package/dist/src/output.js
CHANGED
|
@@ -30,17 +30,28 @@ exports.log = {
|
|
|
30
30
|
},
|
|
31
31
|
section: (msg) => console.log(colorize(` → ${msg}`, "cyan")),
|
|
32
32
|
success: (msg) => console.log(colorize(` ✓ ${msg}`, "green")),
|
|
33
|
+
warn: (msg) => console.log(colorize(` ! ${msg}`, "yellow")),
|
|
33
34
|
final: (msg) => console.log(colorize(` ✓ ${msg}`, "green")),
|
|
34
35
|
summary: (counts) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
colorize(counts.
|
|
38
|
-
|
|
39
|
-
colorize(
|
|
40
|
-
|
|
41
|
-
colorize(`
|
|
42
|
-
|
|
43
|
-
colorize(counts.
|
|
44
|
-
|
|
36
|
+
const parts = [];
|
|
37
|
+
if (counts.added > 0)
|
|
38
|
+
parts.push(colorize(`+${counts.added} added`, "green"));
|
|
39
|
+
if (counts.updated > 0)
|
|
40
|
+
parts.push(colorize(`~${counts.updated} updated`, "white"));
|
|
41
|
+
if (counts.merged > 0)
|
|
42
|
+
parts.push(colorize(`M${counts.merged} merged`, "yellow"));
|
|
43
|
+
if (counts.backedUp > 0)
|
|
44
|
+
parts.push(colorize(`!${counts.backedUp} backed up`, "yellow"));
|
|
45
|
+
if (counts.removed > 0)
|
|
46
|
+
parts.push(colorize(`-${counts.removed} removed`, "red"));
|
|
47
|
+
if (counts.warned > 0)
|
|
48
|
+
parts.push(colorize(`!${counts.warned} modified (skipped)`, "yellow"));
|
|
49
|
+
if (parts.length === 0) {
|
|
50
|
+
console.log(colorize("\n ✓ Everything up to date!", "green"));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(colorize("\n ✓ Done!", "green"));
|
|
54
|
+
console.log(" " + parts.join(colorize(", ", "dim")));
|
|
55
|
+
}
|
|
45
56
|
},
|
|
46
57
|
};
|
package/dist/src/plan.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emptySyncChanges = emptySyncChanges;
|
|
4
|
+
exports.diffDesired = diffDesired;
|
|
5
|
+
exports.mergeOpencodeJson = mergeOpencodeJson;
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const template_1 = require("./template");
|
|
9
|
+
function emptySyncChanges() {
|
|
10
|
+
return {
|
|
11
|
+
added: 0,
|
|
12
|
+
updated: 0,
|
|
13
|
+
merged: 0,
|
|
14
|
+
backedUp: 0,
|
|
15
|
+
removed: 0,
|
|
16
|
+
skipped: 0,
|
|
17
|
+
warned: 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function diffDesired(desired, manifest, cwd) {
|
|
21
|
+
const actions = [];
|
|
22
|
+
for (const [relPath, df] of desired) {
|
|
23
|
+
const lastEntry = manifest?.files[relPath];
|
|
24
|
+
const targetPath = (0, path_1.join)(cwd, relPath);
|
|
25
|
+
let onDiskContent = null;
|
|
26
|
+
try {
|
|
27
|
+
onDiskContent = (0, fs_1.readFileSync)(targetPath, "utf-8");
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// file doesn't exist
|
|
31
|
+
}
|
|
32
|
+
if (onDiskContent === null) {
|
|
33
|
+
actions.push({ action: "add", relPath, content: df.content });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const normalizedDisk = (0, template_1.stripDates)(onDiskContent);
|
|
37
|
+
const normalizedDesired = (0, template_1.stripDates)(df.content);
|
|
38
|
+
if (normalizedDisk === normalizedDesired) {
|
|
39
|
+
actions.push({ action: "skip", relPath });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const sourceChanged = !lastEntry || lastEntry.sourceHash !== df.identity;
|
|
43
|
+
if (df.category === "opencode-json") {
|
|
44
|
+
actions.push({ action: "merge", relPath, content: df.content });
|
|
45
|
+
}
|
|
46
|
+
else if (sourceChanged && df.category === "agents-md") {
|
|
47
|
+
actions.push({ action: "backup", relPath, content: df.content });
|
|
48
|
+
}
|
|
49
|
+
else if (sourceChanged) {
|
|
50
|
+
actions.push({ action: "update", relPath, content: df.content });
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
actions.push({ action: "warn", relPath });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (manifest) {
|
|
57
|
+
for (const relPath of Object.keys(manifest.files)) {
|
|
58
|
+
if (!desired.has(relPath)) {
|
|
59
|
+
actions.push({ action: "remove", relPath });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return actions;
|
|
64
|
+
}
|
|
65
|
+
function mergeOpencodeJson(desiredContent, currentContent) {
|
|
66
|
+
const desired = JSON.parse(desiredContent);
|
|
67
|
+
const current = JSON.parse(currentContent);
|
|
68
|
+
const result = { ...desired };
|
|
69
|
+
if (current.mcp && typeof current.mcp === "object") {
|
|
70
|
+
const resultMcp = result.mcp;
|
|
71
|
+
for (const [key, value] of Object.entries(current.mcp)) {
|
|
72
|
+
if (!(key in resultMcp)) {
|
|
73
|
+
resultMcp[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (current.command && typeof current.command === "object") {
|
|
78
|
+
const resultCmd = result.command;
|
|
79
|
+
for (const [key, value] of Object.entries(current.command)) {
|
|
80
|
+
if (!(key in resultCmd)) {
|
|
81
|
+
resultCmd[key] = value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const key of Object.keys(current)) {
|
|
86
|
+
if (!(key in result)) {
|
|
87
|
+
result[key] = current[key];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return JSON.stringify(result, null, 2) + "\n";
|
|
91
|
+
}
|
package/dist/src/template.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.processTemplate = processTemplate;
|
|
4
|
+
exports.stripDates = stripDates;
|
|
4
5
|
function processTemplate(content) {
|
|
5
6
|
const now = new Date();
|
|
6
7
|
const isoDate = now.toISOString().split("T")[0];
|
|
@@ -8,3 +9,8 @@ function processTemplate(content) {
|
|
|
8
9
|
.replace(/\{\{FOOTER}}/g, `Last updated: ${isoDate}. This file extends the global rules in @AGENTS.md. Always check both files.`)
|
|
9
10
|
.replace(/\{\{AGENTS_FOOTER}}/g, `This file was last updated: ${isoDate}. Always check the \`.agents/rules/\` directory for the most current language-specific guidelines.`);
|
|
10
11
|
}
|
|
12
|
+
function stripDates(content) {
|
|
13
|
+
return content
|
|
14
|
+
.replace(/Last updated: \d{4}-\d{2}-\d{2}\. This file extends the global rules in @AGENTS\.md\. Always check both files\./g, "{{FOOTER}}")
|
|
15
|
+
.replace(/This file was last updated: \d{4}-\d{2}-\d{2}\. Always check the `\.agents\/rules\/` directory for the most current language-specific guidelines\./g, "{{AGENTS_FOOTER}}");
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clubmatto/ai-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "The AI configuration CLI from Club Matto",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"commander": "^14.0.3",
|
|
33
|
+
"fdir": "6.5.0",
|
|
33
34
|
"gradient-string": "^2.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|