@maccesar/aiskills 1.20.0 → 1.21.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 CHANGED
@@ -324,13 +324,15 @@ The convention:
324
324
 
325
325
  | File | Holds | Loaded at startup |
326
326
  | --- | --- | --- |
327
- | `status.md` | Where the work stands: half-done things, next step, what's blocked, deployment state | **No** |
327
+ | `status.md` | Where the work stands: half-done things, next step, what's blocked, deployment state, which assistant wrote the note | **No** |
328
328
  | `requirements.md` | What the system must do, and the acceptance criterion for each item | Yes |
329
329
  | `decisions.md` | What was chosen and why. Append-only, dated | Yes |
330
- | `context.md` | Documentation map, architecture, conventions, traps | Yes |
330
+ | `context.md` | Documentation map, architecture, conventions, traps, provenance | Yes |
331
331
 
332
332
  **Why `status.md` is excluded from startup.** Cached context is matched as a prefix — the first byte that differs invalidates everything after it. Status written inside a startup-loaded file means every update throws away the cache for all the stable content behind it. The file you edit most often is the one that must not load at startup.
333
333
 
334
+ **It also records what built the project.** Months in, a question comes up that the four files used to leave unanswered: what was this made with? You want to reopen a piece of work by referring back to it, and that only works in the tool that still holds the transcript — a model remembers nothing between sessions, and Codex, Claude Code and Gemini each keep their own history where no other one can read it. So `context.md` gets a provenance table naming the tool, the model and what it produced, and `status.md` gets a line naming what wrote that note. One row per stretch of work, never per session: a row per session would grow without bound inside a startup-loaded file, which is the problem the split above exists to avoid. The model is recorded only as the environment states it — an invented model id reads as verified whether or not anyone checked it.
335
+
334
336
  How to use it — just say it, in whatever words you'd use anyway:
335
337
 
