@iowarp/clio-coder 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-S5R4RR5B.js} +7 -6
  5. package/dist/{agents-72W3BI7I.js → agents-P6DMMVZY.js} +24 -21
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-2DJ2KNFG.js → chunk-2LZI5CAG.js} +133 -13
  10. package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
  11. package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
  12. package/dist/{chunk-4XUGQOHA.js → chunk-35MKKU5R.js} +4 -4
  13. package/dist/{chunk-LM5TQCJZ.js → chunk-3HZ5RWN2.js} +5 -5
  14. package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
  15. package/dist/{chunk-KZWTDYJF.js → chunk-4JUF2NNX.js} +7 -7
  16. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  17. package/dist/chunk-5M54SPOL.js +926 -0
  18. package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
  19. package/dist/{chunk-X6IAEBZR.js → chunk-A2GZF7DC.js} +5 -5
  20. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  21. package/dist/chunk-AOCYTWAV.js +449 -0
  22. package/dist/chunk-BEY543CS.js +258 -0
  23. package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
  24. package/dist/chunk-BPGS2WCQ.js +612 -0
  25. package/dist/{chunk-V6RTAOC2.js → chunk-BRXQQJFP.js} +8 -8
  26. package/dist/chunk-CFGTUFWB.js +67 -0
  27. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  28. package/dist/{chunk-DUYJ5IO6.js → chunk-EDRHSCIE.js} +4 -4
  29. package/dist/{chunk-XBXAASKX.js → chunk-EFADSJET.js} +2 -2
  30. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  31. package/dist/chunk-FYYLNIL5.js +313 -0
  32. package/dist/{chunk-FNTMWMX5.js → chunk-HV5X7OR2.js} +14 -12
  33. package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
  34. package/dist/{chunk-5UFT4SUX.js → chunk-K6WL7QZT.js} +3 -3
  35. package/dist/chunk-K7VKOLQQ.js +15 -0
  36. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  37. package/dist/{chunk-OC7FIQPC.js → chunk-KRPY7NTG.js} +10 -7
  38. package/dist/chunk-LL4KHSZI.js +22 -0
  39. package/dist/{chunk-DSELYM6W.js → chunk-MEQ45TQ4.js} +15 -9
  40. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  41. package/dist/{chunk-6SGHMWE3.js → chunk-N4CZJQRK.js} +5 -5
  42. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  43. package/dist/chunk-OZNBF4L3.js +23 -0
  44. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  45. package/dist/chunk-QQK64KLB.js +1360 -0
  46. package/dist/{chunk-SRDMMSEP.js → chunk-QQL5RT5M.js} +979 -1619
  47. package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
  48. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  49. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  50. package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
  51. package/dist/chunk-TW3WDMVS.js +677 -0
  52. package/dist/chunk-TZSKNMZG.js +434 -0
  53. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  54. package/dist/{chunk-PIWWS5BL.js → chunk-UZHIZC5S.js} +7 -7
  55. package/dist/{chunk-UFIIWP2H.js → chunk-VAWWTKDP.js} +8 -8
  56. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  57. package/dist/{chunk-LW6DSM3M.js → chunk-VMNQ6OZA.js} +98 -202
  58. package/dist/chunk-VSNATDE6.js +122 -0
  59. package/dist/chunk-W6GROXXM.js +69 -0
  60. package/dist/chunk-WPQLXFOZ.js +375 -0
  61. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  62. package/dist/{chunk-PAJK6MAQ.js → chunk-X6COSD2O.js} +5 -5
  63. package/dist/chunk-ZGVHUX3M.js +66 -0
  64. package/dist/{chunk-LWLEKMDQ.js → chunk-ZYKPLLNQ.js} +510 -547
  65. package/dist/cli/index.js +27 -23
  66. package/dist/{clio-JOU4FXVA.js → clio-J5JIOIDS.js} +7 -6
  67. package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
  68. package/dist/{config-XCDVKR23.js → config-OEBMIN2U.js} +37 -27
  69. package/dist/{configure-4GAP54ZW.js → configure-PUQOSIXQ.js} +16 -13
  70. package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
  71. package/dist/{context-4UOGGLQ5.js → context-MGSE4Z2T.js} +33 -23
  72. package/dist/{context-77FM5DV5.js → context-URSXPBCK.js} +17 -9
  73. package/dist/{context-clear-XXJRLCJJ.js → context-clear-KDAJRNUK.js} +33 -23
  74. package/dist/context-working-set-SBKMPPI2.js +1552 -0
  75. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-MSWN72NK.js} +43 -29
  76. package/dist/{doctor-HR46URBJ.js → doctor-7BSE27PJ.js} +10 -10
  77. package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
  78. package/dist/{evidence-6HG2PY2B.js → evidence-SR7WXB5B.js} +51 -23
  79. package/dist/{evolve-K7YU3NCY.js → evolve-K7VE2CBX.js} +30 -20
  80. package/dist/{fleet-VY3HHKN6.js → fleet-7XMJNQNF.js} +48 -38
  81. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
  82. package/dist/{init-JYGXI3FK.js → init-JGNPAYXT.js} +41 -31
  83. package/dist/{memory-WFZMGYHX.js → memory-4ALKDJ4Q.js} +32 -22
  84. package/dist/{models-I5QWSEOM.js → models-ZMMLFJNN.js} +22 -19
  85. package/dist/{monitor-GE4ID3IA.js → monitor-2F3T5KHP.js} +55 -43
  86. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-ORHT43JB.js} +572 -388
  87. package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
  88. package/dist/{run-ZU3QMZPZ.js → run-RF4WJGMT.js} +51 -41
  89. package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
  90. package/dist/{skills-X5VXCRNQ.js → skills-PSACKC5Q.js} +2 -2
  91. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-WJSI55RZ.js} +34 -24
  92. package/dist/{targets-SNCPI2NR.js → targets-PIIRAOYS.js} +23 -20
  93. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-ULWXWNVY.js} +4 -3
  94. package/dist/{upgrade-JQHHPQ4K.js → upgrade-346TZ6AV.js} +18 -17
  95. package/dist/{usage-OR4O5SMZ.js → usage-6KKXR32N.js} +34 -24
  96. package/dist/verifiers-4UUM6TEE.js +1214 -0
  97. package/dist/verify-X5HDROLA.js +25 -0
  98. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-7STOCIFZ.js} +42 -31
  99. package/dist/worker/entry.js +33 -24
  100. package/docs/README.md +7 -6
  101. package/docs/acp.md +1 -1
  102. package/docs/alcf-provider.md +1 -1
  103. package/docs/architecture.md +2 -2
  104. package/docs/artifact-versions.md +1 -1
  105. package/docs/built-in-agents.md +1 -1
  106. package/docs/capacity-and-scheduling.md +1 -1
  107. package/docs/commands-and-modes.md +53 -21
  108. package/docs/config-knobs-audit.md +1 -2
  109. package/docs/configuration-and-targets.md +15 -1
  110. package/docs/context-engine.md +64 -12
  111. package/docs/context-working-set.md +194 -0
  112. package/docs/development-pipeline.md +1 -1
  113. package/docs/documentation-coverage.md +5 -5
  114. package/docs/documentation-guide.md +6 -5
  115. package/docs/environment-variables.md +2 -1
  116. package/docs/eval-runner.md +1 -1
  117. package/docs/evals-internal.md +14 -1
  118. package/docs/evidence-and-memory.md +74 -2
  119. package/docs/evolution.md +1 -1
  120. package/docs/exit-codes-and-output.md +1 -1
  121. package/docs/extensions-and-sharing.md +2 -2
  122. package/docs/fleet-dispatch.md +22 -7
  123. package/docs/glossary.md +21 -1
  124. package/docs/installation-and-lifecycle.md +2 -2
  125. package/docs/middleware-and-components.md +1 -1
  126. package/docs/model-catalog.md +7 -9
  127. package/docs/observability.md +4 -4
  128. package/docs/proactive-memory.md +1 -1
  129. package/docs/prompt-envelope-and-tools.md +4 -4
  130. package/docs/provider-adapter-cookbook.md +1 -1
  131. package/docs/release-cut-checklist.md +31 -31
  132. package/docs/safety-model.md +23 -4
  133. package/docs/scientific-validation.md +21 -3
  134. package/docs/session-lifecycle.md +3 -3
  135. package/docs/skills-marketplace.md +1 -1
  136. package/docs/tool-usage.md +79 -12
  137. package/docs/trace-store.md +1 -1
  138. package/docs/troubleshooting.md +1 -1
  139. package/docs/tui-design.md +1 -1
  140. package/docs/worker-dispatch-mechanics.md +11 -1
  141. package/package.json +8 -11
  142. package/skills/meta/clio-test/SKILL.md +20 -17
  143. package/skills/meta/clio-test/evals.md +3 -3
  144. package/skills/meta/clio-test/references/harness.md +35 -6
  145. package/skills/meta/clio-test/references/test-map.md +20 -10
  146. package/skills/registry.yaml +2 -2
  147. package/skills/skill-marketplace.json +1 -1
  148. package/src/cli/context-working-set.ts +513 -0
  149. package/src/cli/context.ts +8 -0
  150. package/src/cli/evidence.ts +20 -2
  151. package/src/cli/index.ts +4 -0
  152. package/src/cli/verifiers.ts +325 -0
  153. package/src/core/bash-exec.ts +39 -14
  154. package/src/core/bus-events.ts +19 -4
  155. package/src/core/config.ts +54 -0
  156. package/src/core/defaults.ts +50 -3
  157. package/src/core/verification-scripts.ts +6 -0
  158. package/src/domains/agents/builtins/verifier.md +3 -0
  159. package/src/domains/config/classify.ts +1 -0
  160. package/src/domains/context/working-set/contract.ts +161 -0
  161. package/src/domains/context/working-set/defaults.ts +28 -0
  162. package/src/domains/context/working-set/engine.ts +203 -0
  163. package/src/domains/context/working-set/fold.ts +62 -0
  164. package/src/domains/context/working-set/horizon.ts +38 -0
  165. package/src/domains/context/working-set/marker.ts +103 -0
  166. package/src/domains/context/working-set/path-index.ts +436 -0
  167. package/src/domains/context/working-set/payload.ts +152 -0
  168. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  169. package/src/domains/context/working-set/policies/index.ts +21 -0
  170. package/src/domains/context/working-set/policies/structural.ts +160 -0
  171. package/src/domains/context/working-set/project.ts +132 -0
  172. package/src/domains/context/working-set/protect.ts +109 -0
  173. package/src/domains/context/working-set/recall.ts +177 -0
  174. package/src/domains/context/working-set/replay/controls.ts +112 -0
  175. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  176. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  177. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  178. package/src/domains/context/working-set/replay/report.ts +139 -0
  179. package/src/domains/context/working-set/replay/runner.ts +325 -0
  180. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  181. package/src/domains/context/working-set/replay/trace.ts +21 -0
  182. package/src/domains/context/working-set/visible.ts +54 -0
  183. package/src/domains/evidence/build.ts +112 -45
  184. package/src/domains/evidence/eval.ts +24 -7
  185. package/src/domains/evidence/index.ts +53 -0
  186. package/src/domains/evidence/ordering.ts +12 -0
  187. package/src/domains/evidence/run-trust.ts +221 -0
  188. package/src/domains/evidence/store.ts +46 -6
  189. package/src/domains/evidence/trust-status.ts +854 -0
  190. package/src/domains/evidence/types.ts +26 -0
  191. package/src/domains/middleware/memory-intervention.ts +3 -0
  192. package/src/domains/middleware/stalled-turn.ts +165 -4
  193. package/src/domains/safety/autonomy.ts +1 -1
  194. package/src/domains/safety/default-path-policy.ts +8 -0
  195. package/src/domains/safety/finish-contract.ts +4 -3
  196. package/src/domains/safety/policy-engine.ts +48 -6
  197. package/src/domains/session/compaction/compact.ts +23 -1
  198. package/src/domains/session/compaction/cut-point.ts +2 -0
  199. package/src/domains/session/compaction/tokens.ts +16 -1
  200. package/src/domains/session/context-ledger.ts +2 -0
  201. package/src/domains/session/entries.ts +107 -1
  202. package/src/domains/session/manager.ts +9 -2
  203. package/src/domains/session/migrations/index.ts +22 -3
  204. package/src/engine/acp/server.ts +3 -0
  205. package/src/engine/agent.ts +18 -1
  206. package/src/engine/session.ts +9 -3
  207. package/src/entry/orchestrator.ts +16 -4
  208. package/src/interactive/chat-loop-messages.ts +18 -6
  209. package/src/interactive/chat-panel.ts +17 -1
  210. package/src/interactive/chat-renderer.ts +30 -21
  211. package/src/interactive/context-meter.ts +10 -0
  212. package/src/interactive/context-overlay.ts +81 -6
  213. package/src/interactive/context-recall-command.ts +110 -0
  214. package/src/interactive/interactive-slash-runtime.ts +37 -1
  215. package/src/interactive/model-session-replay.ts +21 -0
  216. package/src/interactive/overlay-general-openers.ts +6 -0
  217. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  218. package/src/interactive/renderers/tool-execution.ts +18 -2
  219. package/src/interactive/session-transcript.ts +2 -2
  220. package/src/interactive/slash-commands.ts +29 -2
  221. package/src/interactive/turn-context.ts +238 -88
  222. package/src/interactive/turn-middleware.ts +6 -6
  223. package/src/tools/agent-tools.ts +11 -4
  224. package/src/tools/bash.ts +144 -82
  225. package/src/tools/builtin-tool-catalog.ts +11 -5
  226. package/src/tools/context/index.ts +105 -3
  227. package/src/tools/context/surface.ts +3 -2
  228. package/src/tools/core-bootstrap.ts +21 -0
  229. package/src/tools/dispatch-runner.ts +9 -7
  230. package/src/tools/monitor.ts +28 -20
  231. package/src/tools/registry.ts +59 -7
  232. package/src/tools/result-disposition.ts +550 -0
  233. package/src/tools/result-shaping.ts +262 -19
  234. package/src/tools/safe-exec.ts +2 -0
  235. package/src/tools/verify/authoring.ts +1119 -0
  236. package/src/tools/verify/catalog.ts +346 -0
  237. package/src/tools/verify/index.ts +13 -3
  238. package/src/tools/verify/scripts.ts +135 -37
  239. package/src/tools/verify/surface.ts +9 -5
  240. package/src/tools/worker-evidence.ts +35 -12
  241. package/dist/chunk-J7CWMCQD.js +0 -255
  242. package/dist/chunk-T6YILFSB.js +0 -80
  243. package/dist/chunk-VAKQQHWR.js +0 -434
  244. package/dist/chunk-VPAYEGVX.js +0 -184
