@dujavi/ai-md 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -106
- package/bin/ai-md.js +358 -191
- package/lib/build.js +268 -0
- package/lib/commands.js +421 -99
- package/lib/config-paths.js +73 -0
- package/lib/config.js +124 -67
- package/lib/harnesses/index.js +309 -0
- package/lib/link.js +224 -0
- package/lib/remote.js +116 -0
- package/lib/rescue.js +95 -0
- package/lib/skeleton.js +117 -0
- package/lib/status.js +114 -36
- package/package.json +4 -3
- package/scripts/sync-config.sh +10 -3
- package/skeleton/README.md +3 -0
- package/skeleton/agents/agents/skills/.gitkeep +0 -0
- package/skeleton/agents/claude/rules/.gitkeep +0 -0
- package/skeleton/agents/claude/skills/.gitkeep +0 -0
- package/skeleton/agents/cursor/skills/.gitkeep +0 -0
- package/skeleton/agents/gemini/skills/.gitkeep +0 -0
- package/skeleton/agents/opencode/skills/.gitkeep +0 -0
- package/skeleton/projects/.gitkeep +0 -0
- package/skeleton/scripts/.gitkeep +0 -0
- package/skeleton/shared/rules/edit-source-not-dist.mdc +23 -0
- package/skeleton/shared/rules/personal-config.mdc +49 -0
- package/skeleton/shared/skills/ai-md-config/SKILL.md +51 -0
- package/skeleton/templates/base/rules/.gitkeep +0 -0
- package/skeleton/templates/base/rules/project-context.mdc +8 -0
- package/skeleton/templates/base/skills/.gitkeep +0 -0
package/lib/status.js
CHANGED
|
@@ -12,9 +12,16 @@ const {
|
|
|
12
12
|
countSkills,
|
|
13
13
|
listSkillNames,
|
|
14
14
|
listRuleNames,
|
|
15
|
-
symlinkState,
|
|
16
|
-
agentSkillTargets,
|
|
17
15
|
} = require("./config");
|
|
16
|
+
const { linkState } = require("./link");
|
|
17
|
+
const { distRoot, distDirty, sharedRoots } = require("./build");
|
|
18
|
+
const {
|
|
19
|
+
selectHarnesses,
|
|
20
|
+
resolveHarness,
|
|
21
|
+
isHarnessInstalled,
|
|
22
|
+
} = require("./harnesses");
|
|
23
|
+
const { isWsl } = require("./config-paths");
|
|
24
|
+
const { SKELETON_VERSION } = require("./skeleton");
|
|
18
25
|
|
|
19
26
|
function git(repo, args) {
|
|
20
27
|
try {
|
|
@@ -60,9 +67,7 @@ function collectProjects(cfg, { full = false, from = "base" } = {}) {
|
|
|
60
67
|
const tmplDir = templatePath(cfg, from);
|
|
61
68
|
if (!fs.existsSync(cfg.projectsDir)) return projects;
|
|
62
69
|
for (const name of fs.readdirSync(cfg.projectsDir).sort()) {
|
|
63
|
-
if (name.startsWith(".")) continue;
|
|
64
|
-
// legacy leftover
|
|
65
|
-
if (name === "template") continue;
|
|
70
|
+
if (name.startsWith(".") || name === "template") continue;
|
|
66
71
|
const projectDir = path.join(cfg.projectsDir, name);
|
|
67
72
|
if (!fs.statSync(projectDir).isDirectory()) continue;
|
|
68
73
|
const drift = templateDrift(projectDir, tmplDir);
|
|
@@ -83,25 +88,50 @@ function collectProjects(cfg, { full = false, from = "base" } = {}) {
|
|
|
83
88
|
function collectStatus(opts = {}) {
|
|
84
89
|
const cfg = resolveConfig();
|
|
85
90
|
const full = Boolean(opts.full);
|
|
86
|
-
const agents = opts.agents || ["cursor"];
|
|
91
|
+
const agents = opts.agents || cfg.agents || ["cursor"];
|
|
87
92
|
const from = opts.from || "base";
|
|
88
93
|
|
|
89
94
|
const migration = migrateLegacyTemplate(cfg);
|
|
90
|
-
|
|
91
95
|
const exists = fs.existsSync(cfg.dir);
|
|
92
96
|
const isGit = exists && fs.existsSync(path.join(cfg.dir, ".git"));
|
|
97
|
+
const shared = sharedRoots(cfg);
|
|
93
98
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
const { selected, skipped, warnings } = selectHarnesses(cfg, agents, {
|
|
100
|
+
forceLink: false,
|
|
101
|
+
includeUninstalled: true,
|
|
102
|
+
});
|
|
98
103
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
const links = [];
|
|
105
|
+
const harnesses = [];
|
|
106
|
+
for (const h of selected) {
|
|
107
|
+
const installed = isHarnessInstalled(h);
|
|
108
|
+
const dist = distRoot(cfg, h.distId);
|
|
109
|
+
const dirty = distDirty(dist);
|
|
110
|
+
const entry = {
|
|
111
|
+
id: h.id,
|
|
112
|
+
distId: h.distId,
|
|
113
|
+
installed,
|
|
114
|
+
aliasOf: h.aliasOf,
|
|
115
|
+
format: h.format,
|
|
116
|
+
distDirty: dirty.dirty,
|
|
117
|
+
...(full && dirty.dirty ? { dirtyFiles: dirty.files } : {}),
|
|
118
|
+
};
|
|
119
|
+
if (installed) {
|
|
120
|
+
if (h.skills) {
|
|
121
|
+
const st = linkState(h.skills, path.join(dist, "skills"));
|
|
122
|
+
links.push({ harness: h.id, kind: "skills", ...st });
|
|
123
|
+
entry.skillsLink = st.state;
|
|
124
|
+
}
|
|
125
|
+
if (h.rules && h.format !== "skills-only") {
|
|
126
|
+
const st = linkState(h.rules, path.join(dist, "rules"));
|
|
127
|
+
links.push({ harness: h.id, kind: "rules", ...st });
|
|
128
|
+
entry.rulesLink = st.state;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
entry.skillsLink = "skipped_not_installed";
|
|
132
|
+
}
|
|
133
|
+
harnesses.push(entry);
|
|
134
|
+
}
|
|
105
135
|
|
|
106
136
|
const templates = collectTemplates(cfg, { full });
|
|
107
137
|
const projects = collectProjects(cfg, { full, from });
|
|
@@ -124,44 +154,70 @@ function collectStatus(opts = {}) {
|
|
|
124
154
|
const problems = [];
|
|
125
155
|
if (!exists) problems.push("ai_md_missing");
|
|
126
156
|
else if (!isGit) problems.push("ai_md_not_git");
|
|
157
|
+
if (fs.existsSync(path.join(cfg.dir, "rules")) || fs.existsSync(path.join(cfg.dir, "skills"))) {
|
|
158
|
+
problems.push("legacy_flat_layout");
|
|
159
|
+
}
|
|
127
160
|
for (const l of links) {
|
|
128
|
-
if (l.state !== "ok"
|
|
161
|
+
if (l.state !== "ok" && l.state !== "directory") {
|
|
162
|
+
problems.push(`${l.harness}_${l.kind}_${l.state}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const h of harnesses) {
|
|
166
|
+
if (h.installed && h.distDirty) problems.push(`dist_dirty_${h.distId}`);
|
|
129
167
|
}
|
|
130
168
|
if (!fs.existsSync(templatePath(cfg, "base")) && templates.length === 0) {
|
|
131
169
|
problems.push("templates_missing");
|
|
132
170
|
}
|
|
133
171
|
|
|
172
|
+
let skeletonVersion = null;
|
|
173
|
+
try {
|
|
174
|
+
skeletonVersion = Number(
|
|
175
|
+
fs.readFileSync(path.join(cfg.dir, ".ai-md-skeleton-version"), "utf8").trim()
|
|
176
|
+
);
|
|
177
|
+
} catch {
|
|
178
|
+
/* none */
|
|
179
|
+
}
|
|
180
|
+
if (skeletonVersion != null && skeletonVersion < SKELETON_VERSION) {
|
|
181
|
+
problems.push("skeleton_outdated");
|
|
182
|
+
}
|
|
183
|
+
|
|
134
184
|
return {
|
|
135
185
|
generatedAt: new Date().toISOString(),
|
|
136
186
|
dir: cfg.dir,
|
|
137
|
-
remote: remote || cfg.remote,
|
|
187
|
+
remote: remote || cfg.remote || null,
|
|
188
|
+
configuredRemote: cfg.remote || null,
|
|
189
|
+
gitRemote: remote,
|
|
138
190
|
sources: cfg.sources,
|
|
191
|
+
remoteDetection: cfg.remoteDetection,
|
|
192
|
+
linkMode: cfg.linkMode,
|
|
193
|
+
wsl: isWsl(),
|
|
139
194
|
machineConfigPath: cfg.machineConfigPath,
|
|
140
195
|
machineConfig: cfg.machineConfig,
|
|
141
196
|
branch,
|
|
142
197
|
dirty,
|
|
143
198
|
statusLine: aheadBehind,
|
|
144
199
|
layout: {
|
|
145
|
-
|
|
200
|
+
shared: { rules: shared.rules, skills: shared.skills },
|
|
201
|
+
agents: cfg.agentsDir,
|
|
202
|
+
dist: cfg.distDir,
|
|
146
203
|
templates: cfg.templatesDir,
|
|
147
204
|
projects: cfg.projectsDir,
|
|
148
205
|
},
|
|
149
206
|
migration,
|
|
150
207
|
counts: {
|
|
151
|
-
rules: countRules(
|
|
152
|
-
skills: countSkills(
|
|
208
|
+
rules: countRules(shared.rules),
|
|
209
|
+
skills: countSkills(shared.skills),
|
|
153
210
|
templates: templates.length,
|
|
154
211
|
projects: projects.length,
|
|
155
212
|
drifting: drifting.length,
|
|
156
213
|
problems: problems.length,
|
|
157
214
|
},
|
|
215
|
+
harnesses,
|
|
216
|
+
skipped,
|
|
217
|
+
warnings,
|
|
158
218
|
links: links.map((l) => ({
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
...(full ? { target: l.target, expected: l.expected } : {}),
|
|
162
|
-
})),
|
|
163
|
-
agentLinks: agentLinks.map((l) => ({
|
|
164
|
-
agent: l.agent,
|
|
219
|
+
harness: l.harness,
|
|
220
|
+
kind: l.kind,
|
|
165
221
|
path: l.path,
|
|
166
222
|
state: l.state,
|
|
167
223
|
...(full ? { target: l.target, expected: l.expected } : {}),
|
|
@@ -169,6 +225,8 @@ function collectStatus(opts = {}) {
|
|
|
169
225
|
templates,
|
|
170
226
|
projects,
|
|
171
227
|
driftFrom: from,
|
|
228
|
+
skeletonVersion,
|
|
229
|
+
packageSkeletonVersion: SKELETON_VERSION,
|
|
172
230
|
problems,
|
|
173
231
|
};
|
|
174
232
|
}
|
|
@@ -177,7 +235,12 @@ function statusHelp(data) {
|
|
|
177
235
|
const help = [];
|
|
178
236
|
if (data.problems.includes("ai_md_missing") || data.problems.includes("ai_md_not_git")) {
|
|
179
237
|
help.push(
|
|
180
|
-
"Run `ai-md
|
|
238
|
+
"Run `ai-md init` (no remote) or `ai-md setup --remote <git-url>`"
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
if (data.problems.includes("legacy_flat_layout")) {
|
|
242
|
+
help.push(
|
|
243
|
+
"Migrate flat rules/skills into shared/ (and agents/<id>/); see package README"
|
|
181
244
|
);
|
|
182
245
|
}
|
|
183
246
|
if (!data.machineConfig) {
|
|
@@ -185,23 +248,38 @@ function statusHelp(data) {
|
|
|
185
248
|
"Persist remote/dir with `ai-md setup --remote <url>` or `ai-md config set --remote <url> --dir ~/.ai-md`"
|
|
186
249
|
);
|
|
187
250
|
}
|
|
188
|
-
if (data.links.some((l) => l.state !== "ok")) {
|
|
189
|
-
help.push("Run `ai-md doctor --fix` to
|
|
251
|
+
if (data.links.some((l) => l.state !== "ok" && l.state !== "directory")) {
|
|
252
|
+
help.push("Run `ai-md doctor --fix` to rebuild dist and repair links");
|
|
253
|
+
}
|
|
254
|
+
if (data.problems.some((p) => p.startsWith("dist_dirty_"))) {
|
|
255
|
+
help.push("Run `ai-md rescue --agents <id>` or `ai-md build --force`");
|
|
256
|
+
}
|
|
257
|
+
if (data.problems.includes("skeleton_outdated")) {
|
|
258
|
+
help.push("Run `ai-md seed-skeleton` to add new recommended files");
|
|
259
|
+
}
|
|
260
|
+
if (data.wsl) {
|
|
261
|
+
help.push(
|
|
262
|
+
"WSL detected: use Windows ai-md for Windows Cursor (homes differ)"
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (data.skipped && data.skipped.some((s) => s.reason === "not_installed")) {
|
|
266
|
+
help.push(
|
|
267
|
+
"Some harnesses skipped (AI not installed). Install the tool or use --force-link"
|
|
268
|
+
);
|
|
190
269
|
}
|
|
191
270
|
if (data.problems.includes("templates_missing")) {
|
|
192
|
-
help.push("
|
|
271
|
+
help.push("Run `ai-md seed-skeleton` or add templates/base");
|
|
193
272
|
}
|
|
194
273
|
if (data.counts.drifting > 0) {
|
|
195
274
|
help.push(
|
|
196
|
-
`Run \`ai-md apply-template --project <name> --from ${data.driftFrom || "base"}
|
|
275
|
+
`Run \`ai-md apply-template --project <name> --from ${data.driftFrom || "base"}\``
|
|
197
276
|
);
|
|
198
277
|
}
|
|
199
278
|
if (data.dirty) {
|
|
200
|
-
help.push('Run `ai-md push -m "<why>"` to commit and push
|
|
279
|
+
help.push('Run `ai-md push -m "<why>"` to commit and push');
|
|
201
280
|
} else {
|
|
202
|
-
help.push("Edit
|
|
281
|
+
help.push("Edit shared/ or agents/<id>/ only; then `ai-md build`");
|
|
203
282
|
}
|
|
204
|
-
help.push("Run `ai-md init-project --repo <path> --from base` to seed a project");
|
|
205
283
|
help.push("Run `ai-md status --json` for machine-readable output");
|
|
206
284
|
return help;
|
|
207
285
|
}
|
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.6.1",
|
|
4
|
+
"description": "AXI-shaped CLI for private ~/.ai-md: shared+agents build to dist, multi-harness link (Cursor, Claude, agents, …)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ai-md": "bin/ai-md.js"
|
|
7
7
|
},
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"bin/",
|
|
10
10
|
"lib/",
|
|
11
11
|
"scripts/",
|
|
12
|
+
"skeleton/",
|
|
12
13
|
"docs/",
|
|
13
14
|
"README.md",
|
|
14
15
|
"LICENSE"
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
"node": ">=18"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
|
-
"test": "node bin/ai-md.js --help && node bin/ai-md.js status --json >/dev/null"
|
|
21
|
+
"test": "node bin/ai-md.js --help && node bin/ai-md.js harness list --json >/dev/null && node bin/ai-md.js status --json >/dev/null"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@toon-format/toon": "^2.3.1"
|
package/scripts/sync-config.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
set -euo pipefail
|
|
5
5
|
|
|
6
6
|
REPO="${AI_MD_DIR:-${CURSOR_MD_DIR:-$HOME/.ai-md}}"
|
|
7
|
-
REMOTE_URL="${AI_MD_REMOTE:-${CURSOR_MD_REMOTE:-
|
|
7
|
+
REMOTE_URL="${AI_MD_REMOTE:-${CURSOR_MD_REMOTE:-}}"
|
|
8
8
|
DRY_RUN=0
|
|
9
9
|
FORCE=0
|
|
10
10
|
COMMIT_MSG="Update personal AI skills/rules"
|
|
@@ -32,10 +32,10 @@ Options:
|
|
|
32
32
|
|
|
33
33
|
Environment:
|
|
34
34
|
AI_MD_DIR Private config path (default: ~/.ai-md)
|
|
35
|
-
AI_MD_REMOTE Clone URL if repo missing (default
|
|
35
|
+
AI_MD_REMOTE Clone URL if repo missing (required for install clone; no hardcoded default)
|
|
36
36
|
|
|
37
37
|
Examples:
|
|
38
|
-
ai-md install
|
|
38
|
+
AI_MD_REMOTE=https://github.com/<you>/.ai-md.git ai-md install
|
|
39
39
|
ai-md pull
|
|
40
40
|
ai-md push -m "Add grafana rule"
|
|
41
41
|
ai-md status
|
|
@@ -82,6 +82,13 @@ clone_if_missing() {
|
|
|
82
82
|
fi
|
|
83
83
|
if [[ -d "$REPO" ]] && [[ -n "$(ls -A "$REPO" 2>/dev/null || true)" ]]; then
|
|
84
84
|
err "$REPO exists but is not a git repo (and is not empty)"
|
|
85
|
+
err " Move it aside, then clone — do not seed a skeleton over an unsynced remote tree"
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
if [[ -z "$REMOTE_URL" ]]; then
|
|
89
|
+
err "No remote set and $REPO is missing/empty."
|
|
90
|
+
err " export AI_MD_REMOTE=https://github.com/<you>/.ai-md.git"
|
|
91
|
+
err " or: ai-md setup --remote <url> / ai-md init"
|
|
85
92
|
exit 1
|
|
86
93
|
fi
|
|
87
94
|
log "Cloning $REMOTE_URL → $REPO"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Edit ~/.ai-md shared/ and agents/ only — never live harness dirs or dist/
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Edit source, not dist or live harness paths
|
|
7
|
+
|
|
8
|
+
`~/.ai-md` is the source of truth. Live tool dirs (`~/.cursor`, `~/.claude`, `~/.agents`, …) and `~/.ai-md/dist/` are **build outputs**.
|
|
9
|
+
|
|
10
|
+
## Do
|
|
11
|
+
|
|
12
|
+
- Edit `~/.ai-md/shared/` for rules/skills that apply to all harnesses.
|
|
13
|
+
- Edit `~/.ai-md/agents/<harness>/` for harness-specific rules/skills.
|
|
14
|
+
- After edits: `ai-md build` (or `ai-md pull` / `install`, which rebuild).
|
|
15
|
+
|
|
16
|
+
## Do not
|
|
17
|
+
|
|
18
|
+
- Edit `~/.cursor/{rules,skills}`, `~/.claude/…`, `~/.agents/…`, or any other live harness path.
|
|
19
|
+
- Edit `~/.ai-md/dist/` directly.
|
|
20
|
+
|
|
21
|
+
## If you already edited a live path
|
|
22
|
+
|
|
23
|
+
Stop. Run `ai-md rescue --agents <harness>` to promote dirty files into `agents/<harness>/`, then review and `ai-md build`. Do not keep iterating under dist or live dirs.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Sync global personal skills and rules via ~/.ai-md and the ai-md CLI (build → dist → link)
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Personal skills and rules — ai-md
|
|
7
|
+
|
|
8
|
+
**`~/.ai-md`** is the private source of truth. Use **`ai-md`** (`@dujavi/ai-md`) — not Cursor Sync / Gist.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
| Path | Role |
|
|
13
|
+
|------|------|
|
|
14
|
+
| `shared/rules`, `shared/skills` | System base for all harnesses |
|
|
15
|
+
| `agents/<id>/` | Harness-specific overlays |
|
|
16
|
+
| `dist/<id>/` | Build output (gitignored) — do not edit |
|
|
17
|
+
| `templates/<type>/` | Project-type starters |
|
|
18
|
+
| `projects/<name>/` | Per-app overlays |
|
|
19
|
+
| `scripts/` | Private machine scripts |
|
|
20
|
+
|
|
21
|
+
See also `edit-source-not-dist.mdc` and skill `ai-md-config`.
|
|
22
|
+
|
|
23
|
+
## Sync
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ai-md pull
|
|
27
|
+
# edit shared/ or agents/<id>/
|
|
28
|
+
ai-md build
|
|
29
|
+
ai-md push -m "why"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`install` / `pull` / `doctor --fix` rebuild and link **only for installed AIs** (detect `~/.cursor`, `claude` on PATH, etc.). Use `--force-link` to override.
|
|
33
|
+
|
|
34
|
+
## Where to edit
|
|
35
|
+
|
|
36
|
+
| What | Edit here |
|
|
37
|
+
|------|-----------|
|
|
38
|
+
| Cross-harness rules/skills | `~/.ai-md/shared/` |
|
|
39
|
+
| Cursor-only (e.g. Grok CLI policy) | `~/.ai-md/agents/cursor/` |
|
|
40
|
+
| Project starters | `~/.ai-md/templates/<type>/` |
|
|
41
|
+
| App overlays | `~/.ai-md/projects/<repo>/` |
|
|
42
|
+
|
|
43
|
+
Never edit `~/.cursor/skills-cursor/`, live harness dirs, or `dist/`.
|
|
44
|
+
|
|
45
|
+
## Agent behavior
|
|
46
|
+
|
|
47
|
+
- Global changes → `shared/` or `agents/<id>/`; remind `ai-md build` then `push`.
|
|
48
|
+
- Symlink / dist issues → `ai-md doctor --fix` or `ai-md rescue`.
|
|
49
|
+
- New harness → `ai-md harness set …` then content under `agents/<id>/`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai-md-config
|
|
3
|
+
description: >-
|
|
4
|
+
Manage personal AI config in ~/.ai-md: shared vs agents layers, build/dist,
|
|
5
|
+
harness registration, rescue dirty dist. Use when adding rules/skills, fixing
|
|
6
|
+
links, or supporting a new AI tool.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ai-md config playbook
|
|
10
|
+
|
|
11
|
+
## Layout
|
|
12
|
+
|
|
13
|
+
| Path | Role |
|
|
14
|
+
|------|------|
|
|
15
|
+
| `shared/rules`, `shared/skills` | All harnesses |
|
|
16
|
+
| `agents/<id>/rules`, `agents/<id>/skills` | One harness (overlay wins on name conflict) |
|
|
17
|
+
| `dist/<id>/` | Generated — never edit |
|
|
18
|
+
| `projects/<name>/` | Per-repo overlays (`.cursor` → here) |
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ai-md pull / push -m "why"
|
|
24
|
+
ai-md build [--agents cursor,claude] [--force]
|
|
25
|
+
ai-md rescue --agents cursor
|
|
26
|
+
ai-md harness list | show <id>
|
|
27
|
+
ai-md harness set <id> --skills ~/path [--rules ~/path] [--format mdc|md|skills-only]
|
|
28
|
+
ai-md harness enable|disable <id>
|
|
29
|
+
ai-md init | seed-skeleton
|
|
30
|
+
ai-md doctor --fix
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Add a rule or skill
|
|
34
|
+
|
|
35
|
+
1. Choose layer: `shared/` (all tools) vs `agents/<id>/` (one tool).
|
|
36
|
+
2. Write the file under that path (never under `dist/` or `~/.cursor/…`).
|
|
37
|
+
3. `ai-md build`
|
|
38
|
+
4. `ai-md push -m "…"` when ready.
|
|
39
|
+
|
|
40
|
+
## New harness
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ai-md harness set my-tool --skills ~/.my-tool/skills --rules ~/.my-tool/rules --format md
|
|
44
|
+
# put tool-specific content in agents/my-tool/
|
|
45
|
+
ai-md build --agents my-tool
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Unique vs shared `.agents`
|
|
49
|
+
|
|
50
|
+
- Unique live roots: `cursor`, `claude`, `gemini`, `opencode`, `copilot`
|
|
51
|
+
- Shared `~/.agents/skills`: `agents` (canonical writer); `codex` is an alias (no second emit)
|
|
File without changes
|
|
File without changes
|