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,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
+ ```