@@ -0,0 +1,346 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { parseDocument } from "yaml";
4
+ import { resolveSafeCwd, SAFE_EXEC_DEFAULT_TIMEOUT_MS } from "../../core/safe-exec.js";
5
+ import { isProjectVerifierCheckId } from "../../core/verification-scripts.js";
6
+ import { compareCodepoints } from "../../domains/evidence/ordering.js";
7
+
8
+ export const PROJECT_VERIFIER_CATALOG_RELATIVE_PATH = ".clio-coder/verifiers.yaml";
9
+ export const PROJECT_VERIFIER_CATALOG_VERSION = 1;
10
+
11
+ /** Public schema limits. Diagnostics cite these values instead of hiding policy. */
12
+ export const PROJECT_VERIFIER_CATALOG_CAPS = Object.freeze({
13
+ fileBytes: 256 * 1024,
14
+ checks: 128,
15
+ idBytes: 64,
16
+ descriptionBytes: 512,
17
+ argvEntries: 64,
18
+ argumentBytes: 4096,
19
+ cwdBytes: 512,
20
+ timeoutMs: 900_000,
21
+ tags: 16,
22
+ tagBytes: 32,
23
+ });
24
+
25
+ export type DeclaredCheckSourceKind = "package.json" | "project-catalog";
26
+
27
+ export interface DeclaredCheckSourceRef {
28
+ kind: DeclaredCheckSourceKind;
29
+ path: string;
30
+ }
31
+
32
+ /** Canonical projection shared by built-in and project check providers. */
33
+ export interface DeclaredCheck {
34
+ id: string;
35
+ description: string;
36
+ command: string[];
37
+ cwd: string;
38
+ timeoutMs: number;
39
+ tags: string[];
40
+ source: DeclaredCheckSourceRef;
41
+ }
42
+
43
+ export interface DeclaredCheckSource extends DeclaredCheckSourceRef {
44
+ checks: DeclaredCheck[];
45
+ }
46
+
47
+ export type ProjectCatalogLoadResult = { ok: true; source: DeclaredCheckSource | null } | { ok: false; reason: string };
48
+
49
+ const ROOT_FIELDS = new Set(["version", "checks"]);
50
+ const CHECK_FIELDS = new Set(["id", "description", "command", "cwd", "timeoutMs", "tags"]);
51
+ const TAG_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
52
+ const SHELL_EXECUTABLES = new Set([
53
+ "bash",
54
+ "cmd",
55
+ "command.com",
56
+ "dash",
57
+ "fish",
58
+ "ksh",
59
+ "powershell",
60
+ "pwsh",
61
+ "sh",
62
+ "tcsh",
63
+ "zsh",
64
+ ]);
65
+
66
+ function isRecord(value: unknown): value is Record<string, unknown> {
67
+ return value !== null && typeof value === "object" && !Array.isArray(value);
68
+ }
69
+
70
+ function utf8Bytes(value: string): number {
71
+ return Buffer.byteLength(value, "utf8");
72
+ }
73
+
74
+ function hasControlCharacter(value: string): boolean {
75
+ for (let index = 0; index < value.length; index += 1) {
76
+ const code = value.charCodeAt(index);
77
+ if (code <= 31 || code === 127) return true;
78
+ }
79
+ return false;
80
+ }
81
+
82
+ function catalogDiagnostic(message: string): ProjectCatalogLoadResult {
83
+ return { ok: false, reason: `${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH}: ${message}` };
84
+ }
85
+
86
+ function unknownFields(record: Record<string, unknown>, allowed: ReadonlySet<string>): string[] {
87
+ return Object.keys(record)
88
+ .filter((key) => !allowed.has(key))
89
+ .sort(compareCodepoints);
90
+ }
91
+
92
+ function relativeCwd(workspaceRoot: string, resolved: string): string {
93
+ const relative = path.relative(workspaceRoot, resolved);
94
+ return relative.length === 0 ? "." : relative.split(path.sep).join("/");
95
+ }
96
+
97
+ interface ProjectVerifierCwdResolution {
98
+ declared: string;
99
+ execution: string;
100
+ }
101
+
102
+ function safelyResolveCatalogCwd(
103
+ rawCwd: string,
104
+ workspaceRoot: string,
105
+ location: string,
106
+ ): ProjectVerifierCwdResolution | Error {
107
+ if (path.isAbsolute(rawCwd) || path.win32.isAbsolute(rawCwd)) {
108
+ return new Error(`${location} must be repository-relative; absolute cwd '${rawCwd}' is not allowed`);
109
+ }
110
+ let resolved: string;
111
+ try {
112
+ resolved = resolveSafeCwd(rawCwd, workspaceRoot);
113
+ } catch {
114
+ return new Error(`${location} escapes the workspace root: '${rawCwd}'`);
115
+ }
116
+ let realCwd: string;
117
+ try {
118
+ const cwdStat = statSync(resolved);
119
+ if (!cwdStat.isDirectory()) return new Error(`${location} is not a directory: '${rawCwd}'`);
120
+ const realRoot = realpathSync(workspaceRoot);
121
+ realCwd = realpathSync(resolved);
122
+ resolveSafeCwd(realCwd, realRoot);
123
+ } catch (error) {
124
+ const message = error instanceof Error ? error.message : String(error);
125
+ if (message.includes("escapes workspace root")) {
126
+ return new Error(`${location} escapes the workspace root through a symbolic link: '${rawCwd}'`);
127
+ }
128
+ return new Error(`${location} cannot be resolved as a repository directory: '${rawCwd}' (${message})`);
129
+ }
130
+ return { declared: relativeCwd(workspaceRoot, resolved), execution: realCwd };
131
+ }
132
+
133
+ /** Revalidate a stored catalog cwd and return its canonical in-workspace execution path. */
134
+ export function resolveProjectVerifierExecutionCwd(rawCwd: string, workspaceRoot: string): string | Error {
135
+ const resolution = safelyResolveCatalogCwd(rawCwd, workspaceRoot, "project check cwd");
136
+ return resolution instanceof Error ? resolution : resolution.execution;
137
+ }
138
+
139
+ function validateId(value: unknown, location: string): string | Error {
140
+ if (typeof value !== "string" || value.length === 0) return new Error(`${location} must be a non-empty string`);
141
+ if (utf8Bytes(value) > PROJECT_VERIFIER_CATALOG_CAPS.idBytes) {
142
+ return new Error(`${location} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.idBytes}-byte cap`);
143
+ }
144
+ if (!isProjectVerifierCheckId(value)) {
145
+ return new Error(`${location} must match /^[a-z0-9][a-z0-9._:-]*$/`);
146
+ }
147
+ if (value === "frontend") return new Error(`${location} uses reserved built-in check id 'frontend'`);
148
+ return value;
149
+ }
150
+
151
+ function validateDescription(value: unknown, location: string): string | Error {
152
+ if (typeof value !== "string" || value.length === 0) return new Error(`${location} must be a non-empty string`);
153
+ if (value.trim() !== value || hasControlCharacter(value)) {
154
+ return new Error(`${location} must be trimmed, single-line text without control characters`);
155
+ }
156
+ if (utf8Bytes(value) > PROJECT_VERIFIER_CATALOG_CAPS.descriptionBytes) {
157
+ return new Error(`${location} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.descriptionBytes}-byte cap`);
158
+ }
159
+ return value;
160
+ }
161
+
162
+ function validateCommand(value: unknown, location: string): string[] | Error {
163
+ if (!Array.isArray(value) || value.length === 0) {
164
+ return new Error(`${location} must be a non-empty argv string array; shell command strings are not allowed`);
165
+ }
166
+ if (value.length > PROJECT_VERIFIER_CATALOG_CAPS.argvEntries) {
167
+ return new Error(`${location} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.argvEntries}-entry cap`);
168
+ }
169
+ const command: string[] = [];
170
+ for (const [index, entry] of value.entries()) {
171
+ const entryLocation = `${location}[${index}]`;
172
+ if (typeof entry !== "string" || entry.length === 0) {
173
+ return new Error(`${entryLocation} must be a non-empty string`);
174
+ }
175
+ if (entry.includes("\0")) return new Error(`${entryLocation} must not contain a NUL byte`);
176
+ if (utf8Bytes(entry) > PROJECT_VERIFIER_CATALOG_CAPS.argumentBytes) {
177
+ return new Error(`${entryLocation} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.argumentBytes}-byte cap`);
178
+ }
179
+ command.push(entry);
180
+ }
181
+ const executable = command[0] ?? "";
182
+ if (/\s/u.test(executable)) {
183
+ return new Error(`${location}[0] must be one executable token, not a shell command string`);
184
+ }
185
+ const executableName = path
186
+ .basename(executable)
187
+ .toLowerCase()
188
+ .replace(/\.exe$/u, "");
189
+ if (SHELL_EXECUTABLES.has(executableName)) {
190
+ return new Error(`${location}[0] may not invoke shell executable '${executable}'`);
191
+ }
192
+ return command;
193
+ }
194
+
195
+ function validateTimeout(value: unknown, location: string): number | Error {
196
+ if (!Number.isInteger(value) || typeof value !== "number" || value <= 0) {
197
+ return new Error(`${location} must be a positive integer number of milliseconds`);
198
+ }
199
+ if (value > PROJECT_VERIFIER_CATALOG_CAPS.timeoutMs) {
200
+ return new Error(`${location} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.timeoutMs}ms cap`);
201
+ }
202
+ return value;
203
+ }
204
+
205
+ function validateTags(value: unknown, location: string): string[] | Error {
206
+ if (!Array.isArray(value)) return new Error(`${location} must be an array of bounded tag strings`);
207
+ if (value.length > PROJECT_VERIFIER_CATALOG_CAPS.tags) {
208
+ return new Error(`${location} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.tags}-tag cap`);
209
+ }
210
+ const tags: string[] = [];
211
+ const seen = new Set<string>();
212
+ for (const [index, entry] of value.entries()) {
213
+ const entryLocation = `${location}[${index}]`;
214
+ if (typeof entry !== "string" || entry.length === 0) {
215
+ return new Error(`${entryLocation} must be a non-empty string`);
216
+ }
217
+ if (utf8Bytes(entry) > PROJECT_VERIFIER_CATALOG_CAPS.tagBytes) {
218
+ return new Error(`${entryLocation} exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.tagBytes}-byte cap`);
219
+ }
220
+ if (!TAG_PATTERN.test(entry)) return new Error(`${entryLocation} must match /^[a-z0-9][a-z0-9._-]*$/`);
221
+ if (seen.has(entry)) return new Error(`${location} contains duplicate tag '${entry}'`);
222
+ seen.add(entry);
223
+ tags.push(entry);
224
+ }
225
+ return tags;
226
+ }
227
+
228
+ function sourcePathInsideWorkspace(catalogPath: string, workspaceRoot: string): string | Error {
229
+ try {
230
+ const realRoot = realpathSync(workspaceRoot);
231
+ const realCatalogPath = realpathSync(catalogPath);
232
+ resolveSafeCwd(realCatalogPath, realRoot);
233
+ return realCatalogPath;
234
+ } catch (error) {
235
+ const message = error instanceof Error ? error.message : String(error);
236
+ return new Error(`catalog file must resolve inside the workspace root (${message})`);
237
+ }
238
+ }
239
+
240
+ export function loadProjectVerifierCatalog(workspaceRoot: string): ProjectCatalogLoadResult {
241
+ const catalogPath = path.join(workspaceRoot, PROJECT_VERIFIER_CATALOG_RELATIVE_PATH);
242
+ if (!existsSync(catalogPath)) return { ok: true, source: null };
243
+ const safeSourcePath = sourcePathInsideWorkspace(catalogPath, workspaceRoot);
244
+ if (safeSourcePath instanceof Error) return catalogDiagnostic(safeSourcePath.message);
245
+ let text: string;
246
+ try {
247
+ const stats = statSync(safeSourcePath);
248
+ if (!stats.isFile()) return catalogDiagnostic("catalog path must be a regular file");
249
+ if (stats.size > PROJECT_VERIFIER_CATALOG_CAPS.fileBytes) {
250
+ return catalogDiagnostic(`file exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.fileBytes}-byte cap`);
251
+ }
252
+ text = readFileSync(safeSourcePath, "utf8");
253
+ } catch (error) {
254
+ return catalogDiagnostic(`cannot read catalog (${error instanceof Error ? error.message : String(error)})`);
255
+ }
256
+ return parseProjectVerifierCatalogText(text, workspaceRoot, catalogPath);
257
+ }
258
+
259
+ /** Parse catalog text through the production schema and canonical normalizer. */
260
+ export function parseProjectVerifierCatalogText(
261
+ text: string,
262
+ workspaceRoot: string,
263
+ catalogPath = path.join(workspaceRoot, PROJECT_VERIFIER_CATALOG_RELATIVE_PATH),
264
+ ): ProjectCatalogLoadResult {
265
+ const document = parseDocument(text, { prettyErrors: false, strict: true, uniqueKeys: true });
266
+ if (document.errors.length > 0) {
267
+ return catalogDiagnostic(`invalid YAML: ${document.errors.map((error) => error.message).join("; ")}`);
268
+ }
269
+ let parsed: unknown;
270
+ try {
271
+ parsed = document.toJS({ maxAliasCount: 0 }) as unknown;
272
+ } catch (error) {
273
+ return catalogDiagnostic(`invalid YAML: ${error instanceof Error ? error.message : String(error)}`);
274
+ }
275
+ if (!isRecord(parsed)) return catalogDiagnostic("root must be an object with version and checks fields");
276
+ const rootUnknown = unknownFields(parsed, ROOT_FIELDS);
277
+ if (rootUnknown.length > 0) return catalogDiagnostic(`root has unknown field(s): ${rootUnknown.join(", ")}`);
278
+ if (!Object.hasOwn(parsed, "version")) return catalogDiagnostic("root.version is required");
279
+ if (parsed.version !== PROJECT_VERIFIER_CATALOG_VERSION) {
280
+ return catalogDiagnostic(
281
+ `unsupported version ${JSON.stringify(parsed.version)}; supported version is ${PROJECT_VERIFIER_CATALOG_VERSION}`,
282
+ );
283
+ }
284
+ if (!Object.hasOwn(parsed, "checks")) return catalogDiagnostic("root.checks is required");
285
+ if (!Array.isArray(parsed.checks)) return catalogDiagnostic("root.checks must be an array");
286
+ if (parsed.checks.length > PROJECT_VERIFIER_CATALOG_CAPS.checks) {
287
+ return catalogDiagnostic(`root.checks exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.checks}-check cap`);
288
+ }
289
+
290
+ const sourceRef: DeclaredCheckSourceRef = { kind: "project-catalog", path: catalogPath };
291
+ const checks: DeclaredCheck[] = [];
292
+ const ids = new Map<string, number>();
293
+ for (const [index, value] of parsed.checks.entries()) {
294
+ const location = `checks[${index}]`;
295
+ if (!isRecord(value)) return catalogDiagnostic(`${location} must be an object`);
296
+ const checkUnknown = unknownFields(value, CHECK_FIELDS);
297
+ if (checkUnknown.length > 0) {
298
+ return catalogDiagnostic(`${location} has unknown field(s): ${checkUnknown.join(", ")}`);
299
+ }
300
+ for (const field of CHECK_FIELDS) {
301
+ if (!Object.hasOwn(value, field)) return catalogDiagnostic(`${location}.${field} is required`);
302
+ }
303
+
304
+ const id = validateId(value.id, `${location}.id`);
305
+ if (id instanceof Error) return catalogDiagnostic(id.message);
306
+ const duplicateIndex = ids.get(id);
307
+ if (duplicateIndex !== undefined) {
308
+ return catalogDiagnostic(`${location}.id duplicates '${id}' from checks[${duplicateIndex}].id`);
309
+ }
310
+ const description = validateDescription(value.description, `${location}.description`);
311
+ if (description instanceof Error) return catalogDiagnostic(description.message);
312
+ const command = validateCommand(value.command, `${location}.command`);
313
+ if (command instanceof Error) return catalogDiagnostic(command.message);
314
+ if (typeof value.cwd !== "string" || value.cwd.length === 0) {
315
+ return catalogDiagnostic(`${location}.cwd must be a non-empty repository-relative string`);
316
+ }
317
+ if (value.cwd.includes("\0")) return catalogDiagnostic(`${location}.cwd must not contain a NUL byte`);
318
+ if (utf8Bytes(value.cwd) > PROJECT_VERIFIER_CATALOG_CAPS.cwdBytes) {
319
+ return catalogDiagnostic(`${location}.cwd exceeds the ${PROJECT_VERIFIER_CATALOG_CAPS.cwdBytes}-byte cap`);
320
+ }
321
+ const cwdResolution = safelyResolveCatalogCwd(value.cwd, workspaceRoot, `${location}.cwd`);
322
+ if (cwdResolution instanceof Error) return catalogDiagnostic(cwdResolution.message);
323
+ const cwd = cwdResolution.declared;
324
+ const timeoutMs = validateTimeout(value.timeoutMs, `${location}.timeoutMs`);
325
+ if (timeoutMs instanceof Error) return catalogDiagnostic(timeoutMs.message);
326
+ const tags = validateTags(value.tags, `${location}.tags`);
327
+ if (tags instanceof Error) return catalogDiagnostic(tags.message);
328
+
329
+ ids.set(id, index);
330
+ checks.push({ id, description, command, cwd, timeoutMs, tags, source: { ...sourceRef } });
331
+ }
332
+ checks.sort((left, right) => compareCodepoints(left.id, right.id));
333
+ return { ok: true, source: { ...sourceRef, checks } };
334
+ }
335
+
336
+ export function packageDeclaredCheck(id: string, packagePath: string, cwd: string, tags: string[]): DeclaredCheck {
337
+ return {
338
+ id,
339
+ description: `Run package.json script '${id}'.`,
340
+ command: ["npm", "run", id],
341
+ cwd,
342
+ timeoutMs: SAFE_EXEC_DEFAULT_TIMEOUT_MS,
343
+ tags,
344
+ source: { kind: "package.json", path: packagePath },
345
+ };
346
+ }
@@ -1,13 +1,19 @@
1
1
  import { isVerificationScriptName } from "../../core/verification-scripts.js";
