Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6328816584 | ||
|
|
c52e351954 | ||
|
|
529ed31cef | ||
|
|
17d67fbfa9 | ||
|
|
18fbdbc8e4 | ||
|
|
c2c56ff948 | ||
|
|
3ff0741857 | ||
|
|
8c583b5fd5 | ||
|
|
cacfa1b374 | ||
|
|
f30fbacf14 | ||
|
|
f22836ff7e | ||
|
|
4357da5b4d | ||
|
|
b6a5915520 | ||
|
|
025ad4a5af |
No files matched your search
@@ -1,59 +1,59 @@
|
||||
{
|
||||
"name": "holocron",
|
||||
"description": "AI development skills for Claude Code, and for GitHub Copilot through apm — factory, design, implement, review, and cross-cutting workflows.",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.2",
|
||||
"owner": {
|
||||
"name": "Defame1297",
|
||||
"email": "[email protected]",
|
||||
"url": "https://git.dev.rkdr.net/Defame1297/"
|
||||
"url": "https://git.rkdr.net/Defame1297/"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "kyberforge",
|
||||
"description": "Skills and agents for creating, maintaining, and managing an apm plugin marketplace for Claude Code and GitHub Copilot.",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"category": "Developer Tools",
|
||||
"source": "./plugins/kyberforge"
|
||||
},
|
||||
{
|
||||
"name": "bin",
|
||||
"description": "Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"category": "Utilities",
|
||||
"source": "./plugins/bin"
|
||||
},
|
||||
{
|
||||
"name": "git",
|
||||
"description": "Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.9",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/git"
|
||||
},
|
||||
{
|
||||
"name": "gitea",
|
||||
"description": "Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.",
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.10",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/gitea"
|
||||
},
|
||||
{
|
||||
"name": "onedev",
|
||||
"description": "Skills and agents for working with a OneDev forge through the TOD CLI — the forge's own objects, as distinct from the local git clone.",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"category": "Version Control",
|
||||
"source": "./plugins/onedev"
|
||||
},
|
||||
{
|
||||
"name": "core",
|
||||
"description": "Skills for authoring and auditing a repo's AGENTS.md and the provider adapter files that defer to it.",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.4",
|
||||
"category": "Productivity",
|
||||
"source": "./plugins/core"
|
||||
},
|
||||
{
|
||||
"name": "lint",
|
||||
"description": "Skills and agents for configuring and running linters.",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"category": "Developer Tools",
|
||||
"source": "./plugins/lint"
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@
|
||||
ignore = dirty
|
||||
[submodule "docs/wiki"]
|
||||
path = docs/wiki
|
||||
url = git@git.dev.rkdr.net:Defame1297/holocron.wiki.git
|
||||
url = [email protected]:Defame1297/holocron.wiki.git
|
||||
+17
-34
@@ -27,41 +27,24 @@ repos:
|
||||
stages: ['pre-commit']
|
||||
- id: pretty-format-json
|
||||
stages: ['pre-commit']
|
||||
args: [--autofix]
|
||||
# Every generated manifest lives at a KNOWN path, so every alternative is
|
||||
# root-anchored and spells that path out. This was five `(^|/)`
|
||||
# any-depth alternatives plus one `^` root-only one -- a mixture with no
|
||||
# rationale, under which a fixture or vendored tree containing
|
||||
# `.../.claude-plugin/marketplace.json` would have been silently excluded
|
||||
# from formatting while an equivalent
|
||||
# `.../.agents/plugins/marketplace.json` would not. Only the one root
|
||||
# marketplace manifest matches now; anything else is hand-authored and
|
||||
# gets formatted. The twelve per-plugin `plugin.json` alternatives were
|
||||
# dropped with the plugin manifests themselves when native
|
||||
# `claude plugin install` support was removed (ADR-0024) -- apm probes
|
||||
# `apm.yml` and never reached them. The `.agents/plugins/` and
|
||||
# `.github/plugin/` marketplace mirrors went the same way, and their
|
||||
# alternations went with them: `check-useless-excludes` fails on a
|
||||
# pattern that matches no file.
|
||||
args: [--autofix, --no-sort-keys]
|
||||
# `--no-sort-keys` is load-bearing. apm OWNS `.claude/settings.json` and its
|
||||
# `.claude/apm-hooks.json` sidecar (ADR-0018, ADR-0019), and
|
||||
# `apm audit --ci` replays the install and diffs the result byte-for-byte.
|
||||
# apm emits insertion order (`matcher` before `hooks`); the formatter's
|
||||
# default sorts keys, rewrites that into a form apm would never produce,
|
||||
# and the `apm-audit-ci` pre-push hook then reports drift on a file with
|
||||
# no git diff (#102, first hit at 2e395a4). Keeping insertion order means
|
||||
# those two files need no exclude. Dropping the flag is caught at pre-push
|
||||
# by `apm-audit-ci` as drift on `.claude/settings.json`.
|
||||
#
|
||||
# `.claude/settings.json` and its `.claude/apm-hooks.json` ownership
|
||||
# sidecar are the last two alternations, and they are the only ones
|
||||
# here for a reason other than "generated manifest":
|
||||
# apm OWNS that file (ADR-0018, ADR-0019), and
|
||||
# `apm audit --ci` replays the install into a scratch tree and diffs
|
||||
# the result byte-for-byte. `pretty-format-json` sorts object keys
|
||||
# unless `--no-sort-keys` is passed, while apm's hook integrator emits
|
||||
# insertion order (`matcher` before `hooks`, `type` before `command`).
|
||||
# Formatting the file therefore rewrites apm's output into a form apm
|
||||
# would never produce, and the `apm-audit-ci` pre-push hook reports it
|
||||
# as permanent drift on a file with no git diff -- exactly what
|
||||
# happened when the SessionStart hook first landed in 2e395a4.
|
||||
# Re-running `apm install` fixes the file; leaving it in scope here
|
||||
# would re-break it on the very commit that carries the fix. The
|
||||
# sidecar is committed so a fresh clone's install can claim the
|
||||
# settings entry instead of duplicating it (ADR-0019, 2026-09-16
|
||||
# correction), and it is apm output under the same byte-for-byte replay.
|
||||
exclude: '^(\.claude-plugin/marketplace\.json|\.claude/(settings|apm-hooks)\.json)$'
|
||||
# `.claude-plugin/marketplace.json` is the one remaining exclude. It
|
||||
# round-trips except for non-ASCII: it carries literal em dashes and the
|
||||
# formatter re-escapes them to `\u2014` (`--no-ensure-ascii` would fix that,
|
||||
# but it changes the output for every JSON file). Root-anchored because it
|
||||
# is one known path; `check-useless-excludes` fails on a pattern that
|
||||
# matches no file.
|
||||
exclude: '^\.claude-plugin/marketplace\.json$'
|
||||
- id: check-yaml
|
||||
stages: ['pre-commit']
|
||||
- id: trailing-whitespace
|
||||
|
||||
@@ -31,7 +31,7 @@ Fall back to raw shell only when no skill covers it.
|
||||
- **`apm.lock.yaml` turning up modified is expected, not a bug.** kyberforge's `SessionStart` hook keeps the install current on launch and rewrites the lock in the process (ADR-0019). On `main`, commit or discard it deliberately. On a feature branch, discard it (`git checkout -- apm.lock.yaml`, then `apm install`). This keeps unrelated lock churn out of the branch diff and keeps `apm pack --check-clean` consistent with the committed lock. The session then runs the older `main` that the lock records, which is accepted on a branch, and the next session start refreshes again.
|
||||
- **A `.apm/` edit is not live until it is on the remote's `main`.** The six dependencies resolve from the holocron remote, unpinned against the default branch, so pushing a feature branch does not deploy it (ADR-0019). `apm install` deploys from the lock; `apm update` is what re-resolves refs.
|
||||
- **No pre-push hook needs the network — once `apm install` has run.** Root `apm.yml`'s marketplace has no remote package entries, so every hook resolves locally. The guarantee is a property of a populated `apm_modules/`, not of the hook set: on a fresh clone `apm-audit-ci`'s `deployed-files-present` fails outright, and its `drift` and `config-consistency` install-replays have no cache to replay from and clone from the remote. Run `apm install` once on a new checkout and the offline guarantee holds from then on (`docs/spec/gates.md`, "Pushing without a network").
|
||||
- **This repo and OneDev are the only source of truth.** All project state, decisions, and working conventions live here. Do not use an external memory system for this project — cached state diverges from the repo and you get a split brain. Before answering any design or architecture question, check `docs/adr/` for an existing decision.
|
||||
- **This repo and Gitea are the only source of truth.** All project state, decisions, and working conventions live here. Do not use an external memory system for this project — cached state diverges from the repo and you get a split brain. Before answering any design or architecture question, check `docs/adr/` for an existing decision.
|
||||
|
||||
## Key documents
|
||||
|
||||
|
||||
+2
-3
@@ -133,9 +133,8 @@ than to enumerate siblings. Detail: `factory-audit/references/skill-description-
|
||||
_Avoid_: overlap, similar skill
|
||||
|
||||
**Issue**:
|
||||
The cross-provider term for a tracked unit of work. OneDev is this repo's canonical tracker
|
||||
(ADR-0007, superseded by ADR-0029), but skills say "linked issue" generically rather than naming
|
||||
a provider.
|
||||
The cross-provider term for a tracked unit of work. Gitea is this repo's canonical tracker
|
||||
(ADR-0007), but skills say "linked issue" generically rather than naming a provider.
|
||||
_Avoid_: ticket, card, task
|
||||
|
||||
**Family prefix**:
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ Widening a description-opener rule to also catch mid-sentence text looked like a
|
||||
|
||||
## 2026-08-14 — A formatter in the commit path manufactures drift on a file with a clean git diff
|
||||
|
||||
`apm audit --ci` failed on `.claude/settings.json` with an empty `git diff` — `pretty-format-json --autofix` silently re-sorts JSON keys, and this generated file was missing from its exclude list, so every commit re-sorted apm's insertion-ordered output before apm compared against it. Separately, a defect introduced 3 hours earlier on the same branch was first mis-described as "pre-existing," an unverified claim about history. Fix: add tool-owned paths to every autofixing hook's exclude the moment ownership is declared, and verify "pre-existing" claims with `git log -S` or `git branch --contains` before writing them down.
|
||||
`apm audit --ci` failed on `.claude/settings.json` with an empty `git diff` — `pretty-format-json --autofix` silently re-sorts JSON keys, and this generated file was missing from its exclude list, so every commit re-sorted apm's insertion-ordered output before apm compared against it. Separately, a defect introduced 3 hours earlier on the same branch was first mis-described as "pre-existing," an unverified claim about history. Fix: add tool-owned paths to every autofixing hook's exclude the moment ownership is declared (for JSON, superseded by #102: `--no-sort-keys` makes the exclude unnecessary), and verify "pre-existing" claims with `git log -S` or `git branch --contains` before writing them down.
|
||||
|
||||
## 2026-08-16 — A rule reversed inside a retrofit leaves no trace unless someone writes it down (historical)
|
||||
|
||||
|
||||
+627
-627
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
||||
name: holocron
|
||||
version: 0.5.0
|
||||
version: 0.5.2
|
||||
description: AI development skills for Claude Code, and for GitHub Copilot through apm — factory, design, implement, review, and cross-cutting workflows.
|
||||
license: MIT
|
||||
|
||||
@@ -16,17 +16,17 @@ targets:
|
||||
- claude
|
||||
dependencies:
|
||||
apm:
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/bin
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/core
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/git
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/gitea
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/kyberforge
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/lint
|
||||
# TOD's skills arrive transitively through this wrapper rather than as a
|
||||
# direct entry, so the marketplace and this repo consume onedev by the same
|
||||
@@ -38,7 +38,7 @@ dependencies:
|
||||
# `apm install` fails, which includes the copy kyberforge's SessionStart
|
||||
# hook runs on launch. Accepted deliberately: this branch is merging
|
||||
# immediately.
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: [email protected]:Defame1297/holocron.git
|
||||
path: plugins/onedev
|
||||
mcp: []
|
||||
|
||||
@@ -61,7 +61,7 @@ dependencies:
|
||||
# an apm mechanic.
|
||||
executables:
|
||||
allow:
|
||||
kyberforge#2.0.0:
|
||||
kyberforge#2.1.0:
|
||||
hooks: true
|
||||
bin: true
|
||||
|
||||
@@ -71,11 +71,11 @@ marketplace:
|
||||
# top-level apm.yml description:/version: above are NOT inherited into the
|
||||
# compiled output despite being used elsewhere (e.g. by `apm audit`).
|
||||
description: AI development skills for Claude Code, and for GitHub Copilot through apm — factory, design, implement, review, and cross-cutting workflows.
|
||||
version: 0.5.0
|
||||
version: 0.5.2
|
||||
owner:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
|
||||
# Default tag pattern used to resolve version ranges for each package.
|
||||
build:
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Gitea is the exclusive issue tracker — file-based fallback removed
|
||||
|
||||
**Superseded by:** ADR-0029 (OneDev supersedes Gitea as this repo's canonical forge — this repo's own hosting, issue tracking, and PRs move to OneDev; `gitea/` continues to ship as a marketplace product regardless)
|
||||
|
||||
**Supersedes:** ADR-0011 (provider-agnostic issue tracker with file-based default — archived during refactoring)
|
||||
|
||||
> **Note on the ADR-0011 number.** Every "ADR-0011" on this page means the *archived* provider-agnostic issue tracker ADR, which no longer exists in `docs/adr/` — it was removed when it was superseded, and the number 0011 was later reused for an unrelated decision, `docs/adr/0011-gitea-skill-deep-modules.md` (the gitea skill's split into deep modules). That file is not the ADR referenced below. The number is not renumbered here: these ADRs are a published record and renumbering would break every citation that already points at either one. The archived text is recoverable from git history.
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# OneDev supersedes Gitea as this repo's canonical forge
|
||||
|
||||
**Supersedes:** ADR-0007 (Gitea as the exclusive issue tracker)
|
||||
|
||||
This repo's own hosting, issue tracking, and pull requests move from Gitea (`git.dev.rkdr.net`) to
|
||||
OneDev (`onedev.dev.rkdr.net/Holocron`). Gitea is frozen and kept reachable read-only as a historical
|
||||
archive rather than deleted, so commit messages, branch names, and ADRs that cite Gitea issue/PR
|
||||
numbers (e.g. `#124`, `#140`) stay resolvable. `plugins/gitea/` is unaffected — it continues to ship
|
||||
as a marketplace product for consumers with Gitea repos of their own; this decision is about what
|
||||
*this* repo uses on itself, not what this repo authors and distributes.
|
||||
|
||||
## Considered and rejected
|
||||
|
||||
- **Preserving Gitea's issue/PR numbers in OneDev.** Rejected: OneDev issues and pull requests use
|
||||
independent per-type counters, unlike Gitea's single shared sequence — there is no API path to
|
||||
reproduce both simultaneously without a fragile create/delete padding hack. Migrated issues carry a
|
||||
back-link to their original Gitea URL instead; new work uses OneDev's own numbers from the cutover
|
||||
point forward.
|
||||
- **Recreating historical PR objects (title/description/reviews) in OneDev.** Rejected for the
|
||||
existing ~140 closed/merged PRs: `tod pr create` requires a live source branch, and Gitea already
|
||||
deletes head branches on merge, so recreating them means resurrecting deleted branches from
|
||||
merge-commit parent SHAs, opening throwaway PRs, and discarding them without merging (to avoid a
|
||||
second, divergent merge commit alongside the mirrored git history). The git mirror already carries
|
||||
every commit, message, author, and merge losslessly; the archived Gitea instance still holds the
|
||||
original PR/review UI for anyone who needs it. Any PRs genuinely open and in flight at cutover time
|
||||
are migrated for real, not archived.
|
||||
- **Recreating Gitea's `Reviewed/*`, `Status/*`, and `Compat/Breaking` labels as new OneDev labels.**
|
||||
Rejected in favor of OneDev's own out-of-the-box shape: structured `Type`/`Priority` fields (which
|
||||
`Kind/*` and `Priority/*` map onto directly) plus the native three-state workflow (`Open` /
|
||||
`In Progress` / `Closed`, no built-in disposition states). Disposition information that has no
|
||||
native home becomes a one-line note in the migrated issue body instead of a second, parallel,
|
||||
unstructured label taxonomy next to the real fields.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Gitea issue/PR numbers cited in existing commit messages and docs remain valid only as long as the
|
||||
archived Gitea instance stays reachable; they are not remapped to OneDev numbers anywhere.
|
||||
- The six first-party `apm.yml` plugin dependencies (`git`, `gitea`, `kyberforge`, `lint`, `core`,
|
||||
`bin`), previously resolved unpinned against `[email protected]:Defame1297/holocron.git`, are
|
||||
repointed to the OneDev remote as part of this migration — apm's own dependency resolution must
|
||||
track the now-canonical remote, not a frozen archive.
|
||||
- `AGENTS.md`'s "this repo and Gitea are the only source of truth" language is updated to name
|
||||
OneDev.
|
||||
@@ -1,130 +0,0 @@
|
||||
# Gitea → OneDev migration plan
|
||||
|
||||
Executes ADR-0029 (supersedes ADR-0007). Scope: this repo's own self-hosting only — git history,
|
||||
issues, milestones, wiki (already mirrored), and this repo's own tooling config. `plugins/gitea/`
|
||||
ships unchanged as a marketplace product. Historical PR objects and Gitea's exact issue/PR numbering
|
||||
are explicitly not migrated (see ADR-0029's "Considered and rejected").
|
||||
|
||||
Source: `Defame1297/holocron` on `git.dev.rkdr.net`. Target: `Holocron` (project id 1, currently
|
||||
empty) on `onedev.dev.rkdr.net`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [x] `tod` installed and configured — `~/.config/tod/config` already has `server-url` and
|
||||
`access-token`; `~/.bashrc` sources `~/.config/tod/env` automatically (`set -a; . env; set +a`),
|
||||
but non-interactive shells (scripts, CI, this tool) must source it explicitly per invocation.
|
||||
- [x] OneDev project `Holocron` exists (`tod project get Holocron`), `codeManagement` /
|
||||
`issueManagement` enabled, currently no `defaultBranch` (empty repo).
|
||||
- [ ] **Gotcha to build scripts around:** `tod issue`/`tod pr` subcommands resolve their target
|
||||
project from the working directory's git remote, not from `--project` (verified — `--project`
|
||||
is accepted by the flag parser but ignored; commands fail outside a repo with a OneDev remote).
|
||||
Every migration script step below must run from inside a local clone with a remote pointing at
|
||||
`onedev.dev.rkdr.net/Holocron`.
|
||||
- [ ] Create the 7 OneDev **Iterations** (milestone equivalent) manually via the OneDev web UI —
|
||||
`tod` has no iteration-create command. Names, verbatim, to match Gitea milestones for clean
|
||||
`--iteration` references on migrated issues:
|
||||
`Governance: enforcement`, `Kyberforge basics`, `Legacy / Triage`, `Road to homelab - prep`,
|
||||
`Skills & Agents`, `The great refactoring`, `Tooling`.
|
||||
- [ ] Freeze Gitea: stop merging PRs there once Phase 1 starts. Solo-maintainer repo, so this is just
|
||||
"don't push to `git.dev.rkdr.net` after the mirror point."
|
||||
|
||||
## Phase 1 — Mirror git history (lossless, zero risk)
|
||||
|
||||
1. `git remote add onedev https://onedev.dev.rkdr.net/Holocron` in the local clone.
|
||||
2. `git push onedev refs/heads/*:refs/heads/* refs/tags/*:refs/tags/*` (explicit branch+tag push,
|
||||
not `--mirror` — avoids touching any Gitea-internal refs that aren't real branches).
|
||||
3. Verify: `tod project get Holocron` shows `defaultBranch` populated; HEAD of `main` on OneDev
|
||||
matches Gitea `main` HEAD (`d654dca...` as of this plan).
|
||||
4. This alone carries every commit, author, message, and merge losslessly — nothing else in this
|
||||
plan is required for code-level fidelity.
|
||||
|
||||
## Phase 2 — Issues (~95 total: 81 closed + 14 open across 7 milestones, per Gitea milestone counts)
|
||||
|
||||
Field mapping (OneDev's out-of-the-box `Type`/`Priority` fields, no new labels created):
|
||||
|
||||
| Gitea label | OneDev field |
|
||||
|---|---|
|
||||
| `Kind/Bug` | `Type: Bug` |
|
||||
| `Kind/Feature` | `Type: New Feature` |
|
||||
| `Kind/Enhancement` | `Type: Improvement` |
|
||||
| `Kind/Documentation`, `Kind/Testing` | `Type: Task` |
|
||||
| `Kind/Security` | `Type: Bug` |
|
||||
| `Priority/Critical` | `Priority: Critical` |
|
||||
| `Priority/High` | `Priority: Major` |
|
||||
| `Priority/Medium` | `Priority: Normal` |
|
||||
| `Priority/Low` | `Priority: Minor` |
|
||||
| `Reviewed/*`, `Status/*`, `Compat/Breaking` | no field/state equivalent — fold into a one-line note in the migrated body |
|
||||
|
||||
State mapping: Gitea `open` → OneDev `Open` (default, no action); Gitea `closed` →
|
||||
`tod issue change-state <ref> Closed`. OneDev's out-of-the-box workflow only has
|
||||
Open/In Progress/Closed — no disposition states, confirmed by probing the live server.
|
||||
|
||||
Per issue:
|
||||
5. `tod issue create "<title>" --field Type=<mapped> --field Priority=<mapped> --iteration "<milestone>" --description "<body>\n\n---\nMigrated from git.dev.rkdr.net/Defame1297/holocron/issues/<N>.[\nGitea disposition: <label>.]"`
|
||||
6. Replay comments via `tod issue add-comment` (low effort, worth doing for continuity).
|
||||
7. `tod issue change-state <new-ref> Closed` for originally-closed issues.
|
||||
8. Spot-check a sample (e.g. 5 issues across different milestones) against the Gitea source.
|
||||
|
||||
Numbers will not match Gitea's (accepted — ADR-0029). Author/submitter on migrated issues will be
|
||||
the migration token's own OneDev account, not the original Gitea author — no CLI-exposed way to
|
||||
override this (the `onBehalfOf` field exists in OneDev's issue schema but isn't exposed through `tod`;
|
||||
using it would mean raw, unverified REST calls, not worth it for this scope).
|
||||
|
||||
## Phase 3 — Pull requests
|
||||
|
||||
- **Currently-open PRs only** (check at execution time: `list_pull_requests state=open`): migrate for
|
||||
real via `tod pr create`, since the source branch still exists. Add description, reviewers.
|
||||
- **Closed/merged historical PRs (~140 of them): explicitly skipped.** Per ADR-0029, their content
|
||||
survives losslessly in the Phase 1 git mirror; the archived Gitea instance remains the record for
|
||||
anyone who wants the original review thread.
|
||||
|
||||
## Phase 4 — Releases
|
||||
|
||||
Git tags (`v1.0.0`, `v2.0.0`, `v2.0.1`) carry over automatically in Phase 1. OneDev has no confirmed
|
||||
first-class "Release" object with a rendered markdown body the way Gitea does — this needs a quick
|
||||
check against the live server before deciding further (not yet verified in this session). Fallback if
|
||||
none exists: leave the 3 release-note bodies in the archived Gitea (read-only) and optionally fold
|
||||
them into a `CHANGELOG.md` in the repo for local discoverability. **Flag this to the user before
|
||||
executing Phase 4** — not fully resolved.
|
||||
|
||||
## Phase 5 — Wiki
|
||||
|
||||
Nothing to do. `docs/wiki/HUMANS.md` and `docs/wiki/Home.md` already mirror the two Gitea wiki pages
|
||||
in-repo (confirmed identical), and OneDev has no separate wiki feature to migrate into (confirmed: no
|
||||
wiki flag on the project object, no wiki REST resource). They travel with Phase 1 automatically.
|
||||
|
||||
## Phase 6 — Repo self-reference updates (code changes)
|
||||
|
||||
9. Repoint the six first-party `apm.yml` plugin dependencies (`git`, `gitea`, `kyberforge`, `lint`,
|
||||
`core`, `bin`) from `[email protected]:Defame1297/holocron.git` to the OneDev remote.
|
||||
10. Update `AGENTS.md`'s routing table (currently: *"Issues, PRs, labels, milestones →
|
||||
`gitea-issues`, `gitea-prs`, `gitea-labels-milestones`..."*) to route this repo's own
|
||||
issue/PR operations to the OneDev/tod skills instead (`using-tod` as the catch-all, plus
|
||||
`work-on-issue`, `work-on-pull-request`, `submit-issue-work`, `submit-pull-request-work`). This
|
||||
needs a deliberate mapping pass, not a mechanical find-replace — the tod skillset is
|
||||
workflow-shaped, not CRUD-shaped like the gitea skills it replaces.
|
||||
11. Update local `origin` remote to point at OneDev; rename the old one (e.g. `git remote rename
|
||||
origin gitea-archive`) rather than deleting it.
|
||||
12. Run `apm install` against the repointed remote and verify it resolves cleanly.
|
||||
13. Sweep `README.md` and any other doc prose that names Gitea as *this repo's own* host (separate
|
||||
from `plugins/gitea/`'s own product documentation, which is unaffected).
|
||||
|
||||
## Phase 7 — Freeze and archive Gitea
|
||||
|
||||
14. Set the Gitea repository to read-only/archived via the Gitea web UI (no MCP tool exposes this —
|
||||
manual step).
|
||||
|
||||
## Verification checklist
|
||||
|
||||
- [ ] `tod project get Holocron` → `defaultBranch: main`, HEAD SHA matches Gitea's `main`.
|
||||
- [ ] Issue count on OneDev matches Gitea's ~95 (open + closed).
|
||||
- [ ] `apm install` succeeds from a fresh clone against the new remote.
|
||||
- [ ] Pre-commit hooks (`pre-commit run --all-files`) pass in a fresh OneDev clone.
|
||||
- [ ] Gitea repo is read-only; a test push to it fails as expected.
|
||||
|
||||
## Explicitly out of scope (deferred, per earlier decisions)
|
||||
|
||||
- `.onedev-buildspec.yml` / CI setup — no Gitea Actions exist today to migrate; separate follow-up
|
||||
task via the `edit-build-spec` skill.
|
||||
- Sunsetting `plugins/gitea/` as a marketplace product — agreed as a *later* phase, not part of this
|
||||
migration.
|
||||
+16
-15
@@ -1279,24 +1279,25 @@ point only). Machine-specific settings go in the gitignored `.claude/settings.lo
|
||||
does not deploy and the replay does not compare; shared enforcement belongs in
|
||||
`.pre-commit-config.yaml`.
|
||||
|
||||
### Why it is excluded from `pretty-format-json`
|
||||
|
||||
It is in the **second and last alternation** in that hook's `exclude:` pattern, and that alternation
|
||||
is the only one there for a reason other than "generated manifest". Mind which number you are
|
||||
quoting: the pattern is `^(\.claude-plugin/marketplace\.json|\.claude/(settings|apm-hooks)\.json)$`
|
||||
— **two top-level alternations, expanding to three real tracked files**:
|
||||
`.claude-plugin/marketplace.json`, this one, and its committed `.claude/apm-hooks.json` sidecar,
|
||||
which is apm output under the same byte-for-byte replay and is excluded for the same reason.
|
||||
### Why `pretty-format-json` runs with `--no-sort-keys`
|
||||
|
||||
`pretty-format-json --autofix` sorts object keys unless `--no-sort-keys` is passed, while apm's hook
|
||||
integrator emits insertion order (`matcher` before `hooks`, `type` before `command`). Leaving the
|
||||
file in that hook's scope therefore rewrites apm's output into a form apm would never produce on the
|
||||
way into **every** commit, and `apm-audit-ci` then reports permanent drift on a file with an empty
|
||||
`git diff` — exactly what happened when the `SessionStart` hook first landed in `2e395a4`. Re-running
|
||||
`apm install` fixes the file; leaving it in scope would re-break it on the very commit carrying the
|
||||
fix.
|
||||
integrator emits insertion order (`matcher` before `hooks`, `type` before `command`). In scope with
|
||||
the default, the formatter rewrites apm's output into a form apm would never produce on the way into
|
||||
**every** commit, and `apm-audit-ci` then reports permanent drift on a file with an empty `git diff`
|
||||
— exactly what happened when the `SessionStart` hook first landed in `2e395a4` (#102).
|
||||
|
||||
**Load-bearing. Do not tidy it out of that list** (see `LESSONS.md`, 2026-08-14).
|
||||
The hook now passes `--no-sort-keys`, so this file and its committed `.claude/apm-hooks.json` sidecar
|
||||
(apm output under the same byte-for-byte replay) need **no exclude**: the formatter's default 2-space
|
||||
indent already matches apm's, and with insertion order kept they round-trip untouched. No dedicated
|
||||
test pins this: dropping `--no-sort-keys` surfaces at pre-push as `apm-audit-ci` drift on
|
||||
`.claude/settings.json`, which is the same gate that caught the original failure.
|
||||
|
||||
**Load-bearing. Do not remove `--no-sort-keys`.** `.claude-plugin/marketplace.json` is the one path
|
||||
still in that hook's `exclude:`: it carries literal em dashes that the formatter re-escapes to
|
||||
`\u2014`, which `--no-ensure-ascii` would stop but for every JSON file. This closes the JSON case
|
||||
only; a new tool-owned file in the scope of another autofixer is still caught only by `apm-audit-ci`
|
||||
drift after the fact, not by a derived gate.
|
||||
|
||||
## Pushing without a network
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ apm is the only supported install path (ADR-0024). Declare this package in the c
|
||||
```yaml
|
||||
dependencies:
|
||||
apm:
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: git@git.rkdr.net:Defame1297/holocron.git
|
||||
path: plugins/bin
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ Then:
|
||||
apm install
|
||||
```
|
||||
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add git@git.dev.rkdr.net:Defame1297/holocron.git --name holocron`) gets you the `bin@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add [email protected]:Defame1297/holocron.git --name holocron`) gets you the `bin@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
|
||||
**Native plugin installs do not work.** This package ships no per-plugin manifest and no flat content directories, so a host that installs it natively gets zero skills — and Claude Code raises no error while doing it (ADR-0024).
|
||||
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
name: bin
|
||||
version: 1.1.8
|
||||
version: 1.1.9
|
||||
description: Skills for everyday AI-assisted development work that is not tied to a single tool, forge or language, and has not yet been split into a focused plugin.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/bin
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/bin
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/bin
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/bin
|
||||
keywords:
|
||||
- utility
|
||||
- diagnostics
|
||||
|
||||
@@ -9,7 +9,7 @@ apm is the only supported install path (ADR-0024). Declare this package in the c
|
||||
```yaml
|
||||
dependencies:
|
||||
apm:
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: git@git.rkdr.net:Defame1297/holocron.git
|
||||
path: plugins/core
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ Then:
|
||||
apm install
|
||||
```
|
||||
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add git@git.dev.rkdr.net:Defame1297/holocron.git --name holocron`) gets you the `core@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add [email protected]:Defame1297/holocron.git --name holocron`) gets you the `core@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
|
||||
**Native plugin installs do not work.** This package ships no per-plugin manifest and no flat content directories, so a host that installs it natively gets zero skills — and Claude Code raises no error while doing it (ADR-0024).
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
name: core
|
||||
version: 1.1.3
|
||||
version: 1.1.4
|
||||
description: Skills for authoring and auditing a repo's AGENTS.md and the provider adapter files that defer to it.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/core
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/core
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/core
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/core
|
||||
keywords:
|
||||
- agents-md
|
||||
- documentation
|
||||
|
||||
@@ -9,7 +9,7 @@ apm is the only supported install path (ADR-0024). Declare this package in the c
|
||||
```yaml
|
||||
dependencies:
|
||||
apm:
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: git@git.rkdr.net:Defame1297/holocron.git
|
||||
path: plugins/git
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ Then:
|
||||
apm install
|
||||
```
|
||||
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add git@git.dev.rkdr.net:Defame1297/holocron.git --name holocron`) gets you the `git@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add [email protected]:Defame1297/holocron.git --name holocron`) gets you the `git@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
|
||||
**Native plugin installs do not work.** This package ships no per-plugin manifest and no flat content directories, so a host that installs it natively gets zero skills and zero agents — and Claude Code raises no error while doing it (ADR-0024).
|
||||
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
name: git
|
||||
version: 1.3.8
|
||||
version: 1.3.9
|
||||
description: Skills and agents for working with a local Git clone over the git wire protocol, and for authoring and running the pre-commit hooks that guard it.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/git
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/git
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/git
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/git
|
||||
keywords:
|
||||
- git
|
||||
- vcs
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
name: gitea
|
||||
version: 1.3.9
|
||||
version: 1.3.10
|
||||
description: Skills and agents for working with a Gitea forge through its HTTP API — the forge's own objects, as distinct from the local git clone.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea
|
||||
keywords:
|
||||
- gitea
|
||||
- issues
|
||||
|
||||
@@ -5,7 +5,7 @@ description: >
|
||||
the dependencies it declares, or an apm marketplace — even when the user does
|
||||
not say "apm". Not the apm binary or an agent runtime -> `apm-install`.
|
||||
metadata:
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
category: apm
|
||||
source_keys:
|
||||
- context7-microsoft-apm
|
||||
|
||||
@@ -12,7 +12,7 @@ apm compile --clean # zero-write sanity check; use for skill/agent-o
|
||||
apm compile --clean --dry-run # pure preview, no writes
|
||||
```
|
||||
|
||||
Compiles `.apm/instructions/` + `.apm/agents/*.agent.md` primitives into consumer-side context files (AGENTS.md/CLAUDE.md CONTEXT files) for the deployment target, per the `compilation:` block in `apm.yml`. This is the consumer/deployment side — it is NOT the producer of `plugin.json`/`marketplace.json`; that's `apm pack`'s job (below). Run `apm compile` after any change to `.apm/instructions/`/`.apm/agents/` content or to `compilation:`/`targets:` in `apm.yml`.
|
||||
Compiles `.apm/instructions/` + `.apm/agents/*.agent.md` primitives into consumer-side context files (AGENTS.md/CLAUDE.md CONTEXT files) for the deployment target, per the `compilation:` block in `apm.yml`. This is the consumer/deployment side — it is NOT the producer of `plugin.json`/`marketplace.json`; that's `apm pack`'s job (below). Run `apm compile` after any change to `.apm/instructions/`/`.apm/agents/` content or to `compilation:`/`targets:` in `apm.yml`. To author an instructions file, use `instructions-author` — it covers which fields each target drops.
|
||||
|
||||
## Pack
|
||||
|
||||
|
||||
@@ -1,33 +1,29 @@
|
||||
---
|
||||
name: forge
|
||||
description: >
|
||||
Use when the user wants to build or improve something but has not yet named
|
||||
the artifact type — skill, agent, plugin, or marketplace entry; "not sure if
|
||||
this should be a skill or a plugin", "I have an idea but don't know where it
|
||||
belongs". Routes to the matching author skill. Do not use when the type is
|
||||
already named — invoke `skill-author`, `agent-author` or `apm-workflow`
|
||||
directly.
|
||||
Use when the user wants to build or improve something without naming the
|
||||
artifact type ("not sure if this should be a skill or a plugin"). Not a named
|
||||
skill -> `skill-author`. Not a named agent -> `agent-author`. Not a named
|
||||
plugin -> `apm-workflow`.
|
||||
metadata:
|
||||
version: "1.0.1"
|
||||
version: "1.0.3"
|
||||
category: factory
|
||||
source_keys:
|
||||
- claude-code-subagents-docs
|
||||
- context7-websites-code-claude
|
||||
- agentskills-spec
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
|
||||
- forge is an optional guided entry point, not a gate — `skill-author`, `agent-author`, `factory-audit` and `apm-workflow` all stay directly invokable, and forge never intercepts a direct call to one.
|
||||
- Claude Code's skill-level `context: fork` frontmatter field and the `/fork` subagent command are opposites despite the shared word: `context: fork` isolates (fresh context, no parent access), while `/fork` inherits the full conversation. The route reference each classification loads spends that distinction: `references/author-routes.md` chooses between the two, `references/apm-routes.md` rules the fork out.
|
||||
|
||||
## Step 1 — Grill the intent
|
||||
|
||||
Call `grill-with-docs` unless a grill session has already run and is available in the context.
|
||||
|
||||
`grill-with-docs` ships in a sibling plugin that kyberforge does not declare as an apm dependency, so it resolves in the authoring monorepo but can be absent where kyberforge is installed alone. If it does not resolve, grill inline yourself rather than skipping the step: what problem the artifact solves, who invokes it and how, what it must refuse, and which existing skill or plugin already owns part of the job. Say which path you took.
|
||||
`grill-with-docs` ships in a sibling plugin kyberforge does not declare as an apm dependency, so it can be absent where kyberforge is installed alone. If it does not resolve, grill inline yourself rather than skipping the step: what problem the artifact solves, who invokes it and how, what it must refuse, and which existing skill or plugin already owns part of the job. Say which path you took.
|
||||
|
||||
Grilling regularly overturns the artifact type assumed at the start, or splits one idea into several artifacts, so it runs before classification rather than confirming it. Run it inline in the current conversation — grilling is interactive and a subagent cannot hold the back-and-forth.
|
||||
Grilling often overturns the assumed artifact type or splits one idea into several, so it runs before classification. Run it inline: a subagent cannot hold the back-and-forth.
|
||||
|
||||
## Step 2 — Classify and dispatch
|
||||
|
||||
@@ -37,12 +33,13 @@ Match the grilled intent against exactly one row — or more than one, if the in
|
||||
|---|---|---|---|
|
||||
| A reusable capability the agent loads inline in the main conversation, triggered by description-matching, free to bundle its own `references/`, `scripts/` or `assets/` | Skill | `skill-author` | `references/author-routes.md` |
|
||||
| A recurring task needs its own reusable definition — dedicated system prompt, tools and description, invokable by name across sessions | Agent / subagent | `agent-author` | `references/author-routes.md` |
|
||||
| Always-on or path-scoped agent guidance in `.apm/instructions/*.instructions.md` | Instructions file | `instructions-author` | `references/author-routes.md` |
|
||||
| A new distributable unit — no existing plugin is the right home for the skill, agent, hook or MCP server being built, or the bundle needs its own manifest, versioning and install lifecycle | Plugin | `apm-workflow` (`apm plugin init`) | `references/apm-routes.md` |
|
||||
| The plugin already exists and only its marketplace-facing metadata changes — a first listing, or a version/description update, never the plugin's contents | Marketplace entry | `apm-workflow` (`apm marketplace package add`) | `references/apm-routes.md` |
|
||||
|
||||
The table classifies what to build, not how to run it: a one-off task that merely needs an isolated or context-inheriting run is not an artifact and has no row here. If the intent stays genuinely ambiguous between rows after grilling, ask the user rather than guessing.
|
||||
|
||||
A real artifact that matches no row — a hook, an MCP server, an AGENTS.md, a research doc — has no route here. Say so, hand the user the skill that does own it, and never bend it into a row to make the table fit.
|
||||
An artifact that matches no row — a hook, an MCP server, an AGENTS.md, a research doc — has no route here. Say so, hand the user the skill that owns it, and never bend it into a row.
|
||||
|
||||
When the intent spans several rows, chain the routes in dependency order — an artifact that must exist on disk before another skill can target it goes first, so `apm-workflow` scaffolds the plugin directory before `skill-author` scaffolds a skill inside it.
|
||||
|
||||
@@ -51,4 +48,4 @@ When the intent spans several rows, chain the routes in dependency order — an
|
||||
## Step 3 — Closing gates, common to every route
|
||||
|
||||
- **Resolve before closing.** A route is finished only when its verification reports nothing unresolved. An actionable finding reopens the route; it is never reported onward as a caveat.
|
||||
- **Bump the package version.** A skill route always lands here: `skill-author` moves only a skill's own `metadata.version`, which is not the package `apm.yml`'s number — so read `references/version-bump.md` after one. `agent-author` and the apm routes bump the package themselves at plugin scope; after those, read it only when their output does not say they did.
|
||||
- **Bump the package version.** A skill route always lands here: `skill-author` moves only a skill's own `metadata.version`, which is not the package `apm.yml`'s number — so read `references/version-bump.md` after one. `agent-author`, `instructions-author` and the apm routes bump the package themselves at plugin scope; after those, read it only when their output does not say they did.
|
||||
@@ -1,14 +1,24 @@
|
||||
---
|
||||
source_keys:
|
||||
- claude-code-subagents-docs
|
||||
- context7-websites-code-claude
|
||||
---
|
||||
|
||||
# Routing a skill or agent to its author skill
|
||||
# Routing a skill, agent or instructions file to its author skill
|
||||
|
||||
Reached from `SKILL.md` Step 2 when the classified artifact is a skill or an agent/subagent
|
||||
definition. Route a skill to `skill-author` and an agent to `agent-author`. The two branches
|
||||
differ on the author skill only — both verify the result with `factory-audit`, which detects the
|
||||
artifact type itself — and everything below applies to both.
|
||||
Reached from `SKILL.md` Step 2 when the classified artifact is a skill, an agent/subagent
|
||||
definition or an instructions file. Route a skill to `skill-author`, an agent to `agent-author` and
|
||||
an instructions file to `instructions-author`. The branches differ on the author skill only, and
|
||||
everything below applies to all three — except that `factory-audit` has no instructions flow yet, so
|
||||
an instructions file is verified by `instructions-author`'s own throwaway-package check and the
|
||||
clean-context rerun below is skipped for it.
|
||||
|
||||
## Gotcha: `context: fork` is not `/fork`
|
||||
|
||||
Claude Code's skill-level `context: fork` frontmatter field and the `/fork` subagent command are
|
||||
opposites despite the shared word: `context: fork` isolates (fresh context, no parent access),
|
||||
while `/fork` inherits the full conversation. The fork-versus-inline choice below is about `/fork`.
|
||||
`references/apm-routes.md` rules the fork out entirely.
|
||||
|
||||
## Choose fork or inline
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
- **URL:** context7:/websites/code_claude
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/claude-code-plugins/sources.md
|
||||
- **Description:** Official Claude Code documentation site indexed by Context7 — confirms the `context: fork` skill-level frontmatter field means isolated/fresh execution, the opposite of what the `/fork` subagent command does (inherits conversation). Informs the Gotchas entry in `SKILL.md` warning against conflating the two; nothing else in this skill draws on it, and no `references/` file mentions the `context: fork` field.
|
||||
- **Contributing files:** SKILL.md
|
||||
- **Description:** Official Claude Code documentation site indexed by Context7 — confirms the `context: fork` skill-level frontmatter field means isolated/fresh execution, the opposite of what the `/fork` subagent command does (inherits conversation). Informs the `context: fork` gotcha in `references/author-routes.md` warning against conflating the two; nothing else in this skill draws on it.
|
||||
- **Contributing files:** references/author-routes.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## claude-code-plugins-docs
|
||||
|
||||
@@ -7,8 +7,8 @@ source_keys:
|
||||
|
||||
Reached from `SKILL.md` Step 3 after a route has finished. A skill route always lands here:
|
||||
`skill-author` moves only a skill's own `metadata.version`, which is not the package manifest's
|
||||
number, so the package version is still behind when it reports done. `agent-author` bumps the
|
||||
resolved package's `apm.yml` itself at plugin/APM scope, and `apm-workflow`'s configure flow
|
||||
number, so the package version is still behind when it reports done. `agent-author` and `instructions-author` bump the
|
||||
resolved package's `apm.yml` themselves (`agent-author` at plugin/APM scope), and `apm-workflow`'s configure flow
|
||||
carries the same policy — read those routes' output before acting here, because a second bump for
|
||||
one change is wrong.
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: instructions-author
|
||||
description: >
|
||||
Use when creating or revising an apm instructions file
|
||||
(`.apm/instructions/*.instructions.md`). Not read-only review ->
|
||||
`factory-audit`. Not skills -> `skill-author`. Not agents -> `agent-author`.
|
||||
Not AGENTS.md -> `agentsmd-author`.
|
||||
compatibility: Requires the apm CLI; behaviour verified against apm 0.28.0.
|
||||
allowed-tools: Bash Read Write Edit
|
||||
metadata:
|
||||
version: "0.1.0"
|
||||
category: factory
|
||||
source_keys:
|
||||
- apm-docs-site
|
||||
- apm-cli-0-28-0-experiments
|
||||
- claude-code-memory-docs
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Claude Code drops `description`; only Copilot and Cursor keep it. Write a body that explains itself.
|
||||
- Quote every `applyTo`. An unquoted `**/*.py` fails to parse, compile skips the file, and `apm install` still deploys it with no `paths:`, so it loads in every session and nothing errors.
|
||||
- `apm compile --validate` always exits 0 and hides the missing-`description`, missing-`applyTo` and empty-body warnings. It is not a lint gate.
|
||||
- Once rules sit in `.claude/rules/`, `apm compile --target claude` writes no `CLAUDE.md` and still exits 0; an exit-code check verifies nothing.
|
||||
- A source must be flat in `.apm/instructions/` and end `.instructions.md`; anything else is ignored or never installed.
|
||||
|
||||
## Step 1 — Dispatch
|
||||
|
||||
| Condition | Flow | Reference |
|
||||
|---|---|---|
|
||||
| No file at the target path | Create | `references/create.md` |
|
||||
| A file exists, at least one improvement signal present | Improve | `references/improve.md` |
|
||||
| A file exists, no signals | Stop and ask | — |
|
||||
|
||||
Signals: grill output, audit findings, inline feedback, a session describing a rule that loaded when it should not or failed to load. With none, ask whether the user meant to create a new file or has feedback to apply.
|
||||
|
||||
Read only the reference for the resolved flow. Capture `rtk git log --oneline -1` before touching the filesystem; Step 3 needs it.
|
||||
|
||||
## Step 2 — Contract
|
||||
|
||||
Gates on every file, whichever flow wrote it:
|
||||
|
||||
- **One topic per file.** Two topics are two files.
|
||||
- **Scope.** Omit `applyTo` only for a rule that must load in every session, and tell the user it then costs context at every launch.
|
||||
- **Stem.** It becomes the deployed filename, and install overwrites a hand-authored rule of the same name on most targets without a prompt. Check for a collision before choosing it.
|
||||
- **Body.** Bullets, paths in backticks, nothing assuming another file is loaded, under 200 lines.
|
||||
|
||||
If a field, glob or location is in question, read `references/schema.md`. If the question is which target keeps which field, or what compile does, read `references/target-mapping.md`.
|
||||
|
||||
## Step 3 — Validate and close
|
||||
|
||||
- [ ] Verify with a real compile and a throwaway deploy: read `references/verify.md`. Resolve every warning and confirm a scoped rule deploys with `paths:`.
|
||||
- [ ] Bump the owning package's `apm.yml` `version` — **minor** on create, **patch** on improve — because consumers compare it to detect updates.
|
||||
|
||||
`factory-audit` has no instructions checks yet, so nothing else gates the file; report only what the verification showed.
|
||||
|
||||
**Commit verification.** Once verification is clean, run `rtk git add` and `rtk git commit`. Re-run `rtk git log --oneline -1` and confirm the hash changed from Step 1's. A non-empty `git diff --stat` is not proof: staged-but-uncommitted work is lost if the tree is cleaned up. Report done only once the hash has changed.
|
||||
@@ -0,0 +1,5 @@
|
||||
# assets/
|
||||
|
||||
## templates/
|
||||
|
||||
- **`instructions.md`** — minimal valid `.apm/instructions/<name>.instructions.md`, copied by `scripts/new-instructions.sh`. Carries a `description`, a quoted `applyTo` and a one-topic body, each marked `FILL IN:`. The `applyTo` comment is the only guidance it carries; field semantics are in `references/schema.md`.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Delete these comments once filled in; Copilot receives this file verbatim.
|
||||
description: FILL IN: one line on what this rule covers. Only Copilot and Cursor keep it.
|
||||
applyTo: "FILL IN: quoted glob, e.g. **/*.py"
|
||||
# applyTo is always quoted: an unquoted ** is a YAML alias error and the rule
|
||||
# deploys unscoped. Several globs: "**/*.css,**/*.scss". Delete the line only
|
||||
# for a rule that must load in every session.
|
||||
---
|
||||
# FILL IN: one topic per file
|
||||
|
||||
- FILL IN: the first rule, stated as a bullet.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-docs-site
|
||||
- apm-cli-0-28-0-experiments
|
||||
---
|
||||
|
||||
# Creating a new instructions file
|
||||
|
||||
Return to `SKILL.md` Step 3 once Step 3 below is done.
|
||||
|
||||
## Before touching the filesystem
|
||||
|
||||
Confirm, and ask the user for anything missing:
|
||||
|
||||
- [ ] The one topic the file covers. Two topics are two files.
|
||||
- [ ] Which files it governs, as a glob, or that it must load in every session.
|
||||
- [ ] A kebab-case stem. It becomes the deployed filename.
|
||||
|
||||
## Step 1 — Check the stem
|
||||
|
||||
Install overwrites a hand-authored file at `.claude/rules/<stem>.md`, `.cursor/rules/<stem>.mdc`, `.windsurf/rules/<stem>.md`, `.kiro/steering/<stem>.md` and `.agents/rules/<stem>.md` without a prompt. List those paths in the consuming project and choose another stem on any hit.
|
||||
|
||||
## Step 2 — Scaffold
|
||||
|
||||
```bash
|
||||
bash scripts/new-instructions.sh <name> <path-inside-the-package>
|
||||
```
|
||||
|
||||
The script walks up for a `type:`-bearing `apm.yml`. With none it exits 1 and names `/apm-workflow configure`; run that first, then retry. It never overwrites an existing file.
|
||||
|
||||
## Step 3 — Fill in
|
||||
|
||||
Replace every `FILL IN:` and delete the template's comments.
|
||||
|
||||
- `applyTo`: quoted. Omit it only for a rule that must load in every session, and say so to the user; it costs context at every launch.
|
||||
- `description`: one line. Write the body as if it were absent, because Claude Code never sees it.
|
||||
- Body: bullets, one topic, paths in backticks, nothing that assumes another file is loaded.
|
||||
|
||||
For glob syntax or a field question, read `references/schema.md`.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-docs-site
|
||||
- apm-cli-0-28-0-experiments
|
||||
---
|
||||
|
||||
# Improving an existing instructions file
|
||||
|
||||
Return to `SKILL.md` Step 3 once the edits are made.
|
||||
|
||||
## Step 1 — Read the file and the signals
|
||||
|
||||
Read the file whole. Signals are grill output, audit findings, inline feedback, or a session describing a rule that loaded when it should not, or failed to load. Apply what the signals name and nothing else.
|
||||
|
||||
## Step 2 — Diagnose by symptom
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| A scoped rule loads in every Claude session | `applyTo` is unquoted or malformed, so install deployed no `paths:` | Quote it, then confirm with `references/verify.md` |
|
||||
| Compile warns "Failed to parse" | Broken frontmatter YAML | Repair the YAML; do not delete the field |
|
||||
| The rule is in `CLAUDE.md` but not `.claude/rules/` | The file is nested under `.apm/instructions/` | Move it up to the flat directory |
|
||||
| The rule appears nowhere | The name lacks `.instructions.md` | Rename it |
|
||||
| Claude ignores guidance written in `description` | Claude Code drops `description` | Move the substance into the body |
|
||||
| A hand-written rule vanished after install | The stem collided with a deployed name | Restore it from version control and rename the source stem |
|
||||
| The same rule reaches the agent twice | Cursor, Windsurf, Kiro, Codex and OpenCode get both a native file and an `AGENTS.md` copy | State it to the user; it is apm behaviour, not a defect in the file |
|
||||
|
||||
Cases not in the table: read `references/target-mapping.md`.
|
||||
|
||||
## Step 3 — Split or trim
|
||||
|
||||
A file covering two topics, or longer than 200 lines, becomes several files. Do the split only when a signal names it.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-docs-site
|
||||
- apm-github-repo
|
||||
- apm-cli-0-28-0-experiments
|
||||
- claude-code-memory-docs
|
||||
---
|
||||
|
||||
# The instructions source file
|
||||
|
||||
Verified against apm 0.28.0. Reached from `SKILL.md` Step 2 when a frontmatter field, a glob or the file's location is in question.
|
||||
|
||||
## Location and name
|
||||
|
||||
`.apm/instructions/<name>.instructions.md`, flat. The double extension is the discovery key and the stem is the primitive's name; there is no `name` field.
|
||||
|
||||
- A plain `.md` in that directory is ignored by both `apm compile` and `apm install`.
|
||||
- A file in a subdirectory is folded into compiled root files by compile but never deployed by install, so it reaches `CLAUDE.md` and `AGENTS.md` and no native rules directory.
|
||||
- The stem becomes the deployed filename: `<stem>.md`, `<stem>.mdc`, or `<stem>.instructions.md`, by target.
|
||||
|
||||
## Frontmatter
|
||||
|
||||
Only `description` and `applyTo` carry meaning. `author` and `version` are parsed and never emitted to any target.
|
||||
|
||||
- `description`: one line. The apm docs call it required; the binary only warns. Copilot and Cursor keep it; Claude Code, Windsurf, Kiro, Antigravity and every compiled root file drop it. Cursor auto-generates one from the first body sentence when it is missing.
|
||||
- `applyTo`: a glob scoping the rule. The apm docs list it as both required and optional; the binary treats it as optional, with a warning. Empty or absent means an unconditional rule.
|
||||
|
||||
### `applyTo` grammar
|
||||
|
||||
- One glob: `"**/*.py"`.
|
||||
- Several globs in one string, comma-separated: `"**/*.css,**/*.scss"`. Whitespace around segments is trimmed.
|
||||
- A YAML sequence is joined into the same comma form.
|
||||
- Brace alternation is never split: `"**/*.{css,scss},**/*.py"` is two patterns.
|
||||
- A literal comma in a pattern is `\,`; a literal backslash is `\\`.
|
||||
- Always quote the value. An unquoted `**/*.py` is a YAML alias error; see `SKILL.md` Gotchas for what apm then does.
|
||||
|
||||
## Body
|
||||
|
||||
Plain markdown. Official guidance: bullets over prose, one topic per file (`python-style` and `python-testing` are two files), paths in backticks, no greetings or meta-commentary, no assumption that other files are loaded. apm sets no size limit. The downstream tools do: Claude Code recommends under 200 lines per file and Cursor under 500.
|
||||
|
||||
## Validation
|
||||
|
||||
`Instruction.validate()` yields three findings, all demoted to warnings: missing `description`, missing `applyTo` ("will apply globally") and empty content. A broken relative link in the body is a fourth, also non-fatal.
|
||||
|
||||
- A real `apm compile` prints them. `apm compile --validate` prints none and exits 0 even for a file with all three problems.
|
||||
- `apm install` prints none.
|
||||
- `apm audit --ci` checks lockfile, deployed-file presence, content hash and hidden Unicode, not instruction content.
|
||||
- A file whose frontmatter does not parse is skipped by compile ("Failed to parse") but still deployed by install.
|
||||
|
||||
No standalone instructions validator exists, so enforcement is this skill's checks and `references/verify.md`.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-docs-site
|
||||
- apm-github-repo
|
||||
- apm-cli-0-28-0-experiments
|
||||
- claude-code-memory-docs
|
||||
- github-copilot-custom-instructions-docs
|
||||
- cursor-rules-docs
|
||||
---
|
||||
|
||||
# Sources
|
||||
|
||||
## apm-docs-site
|
||||
|
||||
- **URL:** https://microsoft.github.io/apm/
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md (digest: plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-primitive-schema.md)
|
||||
- **Description:** Official apm documentation, the instructions-and-agents authoring page plus targets and compile pages: frontmatter requirements, per-target deploy paths, compile behaviour and flags.
|
||||
- **Contributing files:** SKILL.md, references/schema.md, references/target-mapping.md, references/create.md, references/improve.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## apm-github-repo
|
||||
|
||||
- **URL:** https://github.com/microsoft/apm
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md (digest: plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-primitive-schema.md)
|
||||
- **Description:** apm's own Python source read for the Instruction model, discovery globs and per-target integrators.
|
||||
- **Contributing files:** references/schema.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## apm-cli-0-28-0-experiments
|
||||
|
||||
- **URL:** https://pypi.org/project/apm-cli/0.28.0/
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md (digest: plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-gotchas.md)
|
||||
- **Description:** The installed apm-cli 0.28.0 package plus throwaway install, compile and audit experiments confirming validation severity, unquoted-glob handling, discovery asymmetry, dedup and overwrite behaviour.
|
||||
- **Contributing files:** SKILL.md, references/schema.md, references/target-mapping.md, references/verify.md, references/create.md, references/improve.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## claude-code-memory-docs
|
||||
|
||||
- **URL:** https://code.claude.com/docs/en/memory
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md (digest: plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-target-mapping.md)
|
||||
- **Description:** Claude Code memory documentation: `.claude/rules/` loading, the `paths` field as the only field read, invalid YAML ignored, size guidance.
|
||||
- **Contributing files:** SKILL.md, references/schema.md, references/target-mapping.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## github-copilot-custom-instructions-docs
|
||||
|
||||
- **URL:** https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md (digest: plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-target-mapping.md)
|
||||
- **Description:** GitHub Copilot repository custom instructions: `.github/instructions/*.instructions.md`, `applyTo` and `excludeAgent`, the separate repo-wide file.
|
||||
- **Contributing files:** references/target-mapping.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## cursor-rules-docs
|
||||
|
||||
- **URL:** https://cursor.com/docs/context/rules
|
||||
- **Research doc:** plugins/kyberforge/docs/research/docs/microsoft-apm/sources.md (digest: plugins/kyberforge/docs/research/docs/microsoft-apm/instructions-target-mapping.md)
|
||||
- **Description:** Cursor project rules: the `.mdc` requirement, `description`, `globs` and `alwaysApply`, rule types, size guidance.
|
||||
- **Contributing files:** references/target-mapping.md
|
||||
- **Status:** `extracted`
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-cli-0-28-0-experiments
|
||||
- apm-docs-site
|
||||
- claude-code-memory-docs
|
||||
- github-copilot-custom-instructions-docs
|
||||
- cursor-rules-docs
|
||||
---
|
||||
|
||||
# What each target receives
|
||||
|
||||
Verified against apm 0.28.0 and throwaway installs. Reached from `SKILL.md` Step 2 when the question is which target keeps which field. Source-file syntax is in `references/schema.md`.
|
||||
|
||||
## Two output paths
|
||||
|
||||
`apm install` writes one native file per instruction into each target's rules directory. `apm compile` writes root context files that concatenate instruction bodies, grouped by `applyTo`. Treat install as the primary path for Claude Code and Copilot, and compile as the path for targets with no native instructions directory.
|
||||
|
||||
## Install: deployed path and transform
|
||||
|
||||
| Target | Deployed path | Transform |
|
||||
|---|---|---|
|
||||
| copilot | `.github/instructions/<n>.instructions.md` | Verbatim copy |
|
||||
| claude | `.claude/rules/<n>.md` | `applyTo` becomes a `paths:` list; `description` dropped; no frontmatter at all without `applyTo` |
|
||||
| cursor | `.cursor/rules/<n>.mdc` | `applyTo` becomes `globs`; `description` kept; no `alwaysApply` written |
|
||||
| windsurf | `.windsurf/rules/<n>.md` | `trigger: glob` plus `globs`, or `trigger: always_on`; `description` dropped |
|
||||
| kiro | `.kiro/steering/<n>.md` | `inclusion: fileMatch` plus `fileMatchPattern`, or `inclusion: always`; `description` dropped |
|
||||
| antigravity | `.agents/rules/<n>.md` | `trigger: glob` plus `globs`, or no frontmatter; `description` dropped |
|
||||
| grok-build | `.grok/rules/<n>.instructions.md` | Verbatim copy |
|
||||
| codex, gemini, opencode and the rest | none | Reach instructions only through compile |
|
||||
|
||||
Windsurf, Kiro, Antigravity and Cursor do not deploy at user scope.
|
||||
|
||||
## Field survival
|
||||
|
||||
| Field | Claude | Copilot | Cursor | Windsurf, Kiro, Antigravity | Compiled root file |
|
||||
|---|---|---|---|---|---|
|
||||
| `applyTo` | as `paths` | verbatim | as `globs` | as each target's glob key | grouping only |
|
||||
| `description` | dropped | kept | kept | dropped | dropped |
|
||||
| `author`, `version` | dropped | kept only because the file is verbatim | dropped | dropped | dropped |
|
||||
|
||||
For Claude Code the body's first line or heading is the only descriptive text that survives, so the body must explain itself.
|
||||
|
||||
## Ownership and overwrite
|
||||
|
||||
- Claude, Cursor, Windsurf, Kiro and Antigravity treat each deployed file as apm-owned: install replaces a hand-authored file at the same path without a prompt. Copilot skips an unmanaged file ("local files exist, not managed by APM") until `apm install --force`.
|
||||
- Removing or renaming a source makes the next install delete the file it deployed.
|
||||
|
||||
## Compile
|
||||
|
||||
- `--target claude` writes `CLAUDE.md`; Gemini writes `GEMINI.md` and `AGENTS.md`; every other target writes `AGENTS.md`.
|
||||
- Compile skips instructions already deployed natively, for Claude, Copilot and Antigravity only. With rules populated, `--target claude` exits 0, prints "produced no output files" and writes nothing. `--force-instructions` (alias `--no-dedup`) overrides.
|
||||
- Cursor, Windsurf, Kiro, Grok, Codex and OpenCode have no dedup: compile writes `AGENTS.md` that repeats rules the tool already loads natively.
|
||||
- A compile with no instruction primitives exits 0.
|
||||
|
||||
## Native format facts
|
||||
|
||||
- Claude Code reads `.claude/rules/**/*.md` recursively. `paths` is the only field it reads, as a list or a comma-separated string; other fields are ignored. A rule without `paths` loads at every launch. Frontmatter that fails to parse is ignored and the rule loads without `paths`.
|
||||
- Copilot path-specific files need `applyTo` as a quoted comma-joined string; `excludeAgent` is the only other documented key. Repository-wide instructions are the separate `.github/copilot-instructions.md`.
|
||||
- Cursor ignores a plain `.md` in `.cursor/rules`. A rule with only a `description` is "Apply Intelligently", not always-on.
|
||||
|
||||
## Unverified
|
||||
|
||||
Cursor's handling of a YAML-list `globs`, Copilot's handling of unknown frontmatter keys, and runtime behaviour on Windsurf, Kiro and Antigravity. Say so rather than asserting any of them.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source_keys:
|
||||
- apm-cli-0-28-0-experiments
|
||||
---
|
||||
|
||||
# Verifying a file in a throwaway package
|
||||
|
||||
Reached from `SKILL.md` Step 3. Run step 2 outside the repo: `apm install` writes `apm_modules/`, `apm.lock.yaml` and a rules directory, and install overwrites hand-authored rule files without warning.
|
||||
|
||||
1. From the package root, a real compile, never `--validate`:
|
||||
|
||||
```bash
|
||||
apm compile --dry-run --target claude
|
||||
```
|
||||
|
||||
Resolve every warning it prints: missing `description`, missing `applyTo`, empty content, broken link, "Failed to parse".
|
||||
|
||||
2. For a scoped rule, deploy it where nothing else can be overwritten:
|
||||
|
||||
```bash
|
||||
d=$(mktemp -d)
|
||||
printf 'name: scratch\nversion: 0.1.0\ntype: instructions\ntargets:\n - claude\n' > "$d/apm.yml"
|
||||
mkdir -p "$d/.apm/instructions"
|
||||
cp <package-root>/.apm/instructions/<name>.instructions.md "$d/.apm/instructions/"
|
||||
(cd "$d" && apm install && cat .claude/rules/<name>.md)
|
||||
```
|
||||
|
||||
3. The deployed file must open with `paths:` listing the intended globs. No frontmatter block at all means `applyTo` was missing or did not parse: the rule would load in every session.
|
||||
|
||||
4. To check the compiled root file instead, compile in that same clean directory *before* installing, or pass `--force-instructions`; after an install, `--target claude` writes nothing.
|
||||
|
||||
Delete the directory afterwards. Report only what was observed; Cursor's list-form `globs` and the Windsurf, Kiro and Antigravity runtimes stay unverified.
|
||||
@@ -0,0 +1,3 @@
|
||||
# scripts/
|
||||
|
||||
- **`new-instructions.sh <name> <root>`** — scaffolds `<package-root>/.apm/instructions/<name>.instructions.md` from `assets/templates/instructions.md`. Walks up from `<root>` for the nearest `type:`-bearing `apm.yml`; exits 1 with a pointer to `/apm-workflow configure` when there is none. Never overwrites an existing file. Run `--help` for the full contract.
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEMPLATE="$SKILL_DIR/../assets/templates/instructions.md"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: new-instructions.sh <name> <root>
|
||||
|
||||
Scaffold an apm instructions file from the bundled template.
|
||||
|
||||
Arguments:
|
||||
name Kebab-case stem. Becomes <name>.instructions.md and, after install,
|
||||
the deployed rule's filename.
|
||||
root Existing path at or below the target package. The script walks up for
|
||||
the nearest apm.yml with a top-level type: field (instructions, skill,
|
||||
hybrid or prompts); an apm.yml without type: is a marketplace-only
|
||||
manifest and is skipped. Creates
|
||||
<package-root>/.apm/instructions/<name>.instructions.md
|
||||
|
||||
Exit codes:
|
||||
0 File created, or already existed (no-op)
|
||||
1 Invalid arguments, missing root, no package found, or template not found
|
||||
USAGE
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Error: name and root are required." >&2
|
||||
echo "" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NAME="$1"
|
||||
ROOT="${2/#\~/$HOME}"
|
||||
|
||||
if ! grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$' <<< "$NAME"; then
|
||||
echo "Error: name must use lowercase letters, numbers, and hyphens only." >&2
|
||||
echo " No leading, trailing, or consecutive hyphens." >&2
|
||||
echo " Received: '$NAME'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$TEMPLATE" ]]; then
|
||||
echo "Error: template not found at '$TEMPLATE'." >&2
|
||||
echo " Run this script from its original location inside the instructions-author skill." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$ROOT" ]]; then
|
||||
echo "Error: root directory '$ROOT' does not exist." >&2
|
||||
exit 1
|
||||
fi
|
||||
ROOT="$(cd "$ROOT" && pwd)"
|
||||
|
||||
# Same marker as agent-author's new-agent.sh: a top-level type: naming one of
|
||||
# the four package types, with matching quotes if quoted.
|
||||
is_apm_package_manifest() {
|
||||
local apm_yml="$1" line
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^type:[[:space:]]*(instructions|skill|hybrid|prompts)([[:space:]]|$) ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$line" =~ ^type:[[:space:]]*([\"\'])(instructions|skill|hybrid|prompts)([\"\'])([[:space:]]|$) ]] \
|
||||
&& [[ "${BASH_REMATCH[1]}" == "${BASH_REMATCH[3]}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done < "$apm_yml"
|
||||
return 1
|
||||
}
|
||||
|
||||
PACKAGE_ROOT=""
|
||||
current="$ROOT"
|
||||
while true; do
|
||||
if [[ -f "$current/apm.yml" ]] && is_apm_package_manifest "$current/apm.yml"; then
|
||||
PACKAGE_ROOT="$current"
|
||||
break
|
||||
fi
|
||||
if [[ -e "$current/.git" ]]; then
|
||||
break
|
||||
fi
|
||||
parent="$(dirname "$current")"
|
||||
[[ "$parent" == "$current" ]] && break
|
||||
current="$parent"
|
||||
done
|
||||
|
||||
if [[ -z "$PACKAGE_ROOT" ]]; then
|
||||
echo "Error: no apm package found at or above '$ROOT'." >&2
|
||||
echo " Instructions only deploy from a package's .apm/instructions/. Run" >&2
|
||||
echo " /apm-workflow configure (apm plugin init) there first, then retry." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEST_DIR="$PACKAGE_ROOT/.apm/instructions"
|
||||
DEST="$DEST_DIR/$NAME.instructions.md"
|
||||
|
||||
if [[ -f "$DEST" ]]; then
|
||||
echo "Skipping '$DEST' — already exists." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$DEST_DIR"
|
||||
cp "$TEMPLATE" "$DEST"
|
||||
echo "Created: $DEST" >&2
|
||||
echo "" >&2
|
||||
echo "Next steps:" >&2
|
||||
echo " 1. Fill in $DEST — replace every FILL IN: placeholder and delete the comments." >&2
|
||||
echo " 2. Check '$NAME' does not collide with a hand-authored rule: install overwrites" >&2
|
||||
echo " <target>/rules/$NAME.* on most targets without warning." >&2
|
||||
echo " 3. Verify with a real compile, not --validate: apm compile --dry-run --target <target>" >&2
|
||||
@@ -0,0 +1,15 @@
|
||||
# tests/
|
||||
|
||||
- **`new-instructions.bats`** — covers `scripts/new-instructions.sh` (name validation, package walk-up, no-package refusal, no-op on an existing file, template placeholders) and the apm behaviour the skill's gotchas rest on, run against throwaway packages: a filled scaffold compiles into `CLAUDE.md` and installs into `.claude/rules/` with `description` dropped, compile writes nothing once rules are installed, an unquoted `applyTo` installs unscoped, and `--validate` hides the warnings a real compile prints.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The test file loads `bats-support` and `bats-assert` from the repo root's `tests/test_helper/`, and runs on the repo's bats submodule at `tests/bats/`. The first `bash tests/run-bats.sh` initialises the submodules.
|
||||
|
||||
The apm tests need the `apm` CLI on `PATH` and skip when it is absent. They assert apm 0.28.0 behaviour, so a failure after an apm upgrade is a finding about the skill's gotchas, not a flaky test.
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
tests/bats/bin/bats plugins/kyberforge/.apm/skills/instructions-author/tests/new-instructions.bats
|
||||
```
|
||||
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
setup() {
|
||||
REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../../../../../../" && pwd)"
|
||||
load "$REPO_ROOT/tests/test_helper/bats-support/load"
|
||||
load "$REPO_ROOT/tests/test_helper/bats-assert/load"
|
||||
|
||||
SCRIPT="$(cd "$BATS_TEST_DIRNAME/../scripts" && pwd)/new-instructions.sh"
|
||||
ROOT="$(mktemp -d)"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf "$ROOT"
|
||||
}
|
||||
|
||||
make_package() {
|
||||
printf 'name: my-package\nversion: 0.1.0\ntype: instructions\ntargets:\n - claude\n' > "$ROOT/apm.yml"
|
||||
}
|
||||
|
||||
# Replace every placeholder and drop the template's comments, leaving a valid file.
|
||||
fill() {
|
||||
sed -i -E \
|
||||
-e '/^#/{/^# FILL IN/!d}' \
|
||||
-e 's/^description: FILL IN.*/description: Python style rules/' \
|
||||
-e 's/^applyTo: .*/applyTo: "**\/*.py"/' \
|
||||
-e 's/^# FILL IN.*/# Python style/' \
|
||||
-e 's/^- FILL IN.*/- Use type hints./' \
|
||||
"$1"
|
||||
}
|
||||
|
||||
need_apm() {
|
||||
command -v apm >/dev/null 2>&1 || skip "apm CLI not installed"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Help and arguments
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test "--help exits 0" {
|
||||
run bash "$SCRIPT" --help
|
||||
assert_success
|
||||
assert_output --partial "Usage:"
|
||||
}
|
||||
|
||||
@test "missing arguments exits 1" {
|
||||
run bash "$SCRIPT"
|
||||
assert_failure
|
||||
assert_output --partial "name and root are required"
|
||||
}
|
||||
|
||||
@test "nonexistent root exits 1" {
|
||||
run bash "$SCRIPT" my-rule "$ROOT/missing"
|
||||
assert_failure
|
||||
assert_output --partial "does not exist"
|
||||
}
|
||||
|
||||
@test "rejects names that are not kebab-case" {
|
||||
make_package
|
||||
for bad in My-Rule my_rule -leading trailing- double--hyphen; do
|
||||
run bash "$SCRIPT" "$bad" "$ROOT"
|
||||
assert_failure
|
||||
assert_output --partial "lowercase letters"
|
||||
done
|
||||
assert [ ! -d "$ROOT/.apm" ]
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Package resolution
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test "creates <name>.instructions.md under .apm/instructions/" {
|
||||
make_package
|
||||
run bash "$SCRIPT" my-rule "$ROOT"
|
||||
assert_success
|
||||
assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ]
|
||||
}
|
||||
|
||||
@test "walks up from a subdirectory to the package root" {
|
||||
make_package
|
||||
mkdir -p "$ROOT/deep/er"
|
||||
run bash "$SCRIPT" my-rule "$ROOT/deep/er"
|
||||
assert_success
|
||||
assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ]
|
||||
assert [ ! -d "$ROOT/deep/er/.apm" ]
|
||||
}
|
||||
|
||||
@test "skips a type-less apm.yml and keeps walking up" {
|
||||
make_package
|
||||
mkdir -p "$ROOT/marketplace"
|
||||
printf 'name: catalog\nmarketplace:\n packages: []\n' > "$ROOT/marketplace/apm.yml"
|
||||
run bash "$SCRIPT" my-rule "$ROOT/marketplace"
|
||||
assert_success
|
||||
assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ]
|
||||
assert [ ! -d "$ROOT/marketplace/.apm" ]
|
||||
}
|
||||
|
||||
@test "accepts a quoted type value" {
|
||||
printf 'name: p\ntype: "hybrid"\n' > "$ROOT/apm.yml"
|
||||
run bash "$SCRIPT" my-rule "$ROOT"
|
||||
assert_success
|
||||
assert [ -f "$ROOT/.apm/instructions/my-rule.instructions.md" ]
|
||||
}
|
||||
|
||||
@test "no package: exits 1, points at apm-workflow configure, writes nothing" {
|
||||
mkdir -p "$ROOT/.git"
|
||||
run bash "$SCRIPT" my-rule "$ROOT"
|
||||
assert_failure
|
||||
assert_output --partial "apm-workflow configure"
|
||||
assert [ ! -d "$ROOT/.apm" ]
|
||||
}
|
||||
|
||||
@test "does not walk above a .git boundary" {
|
||||
make_package
|
||||
mkdir -p "$ROOT/repo/.git"
|
||||
run bash "$SCRIPT" my-rule "$ROOT/repo"
|
||||
assert_failure
|
||||
assert [ ! -d "$ROOT/.apm" ]
|
||||
}
|
||||
|
||||
@test "no-op when the file already exists" {
|
||||
make_package
|
||||
mkdir -p "$ROOT/.apm/instructions"
|
||||
echo "existing" > "$ROOT/.apm/instructions/my-rule.instructions.md"
|
||||
run bash "$SCRIPT" my-rule "$ROOT"
|
||||
assert_success
|
||||
run cat "$ROOT/.apm/instructions/my-rule.instructions.md"
|
||||
assert_output "existing"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Template contents
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test "scaffold carries FILL IN placeholders and a quoted applyTo" {
|
||||
make_package
|
||||
bash "$SCRIPT" my-rule "$ROOT"
|
||||
file="$ROOT/.apm/instructions/my-rule.instructions.md"
|
||||
run grep -c 'FILL IN' "$file"
|
||||
assert_success
|
||||
run grep -E '^applyTo: "' "$file"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# apm behaviour the skill's gotchas rest on (verified against apm 0.28.0)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test "filled scaffold compiles for the Claude target into CLAUDE.md without the description" {
|
||||
need_apm
|
||||
make_package
|
||||
bash "$SCRIPT" my-rule "$ROOT"
|
||||
fill "$ROOT/.apm/instructions/my-rule.instructions.md"
|
||||
cd "$ROOT"
|
||||
run apm compile --target claude
|
||||
assert_success
|
||||
assert [ -f "$ROOT/CLAUDE.md" ]
|
||||
run grep -F 'Use type hints.' "$ROOT/CLAUDE.md"
|
||||
assert_success
|
||||
run grep -F 'Python style rules' "$ROOT/CLAUDE.md"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "install deploys .claude/rules with paths: and drops the description" {
|
||||
need_apm
|
||||
make_package
|
||||
bash "$SCRIPT" my-rule "$ROOT"
|
||||
fill "$ROOT/.apm/instructions/my-rule.instructions.md"
|
||||
cd "$ROOT"
|
||||
run apm install
|
||||
assert_success
|
||||
rule="$ROOT/.claude/rules/my-rule.md"
|
||||
assert [ -f "$rule" ]
|
||||
run grep -F 'paths:' "$rule"
|
||||
assert_success
|
||||
run grep -F '**/*.py' "$rule"
|
||||
assert_success
|
||||
run grep -F 'Python style rules' "$rule"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "once rules are installed, compile --target claude writes no CLAUDE.md and exits 0" {
|
||||
need_apm
|
||||
make_package
|
||||
bash "$SCRIPT" my-rule "$ROOT"
|
||||
fill "$ROOT/.apm/instructions/my-rule.instructions.md"
|
||||
cd "$ROOT"
|
||||
apm install
|
||||
run apm compile --target claude
|
||||
assert_success
|
||||
assert [ ! -f "$ROOT/CLAUDE.md" ]
|
||||
}
|
||||
|
||||
@test "an unquoted applyTo still installs, as a rule with no paths:" {
|
||||
need_apm
|
||||
make_package
|
||||
mkdir -p "$ROOT/.apm/instructions"
|
||||
printf -- '---\ndescription: x\napplyTo: **/*.py\n---\n# T\n\n- a\n' > "$ROOT/.apm/instructions/bad.instructions.md"
|
||||
cd "$ROOT"
|
||||
run apm install
|
||||
assert_success
|
||||
assert [ -f "$ROOT/.claude/rules/bad.md" ]
|
||||
run grep -F 'paths:' "$ROOT/.claude/rules/bad.md"
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "compile --validate exits 0 and hides the warnings a real compile prints" {
|
||||
need_apm
|
||||
make_package
|
||||
mkdir -p "$ROOT/.apm/instructions"
|
||||
printf -- '---\napplyTo: "**/*.py"\n---\n' > "$ROOT/.apm/instructions/bare.instructions.md"
|
||||
cd "$ROOT"
|
||||
run apm compile --validate
|
||||
assert_success
|
||||
refute_output --partial "Missing 'description'"
|
||||
run apm compile --dry-run --target claude
|
||||
assert_success
|
||||
assert_output --partial "Missing 'description'"
|
||||
assert_output --partial "Empty content"
|
||||
}
|
||||
@@ -9,7 +9,7 @@ apm is the only supported install path (ADR-0024). Declare this package in the c
|
||||
```yaml
|
||||
dependencies:
|
||||
apm:
|
||||
- git: git@git.dev.rkdr.net:Defame1297/holocron.git
|
||||
- git: git@git.rkdr.net:Defame1297/holocron.git
|
||||
path: plugins/kyberforge
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ Then:
|
||||
apm install
|
||||
```
|
||||
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add git@git.dev.rkdr.net:Defame1297/holocron.git --name holocron`) gets you the `kyberforge@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
The entry above is unpinned and tracks the remote's default branch — add `ref: <tag>` to pin a release. Registering the catalogue instead (`apm marketplace add [email protected]:Defame1297/holocron.git --name holocron`) gets you the `kyberforge@holocron` short name, but writes to `~/.apm/marketplaces.json` at user scope; the git+path object needs nothing beyond the manifest.
|
||||
|
||||
**Native plugin installs do not work.** This package ships no per-plugin manifest and no flat content directories, so a host that installs it natively gets zero skills, agents and hooks — and Claude Code raises no error while doing it (ADR-0024).
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
name: kyberforge
|
||||
version: 2.0.0
|
||||
version: 2.1.0
|
||||
description: Skills and agents for creating, maintaining, and managing an apm plugin marketplace for Claude Code and GitHub Copilot.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/kyberforge
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/kyberforge
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/kyberforge
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/kyberforge
|
||||
keywords:
|
||||
- marketplace
|
||||
- plugin
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
topic: instructions-gotchas
|
||||
source_keys:
|
||||
- apm-cli-0-28-0-experiments
|
||||
- apm-docs-site
|
||||
- claude-code-memory-docs
|
||||
- cursor-rules-docs
|
||||
- github-copilot-custom-instructions-docs
|
||||
---
|
||||
|
||||
Surprising behaviours and source contradictions for the instructions primitive. "Verified" means observed with the installed apm 0.28.0 in a throwaway directory outside the repo. Everything else is stated as sourced or inferred.
|
||||
|
||||
## Verified failure modes
|
||||
|
||||
### Unquoted glob silently widens scope
|
||||
|
||||
`applyTo: **/*.py` (unquoted) is a YAML alias error. Verified outcome:
|
||||
|
||||
- `apm compile` and `apm compile --validate` print "Failed to parse" and skip the file, exit 0; the validated-primitive count is one lower.
|
||||
- `apm install` still deploys the file, to `.claude/rules/<name>.md` with no `paths:` frontmatter. A rule meant for Python files becomes an unconditional rule loaded in every session. Nothing errors.
|
||||
- Any frontmatter that is broken YAML (for example `description: [broken`) behaves the same way.
|
||||
- Claude Code itself behaves consistently: invalid frontmatter is ignored and the rule loads without `paths`.
|
||||
|
||||
Rule for the skill: always quote `applyTo`, and after scaffolding check that the deployed file has the expected `paths:`; a missing frontmatter block is the symptom.
|
||||
|
||||
### Validation never fails
|
||||
|
||||
Missing `description`, missing `applyTo` and an empty body are warnings only. `apm compile --validate` prints "All primitives validated successfully" and exits 0 even for those, and shows none of the warnings. Only a real `apm compile` prints them. `apm install` and `apm audit --ci` print nothing about instruction content. The official docs call `description` and `applyTo` required; the binary does not enforce either. Any enforcement has to live in this repo's own checks.
|
||||
|
||||
### Nested files: compile sees them, install does not
|
||||
|
||||
`.apm/instructions/sub/x.instructions.md` is folded into compiled root files but never deployed natively. A plain `x.md` (no `.instructions` infix) is ignored by both.
|
||||
|
||||
### Compile writes nothing when native rules exist (Claude, Copilot, Antigravity)
|
||||
|
||||
`apm compile --target claude` after an install exits 0, prints "produced no output files" and creates no `CLAUDE.md`. Use `--force-instructions` or compile in a project with no native rules. A test that only checks the exit code passes without testing anything.
|
||||
|
||||
### Compile duplicates content for the other targets
|
||||
|
||||
For cursor, windsurf, kiro, codex (and grok, opencode by source) compile still writes `AGENTS.md` even though native rules exist, so the same instruction reaches the agent twice.
|
||||
|
||||
### Install overwrites hand-authored rule files
|
||||
|
||||
For claude, cursor, windsurf, kiro and antigravity, an existing file at the deployed path is replaced without warning. Copilot skips it and asks for `--force`.
|
||||
|
||||
### Empty-source compile is not an error
|
||||
|
||||
Plain `apm compile` and `--target all` in a project with no instruction primitives print "no source primitives remain" and exit 0. The apm-workflow compile reference currently says this hard-fails with exit 1 and "No instruction files found in .apm/ directory"; that does not hold in 0.28.0 (see contradictions).
|
||||
|
||||
## Cursor-specific
|
||||
|
||||
- Install emits `globs` plus `description` and never `alwaysApply`. Per the Cursor docs a rule with only a `description` is "Apply Intelligently", so an unscoped apm instruction does not become always-on in Cursor (inferred from docs plus verified output; Cursor runtime not tested).
|
||||
- Multiple globs are emitted as a YAML list (Kiro likewise). The Cursor docs show only a comma-separated string. Unverified whether Cursor honours the list form.
|
||||
|
||||
## Claude-specific
|
||||
|
||||
- `description` is dropped, so it can never appear in a `.claude/rules/` file; do not rely on it for Claude Code. Source: the apm source transform and verified deployed output. The apm docs do not state this.
|
||||
- A rule with no `applyTo` becomes a file with no frontmatter and loads at every launch, which costs context. Claude Code guidance is to keep each file short (under 200 lines).
|
||||
- The documented Claude `paths` budget is 1,000 brace-expanded patterns and 4 MiB.
|
||||
|
||||
## Contradictions between sources
|
||||
|
||||
| Point | Official apm docs | Installed 0.28.0 behaviour |
|
||||
|---|---|---|
|
||||
| `description` | Required | Warning only |
|
||||
| `applyTo` | Listed as required and also as optional | Optional, warning only |
|
||||
| Instruction with no `applyTo` | Folded into compiled root files instead of a per-file rule | Still deployed per-file on every rule-directory target (Claude: no frontmatter; Cursor: description only; Windsurf: `always_on`; Kiro: `always`) and also compiled |
|
||||
| Grok deployed name | `.grok/rules/<name>.md` | `.grok/rules/<name>.instructions.md` |
|
||||
| Compile with nothing to compile | apm-workflow compile reference: exit 1 with a "No instruction files found" message | Exit 0 |
|
||||
| Compile scope | Docs say compile "only handles instructions" | Consistent for content, but compile also emits GEMINI.md and honours the agents_md mode |
|
||||
| Cursor and Windsurf at user scope | Two fetches of the docs disagreed | Source excludes both at user scope; the source was preferred |
|
||||
|
||||
An earlier version of this topic's schema file described missing `description` and empty content as errors and `skip_instructions` as a config flag; both were wrong for 0.28.0 (warnings; internal variable).
|
||||
|
||||
## Unverified
|
||||
|
||||
- Whether Cursor accepts a YAML list for `globs`.
|
||||
- Whether Copilot ignores unknown frontmatter keys such as `description`; its docs list only `applyTo` and `excludeAgent`.
|
||||
- Runtime behaviour of Windsurf, Kiro and Antigravity on the emitted frontmatter; no downstream docs were fetched.
|
||||
- Windsurf user-scope global rules.
|
||||
- The Context7 step was unavailable (invalid API key), so the registry carries no fresh Context7 pull. Doc pages were summarised by a smaller model before reaching this file and can be lossy.
|
||||
- Apm versions other than 0.28.0 were not tested.
|
||||
+50
-42
@@ -3,64 +3,72 @@ topic: instructions-primitive-schema
|
||||
source_keys:
|
||||
- context7-microsoft-apm
|
||||
- apm-github-repo
|
||||
- apm-docs-site
|
||||
- apm-cli-0-28-0-experiments
|
||||
---
|
||||
|
||||
## File location, naming, and frontmatter
|
||||
Scope of this file: what an instructions source file is, where it lives, what its frontmatter means, how `applyTo` is parsed, and what validation exists. Per-target output is in `instructions-target-mapping.md`; behaviours that surprised us and where sources disagree are in `instructions-gotchas.md`. Everything here was re-checked against apm 0.28.0 (the installed binary) in this revision.
|
||||
|
||||
`.apm/instructions/*.instructions.md`. Confirmed as the genuine required extension (not assumed) via APM's own discovery glob in `apm_cli/primitives/discovery.py`: `**/.apm/instructions/*.instructions.md` (and the `.github/instructions/` mirror, plus a bare `**/*.instructions.md` fallback).
|
||||
## File location, naming, and discovery
|
||||
|
||||
Unlike prompts and hooks, instructions **do** have a small, concretely modeled dataclass — `apm_cli.primitives.models.Instruction` — because instructions feed APM's own compile pipeline (they get folded into root context files), not just pass-through deployment:
|
||||
Source files live at `.apm/instructions/<name>.instructions.md`. The `.instructions.md` double extension is the real discovery key: the parser strips `.instructions.md` to get the primitive name, and a plain `.md` file in `.apm/instructions/` is ignored by both compile and install (verified by experiment).
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Instruction:
|
||||
name: str
|
||||
file_path: Path
|
||||
description: str
|
||||
apply_to: str # from frontmatter key "applyTo"; empty means global/unconditional
|
||||
content: str
|
||||
author: str | None = None
|
||||
version: str | None = None
|
||||
source: str | None = None
|
||||
```
|
||||
Discovery is not identical in compile and install:
|
||||
|
||||
Frontmatter fields: `description` (required by convention — its absence is a validation error) and `applyTo` (a glob or comma-separated glob list, or a YAML sequence — APM normalizes all three input shapes into one canonical comma-separated form internally via `normalize_apply_to`/`parse_apply_to`). No `applyTo` means the rule is treated as **unconditional** — folded into root context files as always-on guidance rather than scoped to specific paths.
|
||||
- `apm install` (the per-target deploy step) looks only in `.apm/instructions/` of the package, non-recursively. An instruction placed in a subdirectory such as `.apm/instructions/sub/x.instructions.md` is not deployed to any target (verified by experiment).
|
||||
- `apm compile` (the root-context fold-in) discovers with a wider glob set: `.apm/instructions/`, a `.github/instructions/` mirror, and a bare `**/*.instructions.md` fallback. The same nested file that install ignores is picked up by compile (verified by experiment). An author who nests files therefore gets them in `CLAUDE.md`/`AGENTS.md` but not in `.claude/rules/` or any other native rules directory.
|
||||
- Dependency packages are scanned the same way: `instructions/*.instructions.md` under the dependency's `.apm/` (and `.github/` as a fallback).
|
||||
- Primitive name collisions across local and dependency sources are tracked as conflicts; local wins.
|
||||
|
||||
`Instruction.validate()` produces these built-in errors/warnings:
|
||||
- Missing `description` → error: `"Missing 'description' in frontmatter"`.
|
||||
- Missing `applyTo` → warning-level: `"No 'applyTo' pattern specified -- instruction will apply globally"` (not fatal — it's accepted, just broad).
|
||||
- Empty body → error: `"Empty content"`.
|
||||
The deployed filename derives from the source stem: `<stem>.instructions.md` becomes `<stem>.md`, `<stem>.mdc`, or stays `<stem>.instructions.md`, depending on target.
|
||||
|
||||
## Compile-time mapping: two entirely different mechanisms per target
|
||||
## Frontmatter fields
|
||||
|
||||
This is the biggest divergence from the agent/skill/prompt primitives, and the one most likely to surprise: **Claude Code does not get a verbatim copy of the `.instructions.md` file at all.**
|
||||
The parser reads exactly these keys from the frontmatter into the `Instruction` model: `description`, `applyTo`, plus optional `author` and `version`. Nothing else is modelled. There is no `name` field; the name always comes from the filename.
|
||||
|
||||
**Copilot CLI — verbatim, native primitive.** `PrimitiveMapping("instructions", ".instructions.md", "github_instructions")` on the `copilot` target has no `output_compare` flag, so `InstructionIntegrator` copies content through unchanged, preserving the original `applyTo:` frontmatter byte-for-byte (per the integrator's own docstring: "Copilot: `.github/instructions/` (verbatim, preserving applyTo:)"). This is deployed by `apm install`, not `apm compile`.
|
||||
- `description`: one-line summary. The official authoring page lists it as required. In the binary it is only a warning when missing (see Validation). It is consumed by Cursor (kept in the `.mdc`, and auto-generated from the first body sentence when missing) and by Copilot (verbatim file). It is discarded for Claude Code, Windsurf, Kiro, Antigravity, and in all compiled root files.
|
||||
- `applyTo`: a glob that scopes the rule. See the grammar below. The official authoring page labels it required for instructions, yet states elsewhere that omitting it is supported and yields an unconditional rule. The binary treats it as optional with a warning.
|
||||
- `author`, `version`: parsed into the model but never emitted to any target.
|
||||
|
||||
At **Copilot user scope only** (`~/.copilot/`), individual files are not deployed — Copilot CLI at user scope reads a single `copilot-instructions.md`, so APM concatenates all instructions into that one file instead (`user_primitive_overrides: {"instructions": PrimitiveMapping("", ".md", "copilot_user_instructions")}`). Project-scope behavior (per-file, `.github/instructions/`) is unaffected.
|
||||
Body is plain markdown. An empty body is a validation warning. The official guidance for body style is: bullets over prose, one topic per file (split `python-style` from `python-testing`), cite paths in backticks, no greetings or meta-commentary, and do not assume other context is loaded. No numeric size limit is documented by apm; the downstream tools give their own (Claude Code recommends under 200 lines per instruction file; Cursor recommends under 500 lines per rule; Copilot says repository-wide instructions should be no longer than two pages).
|
||||
|
||||
**Claude Code — real reconstruction into `.claude/rules/`, with field-dropping.** `PrimitiveMapping("rules", ".md", "claude_rules", output_compare=True)` marks this as one of APM's four "rule formats" (`RULE_FORMATS = {cursor_rules, claude_rules, windsurf_rules, kiro_steering}`) that transform their source rather than copy it. `InstructionIntegrator._convert_to_claude_rules()`:
|
||||
## applyTo grammar
|
||||
|
||||
- Parses the source frontmatter and pulls only `applyTo` — **`description` is dropped entirely**, not carried into the output in any form.
|
||||
- Converts `applyTo` into a `paths:` YAML list (one `parse_apply_to()`-split glob per line), e.g. `applyTo: "**/*.py"` → `paths:\n - "**/*.py"`.
|
||||
- If there was no `applyTo` (unconditional instruction), the output has **no frontmatter at all** — just the raw body, matching Claude's convention that files without `paths:` in `.claude/rules/` apply unconditionally.
|
||||
- Filename is renamed: `<x>.instructions.md` → `<x>.md` (the primitive's `extension` field, `.md`, replaces the source suffix — this is the general rule for every `output_compare=True` "rule format").
|
||||
`applyTo` is normalised to one comma-separated string and then split by `parse_apply_to`:
|
||||
|
||||
This is architecturally the same category of lossy, real transformation the prior agent-primitive research found for Codex/Kiro agents — except here it's the default behavior for Claude specifically (not an opt-out edge case), and it applies even though Claude and Copilot are both first-class, actively-supported targets.
|
||||
- A single glob: `"**/*.py"`.
|
||||
- A comma-separated list in one string: `"**/*.css,**/*.scss"`. Whitespace around segments is trimmed and empty segments are dropped, so `"**/*.py, **/*.go"` is fine.
|
||||
- A YAML sequence: every non-null entry is kept and joined into the same comma form; an entry that itself contains a top-level comma is escaped so it stays one pattern.
|
||||
- Brace alternation `{a,b}` is never split: `"**/*.{css,scss},**/*.py"` yields two patterns.
|
||||
- A literal top-level comma in a pattern is written `\,`; a literal backslash is `\\`.
|
||||
- Always quote glob values in YAML. An unquoted value starting with `*` (for example `applyTo: **/*.py`) is a YAML alias token and fails to parse. What apm then does is the most dangerous failure mode in this primitive; see `instructions-gotchas.md`.
|
||||
|
||||
## Compile-time file placement
|
||||
When `applyTo` is empty or absent the instruction is unconditional ("global"). Distributed compile places it in the root `AGENTS.md`/`CLAUDE.md`; native deploy produces an always-on rule in the target's own syntax.
|
||||
|
||||
| Target | Output path | Transform |
|
||||
|---|---|---|
|
||||
| Copilot CLI (project scope) | `.github/instructions/<name>.instructions.md` | Verbatim byte copy, `applyTo:` preserved as-is |
|
||||
| Copilot CLI (user scope, `~/.copilot/`) | `~/.copilot/copilot-instructions.md` | Concatenated — all instructions merged into one file, because Copilot CLI at user scope reads only that single file |
|
||||
| Claude Code | `.claude/rules/<name>.md` | Reconstructed: `applyTo` → `paths:` YAML list; `description` dropped; no frontmatter at all if unconditional |
|
||||
Scoped patterns in distributed compile may match files under dot-directories apm knows about (`.agents`, `.apm`, `.claude`, `.codex`, `.cursor`, `.gemini`, `.github`, `.kiro`, `.opencode`, `.windsurf`); other hidden directories are excluded from matching.
|
||||
|
||||
Additionally, **`apm compile`** (distinct from `apm install`) can also fold instruction content directly into root context files — `AGENTS.md` (single-file or per-directory "distributed" mode) and the Claude-specific parallel format `CLAUDE.md`/per-directory `CLAUDE.md` — grouped by directory using `applyTo` pattern analysis (`context_optimizer.optimize_instruction_placement`). To avoid duplicating content between the native `.claude/rules/`+`.github/instructions/` deployment (from `apm install`) and this root-context fold-in (from `apm compile`), a `skip_instructions` config flag (and `compilation.placement.min_instructions_per_file` in `apm.yml`) actively suppresses the redundant copy in AGENTS.md/CLAUDE.md once native per-target files exist — `apm compile --target claude --force-instructions` overrides this dedup when an author explicitly wants both.
|
||||
## Validation
|
||||
|
||||
## Validation constraints and gotchas
|
||||
`Instruction.validate()` returns up to three findings:
|
||||
|
||||
- **The `description` field is real for Copilot but silently discarded for Claude.** An author who relies on `description` to explain *why* a rule exists (common practice, since Copilot's `.instructions.md` UI can surface it) gets that context deleted on every Claude compile — there's no config to keep it as a comment or otherwise.
|
||||
- **No content-level validation for the `paths:` conversion** — if `applyTo` contains a pattern `parse_apply_to` can't split sensibly, the resulting `paths:` list is whatever falls out; no dedicated schema check catches a malformed glob before deploy.
|
||||
- **Directory-distribution logic for AGENTS.md/CLAUDE.md is heuristic, not declarative** — `context_optimizer.optimize_instruction_placement` picks placement directories from `applyTo` patterns algorithmically; `compilation.placement.min_instructions_per_file` in `apm.yml` (default effectively 1) is the only tuning knob, and setting it above 1 causes under-populated directories to have their instructions bubbled up to the parent directory rather than dropped.
|
||||
- **Same "no dedicated primitive validation function" gap noted for agents** — `Instruction.validate()` in `primitives/models.py` is the only validation, and it is invoked as part of the generic primitive-discovery/compile pipeline, not as a standalone `apm audit` check comparable to what exists for `apm.yml` itself.
|
||||
- Missing `description`: "Missing 'description' in frontmatter".
|
||||
- Missing `applyTo`: "No 'applyTo' pattern specified -- instruction will apply globally".
|
||||
- Empty body: "Empty content".
|
||||
|
||||
All three are demoted to warnings by the compiler, so none of them fails any command. Verified by experiment: a file with no `description` and an empty body compiles with exit 0 and three warnings, and `apm install` deploys it (to `.claude/rules/` it produces a file holding only the `paths:` frontmatter). `apm compile --validate` calls the same code but discards warnings: it prints "All primitives validated successfully!" and exits 0 even for the bad file, so it is not a usable lint gate for instruction content. The warnings appear only on a real `apm compile` run, and `apm install` prints none of them.
|
||||
|
||||
Markdown links in the body are also checked at compile time; a broken relative link is a warning with the same non-fatal behaviour.
|
||||
|
||||
Files whose frontmatter does not parse as YAML are skipped by compile with a "Failed to parse" message (and `--validate` then counts one fewer primitive), but are still deployed by install. This asymmetry is covered in the gotchas file.
|
||||
|
||||
There is no standalone instructions validator and `apm audit` does not check instruction content; `apm audit --ci` checks lockfile consistency, deployed-file presence, content hash drift, and hidden Unicode only (verified by experiment on a clean install).
|
||||
|
||||
## Instructions versus AGENTS.md and CLAUDE.md
|
||||
|
||||
An instruction is an input primitive; `AGENTS.md`, `CLAUDE.md`, and `GEMINI.md` are outputs that `apm compile` generates from instructions (and, in this repo, hand-authored root files are a separate concern owned by the AGENTS.md skills). Generated root files carry a "Generated by APM CLI" header and a build id, and must not be hand-edited. Hand-authored files are never deleted by `apm compile --clean`.
|
||||
|
||||
Claude Code's own side of the story: it reads `.claude/rules/*.md` natively; `paths` is the only frontmatter field it reads and any other field is ignored without error; a rule without `paths` loads unconditionally at launch; if the frontmatter YAML does not parse, the frontmatter is ignored and the rule loads as if it had no `paths`. Claude Code reads `AGENTS.md` only when no `CLAUDE.md` exists on the path (unless configured otherwise), which is one reason apm emits `CLAUDE.md` for the claude target instead of relying on `AGENTS.md`.
|
||||
|
||||
## Package type
|
||||
|
||||
`apm.yml` `type: instructions` is a routing hint documented as "compiles to AGENTS.md only". It validates nothing about what is in `.apm/`; see the apm-workflow configure reference for the confirmed behaviour. An install with `targets:` set deploys instructions regardless of the declared type.
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
topic: instructions-target-mapping
|
||||
source_keys:
|
||||
- apm-cli-0-28-0-experiments
|
||||
- apm-docs-site
|
||||
- claude-code-memory-docs
|
||||
- github-copilot-custom-instructions-docs
|
||||
- cursor-rules-docs
|
||||
---
|
||||
|
||||
What each target receives from an instruction file, at install time (native per-file deploy) and at compile time (folded into a root context file). Verified against apm 0.28.0 source and throwaway installs unless marked otherwise. Field syntax of the source file is in `instructions-primitive-schema.md`.
|
||||
|
||||
## Two separate output paths
|
||||
|
||||
`apm install` writes native files, one per instruction, into each target's own rules directory. `apm compile` writes root context files (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`) that concatenate instruction bodies. The two overlap, which is why compile has a dedup rule (below). A skill author should treat install as the primary path for Claude Code and Copilot, and compile as the path for targets that have no native instructions directory.
|
||||
|
||||
## Install-time mapping
|
||||
|
||||
| Target | Deployed path | Transform |
|
||||
|---|---|---|
|
||||
| copilot | `.github/instructions/<n>.instructions.md` | Verbatim copy, frontmatter untouched |
|
||||
| copilot, user scope | `~/.copilot/copilot-instructions.md` | All bodies concatenated into one file, frontmatter stripped, provenance markers added |
|
||||
| claude | `.claude/rules/<n>.md` | `applyTo` becomes a `paths:` list; `description` dropped; no frontmatter at all when there is no `applyTo` |
|
||||
| cursor | `.cursor/rules/<n>.mdc` | `applyTo` becomes `globs:` (scalar for one pattern, list for several); `description` kept, auto-generated from the first body sentence when missing; no `alwaysApply` is written; not deployed at user scope |
|
||||
| windsurf | `.windsurf/rules/<n>.md` | `trigger: glob` plus `globs:`, or `trigger: always_on` when unscoped; `description` dropped; not deployed at user scope |
|
||||
| kiro | `.kiro/steering/<n>.md` | `inclusion: fileMatch` plus `fileMatchPattern`, or `inclusion: always` when unscoped; `description` dropped |
|
||||
| antigravity | `.agents/rules/<n>.md` | `trigger: glob` plus `globs`, or no frontmatter when unscoped; not deployed at user scope |
|
||||
| grok-build | `.grok/rules/<n>.instructions.md` | Verbatim copy; keeps the `.instructions.md` name |
|
||||
| codex, gemini, opencode, agent-skills, openclaw, hermes, grok-cloud, copilot-cowork, copilot-app | none | No instructions mapping; these targets receive instructions only through compile |
|
||||
|
||||
Rename rule: the source suffix `.instructions.md` is replaced by the target's extension (`.md`, `.mdc`) except for Copilot and Grok, which keep the full suffix.
|
||||
|
||||
### Ownership and overwrite
|
||||
|
||||
The rule-directory targets (cursor, claude, windsurf, kiro, antigravity) are treated as APM-owned per file: install overwrites an existing hand-authored file with the same deployed name without a prompt (verified: a hand-written `.claude/rules/u.md` was replaced). Copilot behaves differently: an existing unmanaged file is skipped with the message "local files exist, not managed by APM" and needs `apm install --force` to overwrite. A name collision with a hand-authored rule in `.claude/rules/` is therefore silent data loss, so authors should not reuse stems of existing hand-written rules.
|
||||
|
||||
Removing or renaming a source instruction makes the next install delete the previously deployed file ("Cleaned N stale files"), and `apm audit --ci` passes after a clean install.
|
||||
|
||||
Install with explicit `targets:` in `apm.yml` creates the target directories (`.claude/`, `.github/`) if they do not exist.
|
||||
|
||||
## Per-target field survival
|
||||
|
||||
| Field | Claude | Copilot | Cursor | Windsurf | Kiro | Antigravity | Compiled root file |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `applyTo` | as `paths` | kept verbatim | as `globs` | as `globs` | as `fileMatchPattern` | as `globs` | used for grouping only |
|
||||
| `description` | dropped | kept (verbatim file) | kept | dropped | dropped | dropped | dropped |
|
||||
| `author`, `version` | dropped | kept only because the file is verbatim | dropped | dropped | dropped | dropped | dropped |
|
||||
|
||||
Consequence for authors: a `description` is useful only for Copilot and Cursor. For Claude Code the first line or heading of the body is the only descriptive text that survives, so the body must be self-explanatory.
|
||||
|
||||
## Native format facts from the downstream tools
|
||||
|
||||
Claude Code: `.claude/rules/*.md` is found recursively. `paths` is the only field read; it accepts a YAML list or a comma-separated string. Other fields are ignored with no error. Rules without `paths` load unconditionally at launch; path-scoped rules load when matching files are read. Invalid frontmatter YAML is ignored and the rule loads without `paths`. Brace expansion in `paths` is capped at 1,000 patterns and 4 MiB.
|
||||
|
||||
Copilot: path-specific files live at `.github/instructions/**/NAME.instructions.md`. `applyTo` is required and is a quoted, comma-joined string. An optional `excludeAgent` takes `"code-review"` or `"cloud-agent"`. The docs do not mention a `description` key. Repository-wide instructions use the separate `.github/copilot-instructions.md`, which has no frontmatter. Path-specific files apply on GitHub.com only to the cloud agent and code review; IDE use differs.
|
||||
|
||||
Cursor: project rules must have the `.mdc` extension; a plain `.md` in `.cursor/rules` is ignored. Fields are `description`, `globs` (documented as a comma-separated string) and `alwaysApply` (boolean). A rule with only a `description` is "Apply Intelligently" (the agent decides), not always-on. The documented limit is 500 lines per rule.
|
||||
|
||||
Windsurf, Kiro, Antigravity: the apm source emits their trigger keys, but no downstream documentation was fetched for them, so runtime behaviour is unverified.
|
||||
|
||||
## Compile-time behaviour
|
||||
|
||||
Output file by target:
|
||||
|
||||
- `--target claude` writes `CLAUDE.md`.
|
||||
- Gemini writes `GEMINI.md` (which imports `AGENTS.md`) and `AGENTS.md`.
|
||||
- Every other target writes `AGENTS.md`.
|
||||
|
||||
Instructions are grouped by `applyTo`: a "Global Instructions" section for unscoped ones and one "Files matching `<pattern>`" section per distinct pattern. Descriptions are omitted. In distributed strategy, scoped instructions are placed in nested directory files near the matching files, subject to `placement.min_instructions_per_file` (default 1); `single-file` strategy puts everything in one root file.
|
||||
|
||||
### Dedup against native files
|
||||
|
||||
Compile skips instructions already deployed natively, but only for three targets: Claude (`.claude/rules/`), Copilot (`.github/instructions/`) and Antigravity (`.agents/rules/`). With populated native rules, `apm compile --target claude` prints a dedup message and "produced no output files" and exits 0 without writing `CLAUDE.md`. `--force-instructions` (alias `--no-dedup`) overrides it and writes the file. Cursor, Windsurf, Kiro, Grok, Codex and OpenCode have no dedup, so compile writes `AGENTS.md` that duplicates the native rules already loaded by the tool (verified for cursor, windsurf, kiro, codex).
|
||||
|
||||
Test implication: a compile-based test for the Claude target must run in a project with no populated `.claude/rules/`, or pass `--force-instructions`; otherwise it produces no file and silently asserts nothing.
|
||||
|
||||
### apm.yml compilation block
|
||||
|
||||
Keys: `target`, `strategy` (`distributed` or `single-file`), `single_file`, `output`, `chatmode`, `resolve_links`, `source_attribution`, `exclude`, `placement.min_instructions_per_file`, and `agents_md.mode` (`full` or `managed_section`; the latter writes only between markers and leaves the rest of the file alone).
|
||||
|
||||
### Relevant compile flags
|
||||
|
||||
`--validate` (parse only; see schema file for why it is a weak check), `--dry-run`, `--clean` (removes orphaned generated files, never hand-authored ones), `--target`, `--all`, `--root`, `-g`, `--local-only`, `--single-agents`, `--no-links`, `--with-constitution`, `--force-instructions`. Documented exit codes: 0 success, 1 error, 2 conflicting flags. A compile with no instruction primitives at all exits 0 in 0.28.0.
|
||||
@@ -15,3 +15,38 @@
|
||||
- **Status:** `extracted`
|
||||
|
||||
Note: `releasing.md`'s `--check-clean`/`--check-versions` scope, `apm pack` exit-code semantics, and the `.apm/`-vs-root-flat-dir mutual exclusivity referenced there were additionally cross-checked directly against `apm_cli/bundle/plugin_exporter.py`, `apm_cli/commands/pack.py`, and `apm_cli/marketplace/drift_check.py` in the installed `apm-cli` 0.28.0 package (`/root/.local/pipx/venvs/apm-cli/`), not just Context7 doc snippets — confirmed by a live `apm pack --format plugin` run inside `plugins/bin` that reproduced the documented `[!] Skipping root-level skills/ because .apm/ is present` warning.
|
||||
|
||||
## apm-docs-site
|
||||
|
||||
- **URL:** https://microsoft.github.io/apm/
|
||||
- **Description:** Official apm documentation site, specifically the instructions-and-agents authoring page and the targets and compile pages: frontmatter requirements, unconditional-rule wording, per-target deploy paths, compile behaviour and flags. Fetched through subagent summaries, so lossy.
|
||||
- **Contributing files:** instructions-primitive-schema.md, instructions-target-mapping.md, instructions-gotchas.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## claude-code-memory-docs
|
||||
|
||||
- **URL:** https://code.claude.com/docs/en/memory
|
||||
- **Description:** Claude Code memory documentation: `.claude/rules/` loading, the `paths` frontmatter field (only field read, invalid YAML ignored), AGENTS.md versus CLAUDE.md precedence, size guidance.
|
||||
- **Contributing files:** instructions-primitive-schema.md, instructions-target-mapping.md, instructions-gotchas.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## github-copilot-custom-instructions-docs
|
||||
|
||||
- **URL:** https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions
|
||||
- **Description:** GitHub Copilot repository custom-instructions documentation: `.github/instructions/*.instructions.md`, the `applyTo` and `excludeAgent` frontmatter, the separate repo-wide `copilot-instructions.md`, where path-specific files apply.
|
||||
- **Contributing files:** instructions-target-mapping.md, instructions-gotchas.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## cursor-rules-docs
|
||||
|
||||
- **URL:** https://cursor.com/docs/context/rules
|
||||
- **Description:** Cursor project rules documentation: `.mdc` requirement, `description`, `globs` and `alwaysApply` frontmatter, rule types (always, auto-attached, apply intelligently, manual), size guidance.
|
||||
- **Contributing files:** instructions-target-mapping.md, instructions-gotchas.md
|
||||
- **Status:** `extracted`
|
||||
|
||||
## apm-cli-0-28-0-experiments
|
||||
|
||||
- **URL:** https://pypi.org/project/apm-cli/0.28.0/
|
||||
- **Description:** The installed apm-cli 0.28.0 package (source under the pipx venv for apm-cli) read for integrator, target-table and pattern-parsing code, plus throwaway install, compile and audit experiments run in a scratchpad outside the repo to confirm validation severity, unquoted-glob handling, discovery asymmetry, dedup, overwrite and exit-code behaviour.
|
||||
- **Contributing files:** instructions-primitive-schema.md, instructions-target-mapping.md, instructions-gotchas.md
|
||||
- **Status:** `extracted`
|
||||
@@ -1,13 +1,13 @@
|
||||
name: lint
|
||||
version: 1.1.8
|
||||
version: 1.1.9
|
||||
description: Skills and agents for configuring and running linters.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/lint
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/lint
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/lint
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/lint
|
||||
keywords:
|
||||
- lint
|
||||
- style
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
name: onedev
|
||||
version: 0.1.0
|
||||
version: 0.1.1
|
||||
description: Skills and agents for working with a OneDev forge through the TOD CLI — the forge's own objects, as distinct from the local git clone.
|
||||
author:
|
||||
name: Defame1297
|
||||
email: [email protected]
|
||||
url: https://git.dev.rkdr.net/Defame1297/
|
||||
url: https://git.rkdr.net/Defame1297/
|
||||
license: MIT
|
||||
homepage: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/onedev
|
||||
repository: https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/onedev
|
||||
homepage: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/onedev
|
||||
repository: https://git.rkdr.net/Defame1297/holocron/src/branch/main/plugins/onedev
|
||||
keywords:
|
||||
- onedev
|
||||
- tod
|
||||
|
||||
+13
-1
@@ -46,6 +46,17 @@ fi
|
||||
# which is ADR-0024 consequence 2 arriving here. Keeping the exclusion now is
|
||||
# what stops that landing as a mystery double-run on the merge that enables it.
|
||||
#
|
||||
# build/ is excluded for the same reason again, one layer further out: `apm
|
||||
# pack` stages a full copy of a package's tree (including its skills' tests/
|
||||
# directories) under build/<package>-<version>/ before archiving it. Those
|
||||
# staged .bats files carry the same six-levels-up REPO_ROOT walk-up as any
|
||||
# other copy, which resolves past this repo's actual root and fails on a
|
||||
# missing bats-support helper -- the same failure mode apm_modules/ and
|
||||
# .claude/skills/ above already guard against, just from a different apm
|
||||
# subcommand. build/ is gitignored and regenerated on demand, so nothing here
|
||||
# depends on its contents; the exclusion only stops a stray local `apm pack`
|
||||
# output from being discovered and double-run.
|
||||
#
|
||||
# The walk runs from inside REPO_ROOT so the exclusions match paths RELATIVE to
|
||||
# it, the same universe the `git ls-files` grep below sees. Matched against
|
||||
# absolute paths, `*/.claude/worktrees/*` excluded every file whenever the
|
||||
@@ -61,6 +72,7 @@ done < <(
|
||||
-not -path "*/.claude/worktrees/*" \
|
||||
-not -path "*/apm_modules/*" \
|
||||
-not -path "*/.claude/skills/*" \
|
||||
-not -path "*/build/*" \
|
||||
| sort
|
||||
)
|
||||
|
||||
@@ -99,7 +111,7 @@ if [[ -n "$GIT_TOPLEVEL" && "$GIT_TOPLEVEL" == "$REPO_ROOT" ]]; then
|
||||
[[ -n "$f" ]] && EXPECTED_FILES+=("$REPO_ROOT/$f")
|
||||
done < <(
|
||||
git -C "$REPO_ROOT" ls-files -- '*.bats' \
|
||||
| grep -Ev '(^|/)tests/bats/|(^|/)test_helper/|(^|/)\.claude/worktrees/|(^|/)apm_modules/|(^|/)\.claude/skills/' \
|
||||
| grep -Ev '(^|/)tests/bats/|(^|/)test_helper/|(^|/)\.claude/worktrees/|(^|/)apm_modules/|(^|/)\.claude/skills/|(^|/)build/' \
|
||||
| sort || true
|
||||
)
|
||||
else
|
||||
|
||||
@@ -476,6 +476,40 @@ else
|
||||
fail "the plan-shortfall run failed with the wrong count: $FAKE_OUT"
|
||||
fi
|
||||
|
||||
# --- 12. A build/ directory (apm pack's staging output) is excluded, the same
|
||||
# way apm_modules/ and .claude/skills/ above are. A stray local `apm pack` run
|
||||
# leaves build/<pkg>-<version>/ on disk holding a full copy of every packaged
|
||||
# skill's tests/ directory, gitignored and regenerable, but discoverable by a
|
||||
# bare `find` all the same. Those staged .bats files carry the same
|
||||
# several-levels-up REPO_ROOT walk-up as any other copy, which overshoots this
|
||||
# fixture's root, so an unexcluded build/ turns into the same
|
||||
# bats-support-not-found failure apm_modules/ and .claude/skills/ already guard
|
||||
# against -- this was caught live with 423 duplicate failures against a real
|
||||
# checkout holding a stray build/holocron-*/ from an earlier `apm pack`.
|
||||
echo ""
|
||||
echo "--- a build/ directory holding staged .bats copies is excluded ---"
|
||||
DIR12="$(make_fake_repo)"
|
||||
FIXTURES+=("$DIR12")
|
||||
seed_bats_files "$DIR12"
|
||||
mkdir -p "$DIR12/build/some-pkg-1.0.0/tests"
|
||||
printf '@test "staged" { false; }\n' > "$DIR12/build/some-pkg-1.0.0/tests/staged.bats"
|
||||
install_stub_bats "$DIR12" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "1..1"
|
||||
echo "ok 1 first"
|
||||
exit 0
|
||||
EOF
|
||||
run_fake "$DIR12"
|
||||
if [[ $FAKE_RC -ne 0 ]]; then
|
||||
fail "a tree holding a build/ directory failed the run: $FAKE_OUT"
|
||||
elif grep -q "build/some-pkg-1.0.0" <<< "$FAKE_OUT"; then
|
||||
fail "a .bats file staged under build/ was discovered and run: $FAKE_OUT"
|
||||
elif grep -q "^2 tests, 0 failures$" <<< "$FAKE_OUT"; then
|
||||
pass "a build/ directory's staged .bats copies are excluded from discovery"
|
||||
else
|
||||
fail "the build/-exclusion run passed with an unexpected count: $FAKE_OUT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
Reference in new issue
Block a user