coding-agent-skills 0.2.15 → 0.2.16

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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes follow [Semantic Versioning](docs/versioning/README.md).
4
4
 
5
+ ## [0.2.16] - 2026-07-03
6
+
7
+ ### Added
8
+
9
+ - Optional `--json` output for every public `coding-agent-skills` CLI command.
10
+ - OpenClaw-compatible structured result fields for command identity, skill id, package
11
+ version, status, findings, warnings, skipped checks, refused behavior, safety summary,
12
+ `recommendedNextAction`, and exit-code meaning.
13
+ - Release tests that validate the JSON contract across the public CLI surface and confirm
14
+ redaction of local home paths and secret-like values.
15
+
16
+ ### Changed
17
+
18
+ - The public wrapper now preserves default human-readable output while offering sanitized
19
+ machine-readable results for orchestrator callers.
20
+ - Usage, release, testing, roadmap, ledger, and run-log docs now describe the exit-code
21
+ contract and OpenClaw integration boundary.
22
+ - Stale v0.2.15 ledger and run-log entries now reflect that the deployment-preflight
23
+ release was published, smoke-tested, and released.
24
+
5
25
  ## [0.2.15] - 2026-07-03
6
26
 
7
27
  ### Added
package/README.md CHANGED
@@ -52,6 +52,9 @@ Every skill emits the evidence-pack contract. A command being attempted is never
52
52
  - Review static migration and schema evidence with `coding-agent-skills migration-review <project-root>`.
53
53
  - Prepare local Git handoff evidence with `coding-agent-skills github-handoff <project-root>`.
54
54
  - Map static deployment readiness evidence with `coding-agent-skills deployment-preflight <project-root>`.
55
+ - Add `--json` to any public CLI command when an OpenClaw-style orchestrator
56
+ needs a sanitized machine-readable result with `success`, `status`,
57
+ `recommendedNextAction`, safety flags, and exit-code meaning.
55
58
  - Validate project adapters against [the formal adapter schema](schemas/project-adapter.schema.json).
56
59
  - Review [external adapter discovery](docs/adapters/discovery.md).
57
60
  - Run `node scripts/validate-adapters.mjs <adapter-root>` for a disposable external root.
@@ -92,6 +95,29 @@ Every skill emits the evidence-pack contract. A command being attempted is never
92
95
  Governance lives in [CONTRIBUTING.md](CONTRIBUTING.md), [ROADMAP.md](ROADMAP.md), and the [release policy](docs/release/README.md).
93
96
  The [harness guide](docs/testing/README.md) explains trigger, command, mutation, privacy, adapter, and completion checks.
94
97
 
98
+ ## Orchestrator Output
99
+
100
+ The default CLI output remains human-readable. OpenClaw-style callers can request a
101
+ structured result with `--json`:
102
+
103
+ ```bash
104
+ coding-agent-skills repo-map /path/to/project --json
105
+ ```
106
+
107
+ JSON output is read-only and sanitized. It includes command identity, package version,
108
+ skill id, status, findings, warnings, skipped checks, refused behavior, safety flags,
109
+ and `recommendedNextAction`. Exit codes follow the public contract:
110
+
111
+ - `0`: handled execution path, including complete, partial, blocked, or controlled audit results
112
+ - `2`: usage error
113
+ - `3`: safety refusal
114
+ - `4`: missing required input or file
115
+ - `5`: unexpected internal or runtime failure
116
+
117
+ OpenClaw should remain the owner of memory, routing, permissions, scheduling, user
118
+ interaction, and workflow state. `coding-agent-skills` is a safe callable evidence
119
+ producer, not an orchestrator.
120
+
95
121
  ## Autonomous Maintainer Loop
96
122
 
97
123
  The local maintainer loop reads Git tags, `ROADMAP.md`, `CHANGELOG.md`, and
package/ROADMAP.md CHANGED
@@ -42,6 +42,8 @@ execution constraints remain unchanged.
42
42
  evidence before separately approved GitHub work.
43
43
  - `v0.2.15`: audit-only `deployment-preflight` skill and CLI renderer for static
44
44
  deployment readiness evidence before separately approved deployment work.
45
+ - `v0.2.16`: OpenClaw-compatible optional `--json` output and documented exit-code
46
+ semantics for every public CLI command.
45
47
 
46
48
  The next milestone is recorded in [work-ledger.md](work-ledger.md). The
47
49
  [maintainer loop](RUNBOOK.md) may select and evidence that milestone, but it must stop
@@ -89,6 +91,7 @@ Next safe milestone options:
89
91
  | `migration-review-skill` | General with platform adapters | Audit-only | Implemented in `v0.2.13` |
90
92
  | `github-handoff-skill` | General | Audit-only | Implemented in `v0.2.14` |
91
93
  | `deployment-preflight-skill` | General | Audit-only | Implemented in `v0.2.15` |
