cyber-aced 0.3.0 → 0.4.1
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 +1 -1
- package/.codex-plugin/plugin.json +18 -0
- package/.plugin/plugin.json +1 -1
- package/agents/aced-impl-judge.md +2 -2
- package/agents/aced-scenario-writer.md +1 -1
- package/agents/aced-spec-validator.md +1 -1
- package/dist/aced-bench.js +1256 -0
- package/governances/README.md +1 -1
- package/governances/skill-design.md +5 -3
- package/license +21 -0
- package/package.json +43 -43
- package/skills/check-freshness/README.md +10 -1
- package/skills/check-freshness/SKILL.md +12 -0
- package/skills/check-freshness/scripts/check-freshness.mts +134 -1
- package/skills/define-skill/README.md +1 -1
- package/skills/define-skill/SKILL.md +16 -12
- package/skills/define-skill/references/governances/agent-tool-output.md +86 -0
- package/skills/define-skill/references/governances/skill-design.md +260 -0
- package/skills/improve-skill/SKILL.md +5 -11
- package/skills/improve-skill/references/check-definitions.md +2 -2
- package/skills/improve-skill/references/governances/agent-tool-output.md +86 -0
- package/skills/improve-skill/references/governances/skill-design.md +260 -0
- package/skills/improve-skill/scripts/validate.mts +31 -3
- package/skills/run/SKILL.md +1 -1
package/governances/README.md
CHANGED
|
@@ -48,5 +48,5 @@ rationale belong in ADRs and research dossiers.
|
|
|
48
48
|
Owned elsewhere and referenced from these documents: `plugin-design`, `slash-invocation`, and
|
|
49
49
|
`universal-plugin` ship from the `universal-plugin` package.
|
|
50
50
|
|
|
51
|
-
See [ADR-0035](
|
|
51
|
+
See [ADR-0035](https://github.com/cyberuni/cyber-sdd/blob/main/docs/adr/0035-authored-governances-ship-as-package-files.md) for why these
|
|
52
52
|
ship as package files rather than as governance skills.
|
|
@@ -123,8 +123,10 @@ When a skill includes `scripts/` or documents CLI commands agents run, load **ag
|
|
|
123
123
|
### Frontmatter
|
|
124
124
|
|
|
125
125
|
- `name` must match the parent directory name exactly.
|
|
126
|
-
- `description`
|
|
127
|
-
-
|
|
126
|
+
- `description` of a situationally triggered skill states what the skill does, then when it applies — the situations, requests, and implicit phrasings that should select it. Lead with the capability, in third person.
|
|
127
|
+
- Do not open the `description` with a fixed lead-in such as `"Use this skill when"` or `"When to use"`. A trigger clause after the capability (`"… Use when …"`) is fine; no particular phrase is required.
|
|
128
|
+
- A **name-only** skill's `description` is exactly `"By name only"` and carries no capability or trigger text at all.
|
|
129
|
+
- Keep `description` ≤1024 characters — the agentskills.io hard limit. Put the capability and the main triggers first; listings may truncate the tail.
|
|
128
130
|
- `compatibility` — optional; declare environment constraints (required tools, network access, OS, runtime version). Include only when the skill has requirements the agent cannot assume.
|
|
129
131
|
|
|
130
132
|
#### Runtime fields
|
|
@@ -180,7 +182,7 @@ The `activation` field belongs in **SKILL.md frontmatter** as a **top-level fiel
|
|
|
180
182
|
---
|
|
181
183
|
name: my-skill
|
|
182
184
|
activation: per-situation
|
|
183
|
-
description: "
|
|
185
|
+
description: "<what it does>. Use when <situations>."
|
|
184
186
|
---
|
|
185
187
|
```
|
|
186
188
|
|
package/license
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Unional
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
2
|
+
"name": "cyber-aced",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Agent Config Evaluation & Development. Spec, evaluate, and improve agent configurations: skills, AGENTS.md sections, subagent definitions, and commands.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/cyberuni/cyber-sdd.git",
|
|
8
|
+
"directory": "plugins/aced"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/cyberuni/cyber-sdd",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"aced-bench": "./dist/aced-bench.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"skills",
|
|
19
|
+
"agents",
|
|
20
|
+
"governances",
|
|
21
|
+
".plugin",
|
|
22
|
+
".codex-plugin",
|
|
23
|
+
".claude-plugin",
|
|
24
|
+
"!skills/**/*.test.mts"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@cyberuni/agent-harness": "0.6.0"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"tsdown": "^0.23.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.11"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsdown",
|
|
40
|
+
"check:spec": "node ../sdd/skills/check-project-specs/scripts/check-project-specs.mts",
|
|
41
|
+
"test": "node --test \"skills/*/scripts/*.test.mts\"",
|
|
42
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -21,6 +21,14 @@ A directory the run expanded carries both its own entry and a `file` entry per f
|
|
|
21
21
|
yielded — the directory hash covers names only, so without the per-file entries an edit inside that
|
|
22
22
|
directory would read `current` forever.
|
|
23
23
|
|
|
24
|
+
## Measured records
|
|
25
|
+
|
|
26
|
+
The bench engine writes a measured record per arm to `.agents/aced/results/bench/<suite>/` with an
|
|
27
|
+
`evaluated` set in the same entry shape but no `target`. `--suite <suite> --arm <label>` finds those
|
|
28
|
+
records by the `suite` and `arm` they record, picks the greatest recorded `createdAt`, and compares.
|
|
29
|
+
Every recorded input — `tasks.json`, a check, a `file` arm's source — changes what was measured, so
|
|
30
|
+
any mismatch is `stale`; there is no `incomplete` here.
|
|
31
|
+
|
|
24
32
|
## Why the record and not the tree
|
|
25
33
|
|
|
26
34
|
The first attempt at this capability inferred the subject's dependencies from outside: it guessed
|
|
@@ -50,7 +58,8 @@ oversight.
|
|
|
50
58
|
## Tests
|
|
51
59
|
|
|
52
60
|
`scripts/check-freshness.test.mts` — one test per frozen scenario, with fixtures chosen so a
|
|
53
|
-
guessing implementation cannot pass: the result is filed under a deliberately wrong directory name
|
|
61
|
+
guessing implementation cannot pass: the result is filed under a deliberately wrong directory name (a
|
|
62
|
+
measured record under a file name ending in the wrong arm label),
|
|
54
63
|
alphabetical filename order is the reverse of recorded timestamp order, and one fixture rewrites a
|
|
55
64
|
file's modification time while leaving its bytes identical.
|
|
56
65
|
|
|
@@ -26,6 +26,18 @@ node "<skill>/scripts/check-freshness.mts" --node <node-dir>
|
|
|
26
26
|
`.feature`. Exit status is **zero only for `current`**; every other verdict, and every fail-closed
|
|
27
27
|
path, exits non-zero.
|
|
28
28
|
|
|
29
|
+
For a **measured record** the bench engine wrote, name the suite and arm instead:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
node "<skill>/scripts/check-freshness.mts" --suite <suite> --arm <label>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
It reads `.agents/aced/results/bench/<suite>/`, keeps the records whose own `suite` and `arm` fields
|
|
36
|
+
match (never the file name), takes the greatest recorded `createdAt`, and compares its evaluated set —
|
|
37
|
+
the suite's `tasks.json`, its `checks/`, and a `file` arm's source. A measured record has no frozen
|
|
38
|
+
suite, so any moved input reads `stale`; it never reads `incomplete`. Run it from inside the
|
|
39
|
+
repository. `--suite` without `--arm`, or the reverse, fails closed with no verdict.
|
|
40
|
+
|
|
29
41
|
It also exposes the hashing routine, so `run` records entries with the same implementation that
|
|
30
42
|
reads them back — never a second one that merely agrees today:
|
|
31
43
|
|
|
@@ -282,6 +282,119 @@ export function findRepoRoot(from: string): string | null {
|
|
|
282
282
|
|
|
283
283
|
export const RESULTS_DIR = join('.agents', 'aced', 'results')
|
|
284
284
|
|
|
285
|
+
// ─── measured records ─────────────────────────────────────────────────────────
|
|
286
|
+
//
|
|
287
|
+
// The bench engine writes one measured record per arm to `.agents/aced/results/bench/<suite>/`.
|
|
288
|
+
// It names no `target`, so it is found by the `suite` and `arm` it RECORDS — never by its file
|
|
289
|
+
// name — and compared with the same hash rule. Every recorded input moves what was measured (the
|
|
290
|
+
// prompts, what `pass` means, the arm itself), so there is no frozen suite to split off and no
|
|
291
|
+
// `incomplete`: any moved input reads `stale`.
|
|
292
|
+
|
|
293
|
+
export const MEASURED_RESULTS_DIR = join(RESULTS_DIR, 'bench')
|
|
294
|
+
const MEASURED_BENCH_DIR = join('.agents', 'aced', 'bench')
|
|
295
|
+
|
|
296
|
+
export interface MeasuredRecord {
|
|
297
|
+
layer?: string
|
|
298
|
+
suite?: string
|
|
299
|
+
arm?: string
|
|
300
|
+
createdAt?: string
|
|
301
|
+
subject?: { kind?: string; from?: string }
|
|
302
|
+
evaluated?: EvaluatedEntry[]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface MeasuredScan {
|
|
306
|
+
forArm: Array<{ file: string; record: MeasuredRecord }>
|
|
307
|
+
unreadable: string[]
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Keeps the measured records whose OWN `suite` and `arm` fields match. A file that parses but is
|
|
312
|
+
* not a measured record — a compare record sitting beside them — is simply not a candidate.
|
|
313
|
+
*/
|
|
314
|
+
export function scanMeasured(files: string[], suite: string, arm: string, read: (f: string) => string): MeasuredScan {
|
|
315
|
+
const forArm: MeasuredScan['forArm'] = []
|
|
316
|
+
const unreadable: string[] = []
|
|
317
|
+
for (const file of files) {
|
|
318
|
+
let record: MeasuredRecord
|
|
319
|
+
try {
|
|
320
|
+
record = JSON.parse(read(file)) as MeasuredRecord
|
|
321
|
+
} catch {
|
|
322
|
+
unreadable.push(file)
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
if (record && record.layer === 'measured' && record.suite === suite && record.arm === arm)
|
|
326
|
+
forArm.push({ file, record })
|
|
327
|
+
}
|
|
328
|
+
return { forArm, unreadable }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Newest by RECORDED `createdAt`, for the same reason `selectNewest` ignores file names. */
|
|
332
|
+
export function selectNewestMeasured(records: MeasuredScan['forArm']): MeasuredScan['forArm'][number] | null {
|
|
333
|
+
let best: MeasuredScan['forArm'][number] | null = null
|
|
334
|
+
for (const r of records) {
|
|
335
|
+
const t = r.record.createdAt ?? ''
|
|
336
|
+
if (!best || t > (best.record.createdAt ?? '')) best = r
|
|
337
|
+
}
|
|
338
|
+
return best
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The bench engine always records the suite's `tasks.json`, and records a `file` arm's source
|
|
343
|
+
* whenever the arm reads it from a path. A set omitting either contradicts the record carrying it.
|
|
344
|
+
*/
|
|
345
|
+
export function measuredIncoherence(record: MeasuredRecord, suite: string): string | null {
|
|
346
|
+
const paths = new Set((record.evaluated ?? []).map((e) => e.path))
|
|
347
|
+
const tasks = join(MEASURED_BENCH_DIR, suite, 'tasks.json')
|
|
348
|
+
if (!paths.has(tasks)) {
|
|
349
|
+
return `the recorded provenance contradicts the record it accompanies: it carries no entry for ${tasks}`
|
|
350
|
+
}
|
|
351
|
+
const from = record.subject?.kind === 'file' ? record.subject.from : undefined
|
|
352
|
+
if (from?.startsWith('path:')) {
|
|
353
|
+
const source = from.slice(5)
|
|
354
|
+
if (!paths.has(source)) {
|
|
355
|
+
return `the recorded provenance omits the file arm's source: no entry for ${source}`
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function measuredMain(repoRoot: string, suite: string, arm: string): number {
|
|
362
|
+
process.stdout.write(`suite: ${suite}\narm: ${arm}\n`)
|
|
363
|
+
|
|
364
|
+
const dir = join(repoRoot, MEASURED_RESULTS_DIR, suite)
|
|
365
|
+
if (!existsSync(dir)) {
|
|
366
|
+
report({ verdict: 'absent', reason: 'nothing is measured for this suite', mismatched: [], skipped: [] })
|
|
367
|
+
return 1
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const scan = scanMeasured(collectResultFiles(dir), suite, arm, (f) => readFileSync(f, 'utf8'))
|
|
371
|
+
const skipped = scan.unreadable.map((f) => relative(repoRoot, f))
|
|
372
|
+
const newest = selectNewestMeasured(scan.forArm)
|
|
373
|
+
if (!newest) {
|
|
374
|
+
report({
|
|
375
|
+
verdict: 'absent',
|
|
376
|
+
reason: 'no measured record is recorded for this arm',
|
|
377
|
+
mismatched: [],
|
|
378
|
+
skipped,
|
|
379
|
+
})
|
|
380
|
+
return 1
|
|
381
|
+
}
|
|
382
|
+
if (!newest.record.evaluated) {
|
|
383
|
+
report({ verdict: 'absent', reason: 'the record carries no recorded provenance', mismatched: [], skipped })
|
|
384
|
+
return 1
|
|
385
|
+
}
|
|
386
|
+
const bad = measuredIncoherence(newest.record, suite)
|
|
387
|
+
if (bad) {
|
|
388
|
+
report({ verdict: 'absent', reason: bad, mismatched: [], skipped })
|
|
389
|
+
return 1
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// No frozen suite: every entry is a subject input, so the verdict is `stale` or `current`.
|
|
393
|
+
const decision = decideVerdict(newest.record, repoRoot, '', skipped)
|
|
394
|
+
report(decision)
|
|
395
|
+
return decision.verdict === 'current' ? 0 : 1
|
|
396
|
+
}
|
|
397
|
+
|
|
285
398
|
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
286
399
|
|
|
287
400
|
function report(d: Decision): void {
|
|
@@ -314,10 +427,30 @@ export function main(argv: string[]): number {
|
|
|
314
427
|
return 0
|
|
315
428
|
}
|
|
316
429
|
|
|
430
|
+
const suiteIdx = argv.indexOf('--suite')
|
|
431
|
+
const armIdx = argv.indexOf('--arm')
|
|
432
|
+
if (suiteIdx !== -1 || armIdx !== -1) {
|
|
433
|
+
// Fail closed, as the --node path does: one option without the other emits no verdict.
|
|
434
|
+
const suite = suiteIdx === -1 ? undefined : argv[suiteIdx + 1]
|
|
435
|
+
const arm = armIdx === -1 ? undefined : argv[armIdx + 1]
|
|
436
|
+
if (!suite || !arm) {
|
|
437
|
+
console.error(
|
|
438
|
+
`✗ a measured check needs both --suite <suite> and --arm <label>; missing ${suite ? '--arm' : '--suite'}; no verdict`,
|
|
439
|
+
)
|
|
440
|
+
return 1
|
|
441
|
+
}
|
|
442
|
+
const repoRoot = findRepoRoot(process.cwd())
|
|
443
|
+
if (!repoRoot) {
|
|
444
|
+
console.error('✗ no repository root above the working directory; no verdict')
|
|
445
|
+
return 1
|
|
446
|
+
}
|
|
447
|
+
return measuredMain(repoRoot, suite, arm)
|
|
448
|
+
}
|
|
449
|
+
|
|
317
450
|
const nodeIdx = argv.indexOf('--node')
|
|
318
451
|
const nodeArg = nodeIdx === -1 ? undefined : argv[nodeIdx + 1]
|
|
319
452
|
if (!nodeArg) {
|
|
320
|
-
console.error('✗ usage: check-freshness --node <node-dir>')
|
|
453
|
+
console.error('✗ usage: check-freshness --node <node-dir> | --suite <suite> --arm <label>')
|
|
321
454
|
return 1
|
|
322
455
|
}
|
|
323
456
|
const nodeDir = resolve(nodeArg)
|
|
@@ -24,7 +24,7 @@ The skill walks you through the shape before writing anything:
|
|
|
24
24
|
3. **Pattern** — process, tool-based, or standard (personas go to `define-agent`)
|
|
25
25
|
4. **Placement** — user-global, project-private, or project-public, plus which runtime symlinks to create
|
|
26
26
|
|
|
27
|
-
It then drafts the SKILL.md (kebab-case name, a
|
|
27
|
+
It then drafts the SKILL.md (kebab-case name, a description that leads with the capability and then says when it applies, ≤1024 chars, a step body under the size bar), adds a README for a public skill, runs the structural audit and fixes CRITICAL/HIGH findings, and points you at the ACED eval loop (`start-mission` / `add-scenario` / `run`) to spec and score it.
|
|
28
28
|
|
|
29
29
|
## Enhanced from create-skill
|
|
30
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: define-skill
|
|
3
|
-
description: '
|
|
3
|
+
description: 'Creates, scaffolds, or formalizes a workflow skill — a process, tool-based, or standard SKILL.md — from scratch or from an existing ad-hoc workflow. Use when the user asks for one — "create a skill", "write a skill for X", "I want a skill that…", "turn this workflow into a skill", "help me build this skill" — even if they do not say "skill" explicitly. Not for agent definitions or personas (define-agent), reference-only rule sets (define-governance), or extracting the current session (skillify).'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Define Skill
|
|
@@ -45,16 +45,16 @@ skill here.
|
|
|
45
45
|
|
|
46
46
|
## Load the governance
|
|
47
47
|
|
|
48
|
-
Before writing content, load the **skill-design** governance
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
Before writing content, load the **skill-design** governance as the authoritative rules for
|
|
49
|
+
principles, progressive disclosure, description structure, and when to extract deterministic logic to
|
|
50
|
+
a script. Read the first that exists:
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```
|
|
52
|
+
1. `.agents/governances/skill-design.md` — a project override
|
|
53
|
+
2. `references/governances/skill-design.md` in this skill — the default copy
|
|
55
54
|
|
|
56
55
|
If the skill will ship a `scripts/` directory or document CLI commands agents run, also load
|
|
57
|
-
**agent-tool-output** for stdout / JSON /
|
|
56
|
+
**agent-tool-output** the same way (`references/governances/agent-tool-output.md`) for stdout / JSON /
|
|
57
|
+
non-interactive / stderr rules.
|
|
58
58
|
|
|
59
59
|
## Settle the shape — five questions
|
|
60
60
|
|
|
@@ -129,8 +129,10 @@ description: <capability> + "Use when <trigger>" + an implicit-phrasing example
|
|
|
129
129
|
2. <step>
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
- **Description** — the only field loaded at startup; it carries the whole triggering burden.
|
|
133
|
-
|
|
132
|
+
- **Description** — the only field loaded at startup; it carries the whole triggering burden. Lead
|
|
133
|
+
with the capability, then a "Use when…" trigger clause, then an implicit phrasing. Never open with a
|
|
134
|
+
fixed lead-in such as "Use this skill when" or "When to use". Keep it ≤1024 characters, the
|
|
135
|
+
capability and main triggers first. For a
|
|
134
136
|
**partial skill** (a reusable part other skills call by name, not user-triggered), set
|
|
135
137
|
`user-invocable: false` and lead the description with the `"Partial Skill:"` prefix (recommended
|
|
136
138
|
form `"Partial Skill: invoke by name only — <identity>. <caller>."`), kept minimal and
|
|
@@ -166,7 +168,7 @@ name it by that gate/scope, not a bare action verb — `<domain>-<gate>-judge` f
|
|
|
166
168
|
`implementer`, `judge`, `validator`, `reviewer`, `checker` alone. A producer subagent (e.g.
|
|
167
169
|
`scenario-writer`, `doc-writer`) keeps its action-oriented name — this check does not fire for it.
|
|
168
170
|
|
|
169
|
-
Evaluate this **in-skill quality check** — separate from the mechanical
|
|
171
|
+
Evaluate this **in-skill quality check** — separate from the mechanical structural audit below — before
|
|
170
172
|
handing the skill back: a drafted gate/case scorer named with a bare action verb is a **HIGH** severity
|
|
171
173
|
finding; fix it (rename to the gate-and-scope form) before presenting the skill.
|
|
172
174
|
|
|
@@ -175,9 +177,11 @@ finding; fix it (rename to the gate-and-scope form) before presenting the skill.
|
|
|
175
177
|
Run the structural audit and fix any CRITICAL or HIGH finding **before** presenting the skill:
|
|
176
178
|
|
|
177
179
|
```bash
|
|
178
|
-
|
|
180
|
+
node "<improve-skill>/scripts/validate.mts" --path <placement-dir>/<name>
|
|
179
181
|
```
|
|
180
182
|
|
|
183
|
+
Replace `<improve-skill>` with the directory of the `improve-skill` skill installed beside this one.
|
|
184
|
+
|
|
181
185
|
For a fuller pass, invoke the `improve-skill` skill. Do not present a skill with an
|
|
182
186
|
open CRITICAL finding.
|
|
183
187
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Agent Tool Output
|
|
2
|
+
|
|
3
|
+
Rules for scripts, hooks, and CLIs that AI agents invoke. Apply when authoring skill `scripts/` or documenting CLI commands in SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Agent-native tools
|
|
6
|
+
|
|
7
|
+
Use for skill `scripts/`, hook runtime commands, and any tool where the primary consumer is always an agent.
|
|
8
|
+
|
|
9
|
+
### Stdout is the machine contract
|
|
10
|
+
|
|
11
|
+
- Emit **one JSON value** per run, or silence on success with no payload.
|
|
12
|
+
- Use `process.stdout.write(JSON.stringify(result) + '\n')` — never `console.log` for contract output.
|
|
13
|
+
- No prose, tables, progress bars, or ANSI decoration on stdout in default mode.
|
|
14
|
+
|
|
15
|
+
### Stderr is human/diagnostic
|
|
16
|
+
|
|
17
|
+
- Use `console.warn`, `console.error`, or `process.stderr.write` for diagnostics.
|
|
18
|
+
- Gate tables, progress, and verbose human output behind a `--verbose` flag.
|
|
19
|
+
|
|
20
|
+
### Files hold durable state
|
|
21
|
+
|
|
22
|
+
- Write artifacts to disk; stdout JSON names paths or fields only.
|
|
23
|
+
- Agents read artifact files by path (from stdout JSON or SKILL.md), not by scraping stdout prose.
|
|
24
|
+
|
|
25
|
+
### Skills present; tools record
|
|
26
|
+
|
|
27
|
+
- SKILL.md tells the agent which file or JSON field to read.
|
|
28
|
+
- The agent summarizes results for the user — the tool does not format user-facing prose on stdout.
|
|
29
|
+
|
|
30
|
+
### Autonomous runs
|
|
31
|
+
|
|
32
|
+
- Document `--yes` or an equivalent non-interactive flag when the tool can prompt.
|
|
33
|
+
- Never put interactive prompts on stdout.
|
|
34
|
+
- Agents must be able to run the tool without human input.
|
|
35
|
+
|
|
36
|
+
### Exit codes
|
|
37
|
+
|
|
38
|
+
- `0` = success.
|
|
39
|
+
- Non-zero = failure; include a concise error on stderr.
|
|
40
|
+
|
|
41
|
+
## Dual-audience CLI
|
|
42
|
+
|
|
43
|
+
Use for command-line tools used by both humans and agents.
|
|
44
|
+
|
|
45
|
+
### Default stdout = human-readable
|
|
46
|
+
|
|
47
|
+
- Tables, aligned fields, and prose summaries are fine for interactive terminal use.
|
|
48
|
+
|
|
49
|
+
### `--format agent` = agent contract
|
|
50
|
+
|
|
51
|
+
- Agents **must** pass `--format agent` when they need output optimized for LLM reasoning.
|
|
52
|
+
- `--format agent` stdout is terse, structured text — lower token cost and better reasoning than JSON.
|
|
53
|
+
- `--format json` is for non-LLM machine consumers (scripts, pipelines, database inserts).
|
|
54
|
+
- SKILL.md must instruct agents to use `--format agent`, not to parse default prose or tables.
|
|
55
|
+
- `--json` is a deprecated alias for `--format json`, accepted for backward compatibility but not shown in `--help`.
|
|
56
|
+
|
|
57
|
+
### Default-stdout exception
|
|
58
|
+
|
|
59
|
+
- When a tool documents **default stdout** as the machine contract (not `--json`), SKILL.md must say so explicitly.
|
|
60
|
+
- Do not assume default stdout is parseable for arbitrary CLIs.
|
|
61
|
+
|
|
62
|
+
### Stderr and verbosity
|
|
63
|
+
|
|
64
|
+
- Errors and validation failures go to stderr.
|
|
65
|
+
- Optional human status (dry-run summaries, registration tables) goes to stderr when `--verbose` is set.
|
|
66
|
+
|
|
67
|
+
### Same non-interactive and exit-code rules
|
|
68
|
+
|
|
69
|
+
- Document `--yes` / non-interactive paths where prompts exist.
|
|
70
|
+
- Exit `0` on success; non-zero on failure.
|
|
71
|
+
|
|
72
|
+
## SKILL.md authoring
|
|
73
|
+
|
|
74
|
+
When a skill documents commands agents run:
|
|
75
|
+
|
|
76
|
+
- Tell agents which flag to use: `--format agent` for LLM consumption, `--format json` for non-LLM machine consumers.
|
|
77
|
+
- Do **not** instruct agents to parse default stdout prose, summary tables, or generic "script output" as data.
|
|
78
|
+
- Prefer: "read `<artifact-path>`" or "run with `--format agent`" or "run with `--format json` and parse the array."
|
|
79
|
+
|
|
80
|
+
## References
|
|
81
|
+
|
|
82
|
+
Related governances (load on demand; read stdout as authoritative):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx cyberplace@<version> governance show skill-design
|
|
86
|
+
```
|