cyber-aced 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.claude-plugin/plugin.json +18 -0
  2. package/.plugin/pins.json +1 -0
  3. package/.plugin/plugin.json +18 -0
  4. package/agents/aced-case-judge.md +165 -0
  5. package/agents/aced-impl-judge.md +55 -0
  6. package/agents/aced-scenario-writer.md +76 -0
  7. package/agents/aced-spec-validator.md +66 -0
  8. package/governances/README.md +52 -0
  9. package/governances/agent-tool-output.md +86 -0
  10. package/governances/cli-resolution.md +102 -0
  11. package/governances/skill-design.md +258 -0
  12. package/governances/skill-repo-structure.md +173 -0
  13. package/package.json +44 -0
  14. package/readme.md +124 -0
  15. package/skills/aced-architect-impl/README.md +9 -0
  16. package/skills/aced-architect-impl/SKILL.md +73 -0
  17. package/skills/aced-builder-impl/README.md +14 -0
  18. package/skills/aced-builder-impl/SKILL.md +46 -0
  19. package/skills/aced-builder-spec/README.md +15 -0
  20. package/skills/aced-builder-spec/SKILL.md +130 -0
  21. package/skills/aced-fit/README.md +21 -0
  22. package/skills/aced-fit/SKILL.md +105 -0
  23. package/skills/aced-impl-producer/SKILL.md +75 -0
  24. package/skills/add-scenario/README.md +25 -0
  25. package/skills/add-scenario/SKILL.md +75 -0
  26. package/skills/bench/README.md +64 -0
  27. package/skills/bench/SKILL.md +199 -0
  28. package/skills/bench/scripts/bench-claude-code.mts +187 -0
  29. package/skills/bench/scripts/bench-compare.mts +369 -0
  30. package/skills/bench/scripts/bench-record.mts +133 -0
  31. package/skills/bench/scripts/bench.mts +930 -0
  32. package/skills/check-freshness/README.md +57 -0
  33. package/skills/check-freshness/SKILL.md +76 -0
  34. package/skills/check-freshness/scripts/check-freshness.mts +399 -0
  35. package/skills/compare/README.md +25 -0
  36. package/skills/compare/SKILL.md +87 -0
  37. package/skills/contribute-skill/README.md +25 -0
  38. package/skills/contribute-skill/SKILL.md +241 -0
  39. package/skills/contribute-skill/scripts/source.mts +188 -0
  40. package/skills/define-agent/README.md +40 -0
  41. package/skills/define-agent/SKILL.md +279 -0
  42. package/skills/define-command/README.md +36 -0
  43. package/skills/define-command/SKILL.md +174 -0
  44. package/skills/define-governance/SKILL.md +151 -0
  45. package/skills/define-skill/README.md +37 -0
  46. package/skills/define-skill/SKILL.md +200 -0
  47. package/skills/extract-situation/README.md +32 -0
  48. package/skills/extract-situation/SKILL.md +54 -0
  49. package/skills/extract-situation/scripts/extract-situation.mts +287 -0
  50. package/skills/improve/README.md +25 -0
  51. package/skills/improve/SKILL.md +100 -0
  52. package/skills/improve-skill/README.md +38 -0
  53. package/skills/improve-skill/SKILL.md +169 -0
  54. package/skills/improve-skill/references/check-definitions.md +215 -0
  55. package/skills/improve-skill/scripts/validate.mts +1033 -0
  56. package/skills/init-aced/README.md +5 -0
  57. package/skills/init-aced/SKILL.md +76 -0
  58. package/skills/init-aced/scripts/ensure-results-ignored.mts +117 -0
  59. package/skills/list-skills/README.md +19 -0
  60. package/skills/list-skills/SKILL.md +55 -0
  61. package/skills/list-skills/scripts/list-skills.mts +210 -0
  62. package/skills/manage/README.md +35 -0
  63. package/skills/manage/SKILL.md +65 -0
  64. package/skills/manage-model-runners/README.md +25 -0
  65. package/skills/manage-model-runners/SKILL.md +113 -0
  66. package/skills/manage-skill-dirs/README.md +17 -0
  67. package/skills/manage-skill-dirs/SKILL.md +54 -0
  68. package/skills/manage-skill-dirs/scripts/manage-skill-dirs.mts +328 -0
  69. package/skills/repair-private-skills/README.md +17 -0
  70. package/skills/repair-private-skills/SKILL.md +59 -0
  71. package/skills/repair-private-skills/scripts/repair-private-skills.mts +279 -0
  72. package/skills/report/README.md +25 -0
  73. package/skills/report/SKILL.md +74 -0
  74. package/skills/run/README.md +25 -0
  75. package/skills/run/SKILL.md +220 -0
  76. package/skills/skillify/README.md +25 -0
  77. package/skills/skillify/SKILL.md +141 -0