2
2
  import type { ToolResult, ToolSpec } from "../registry.js";
3
3
  import { runFrontendCheck } from "./frontend.js";
4
- import { listChecks, runScriptCheck, VERIFICATION_SCRIPT_FAMILY_HINT } from "./scripts.js";
4
+ import {
5
+ discoverDeclaredChecks,
6
+ listChecks,
7
+ runProjectCheck,
8
+ runScriptCheck,
9
+ VERIFICATION_SCRIPT_FAMILY_HINT,
10
+ } from "./scripts.js";
5
11
  import { prepareVerifyArguments, verifyToolSurface } from "./surface.js";
6
12
 
7
13
  /**
8
14
  * The verify tool: one EXECUTE entry point for declared verification.
9
- * verify() lists declared checks, verify(check=<script>) runs a package.json
10
- * verification script via the safe-exec spine, verify(check="frontend",
15
+ * verify() lists canonical checks, verify(check=<id>) runs a package script or
16
+ * exact project-catalog vector via safe-exec, and verify(check="frontend",
11
17
  * path=<file>) validates an HTML/CSS/JS artifact without shell access.
12
18
  */
13
19
 
@@ -20,6 +26,10 @@ export const verifyTool: ToolSpec = {
20
26
  return listChecks(typeof args.cwd === "string" && args.cwd.length > 0 ? args.cwd : undefined);
21
27
  }
