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.
- package/.claude-plugin/plugin.json +18 -0
- package/.plugin/pins.json +1 -0
- package/.plugin/plugin.json +18 -0
- package/agents/aced-case-judge.md +165 -0
- package/agents/aced-impl-judge.md +55 -0
- package/agents/aced-scenario-writer.md +76 -0
- package/agents/aced-spec-validator.md +66 -0
- package/governances/README.md +52 -0
- package/governances/agent-tool-output.md +86 -0
- package/governances/cli-resolution.md +102 -0
- package/governances/skill-design.md +258 -0
- package/governances/skill-repo-structure.md +173 -0
- package/package.json +44 -0
- package/readme.md +124 -0
- package/skills/aced-architect-impl/README.md +9 -0
- package/skills/aced-architect-impl/SKILL.md +73 -0
- package/skills/aced-builder-impl/README.md +14 -0
- package/skills/aced-builder-impl/SKILL.md +46 -0
- package/skills/aced-builder-spec/README.md +15 -0
- package/skills/aced-builder-spec/SKILL.md +130 -0
- package/skills/aced-fit/README.md +21 -0
- package/skills/aced-fit/SKILL.md +105 -0
- package/skills/aced-impl-producer/SKILL.md +75 -0
- package/skills/add-scenario/README.md +25 -0
- package/skills/add-scenario/SKILL.md +75 -0
- package/skills/bench/README.md +64 -0
- package/skills/bench/SKILL.md +199 -0
- package/skills/bench/scripts/bench-claude-code.mts +187 -0
- package/skills/bench/scripts/bench-compare.mts +369 -0
- package/skills/bench/scripts/bench-record.mts +133 -0
- package/skills/bench/scripts/bench.mts +930 -0
- package/skills/check-freshness/README.md +57 -0
- package/skills/check-freshness/SKILL.md +76 -0
- package/skills/check-freshness/scripts/check-freshness.mts +399 -0
- package/skills/compare/README.md +25 -0
- package/skills/compare/SKILL.md +87 -0
- package/skills/contribute-skill/README.md +25 -0
- package/skills/contribute-skill/SKILL.md +241 -0
- package/skills/contribute-skill/scripts/source.mts +188 -0
- package/skills/define-agent/README.md +40 -0
- package/skills/define-agent/SKILL.md +279 -0
- package/skills/define-command/README.md +36 -0
- package/skills/define-command/SKILL.md +174 -0
- package/skills/define-governance/SKILL.md +151 -0
- package/skills/define-skill/README.md +37 -0
- package/skills/define-skill/SKILL.md +200 -0
- package/skills/extract-situation/README.md +32 -0
- package/skills/extract-situation/SKILL.md +54 -0
- package/skills/extract-situation/scripts/extract-situation.mts +287 -0
- package/skills/improve/README.md +25 -0
- package/skills/improve/SKILL.md +100 -0
- package/skills/improve-skill/README.md +38 -0
- package/skills/improve-skill/SKILL.md +169 -0
- package/skills/improve-skill/references/check-definitions.md +215 -0
- package/skills/improve-skill/scripts/validate.mts +1033 -0
- package/skills/init-aced/README.md +5 -0
- package/skills/init-aced/SKILL.md +76 -0
- package/skills/init-aced/scripts/ensure-results-ignored.mts +117 -0
- package/skills/list-skills/README.md +19 -0
- package/skills/list-skills/SKILL.md +55 -0
- package/skills/list-skills/scripts/list-skills.mts +210 -0
- package/skills/manage/README.md +35 -0
- package/skills/manage/SKILL.md +65 -0
- package/skills/manage-model-runners/README.md +25 -0
- package/skills/manage-model-runners/SKILL.md +113 -0
- package/skills/manage-skill-dirs/README.md +17 -0
- package/skills/manage-skill-dirs/SKILL.md +54 -0
- package/skills/manage-skill-dirs/scripts/manage-skill-dirs.mts +328 -0
- package/skills/repair-private-skills/README.md +17 -0
- package/skills/repair-private-skills/SKILL.md +59 -0
- package/skills/repair-private-skills/scripts/repair-private-skills.mts +279 -0
- package/skills/report/README.md +25 -0
- package/skills/report/SKILL.md +74 -0
- package/skills/run/README.md +25 -0
- package/skills/run/SKILL.md +220 -0
- package/skills/skillify/README.md +25 -0
- 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.
|