coding-agent-skills 0.2.15 → 0.2.17

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,35 @@
2
2
 
3
3
  All notable changes follow [Semantic Versioning](docs/versioning/README.md).
4
4
 
5
+ ## [0.2.17] - 2026-07-04
6
+
7
+ ### Changed
8
+
9
+ - `repo-map` now supports generic safe discovery when no `.coding-agent` project declaration exists.
10
+ - JSON output now reports `adapterPresent: false`, `mode: generic-safe-discovery`, reduced confidence, `changedState: false`, no target-project commands, and no secret-file reads for no-adapter repo-map runs.
11
+ - Invalid or unsafe project adapters still fail closed instead of falling back silently.
12
+ - Usage, adapter, and release documentation now clarify that adapters are optional hints, not mandatory requirements.
13
+
14
+ ## [0.2.16] - 2026-07-03
15
+
16
+ ### Added
17
+
18
+ - Optional `--json` output for every public `coding-agent-skills` CLI command.
19
+ - OpenClaw-compatible structured result fields for command identity, skill id, package
20
+ version, status, findings, warnings, skipped checks, refused behavior, safety summary,
21
+ `recommendedNextAction`, and exit-code meaning.
22
+ - Release tests that validate the JSON contract across the public CLI surface and confirm
23
+ redaction of local home paths and secret-like values.
24
+
25
+ ### Changed
26
+
27
+ - The public wrapper now preserves default human-readable output while offering sanitized
28
+ machine-readable results for orchestrator callers.
29
+ - Usage, release, testing, roadmap, ledger, and run-log docs now describe the exit-code
30
+ contract and OpenClaw integration boundary.
31
+ - Stale v0.2.15 ledger and run-log entries now reflect that the deployment-preflight
32
+ release was published, smoke-tested, and released.
33
+
5
34
  ## [0.2.15] - 2026-07-03
6
35
 
7
36
  ### Added
package/README.md CHANGED
@@ -52,12 +52,16 @@ 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.
58
61
  - Review [project-owned installation and pinning](docs/adapters/project-installation.md).
59
62
  - Run `node scripts/validate-project-adapters.mjs <project-root>` for a declared project root.
60
- - Render adapter-aware `repo-map` orientation with
63
+ - Render repo-map orientation with optional adapter hints and generic safe
64
+ discovery when no adapter is present:
61
65
  `node scripts/render-adapter-repo-map.mjs <project-root>`.
62
66
  - Render a static route-trace report with
63
67
  `node scripts/render-route-trace.mjs <project-root>`.
@@ -92,6 +96,35 @@ Every skill emits the evidence-pack contract. A command being attempted is never
92
96
  Governance lives in [CONTRIBUTING.md](CONTRIBUTING.md), [ROADMAP.md](ROADMAP.md), and the [release policy](docs/release/README.md).
93
97
  The [harness guide](docs/testing/README.md) explains trigger, command, mutation, privacy, adapter, and completion checks.
94
98
 
99
+ ## Orchestrator Output
100
+
101
+ The default CLI output remains human-readable. OpenClaw-style callers can request a
102
+ structured result with `--json`:
103
+
104
+ ```bash
105
+ coding-agent-skills repo-map /path/to/project --json
106
+ ```
107
+
108
+ JSON output is read-only and sanitized. It includes command identity, package version,
109
+ skill id, status, findings, warnings, skipped checks, refused behavior, safety flags,
110
+ and `recommendedNextAction`. Exit codes follow the public contract:
111
+
112
+ - `0`: handled execution path, including complete, partial, blocked, or controlled audit results
113
+ - `2`: usage error
114
+ - `3`: safety refusal
115
+ - `4`: missing required input or file
116
+ - `5`: unexpected internal or runtime failure
117
+
118
+ OpenClaw should remain the owner of memory, routing, permissions, scheduling, user
119
+ interaction, and workflow state. `coding-agent-skills` is a safe callable evidence
120
+ producer, not an orchestrator.
121
+
122
+ Adapters are optional hints, not a prerequisite for safe orientation. `repo-map`
123
+ falls back to `generic-safe-discovery` when no `.coding-agent` declaration exists,
124
+ marks `adapterPresent: false`, reduces confidence, and still refuses target project
125
+ builds, tests, runtime checks, deploys, migrations, package installs, and secret-file
126
+ reads.
127
+
95
128
  ## Autonomous Maintainer Loop
