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,279 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// repair-private-skills — the concrete validate/repair engine for repo-private skill hygiene under
|
|
3
|
+
// `.agents/skills`. Loaded in-session by the ACED `manage` gateway; not user-invocable.
|
|
4
|
+
//
|
|
5
|
+
// Checks each repo-private skill directory for two problems:
|
|
6
|
+
// - a stray symlink resolving into the public `skills/` tree (should be a real repo-private dir)
|
|
7
|
+
// - a SKILL.md missing `metadata.internal: true` in its YAML frontmatter
|
|
8
|
+
// A directory holding only SKILL.local.md / SKILL.project.md (no SKILL.md) is an augmentation-only
|
|
9
|
+
// entry and is never flagged.
|
|
10
|
+
//
|
|
11
|
+
// Operations:
|
|
12
|
+
// validate read-only; reports issues; makes NO filesystem changes; nonzero exit when issues exist
|
|
13
|
+
// repair writes fixes — deletes the stray symlink, inserts the missing metadata.internal: true
|
|
14
|
+
//
|
|
15
|
+
// Write boundary: repair writes/deletes ONLY under `.agents/skills` — it never creates, modifies, or
|
|
16
|
+
// deletes anything under the public `skills/` tree. validate performs no writes at all.
|
|
17
|
+
//
|
|
18
|
+
// Pure functions are exported for node:test; running the file directly drives the CLI. No deps (the
|
|
19
|
+
// repo's node-≥23.6 convention).
|
|
20
|
+
|
|
21
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
22
|
+
import { join, sep } from 'node:path'
|
|
23
|
+
import { pathToFileURL } from 'node:url'
|
|
24
|
+
|
|
25
|
+
// ── shared shapes ──
|
|
26
|
+
|
|
27
|
+
export interface RepairAction {
|
|
28
|
+
skill: string
|
|
29
|
+
action:
|
|
30
|
+
| 'removed_public_symlink'
|
|
31
|
+
| 'updated_metadata'
|
|
32
|
+
| 'already_internal'
|
|
33
|
+
| 'local_augmentation_only'
|
|
34
|
+
| 'skipped_missing_skill'
|
|
35
|
+
| 'skipped_no_frontmatter'
|
|
36
|
+
details?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RepairResult {
|
|
40
|
+
changed: boolean
|
|
41
|
+
actions: RepairAction[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PrivateSkillIssue {
|
|
45
|
+
skill: string
|
|
46
|
+
issue: 'missing_metadata_internal' | 'public_skill_symlink' | 'missing_skill_file' | 'missing_frontmatter'
|
|
47
|
+
details?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PrivateSkillCheckResult {
|
|
51
|
+
ok: boolean
|
|
52
|
+
issues: PrivateSkillIssue[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── frontmatter metadata.internal: true insertion (pure — never touches disk) ──
|
|
56
|
+
|
|
57
|
+
// Given a SKILL.md's raw content, returns whether metadata.internal: true is already present and,
|
|
58
|
+
// if not, the content with it inserted (under an existing `metadata:` block or a new one appended
|
|
59
|
+
// to the frontmatter). Content whose first line is not a `---` delimiter is returned unchanged.
|
|
60
|
+
export function ensureInternalMetadata(content: string): { changed: boolean; content: string } {
|
|
61
|
+
const lines = content.split('\n')
|
|
62
|
+
if (lines[0]?.trim() !== '---') return { changed: false, content }
|
|
63
|
+
|
|
64
|
+
let endIndex = -1
|
|
65
|
+
for (let i = 1; i < lines.length; i++) {
|
|
66
|
+
if (lines[i]?.trim() === '---') {
|
|
67
|
+
endIndex = i
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (endIndex === -1) return { changed: false, content }
|
|
72
|
+
|
|
73
|
+
const frontmatter = lines.slice(1, endIndex)
|
|
74
|
+
let metadataIndex = -1
|
|
75
|
+
let metadataIndent = 0
|
|
76
|
+
let hasInternal = false
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < frontmatter.length; i++) {
|
|
79
|
+
const line = frontmatter[i] ?? ''
|
|
80
|
+
const metadataMatch = line.match(/^(\s*)metadata:\s*$/)
|
|
81
|
+
if (metadataMatch) {
|
|
82
|
+
metadataIndex = i
|
|
83
|
+
metadataIndent = metadataMatch[1]?.length ?? 0
|
|
84
|
+
for (let j = i + 1; j < frontmatter.length; j++) {
|
|
85
|
+
const nested = frontmatter[j] ?? ''
|
|
86
|
+
if (!nested.trim()) continue
|
|
87
|
+
const indent = nested.match(/^(\s*)/)?.[1]?.length ?? 0
|
|
88
|
+
if (indent <= metadataIndent) break
|
|
89
|
+
if (/^\s*internal:\s*true\s*$/i.test(nested)) hasInternal = true
|
|
90
|
+
}
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (hasInternal) return { changed: false, content }
|
|
96
|
+
|
|
97
|
+
if (metadataIndex >= 0) {
|
|
98
|
+
frontmatter.splice(metadataIndex + 1, 0, `${' '.repeat(metadataIndent + 2)}internal: true`)
|
|
99
|
+
} else {
|
|
100
|
+
frontmatter.push('metadata:', ' internal: true')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const updated = ['---', ...frontmatter, '---', ...lines.slice(endIndex + 1)].join('\n')
|
|
104
|
+
return { changed: true, content: updated }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Resolve the public skills tree's real path (handles a symlinked ancestor, e.g. macOS
|
|
108
|
+
// /var -> /private/var), falling back to the nominal path when it does not exist.
|
|
109
|
+
function resolvePublicSkillsDir(root: string): string {
|
|
110
|
+
const publicSkillsDir = join(root, 'skills')
|
|
111
|
+
return existsSync(publicSkillsDir) ? realpathSync(publicSkillsDir) : publicSkillsDir
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// True when a repo-private entry's resolved target lives inside (or equals) the public skills tree.
|
|
115
|
+
function isPublicSkillSymlink(
|
|
116
|
+
skillDir: string,
|
|
117
|
+
entryName: string,
|
|
118
|
+
resolvedPublicSkillsDir: string,
|
|
119
|
+
): { isSymlink: boolean; inPublicTree: boolean; resolved?: string } {
|
|
120
|
+
const stat = lstatSync(skillDir)
|
|
121
|
+
if (!stat.isSymbolicLink()) return { isSymlink: false, inPublicTree: false }
|
|
122
|
+
const resolved = realpathSync(skillDir)
|
|
123
|
+
const resolvedPublicSkillDir = join(resolvedPublicSkillsDir, entryName)
|
|
124
|
+
const inPublicTree = resolved === resolvedPublicSkillDir || resolved.startsWith(`${resolvedPublicSkillsDir}${sep}`)
|
|
125
|
+
return { isSymlink: true, inPublicTree, resolved }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── validate (read-only) ──
|
|
129
|
+
|
|
130
|
+
// Checks every repo-private skill under `.agents/skills` and reports issues. Makes NO filesystem
|
|
131
|
+
// changes — a missing `.agents/skills` directory is not an error (ok: true, no issues).
|
|
132
|
+
export function validatePrivateSkills(root: string): PrivateSkillCheckResult {
|
|
133
|
+
const privateSkillsDir = join(root, '.agents', 'skills')
|
|
134
|
+
const issues: PrivateSkillIssue[] = []
|
|
135
|
+
|
|
136
|
+
if (!existsSync(privateSkillsDir)) return { ok: true, issues }
|
|
137
|
+
|
|
138
|
+
const resolvedPublicSkillsDir = resolvePublicSkillsDir(root)
|
|
139
|
+
|
|
140
|
+
for (const entry of readdirSync(privateSkillsDir, { withFileTypes: true })) {
|
|
141
|
+
const skillDir = join(privateSkillsDir, entry.name)
|
|
142
|
+
|
|
143
|
+
const symlinkCheck = isPublicSkillSymlink(skillDir, entry.name, resolvedPublicSkillsDir)
|
|
144
|
+
if (symlinkCheck.isSymlink && symlinkCheck.inPublicTree) {
|
|
145
|
+
issues.push({
|
|
146
|
+
skill: entry.name,
|
|
147
|
+
issue: 'public_skill_symlink',
|
|
148
|
+
details: `${skillDir} -> ${symlinkCheck.resolved}`,
|
|
149
|
+
})
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const skillFile = join(skillDir, 'SKILL.md')
|
|
154
|
+
if (!existsSync(skillFile)) {
|
|
155
|
+
if (existsSync(join(skillDir, 'SKILL.local.md')) || existsSync(join(skillDir, 'SKILL.project.md'))) continue
|
|
156
|
+
issues.push({ skill: entry.name, issue: 'missing_skill_file' })
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const original = readFileSync(skillFile, 'utf8')
|
|
161
|
+
if (original.split('\n')[0]?.trim() !== '---') {
|
|
162
|
+
issues.push({ skill: entry.name, issue: 'missing_frontmatter' })
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const metadata = ensureInternalMetadata(original)
|
|
167
|
+
if (metadata.changed) {
|
|
168
|
+
issues.push({ skill: entry.name, issue: 'missing_metadata_internal' })
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { ok: issues.length === 0, issues }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── repair (writes) ──
|
|
176
|
+
|
|
177
|
+
// Fixes every repo-private skill under `.agents/skills`: deletes a stray public-tree symlink and
|
|
178
|
+
// inserts a missing `metadata.internal: true`. Every write/delete this makes is confined to
|
|
179
|
+
// `.agents/skills` — it never touches the public `skills/` tree.
|
|
180
|
+
export function repairPrivateSkills(root: string): RepairResult {
|
|
181
|
+
const privateSkillsDir = join(root, '.agents', 'skills')
|
|
182
|
+
const actions: RepairAction[] = []
|
|
183
|
+
let changed = false
|
|
184
|
+
|
|
185
|
+
if (!existsSync(privateSkillsDir)) return { changed: false, actions }
|
|
186
|
+
|
|
187
|
+
const resolvedPublicSkillsDir = resolvePublicSkillsDir(root)
|
|
188
|
+
|
|
189
|
+
for (const entry of readdirSync(privateSkillsDir, { withFileTypes: true })) {
|
|
190
|
+
const skillDir = join(privateSkillsDir, entry.name)
|
|
191
|
+
|
|
192
|
+
const symlinkCheck = isPublicSkillSymlink(skillDir, entry.name, resolvedPublicSkillsDir)
|
|
193
|
+
if (symlinkCheck.isSymlink && symlinkCheck.inPublicTree) {
|
|
194
|
+
unlinkSync(skillDir)
|
|
195
|
+
actions.push({
|
|
196
|
+
skill: entry.name,
|
|
197
|
+
action: 'removed_public_symlink',
|
|
198
|
+
details: `${skillDir} -> ${symlinkCheck.resolved}`,
|
|
199
|
+
})
|
|
200
|
+
changed = true
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const skillFile = join(skillDir, 'SKILL.md')
|
|
205
|
+
if (!existsSync(skillFile)) {
|
|
206
|
+
if (existsSync(join(skillDir, 'SKILL.local.md')) || existsSync(join(skillDir, 'SKILL.project.md'))) {
|
|
207
|
+
actions.push({ skill: entry.name, action: 'local_augmentation_only' })
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
actions.push({ skill: entry.name, action: 'skipped_missing_skill' })
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const original = readFileSync(skillFile, 'utf8')
|
|
215
|
+
if (original.split('\n')[0]?.trim() !== '---') {
|
|
216
|
+
actions.push({ skill: entry.name, action: 'skipped_no_frontmatter' })
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const updated = ensureInternalMetadata(original)
|
|
221
|
+
if (updated.changed) {
|
|
222
|
+
writeFileSync(skillFile, updated.content)
|
|
223
|
+
actions.push({ skill: entry.name, action: 'updated_metadata' })
|
|
224
|
+
changed = true
|
|
225
|
+
} else {
|
|
226
|
+
actions.push({ skill: entry.name, action: 'already_internal' })
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { changed, actions }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ── CLI ──
|
|
234
|
+
|
|
235
|
+
function flag(argv: string[], name: string): string | undefined {
|
|
236
|
+
const i = argv.indexOf(name)
|
|
237
|
+
return i === -1 ? undefined : argv[i + 1]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function main(argv: string[]): number {
|
|
241
|
+
const root = flag(argv, '--root') ?? '.'
|
|
242
|
+
const json = flag(argv, '--format') === 'json'
|
|
243
|
+
const w = (s: string) => process.stdout.write(`${s}\n`)
|
|
244
|
+
|
|
245
|
+
if (argv.includes('validate')) {
|
|
246
|
+
const result = validatePrivateSkills(root)
|
|
247
|
+
if (json) {
|
|
248
|
+
w(JSON.stringify(result))
|
|
249
|
+
} else if (result.ok) {
|
|
250
|
+
w('ok: no issues found')
|
|
251
|
+
} else {
|
|
252
|
+
for (const issue of result.issues) {
|
|
253
|
+
w(`${issue.skill}: ${issue.issue}${issue.details ? ` (${issue.details})` : ''}`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return result.ok ? 0 : 1
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (argv.includes('repair')) {
|
|
260
|
+
const result = repairPrivateSkills(root)
|
|
261
|
+
if (json) {
|
|
262
|
+
w(JSON.stringify(result))
|
|
263
|
+
} else if (!result.changed) {
|
|
264
|
+
w('no changes needed')
|
|
265
|
+
} else {
|
|
266
|
+
for (const action of result.actions) {
|
|
267
|
+
w(`${action.skill}: ${action.action}${action.details ? ` (${action.details})` : ''}`)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return 0
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
w('usage: repair-private-skills.mts [--root .] <validate|repair> [--format json]')
|
|
274
|
+
return 1
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
278
|
+
process.exit(main(process.argv.slice(2)))
|
|
279
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# report
|
|
2
|
+
|
|
3
|
+
Generate a project-wide health dashboard across every ACED eval suite — pass rate, mean %max, trend vs. the previous run, and which agent configurations need attention.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
Use this skill when you want a status overview of ACED coverage across the whole project rather than a single suite.
|
|
8
|
+
|
|
9
|
+
Good triggers include:
|
|
10
|
+
|
|
11
|
+
- "How are our agent configs doing?" / "Give me an ACED health check"
|
|
12
|
+
- Before a release, to confirm nothing has regressed unnoticed
|
|
13
|
+
- Deciding what to work on next across many tracked skills/agents
|
|
14
|
+
|
|
15
|
+
Defer to a sibling when the request is about one specific target: `run` to score it, `improve` to fix failures, `compare` to diff two versions.
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
Scans the project spec (`.agents/specs/`) for behavioral-leaf nodes' `eval.md`, computes per-suite pass rate, mean %max (each scenario's total over its own maximum, normalized before averaging), and trend from the two most recent `results/` files, classifies each suite as healthy / degraded / critical / no-data / trending-down, and prints a dashboard with a "needs attention" list and suggested next action per suite.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx skills add cyberuni/cyberplace --skill aced/report
|
|
25
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: report
|
|
3
|
+
description: Use this skill when the user wants a project-wide health summary of all ACED eval suites — pass rates, trends, and which agent configurations need attention.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ACED Report
|
|
7
|
+
|
|
8
|
+
Generate a project-wide health dashboard across all eval suites.
|
|
9
|
+
|
|
10
|
+
## Discover eval suites
|
|
11
|
+
|
|
12
|
+
Scan the project spec (`.agents/specs/`) for behavioral-leaf nodes carrying an `eval.md` (subject + run policy) alongside their `<node>.feature`. For each, read:
|
|
13
|
+
- `eval.md` for target path and threshold
|
|
14
|
+
- The most recent file in `results/` (sort by filename descending)
|
|
15
|
+
- The second-most-recent file in `results/` for trend (if it exists)
|
|
16
|
+
|
|
17
|
+
If no matching directories are found, report that no eval suites are initialized and suggest `sdd:start-mission` (the conductor resolves the ACED roles).
|
|
18
|
+
|
|
19
|
+
## Compute per-suite metrics
|
|
20
|
+
|
|
21
|
+
For each suite:
|
|
22
|
+
- Pass rate (latest run)
|
|
23
|
+
- Pass rate delta vs. previous run (trend)
|
|
24
|
+
- Worst failing case: its name, and its total against **its own maximum** (`3/5 vs 4`)
|
|
25
|
+
|
|
26
|
+
- Mean **%max** — the mean of each rubric scenario's `total ÷ its own maximum`, as a percentage
|
|
27
|
+
|
|
28
|
+
**Never mean the raw totals.** Each scenario's rubric declares its own per-dimension maxima, so
|
|
29
|
+
totals from different scenarios sit on different scales and averaging them measures nothing. Normalize
|
|
30
|
+
first: `total ÷ max` per scenario, then mean those fractions. Always label the column `%max` so it is
|
|
31
|
+
never read as a raw score. A suite with no rubric scenarios (all boolean or trigger) has no `%max` —
|
|
32
|
+
render `—`.
|
|
33
|
+
|
|
34
|
+
## Classify health
|
|
35
|
+
|
|
36
|
+
| Status | Condition |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `healthy` | Pass rate ≥ 90% |
|
|
39
|
+
| `degraded` | Pass rate 70–89% |
|
|
40
|
+
| `critical` | Pass rate < 70% |
|
|
41
|
+
| `no-data` | No results file yet |
|
|
42
|
+
| `trending-down` | Pass rate dropped ≥ 10% vs. previous run |
|
|
43
|
+
|
|
44
|
+
## Report
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
ACED Project Report
|
|
48
|
+
═══════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
Suite Status Pass Mean %max Trend
|
|
51
|
+
───────────────────────────────────────────────────────────
|
|
52
|
+
commit-discipline healthy 95% 92% ↑ +5%
|
|
53
|
+
create-skill degraded 76% 74% → 0%
|
|
54
|
+
aced-case-judge critical 58% 61% ↓ -12%
|
|
55
|
+
subagents/researcher no-data — — —
|
|
56
|
+
|
|
57
|
+
NEEDS ATTENTION:
|
|
58
|
+
✗ aced-case-judge (critical, trending down) — run aced-improve
|
|
59
|
+
worst: red tests block the commit 1/5 vs 4 (correctness 0/3, completeness 1/2)
|
|
60
|
+
⚠ create-skill (degraded) — run run for details
|
|
61
|
+
|
|
62
|
+
Suites with no results: subagents/researcher — run run to initialize
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Optional: full detail mode
|
|
66
|
+
|
|
67
|
+
If the user asks for details on a specific suite, print all failing cases from the latest result file with their per-dimension scores, their total against its maximum, the threshold, and the case's `what_failed`.
|
|
68
|
+
|
|
69
|
+
## Suggest next actions
|
|
70
|
+
|
|
71
|
+
- For `critical` or `trending-down`: suggest `improve`
|
|
72
|
+
- For `degraded`: suggest `run` for details, then `improve`
|
|
73
|
+
- For `no-data`: suggest `run`
|
|
74
|
+
- For all `healthy` with no trend data: suggest `add-scenario` to expand coverage
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# run
|
|
2
|
+
|
|
3
|
+
Run the frozen `.feature` suite for one target agent configuration and report pass rate and per-dimension scores, and per-scenario results.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
Use this skill when you want to score a skill, subagent, command, or AGENTS.md section against its frozen `.feature` suite — typically right after editing it.
|
|
8
|
+
|
|
9
|
+
Good triggers include:
|
|
10
|
+
|
|
11
|
+
- "Run the evals for this skill"
|
|
12
|
+
- "Score this agent config"
|
|
13
|
+
- Checking whether a config still passes after an edit, before deciding whether to `improve` or `compare`
|
|
14
|
+
|
|
15
|
+
Defer to a sibling when the request is about diagnosing failures (`improve`), diffing two versions (`compare`), adding a new case (`add-scenario`), or a project-wide summary (`report`).
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
Locates the target's `eval.md` and frozen `.feature`, invokes `aced-case-judge` once per scenario (skipping layers not enabled in `eval.md`'s `eval.layers`), computes the pass rate and each case's per-dimension total against its own maximum / per-layer breakdown, writes a timestamped JSON result file, and reports a summary with the worst-scoring failures first.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx skills add cyberuni/cyberplace --skill aced/run
|
|
25
|
+
```
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: run
|
|
3
|
+
description: Use this skill when running ACED evals to score agent configuration behavior against its frozen .feature suite — after editing a skill, AGENTS.md section, subagent, or command.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ACED Run
|
|
7
|
+
|
|
8
|
+
Run the frozen `.feature` suite for a target agent configuration and report results. The `.feature`
|
|
9
|
+
is the single eval source — scenarios, inline `@rubric` criteria, and `@trigger` `Examples` all live
|
|
10
|
+
in it. `eval.md` carries only the subject binding and run policy.
|
|
11
|
+
|
|
12
|
+
## Locate the suite
|
|
13
|
+
|
|
14
|
+
If the user specifies a target, find the target's node in the project spec —
|
|
15
|
+
`.agents/specs/<project>/…/<node>/` (discovered through the SDD spec tree; the node's `eval.md` names
|
|
16
|
+
the subject) — which holds the frozen `<node>.feature` and its colocated `eval.md` (subject + run
|
|
17
|
+
policy). If no target is specified and only one node in the project spec carries an `eval.md`, use it.
|
|
18
|
+
If multiple exist, ask which to run. **If no suite exists at all, report that no eval suite is
|
|
19
|
+
initialized and stop — do not run.**
|
|
20
|
+
|
|
21
|
+
Read `eval.md` for the **measurement policy** (two-level shape):
|
|
22
|
+
|
|
23
|
+
- `subject` — the agent configuration under test.
|
|
24
|
+
- `eval.layers` — which layers run (`trigger` / `behavior` / `quality`).
|
|
25
|
+
- `eval.judge.model` and `eval.judge.default_threshold` — the scoring engine + fallback threshold.
|
|
26
|
+
- `eval.trigger.{activation_threshold, runs}` — the trigger-layer run policy.
|
|
27
|
+
|
|
28
|
+
Read the `subject` agent configuration in full before evaluating — the judge needs the current version.
|
|
29
|
+
|
|
30
|
+
## Run each scenario
|
|
31
|
+
|
|
32
|
+
Enumerate the scenarios of `<feature-name>.feature` **in file order**. For each scenario:
|
|
33
|
+
|
|
34
|
+
1. Determine its layer from its tag (`@trigger` / `@behavior` / `@quality`); a scenario with no layer
|
|
35
|
+
tag defaults to `behavior`.
|
|
36
|
+
2. **Skip** the scenario if its layer is not listed in `eval.layers`.
|
|
37
|
+
3. Extract the eval from the scenario itself:
|
|
38
|
+
- `@rubric` scenario → the inline rubric docstring (dimensions + per-dimension `max` + `threshold`);
|
|
39
|
+
an inline `threshold` overrides `eval.judge.default_threshold`.
|
|
40
|
+
- `@trigger` `Scenario Outline` → each `Examples` row is one case; invoke the judge **once per row**, passing its zero-based `ROW`.
|
|
41
|
+
- a deterministic boolean scenario → its boolean `Then` assertions.
|
|
42
|
+
4. Invoke `aced-case-judge` with the `subject`, the **`.feature` path and the scenario name** (plus the
|
|
43
|
+
`ROW` for a trigger outline), and its threshold, over the run count for its layer
|
|
44
|
+
(`eval.trigger.runs` for trigger; else a single behavior/quality run unless the caller sets N).
|
|
45
|
+
5. Collect, by shape: a `@rubric` case returns a score per named dimension against that dimension's
|
|
46
|
+
own `max`, plus the total and pass/fail (pass = total ≥ threshold; a triggered must-not-do is a
|
|
47
|
+
fail outright). A trigger row returns its invoke decision against the expected one — accuracy is
|
|
48
|
+
yours to aggregate across rows and runs, against `eval.trigger.activation_threshold`. A boolean
|
|
49
|
+
scenario returns pass/fail with no dimension scores. Every shape also returns `WHAT WORKED` and
|
|
50
|
+
`WHAT FAILED` — those two are the whole explanation the judge emits, and it emits nothing else.
|
|
51
|
+
|
|
52
|
+
Run all scenarios before reporting. Do not stop on first failure.
|
|
53
|
+
|
|
54
|
+
## Invoking aced-case-judge
|
|
55
|
+
|
|
56
|
+
Pass this context block to the judge:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
SUBJECT:
|
|
60
|
+
<full agent configuration text>
|
|
61
|
+
|
|
62
|
+
FEATURE_PATH: <path to the frozen .feature>
|
|
63
|
+
SCENARIO: <exact scenario name>
|
|
64
|
+
ROW: <zero-based Examples row — trigger outlines only>
|
|
65
|
+
LAYER: <layer>
|
|
66
|
+
THRESHOLD: <inline threshold, else eval.judge.default_threshold>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Pass the path and the name — never the steps, the `Then`, or the rubric.** The judge simulates and
|
|
70
|
+
scores in two separate contexts and composes the simulating context's brief with the
|
|
71
|
+
`extract-situation` engine; handing it the scenario body would put the answer key back in the
|
|
72
|
+
context that has to reach the answer. One invocation covers both passes — never sequence them here.
|
|
73
|
+
|
|
74
|
+
**Wait for each judge, and bank only its last word.** The dispatch is asynchronous, and the judge is
|
|
75
|
+
itself waiting on its own blind simulator — so it can report before its measurement exists and
|
|
76
|
+
correct itself afterwards. Where an agent reports more than once, the **final** report for that agent
|
|
77
|
+
id supersedes the earlier one entirely: never bank the first, never merge them. Do not compute
|
|
78
|
+
results while any judge is still live; a run that ends with cases outstanding reports those cases as
|
|
79
|
+
**unmeasured**, not as passes or failures. A judge that emits `BLOCKER: <reason>` in place of a score
|
|
80
|
+
is likewise unmeasured — surface it rather than folding it into the pass rate.
|
|
81
|
+
|
|
82
|
+
## Compute results
|
|
83
|
+
|
|
84
|
+
After all scenarios:
|
|
85
|
+
|
|
86
|
+
- Pass rate = passing scenarios / total scenarios
|
|
87
|
+
- Per-layer breakdown (trigger pass rate, behavior pass rate)
|
|
88
|
+
- Failing scenarios sorted by **margin** (`total − threshold`) ascending, worst first
|
|
89
|
+
|
|
90
|
+
Report each scenario's total **against its own maximum** (`4/5`), never as a bare number. Maxima
|
|
91
|
+
differ per scenario, so a mean taken across raw totals compares scales that do not line up — if you
|
|
92
|
+
report a headline number, report the mean **margin** or the mean **fraction of maximum**, and say
|
|
93
|
+
which.
|
|
94
|
+
|
|
95
|
+
## Record what was evaluated
|
|
96
|
+
|
|
97
|
+
Before writing the record, list **every input you consumed to judge this target** — the target
|
|
98
|
+
configuration, each file it loads, the target's `eval.md`, the frozen `.feature`, and any directory
|
|
99
|
+
you **listed** to find those files. This is what makes a recorded result checkable later:
|
|
100
|
+
`check-freshness` re-hashes exactly these paths and nothing else, so a result whose provenance is
|
|
101
|
+
missing or wrong is worse than one carrying none.
|
|
102
|
+
|
|
103
|
+
**Record only what you actually consumed.** A file you did not open does not belong in the set —
|
|
104
|
+
this is not "everything next to the config". Over-reporting is visible against a fixture and will
|
|
105
|
+
fail the suite; it also makes every later freshness answer wrong in the direction of `stale`.
|
|
106
|
+
|
|
107
|
+
Compute each hash with the **shared engine**, never by hand and never with a second implementation.
|
|
108
|
+
`<aced-skills>` is the directory **containing** this skill (`run`'s parent), so the engine resolves as
|
|
109
|
+
the sibling `check-freshness`:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
node "<aced-skills>/check-freshness/scripts/check-freshness.mts" --hash-file <repo-relative path>
|
|
113
|
+
node "<aced-skills>/check-freshness/scripts/check-freshness.mts" --hash-dir <repo-relative path>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`check-freshness` compares using that same routine. Two schemes that merely agree today would
|
|
117
|
+
surface their divergence as a permanent `stale` rather than as an error, so there is one routine and
|
|
118
|
+
both sides call it.
|
|
119
|
+
|
|
120
|
+
| Entry `kind` | What the hash covers | Why |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `file` | the bytes of the content you read | a content change must be detectable; a timestamp must never stand in for one |
|
|
123
|
+
| `directory` | the **names** the listing returned (sorted, `\n`-joined) | this is what makes a file later **added** to that directory detectable without re-resolving the subject |
|
|
124
|
+
|
|
125
|
+
**An entry carries no modification time** — not as a hash, and not alongside one. A recorded mtime
|
|
126
|
+
invites a reader to compare it, and a file rewritten with identical bytes would then read as changed.
|
|
127
|
+
|
|
128
|
+
**Record each `path` repo-relative, and run the engine from the repository root.** The reader
|
|
129
|
+
resolves every entry against the repo root, so an absolute path is silently unverifiable. The engine
|
|
130
|
+
resolves its argument against the current directory — invoked from elsewhere it can hash a
|
|
131
|
+
same-named file somewhere else and record a right-looking path over a wrong digest.
|
|
132
|
+
|
|
133
|
+
**A directory you expanded needs BOTH**: the directory entry *and* a `file` entry for each file that
|
|
134
|
+
listing yielded. The directory hash covers names only, so it is invariant under a content edit
|
|
135
|
+
inside it — without the per-file entries, editing a file the subject loads from a references
|
|
136
|
+
directory would read as `current` forever.
|
|
137
|
+
|
|
138
|
+
## Record the model that scored the run
|
|
139
|
+
|
|
140
|
+
Record **the model you dispatched `aced-case-judge` under** — as you can name it, and `unknown`
|
|
141
|
+
exactly when you cannot. That is one condition, not a combination of several:
|
|
142
|
+
|
|
143
|
+
- You dispatched under the `eval.md` `judge.model` → record that value.
|
|
144
|
+
- You dispatched under a different model (a caller named one, or the policy was overridden) → record
|
|
145
|
+
**the model you dispatched under**, never the declaration you did not honor.
|
|
146
|
+
- You cannot name the model you dispatched under → record `unknown`, whatever `eval.md` declares.
|
|
147
|
+
|
|
148
|
+
**`unknown` is a value, never an omitted field.** A missing field and a recorded `unknown` make
|
|
149
|
+
different claims, and a reader grouping results by model has to tell a run nobody attributed from one
|
|
150
|
+
attributed to a named model.
|
|
151
|
+
|
|
152
|
+
**It goes beside the scores, never inside `evaluated`.** The evaluated set is inputs whose bytes get
|
|
153
|
+
re-hashed; a model has none, so recording it there hands `check-freshness` a member it could only
|
|
154
|
+
ever report as unverifiable. The `eval.md` that *declares* the model is already in the set — what
|
|
155
|
+
this field adds is which model actually ran.
|
|
156
|
+
|
|
157
|
+
**One model per record.** It is a property of the run that produced these scores, not of the target:
|
|
158
|
+
two runs for one target under different models each carry their own, which is what keeps a target's
|
|
159
|
+
results groupable by model.
|
|
160
|
+
|
|
161
|
+
## Write results
|
|
162
|
+
|
|
163
|
+
Write to `.agents/aced/results/<target-slug>/<ISO8601-timestamp>.json` — the shared, git-ignored ACED results directory at the repo root, keyed by the target (a filesystem-safe slug of the target agent-configuration path), not scattered under each project-spec directory:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"timestamp": "<ISO8601>",
|
|
168
|
+
"target": "<agent configuration path>",
|
|
169
|
+
"pass_rate": 0.82,
|
|
170
|
+
"scoring_model": "<the model you dispatched the judge under, or \"unknown\">",
|
|
171
|
+
"evaluated": [
|
|
172
|
+
{ "path": "<agent configuration path>", "sha256": "<hex>", "kind": "file" },
|
|
173
|
+
{ "path": "<a directory the config loads from>", "sha256": "<hex>", "kind": "directory" },
|
|
174
|
+
{ "path": "<each file that listing yielded>", "sha256": "<hex>", "kind": "file" },
|
|
175
|
+
{ "path": "<the target eval.md>", "sha256": "<hex>", "kind": "file" },
|
|
176
|
+
{ "path": "<the frozen .feature>", "sha256": "<hex>", "kind": "file" }
|
|
177
|
+
],
|
|
178
|
+
"scenarios": [
|
|
179
|
+
{
|
|
180
|
+
"name": "<scenario name>",
|
|
181
|
+
"layer": "behavior",
|
|
182
|
+
"dimensions": [
|
|
183
|
+
{ "name": "correctness", "score": 2, "max": 3 },
|
|
184
|
+
{ "name": "completeness", "score": 1, "max": 2 }
|
|
185
|
+
],
|
|
186
|
+
"total": 3,
|
|
187
|
+
"max": 5,
|
|
188
|
+
"threshold": 4,
|
|
189
|
+
"pass": false,
|
|
190
|
+
"what_worked": "...",
|
|
191
|
+
"what_failed": "..."
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
A `@trigger` row carries `"row"`, `"invoke"`, `"expected"`, and `"pass"` instead of `"dimensions"`; a
|
|
198
|
+
boolean scenario carries `"pass"` alone.
|
|
199
|
+
|
|
200
|
+
## Report to user
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
ACED Run — <name>
|
|
204
|
+
──────────────────────────
|
|
205
|
+
Pass rate: 18/22 (82%)
|
|
206
|
+
|
|
207
|
+
Trigger layer: 8/10 (80%)
|
|
208
|
+
Behavior layer: 10/12 (83%)
|
|
209
|
+
|
|
210
|
+
FAILING SCENARIOS (worst first):
|
|
211
|
+
✗ no trigger for an audit request [invoked: no, expected: yes] — <what failed>
|
|
212
|
+
✗ stages only related files [3/5 vs 4: correctness 2/3, completeness 1/2] — <what failed>
|
|
213
|
+
✗ trigger on skill creation [invoked: yes, expected: no] — <what failed>
|
|
214
|
+
✗ red tests block the commit [3/5 vs 4: correctness 1/3, completeness 2/2] — <what failed>
|
|
215
|
+
|
|
216
|
+
Run improve to address failing scenarios.
|
|
217
|
+
Run compare after editing the agent configuration.
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If pass rate is 100%, say so and suggest running `add-scenario` to expand edge case coverage.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# skillify
|
|
2
|
+
|
|
3
|
+
Generalize a workflow from the current session into a reusable `SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
Use this skill when a multi-step workflow was just completed and should be encoded for reuse.
|
|
8
|
+
|
|
9
|
+
Good triggers include:
|
|
10
|
+
|
|
11
|
+
- "Skillify this"
|
|
12
|
+
- "Make this reusable"
|
|
13
|
+
- "Turn what we just did into a skill"
|
|
14
|
+
|
|
15
|
+
Different from `aced:define-skill`, which scaffolds from a blank template — this skill analyzes what actually happened in the session.
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
The skill extracts trigger, decisions, steps, inputs, and outputs from session history, then drafts a focused skill following skill-design governance. It validates with `audit validate` before linking or committing.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx skills add cyberuni/cyberplace --skill skillify
|
|
25
|
+
```
|