@dujavi/ai-md 0.2.0 → 0.3.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 +28 -48
- package/bin/ai-md.js +18 -13
- package/lib/commands.js +17 -13
- package/lib/config.js +33 -1
- package/lib/status.js +54 -49
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,69 +1,49 @@
|
|
|
1
1
|
# ai-md
|
|
2
2
|
|
|
3
|
-
Public **AXI-shaped** CLI for a **private** personal AI config directory (`~/.ai-md`)
|
|
3
|
+
Public **AXI-shaped** CLI for a **private** personal AI config directory (`~/.ai-md`).
|
|
4
4
|
|
|
5
|
-
| Layer |
|
|
6
|
-
|
|
7
|
-
| **
|
|
8
|
-
| **
|
|
5
|
+
| Layer | Path | Purpose |
|
|
6
|
+
|-------|------|---------|
|
|
7
|
+
| **System** | `skills/`, `rules/` | Global base — linked to `~/.cursor/{skills,rules}` |
|
|
8
|
+
| **Templates** | `templates/<type>/` | Project-type starters (`base`, later `forms`, …) |
|
|
9
|
+
| **Projects** | `projects/<name>/` | Per-app overlays (repo `.cursor` → here) |
|
|
9
10
|
|
|
10
|
-
No personal
|
|
11
|
+
No personal content ships in this package. Reads default to TOON + `help[]` (`--json` available).
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
npm i -g @dujavi/ai-md
|
|
16
|
-
# binary: ai-md
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
## Layout idea
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
~/.ai-md/
|
|
23
|
+
skills/ # system — all agents see these
|
|
24
|
+
rules/
|
|
25
|
+
templates/
|
|
26
|
+
base/ # default project starter (customizable stubs only)
|
|
27
|
+
# forms/ … # other use cases
|
|
28
|
+
projects/
|
|
29
|
+
presenter/
|
|
30
|
+
sendfolio/
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
ai-md # status (content-first)
|
|
37
|
-
ai-md status --json
|
|
38
|
-
ai-md pull
|
|
39
|
-
ai-md push -m "why"
|
|
40
|
-
ai-md doctor --fix
|
|
41
|
-
```
|
|
33
|
+
Put shared agentic skills/rules in **system** folders. Put only type-specific or per-project starters under **templates/**.
|
|
42
34
|
|
|
43
|
-
##
|
|
35
|
+
## Commands
|
|
44
36
|
|
|
45
37
|
```bash
|
|
46
|
-
#
|
|
47
|
-
ai-md init-project --repo ~/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
ai-md
|
|
51
|
-
|
|
52
|
-
# Link only (empty rules/skills dirs if missing)
|
|
53
|
-
ai-md link-project --repo ~/my-app
|
|
38
|
+
ai-md # status
|
|
39
|
+
ai-md init-project --repo ~/app --from base
|
|
40
|
+
ai-md apply-template --project app --from base
|
|
41
|
+
ai-md doctor --fix --agents cursor,claude
|
|
42
|
+
ai-md pull | push -m "why"
|
|
43
|
+
ai-md ensure-tools
|
|
54
44
|
```
|
|
55
45
|
|
|
56
|
-
## AXI-shaped reads
|
|
57
|
-
|
|
58
|
-
| Flag | Effect |
|
|
59
|
-
|------|--------|
|
|
60
|
-
| (default) | TOON on stdout + `help[]` |
|
|
61
|
-
| `--json` | Normalized JSON |
|
|
62
|
-
| `--full` | Paths + template drift details |
|
|
63
|
-
|
|
64
|
-
Mutations (`push`, `ensure-tools`) stay human/git-oriented.
|
|
65
|
-
|
|
66
46
|
## Environment
|
|
67
47
|
|
|
68
48
|
- `AI_MD_DIR` → `~/.ai-md`
|
|
69
|
-
- `AI_MD_REMOTE` →
|
|
49
|
+
- `AI_MD_REMOTE` → private content git URL
|
package/bin/ai-md.js
CHANGED
|
@@ -35,7 +35,7 @@ function parseArgs(argv) {
|
|
|
35
35
|
repo: null,
|
|
36
36
|
name: null,
|
|
37
37
|
project: null,
|
|
38
|
-
from: "
|
|
38
|
+
from: "base",
|
|
39
39
|
agents: ["cursor"],
|
|
40
40
|
rest: [],
|
|
41
41
|
};
|
|
@@ -117,20 +117,25 @@ function parseArgs(argv) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
function printHelp() {
|
|
120
|
-
process.stdout.write(`ai-md — private ~/.ai-md skills/rules +
|
|
120
|
+
process.stdout.write(`ai-md — private ~/.ai-md: system skills/rules + templates/ + projects/ (AXI-shaped)
|
|
121
121
|
|
|
122
122
|
Usage:
|
|
123
123
|
ai-md [command] [options]
|
|
124
124
|
npx -y @dujavi/ai-md [command] [options]
|
|
125
125
|
|
|
126
|
+
Layout:
|
|
127
|
+
~/.ai-md/skills, rules System (global) base — linked to ~/.cursor
|
|
128
|
+
~/.ai-md/templates/<type> Project-type starters (default: base)
|
|
129
|
+
~/.ai-md/projects/<name> Per-app overlays (repo .cursor → here)
|
|
130
|
+
|
|
126
131
|
Commands:
|
|
127
132
|
status Snapshot (default when no command) [AXI]
|
|
128
133
|
doctor Diagnose links/projects; --fix repairs
|
|
129
134
|
install Clone remote if needed; link ~/.cursor + optional agents
|
|
130
135
|
pull | push Sync private git repo
|
|
131
136
|
ensure-tools Install/update grok + quota-axi (alias: tools)
|
|
132
|
-
init-project Seed projects/<name> from
|
|
133
|
-
apply-template Merge missing template
|
|
137
|
+
init-project Seed projects/<name> from templates/<from> + link .cursor/
|
|
138
|
+
apply-template Merge missing files from a template into a project
|
|
134
139
|
link-project Link repo .cursor/ without seeding (alias: link)
|
|
135
140
|
help Show this help
|
|
136
141
|
|
|
@@ -141,23 +146,18 @@ Options:
|
|
|
141
146
|
--repo <path> App repository root
|
|
142
147
|
--name <id> Project id under projects/ (default: basename)
|
|
143
148
|
--project <id> Target project for apply-template
|
|
144
|
-
--from <id> Template
|
|
149
|
+
--from <id> Template under templates/ (default: base)
|
|
145
150
|
--force Replace non-symlink .cursor / re-merge
|
|
146
151
|
--dry-run Preview without writing
|
|
147
152
|
--fix doctor: repair symlinks
|
|
148
153
|
-m, --message push commit message
|
|
149
154
|
|
|
150
|
-
Environment:
|
|
151
|
-
AI_MD_DIR Private config dir (default: ~/.ai-md)
|
|
152
|
-
AI_MD_REMOTE Clone URL (default: https://github.com/dujavi/.ai-md.git)
|
|
153
|
-
|
|
154
155
|
Examples:
|
|
155
156
|
ai-md
|
|
156
157
|
ai-md status --json
|
|
158
|
+
ai-md init-project --repo ~/presenter --from base
|
|
159
|
+
ai-md apply-template --project presenter --from base
|
|
157
160
|
ai-md doctor --fix --agents cursor,claude
|
|
158
|
-
ai-md init-project --repo ~/presenter
|
|
159
|
-
ai-md apply-template --project presenter
|
|
160
|
-
ai-md push -m "Add rule"
|
|
161
161
|
`);
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -184,7 +184,11 @@ function main() {
|
|
|
184
184
|
try {
|
|
185
185
|
switch (opts.cmd) {
|
|
186
186
|
case "status": {
|
|
187
|
-
const data = collectStatus({
|
|
187
|
+
const data = collectStatus({
|
|
188
|
+
full: opts.full,
|
|
189
|
+
agents: opts.agents,
|
|
190
|
+
from: opts.from,
|
|
191
|
+
});
|
|
188
192
|
emit({ data, json: opts.json, help: statusHelp(data) });
|
|
189
193
|
process.exitCode = data.counts.problems > 0 ? 1 : 0;
|
|
190
194
|
break;
|
|
@@ -225,6 +229,7 @@ function main() {
|
|
|
225
229
|
case "apply-template": {
|
|
226
230
|
const data = applyTemplate({
|
|
227
231
|
project: opts.project || opts.name,
|
|
232
|
+
from: opts.from,
|
|
228
233
|
dryRun: opts.dryRun,
|
|
229
234
|
});
|
|
230
235
|
emit({
|
package/lib/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const {
|
|
6
6
|
resolveConfig,
|
|
7
|
+
templatePath,
|
|
7
8
|
symlinkState,
|
|
8
9
|
agentSkillTargets,
|
|
9
10
|
} = require("./config");
|
|
@@ -86,7 +87,7 @@ function copyDir(src, dest, { dryRun = false } = {}) {
|
|
|
86
87
|
function initProject({
|
|
87
88
|
repo,
|
|
88
89
|
name,
|
|
89
|
-
from = "
|
|
90
|
+
from = "base",
|
|
90
91
|
force = false,
|
|
91
92
|
dryRun = false,
|
|
92
93
|
} = {}) {
|
|
@@ -103,11 +104,13 @@ function initProject({
|
|
|
103
104
|
}
|
|
104
105
|
const absRepo = fs.realpathSync(repo);
|
|
105
106
|
const projectName = name || path.basename(absRepo);
|
|
106
|
-
const
|
|
107
|
+
const tmplDir = templatePath(cfg, from);
|
|
107
108
|
const projectDir = path.join(cfg.projectsDir, projectName);
|
|
108
109
|
|
|
109
|
-
if (!fs.existsSync(
|
|
110
|
-
const err = new Error(
|
|
110
|
+
if (!fs.existsSync(tmplDir)) {
|
|
111
|
+
const err = new Error(
|
|
112
|
+
`template not found: ${tmplDir} (available under ${cfg.templatesDir}/)`
|
|
113
|
+
);
|
|
111
114
|
err.code = "ENOENT";
|
|
112
115
|
throw err;
|
|
113
116
|
}
|
|
@@ -121,11 +124,10 @@ function initProject({
|
|
|
121
124
|
if (hasContent && !force) {
|
|
122
125
|
actions.push({ action: "exists", path: projectDir });
|
|
123
126
|
} else {
|
|
124
|
-
|
|
125
|
-
actions.push(...mergeTemplate(templateDir, projectDir, { dryRun }));
|
|
127
|
+
actions.push(...mergeTemplate(tmplDir, projectDir, { dryRun }));
|
|
126
128
|
}
|
|
127
129
|
} else {
|
|
128
|
-
actions.push(copyDir(
|
|
130
|
+
actions.push(copyDir(tmplDir, projectDir, { dryRun }));
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
const link = path.join(absRepo, ".cursor");
|
|
@@ -137,6 +139,7 @@ function initProject({
|
|
|
137
139
|
projectDir,
|
|
138
140
|
repo: absRepo,
|
|
139
141
|
from,
|
|
142
|
+
templateDir: tmplDir,
|
|
140
143
|
actions,
|
|
141
144
|
};
|
|
142
145
|
}
|
|
@@ -172,7 +175,7 @@ function mergeTemplate(templateDir, projectDir, { dryRun = false } = {}) {
|
|
|
172
175
|
return actions;
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
function applyTemplate({ project, dryRun = false } = {}) {
|
|
178
|
+
function applyTemplate({ project, from = "base", dryRun = false } = {}) {
|
|
176
179
|
const cfg = resolveConfig();
|
|
177
180
|
if (!project) {
|
|
178
181
|
const err = new Error("missing --project <name>");
|
|
@@ -185,13 +188,14 @@ function applyTemplate({ project, dryRun = false } = {}) {
|
|
|
185
188
|
err.code = "ENOENT";
|
|
186
189
|
throw err;
|
|
187
190
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
err
|
|
191
|
+
const tmplDir = templatePath(cfg, from);
|
|
192
|
+
if (!fs.existsSync(tmplDir)) {
|
|
193
|
+
const err = new Error(`template not found: ${tmplDir}`);
|
|
194
|
+
err.code = "ENOENT";
|
|
191
195
|
throw err;
|
|
192
196
|
}
|
|
193
|
-
const actions = mergeTemplate(
|
|
194
|
-
return { project, projectDir, actions };
|
|
197
|
+
const actions = mergeTemplate(tmplDir, projectDir, { dryRun });
|
|
198
|
+
return { project, projectDir, from, templateDir: tmplDir, actions };
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
function linkProject({ repo, name, force = false, dryRun = false } = {}) {
|
package/lib/config.js
CHANGED
|
@@ -12,6 +12,7 @@ function resolveConfig(env = process.env) {
|
|
|
12
12
|
env.AI_MD_REMOTE ||
|
|
13
13
|
env.CURSOR_MD_REMOTE ||
|
|
14
14
|
"https://github.com/dujavi/.ai-md.git";
|
|
15
|
+
const templatesDir = path.join(dir, "templates");
|
|
15
16
|
return {
|
|
16
17
|
home,
|
|
17
18
|
dir,
|
|
@@ -19,12 +20,27 @@ function resolveConfig(env = process.env) {
|
|
|
19
20
|
cursorSkills: path.join(home, ".cursor", "skills"),
|
|
20
21
|
cursorRules: path.join(home, ".cursor", "rules"),
|
|
21
22
|
projectsDir: path.join(dir, "projects"),
|
|
22
|
-
|
|
23
|
+
templatesDir,
|
|
24
|
+
/** @deprecated use templatePath(cfg, name) */
|
|
25
|
+
templateDir: path.join(templatesDir, "base"),
|
|
23
26
|
skillsDir: path.join(dir, "skills"),
|
|
24
27
|
rulesDir: path.join(dir, "rules"),
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
function templatePath(cfg, name = "base") {
|
|
32
|
+
return path.join(cfg.templatesDir, name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function listTemplates(cfg) {
|
|
36
|
+
if (!fs.existsSync(cfg.templatesDir)) return [];
|
|
37
|
+
return fs
|
|
38
|
+
.readdirSync(cfg.templatesDir, { withFileTypes: true })
|
|
39
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith("."))
|
|
40
|
+
.map((d) => d.name)
|
|
41
|
+
.sort();
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
function countRules(rulesDir) {
|
|
29
45
|
if (!fs.existsSync(rulesDir)) return 0;
|
|
30
46
|
return fs
|
|
@@ -88,8 +104,24 @@ function agentSkillTargets(home, agents) {
|
|
|
88
104
|
.map((name) => ({ name, path: map[name] }));
|
|
89
105
|
}
|
|
90
106
|
|
|
107
|
+
/** One-time: projects/template → templates/base */
|
|
108
|
+
function migrateLegacyTemplate(cfg, { dryRun = false } = {}) {
|
|
109
|
+
const legacy = path.join(cfg.projectsDir, "template");
|
|
110
|
+
const dest = templatePath(cfg, "base");
|
|
111
|
+
if (!fs.existsSync(legacy) || fs.existsSync(dest)) {
|
|
112
|
+
return { action: "skip" };
|
|
113
|
+
}
|
|
114
|
+
if (dryRun) return { action: "would_migrate", from: legacy, to: dest };
|
|
115
|
+
fs.mkdirSync(cfg.templatesDir, { recursive: true });
|
|
116
|
+
fs.renameSync(legacy, dest);
|
|
117
|
+
return { action: "migrated", from: legacy, to: dest };
|
|
118
|
+
}
|
|
119
|
+
|
|
91
120
|
module.exports = {
|
|
92
121
|
resolveConfig,
|
|
122
|
+
templatePath,
|
|
123
|
+
listTemplates,
|
|
124
|
+
migrateLegacyTemplate,
|
|
93
125
|
countRules,
|
|
94
126
|
countSkills,
|
|
95
127
|
listSkillNames,
|
package/lib/status.js
CHANGED
|
@@ -5,6 +5,9 @@ const path = require("path");
|
|
|
5
5
|
const { execFileSync } = require("child_process");
|
|
6
6
|
const {
|
|
7
7
|
resolveConfig,
|
|
8
|
+
templatePath,
|
|
9
|
+
listTemplates,
|
|
10
|
+
migrateLegacyTemplate,
|
|
8
11
|
countRules,
|
|
9
12
|
countSkills,
|
|
10
13
|
listSkillNames,
|
|
@@ -19,17 +22,17 @@ function git(repo, args) {
|
|
|
19
22
|
encoding: "utf8",
|
|
20
23
|
stdio: ["ignore", "pipe", "pipe"],
|
|
21
24
|
}).trim();
|
|
22
|
-
} catch
|
|
25
|
+
} catch {
|
|
23
26
|
return null;
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
function templateDrift(projectDir,
|
|
28
|
-
if (!fs.existsSync(
|
|
30
|
+
function templateDrift(projectDir, tmplDir) {
|
|
31
|
+
if (!fs.existsSync(tmplDir)) {
|
|
29
32
|
return { missingRules: [], missingSkills: [], extraRules: [], extraSkills: [] };
|
|
30
33
|
}
|
|
31
|
-
const tRules = new Set(listRuleNames(path.join(
|
|
32
|
-
const tSkills = new Set(listSkillNames(path.join(
|
|
34
|
+
const tRules = new Set(listRuleNames(path.join(tmplDir, "rules")));
|
|
35
|
+
const tSkills = new Set(listSkillNames(path.join(tmplDir, "skills")));
|
|
33
36
|
const pRules = new Set(listRuleNames(path.join(projectDir, "rules")));
|
|
34
37
|
const pSkills = new Set(listSkillNames(path.join(projectDir, "skills")));
|
|
35
38
|
return {
|
|
@@ -40,44 +43,38 @@ function templateDrift(projectDir, templateDir) {
|
|
|
40
43
|
};
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
function
|
|
46
|
+
function collectTemplates(cfg, { full = false } = {}) {
|
|
47
|
+
return listTemplates(cfg).map((name) => {
|
|
48
|
+
const dir = templatePath(cfg, name);
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
rules: countRules(path.join(dir, "rules")),
|
|
52
|
+
skills: countSkills(path.join(dir, "skills")),
|
|
53
|
+
...(full ? { path: dir } : {}),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function collectProjects(cfg, { full = false, from = "base" } = {}) {
|
|
44
59
|
const projects = [];
|
|
60
|
+
const tmplDir = templatePath(cfg, from);
|
|
45
61
|
if (!fs.existsSync(cfg.projectsDir)) return projects;
|
|
46
62
|
for (const name of fs.readdirSync(cfg.projectsDir).sort()) {
|
|
47
63
|
if (name.startsWith(".")) continue;
|
|
64
|
+
// legacy leftover
|
|
65
|
+
if (name === "template") continue;
|
|
48
66
|
const projectDir = path.join(cfg.projectsDir, name);
|
|
49
67
|
if (!fs.statSync(projectDir).isDirectory()) continue;
|
|
50
|
-
|
|
51
|
-
projects.push({
|
|
52
|
-
name,
|
|
53
|
-
kind: "template",
|
|
54
|
-
rules: countRules(path.join(projectDir, "rules")),
|
|
55
|
-
skills: countSkills(path.join(projectDir, "skills")),
|
|
56
|
-
missingRules: 0,
|
|
57
|
-
missingSkills: 0,
|
|
58
|
-
...(full
|
|
59
|
-
? {
|
|
60
|
-
path: projectDir,
|
|
61
|
-
drift: templateDrift(projectDir, cfg.templateDir),
|
|
62
|
-
}
|
|
63
|
-
: {}),
|
|
64
|
-
});
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const drift = templateDrift(projectDir, cfg.templateDir);
|
|
68
|
+
const drift = templateDrift(projectDir, tmplDir);
|
|
68
69
|
projects.push({
|
|
69
70
|
name,
|
|
70
71
|
kind: "project",
|
|
72
|
+
from,
|
|
71
73
|
rules: countRules(path.join(projectDir, "rules")),
|
|
72
74
|
skills: countSkills(path.join(projectDir, "skills")),
|
|
73
75
|
missingRules: drift.missingRules.length,
|
|
74
76
|
missingSkills: drift.missingSkills.length,
|
|
75
|
-
...(full
|
|
76
|
-
? {
|
|
77
|
-
path: projectDir,
|
|
78
|
-
drift,
|
|
79
|
-
}
|
|
80
|
-
: {}),
|
|
77
|
+
...(full ? { path: projectDir, drift } : {}),
|
|
81
78
|
});
|
|
82
79
|
}
|
|
83
80
|
return projects;
|
|
@@ -87,6 +84,9 @@ function collectStatus(opts = {}) {
|
|
|
87
84
|
const cfg = resolveConfig();
|
|
88
85
|
const full = Boolean(opts.full);
|
|
89
86
|
const agents = opts.agents || ["cursor"];
|
|
87
|
+
const from = opts.from || "base";
|
|
88
|
+
|
|
89
|
+
const migration = migrateLegacyTemplate(cfg);
|
|
90
90
|
|
|
91
91
|
const exists = fs.existsSync(cfg.dir);
|
|
92
92
|
const isGit = exists && fs.existsSync(path.join(cfg.dir, ".git"));
|
|
@@ -103,9 +103,9 @@ function collectStatus(opts = {}) {
|
|
|
103
103
|
...symlinkState(t.path, cfg.skillsDir),
|
|
104
104
|
}));
|
|
105
105
|
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const drifting =
|
|
106
|
+
const templates = collectTemplates(cfg, { full });
|
|
107
|
+
const projects = collectProjects(cfg, { full, from });
|
|
108
|
+
const drifting = projects.filter(
|
|
109
109
|
(p) => p.missingRules > 0 || p.missingSkills > 0
|
|
110
110
|
);
|
|
111
111
|
|
|
@@ -127,6 +127,9 @@ function collectStatus(opts = {}) {
|
|
|
127
127
|
for (const l of links) {
|
|
128
128
|
if (l.state !== "ok") problems.push(`cursor_${path.basename(l.path)}_${l.state}`);
|
|
129
129
|
}
|
|
130
|
+
if (!fs.existsSync(templatePath(cfg, "base")) && templates.length === 0) {
|
|
131
|
+
problems.push("templates_missing");
|
|
132
|
+
}
|
|
130
133
|
|
|
131
134
|
return {
|
|
132
135
|
generatedAt: new Date().toISOString(),
|
|
@@ -135,10 +138,17 @@ function collectStatus(opts = {}) {
|
|
|
135
138
|
branch,
|
|
136
139
|
dirty,
|
|
137
140
|
statusLine: aheadBehind,
|
|
141
|
+
layout: {
|
|
142
|
+
system: { rules: cfg.rulesDir, skills: cfg.skillsDir },
|
|
143
|
+
templates: cfg.templatesDir,
|
|
144
|
+
projects: cfg.projectsDir,
|
|
145
|
+
},
|
|
146
|
+
migration,
|
|
138
147
|
counts: {
|
|
139
148
|
rules: countRules(cfg.rulesDir),
|
|
140
149
|
skills: countSkills(cfg.skillsDir),
|
|
141
|
-
|
|
150
|
+
templates: templates.length,
|
|
151
|
+
projects: projects.length,
|
|
142
152
|
drifting: drifting.length,
|
|
143
153
|
problems: problems.length,
|
|
144
154
|
},
|
|
@@ -153,18 +163,9 @@ function collectStatus(opts = {}) {
|
|
|
153
163
|
state: l.state,
|
|
154
164
|
...(full ? { target: l.target, expected: l.expected } : {}),
|
|
155
165
|
})),
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
: {
|
|
160
|
-
name: p.name,
|
|
161
|
-
kind: p.kind,
|
|
162
|
-
rules: p.rules,
|
|
163
|
-
skills: p.skills,
|
|
164
|
-
missingRules: p.missingRules,
|
|
165
|
-
missingSkills: p.missingSkills,
|
|
166
|
-
}
|
|
167
|
-
),
|
|
166
|
+
templates,
|
|
167
|
+
projects,
|
|
168
|
+
driftFrom: from,
|
|
168
169
|
problems,
|
|
169
170
|
};
|
|
170
171
|
}
|
|
@@ -177,17 +178,20 @@ function statusHelp(data) {
|
|
|
177
178
|
if (data.links.some((l) => l.state !== "ok")) {
|
|
178
179
|
help.push("Run `ai-md doctor --fix` to repair ~/.cursor skills/rules symlinks");
|
|
179
180
|
}
|
|
181
|
+
if (data.problems.includes("templates_missing")) {
|
|
182
|
+
help.push("Create ~/.ai-md/templates/base with project starters (system skills/rules stay in skills/ and rules/)");
|
|
183
|
+
}
|
|
180
184
|
if (data.counts.drifting > 0) {
|
|
181
185
|
help.push(
|
|
182
|
-
|
|
186
|
+
`Run \`ai-md apply-template --project <name> --from ${data.driftFrom || "base"}\` to merge missing template files`
|
|
183
187
|
);
|
|
184
188
|
}
|
|
185
189
|
if (data.dirty) {
|
|
186
190
|
help.push('Run `ai-md push -m "<why>"` to commit and push private config');
|
|
187
191
|
} else {
|
|
188
|
-
help.push("
|
|
192
|
+
help.push("Edit system skills/rules under ~/.ai-md/{skills,rules}; project overlays under projects/");
|
|
189
193
|
}
|
|
190
|
-
help.push("Run `ai-md init-project --repo <path
|
|
194
|
+
help.push("Run `ai-md init-project --repo <path> --from base` to seed a project");
|
|
191
195
|
help.push("Run `ai-md status --json` for machine-readable output");
|
|
192
196
|
return help;
|
|
193
197
|
}
|
|
@@ -195,6 +199,7 @@ function statusHelp(data) {
|
|
|
195
199
|
module.exports = {
|
|
196
200
|
collectStatus,
|
|
197
201
|
collectProjects,
|
|
202
|
+
collectTemplates,
|
|
198
203
|
templateDrift,
|
|
199
204
|
statusHelp,
|
|
200
205
|
git,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dujavi/ai-md",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AXI-shaped CLI for
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "AXI-shaped CLI for ~/.ai-md system skills/rules, templates/, and projects/",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-md": "bin/ai-md.js"
|
|
7
7
|
},
|