96
129
 
97
130
  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 reported repo boundaries",
46
+ reason: "Use the reported docs, safe paths, ignored paths, adapter status, confidence, 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
  }
@@ -35,6 +35,12 @@ ignored paths, required evidence, package-manager hints, repository bounds, and
35
35
  Git branch state. It does not read target project file contents, run project tests or
36
36
  builds, install packages, perform runtime checks, deploy, migrate, or read `.env` files.
37
37
 
38
+ Adapters are optional hints, not mandatory gates. If no `.coding-agent` declaration is
39
+ present, `repo-map` falls back to `generic-safe-discovery`, reports `adapterPresent: false`,
40
+ uses reduced confidence, applies built-in ignored paths, and still refuses target project
41
+ builds, tests, runtime checks, deployments, migrations, package installs, and secret-file
42
+ reads. Invalid or weakening adapters still fail closed.
43
+
38
44
  This is agent context for safer repository understanding. It is not target-application
39
45
  product behavior.
40
46
 
@@ -109,6 +109,12 @@ The renderer is metadata-only. It does not read target project file contents, ru
109
109
  project tests, run builds, install packages, perform runtime checks, deploy, migrate, read
110
110
  `.env` files, or modify project state.
111
111
 
112
+ Project adapters are optional hints. If no project declaration exists, `repo-map` still
113
+ runs a bounded `generic-safe-discovery` report with `adapterPresent: false`, reduced
114
+ confidence, built-in ignored paths, and explicit no-secret/no-build/no-runtime safety
115
+ warnings. A declared adapter that is invalid, unsafe, or incompatible remains a hard
116
+ failure.
117
+
112
118
  A project-owned adapter can also enable read-only `route-trace` context:
113
119
 
114
120
  ```bash
@@ -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.17`.
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,
@@ -41,16 +41,18 @@ Select the least-privileged skill that matches the request:
41
41
 
42
42
  Every skill emits an evidence pack. Read `status`, skipped checks, failures, confidence, and changed state before relying on a completion claim.
43
43
 
44
- When a project owns a compatible adapter, render read-only adapter-aware `repo-map` context
45
- with:
44
+ Render read-only `repo-map` context with:
46
45
 
47
46
  ```bash
48
47
  node scripts/render-adapter-repo-map.mjs <project-root>
49
48
  ```
50
49
 
51
- This validates the project adapter first, then reports adapter-declared documentation
52
- precedence, safe read paths, ignored paths, and required evidence. It is not a build,
53
- test, runtime, deployment, migration, package-install, or secret-reading flow.
50
+ When a project owns a compatible adapter, this validates the adapter first and reports
51
+ adapter-declared documentation precedence, safe read paths, ignored paths, and required
52
+ evidence. When no `.coding-agent` declaration exists, adapters remain optional: `repo-map`
53
+ uses `generic-safe-discovery`, reports `adapterPresent: false`, reduces confidence, and
54
+ still refuses builds, tests, runtime checks, deployments, migrations, package installs, and
55
+ secret-file reads.
54
56
 
55
57
  See [examples](../../examples/README.md) for safe concrete inputs and outputs.
56
58
 