336
338
  ```
@@ -9,8 +9,10 @@ import { join } from 'path';
9
9
  import os from 'os';
10
10
  import {
11
11
  SKILLS,
12
+ COMMANDS,
12
13
  PACKAGE_VERSION,
13
14
  getAgentsSkillsDir,
15
+ getClaudeCommandsDir,
14
16
  getConfigDir,
15
17
  getPlatforms,
16
18
  } from '../config.js';
@@ -20,6 +22,7 @@ import {
20
22
  isClaudePluginEnabled,
21
23
  hasClaudePluginCache,
22
24
  pluginProvidesSkill,
25
+ pluginProvidesCommand,
23
26
  } from '../claude-plugin.js';
24
27
 
25
28
  const CHECK = chalk.green('✓');
@@ -30,6 +33,7 @@ export async function doctorCommand() {
30
33
  const homeDir = os.homedir();
31
34
  const skillsDir = getAgentsSkillsDir(homeDir);
32
35
  const claudeDir = join(homeDir, '.claude');
36
+ const commandsDir = getClaudeCommandsDir(homeDir);
33
37
  const cacheDir = getConfigDir();
34
38
 
35
39
  let issues = 0;
@@ -59,6 +63,27 @@ export async function doctorCommand() {
59
63
  issues += missingSkills.length;
60
64
  }
61
65
 
66
+ // Slash commands the enabled marketplace plugin serves are intentionally
67
+ // absent from ~/.claude/commands/ to avoid duplicate autocomplete entries.
68
+ const missingCommands = [];
69
+ const pluginCommands = [];
70
+ for (const command of COMMANDS) {
71
+ if (pluginProvidesCommand(command, homeDir)) {
72
+ pluginCommands.push(command);
73
+ } else if (!existsSync(join(commandsDir, `${command}.md`))) {
74
+ missingCommands.push(command);
75
+ }
76
+ }
77
+ const expectedCommands = COMMANDS.length - pluginCommands.length;
78
+ if (pluginCommands.length === COMMANDS.length) {
79
+ console.log(` ${CHECK} Slash commands: all ${COMMANDS.length} served by the marketplace plugin`);
80
+ } else if (missingCommands.length === 0) {
81
+ console.log(` ${CHECK} Slash commands: ${expectedCommands}/${expectedCommands} installed in ~/.claude/commands/`);
82
+ } else {
83
+ console.log(` ${CROSS} Slash commands: ${expectedCommands - missingCommands.length}/${expectedCommands} installed (missing: ${missingCommands.join(', ')})`);
84
+ issues += missingCommands.length;
85
+ }
86
+
62
87
  // Hook check
63
88
  if (hasHook(claudeDir)) {
64
89
  console.log(` ${CHECK} Hook: SessionStart configured`);
@@ -304,11 +304,21 @@ export async function skillsCommand(options) {
304
304
  skillsToInstall,
305
305
  baseDir
306
306
  );
307
- if (symlinkResult.linked.length === skillsToInstall.length) {
308
- spinner.succeed(`${platform.displayName}: Skills linked`);
307
+ const expected = skillsToInstall.length - symlinkResult.skipped.length;
308
+ const pluginNote =
309
+ symlinkResult.skipped.length > 0
310
+ ? ` (${symlinkResult.skipped.length} served by the marketplace plugin)`
311
+ : '';
312
+
313
+ if (expected === 0) {
314
+ spinner.info(
315
+ `${platform.displayName}: All ${symlinkResult.skipped.length} skills served by the marketplace plugin`
316
+ );
317
+ } else if (symlinkResult.linked.length === expected) {
318
+ spinner.succeed(`${platform.displayName}: Skills linked${pluginNote}`);
309
319
  } else {
310
320
  spinner.warn(
311
- `${platform.displayName}: ${symlinkResult.linked.length}/${skillsToInstall.length} skills linked`
321
+ `${platform.displayName}: ${symlinkResult.linked.length}/${expected} skills linked${pluginNote}`
312
322
  );
313
323
  }
314
324
  }
package/lib/config.js CHANGED
@@ -52,7 +52,8 @@ export const LEGACY_COMMANDS = [];
52
52
  export const getConfigDir = () => path.join(os.homedir(), '.aiskills');
53
53
 
54
54
  // Directory paths
55
- export const getAgentsSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.agents', 'skills');
55
+ export const getAgentsDir = (baseDir = os.homedir()) => path.join(baseDir, '.agents');
56
+ export const getAgentsSkillsDir = (baseDir = os.homedir()) => path.join(getAgentsDir(baseDir), 'skills');
56
57
  export const getClaudeSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'skills');
57
58
  export const getClaudeCommandsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'commands');
58
59
  export const getGeminiSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.gemini', 'skills');
@@ -111,11 +112,17 @@ export default {
111
112
  COMMANDS,
112
113
  LEGACY_COMMANDS,
113
114
  getConfigDir,
115
+ getAgentsDir,
114
116
  getAgentsSkillsDir,
115
117
  getClaudeSkillsDir,
116
118
  getClaudeCommandsDir,
117
119
  getGeminiSkillsDir,
118
120
  getCodexSkillsDir,
121
+ CLAUDE_PLUGIN_MARKETPLACE,
122
+ CLAUDE_PLUGIN_NAME,
123
+ CLAUDE_PLUGIN_KEY,
124
+ getClaudePluginSkillsPath,
125
+ getClaudeSettingsPaths,
119
126
  getPlatforms,
120
127
  GITHUB_API_HEADERS,
121
128
  };
package/lib/installer.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  getClaudeCommandsDir,
19
19
  } from './config.js';
20
20
  import { removeSkills, removeCommands } from './cleanup.js';
21
+ import { createSymlinkOrCopy } from './symlink.js';
21
22
 
22
23
  /**
23
24
  * Recursively copy a directory
@@ -37,6 +38,8 @@ export async function copyDirectory(src, dest) {
37
38
  * @param {string} repoDir - Repository directory
38
39
  * @param {string} skillName - Name of the skill
39
40
  * @param {string} baseDir - Base directory for installation
41
+ * Development checkouts are linked so `npm link` users see edits immediately.
42
+ * Published npm packages are copied so installed skills remain independent.
40
43
  * @returns {Promise<boolean>} True if installed successfully
41
44
  */
42
45
  export async function installSkill(repoDir, skillName, baseDir = os.homedir()) {
@@ -52,6 +55,10 @@ export async function installSkill(repoDir, skillName, baseDir = os.homedir()) {
52
55
  return false;
53
56
  }
54
57
 
58
+ if (existsSync(join(repoDir, '.git'))) {
59
+ return createSymlinkOrCopy(skillSrc, skillDest, true);
60
+ }
61
+
55
62
  if (existsSync(skillDest)) {
56
63
  await remove(skillDest);
57
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maccesar/aiskills",
3
- "version": "1.20.0",
3
+ "version": "1.21.1",
4
4
  "description": "AI coding assistant skills for Claude Code, Gemini CLI, and Codex CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -56,6 +56,8 @@
56
56
  "bin/",
57
57
  "lib/",
58
58
  "skills/",
59
+ "!skills/**/__pycache__/",
60
+ "!skills/**/*.py[cod]",
59
61
  "commands/"
60
62
  ],
61
63
  "publishConfig": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-log
3
- description: 'The convention that decides WHERE a project keeps its working state, and why part of it must not load at startup. Four fixed files under docs/project/ — status (volatile, never imported), requirements, decisions, context (stable, imported) — plus a pointer written into every context file the repo has, so the notes are findable from Claude Code, Codex or Gemini alike. Use this whenever someone closes a working session or picks one up ("ya me voy, déjame anotado dónde quedé", "where did we leave off?"), asks where project notes should live or why they keep ending up scattered, wonders whether progress and dates belong inside CLAUDE.md / AGENTS.md / GEMINI.md, or wants project tracking set up or migrated — even when they never say "notes" or name this skill. Not for: releases and version bumps, commit messages, CHANGELOG entries, build or deploy status, issue trackers, or a spoken recap the user only wants to read.'
3
+ description: 'The convention that decides WHERE a project keeps its working state, and why part of it must not load at startup. Four fixed files under docs/project/ — status (volatile, never imported), requirements, decisions, context (stable, imported) — plus a pointer written into every context file the repo has, so the notes are findable from Claude Code, Codex or Gemini alike. Use this whenever someone closes a working session or picks one up ("ya me voy, déjame anotado dónde quedé", "where did we leave off?"), asks where project notes should live or why they keep ending up scattered, wonders whether progress and dates belong inside CLAUDE.md / AGENTS.md / GEMINI.md, asks which assistant or model built a project and which one to go back to for a follow-up, or wants project tracking set up or migrated — even when they never say "notes" or name this skill. Not for: releases and version bumps, commit messages, CHANGELOG entries, build or deploy status, issue trackers, or a spoken recap the user only wants to read.'
4
4
  ---
5
5
 
6
6
  # Session Log
@@ -156,6 +156,39 @@ An acceptance criterion is the useful half. "Payments work" isn't checkable; "ch
156
156
 
157
157
  Proposal, requirements gathering, design — the work is real and it's exactly where people forget where they left off, but there's no diff to verify against. Adapt rather than skip: `requirements.md` and `decisions.md` carry the weight, `status.md` records what the client agreed and what's still open, and verification runs against the artifacts that do exist — a requirement that says "as agreed in the proposal" can be checked against the proposal. Don't create empty files waiting for a phase that hasn't arrived; `references/file-layout.md` covers how to size this down.
158
158
 
159
+ ## Which assistant did the work
160
+
161
+ Months into a project a question comes up that none of the four files answers: what was this built with? It matters for a concrete reason — someone wants to reopen a piece of work by referring back to it, the way people refer to a conversation they had, and pick up a detail in the payments screen from last month. That only works in the tool that still holds the transcript.
162
+
163
+ The intuition behind it is slightly wrong in a way that changes the answer, so it's worth being precise. The model remembers nothing between sessions; there is no continuity to return to. What exists is the **transcript, stored locally by the tool** — Codex keeps its own, Claude Code keeps its own, Gemini keeps its own, and none of them can read another's. So the thing worth recording is which tool was driving, with the model next to it, because the same tool behaves differently across models and the model is what you'd pick again.
164
+
165
+ That belongs in `context.md`, and it earns a place in a startup-loaded file the same way the rest of that file does: it explains what the code doesn't say. A codebase whose scaffolding came from one assistant and whose auth rewrite came from another has two styles in it for a reason, and knowing that is the difference between "this is inconsistent" and "this had two authors".
166
+
167
+ One row per **stretch of work**, never per session:
168
+
169
+ | When | Assistant · model | What it produced |
170
+ | --- | --- | --- |
171
+ | 2026-06 | Codex · gpt-5.6-sol | Initial scaffolding, API layer |
172
+ | 2026-08-17 | Claude Code · Opus 5 (`claude-opus-5`) | Auth rewrite, test suite |
173
+
174
+ A row per session would grow without end and invalidate the cached prefix on every update — the one failure this convention exists to prevent. Add a row when a new assistant or model touches the project, or when one of them does something substantial enough that you'd want to return to it. Ten sessions of the same model on the same feature is one row whose date extends.
175
+
176
+ `status.md` carries the volatile half: a `**Session by:**` line naming what wrote that note. It gets overwritten like the rest of the file, and `git log docs/project/status.md` then hands you the session-by-session history for free — which is why nothing has to accumulate anywhere.
177
+
178
+ ### Record what the environment tells you, not what you infer
179
+
180
+ An assistant is a poor witness to its own identity. The tool name and the model are usually stated somewhere concrete — a line in the system prompt, a `--model` flag, a config file — and that is what to write down. **When you don't have it, name the tool and leave the model out**, or ask.
181
+
182
+ An invented model id is worse than an absent one, because it reads as something that was checked. `gpt-5.6-sol` and `claude-opus-5` are exactly the kind of string that looks verified whether or not it is, and months later nobody can tell which. Write the commercial name and the id when you have both — commercial names get reused across versions, ids don't — and always the date, which is what makes a wrong guess recoverable.
183
+
184
+ ### If the continuation depends on a transcript, the record failed
185
+
186
+ The table is a workaround, and mistaking it for the fix is the trap. Needing the original tool's transcript to continue means the reasoning behind that work exists only in a chat log: on one machine, inside one vendor's directory, unsearchable by anyone else and gone the day the project folder moves.
187
+
188
+ That reasoning belongs in `decisions.md`, and the shape of the work in `context.md`. Do that and the table becomes a convenience — "this part came from Codex, which is why it's structured differently" — instead of the only way back in. A project any assistant can pick up is the goal; the table just says which one has the shortcut.
189
+
190
+ Where each tool keeps its transcripts, and why Claude Code's are keyed to the project's absolute path — so moving the folder orphans them — is in `references/file-layout.md`.
191
+
159
192
  ## Which job this is
160
193
 
161
194
  Three jobs share this skill — install the convention, resume work, close a session — and the first has a variant worth catching before you write anything. Which one it is depends on the repo and on whether the person is arriving or leaving, not on how the request was phrased. Look before deciding:
@@ -220,7 +253,7 @@ Three more things belong in `status.md` and are routinely left out:
220
253
  - **What's blocked by someone else.** A client who hasn't sent the copy, a store review, a provider whose sandbox is down. These read like pending work but can't be unblocked by working, so mixing them into the technical list makes the list lie about what's actionable.
221
254
  - **Which phase the project is in** — proposal, requirements, design, build, testing, live. One line. It tells whoever arrives which of these files matters today.
222
255
 
223
- Update `requirements.md`, `decisions.md` or `context.md` only when something genuinely stable changed: scope moved, a choice was made, a new document appeared. Most sessions change nothing there, and that's normal.
256
+ Update `requirements.md`, `decisions.md` or `context.md` only when something genuinely stable changed: scope moved, a choice was made, a new document appeared, a different assistant or model took over a stretch of the work. Most sessions change nothing there, and that's normal.
224
257
 
225
258
  **When `status.md` grows, the cause is usually stable content that drifted in.** The test is simple: would this text be the same next week? A test checklist, an acceptance walkthrough, a list of platform-specific gotchas — those don't change between sessions, so they belong in `requirements.md` or `context.md` even though you're using them right now. Being *currently relevant* is not the same as being *volatile*, and confusing the two is how the volatile file ends up carrying half the project.
226
259
 
@@ -142,6 +142,38 @@
142
142
  "Does not create a docs/project/ inside apps/api or apps/web",
143
143
  "Pointer block written into the root AGENTS.md"
144
144
  ]
145
+ },
146
+ {
147
+ "id": 9,
148
+ "name": "which-assistant-built-this",
149
+ "prompt": "ya no me acuerdo con qué hice este proyecto. si quiero seguirle a la pantalla de pagos, a cuál le pido?",
150
+ "fixture": "gym-api-provenance",
151
+ "fixture_spec": "Laravel repo with docs/project/ installed. context.md has a Provenance section with two rows: 2026-06 Codex · gpt-5.6-sol (initial scaffolding, API layer) and 2026-08 Claude Code · Opus 5 (auth rewrite). The payments screen is not named in either row. status.md carries a Session by line naming Claude Code · Opus 5.",
152
+ "expected_output": "Answers from the provenance table, says which stretch each assistant produced, and is explicit that the payments screen is not attributed in the table rather than guessing — offering git log or the CHANGELOG as the way to narrow it.",
153
+ "assertions": [
154
+ "Answers from the provenance table in context.md rather than guessing",
155
+ "Does not attribute the payments screen to an assistant the table does not attribute it to",
156
+ "Suggests a checkable source (git log, commit authorship, CHANGELOG) for the part the table cannot answer",
157
+ "Does not claim that a model remembers the earlier work itself",
158
+ "Writes nothing — this is a read, not a session close"
159
+ ]
160
+ },
161
+ {
162
+ "id": 10,
163
+ "name": "first-session-by-a-new-assistant",
164
+ "prompt": "cierra la sesión. este proyecto lo empecé con Codex hace como dos meses y hoy fue la primera vez que le meto mano desde aquí",
165
+ "fixture": "tienda-app-no-provenance",
166
+ "fixture_spec": "Titanium/Alloy repo with docs/project/ installed and no Provenance section in context.md. Uncommitted work from today: two new controllers and a change to tiapp.xml. git log shows commits from two months ago authored with a Codex co-author trailer, then nothing until today.",
167
+ "expected_output": "Writes the handoff to status.md with a Session by line, and adds a Provenance section to context.md with a row for Codex (dated from the commit history) and a row for this session's assistant and model as reported by the environment.",
168
+ "assertions": [
169
+ "Adds a Provenance section to context.md with a row for the earlier Codex work and a row for this session",
170
+ "The date for the earlier row comes from the commit history rather than being invented",
171
+ "Names its own tool and model as the environment reports them, with no invented model id",
172
+ "status.md gains a Session by line naming the tool that wrote the note",
173
+ "Does not add one row per session or turn the table into a session log",
174
+ "The handoff itself still reflects the real uncommitted work (the two controllers and tiapp.xml)",
175
+ "Does not commit, tag or push"
176
+ ]
145
177
  }
146
178
  ]
147
179
  }
@@ -10,6 +10,7 @@ Read this when installing the convention in a project or migrating one that keep
10
10
  - [Two variants](#two-variants)
11
11
  - [Repos that come in pairs](#repos-that-come-in-pairs)
12
12
  - [Templates](#templates)
13
+ - [Where each assistant keeps its transcripts](#where-each-assistant-keeps-its-transcripts)
13
14
  - [Migrating an existing project](#migrating-an-existing-project)
14
15
  - [Sizing](#sizing)
15
16
 
@@ -114,6 +115,8 @@ The only file that changes every session. Keep it short enough that someone actu
114
115
  # Status — <YYYY-MM-DD>
115
116
 
116
117
  **Phase:** <proposal · requirements · design · build · testing · live>
118
+ **Session by:** <tool · model that wrote this note — drop the model if you
119
+ can't confirm it from the environment>
117
120
  **Deployed:** <what's in production and since when — or "nothing yet">
118
121
  **Branch:** <branch, and whether it's pushed>
119
122
  **Sibling:** <other repo of this same project, and what it's waiting on — omit
@@ -146,7 +149,9 @@ The only file that changes every session. Keep it short enough that someone actu
146
149
 
147
150
  Absolute dates, never "yesterday" — the file outlives the session that wrote it.
148
151
 
149
- The three header lines exist because they're the questions asked first and answered worst. **Deployed is not the same as committed**: on a project that deploys by file sync, a change can be live and uncommitted, or committed and never uploaded. Write what you know and mark what you don't; a confident wrong answer here sends someone debugging the wrong copy of the code.
152
+ The header lines exist because they're the questions asked first and answered worst. **Deployed is not the same as committed**: on a project that deploys by file sync, a change can be live and uncommitted, or committed and never uploaded. Write what you know and mark what you don't; a confident wrong answer here sends someone debugging the wrong copy of the code.
153
+
154
+ **Session by** is overwritten every session, on purpose — `git log docs/project/status.md` is the session-by-session history, so nothing needs to pile up here. The lasting record of which assistant produced which stretch of the project is the provenance table in `context.md`.
150
155
 
151
156
  ### `docs/project/requirements.md`
152
157
 
@@ -254,10 +259,31 @@ How the project is put together, and what a newcomer would get wrong on day one.
254
259
 
255
260
  ## Traps
256
261
  - <The thing that cost someone a day, and how to avoid it.>
262
+
263
+ ## Provenance
264
+ | When | Assistant · model | What it produced |
265
+ | --- | --- | --- |
266
+ | <YYYY-MM> | <tool · model, id in backticks if known> | <the stretch of work> |
257
267
  ```
