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.
@@ -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](../../../docs/adr/0035-authored-governances-ship-as-package-files.md) for why these
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` must contain `"Use this skill when"` or `"When to use"` trigger language — **unless the skill is name-only**, whose `description` is exactly `"By name only"` and carries no trigger language at all.
127
- - Keep `description` ≤120 characters — long descriptions are truncated in the agent context window.
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: "Use this skill when..."
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
- "name": "cyber-aced",
3
- "version": "0.3.0",
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.5.0"
28
- },
29
- "scripts": {
30
- "build": "tsdown",
31
- "check:spec": "node ../sdd/skills/check-project-specs/scripts/check-project-specs.mts",
32
- "test": "node --test \"skills/*/scripts/*.test.mts\"",
33
- "typecheck": "tsc -p tsconfig.json"
34
- },
35
- "publishConfig": {
36
- "access": "public"
37
- },
38
- "devDependencies": {
39
- "tsdown": "^0.23.0"
40
- },
41
- "engines": {
42
- "node": ">=22.11"
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 150–400 char trigger-bearing description, 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.
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: 'Use this skill when the user wants to create, scaffold, or formalize a workflow skill — a process, tool-based, or standard SKILL.md — from scratch or from an existing ad-hoc workflow. Trigger on "create a skill", "write a skill for X", "I want a skill that…", "turn this workflow into a skill", or "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).'
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 and read stdout as the authoritative
49
- rules for principles, progressive disclosure, description structure, and when to extract deterministic
50
- logic to a script:
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
- ```bash
53
- npx cyberplace@0.2.4 governance show skill-design # resolve <version> via: npm view cyberplace version — never @latest
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 / non-interactive / stderr rules.
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. Target
133
- 150–400 characters (≤1024 hard limit): capability + "Use when…" + an implicit phrasing. For a
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 `audit validate` below — before
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
- npx cyberplace@0.2.4 audit validate --path <placement-dir>/<name>
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
+ ```