22
28
  if (check === "frontend") return runFrontendCheck(args, options);
29
+ const discovery = discoverDeclaredChecks(typeof args.cwd === "string" && args.cwd.length > 0 ? args.cwd : undefined);
30
+ if (!discovery.ok) return { kind: "error", message: `verify: ${discovery.reason}` };
31
+ const declared = discovery.sources.flatMap((source) => source.checks).find((candidate) => candidate.id === check);
32
+ if (declared?.source.kind === "project-catalog") return runProjectCheck(declared, options);
23
33
  if (!isVerificationScriptName(check)) {
24
34
  return {
25
35
  kind: "error",
@@ -4,58 +4,156 @@ import { resolveSafeCwd } from "../../core/safe-exec.js";
4
4
  import { declaredVerificationScripts, VERIFICATION_SCRIPT_FAMILY_HINT } from "../../core/verification-scripts.js";
5
5
  import type { ToolResult } from "../registry.js";
6
6
  import { runVectorTool } from "../safe-exec.js";
7
+ import {
8
+ type DeclaredCheck,
9
+ type DeclaredCheckSource,
10
+ loadProjectVerifierCatalog,
11
+ PROJECT_VERIFIER_CATALOG_RELATIVE_PATH,
12
+ packageDeclaredCheck,
13
+ resolveProjectVerifierExecutionCwd,
14
+ } from "./catalog.js";
7
15
 
8
16
  /**
9
- * verify(check=<script>): run one declared package.json verification script
10
- * through npm with no shell, and verify() with no check: list what is
11
- * declared. Internal module of the verify tool. The listing shape is grouped
12
- * by source so later check providers (Makefile, justfile, pixi) can add their
13
- * own groups without changing the surface.
17
+ * Package scripts and the project catalog meet here as one canonical check
18
+ * projection. Project checks retain their admitted argv/cwd/timeout exactly;
19
+ * package scripts keep the established npm argument-widening behavior.
14
20
  */
15
21
 
16
- export interface DeclaredCheckSource {
17
- kind: "package.json";
18
- path: string;
19
- checks: string[];
22
+ export type DeclaredCheckDiscoveryResult = { ok: true; sources: DeclaredCheckSource[] } | { ok: false; reason: string };
23
+
24
+ function repositoryRelativeCwd(workspaceRoot: string, resolved: string): string {
25
+ const relative = path.relative(workspaceRoot, resolved);
26
+ return relative.length === 0 ? "." : relative.split(path.sep).join("/");
20
27
  }
21
28
 
22
- function declaredCheckSources(cwd: string): DeclaredCheckSource[] {
23
- const pkgPath = path.join(cwd, "package.json");
24
- if (!existsSync(pkgPath)) return [];
25
- const pkg = parsePackageJson(pkgPath);
26
- if (!pkg.ok) return [];
27
- return [{ kind: "package.json", path: pkgPath, checks: declaredVerificationScripts(pkg.scripts) }];
29
+ function packageTag(id: string): string[] {
30
+ const separator = id.search(/[:.-]/u);
31
+ return [separator === -1 ? id : id.slice(0, separator)];
28
32
  }
29
33
 
30
- export function listChecks(cwdArg: string | undefined): ToolResult {
31
- let cwd: string;
34
+ function packageCheckSource(packageRoot: string, workspaceRoot: string): DeclaredCheckSource | null {
35
+ const packagePath = path.join(packageRoot, "package.json");
36
+ if (!existsSync(packagePath)) return null;
37
+ const pkg = parsePackageJson(packagePath);
38
+ if (!pkg.ok) return null;
39
+ const cwd = repositoryRelativeCwd(workspaceRoot, packageRoot);
40
+ const checks = declaredVerificationScripts(pkg.scripts).map((id) =>
41
+ packageDeclaredCheck(id, packagePath, cwd, packageTag(id)),
42
+ );
43
+ return { kind: "package.json", path: packagePath, checks };
44
+ }
45
+
46
+ function providerCollision(sources: ReadonlyArray<DeclaredCheckSource>): string | null {
47
+ const seen = new Map<string, DeclaredCheck>();
48
+ for (const source of sources) {
49
+ for (const check of source.checks) {
50
+ const prior = seen.get(check.id);
51
+ if (prior !== undefined) {
52
+ return (
53
+ `duplicate declared check id '${check.id}' from ` +
54
+ `${prior.source.kind} (${prior.source.path}) and ${check.source.kind} (${check.source.path})`
55
+ );
56
+ }
57
+ seen.set(check.id, check);
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ export function discoverDeclaredChecksAtRoot(
64
+ workspaceRoot: string,
65
+ cwdArg: string | undefined,
66
+ ): DeclaredCheckDiscoveryResult {
67
+ let packageRoot: string;
32
68
  try {
33
- cwd = resolveSafeCwd(cwdArg, process.cwd());
34
- } catch (err) {
35
- return { kind: "error", message: `verify: ${err instanceof Error ? err.message : String(err)}` };
69
+ packageRoot = resolveSafeCwd(cwdArg, workspaceRoot);
70
+ } catch (error) {
71
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
36
72
  }
37
- const sources = declaredCheckSources(cwd);
73
+ const sources: DeclaredCheckSource[] = [];
74
+ const packageSource = packageCheckSource(packageRoot, workspaceRoot);
75
+ if (packageSource !== null) sources.push(packageSource);
76
+ const projectCatalog = loadProjectVerifierCatalog(workspaceRoot);
77
+ if (!projectCatalog.ok) return projectCatalog;
78
+ if (projectCatalog.source !== null) sources.push(projectCatalog.source);
79
+ const collision = providerCollision(sources);
80
+ if (collision !== null) return { ok: false, reason: collision };
81
+ return { ok: true, sources };
82
+ }
83
+
84
+ export function discoverDeclaredChecks(cwdArg: string | undefined): DeclaredCheckDiscoveryResult {
85
+ return discoverDeclaredChecksAtRoot(process.cwd(), cwdArg);
86
+ }
87
+
88
+ function clonedSources(sources: ReadonlyArray<DeclaredCheckSource>): DeclaredCheckSource[] {
89
+ return sources.map((source) => ({
90
+ kind: source.kind,
91
+ path: source.path,
92
+ checks: source.checks.map((check) => ({
93
+ ...check,
94
+ command: [...check.command],
95
+ tags: [...check.tags],
96
+ source: { ...check.source },
97
+ })),
98
+ }));
99
+ }
100
+
101
+ export function listChecks(cwdArg: string | undefined): ToolResult {
102
+ const discovery = discoverDeclaredChecks(cwdArg);
103
+ if (!discovery.ok) return { kind: "error", message: `verify: ${discovery.reason}` };
38
104
  const lines: string[] = [];
39
- if (sources.length === 0 || sources.every((source) => source.checks.length === 0)) {
40
- lines.push("No declared verification checks found (no package.json verification scripts).");
105
+ if (discovery.sources.length === 0 || discovery.sources.every((source) => source.checks.length === 0)) {
106
+ lines.push(
107
+ `No declared verification checks found (no package.json verification scripts or ${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH} entries).`,
108
+ "Run `clio-coder verifiers author` to inspect declared project tooling, preview exact argv checks, and create the catalog after confirmation.",
109
+ );
41
110
  } else {
42
111
  lines.push("Declared verification checks:");
43
- for (const source of sources) {
44
- lines.push(`${source.kind}:`);
45
- for (const check of source.checks) lines.push(`- ${check}`);
112
+ for (const source of discovery.sources) {
113
+ lines.push(source.kind === "package.json" ? "package.json:" : `${PROJECT_VERIFIER_CATALOG_RELATIVE_PATH}:`);
114
+ for (const check of source.checks) {
115
+ const tags = check.tags.length > 0 ? ` [${check.tags.join(", ")}]` : "";
116
+ lines.push(`- ${check.id}${tags}: ${check.description}`);
117
+ }
46
118
  }
47
119
  }
48
120
  lines.push(
49
121
  "",
50
- 'Run one with verify(check="<name>"). verify(check="frontend", path=<file>) validates an HTML/CSS/JS artifact.',
122
+ 'Run one with verify(check="<id>"). verify(check="frontend", path=<file>) validates an HTML/CSS/JS artifact.',
51
123
  );
52
124
  return {
53
125
  kind: "ok",
54
126
  output: lines.join("\n"),
55
- details: { sources: sources.map((source) => ({ ...source })) },
127
+ details: { sources: clonedSources(discovery.sources) },
56
128
  };
57
129
  }
58
130
 
131
+ function withDeclaredEvidence(result: ToolResult, check: DeclaredCheck): ToolResult {
132
+ return {
133
+ ...result,
134
+ details: {
135
+ ...result.details,
136
+ action: "verify",
137
+ check: check.id,
138
+ source: { ...check.source },
139
+ description: check.description,
140
+ declaredCommand: [...check.command],
141
+ declaredCwd: check.cwd,
142
+ declaredTimeoutMs: check.timeoutMs,
143
+ tags: [...check.tags],
144
+ },
145
+ };
146
+ }
147
+
148
+ export async function runProjectCheck(check: DeclaredCheck, options?: { signal?: AbortSignal }): Promise<ToolResult> {
149
+ const [file, ...vector] = check.command;
150
+ if (file === undefined) return { kind: "error", message: `verify: declared check '${check.id}' has empty argv` };
151
+ const cwd = resolveProjectVerifierExecutionCwd(check.cwd, process.cwd());
152
+ if (cwd instanceof Error) return { kind: "error", message: `verify: ${cwd.message}` };
153
+ const result = await runVectorTool("verify", file, vector, { cwd, timeout_ms: check.timeoutMs }, options);
154
+ return withDeclaredEvidence(result, check);
155
+ }
156
+
59
157
  export async function runScriptCheck(
60
158
  check: string,
61
159
  args: Record<string, unknown>,
@@ -64,12 +162,12 @@ export async function runScriptCheck(
64
162
  let cwd: string;
65
163
  try {
66
164
  cwd = resolveSafeCwd(typeof args.cwd === "string" && args.cwd.length > 0 ? args.cwd : undefined, process.cwd());
67
- } catch (err) {
68
- return { kind: "error", message: `verify: ${err instanceof Error ? err.message : String(err)}` };
165
+ } catch (error) {
166
+ return { kind: "error", message: `verify: ${error instanceof Error ? error.message : String(error)}` };
69
167
  }
70
- const pkgPath = path.join(cwd, "package.json");
71
- if (!existsSync(pkgPath)) return { kind: "error", message: `verify: package.json not found in ${cwd}` };
72
- const pkg = parsePackageJson(pkgPath);
168
+ const packagePath = path.join(cwd, "package.json");
169
+ if (!existsSync(packagePath)) return { kind: "error", message: `verify: package.json not found in ${cwd}` };
170
+ const pkg = parsePackageJson(packagePath);
73
171
  if (!pkg.ok) return { kind: "error", message: `verify: ${pkg.reason}` };
74
172
  if (!Object.hasOwn(pkg.scripts, check)) {
75
173
  const declared = declaredVerificationScripts(pkg.scripts);
@@ -90,10 +188,10 @@ export async function runScriptCheck(
90
188
  export { VERIFICATION_SCRIPT_FAMILY_HINT };
91
189
 
92
190
  function parsePackageJson(
93
- pkgPath: string,
191
+ packagePath: string,
94
192
  ): { ok: true; scripts: Record<string, unknown> } | { ok: false; reason: string } {
95
193
  try {
96
- const parsed = JSON.parse(readFileSync(pkgPath, "utf8")) as unknown;
194
+ const parsed = JSON.parse(readFileSync(packagePath, "utf8")) as unknown;
97
195
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
98
196
  return { ok: false, reason: "package.json root must be an object" };
99
197
  }
@@ -102,7 +200,7 @@ function parsePackageJson(
102
200
  return { ok: false, reason: "package.json has no scripts object" };
103
201
  }
104
202
  return { ok: true, scripts: scripts as Record<string, unknown> };
105
- } catch (err) {
106
- return { ok: false, reason: err instanceof Error ? err.message : String(err) };
203
+ } catch (error) {
204
+ return { ok: false, reason: error instanceof Error ? error.message : String(error) };
107
205
  }
108
206
  }
@@ -24,20 +24,24 @@ export function prepareVerifyArguments(args: Record<string, unknown>): Record<st
24
24
  export const verifyToolSurface = {
25
25
  name: ToolNames.Verify,
26
26
  description:
27
- 'Run a declared verification check: no arguments lists checks, check=<package.json script> runs it, check="frontend" with path validates an HTML/CSS/JS artifact.',
27
+ 'Run a declared verification check: no arguments lists package scripts and .clio-coder/verifiers.yaml entries, check=<id> runs one, and check="frontend" with path validates an HTML/CSS/JS artifact.',
28
28
  parameters: Type.Object({
29
29
  check: Type.Optional(
30
30
  Type.String({
31
- description: `Declared script name (${VERIFICATION_SCRIPT_FAMILY_HINT}) or "frontend". Omit to list available checks.`,
31
+ description: `Declared project check ID, package script (${VERIFICATION_SCRIPT_FAMILY_HINT}), or "frontend". Omit to list available checks.`,
32
32
  }),
33
33
  ),
34
34
  path: Type.Optional(Type.String({ description: "check=frontend: artifact file under the workspace root." })),
35
- args: Type.Optional(Type.Array(Type.String(), { description: "Extra arguments passed after --." })),
35
+ args: Type.Optional(
36
+ Type.Array(Type.String(), { description: "Package scripts only: extra arguments passed after --." }),
37
+ ),
36
38
  browser: Type.Optional(
37
39
  StringEnum(BROWSER_MODES, { description: "check=frontend: headless browser mode (default auto)." }),
38
40
  ),
39
- cwd: Type.Optional(Type.String({ description: "Working directory." })),
40
- timeout_ms: Type.Optional(Type.Number({ description: "Timeout in ms (default 120000)." })),
41
+ cwd: Type.Optional(Type.String({ description: "Package-script working directory; ignored by project checks." })),
42
+ timeout_ms: Type.Optional(
43
+ Type.Number({ description: "Package/frontend timeout in ms; project checks use their declared timeoutMs." }),
44
+ ),
41
45
  max_output_bytes: Type.Optional(Type.Number({ description: "Output cap in bytes (default 600000)." })),
42
46
  }),
43
47
  baseActionClass: "execute",