@@ -85,8 +87,8 @@ npx coding-agent-skills validate-pack
85
87
  ```
86
88
 
87
89
  These commands wrap the same validated scripts shipped in the repository. `repo-map`
88
- validates the project adapter first, then renders adapter-declared documentation
89
- precedence, safe read paths, ignored paths, and required evidence.
90
+ uses adapter metadata when present and valid; otherwise it falls back to generic safe
91
+ discovery with reduced confidence and clear adapter-absence warnings.
90
92
  `route-trace` validates a project adapter when present, uses adapter-declared safe paths
91
93
  when enabled, and statically reports verified route files, inferred route declarations,
92
94
  skipped items, and not-verified runtime-dependent route classes.
@@ -117,6 +119,50 @@ The installed CLI does not run target project builds or tests, perform runtime c
117
119
  deploy, migrate, mutate services or processes, or read `.env` files. Project adapters
118
120
  narrow context; they do not grant additional power or weaken shared restrictions.
119
121
 
122
+ ## Machine-Readable Output
123
+
124
+ Every public CLI command accepts optional `--json` for OpenClaw-style tool callers:
125
+
126
+ ```bash
127
+ coding-agent-skills repo-map /path/to/project --json
128
+ coding-agent-skills validate-pack --json
129
+ ```
130
+
131
+ The default human-readable output is unchanged. JSON output is sanitized and includes:
132
+
133
+ - `success`
134
+ - `status`
135
+ - `tool`
136
+ - `command`
137
+ - `skillId`
138
+ - `packageVersion`
139
+ - `mode`
140
+ - `changedState`
141
+ - `summary`
142
+ - `findings`
143
+ - `warnings`
144
+ - `risks`
145
+ - `skipped`
146
+ - `notVerified`
147
+ - `refusedBehavior`
148
+ - `adapter`
149
+ - `recommendedNextAction`
150
+ - `safety`
151
+ - `exitCode`
152
+ - `exitCodeMeaning`
153
+
154
+ Exit-code semantics:
155
+
156
+ - `0`: handled execution path, including complete, partial, blocked, or controlled audit result
157
+ - `2`: usage error
158
+ - `3`: safety refusal
159
+ - `4`: missing required input or file
160
+ - `5`: unexpected internal or runtime failure
161
+
162
+ OpenClaw or another orchestrator should own memory, routing, permissions, scheduling,
163
+ chat/user interaction, and workflow state. This package remains a read-only external tool
164
+ and evidence producer.
165
+
120
166
  ## Local Command Surface
121
167
 
122
168
  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.17",
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.
@@ -30,6 +30,87 @@ const REFUSED_BEHAVIOR = [
30
30
  "no project writes",
31
31
  ];
32
32
 
33
+ const GENERIC_IGNORED_PATHS = [
34
+ ".git",
35
+ ".env",
36
+ ".env.*",
37
+ ".next",
38
+ ".nuxt",
39
+ ".output",
40
+ ".turbo",
41
+ ".cache",
42
+ "node_modules",
43
+ "dist",
44
+ "build",
45
+ "coverage",
46
+ "validation-output",
47
+ "supabase/.temp",
48
+ "secrets",
49
+ "tokens",
50
+ "credentials",
51
+ "private",
52
+ ];
53
+
54
+ const GENERIC_DOCUMENTATION_PRECEDENCE = [
55
+ "README.md",
56
+ "docs/README.md",
57
+ "docs/kb/README.md",
58
+ "AGENTS.md",
59
+ "CLAUDE.md",
60
+ "RUNBOOK.md",
61
+ "CONTRIBUTING.md",
62
+ ];
63
+
64
+ const GENERIC_SAFE_READ_PATHS = [
65
+ "README.md",
66
+ "docs",
67
+ "app",
68
+ "apps",
69
+ "src",
70
+ "lib",
71
+ "pages",
72
+ "components",
73
+ "server",
74
+ "api",
75
+ "routes",
76
+ "services",
77
+ "packages",
78
+ "schemas",
79
+ "scripts",
80
+ "tests",
81
+ "package.json",
82
+ "tsconfig.json",
83
+ "vite.config.ts",
84
+ "vite.config.js",
85
+ "next.config.ts",
86
+ "next.config.js",
87
+ ];
88
+
89
+ const GENERIC_ROOT_MARKERS = [
90
+ "README.md",
91
+ "package.json",
92
+ "pnpm-lock.yaml",
93
+ "package-lock.json",
94
+ "yarn.lock",
95
+ "bun.lockb",
96
+ "tsconfig.json",
97
+ "vite.config.ts",
98
+ "vite.config.js",
99
+ "next.config.ts",
100
+ "next.config.js",
101
+ "pyproject.toml",
102
+ "Cargo.toml",
103
+ "go.mod",
104
+ ];
105
+
106
+ const PACKAGE_MANAGER_MARKERS = [
107
+ ["pnpm-lock.yaml", "pnpm"],
108
+ ["package-lock.json", "npm"],
109
+ ["yarn.lock", "yarn"],
110
+ ["bun.lockb", "bun"],
111
+ ["package.json", "npm-compatible"],
112
+ ];
113
+
33
114
  function inside(root, candidate) {
34
115
  const relative = path.relative(root, candidate);
35
116
  return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
@@ -90,6 +171,28 @@ function describeApprovedPath(projectRoot, relativePath) {
90
171
  };
91
172
  }
92
173
 
174
+ function existingPathRecords(projectRoot, candidates) {
175
+ return candidates
176
+ .map((relativePath) => describeApprovedPath(projectRoot, relativePath))
177
+ .filter((record) => record.status === "present");
178
+ }
179
+
180
+ function genericRootMarkers(projectRoot) {
181
+ return GENERIC_ROOT_MARKERS
182
+ .map((relativePath) => describeApprovedPath(projectRoot, relativePath))
183
+ .filter((record) => record.status === "present")
184
+ .map((record) => ({
185
+ kind: record.type,
186
+ path: record.path,
187
+ }));
188
+ }
189
+
190
+ function genericPackageManagers(projectRoot) {
191
+ return PACKAGE_MANAGER_MARKERS
192
+ .filter(([relativePath]) => describeApprovedPath(projectRoot, relativePath).status === "present")
193
+ .map(([, manager]) => manager);
194
+ }
195
+
93
196
  function gitSummary(projectRoot) {
94
197
  const summary = {
95
198
  root: null,
@@ -174,30 +277,92 @@ function loadRepoMapAdapters(loaded) {
174
277
 
175
278
  export function buildAdapterRepoMapReport(projectRootInput, options = {}) {
176
279
  const coreRoot = path.resolve(options.coreRoot ?? DEFAULT_CORE_ROOT);
177
- const validation = validateProjectAdapters(projectRootInput, { coreRoot });
178
- if (!validation.ok) {
280
+ const loaded = readProjectAdapterDeclaration(projectRootInput);
281
+ if (!loaded.ok) {
282
+ if (loaded.codes.length === 1 && loaded.codes[0] === "missing-project-declaration") {
283
+ const projectRoot = fs.realpathSync(path.resolve(projectRootInput));
284
+ const git = gitSummary(projectRoot);
285
+ return {
286
+ ok: true,
287
+ status: "complete",
288
+ coreVersion: PILOT_VERSION,
289
+ projectRoot,
290
+ declarationPath: "not present",
291
+ adapterRoot: "not present",
292
+ projectId: "not declared",
293
+ adapterIds: [],
294
+ manifestPaths: [],
295
+ enabledSkills: ["repo-map"],
296
+ adapter: {
297
+ present: false,
298
+ enabled: false,
299
+ mode: "generic-safe-discovery",
300
+ codes: ["missing-project-declaration"],
301
+ },
302
+ adapterPresent: false,
303
+ mode: "generic-safe-discovery",
304
+ confidence: "reduced",
305
+ rootMarkers: genericRootMarkers(projectRoot),
306
+ maximumDepth: 2,
307
+ scope: "generic-project-root",
308
+ requireApprovalOutsideScope: true,
309
+ documentationPrecedence: existingPathRecords(projectRoot, GENERIC_DOCUMENTATION_PRECEDENCE),
310
+ safeReadPaths: existingPathRecords(projectRoot, GENERIC_SAFE_READ_PATHS),
311
+ ignoredPaths: GENERIC_IGNORED_PATHS,
312
+ requiredEvidence: [
313
+ "repository root markers",
314
+ "bounded generic file/directory inventory",
315
+ "adapter absence evidence",
316
+ ],
317
+ packageManagers: unique(genericPackageManagers(projectRoot)),
318
+ git,
319
+ warnings: unique([
320
+ ...git.warnings,
321
+ "adapterPresent: false",
322
+ "mode: generic-safe-discovery",
323
+ "reduced confidence; adapter-provided project intent is unavailable",
324
+ "no project adapter declaration found; repo-map used generic bounded discovery",
325
+ "no target project build, test, runtime, deployment, migration, package, or secret-reading command was performed",
326
+ "no secrets read",
327
+ ]),
328
+ refusedBehavior: REFUSED_BEHAVIOR,
329
+ validation: {
330
+ ok: false,
331
+ status: "failed",
332
+ acceptedAdapters: 0,
333
+ acceptedSkills: [],
334
+ codes: ["missing-project-declaration"],
335
+ },
336
+ };
337
+ }
179
338
  return {
180
339
  ok: false,
181
340
  status: "failed",
182
- codes: validation.codes,
183
- validation,
341
+ codes: loaded.codes,
342
+ validation: {
343
+ ok: false,
344
+ status: "failed",
345
+ acceptedAdapters: 0,
346
+ acceptedSkills: [],
347
+ codes: loaded.codes,
348
+ },
184
349
  };
185
350
  }
186
- if (!validation.acceptedSkills.includes("repo-map")) {
351
+
352
+ const validation = validateProjectAdapters(loaded.projectRoot, { coreRoot });
353
+ if (!validation.ok) {
187
354
  return {
188
355
  ok: false,
189
356
  status: "failed",
190
- codes: ["repo-map-not-enabled"],
357
+ codes: validation.codes,
191
358
  validation,
192
359
  };
193
360
  }
194
-
195
- const loaded = readProjectAdapterDeclaration(projectRootInput);
196
- if (!loaded.ok) {
361
+ if (!validation.acceptedSkills.includes("repo-map")) {
197
362
  return {
198
363
  ok: false,
199
364
  status: "failed",
200
- codes: loaded.codes,
365
+ codes: ["repo-map-not-enabled"],
201
366
  validation,
202
367
  };
203
368
  }
@@ -246,6 +411,15 @@ export function buildAdapterRepoMapReport(projectRootInput, options = {}) {
246
411
  adapterIds: adapters.map((adapter) => adapter.manifest.adapterId).sort(),
247
412
  manifestPaths: adapters.map((adapter) => adapter.manifestPath).sort(),
248
413
  enabledSkills: ["repo-map"],
414
+ adapter: {
415
+ present: true,
416
+ enabled: true,
417
+ mode: "adapter-limited",
418
+ codes: [],
419
+ },
420
+ adapterPresent: true,
421
+ mode: "adapter-limited",
422
+ confidence: "adapter-declared",
249
423
  rootMarkers,
250
424
  maximumDepth,
251
425
  scope: "declared-project-root",
@@ -294,10 +468,16 @@ export function renderAdapterRepoMapReport(report) {
294
468
  `Project root: ${redactSensitiveText(report.projectRoot)}`,
295
469
  `Declaration: ${report.declarationPath}`,
296
470
  `Adapter root: ${report.adapterRoot}`,
297
- `Adapter IDs: ${report.adapterIds.join(", ")}`,
298
- `Adapter manifests: ${report.manifestPaths.join(", ")}`,
471
+ `Adapter IDs: ${report.adapterIds.length ? report.adapterIds.join(", ") : "none"}`,
472
+ `Adapter manifests: ${report.manifestPaths.length ? report.manifestPaths.join(", ") : "none"}`,
299
473
  `Enabled skills: ${report.enabledSkills.join(", ")}`,
300
474
  "",
475
+ "## Adapter Scope",
476
+ `- Adapter present: ${report.adapterPresent ? "yes" : "no"}`,
477
+ `- Repo-map enabled: ${report.adapter?.enabled ? "yes" : "generic fallback"}`,
478
+ `- Mode: ${report.mode}`,
479
+ `- Confidence: ${report.confidence}`,
480
+ "",
301
481
  "## Git State",
302
482
  `- Git root: ${redactSensitiveText(report.git.root ?? "not detected")}`,
303
483
  `- Branch state: ${redactSensitiveText(report.git.branchState ?? "not detected")}`,
@@ -334,6 +514,8 @@ export function renderAdapterRepoMapReport(report) {
334
514
  ...formatList("Refused Behavior", report.refusedBehavior),
335
515
  "",
336
516
  "No target project build, test, runtime, deployment, migration, package installation, or secret-file read was performed.",
517
+ "No project commands beyond safe repository metadata inspection were performed.",
518
+ "No secrets were read.",
337
519
  ];
338
520
 
339
521
  return lines.join("\n");
@@ -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.17") {
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");
@@ -345,6 +378,10 @@ test("local CLI maps approved commands to existing safe scripts", () => {
345
378
  ["repo-map", path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin")],
346
379
  /# Adapter-Aware Repo Map/,
347
380
  ],
381
+ [
382
+ ["repo-map", path.join(fixtureRoot, "sample-repo")],
383
+ /generic-safe-discovery/,
384
+ ],
348
385
  [
349
386
  ["route-trace", path.join(fixtureRoot, "route-trace", "static-project")],
350
387
  /# Route Trace Report/,
@@ -394,10 +431,69 @@ test("local CLI maps approved commands to existing safe scripts", () => {
394
431
  assert.match(unknown.stderr, /unknown command: deploy/);
395
432
  });
396
433
 
434
+ test("local CLI emits OpenClaw-compatible JSON for public commands", () => {
435
+ const cliPath = path.join(root, "bin", "coding-agent-skills");
436
+ const fixtureRoot = path.join(root, "tests", "fixtures");
437
+ const githubHandoffFixture = createGitFixture(
438
+ path.join("tests", "fixtures", "github-handoff", "static-project"),
439
+ );
440
+ fs.appendFileSync(path.join(githubHandoffFixture, "README.md"), "\nLocal handoff change.\n");
441
+
442
+ const commands = [
443
+ ["validate-pack"],
444
+ ["validate-adapters", path.join(fixtureRoot, "external-adapters", "valid-basic")],
445
+ [
446
+ "validate-project",
447
+ path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin"),
448
+ ],
449
+ ["repo-map", path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin")],
450
+ ["repo-map", path.join(fixtureRoot, "sample-repo")],
451
+ ["route-trace", path.join(fixtureRoot, "route-trace", "static-project")],
452
+ ["env-audit", path.join(fixtureRoot, "env-audit", "static-project")],
453
+ ["secret-audit", path.join(fixtureRoot, "secret-audit", "static-project")],
454
+ ["api-contract-audit", path.join(fixtureRoot, "api-contract-audit", "static-project")],
455
+ ["migration-review", path.join(fixtureRoot, "migration-review", "static-project")],
456
+ ["github-handoff", githubHandoffFixture],
457
+ ["deployment-preflight", path.join(fixtureRoot, "deployment-preflight", "static-project")],
458
+ ];
459
+
460
+ for (const args of commands) {
461
+ const result = spawnSync(cliPath, [...args, "--json"], {
462
+ cwd: root,
463
+ encoding: "utf8",
464
+ stdio: "pipe",
465
+ });
466
+ assert.equal(result.status, 0, `${args.join(" ")}\n${result.stderr}`);
467
+ assert.equal(result.stderr, "");
468
+ const parsed = JSON.parse(result.stdout);
469
+ assertOpenClawJsonContract(parsed, args[0]);
470
+ }
471
+
472
+ const partial = spawnSync(
473
+ cliPath,
474
+ [
475
+ "deployment-preflight",
476
+ path.join(fixtureRoot, "project-adapter-installation", "valid-exact-pin"),
477
+ "--json",
478
+ ],
479
+ {
480
+ cwd: root,
481
+ encoding: "utf8",
482
+ stdio: "pipe",
483
+ },
484
+ );
485
+ assert.equal(partial.status, 0);
486
+ const parsedPartial = JSON.parse(partial.stdout);
487
+ assertOpenClawJsonContract(parsedPartial, "deployment-preflight");
488
+ assert.equal(parsedPartial.status, "partial");
489
+ assert.ok(parsedPartial.skipped.length > 0);
490
+ assert.ok(parsedPartial.refusedBehavior.includes("no deployments"));
491
+ });
492
+
397
493
  test("npm package metadata is public-ready and dependency-free", () => {
398
494
  const packageJson = readJson("package.json");
399
495
  assert.equal(packageJson.name, "coding-agent-skills");
400
- assert.equal(packageJson.version, "0.2.15");
496
+ assert.equal(packageJson.version, "0.2.17");
401
497
  assert.equal(
402
498
  packageJson.description,
403
499
  "Evidence-first, read-only coding-agent skills and project adapter tooling.",
@@ -1678,6 +1774,42 @@ test("adapter-aware repo-map consumes validated project adapter metadata", () =>
1678
1774
  assert.match(rendered, /No target project build, test, runtime, deployment/);
1679
1775
  });
1680
1776
 
1777
+ test("repo-map supports generic safe discovery without a project adapter", () => {
1778
+ const report = buildAdapterRepoMapReport(
1779
+ path.join(root, "tests", "fixtures", "sample-repo"),
1780
+ { coreRoot: root },
1781
+ );
1782
+
1783
+ assert.equal(report.ok, true, report.codes?.join(","));
1784
+ assert.equal(report.adapter.present, false);
1785
+ assert.equal(report.adapter.enabled, false);
1786
+ assert.equal(report.mode, "generic-safe-discovery");
1787
+ assert.equal(report.confidence, "reduced");
1788
+ assert.equal(report.adapterPresent, false);
1789
+ assert.ok(report.validation.codes.includes("missing-project-declaration"));
1790
+ assert.ok(report.warnings.includes("adapterPresent: false"));
1791
+ assert.ok(report.warnings.includes("mode: generic-safe-discovery"));
1792
+ assert.ok(report.warnings.includes("no secrets read"));
1793
+ assert.ok(report.ignoredPaths.includes(".env"));
1794
+ assert.ok(report.ignoredPaths.includes(".env.*"));
1795
+ assert.ok(
1796
+ report.documentationPrecedence.some((record) => record.path === "README.md"),
1797
+ );
1798
+
1799
+ const rendered = renderAdapterRepoMapReport(report);
1800
+ assert.match(rendered, /Adapter present: no/);
1801
+ assert.match(rendered, /Mode: generic-safe-discovery/);
1802
+ assert.match(rendered, /Confidence: reduced/);
1803
+ assert.match(rendered, /No secrets were read/);
1804
+
1805
+ const cli = adapterRepoMapCliResult(path.join(root, "tests", "fixtures", "sample-repo"), {
1806
+ coreRoot: root,
1807
+ });
1808
+ assert.equal(cli.exitCode, 0);
1809
+ assert.equal(cli.stream, "stdout");
1810
+ assert.match(cli.lines.join("\n"), /generic-safe-discovery/);
1811
+ });
1812
+
1681
1813
  test("adapter-aware repo-map fails closed without repo-map compatibility", () => {
1682
1814
  const fixtureRoot = path.join(
1683
1815
  root,
@@ -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.17") {
691
+ failures.push("package.json version must be 0.2.17 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