258
268
 
259
269
  The map goes first because it's what someone new needs first: knowing what exists saves them from rewriting it. List every document you found, including the ones that turned out to be stale — marked as stale. A document omitted from the map is a document nobody will open again.
260
270
 
271
+ **Provenance goes last** because it's the section consulted least often and the one that most easily turns into noise. One row per stretch of work, not per session: a row per session grows without bound inside an imported file, which is the cache problem this convention exists to avoid. Extend an existing row's date rather than adding a row for the same model doing more of the same thing.
272
+
273
+ ### Where each assistant keeps its transcripts
274
+
275
+ The provenance table says which tool to go back to. This is where that tool's memory of the work actually sits — and none of them can read another's, which is the whole reason the table is worth keeping.
276
+
277
+ | Tool | Transcripts |
278
+ | --- | --- |
279
+ | Claude Code | `~/.claude/projects/<absolute-path-with-slashes-as-dashes>/` |
280
+ | Codex | `~/.codex/sessions/<year>/…`, older ones under `~/.codex/archived_sessions/` |
281
+ | Gemini CLI | under `~/.gemini/` |
282
+
283
+ Check the layout on the machine instead of trusting this table. These are vendor-internal directories, they get reorganized without notice, and a confidently wrong path here sends someone hunting for a history that is sitting somewhere else.
284
+
285
+ Claude Code's key is the project's **absolute path**, with `/` replaced by `-`. Rename or move the project folder and the slug changes: the old transcripts stay on disk, unreachable, and the project starts again with no history. That is a good reason not to let a chat log be the only record of a decision — and the reason the four files live inside the repo, where they travel with it.
286
+
261
287
  ## Migrating an existing project
262
288
 
263
289
  Projects that already keep notes somewhere — `.claude/memory/`, a roadmap under `docs/`, a planning doc — get migrated rather than duplicated. Two locations is the problem this convention exists to solve.