94
+ | `orchestrator-json-output-contract` | General tooling | Read-only CLI contract | Implemented in `v0.2.16` |
92
95
  | `cloudflare-preflight-skill` | Platform-specific | Audit-only | Builder-mode approved; later in wave |
93
96
  | `cloudflare-deploy-skill` | Platform-specific | Action-capable | Blocked on approval model |
94
97
  | `supabase-rls-audit-skill` | Platform-specific | Audit-only | Builder-mode approved; later in wave |
@@ -1,9 +1,125 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
 
7
+ import { externalAdapterCliResult } from "../scripts/lib/adapter-discovery.mjs";
8
+ import { adapterRepoMapCliResult } from "../scripts/lib/adapter-repo-map.mjs";
9
+ import { apiContractAuditCliResult } from "../scripts/lib/api-contract-audit.mjs";
10
+ import { deploymentPreflightCliResult } from "../scripts/lib/deployment-preflight.mjs";
11
+ import { envAuditCliResult } from "../scripts/lib/env-audit.mjs";
12
+ import { githubHandoffCliResult } from "../scripts/lib/github-handoff.mjs";
13
+ import { migrationReviewCliResult } from "../scripts/lib/migration-review.mjs";
14
+ import { redactSensitiveText } from "../scripts/lib/pack-rules.mjs";
15
+ import { projectAdapterCliResult } from "../scripts/lib/project-adapter-installation.mjs";
16
+ import { routeTraceCliResult } from "../scripts/lib/route-trace.mjs";
17
+ import { secretAuditCliResult } from "../scripts/lib/secret-audit.mjs";
18
+
6
19
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
20
+ const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
21
+
22
+ const commandMetadata = {
23
+ "validate-pack": {
24
+ skillId: "pack-validation",
25
+ mode: "validation",
26
+ next: {
27
+ label: "Select a read-only audit command",
28
+ reason: "The package is structurally valid; choose the narrowest command for the target task.",
29
+ requiresApproval: false,
30
+ },
31
+ },
32
+ "validate-project": {
33
+ skillId: "project-adapter-validation",
34
+ mode: "validation",
35
+ next: {
36
+ label: "Run an adapter-aware read-only report",
37
+ reason: "The project adapter declaration is valid; use a scoped report command next.",
38
+ requiresApproval: false,
39
+ },
40
+ },
41
+ "repo-map": {
42
+ skillId: "repo-map",
43
+ mode: "audit-only",
44
+ next: {
45
+ label: "Review adapter-declared repo boundaries",
46
+ reason: "Use the reported docs, safe paths, ignored paths, and evidence requirements before choosing another action.",
47
+ requiresApproval: false,
48
+ },
49
+ },
50
+ "route-trace": {
51
+ skillId: "route-trace",
52
+ mode: "audit-only",
53
+ next: {
54
+ label: "Review static route findings",
55
+ reason: "Inspect verified and inferred route surfaces before approving code or runtime work.",
56
+ requiresApproval: false,
57
+ },
58
+ },
59
+ "env-audit": {
60
+ skillId: "env-audit",
61
+ mode: "audit-only",
62
+ next: {
63
+ label: "Review variable-name inventory",
64
+ reason: "Use value-free environment names to plan documentation or configuration review without reading secrets.",
65
+ requiresApproval: false,
66
+ },
67
+ },
68
+ "secret-audit": {
69
+ skillId: "secret-audit",
70
+ mode: "audit-only",
71
+ next: {
72
+ label: "Review redacted secret-risk findings",
73
+ reason: "Any remediation, credential rotation, or repository mutation needs separate approval.",
74
+ requiresApproval: true,
75
+ },
76
+ },
77
+ "api-contract-audit": {
78
+ skillId: "api-contract-audit",
79
+ mode: "audit-only",
80
+ next: {
81
+ label: "Review static API contract surfaces",
82
+ reason: "Use contract, endpoint, client, and schema evidence before editing API code.",
83
+ requiresApproval: false,
84
+ },
85
+ },
86
+ "migration-review": {
87
+ skillId: "migration-review",
88
+ mode: "audit-only",
89
+ next: {
90
+ label: "Review migration evidence",
91
+ reason: "Database access, migration edits, generation, or execution require separate approval.",
92
+ requiresApproval: true,
93
+ },
94
+ },
95
+ "github-handoff": {
96
+ skillId: "github-handoff",
97
+ mode: "audit-only",
98
+ next: {
99
+ label: "Request explicit GitHub handoff approval",
100
+ reason: "Commit, push, tag, pull request, or API mutation work is outside this read-only report.",
101
+ requiresApproval: true,
102
+ },
103
+ },
104
+ "deployment-preflight": {
105
+ skillId: "deployment-preflight",
106
+ mode: "audit-only",
107
+ next: {
108
+ label: "Request explicit deployment approval",
109
+ reason: "Deployment, provider API, provider CLI, build, runtime, or migration work is outside this read-only report.",
110
+ requiresApproval: true,
111
+ },
112
+ },
113
+ "validate-adapters": {
114
+ skillId: "external-adapter-validation",
115
+ mode: "validation",
116
+ next: {
117
+ label: "Use accepted adapters only as narrowing metadata",
118
+ reason: "Adapters may narrow safe context but must not grant new powers or weaken shared restrictions.",
119
+ requiresApproval: false,
120
+ },
121
+ },
122
+ };
7
123
 