@@ -0,0 +1,5 @@
1
+ # init-aced
2
+
3
+ Registers ACED as an SDD plugin for agent-configuration domains (`skill`, `subagent`, `command`, `agents-section`) by writing a role-map entry to `.agents/universal-plugin.json` under `sdd-plugins[]`. Once registered, the conductor resolves the ACED production-chain roles — `aced-scenario-writer` (spec-producer), `aced-spec-validator` (spec-judge), `aced-impl-judge` (impl-judge) — by reading only that one file at runtime (no plugin-directory scanning).
4
+
5
+ Idempotent: re-running rewrites an old-shape or version-stale entry in place (rewrite-on-init migration). Spec creation itself is owned by the `sdd` plugin's `start-mission` / `spec-gate`; this skill only wires the registry.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: init-aced
3
+ description: Use this skill to register ACED as the SDD plugin for agent-configuration domains — writes an aced role-map entry to .agents/universal-plugin.json so the conductor resolves the ACED production-chain roles for skills, subagents, commands, and AGENTS.md sections.
4
+ ---
5
+
6
+ # Init ACED
7
+
8
+ Register ACED in the project's SDD plugin registry so the conductor resolves the ACED production-chain roles by reading **only** `.agents/universal-plugin.json` — no plugin-directory scanning at runtime (the lockfile pattern).
9
+
10
+ ## Workflow
11
+
12
+ ### 1. Locate the registry file
13
+
14
+ Look for `.agents/universal-plugin.json` at the project root. If it exists, read and parse it; otherwise create it with `{}`.
15
+
16
+ **Fail closed on a corrupt file.** If the file exists but contains malformed JSON, **fail with an error and stop — do not overwrite it**. Silently rewriting a corrupt file could destroy valid entries from other plugins; leave the file untouched and let a human repair it.
17
+
18
+ ### 2. Determine the ACED version
19
+
20
+ Read ACED's own version from its plugin manifest (it ships inside the plugin, so it knows its version for free). Use it as the `version` stamp.
21
+
22
+ ### 3. Write the aced entry (rewrite-on-init migration)
23
+
24
+ Find the entry where `"name": "aced"` in the `sdd-plugins` array:
25
+
26
+ - **Not found** → append the canonical entry (create the array if absent).
27
+ - **Found, old shape** (the legacy `scenario-advisor` / `implementer` keys, or the legacy `domains[]` + shared `roles`/`governances`) → **rewrite** it to the `squads[]` shape below.
28
+ - **Found, `squads[]` shape, stale `version`** → rewrite when the recorded version differs (install / upgrade / manual re-run reconciles drift here; the conductor never compares versions at runtime).
29
+
30
+ Do not reorder or reformat other entries.
31
+
32
+ **Canonical entry:**
33
+
34
+ ```json
35
+ {
36
+ "name": "aced",
37
+ "version": "<aced version>",
38
+ "squads": [
39
+ {
40
+ "artifact-types": ["skill", "subagent", "command", "agents-section"],
41
+ "roles": {
42
+ "spec-producer": "aced-scenario-writer",
43
+ "solution-producer": null,
44
+ "spec-judge": "aced-spec-validator",
45
+ "impl-producer": null,
46
+ "impl-judge": "aced-impl-judge"
47
+ },
48
+ "governances": {
49
+ "oracle-spec": null,
50
+ "builder-spec": "aced-builder-spec",
51
+ "builder-impl": "aced-builder-impl",
52
+ "architect-spec": null,
53
+ "architect-impl": null
54
+ }
55
+ }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ ACED serves its four agent-config artifact-types with one squad. `impl-producer: null` — writing the agent config is done by the `define-agent` / `define-command` / `improve` skills or the SDD-default impl-producer (the conductor running `impl-producer-governance` via a spawned builder), not a bound impl-producer agent. `solution-producer: null` uses the SDD default. ACED binds its own `builder-spec` (`aced-builder-spec`) and `builder-impl` (`aced-builder-impl`) bars; each remaining `null` governance uses the SDD default actor-gate bar.
61
+
62
+ **Each squad's `governances` block is required.** Every squad must carry a `governances` map (each binding may be `null`, but the block itself must be present). Reject a payload with a squad missing its `governances` block — fail with an error and **do not write** the file.
63
+
64
+ ### 4. Write the updated file
65
+
66
+ Write `.agents/universal-plugin.json` back with the updated contents.
67
+
68
+ ### 5. Ensure ACED run output is git-ignored
69
+
70
+ Run `scripts/ensure-results-ignored.mts` (this skill's own `scripts/` dir) to ensure `.agents/aced/results/` — the shared ACED run-output directory `run` writes to — is git-ignored at the repo root. Idempotent; a non-zero exit means it could not guarantee the ignore (e.g. not inside a git repo) — surface that to the user rather than continuing silently.
71
+
72
+ ### 6. Report
73
+
74
+ Confirm the `.agents/universal-plugin.json` aced entry is present under `sdd-plugins`, stamped with the ACED version, with a squad serving artifact-types `skill`, `subagent`, `command`, `agents-section`.
75
+
76
+ Next step: use `sdd:start-mission` (sdd plugin) to scaffold an agent-configuration spec; the conductor resolves the ACED roles automatically.
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ // ensure-results-ignored — setup/ignore-run-output's deterministic engine. Ensures the ACED
3
+ // run-output directory (.agents/aced/results/) is git-ignored at the repo root, so `run` never
4
+ // commits timestamped, non-deterministic judge output into the tracked tree.
5
+ //
6
+ // Behavior (frozen — see .agents/specs/aced/setup/ignore-run-output/):
7
+ // 1. Resolve the git repo root from the start directory. Not inside a repo -> fail closed, write
8
+ // nothing.
9
+ // 2. If the results directory is already effectively ignored (git check-ignore on a probe path
10
+ // succeeds) -> no-op, exit 0, write nothing.
11
+ // 3. Else, if .gitignore is not writable/creatable -> fail closed, before any write.
12
+ // 4. Else append the rule as the LAST line of .gitignore (creating it if absent, preserving every
13
+ // existing line). Appending last is load-bearing: gitignore's last-match-wins guarantees the
14
+ // path ends ignored even if an earlier `!`-negation re-included it. There is no post-write
15
+ // verification branch — the guarantee holds by construction.
16
+ //
17
+ // Idempotent: a second run finds the path already ignored and writes nothing, so exactly one
18
+ // matching rule ever exists. Pure functions are exported for node:test; running the file directly
19
+ // drives the CLI. No deps beyond node:fs, node:path, node:child_process.
20
+
21
+ import { execFileSync } from 'node:child_process'
22
+ import { accessSync, constants, existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
23
+ import { join } from 'node:path'
24
+ import { pathToFileURL } from 'node:url'
25
+
26
+ // The rule ensured in .gitignore — the ACED run-output directory.
27
+ export const RESULTS_DIR_RULE = '.agents/aced/results/'
28
+ // A path under the results directory used to probe `git check-ignore` — never created on disk.
29
+ const PROBE_PATH = '.agents/aced/results/.ensure-ignored-probe'
30
+
31
+ // Resolve the git repo root from `startDir`, or null when it is not inside a git repository.
32
+ export function resolveRepoRoot(startDir: string): string | null {
33
+ try {
34
+ const out = execFileSync('git', ['-C', startDir, 'rev-parse', '--show-toplevel'], {
35
+ encoding: 'utf8',
36
+ stdio: ['ignore', 'pipe', 'ignore'],
37
+ })
38
+ return out.trim()
39
+ } catch {
40
+ return null
41
+ }
42
+ }
43
+
44
+ // True when `relPath` (repo-relative) is reported ignored by git under `repoRoot`.
45
+ export function isIgnored(repoRoot: string, relPath: string): boolean {
46
+ try {
47
+ execFileSync('git', ['-C', repoRoot, 'check-ignore', '-q', relPath], { stdio: 'ignore' })
48
+ return true
49
+ } catch {
50
+ return false
51
+ }
52
+ }
53
+
54
+ // True when .gitignore at `repoRoot` can be written — writable if it exists, else the repo root
55
+ // must be writable so the file can be created.
56
+ export function canWriteGitignore(repoRoot: string): boolean {
57
+ const file = join(repoRoot, '.gitignore')
58
+ try {
59
+ if (existsSync(file)) accessSync(file, constants.W_OK)
60
+ else accessSync(repoRoot, constants.W_OK)
61
+ return true
62
+ } catch {
63
+ return false
64
+ }
65
+ }
66
+
67
+ // Append `rule` as the LAST line of .gitignore at `repoRoot`, creating the file (whose content is
68
+ // then just the rule) if absent, and preserving every pre-existing line untouched.
69
+ function appendRuleLast(repoRoot: string, rule: string): void {
70
+ const file = join(repoRoot, '.gitignore')
71
+ let content = ''
72
+ if (existsSync(file)) {
73
+ content = readFileSync(file, 'utf8')
74
+ if (content.length > 0 && !content.endsWith('\n')) content += '\n'
75
+ }
76
+ content += `${rule}\n`
77
+ writeFileSync(file, content)
78
+ }
79
+
80
+ export type EnsureResult = { ok: true; changed: boolean; repoRoot: string } | { ok: false; reason: string }
81
+
82
+ // The engine's whole behavior, starting from `startDir`. Never throws.
83
+ export function ensureResultsIgnored(startDir: string): EnsureResult {
84
+ const repoRoot = resolveRepoRoot(startDir)
85
+ if (!repoRoot) return { ok: false, reason: 'not inside a git repository' }
86
+
87
+ if (isIgnored(repoRoot, PROBE_PATH)) return { ok: true, changed: false, repoRoot }
88
+
89
+ if (!canWriteGitignore(repoRoot)) return { ok: false, reason: '.gitignore is not writable or creatable' }
90
+
91
+ appendRuleLast(repoRoot, RESULTS_DIR_RULE)
92
+ return { ok: true, changed: true, repoRoot }
93
+ }
94
+
95
+ // ── CLI ──
96
+
97
+ function flag(argv: string[], name: string): string | undefined {
98
+ const i = argv.indexOf(name)
99
+ return i === -1 ? undefined : argv[i + 1]
100
+ }
101
+
102
+ export function main(argv: string[]): number {
103
+ const root = flag(argv, '--root') ?? '.'
104
+ const w = (s: string) => process.stdout.write(`${s}\n`)
105
+
106
+ const r = ensureResultsIgnored(root)
107
+ if (!r.ok) {
108
+ w(`refused: ${r.reason}`)
109
+ return 1
110
+ }
111
+ w(r.changed ? `${RESULTS_DIR_RULE} appended to .gitignore at ${r.repoRoot}` : `already ignored under ${r.repoRoot}`)
112
+ return 0
113
+ }
114
+
115
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
116
+ process.exit(main(process.argv.slice(2)))
117
+ }
@@ -0,0 +1,19 @@
1
+ # list-skills
2
+
3
+ Internal, non-invokable ACED engine — loaded in-session by the `manage` gateway, not triggered
4
+ directly. It reports what skills are installed.
5
+
6
+ ## What it does
7
+
8
+ Scans the four fixed skill sources — repo-private (`.agents/skills`), repo-public (`skills/`),
9
+ user-global (`~/.agents/skills`), and the cyberplace package's shipped skills directory — keeping
10
+ only directories that contain a `SKILL.md`. Duplicate names are deduped with **repo taking
11
+ precedence** over global/package. An optional `--grep` glob (`*`/`?`) filters by skill name.
12
+
13
+ Each surviving skill is reported with its **name**, **foundIn** source, **description**, and
14
+ whether it is **package-managed** (its `skill.json` declares `distribution.install_via:
15
+ "package_manager"`). The report is sorted alphabetically by name and can be printed as text
16
+ (default) or `--format json`.
17
+
18
+ Read-only — it never writes a `SKILL.md`, manifest, or any other file. Ported from the cyberplace
19
+ CLI's `skill list` command (`packages/cyberplace/src/skill/list.ts`).
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: list-skills
3
+ description: "Partial Skill: invoke by name only — the ACED manage-gateway skill-inventory engine — loaded in-session by the manage gateway, not user-triggered."
4
+ user-invocable: false
5
+ metadata:
6
+ internal: true
7
+ ---
8
+
9
+ # list-skills
10
+
11
+ The concrete **discovery engine** for the ACED skill inventory — scans the four fixed skill
12
+ sources, dedupes, filters, and reports what is installed. It is **read-only**: it never writes a
13
+ `SKILL.md`, a manifest, or any other file. Validating or repairing skill content is a different
14
+ engine (`repair-private-skills`); maintaining the per-model runner family is
15
+ `manage-model-runners`. Loaded **in-session** by the `manage` gateway; **not user-invocable**.
16
+
17
+ ## The four sources (scan order, dedupe precedence)
18
+
19
+ | Source | Location | `foundIn` |
20
+ |---|---|---|
21
+ | Repo-private | `<root>/.agents/skills` | `repo` |
22
+ | Repo-public | `<root>/skills` | `repo` |
23
+ | User-global | `~/.agents/skills` | `global` |
24
+ | Package | the cyberplace package's shipped `skills/` directory | `package` |
25
+
26
+ A directory counts as a skill only when it contains a `SKILL.md`. A name found in more than one
27
+ source is reported **once** — the earlier-scanned source wins, so `repo` beats `global`/`package`.
28
+
29
+ ## Run the engine
30
+
31
+ ```bash
32
+ node "<skill>/scripts/list-skills.mts" [--root .] [--grep <pattern>] [--format json]
33
+ ```
34
+
35
+ | Flag | Effect |
36
+ |---|---|
37
+ | `--root <path>` | repo root to scan for the two repo sources (default `.`) |
38
+ | `--grep <pattern>` | `*`/`?` glob filter over skill **name** only; omitted = every discovered skill |
39
+ | `--format json` | emit the report as JSON (`name`, `description`, `foundIn`, `packageManaged`); default is a human-readable text list |
40
+
41
+ Each reported skill carries: **name** (frontmatter `name:`, falling back to the directory name when
42
+ omitted), **foundIn** (`repo` \| `global` \| `package`), **description** (frontmatter
43
+ `description:`), and **packageManaged** (`true` when the skill's `skill.json` declares
44
+ `distribution.install_via: "package_manager"`). The report is sorted **alphabetically by name**.
45
+
46
+ ## Boundaries
47
+
48
+ Read-only inventory — it inspects `SKILL.md` frontmatter and an optional `skill.json` manifest per
49
+ directory and writes nothing. It does not validate skill content, repair broken frontmatter, or
50
+ touch the per-model runner family; those are separate engines.
51
+
52
+ When `node` is absent, an agent performs the same scan by hand: walk the four source directories,
53
+ keep only subdirectories with a `SKILL.md`, parse the `name:`/`description:` frontmatter (directory
54
+ name as fallback), dedupe by name keeping the first-scanned (repo-first) copy, apply the glob
55
+ filter if one was given, and sort the result alphabetically by name.
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ // list-skills — the concrete discovery engine for the ACED `manage` gateway's skill inventory. It
3
+ // scans the four fixed skill sources — repo-private (.agents/skills), repo-public (skills/),
4
+ // user-global (~/.agents/skills), and the cyberplace package's shipped skills dir — dedupes by
5
+ // name (repo takes precedence over global/package), applies an optional --grep glob filter, and
6
+ // reports each surviving skill's name, foundIn source, description, and package-managed status.
7
+ //
8
+ // Read-only: it never writes a SKILL.md, a manifest, or any other file. Validating or repairing
9
+ // skill content is a different engine's job; this one only inventories what is installed.
10
+ //
11
+ // Ported from packages/cyberplace/src/skill/list.ts + manifest.ts + hook/package-root.ts, folded
12
+ // into one self-contained file per the repo's node-≥23.6 / no-deps convention (import ONLY
13
+ // node:*). Pure functions are exported for node:test; running the file directly drives the CLI.
14
+
15
+ import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs'
16
+ import { homedir } from 'node:os'
17
+ import { dirname, join } from 'node:path'
18
+ import { fileURLToPath, pathToFileURL } from 'node:url'
19
+
20
+ // ── package root (inlined + adapted from hook/package-root.ts) ──
21
+
22
+ // Walks up from a starting directory until a package.json is found. Mirrors the cyberplace CLI's
23
+ // original getPackageRoot algorithm, used here as the last-resort candidate.
24
+ function walkUpForPackageJson(startDir: string): string | undefined {
25
+ let dir = startDir
26
+ while (dir !== dirname(dir)) {
27
+ if (existsSync(join(dir, 'package.json'))) return dir
28
+ dir = dirname(dir)
29
+ }
30
+ return undefined
31
+ }
32
+
33
+ // Locates the cyberplace package's root — the source of its shipped `skills/` directory (the
34
+ // "package" scan source). This script is not itself bundled inside cyberplace, so it cannot rely
35
+ // on walking up from its own module location the way the original CLI helper did; instead it
36
+ // tries, in order: an installed dependency under the scanned repo's node_modules, this monorepo's
37
+ // own packages/cyberplace workspace sibling, then falls back to walking up from this module's own
38
+ // file location (covering the case this script is ever vendored inside the package itself).
39
+ // Never throws — an unresolved package root simply yields no "package" source skills.
40
+ export function getPackageRoot(root = '.'): string {
41
+ const candidates = [join(root, 'node_modules', 'cyberplace'), join(root, 'packages', 'cyberplace')]
42
+ for (const candidate of candidates) {
43
+ if (existsSync(join(candidate, 'package.json'))) return candidate
44
+ }
45
+ return walkUpForPackageJson(dirname(fileURLToPath(import.meta.url))) ?? root
46
+ }
47
+
48
+ // ── skill manifest (inlined from skill/manifest.ts) ──
49
+
50
+ export interface SkillManifest {
51
+ distribution?: {
52
+ install_via: string
53
+ package?: { name: string; bin?: string }
54
+ }
55
+ }
56
+
57
+ // Reads a skill directory's skill.json, if present. Returns null when absent or malformed —
58
+ // package-managed status then defaults to false, never throws.
59
+ export function readSkillManifest(skillDir: string): SkillManifest | null {
60
+ const filePath = join(skillDir, 'skill.json')
61
+ if (!existsSync(filePath)) return null
62
+ try {
63
+ return JSON.parse(readFileSync(filePath, 'utf8')) as SkillManifest
64
+ } catch {
65
+ return null
66
+ }
67
+ }
68
+
69
+ export function isPackageManaged(manifest: SkillManifest | null): boolean {
70
+ return manifest?.distribution?.install_via === 'package_manager'
71
+ }
72
+
73
+ // ── discovery (inlined + extended from skill/list.ts) ──
74
+
75
+ export type SkillLocation = 'repo' | 'global' | 'package'
76
+
77
+ export interface SkillSummary {
78
+ name: string
79
+ description: string
80
+ foundIn: SkillLocation
81
+ packageManaged: boolean
82
+ }
83
+
84
+ function parseFrontmatter(content: string): { name: string; description: string } {
85
+ const lines = content.split('\n')
86
+ let fmCount = 0
87
+ let name = ''
88
+ let description = ''
89
+
90
+ for (const line of lines) {
91
+ if (line.trim() === '---') {
92
+ fmCount++
93
+ if (fmCount === 2) break
94
+ continue
95
+ }
96
+ if (fmCount !== 1) continue
97
+
98
+ const nameMatch = line.match(/^name:\s*(.+)/)
99
+ if (nameMatch) name = nameMatch[1]!.trim().replace(/^["']|["']$/g, '')
100
+
101
+ const descMatch = line.match(/^description:\s*(.+)/)
102
+ if (descMatch) description = descMatch[1]!.trim().replace(/^["']|["']$/g, '')
103
+ }
104
+
105
+ return { name, description }
106
+ }
107
+
108
+ // Compile a `*`/`?` glob pattern (name-only, no path separators) into a full-match RegExp.
109
+ export function globToRegExp(pattern: string): RegExp {
110
+ let regex = ''
111
+ for (const char of pattern) {
112
+ if (char === '*') regex += '.*'
113
+ else if (char === '?') regex += '.'
114
+ else if (/[.+^${}()|[\]\\]/.test(char)) regex += `\\${char}`
115
+ else regex += char
116
+ }
117
+ return new RegExp(`^${regex}$`)
118
+ }
119
+
120
+ function collectSkillsFromDir(dir: string, foundIn: SkillLocation): SkillSummary[] {
121
+ if (!existsSync(dir)) return []
122
+
123
+ const results: SkillSummary[] = []
124
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
125
+ if (!entry.isDirectory()) continue
126
+
127
+ const skillDir = join(dir, entry.name)
128
+ const skillFile = join(skillDir, 'SKILL.md')
129
+ if (!existsSync(skillFile)) continue
130
+
131
+ const { name, description } = parseFrontmatter(readFileSync(skillFile, 'utf8'))
132
+ results.push({
133
+ name: name || entry.name,
134
+ description,
135
+ foundIn,
136
+ packageManaged: isPackageManaged(readSkillManifest(skillDir)),
137
+ })
138
+ }
139
+
140
+ return results
141
+ }
142
+
143
+ export interface ListSkillsOptions {
144
+ grep?: string
145
+ home?: string
146
+ packageRoot?: string
147
+ }
148
+
149
+ // Scan the four fixed sources, dedupe by name (repo > global > package, matching scan order),
150
+ // apply the optional --grep glob filter, and return the surviving skills sorted alphabetically.
151
+ export function listSkills(root: string, options: ListSkillsOptions = {}): SkillSummary[] {
152
+ const grep = options.grep ? globToRegExp(options.grep) : undefined
153
+ const home = options.home ?? homedir()
154
+ const packageRoot = options.packageRoot ?? getPackageRoot(root)
155
+
156
+ const sources: { dir: string; foundIn: SkillLocation }[] = [
157
+ { dir: join(root, '.agents', 'skills'), foundIn: 'repo' },
158
+ { dir: join(root, 'skills'), foundIn: 'repo' },
159
+ { dir: join(home, '.agents', 'skills'), foundIn: 'global' },
160
+ { dir: join(packageRoot, 'skills'), foundIn: 'package' },
161
+ ]
162
+
163
+ const seen = new Set<string>()
164
+ const results: SkillSummary[] = []
165
+
166
+ for (const { dir, foundIn } of sources) {
167
+ for (const skill of collectSkillsFromDir(dir, foundIn)) {
168
+ if (seen.has(skill.name)) continue
169
+ if (grep && !grep.test(skill.name)) continue
170
+
171
+ seen.add(skill.name)
172
+ results.push(skill)
173
+ }
174
+ }
175
+
176
+ return results.sort((a, b) => a.name.localeCompare(b.name))
177
+ }
178
+
179
+ // ── CLI ──
180
+
181
+ function flag(argv: string[], name: string): string | undefined {
182
+ const i = argv.indexOf(name)
183
+ return i === -1 ? undefined : argv[i + 1]
184
+ }
185
+
186
+ function formatText(skills: SkillSummary[]): string {
187
+ if (skills.length === 0) return 'No skills found.'
188
+ return skills
189
+ .map((s) => `${s.name} [${s.foundIn}]${s.packageManaged ? ' (package-managed)' : ''}\n ${s.description}`)
190
+ .join('\n')
191
+ }
192
+
193
+ export function main(argv: string[]): number {
194
+ const root = flag(argv, '--root') ?? '.'
195
+ const grep = flag(argv, '--grep')
196
+ const format = flag(argv, '--format') ?? 'text'
197
+
198
+ const skills = listSkills(root, { grep })
199
+
200
+ if (format === 'json') {
201
+ process.stdout.write(`${JSON.stringify(skills, null, 2)}\n`)
202
+ } else {
203
+ process.stdout.write(`${formatText(skills)}\n`)
204
+ }
205
+ return 0
206
+ }
207
+
208
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
209
+ process.exit(main(process.argv.slice(2)))
210
+ }
@@ -0,0 +1,35 @@
1
+ # manage
2
+
3
+ The manage-level front door to ACED — a thin dispatcher for **non-mission** work on the agent-config
4
+ tooling ACED evaluates.
5
+
6
+ ## When to use
7
+
8
+ Use this skill for work that is neither authoring a config nor scoring one:
9
+
10
+ - Set up the per-model **runner agents** used to benchmark skills
11
+ - **List** the current model runners
12
+ - **Remove** a model runner
13
+
14
+ Good triggers: "set up the model runners", "list the runners", "remove the haiku runner". If you
15
+ instead want to *create* an agent or skill, use `define-agent` / `define-skill`; to *score* a config,
16
+ use `run`; to *change what ACED specifies*, use `start-mission`.
17
+
18
+ ## What it does
19
+
20
+ `manage` classifies the request and **loads the matching engine in the current session** — it spawns
21
+ nothing, opens no change request, invokes no gate, and writes no contract state. When the operation
22
+ is named it takes the fast path; on a bare invocation it asks a menu of at most four options.
23
+
24
+ Today it routes one group:
25
+
26
+ | Group | Engine |
27
+ |---|---|
28
+ | **Config runners** — set up / list / remove per-model runner agents | `manage-model-runners` (internal) |
29
+
30
+ The table grows as more manage-level ACED engines land.
31
+
32
+ ## Boundaries
33
+
34
+ `manage` never changes what ACED specifies. Authoring redirects to `define-agent` / `define-skill`,
35
+ scoring to the eval-run skills, and a change to ACED's own behavior to `start-mission`.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: manage
3
+ description: Use this skill when doing manage-level (non-mission) ACED work — inspecting or maintaining the agent-config tooling ACED evaluates, such as "set up the per-model runner agents", "list the model runners", "list the installed skills", or "validate/repair the repo-private skills". Routes to the matching engine; it does not author a config (define-agent / define-skill) or change what ACED specifies (start-mission).
4
+ ---
5
+
6
+ # manage
7
+
8
+ The **manage-level** front door to ACED — the user-facing handler for **non-mission** work on the
9
+ agent-config tooling corpus. Where `define-agent` / `define-skill` **author** a config and `run` /
10
+ `add-scenario` **score** one, `manage` does the work that is neither: **inspect and maintain** the
11
+ tooling ACED evaluates.
12
+
13
+ `manage` is a **thin dispatcher**: it classifies a manage request and **loads the matching engine in
14
+ the current session**, holding **no production logic**, loading **no governance**, and writing **no
15
+ contract state**. It **opens no CR**, **invokes no gate**, and **performs no behavior change** itself.
16
+
17
+ > **Manage picks no model.** The model + effort a piece of work needs is set by the **engine you
18
+ > load**. The loaded engine advises; the user switches manually. (`manage` cannot switch the session
19
+ > model itself.)
20
+
21
+ ## Intake
22
+
23
+ - **Fast path — skip the menu.** When the invocation already **names the operation** — "set up the
24
+ model runners", "list the runners", "remove the haiku runner" — load the matching engine directly,
25
+ no menu.
26
+ - **Menu — bare invocation.** When `manage` is invoked with **no operation named**, do not guess. Ask
27
+ via `AskUserQuestion`, presenting **at most four options** (the tool rejects more than four) and
28
+ never truncating silently. The route groups today (**Config runners**, **Skill corpus**) fit the
29
+ four-option rule; as more manage-level engines land, group the menu so a bare invocation still
30
+ resolves within it.
31
+
32
+ ## The routing table — request → engine
33
+
34
+ Classification routes a manage request to the **engine** that handles it. The table grows as more
35
+ manage-level ACED engines land:
36
+
37
+ | Group | Request | Engine (handler) |
38
+ |---|---|---|
39
+ | **Config runners** | set up / list / remove per-model runner agents used to benchmark skills | **`manage-model-runners`** — internal, non-invokable; loaded here |
40
+ | **Skill corpus** | list / inventory the installed skills across the repo, the user-global install, and the shipped package | **`list-skills`** — internal, non-invokable; loaded here |
41
+ | **Skill corpus** | validate / repair repo-private skill metadata under `.agents/skills` | **`repair-private-skills`** — internal, non-invokable; loaded here |
42
+ | **Skill corpus** | list / add / remove / preview the extra skill-scan locations the validate engine uses | **`manage-skill-dirs`** — internal, non-invokable; loaded here |
43
+
44
+ ## Load the engine in-session
45
+
46
+ When the route resolves, **load the matched engine in the current session** and run it directly —
47
+ **spawn nothing**. Write-capable operations stay **owned by their engine**: `manage-model-runners`
48
+ writes its runner agent-def files and their symlinks; `repair-private-skills` writes only under
49
+ `.agents/skills`; `manage-skill-dirs` writes only `.agents/aced/skill-dirs.toml`; `list-skills` is
50
+ read-only. `manage` only routes.
51
+
52
+ ## Non-mission — the boundary
53
+
54
+ `manage` maintains and inspects the ACED tooling corpus; it **never changes what ACED specifies**.
55
+
56
+ - **Opens no CR, invokes no gate, writes no `status` / `approval`.** Those belong to `start-mission`
57
+ and the internal gates.
58
+ - **Authoring is not manage.** A request to **create or improve** an agent definition or a workflow
59
+ skill is redirected to **`define-agent`** / **`define-skill`**, not handled here.
60
+ - **Scoring is not manage.** A request to **run or score** a config's eval suite defers to the
61
+ **eval-run** skills (`run` / `compare` / `report`).
62
+ - **A change to what ACED specifies** — adding or revising ACED's own behavior — is redirected to
63
+ **`start-mission`**, which opens a CR and runs the mission loop.
64
+ - **Thin classifier.** Classifying loads **no governance** and holds **no production logic** — it
65
+ only loads the matched engine.
@@ -0,0 +1,25 @@
1
+ # manage-model-runners
2
+
3
+ Internal, non-invokable ACED engine — loaded in-session by the `manage` gateway, not triggered
4
+ directly. It maintains a family of **runner agent definitions, one per model**, at user-global
5
+ `~/.agents/agents/model-runner-<model>.md`.
6
+
7
+ ## What it does
8
+
9
+ Each runner is a **neutral executor** pinned to a single model, so an ACED skill-under-test can be run
10
+ as a real subagent under that model and its effectiveness / token-cost measured (ACED's eval loop
11
+ otherwise only *simulates* behavior via a judge).
12
+
13
+ Three **additive** operations:
14
+
15
+ - **add** — resolve + confirm a target model list (explicit args → `.agents/aced/models.toml` → the
16
+ known model aliases), then create a runner def for each model that has none. Idempotent.
17
+ - **list** — report the current family (model → path → any effort stamp).
18
+ - **remove** — delete only the runner def(s) the user explicitly names, after confirmation.
19
+
20
+ It **never auto-removes**: a model absent from a target list is left intact — the user may run
21
+ multiple harnesses, so a runner this engine did not just create is never culled.
22
+
23
+ One def per model; effort is not a def axis. Because a plain subagent spawn can override `model` but
24
+ not `effort`, an optional `effort:` stamp can be written on a def on request (for a plain-spawn
25
+ runner) without creating model×effort defs.