8
124
  const commands = {
9
125
  "validate-pack": {
@@ -73,6 +189,29 @@ const commands = {
73
189
  },
74
190
  };
75
191
 
192
+ const jsonHandlers = {
193
+ "validate-project": ([projectRoot]) => projectAdapterCliResult(projectRoot, { coreRoot: repoRoot }),
194
+ "repo-map": ([projectRoot]) => adapterRepoMapCliResult(projectRoot, { coreRoot: repoRoot }),
195
+ "route-trace": ([projectRoot]) => routeTraceCliResult(projectRoot, { coreRoot: repoRoot }),
196
+ "env-audit": ([projectRoot]) => envAuditCliResult(projectRoot, { coreRoot: repoRoot }),
197
+ "secret-audit": ([projectRoot]) => secretAuditCliResult(projectRoot, { coreRoot: repoRoot }),
198
+ "api-contract-audit": ([projectRoot]) => apiContractAuditCliResult(projectRoot, { coreRoot: repoRoot }),
199
+ "migration-review": ([projectRoot]) => migrationReviewCliResult(projectRoot, { coreRoot: repoRoot }),
200
+ "github-handoff": ([projectRoot]) => githubHandoffCliResult(projectRoot, { coreRoot: repoRoot }),
201
+ "deployment-preflight": ([projectRoot]) => deploymentPreflightCliResult(projectRoot, { coreRoot: repoRoot }),
202
+ "validate-adapters": ([adapterRoot]) => externalAdapterCliResult(adapterRoot, { coreRoot: repoRoot }),
203
+ };
204
+
205
+ function stripJsonFlag(args) {
206
+ let json = false;
207
+ const rest = [];
208
+ for (const arg of args) {
209
+ if (arg === "--json") json = true;
210
+ else rest.push(arg);
211
+ }
212
+ return { json, args: rest };
213
+ }
214
+
76
215
  function usage(exitCode = 0) {
77
216
  const lines = [
78
217
  "usage: coding-agent-skills <command> [args]",
@@ -97,7 +236,192 @@ function usage(exitCode = 0) {
97
236
  process.exitCode = exitCode;
98
237
  }
99
238
 
100
- const [commandName, ...args] = process.argv.slice(2);
239
+ function sanitizedLines(text) {
240
+ return redactSensitiveText(text)
241
+ .split(/\r?\n/)
242
+ .map((line) => line.trimEnd())
243
+ .filter((line) => line.length > 0);
244
+ }
245
+
246
+ function sectionLines(lines, heading) {
247
+ const result = [];
248
+ let active = false;
249
+ for (const line of lines) {
250
+ if (line === `## ${heading}`) {
251
+ active = true;
252
+ continue;
253
+ }
254
+ if (active && line.startsWith("## ")) break;
255
+ if (active && line.trim()) result.push(line.replace(/^- /, ""));
256
+ }
257
+ return result.filter((line) => line !== "none" && line !== "none found");
258
+ }
259
+
260
+ function detectedStatus(lines, exitCode) {
261
+ for (const line of lines) {
262
+ const match = /^Status:\s*([a-z-]+)/i.exec(line);
263
+ if (match) return match[1].toLowerCase();
264
+ }
265
+ if (exitCode === 0) return "complete";
266
+ if (exitCode === 2) return "failed";
267
+ return "failed";
268
+ }
269
+
270
+ function exitCodeMeaning(exitCode) {
271
+ if (exitCode === 0) return "handled";
272
+ if (exitCode === 2) return "usage-error";
273
+ if (exitCode === 3) return "safety-refusal";
274
+ if (exitCode === 4) return "missing-required-input";
275
+ return "unexpected-internal-or-runtime-failure";
276
+ }
277
+
278
+ function adapterSummary(lines) {
279
+ const adapter = {};
280
+ const adapterLines = [
281
+ ...sectionLines(lines, "Adapter Scope"),
282
+ ...sectionLines(lines, "Adapter Bounds"),
283
+ ];
284
+ for (const line of adapterLines) {
285
+ const match = /^([^:]+):\s*(.*)$/.exec(line);
286
+ if (!match) continue;
287
+ const key = match[1]
288
+ .trim()
289
+ .replace(/[^a-zA-Z0-9]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase())
290
+ .replace(/^[A-Z]/, (char) => char.toLowerCase());
291
+ adapter[key] = match[2].trim();
292
+ }
293
+ return adapter;
294
+ }
295
+
296
+ function summaryLines(lines) {
297
+ return lines
298
+ .filter((line) => line.trim().length > 0)
299
+ .filter((line) => !line.startsWith("#"))
300
+ .filter((line) => !line.startsWith("## "))
301
+ .filter((line) => !line.startsWith("- "))
302
+ .slice(0, 12);
303
+ }
304
+
305
+ function findingLines(lines) {
306
+ const ignoredSections = new Set([
307
+ "Git State",
308
+ "Adapter Scope",
309
+ "Adapter Bounds",
310
+ "Scope Paths",
311
+ "Ignored Paths",
312
+ "Summary",
313
+ "Skipped",
314
+ "Not Verified",
315
+ "Warnings",
316
+ "Refused Behavior",
317
+ ]);
318
+ const findings = [];
319
+ let current = null;
320
+ for (const line of lines) {
321
+ if (line.startsWith("## ")) {
322
+ current = line.replace(/^## /, "");
323
+ continue;
324
+ }
325
+ if (!current || ignoredSections.has(current)) continue;
326
+ if (line.startsWith("- ") && !line.includes("none found")) findings.push(line.slice(2));
327
+ }
328
+ return findings.slice(0, 100);
329
+ }
330
+
331
+ function normalizeOutcome(outcome) {
332
+ if (Array.isArray(outcome.lines)) {
333
+ return {
334
+ exitCode: outcome.exitCode ?? 5,
335
+ lines: outcome.lines
336
+ .map((line) => redactSensitiveText(line))
337
+ .filter((line) => line.trim().length > 0),
338
+ report: outcome.report ?? outcome.result ?? null,
339
+ };
340
+ }
341
+
342
+ return {
343
+ exitCode: outcome.status ?? 5,
344
+ lines: [
345
+ ...sanitizedLines(outcome.stdout ?? ""),
346
+ ...sanitizedLines(outcome.stderr ?? ""),
347
+ ],
348
+ report: null,
349
+ };
350
+ }
351
+
352
+ function buildJsonResult(commandName, args, outcome) {
353
+ const normalized = normalizeOutcome(outcome);
354
+ const lines = normalized.lines;
355
+ const metadata = commandMetadata[commandName] ?? {
356
+ skillId: commandName,
357
+ mode: "unknown",
358
+ next: null,
359
+ };
360
+ const status = normalized.report?.status ?? detectedStatus(lines, normalized.exitCode);
361
+ const summary = summaryLines(lines);
362
+ const warnings = [
363
+ ...(Array.isArray(normalized.report?.warnings)
364
+ ? normalized.report.warnings.map((line) => redactSensitiveText(line))
365
+ : []),
366
+ ...sectionLines(lines, "Warnings"),
367
+ ...lines.filter((line) => /\bwarning\b/i.test(line) && !line.startsWith("## ")),
368
+ ];
369
+
370
+ return {
371
+ success: normalized.exitCode === 0,
372
+ status,
373
+ tool: "coding-agent-skills",
374
+ command: commandName,
375
+ skillId: metadata.skillId,
376
+ packageVersion: packageJson.version,
377
+ mode: metadata.mode,
378
+ changedState: false,
379
+ summary: summary.length > 0
380
+ ? summary
381
+ : [
382
+ normalized.exitCode === 0
383
+ ? `${commandName} completed successfully`
384
+ : `${commandName} did not complete successfully`,
385
+ ],
386
+ findings: Array.isArray(normalized.report?.findings)
387
+ ? normalized.report.findings.map((finding) => redactSensitiveText(JSON.stringify(finding)))
388
+ : findingLines(lines),
389
+ warnings: [...new Set(warnings)],
390
+ risks: Array.isArray(normalized.report?.riskIndicators)
391
+ ? normalized.report.riskIndicators.map((risk) => redactSensitiveText(JSON.stringify(risk)))
392
+ : sectionLines(lines, "Risk Indicators"),
393
+ skipped: Array.isArray(normalized.report?.skipped)
394
+ ? normalized.report.skipped.map((item) => redactSensitiveText(JSON.stringify(item)))
395
+ : sectionLines(lines, "Skipped"),
396
+ notVerified: Array.isArray(normalized.report?.notVerified)
397
+ ? normalized.report.notVerified.map((item) => redactSensitiveText(String(item)))
398
+ : sectionLines(lines, "Not Verified"),
399
+ refusedBehavior: Array.isArray(normalized.report?.refusedBehavior)
400
+ ? normalized.report.refusedBehavior.map((item) => redactSensitiveText(String(item)))
401
+ : sectionLines(lines, "Refused Behavior"),
402
+ adapter: adapterSummary(lines),
403
+ recommendedNextAction: metadata.next,
404
+ safety: {
405
+ readOnly: true,
406
+ secretsRead: false,
407
+ targetCommandsRun: false,
408
+ mutationsPerformed: false,
409
+ },
410
+ exitCode: normalized.exitCode,
411
+ exitCodeMeaning: exitCodeMeaning(normalized.exitCode),
412
+ invocation: {
413
+ args: args.map((arg) => redactSensitiveText(arg)),
414
+ outputFormat: "json",
415
+ },
416
+ };
417
+ }
418
+
419
+ function printJson(value) {
420
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
421
+ }
422
+
423
+ const { json, args: rawArgs } = stripJsonFlag(process.argv.slice(2));
424
+ const [commandName, ...args] = rawArgs;
101
425
  if (!commandName || commandName === "help" || commandName === "--help" || commandName === "-h") {
102
426
  usage(commandName ? 0 : 2);
103
427
  } else if (!Object.hasOwn(commands, commandName)) {
@@ -108,6 +432,10 @@ if (!commandName || commandName === "help" || commandName === "--help" || comman
108
432
  if ((command.requiredArgs ?? 0) !== args.length) {
109
433
  process.stderr.write(`usage: ${command.usage}\n`);
110
434
  process.exitCode = 2;
435
+ } else if (json && jsonHandlers[commandName]) {
436
+ const outcome = jsonHandlers[commandName](args);
437
+ printJson(buildJsonResult(commandName, args, outcome));
438
+ process.exitCode = outcome.exitCode ?? 5;
111
439
  } else {
112
440
  const result = spawnSync(
113
441
  process.execPath,
@@ -115,10 +443,13 @@ if (!commandName || commandName === "help" || commandName === "--help" || comman
115
443
  {
116
444
  cwd: repoRoot,
117
445
  encoding: "utf8",
118
- stdio: "inherit",
446
+ stdio: json ? "pipe" : "inherit",
119
447
  shell: false,
120
448
  },
121
449
  );
450
+ if (json) {
451
+ printJson(buildJsonResult(commandName, args, result));
452
+ }
122
453
  process.exitCode = result.status ?? 1;
123
454
  }
124
455
  }
@@ -19,20 +19,23 @@
19
19
  13. Inspect tarball contents for local-only files, credentials, `.env` files, dependency
20
20
  folders, generated output, and unrelated repositories.
21
21
  14. Install the tarball into a temporary npm prefix and smoke-test the installed CLI.
22
- 15. Smoke-test any new CLI command such as `coding-agent-skills route-trace`,
22
+ 15. Smoke-test human-readable and `--json` output for public commands touched by the
23
+ release. JSON output must include safety flags, `recommendedNextAction`, and
24
+ `exitCodeMeaning` without secrets or `.env` contents.
25
+ 16. Smoke-test any new CLI command such as `coding-agent-skills route-trace`,
23
26
  `coding-agent-skills env-audit`, `coding-agent-skills secret-audit`,
24
27
  `coding-agent-skills api-contract-audit`, `coding-agent-skills migration-review`,
25
28
  `coding-agent-skills github-handoff`, or `coding-agent-skills deployment-preflight`
26
29
  against synthetic fixtures only unless a real project read-only smoke is explicitly
27
30
  approved.
28
- 16. Review changelog, ledger, run evidence, and versioning impact.
29
- 17. Commit with approved identity.
30
- 18. Push `main` using credential-free remotes.
31
- 19. Confirm a clean synchronized worktree.
32
- 20. Create and push the annotated version tag.
33
- 21. Publish with `npm publish --access public --registry=https://registry.npmjs.org/`.
34
- 22. Install the published package into a temporary prefix and smoke-test the installed CLI.
35
- 23. Create the GitHub Release for the pushed tag.
31
+ 17. Review changelog, ledger, run evidence, and versioning impact.
32
+ 18. Commit with approved identity.
33
+ 19. Push `main` using credential-free remotes.
34
+ 20. Confirm a clean synchronized worktree.
35
+ 21. Create and push the annotated version tag.
36
+ 22. Publish with `npm publish --access public --registry=https://registry.npmjs.org/`.
37
+ 23. Install the published package into a temporary prefix and smoke-test the installed CLI.
38
+ 24. Create the GitHub Release for the pushed tag.
36
39
 
37
40
  Deployments, migrations, runtime mutation, platform actions, and target-project builds or
38
41
  tests remain outside this release process unless separately approved.
@@ -7,7 +7,7 @@ safety model.
7
7
  ## Current Package Shape
8
8
 
9
9
  - Package name: `coding-agent-skills`.
10
- - Package version: `0.2.15`.
10
+ - Package version: `0.2.16`.
11
11
  - CLI bin: `coding-agent-skills` mapped to `bin/coding-agent-skills`.
12
12
  - Module type: `module`.
13
13
  - Dependencies: none.
@@ -37,6 +37,18 @@ coding-agent-skills deployment-preflight /path/to/project
37
37
  coding-agent-skills validate-adapters /path/to/adapter-root
38
38
  ```
39
39
 
40
+ Each public command also supports optional machine-readable output:
41
+
42
+ ```bash
43
+ coding-agent-skills repo-map /path/to/project --json
44
+ ```
45
+
46
+ The JSON contract is intended for OpenClaw-style orchestrators that already own memory,
47
+ routing, approvals, scheduling, and workflow state. It includes `success`, `status`,
48
+ `command`, `skillId`, `packageVersion`, sanitized findings and warnings, safety flags,
49
+ `recommendedNextAction`, and `exitCodeMeaning`. The default human-readable output is
50
+ unchanged.
51
+
40
52
  The package can also be executed without a repo-local install:
41
53
 
42
54
  ```bash
@@ -112,3 +124,11 @@ already permits a bounded local validation action. The installed `repo-map`,
112
124
 
113
125
  Project adapters narrow context for safer repository understanding; they do not weaken
114
126
  shared restrictions or authorize additional command families.
127
+
128
+ ## Exit Codes
129
+
130
+ - `0`: handled execution path, including complete, partial, blocked, or controlled audit result
131
+ - `2`: usage error
132
+ - `3`: safety refusal
133
+ - `4`: missing required input or file
134
+ - `5`: unexpected internal or runtime failure
@@ -126,6 +126,14 @@ compatibility, schema drift, restriction weakening, evidence removal, failure su
126
126
  completion override, mode escalation, `.env` avoidance, traversal, symlinks, and mutation
127
127
  snapshots. Chain summaries use ordinal revision labels rather than directory names.
128
128
 
129
+ ## OpenClaw-Compatible CLI JSON
130
+
131
+ Public CLI tests exercise `--json` for every exposed command. The contract must remain
132
+ valid JSON, preserve default human-readable output, include safety flags and
133
+ `recommendedNextAction`, use handled exit semantics for successful/partial audit results,
134
+ redact local home paths, and avoid token, private-key, authorization-header, or `.env`
135
+ contents.
136
+
129
137
  ## Evidence Bundles
130
138
 
131
139
  Disposable evidence bundles cover valid replay, hash mismatch, missing entries,
@@ -117,6 +117,50 @@ The installed CLI does not run target project builds or tests, perform runtime c
117
117
  deploy, migrate, mutate services or processes, or read `.env` files. Project adapters
118
118
  narrow context; they do not grant additional power or weaken shared restrictions.
119
119
 
120
+ ## Machine-Readable Output
121
+
122
+ Every public CLI command accepts optional `--json` for OpenClaw-style tool callers:
123
+
124
+ ```bash
125
+ coding-agent-skills repo-map /path/to/project --json
126
+ coding-agent-skills validate-pack --json
127
+ ```
128
+
129
+ The default human-readable output is unchanged. JSON output is sanitized and includes:
130
+
131
+ - `success`
132
+ - `status`
133
+ - `tool`
134
+ - `command`
135
+ - `skillId`
136
+ - `packageVersion`
137
+ - `mode`
138
+ - `changedState`
139
+ - `summary`
140
+ - `findings`
141
+ - `warnings`
142
+ - `risks`
143
+ - `skipped`
144
+ - `notVerified`
145
+ - `refusedBehavior`
146
+ - `adapter`
147
+ - `recommendedNextAction`
148
+ - `safety`
149
+ - `exitCode`
150
+ - `exitCodeMeaning`
151
+
152
+ Exit-code semantics:
153
+
154
+ - `0`: handled execution path, including complete, partial, blocked, or controlled audit result
155
+ - `2`: usage error
156
+ - `3`: safety refusal
157
+ - `4`: missing required input or file
158
+ - `5`: unexpected internal or runtime failure
159
+
160
+ OpenClaw or another orchestrator should own memory, routing, permissions, scheduling,
161
+ chat/user interaction, and workflow state. This package remains a read-only external tool
162
+ and evidence producer.
163
+
120
164
  ## Local Command Surface
121
165
 
122
166
  From the shared skill repository root, the same wrapper can be used directly:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-agent-skills",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Evidence-first, read-only coding-agent skills and project adapter tooling.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -277,6 +277,23 @@ This file records bounded maintainer-loop runs. Entries must not contain secrets
277
277
  API calls, no provider CLI execution, no package installs, no builds, no tests, no
278
278
  runtime checks, no service mutation, no deployments, no migrations, and no target-project
279
279
  mutation.
280
+ - Validation commands: full source validation, package dry-run, tarball smoke, registry
281
+ install smoke, npm exec, and GitHub Release verification.
282
+ - Result: passed; `v0.2.15` was committed, pushed, tagged, published to npm, smoke-tested
283
+ from the registry, and released on GitHub.
284
+ - Commit/tag/push status: complete.
285
+
286
+ ## implementation-v0.2.16-openclaw-json-output
287
+
288
+ - Run ID: `implementation-v0.2.16-openclaw-json-output`
289
+ - Repository: `/home/oneclickwebsitedesignfactory/coding-agent-skills`
290
+ - Command used: `bounded approval for OpenClaw-compatible JSON output and exit-code contract`
291
+ - Files changed: CLI wrapper, release tests, usage/release/testing docs, changelog,
292
+ roadmap, work ledger, run log, and package metadata.
293
+ - Safety boundary: read-only machine-readable output only; no `.env` reads, no OpenClaw
294
+ modification, no OpenClaw plugin code, no cron or memory integration, no real project
295
+ repository mutation, no target-project builds/tests/runtime checks, no deployments, no
296
+ migrations, and no dependency changes.
280
297
  - Validation commands: pending final release validation matrix.
281
298
  - Result: pass pending final publication evidence.
282
299
  - Commit/tag/push status: pending approved release workflow.
@@ -221,6 +221,39 @@ function snapshotAbsoluteDirectory(directory) {
221
221
  return digest.digest("hex");
222
222
  }
223
223
 
224
+ function assertOpenClawJsonContract(value, command, packageVersion = "0.2.16") {
225
+ assert.equal(value.tool, "coding-agent-skills");
226
+ assert.equal(value.command, command);
227
+ assert.equal(value.packageVersion, packageVersion);
228
+ assert.equal(value.success, true);
229
+ assert.match(value.status, /^(complete|partial|blocked|failed|empty)$/);
230
+ assert.equal(value.changedState, false);
231
+ assert.equal(value.safety?.readOnly, true);
232
+ assert.equal(value.safety?.secretsRead, false);
233
+ assert.equal(value.safety?.targetCommandsRun, false);
234
+ assert.equal(value.safety?.mutationsPerformed, false);
235
+ assert.equal(value.exitCode, 0);
236
+ assert.equal(value.exitCodeMeaning, "handled");
237
+ assert.ok(value.recommendedNextAction);
238
+ assert.equal(typeof value.recommendedNextAction.label, "string");
239
+ assert.equal(typeof value.recommendedNextAction.reason, "string");
240
+ assert.equal(typeof value.recommendedNextAction.requiresApproval, "boolean");
241
+ for (const key of [
242
+ "summary",
243
+ "findings",
244
+ "warnings",
245
+ "risks",
246
+ "skipped",
247
+ "notVerified",
248
+ "refusedBehavior",
249
+ ]) {
250
+ assert.ok(Array.isArray(value[key]), `${command}.${key} must be an array`);
251
+ }
252
+ const encoded = JSON.stringify(value);
253
+ assert.doesNotMatch(encoded, /github_pat_|ghp_|Authorization:\s*Bearer|BEGIN .*PRIVATE KEY/i);
254
+ assert.doesNotMatch(encoded, /\/home\/oneclickwebsitedesignfactory\//);
255
+ }
256
+
224
257
  const manifestSchema = readJson("schemas/skill-manifest.schema.json");
225
258
  const policySchema = readJson("schemas/command-policy.schema.json");
226
259
  const adapterSchema = readJson("schemas/project-adapter.schema.json");
@@ -394,10 +427,68 @@ test("local CLI maps approved commands to existing safe scripts", () => {
394
427
  assert.match(unknown.stderr, /unknown command: deploy/);
395
428
  });
396
429
 
430
+ test("local CLI emits OpenClaw-compatible JSON for public commands", () => {
431
+ const cliPath = path.join(root, "bin", "coding-agent-skills");
432
+ const fixtureRoot = path.join(root, "tests", "fixtures");
433
+ const githubHandoffFixture = createGitFixture(
434
+ path.join("tests", "fixtures", "github-handoff", "static-project"),
435
+ );
436
+ fs.appendFileSync(path.join(githubHandoffFixture, "README.md"), "\nLocal handoff change.\n");
437
+
438
+ const commands = [
439
+ ["validate-pack"],
440
+ ["validate-adapters", path.join(fixtureRoot, "external-adapters", "valid-basic")],
441
+ [
442
+ "validate-project",
443
+ path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin"),
444
+ ],
445
+ ["repo-map", path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin")],
446
+ ["route-trace", path.join(fixtureRoot, "route-trace", "static-project")],
447
+ ["env-audit", path.join(fixtureRoot, "env-audit", "static-project")],
448
+ ["secret-audit", path.join(fixtureRoot, "secret-audit", "static-project")],
449
+ ["api-contract-audit", path.join(fixtureRoot, "api-contract-audit", "static-project")],
450
+ ["migration-review", path.join(fixtureRoot, "migration-review", "static-project")],
451
+ ["github-handoff", githubHandoffFixture],
452
+ ["deployment-preflight", path.join(fixtureRoot, "deployment-preflight", "static-project")],
453
+ ];
454
+
455
+ for (const args of commands) {
456
+ const result = spawnSync(cliPath, [...args, "--json"], {
457
+ cwd: root,
458
+ encoding: "utf8",
459
+ stdio: "pipe",
460
+ });
461
+ assert.equal(result.status, 0, `${args.join(" ")}\n${result.stderr}`);
462
+ assert.equal(result.stderr, "");
463
+ const parsed = JSON.parse(result.stdout);
464
+ assertOpenClawJsonContract(parsed, args[0]);
465
+ }
466
+
467
+ const partial = spawnSync(
468
+ cliPath,
469
+ [
470
+ "deployment-preflight",
471
+ path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin"),
472
+ "--json",
473
+ ],
474
+ {
475
+ cwd: root,
476
+ encoding: "utf8",
477
+ stdio: "pipe",
478
+ },
479
+ );
480
+ assert.equal(partial.status, 0);
481
+ const parsedPartial = JSON.parse(partial.stdout);
482
+ assertOpenClawJsonContract(parsedPartial, "deployment-preflight");
483
+ assert.equal(parsedPartial.status, "partial");
484
+ assert.ok(parsedPartial.skipped.length > 0);
485
+ assert.ok(parsedPartial.refusedBehavior.includes("no deployments"));
486
+ });
487
+
397
488
  test("npm package metadata is public-ready and dependency-free", () => {
398
489
  const packageJson = readJson("package.json");
399
490
  assert.equal(packageJson.name, "coding-agent-skills");
400
- assert.equal(packageJson.version, "0.2.15");
491
+ assert.equal(packageJson.version, "0.2.16");
401
492
  assert.equal(
402
493
  packageJson.description,
403
494
  "Evidence-first, read-only coding-agent skills and project adapter tooling.",
@@ -687,8 +687,8 @@ if (packageJson) {
687
687
  if (packageJson.name !== "coding-agent-skills") {
688
688
  failures.push("package.json has unexpected package name");
689
689
  }
690
- if (packageJson.version !== "0.2.15") {
691
- failures.push("package.json version must be 0.2.15 for public package validation");
690
+ if (packageJson.version !== "0.2.16") {
691
+ failures.push("package.json version must be 0.2.16 for public package validation");
692
692
  }
693
693
  if (packageJson.type !== "module") failures.push("package.json must preserve ESM mode");
694
694
  if (packageJson.private !== false) {
package/work-ledger.md CHANGED
@@ -10,8 +10,10 @@
10
10
  - First external project-owned adapter adoption completed for `/home/oneclickwebsitedesignfactory/tax-lien-platform` at candidate commit `c548b1a6cbb3455a70b89d0e301e22435bfccac9`.
11
11
  - The adopted adapter is `repo-map` only, docs/metadata-only, and contains no commands, runtime checks, build/test/package behavior, platform/deployment behavior, or secret-aware behavior.
12
12
  - The shared repository does not contain real adapter manifests; real project adapters remain owned by their project repositories.
13
- - Public npm package release `v0.2.15` exposes the dependency-free
13
+ - Public npm package release `v0.2.16` exposes the dependency-free
14
14
  `coding-agent-skills` CLI under MIT license.
15
+ - `v0.2.16` adds optional OpenClaw-compatible `--json` output and documented
16
+ exit-code semantics for every public CLI command.
15
17
  - `route-trace` is implemented as an audit-only static route tracing skill.
16
18
  - `env-audit` is implemented as an audit-only value-free environment variable name mapping
17
19
  skill.
@@ -28,14 +30,14 @@
28
30
 
29
31
  ## Last Completed Version
30
32
 
31
- `v0.2.15`
33
+ `v0.2.16`
32
34
 
33
35
  ## Current Recommended Milestone
34
36
 
35
- The `deployment-preflight` public npm release is in progress under builder-mode approval.
36
- Continue the remaining read-only skill wave one release at a time unless a real safety,
37
- validation, publication, or authentication boundary appears. The next approved wave item is
38
- `cloudflare-preflight-skill`.
37
+ The OpenClaw-compatible JSON output contract is the current release milestone. After
38
+ `v0.2.16` is published and verified, continue the remaining read-only skill wave one
39
+ release at a time unless a real safety, validation, publication, or authentication
40
+ boundary appears. The next approved wave item remains `cloudflare-preflight-skill`.
39
41
 
40
42
  ## Allowed Next Actions
41
43
 
@@ -122,9 +124,20 @@ No autonomous maintainer-loop run has been recorded yet.
122
124
  - Implemented milestone: `deployment-preflight` audit-only static deployment readiness
123
125
  evidence skill and CLI command.
124
126
  - Required permission: `builder-mode-skill-implementation`
125
- - Validation result: pass pending final publication evidence
127
+ - Validation result: passed; `v0.2.15` commit, tag, npm publication, registry smoke,
128
+ npm exec, and GitHub Release completed
129
+ - Next recommended milestone: implement the OpenClaw-compatible JSON output and exit-code
130
+ contract before continuing to `cloudflare-preflight-skill`.
131
+
132
+ ### 2026-07-03T15:00:00Z
133
+
134
+ - Latest tag observed: `v0.2.15`
135
+ - Implemented milestone: OpenClaw-compatible optional `--json` output and documented
136
+ exit-code semantics for every public CLI command.
137
+ - Required permission: `orchestrator-output-contract`
138
+ - Validation result: pass pending final release validation matrix
126
139
  - Next recommended milestone: continue builder-mode wave with `cloudflare-preflight-skill`
127
- after `v0.2.15` publication completes.
140
+ after `v0.2.16` publication completes.
128
141
 
129
142
  ### 2026-07-03T12:00:00Z
130
143