@cleocode/core 2026.10.18 → 2026.10.19

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 (259) hide show
  1. package/dist/build-provenance.json +2 -2
  2. package/dist/check/pr-gate.js +1 -1
  3. package/dist/check/pr-gate.js.map +1 -1
  4. package/dist/cloud/nexus-vault.d.ts +4 -3
  5. package/dist/cloud/nexus-vault.d.ts.map +1 -1
  6. package/dist/cloud/nexus-vault.js +62 -22
  7. package/dist/cloud/nexus-vault.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +11 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/docs/conduit-history-backfill.d.ts +210 -0
  12. package/dist/docs/conduit-history-backfill.d.ts.map +1 -0
  13. package/dist/docs/conduit-history-backfill.js +451 -0
  14. package/dist/docs/conduit-history-backfill.js.map +1 -0
  15. package/dist/docs/docs-ops.d.ts +33 -0
  16. package/dist/docs/docs-ops.d.ts.map +1 -1
  17. package/dist/docs/docs-ops.js +72 -0
  18. package/dist/docs/docs-ops.js.map +1 -1
  19. package/dist/docs/docs-update.d.ts.map +1 -1
  20. package/dist/docs/docs-update.js +71 -0
  21. package/dist/docs/docs-update.js.map +1 -1
  22. package/dist/docs/doctor.d.ts +18 -4
  23. package/dist/docs/doctor.d.ts.map +1 -1
  24. package/dist/docs/doctor.js +124 -3
  25. package/dist/docs/doctor.js.map +1 -1
  26. package/dist/docs/section-merge.d.ts +70 -9
  27. package/dist/docs/section-merge.d.ts.map +1 -1
  28. package/dist/docs/section-merge.js +167 -10
  29. package/dist/docs/section-merge.js.map +1 -1
  30. package/dist/docs/section-write-path.d.ts.map +1 -1
  31. package/dist/docs/section-write-path.js +23 -8
  32. package/dist/docs/section-write-path.js.map +1 -1
  33. package/dist/gateway-client/generated/types.gen.d.ts +4 -0
  34. package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
  35. package/dist/go/driver.d.ts +29 -145
  36. package/dist/go/driver.d.ts.map +1 -1
  37. package/dist/go/driver.js +75 -168
  38. package/dist/go/driver.js.map +1 -1
  39. package/dist/go/index.d.ts +1 -1
  40. package/dist/go/index.d.ts.map +1 -1
  41. package/dist/go/index.js +1 -1
  42. package/dist/go/index.js.map +1 -1
  43. package/dist/goal/close.d.ts +1 -1
  44. package/dist/goal/close.js +2 -2
  45. package/dist/goal/close.js.map +1 -1
  46. package/dist/goal/draft.d.ts +10 -3
  47. package/dist/goal/draft.d.ts.map +1 -1
  48. package/dist/goal/draft.js +14 -9
  49. package/dist/goal/draft.js.map +1 -1
  50. package/dist/goal/index.d.ts +9 -12
  51. package/dist/goal/index.d.ts.map +1 -1
  52. package/dist/goal/index.js +9 -12
  53. package/dist/goal/index.js.map +1 -1
  54. package/dist/goal/runtime.d.ts +126 -0
  55. package/dist/goal/runtime.d.ts.map +1 -0
  56. package/dist/goal/runtime.js +289 -0
  57. package/dist/goal/runtime.js.map +1 -0
  58. package/dist/goal/status.d.ts +39 -0
  59. package/dist/goal/status.d.ts.map +1 -0
  60. package/dist/goal/status.js +101 -0
  61. package/dist/goal/status.js.map +1 -0
  62. package/dist/hooks/project-runner.d.ts +8 -3
  63. package/dist/hooks/project-runner.d.ts.map +1 -1
  64. package/dist/hooks/project-runner.js +104 -14
  65. package/dist/hooks/project-runner.js.map +1 -1
  66. package/dist/hooks/project-state.js +1 -1
  67. package/dist/hooks/project-state.js.map +1 -1
  68. package/dist/internal.d.ts +7 -3
  69. package/dist/internal.d.ts.map +1 -1
  70. package/dist/internal.js +5 -3
  71. package/dist/internal.js.map +1 -1
  72. package/dist/lifecycle/engine-ops.d.ts.map +1 -1
  73. package/dist/lifecycle/engine-ops.js +9 -6
  74. package/dist/lifecycle/engine-ops.js.map +1 -1
  75. package/dist/memory/retrieval/observe.d.ts.map +1 -1
  76. package/dist/memory/retrieval/observe.js +4 -8
  77. package/dist/memory/retrieval/observe.js.map +1 -1
  78. package/dist/paths.d.ts.map +1 -1
  79. package/dist/paths.js +4 -1
  80. package/dist/paths.js.map +1 -1
  81. package/dist/playbooks/cantbook-profile.d.ts +3 -3
  82. package/dist/playbooks/cantbook-profile.js +3 -3
  83. package/dist/playbooks/pi-runner-wiring.d.ts +3 -3
  84. package/dist/playbooks/pi-runner-wiring.js +3 -3
  85. package/dist/release/ci-evidence.d.ts +28 -0
  86. package/dist/release/ci-evidence.d.ts.map +1 -1
  87. package/dist/release/ci-evidence.js +76 -20
  88. package/dist/release/ci-evidence.js.map +1 -1
  89. package/dist/release/open.d.ts +11 -0
  90. package/dist/release/open.d.ts.map +1 -1
  91. package/dist/release/open.js +49 -10
  92. package/dist/release/open.js.map +1 -1
  93. package/dist/release/preflight-skip.d.ts +50 -0
  94. package/dist/release/preflight-skip.d.ts.map +1 -1
  95. package/dist/release/preflight-skip.js +151 -34
  96. package/dist/release/preflight-skip.js.map +1 -1
  97. package/dist/release/reconcile.d.ts.map +1 -1
  98. package/dist/release/reconcile.js +6 -3
  99. package/dist/release/reconcile.js.map +1 -1
  100. package/dist/scaffold/ensure-config.d.ts +1 -1
  101. package/dist/scaffold/ensure-config.d.ts.map +1 -1
  102. package/dist/scaffold/ensure-config.js +2 -0
  103. package/dist/scaffold/ensure-config.js.map +1 -1
  104. package/dist/security/index.d.ts +1 -1
  105. package/dist/security/index.d.ts.map +1 -1
  106. package/dist/security/index.js +1 -1
  107. package/dist/security/index.js.map +1 -1
  108. package/dist/security/override-cap-setting.d.ts +180 -0
  109. package/dist/security/override-cap-setting.d.ts.map +1 -0
  110. package/dist/security/override-cap-setting.js +657 -0
  111. package/dist/security/override-cap-setting.js.map +1 -0
  112. package/dist/security/override-cap.d.ts +20 -29
  113. package/dist/security/override-cap.d.ts.map +1 -1
  114. package/dist/security/override-cap.js +47 -71
  115. package/dist/security/override-cap.js.map +1 -1
  116. package/dist/security/owner-override-auth.d.ts +58 -0
  117. package/dist/security/owner-override-auth.d.ts.map +1 -1
  118. package/dist/security/owner-override-auth.js +82 -1
  119. package/dist/security/owner-override-auth.js.map +1 -1
  120. package/dist/security/owner-override-authority.d.ts +67 -0
  121. package/dist/security/owner-override-authority.d.ts.map +1 -0
  122. package/dist/security/owner-override-authority.js +100 -0
  123. package/dist/security/owner-override-authority.js.map +1 -0
  124. package/dist/sessions/handoff.d.ts.map +1 -1
  125. package/dist/sessions/handoff.js +4 -5
  126. package/dist/sessions/handoff.js.map +1 -1
  127. package/dist/sessions/index.d.ts.map +1 -1
  128. package/dist/sessions/index.js +22 -25
  129. package/dist/sessions/index.js.map +1 -1
  130. package/dist/store/conduit-doc-history.d.ts +39 -7
  131. package/dist/store/conduit-doc-history.d.ts.map +1 -1
  132. package/dist/store/conduit-doc-history.js +62 -12
  133. package/dist/store/conduit-doc-history.js.map +1 -1
  134. package/dist/store/data-safety-central.d.ts.map +1 -1
  135. package/dist/store/data-safety-central.js +3 -2
  136. package/dist/store/data-safety-central.js.map +1 -1
  137. package/dist/store/json.d.ts.map +1 -1
  138. package/dist/store/json.js +7 -7
  139. package/dist/store/json.js.map +1 -1
  140. package/dist/store/portable-bundle-scan.d.ts +23 -1
  141. package/dist/store/portable-bundle-scan.d.ts.map +1 -1
  142. package/dist/store/portable-bundle-scan.js +85 -3
  143. package/dist/store/portable-bundle-scan.js.map +1 -1
  144. package/dist/store/portable-bundle.d.ts.map +1 -1
  145. package/dist/store/portable-bundle.js +6 -1
  146. package/dist/store/portable-bundle.js.map +1 -1
  147. package/dist/store/row-identity-registry.d.ts +5 -3
  148. package/dist/store/row-identity-registry.d.ts.map +1 -1
  149. package/dist/store/row-identity-registry.js +54 -38
  150. package/dist/store/row-identity-registry.js.map +1 -1
  151. package/dist/store/schema/cleo-project/conduit.d.ts.map +1 -1
  152. package/dist/store/schema/cleo-project/conduit.js +4 -3
  153. package/dist/store/schema/cleo-project/conduit.js.map +1 -1
  154. package/dist/store/schema/cleo-project/runtime.d.ts +32 -0
  155. package/dist/store/schema/cleo-project/runtime.d.ts.map +1 -1
  156. package/dist/store/schema/cleo-project/runtime.js +7 -3
  157. package/dist/store/schema/cleo-project/runtime.js.map +1 -1
  158. package/dist/store/session-store.d.ts +45 -0
  159. package/dist/store/session-store.d.ts.map +1 -1
  160. package/dist/store/session-store.js +96 -1
  161. package/dist/store/session-store.js.map +1 -1
  162. package/dist/store/sqlite.d.ts +2 -0
  163. package/dist/store/sqlite.d.ts.map +1 -1
  164. package/dist/store/sqlite.js +12 -0
  165. package/dist/store/sqlite.js.map +1 -1
  166. package/dist/store/sync/merge/rules.d.ts.map +1 -1
  167. package/dist/store/sync/merge/rules.js +0 -16
  168. package/dist/store/sync/merge/rules.js.map +1 -1
  169. package/dist/store/sync/sealer.d.ts.map +1 -1
  170. package/dist/store/sync/sealer.js +27 -1
  171. package/dist/store/sync/sealer.js.map +1 -1
  172. package/dist/store/sync/sequencing.d.ts +3 -1
  173. package/dist/store/sync/sequencing.d.ts.map +1 -1
  174. package/dist/store/sync/sequencing.js +5 -3
  175. package/dist/store/sync/sequencing.js.map +1 -1
  176. package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
  177. package/dist/store/sync/timestamp-canon.js +4 -0
  178. package/dist/store/sync/timestamp-canon.js.map +1 -1
  179. package/dist/store/table-classification.d.ts.map +1 -1
  180. package/dist/store/table-classification.js +12 -8
  181. package/dist/store/table-classification.js.map +1 -1
  182. package/dist/store/twin-collapse.d.ts +21 -0
  183. package/dist/store/twin-collapse.d.ts.map +1 -1
  184. package/dist/store/twin-collapse.js +235 -18
  185. package/dist/store/twin-collapse.js.map +1 -1
  186. package/dist/system/health.d.ts +17 -0
  187. package/dist/system/health.d.ts.map +1 -1
  188. package/dist/system/health.js +96 -0
  189. package/dist/system/health.js.map +1 -1
  190. package/dist/tasks/affected-packages.d.ts +67 -2
  191. package/dist/tasks/affected-packages.d.ts.map +1 -1
  192. package/dist/tasks/affected-packages.js +149 -21
  193. package/dist/tasks/affected-packages.js.map +1 -1
  194. package/dist/tasks/affected-scope.d.ts.map +1 -1
  195. package/dist/tasks/affected-scope.js +91 -5
  196. package/dist/tasks/affected-scope.js.map +1 -1
  197. package/dist/tasks/changeset-plan.d.ts +33 -0
  198. package/dist/tasks/changeset-plan.d.ts.map +1 -0
  199. package/dist/tasks/changeset-plan.js +152 -0
  200. package/dist/tasks/changeset-plan.js.map +1 -0
  201. package/dist/tasks/complete.d.ts.map +1 -1
  202. package/dist/tasks/complete.js +9 -1
  203. package/dist/tasks/complete.js.map +1 -1
  204. package/dist/tasks/done-plan.d.ts.map +1 -1
  205. package/dist/tasks/done-plan.js +28 -14
  206. package/dist/tasks/done-plan.js.map +1 -1
  207. package/dist/tasks/done-record.d.ts.map +1 -1
  208. package/dist/tasks/done-record.js +5 -1
  209. package/dist/tasks/done-record.js.map +1 -1
  210. package/dist/tasks/evidence.d.ts.map +1 -1
  211. package/dist/tasks/evidence.js +149 -18
  212. package/dist/tasks/evidence.js.map +1 -1
  213. package/dist/tasks/gate-runner.d.ts.map +1 -1
  214. package/dist/tasks/gate-runner.js +76 -4
  215. package/dist/tasks/gate-runner.js.map +1 -1
  216. package/dist/tasks/qa-run-binding.d.ts +18 -0
  217. package/dist/tasks/qa-run-binding.d.ts.map +1 -1
  218. package/dist/tasks/qa-run-binding.js +41 -1
  219. package/dist/tasks/qa-run-binding.js.map +1 -1
  220. package/dist/tasks/test-run-binding.d.ts +62 -0
  221. package/dist/tasks/test-run-binding.d.ts.map +1 -1
  222. package/dist/tasks/test-run-binding.js +126 -21
  223. package/dist/tasks/test-run-binding.js.map +1 -1
  224. package/dist/tasks/tool-resolver.d.ts +7 -0
  225. package/dist/tasks/tool-resolver.d.ts.map +1 -1
  226. package/dist/tasks/tool-resolver.js +3 -1
  227. package/dist/tasks/tool-resolver.js.map +1 -1
  228. package/dist/validation/engine-ops.d.ts.map +1 -1
  229. package/dist/validation/engine-ops.js +24 -29
  230. package/dist/validation/engine-ops.js.map +1 -1
  231. package/migrations/drizzle-cleo-project/20261016120000_t12535-playbook-row-uids/migration.sql +24 -0
  232. package/package.json +13 -13
  233. package/schemas/project-context.schema.json +5 -0
  234. package/templates/CLEO-INJECTION.md +1 -1
  235. package/templates/CLEO-REFERENCE.md +42 -1
  236. package/templates/cleo-gitignore +3 -0
  237. package/templates/git-hooks/legacy-hashes.json +1 -0
  238. package/templates/git-hooks/pre-push +35 -7
  239. package/templates/workflows/release-prepare.yml.tmpl +82 -4
  240. package/dist/goal/advance-with-persist.d.ts +0 -80
  241. package/dist/goal/advance-with-persist.d.ts.map +0 -1
  242. package/dist/goal/advance-with-persist.js +0 -105
  243. package/dist/goal/advance-with-persist.js.map +0 -1
  244. package/dist/goal/arm.d.ts +0 -61
  245. package/dist/goal/arm.d.ts.map +0 -1
  246. package/dist/goal/arm.js +0 -65
  247. package/dist/goal/arm.js.map +0 -1
  248. package/dist/goal/continuation.d.ts +0 -66
  249. package/dist/goal/continuation.d.ts.map +0 -1
  250. package/dist/goal/continuation.js +0 -111
  251. package/dist/goal/continuation.js.map +0 -1
  252. package/dist/goal/judge.d.ts +0 -113
  253. package/dist/goal/judge.d.ts.map +0 -1
  254. package/dist/goal/judge.js +0 -193
  255. package/dist/goal/judge.js.map +0 -1
  256. package/dist/goal/loop.d.ts +0 -75
  257. package/dist/goal/loop.d.ts.map +0 -1
  258. package/dist/goal/loop.js +0 -132
  259. package/dist/goal/loop.js.map +0 -1
@@ -0,0 +1,289 @@
1
+ /**
2
+ * CLEO Goal runtime (`@cleocode/goal`, bin `cleo-goal`, alias `ocg`): health
3
+ * and per-project registration.
4
+ *
5
+ * CLEO Goal is a standalone package CLEO talks to only through its bin
6
+ * (T13791 owner decisions). CLEO works fully without it: every function here
7
+ * reports absence instead of throwing.
8
+ *
9
+ * Bin contracts used here: `cleo-goal --version`, `cleo-goal status --json`
10
+ * and `cleo-goal install --project --json` (idempotent, project-local
11
+ * `.opencode/opencode.json`, never user-global). JSON shapes live in
12
+ * `@cleocode/contracts/goal-runtime.js`; an unknown `schemaVersion` is refused.
13
+ *
14
+ * @task T13819
15
+ * @task T13820
16
+ * @task T13821
17
+ * @epic T13791
18
+ */
19
+ import { execFileSync, spawnSync } from 'node:child_process';
20
+ import { readFileSync, realpathSync } from 'node:fs';
21
+ import { dirname, join } from 'node:path';
22
+ import { engineError, engineSuccess } from '@cleocode/contracts/engine-result.js';
23
+ import { ExitCode } from '@cleocode/contracts/exit-codes.js';
24
+ import { GOAL_RUNTIME_SCHEMA_VERSION, } from '@cleocode/contracts/goal-runtime.js';
25
+ import { findOnPath } from '@cleocode/paths';
26
+ import { joinCommandLine } from '../tasks/command-line.js';
27
+ // shortcut: @cleocode/cleo does not list this as an optionalDependency yet, since an unpublished one 404s on every install; add it as ">=0.8.0 <1.0.0" once the package is on npm (T13819 note).
28
+ /** The npm package that ships the CLEO Goal runtime. */
29
+ export const GOAL_PACKAGE = '@cleocode/goal';
30
+ /** Bin names CLEO Goal installs, primary first (`ocg` = "open cleo goal"). */
31
+ export const GOAL_BINS = ['cleo-goal', 'ocg'];
32
+ /** Install command shown in remedies (the fix of every `E_GOAL_RUNTIME_MISSING`). */
33
+ export const GOAL_INSTALL_COMMAND = `npm i -g ${GOAL_PACKAGE}@latest`;
34
+ /** Harnesses `cleo goal install` accepts, in rollout order (owner decision). */
35
+ export const GOAL_HARNESSES = ['opencode', 'claude', 'codex', 'hermes', 'kimi'];
36
+ /** Run a bin; its trimmed stdout, or `null` on any failure. */
37
+ function run(file, args, env, timeout) {
38
+ try {
39
+ // shortcut: no shell, so a Windows `.cmd` shim fails here; add shell resolution when Windows is supported.
40
+ return execFileSync(file, args, {
41
+ encoding: 'utf-8',
42
+ stdio: ['ignore', 'pipe', 'pipe'],
43
+ timeout,
44
+ env: { ...env },
45
+ }).trim();
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /** The first semver in `text` (`cleo-goal 0.5.0` → `0.5.0`), or `null`. */
52
+ function semver(text) {
53
+ return text?.match(/\d+\.\d+\.\d+[\w.+-]*/)?.[0] ?? null;
54
+ }
55
+ /**
56
+ * Whether `bin` belongs to the `@cleocode/goal` package: the nearest
57
+ * `package.json` above its real path is named {@link GOAL_PACKAGE}. Reads
58
+ * files only, never runs the bin.
59
+ */
60
+ function isGoalPackageBin(bin) {
61
+ try {
62
+ let dir = dirname(realpathSync(bin));
63
+ for (let i = 0; i < 6; i++) {
64
+ try {
65
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
66
+ return pkg.name === GOAL_PACKAGE;
67
+ }
68
+ catch {
69
+ // No readable package.json here; keep walking up.
70
+ }
71
+ const parent = dirname(dir);
72
+ if (parent === dir)
73
+ return false;
74
+ dir = parent;
75
+ }
76
+ }
77
+ catch {
78
+ // Dangling link or unreadable path.
79
+ }
80
+ return false;
81
+ }
82
+ /**
83
+ * Locate the CLEO Goal bin on PATH without running anything: `cleo-goal`,
84
+ * else `ocg` only when it belongs to {@link GOAL_PACKAGE} (`ocg` is a short
85
+ * name another tool may own).
86
+ *
87
+ * @param env - environment whose `PATH` is searched (default `process.env`)
88
+ * @returns the bin, and a foreign `ocg` that was skipped
89
+ */
90
+ export function locateGoalBin(env = process.env) {
91
+ const primary = findOnPath('cleo-goal', { env });
92
+ if (primary !== null)
93
+ return { bin: primary, foreign: null };
94
+ const ocg = findOnPath('ocg', { env });
95
+ if (ocg === null)
96
+ return { bin: null, foreign: null };
97
+ return isGoalPackageBin(ocg) ? { bin: ocg, foreign: null } : { bin: null, foreign: ocg };
98
+ }
99
+ /**
100
+ * Resolve the CLEO Goal bin on PATH: `cleo-goal`, then an `ocg` that belongs
101
+ * to {@link GOAL_PACKAGE}. Synchronous and spawn-free.
102
+ *
103
+ * @param env - environment whose `PATH` is searched (default `process.env`)
104
+ * @returns the absolute bin path, or `null`
105
+ */
106
+ export function resolveGoalBin(env = process.env) {
107
+ return locateGoalBin(env).bin;
108
+ }
109
+ /**
110
+ * The Stop dispatcher's tick command line for the resolved bin
111
+ * (`<bin> tick`, shell-quoted), or `null` when CLEO Goal is not installed.
112
+ *
113
+ * @param env - environment whose `PATH` is searched (default `process.env`)
114
+ * @returns the command line, or `null`
115
+ */
116
+ export function goalTickCommand(env = process.env) {
117
+ const bin = resolveGoalBin(env);
118
+ return bin === null ? null : joinCommandLine([bin, 'tick']);
119
+ }
120
+ /**
121
+ * Inspect the installed CLEO Goal runtime. Local only by default: the npm
122
+ * lookup runs only with `checkUpdates` (no network otherwise). Never throws;
123
+ * an unpublished package or no network leaves `latest` null.
124
+ *
125
+ * @param env - environment for the bin lookup and spawns (default `process.env`)
126
+ * @param options - `checkUpdates` asks npm for the latest version
127
+ * @returns installed path, version, npm latest and whether an update exists
128
+ */
129
+ export function inspectGoalRuntime(env = process.env, options = {}) {
130
+ const { bin, foreign } = locateGoalBin(env);
131
+ const version = semver(bin === null ? null : run(bin, ['--version'], env, 2000));
132
+ const npm = options.checkUpdates === true ? findOnPath('npm', { env }) : null;
133
+ // A 404 (not published yet) or no network both leave `latest` null.
134
+ const latest = semver(npm === null ? null : run(npm, ['view', GOAL_PACKAGE, 'version'], env, 8000));
135
+ return {
136
+ bin,
137
+ foreign,
138
+ version,
139
+ latest,
140
+ updateAvailable: version !== null && latest !== null && version !== latest,
141
+ };
142
+ }
143
+ /** `text` parsed as a JSON object carrying a `schemaVersion`, or `null`. */
144
+ function parseVersioned(text) {
145
+ try {
146
+ const parsed = JSON.parse(text);
147
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? parsed : null;
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ }
153
+ /**
154
+ * Run `bin args` in `cwd` and read its JSON reply per the CLEO Goal exit
155
+ * contract: 0 = the `T` shape, 1 = {@link GoalRuntimeError}, 2 = usage error
156
+ * on stderr. Any `schemaVersion` other than {@link GOAL_RUNTIME_SCHEMA_VERSION}
157
+ * is refused.
158
+ */
159
+ function runGoalJson(bin, args, cwd, env, timeout) {
160
+ // shortcut: no shell, so a Windows `.cmd` shim fails here; add shell resolution when Windows is supported.
161
+ const r = spawnSync(bin, args, { cwd, encoding: 'utf-8', timeout, env: { ...env } });
162
+ const command = `${bin} ${args.join(' ')}`;
163
+ if (r.error !== undefined && 'code' in r.error && r.error.code === 'ETIMEDOUT') {
164
+ // @sync-invariant none:local-only the goal bin did not answer in time; nothing is written
165
+ return engineError('E_GOAL_RUNTIME_TIMEOUT', `${command} did not answer within ${timeout} ms`, {
166
+ exitCode: ExitCode.GENERAL_ERROR,
167
+ fix: `Run ${command} in ${cwd} to see why it is slow`,
168
+ });
169
+ }
170
+ const body = parseVersioned(r.stdout ?? '');
171
+ if ((r.status === 0 || r.status === 1) && body === null) {
172
+ const out = (r.stdout ?? '').trim().slice(0, 200);
173
+ // @sync-invariant none:local-only the goal bin replied with non-JSON; nothing is written
174
+ return engineError('E_GOAL_RUNTIME_FAILED', `${command} replied with non-JSON output: ${out || '(empty)'}`, {
175
+ exitCode: ExitCode.GENERAL_ERROR,
176
+ fix: `${GOAL_INSTALL_COMMAND} (this CLEO reads ${command} as JSON, schemaVersion ${GOAL_RUNTIME_SCHEMA_VERSION})`,
177
+ });
178
+ }
179
+ if (r.status === 2) {
180
+ // @sync-invariant none:input-shape the goal bin rejected CLEO's arguments; nothing is written
181
+ return engineError('E_GOAL_RUNTIME_USAGE', `${command}: ${(r.stderr ?? '').trim() || 'usage error'}`, {
182
+ exitCode: ExitCode.GENERAL_ERROR,
183
+ fix: `${GOAL_INSTALL_COMMAND} (this CLEO needs a cleo-goal that accepts: ${args.join(' ')})`,
184
+ });
185
+ }
186
+ if ((r.status === 0 || r.status === 1) && body?.schemaVersion !== GOAL_RUNTIME_SCHEMA_VERSION) {
187
+ // @sync-invariant none:local-only the goal bin speaks an unknown schema; nothing is written
188
+ return engineError('E_GOAL_RUNTIME_SCHEMA', `${command} replied with schemaVersion ${String(body?.schemaVersion)}; this CLEO understands ${GOAL_RUNTIME_SCHEMA_VERSION}`, {
189
+ exitCode: ExitCode.DEPENDENCY_ERROR,
190
+ fix: 'Upgrade both: npm i -g @cleocode/cleo@latest @cleocode/goal@latest',
191
+ });
192
+ }
193
+ if (r.status === 0)
194
+ return engineSuccess(body);
195
+ const reason = r.status === 1
196
+ ? body.error
197
+ : (r.error?.message ?? `exit ${String(r.status ?? r.signal)}`);
198
+ // @sync-invariant none:local-only the goal bin failed on this machine; no cleo.db write
199
+ return engineError('E_GOAL_RUNTIME_FAILED', `${command} failed: ${reason}`, {
200
+ exitCode: ExitCode.GENERAL_ERROR,
201
+ fix: `Run ${command} in ${cwd} to see the full error`,
202
+ });
203
+ }
204
+ /** The missing-bin failure, with the install command as the fix. */
205
+ function goalRuntimeMissing() {
206
+ // @sync-invariant none:local-only the goal bin is absent on this machine; nothing is written
207
+ return engineError('E_GOAL_RUNTIME_MISSING', `CLEO Goal is not installed: neither ${GOAL_BINS.join(' nor ')} is on PATH`, { exitCode: ExitCode.DEPENDENCY_ERROR, fix: GOAL_INSTALL_COMMAND });
208
+ }
209
+ /**
210
+ * Read the live goal through `cleo-goal status --json` in the project. A
211
+ * project with no goal is a success with `status: 'none'`.
212
+ *
213
+ * @param projectRoot - absolute project root (the bin's cwd)
214
+ * @param env - environment for the bin lookup and spawn (default `process.env`)
215
+ * @param timeoutMs - deadline for the bin (default 15 s; `cleo doctor` passes 2 s)
216
+ * @returns the status shape, or a failure (`E_GOAL_RUNTIME_MISSING`,
217
+ * `E_GOAL_RUNTIME_SCHEMA`, `E_GOAL_RUNTIME_USAGE`, `E_GOAL_RUNTIME_TIMEOUT`,
218
+ * `E_GOAL_RUNTIME_FAILED`)
219
+ */
220
+ export function readGoalStatus(projectRoot, env = process.env, timeoutMs = 15_000) {
221
+ const bin = resolveGoalBin(env);
222
+ if (bin === null)
223
+ return goalRuntimeMissing();
224
+ return runGoalJson(bin, ['status', '--json'], projectRoot, env, timeoutMs);
225
+ }
226
+ /**
227
+ * Register CLEO Goal with one harness for this project. Writes only
228
+ * project-level config, never `~/.claude`, `~/.codex` or `~/.config`.
229
+ *
230
+ * - `opencode`: runs `cleo-goal install --project --json` in the project.
231
+ * - `claude` / `codex`: installs only the project Stop dispatcher (`cleo hook
232
+ * stop`, T13796), which runs the goal tick, whatever the heavy-command mode;
233
+ * no separate Stop hook and no `PreToolUse` hook.
234
+ * - `hermes` / `kimi`: `E_NOT_IMPLEMENTED` until the pilot (T13822).
235
+ *
236
+ * @param projectRoot - absolute project root
237
+ * @param harness - harness to register with
238
+ * @param env - environment for the bin lookup and spawn (default `process.env`)
239
+ * @returns the registration result, or a failure with a remedy
240
+ */
241
+ export async function installGoalForHarness(projectRoot, harness, env = process.env) {
242
+ if (!GOAL_HARNESSES.includes(harness)) {
243
+ // @sync-invariant none:input-shape unknown --harness value; nothing is written
244
+ return engineError('E_VALIDATION', `Unknown harness '${harness}'`, {
245
+ exitCode: ExitCode.VALIDATION_ERROR,
246
+ fix: `Use --harness ${GOAL_HARNESSES.join('|')}`,
247
+ });
248
+ }
249
+ const h = harness;
250
+ if (h === 'hermes' || h === 'kimi') {
251
+ // @sync-invariant none:input-shape harness not supported yet; nothing is written
252
+ return engineError('E_NOT_IMPLEMENTED', `cleo goal install --harness ${h} is not implemented yet; harness pilots are tracked in T13822`, { exitCode: ExitCode.GENERAL_ERROR, fix: 'cleo show T13822' });
253
+ }
254
+ const bin = resolveGoalBin(env);
255
+ if (bin === null)
256
+ return goalRuntimeMissing();
257
+ if (h === 'opencode') {
258
+ const installed = runGoalJson(bin, ['install', '--project', '--json'], projectRoot, env, 60_000);
259
+ if (installed.success)
260
+ return engineSuccess({ harness: h, bin, goal: installed.data });
261
+ // @sync-invariant none:local-only the goal bin failed writing project config; no cleo.db write
262
+ return engineError('E_GOAL_INSTALL_FAILED', installed.error.message, {
263
+ exitCode: installed.error.exitCode ?? ExitCode.GENERAL_ERROR,
264
+ ...(installed.error.fix === undefined ? {} : { fix: installed.error.fix }),
265
+ });
266
+ }
267
+ const { loadHeavyHookDelivery } = await import('../resources/heavy-command-hook-delivery.js');
268
+ const sync = (await loadHeavyHookDelivery()).syncProjectStopDispatch;
269
+ if (sync === undefined) {
270
+ // @sync-invariant none:local-only the installed adapters package predates Stop-only delivery; nothing is written
271
+ return engineError('E_GOAL_INSTALL_FAILED', '@cleocode/adapters is too old to install the Stop dispatcher alone', {
272
+ exitCode: ExitCode.DEPENDENCY_ERROR,
273
+ fix: 'npm i -g @cleocode/cleo@latest',
274
+ });
275
+ }
276
+ const delivery = await sync(projectRoot, h === 'claude' ? 'claude-code' : 'codex');
277
+ if (delivery.status === 'failed' ||
278
+ delivery.status === 'needs-consent' ||
279
+ delivery.status === 'skipped') {
280
+ // @sync-invariant none:local-only the project hook file could not be written; no cleo.db write
281
+ return engineError('E_GOAL_INSTALL_FAILED', `Stop dispatcher not installed in ${delivery.target}: ${delivery.reason ?? delivery.status}`, {
282
+ exitCode: ExitCode.GENERAL_ERROR,
283
+ details: delivery,
284
+ ...(delivery.remedy === undefined ? {} : { fix: delivery.remedy }),
285
+ });
286
+ }
287
+ return engineSuccess({ harness: h, bin, delivery });
288
+ }
289
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/goal/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `cleo goal status` — the project's drafted goals, the CLEO tasks they serve,
3
+ * and the live run (T13821).
4
+ *
5
+ * The live run comes only from `cleo-goal status --json` (versioned; an
6
+ * unknown schema version is refused), never from the runtime's internal files.
7
+ * The drafted goals come from their `goal.yaml`, the contract CLEO writes.
8
+ * Read-only.
9
+ *
10
+ * @epic T13791
11
+ * @task T13821
12
+ */
13
+ import type { EngineResult, GoalDraftEntry, GoalStatusReport } from '@cleocode/contracts';
14
+ /**
15
+ * The drafted goals of a project, from each `.opencode/goals/<slug>/goal.yaml`.
16
+ *
17
+ * @param projectRoot - Project root.
18
+ * @returns One entry per goal directory holding a `goal.yaml`, by slug.
19
+ */
20
+ export declare function draftedGoals(projectRoot: string): GoalDraftEntry[];
21
+ /**
22
+ * The drafted goals and the live run. A missing CLEO Goal bin is a report
23
+ * (`runtime: 'not-installed'` with the install commands), not a failure; an
24
+ * unknown status schema or a failing bin is a failure.
25
+ *
26
+ * @param opts - Project root, a task to filter by, and the environment.
27
+ * @returns The report, or the runtime's refusal.
28
+ * @example
29
+ * ```ts
30
+ * const r = goalStatus({ taskId: 'T123' });
31
+ * if (r.success) r.data.live?.status; // the live run, when there is one
32
+ * ```
33
+ */
34
+ export declare function goalStatus(opts?: {
35
+ projectRoot?: string;
36
+ taskId?: string;
37
+ env?: Readonly<Record<string, string | undefined>>;
38
+ }): EngineResult<GoalStatusReport>;
39
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/goal/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA4B1F;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAoBlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,IAAI,GAAE;IACJ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CAC/C,GACL,YAAY,CAAC,gBAAgB,CAAC,CAiBhC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `cleo goal status` — the project's drafted goals, the CLEO tasks they serve,
3
+ * and the live run (T13821).
4
+ *
5
+ * The live run comes only from `cleo-goal status --json` (versioned; an
6
+ * unknown schema version is refused), never from the runtime's internal files.
7
+ * The drafted goals come from their `goal.yaml`, the contract CLEO writes.
8
+ * Read-only.
9
+ *
10
+ * @epic T13791
11
+ * @task T13821
12
+ */
13
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { engineSuccess } from '@cleocode/contracts/engine-result.js';
16
+ import { parse as parseYaml } from 'yaml';
17
+ import { getProjectRoot } from '../paths.js';
18
+ import { GOAL_INSTALL_COMMAND, readGoalStatus } from './runtime.js';
19
+ const record = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
20
+ /** The tasks a contract serves: `cleo.tasks` and every task a criterion `proves`. */
21
+ function contractTasks(contract) {
22
+ const ids = new Set();
23
+ const cleo = contract.cleo;
24
+ if (record(cleo) && Array.isArray(cleo.tasks)) {
25
+ for (const t of cleo.tasks) {
26
+ const id = typeof t === 'string' ? t : record(t) && typeof t.id === 'string' ? t.id : '';
27
+ if (id)
28
+ ids.add(id);
29
+ }
30
+ }
31
+ for (const c of Array.isArray(contract.criteria) ? contract.criteria : []) {
32
+ if (!record(c) || !Array.isArray(c.proves))
33
+ continue;
34
+ for (const ref of c.proves)
35
+ if (typeof ref === 'string')
36
+ ids.add(ref.split('#')[0] ?? ref);
37
+ }
38
+ return [...ids].sort();
39
+ }
40
+ /**
41
+ * The drafted goals of a project, from each `.opencode/goals/<slug>/goal.yaml`.
42
+ *
43
+ * @param projectRoot - Project root.
44
+ * @returns One entry per goal directory holding a `goal.yaml`, by slug.
45
+ */
46
+ export function draftedGoals(projectRoot) {
47
+ const dir = join(projectRoot, '.opencode', 'goals');
48
+ if (!existsSync(dir))
49
+ return [];
50
+ const entries = [];
51
+ for (const d of readdirSync(dir, { withFileTypes: true })) {
52
+ const contractPath = join(dir, d.name, 'goal.yaml');
53
+ if (!d.isDirectory() || !existsSync(contractPath))
54
+ continue;
55
+ const entry = { slug: d.name, title: null, tasks: [] };
56
+ try {
57
+ const contract = parseYaml(readFileSync(contractPath, 'utf8'));
58
+ if (record(contract)) {
59
+ entry.title = typeof contract.title === 'string' ? contract.title : null;
60
+ entry.tasks = contractTasks(contract);
61
+ }
62
+ }
63
+ catch (err) {
64
+ entry.problem = `goal.yaml does not parse: ${err.message}`;
65
+ }
66
+ entries.push(entry);
67
+ }
68
+ return entries.sort((a, b) => a.slug.localeCompare(b.slug));
69
+ }
70
+ /**
71
+ * The drafted goals and the live run. A missing CLEO Goal bin is a report
72
+ * (`runtime: 'not-installed'` with the install commands), not a failure; an
73
+ * unknown status schema or a failing bin is a failure.
74
+ *
75
+ * @param opts - Project root, a task to filter by, and the environment.
76
+ * @returns The report, or the runtime's refusal.
77
+ * @example
78
+ * ```ts
79
+ * const r = goalStatus({ taskId: 'T123' });
80
+ * if (r.success) r.data.live?.status; // the live run, when there is one
81
+ * ```
82
+ */
83
+ export function goalStatus(opts = {}) {
84
+ const root = opts.projectRoot ?? getProjectRoot();
85
+ const goals = draftedGoals(root).filter((g) => !opts.taskId || g.tasks.includes(opts.taskId));
86
+ const live = readGoalStatus(root, opts.env);
87
+ if (!live.success) {
88
+ if (live.error.code !== 'E_GOAL_RUNTIME_MISSING')
89
+ return live;
90
+ return engineSuccess({
91
+ runtime: 'not-installed',
92
+ install: [GOAL_INSTALL_COMMAND, 'cleo goal install --harness opencode'],
93
+ live: null,
94
+ goals,
95
+ });
96
+ }
97
+ // With --task, the live run counts only when it is one of that task's goals.
98
+ const relevant = !opts.taskId || (live.data.goal !== null && goals.some((g) => g.slug === live.data.goal));
99
+ return engineSuccess({ runtime: 'installed', live: relevant ? live.data : null, goals });
100
+ }
101
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/goal/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAIpE,MAAM,MAAM,GAAG,CAAC,CAAmB,EAAgC,EAAE,CACnE,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAE3D,qFAAqF;AACrF,SAAS,aAAa,CAAC,QAAiC;IACtD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzF,IAAI,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC1E,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;YAAE,SAAS;QACrD,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,MAAM;YAAE,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QACpD,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS;QAC5D,MAAM,KAAK,GAAmB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QACvE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,SAAS,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAS,CAAC;YACvE,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrB,KAAK,CAAC,KAAK,GAAG,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;gBACzE,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,CAAC,OAAO,GAAG,6BAA8B,GAAa,CAAC,OAAO,EAAE,CAAC;QACxE,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CACxB,OAII,EAAE;IAEN,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,cAAc,EAAE,CAAC;IAClD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9F,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,wBAAwB;YAAE,OAAO,IAAI,CAAC;QAC9D,OAAO,aAAa,CAAC;YACnB,OAAO,EAAE,eAAe;YACxB,OAAO,EAAE,CAAC,oBAAoB,EAAE,sCAAsC,CAAC;YACvE,IAAI,EAAE,IAAI;YACV,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IACD,6EAA6E;IAC7E,MAAM,QAAQ,GACZ,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5F,OAAO,aAAa,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC3F,CAAC"}
@@ -1,10 +1,15 @@
1
- import { type HookExecutionReceipt, type HookExecutor, type HookInvocation, type HookOutcome } from '@cleocode/contracts/project-hooks.js';
1
+ import { type HookExecutionReceipt, type HookExecutor, type HookInvocation, type HookOutcome, type ProjectHookBinding } from '@cleocode/contracts/project-hooks.js';
2
2
  /** Execute activated definitions in source order; internal faults always allow locally. */
3
3
  export declare function executeProjectHooks(invocation: HookInvocation, id?: string, activationRoot?: string, signal?: AbortSignal): Promise<HookOutcome[]>;
4
4
  /** Contracts port for worktree and other consumers; no internal registry coupling. */
5
5
  export declare const projectHookExecutor: HookExecutor;
6
6
  /** Bind worktree inheritance to an explicitly activated source checkout. */
7
7
  export declare function createProjectHookExecutor(activationRoot: string): HookExecutor;
8
- /** Read only a bounded last execution record and validate its safe shape. */
9
- export declare function readLastProjectHookExecution(cwd: string): Promise<HookExecutionReceipt | undefined>;
8
+ /**
9
+ * Read a bounded redacted execution record, validating its shape and optional binding.
10
+ * @param cwd - Active checkout whose private receipts are inspected.
11
+ * @param binding - Select a retained source/event result; omitted reads the overall latest result.
12
+ * @returns The matching record, or undefined when absent, malformed or mismatched.
13
+ */
14
+ export declare function readLastProjectHookExecution(cwd: string, binding?: ProjectHookBinding): Promise<HookExecutionReceipt | undefined>;
10
15
  //# sourceMappingURL=project-runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-runner.d.ts","sourceRoot":"","sources":["../../src/hooks/project-runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAIjB,MAAM,sCAAsC,CAAC;AAgS9C,2FAA2F;AAC3F,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,cAAc,EAC1B,EAAE,CAAC,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,EACvB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,EAAE,CAAC,CAmKxB;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,EAAE,YAA+C,CAAC;AAElF,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY,CAE9E;AAED,6EAA6E;AAC7E,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAW3C"}
1
+ {"version":3,"file":"project-runner.d.ts","sourceRoot":"","sources":["../../src/hooks/project-runner.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,cAAc,EAEnB,KAAK,WAAW,EAEhB,KAAK,kBAAkB,EAIxB,MAAM,sCAAsC,CAAC;AAwX9C,2FAA2F;AAC3F,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,cAAc,EAC1B,EAAE,CAAC,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,EACvB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,EAAE,CAAC,CAwKxB;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,EAAE,YAA+C,CAAC;AAElF,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,YAAY,CAE9E;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAa3C"}
@@ -1,6 +1,10 @@
1
1
  /** Ordered project executor, separate from the best-effort internal registry (T13343). */
2
- import { spawn } from 'node:child_process';
3
- import { ProjectHookVerdictSchema, } from '@cleocode/contracts/project-hooks.js';
2
+ import { spawn, spawnSync } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
4
+ import { existsSync } from 'node:fs';
5
+ import { lstat } from 'node:fs/promises';
6
+ import { join, relative } from 'node:path';
7
+ import { HookNativeSessionSchema, ProjectHookVerdictSchema, } from '@cleocode/contracts/project-hooks.js';
4
8
  import { discoveryEnv } from '../git/work-tree.js';
5
9
  import { atomicWrite } from '../store/atomic.js';
6
10
  import { computeHookActivation, HookInputUnavailableError, hookBindingsDigest, inspectProjectHooks, matchesProjectHook, readProjectHookRecord, readProjectHooksLocalState, readProjectHooksManifest, resolveProjectHookContext, resolveProjectHookExecutable, resolveProjectHookFile, resolveProjectHookStateFile, } from './project-state.js';
@@ -9,6 +13,78 @@ const MAX_INPUT_BYTES = 65536;
9
13
  const MAX_OUTPUT_BYTES = 65536;
10
14
  const MAX_INVOCATION_MS = 120000;
11
15
  const REACTIVATE = 'Hook inputs changed since activation; review them, then run `cleo hook sync --activate`.';
16
+ /** Fixed-length per-binding slot; event text never becomes a filesystem path. */
17
+ function executionRecordName(binding) {
18
+ const hash = createHash('sha256').update(binding.event).digest('hex');
19
+ return `last-execution-${binding.source}-${hash}.json`;
20
+ }
21
+ /**
22
+ * Sandbox receipt fallback (T13775): Codex workspace-write keeps `.git` read-only, so a receipt the
23
+ * Git-private directory refuses goes to this self-ignoring checkout directory instead.
24
+ */
25
+ const FALLBACK_RECEIPTS = ['.cleo', 'hook-receipts'];
26
+ /** Whether Git tracks a checkout path; an unanswerable probe counts as tracked. */
27
+ function trackedByGit(context, file) {
28
+ return (spawnSync('git', [
29
+ '-C',
30
+ context.projectRoot,
31
+ 'ls-files',
32
+ '--error-unmatch',
33
+ '--',
34
+ relative(context.projectRoot, file),
35
+ ], { stdio: 'ignore', timeout: 5000, env: discoveryEnv() }).status !== 1);
36
+ }
37
+ /**
38
+ * The lexical fallback path for one slot. Every component is checked with lstat before anything
39
+ * resolves it, so a symlinked slot or parent can never redirect a write, and a tracked file is
40
+ * never a destination or a source.
41
+ */
42
+ async function fallbackReceiptFile(context, name) {
43
+ if (!/^(?:[a-z0-9-]+\.json|\.gitignore)$/.test(name))
44
+ // @sync-invariant none:local-only Reject unsafe machine-local hook records or executable inputs; no synced rows are written.
45
+ throw new Error('HOOK_STATE_PATH_INVALID');
46
+ let path = context.projectRoot;
47
+ for (const part of [...FALLBACK_RECEIPTS, name]) {
48
+ path = join(path, part);
49
+ const info = await lstat(path).catch(() => undefined);
50
+ if (info && (info.isSymbolicLink() || (part === name ? !info.isFile() : !info.isDirectory())))
51
+ // @sync-invariant none:local-only Reject unsafe machine-local hook records or executable inputs; no synced rows are written.
52
+ throw new Error('HOOK_STATE_SYMLINK');
53
+ }
54
+ if (trackedByGit(context, path))
55
+ // @sync-invariant none:local-only Reject unsafe machine-local hook records or executable inputs; no synced rows are written.
56
+ throw new Error('HOOK_STATE_TRACKED');
57
+ return path;
58
+ }
59
+ /** Write one receipt slot to the Git-private directory, else the sandbox-writable fallback. */
60
+ async function writeReceipt(context, name, serialized) {
61
+ try {
62
+ await atomicWrite(await resolveProjectHookStateFile(context, name), serialized, {
63
+ mode: 0o600,
64
+ });
65
+ return;
66
+ }
67
+ catch {
68
+ // Fall through to the checkout directory a workspace-write sandbox leaves writable.
69
+ }
70
+ await atomicWrite(await fallbackReceiptFile(context, name), serialized, { mode: 0o600 });
71
+ const ignore = await fallbackReceiptFile(context, '.gitignore');
72
+ if (!existsSync(ignore))
73
+ await atomicWrite(ignore, '*\n');
74
+ }
75
+ /** Resolve and read one receipt location; any fault leaves only this candidate out. */
76
+ async function readReceipt(locate) {
77
+ try {
78
+ const file = await locate();
79
+ if ((await lstat(file)).isSymbolicLink())
80
+ return undefined;
81
+ const { HookExecutionReceiptSchema } = await import('@cleocode/contracts/project-hooks.js');
82
+ return HookExecutionReceiptSchema.parse(JSON.parse(await readProjectHookRecord(file, MAX_OUTPUT_BYTES)));
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }
12
88
  function fault(invocation, code, id = 'cleo.project-hooks') {
13
89
  return {
14
90
  id,
@@ -340,18 +416,27 @@ export async function executeProjectHooks(invocation, id, activationRoot, signal
340
416
  }
341
417
  outcomes.push(await executeOne(hook, invocation, remainingMs, signal));
342
418
  }
343
- // A last-result record is bounded and contains only executor-owned metadata.
419
+ // Last-result records are bounded and contain only executor-owned metadata.
420
+ // Preserve the agent binding even when the subsequent Git hook records its result.
344
421
  // A failed receipt write is visible as infrastructure fault, never a fake pass.
345
422
  try {
423
+ const nativeSession = invocation.nativeSession
424
+ ? HookNativeSessionSchema.parse(invocation.nativeSession)
425
+ : undefined;
346
426
  const receipt = {
347
427
  schemaVersion: 1,
428
+ ...nativeSession,
348
429
  manifestHash: approved?.manifestHash ?? '',
349
430
  source: invocation.source,
350
431
  event: invocation.event.slice(0, 128),
351
432
  recordedAt: new Date().toISOString(),
352
433
  outcomes: outcomes.map(({ message: _message, ...safe }) => safe),
353
434
  };
354
- await atomicWrite(await resolveProjectHookStateFile(inspection.context, 'last-execution.json'), `${JSON.stringify(receipt)}\n`, { mode: 0o600 });
435
+ const serialized = `${JSON.stringify(receipt)}\n`;
436
+ // Each source/event keeps its own slot, so a Git write never replaces an agent receipt.
437
+ await writeReceipt(inspection.context, executionRecordName(invocation), serialized);
438
+ // Keep the existing overall-last diagnostic API compatible.
439
+ await writeReceipt(inspection.context, 'last-execution.json', serialized);
355
440
  }
356
441
  catch {
357
442
  outcomes.push(fault(invocation, 'HOOK_RECEIPT_WRITE_FAILED'));
@@ -368,16 +453,21 @@ export const projectHookExecutor = { execute: executeProjectHooks };
368
453
  export function createProjectHookExecutor(activationRoot) {
369
454
  return { execute: (invocation) => executeProjectHooks(invocation, undefined, activationRoot) };
370
455
  }
371
- /** Read only a bounded last execution record and validate its safe shape. */
372
- export async function readLastProjectHookExecution(cwd) {
456
+ /**
457
+ * Read a bounded redacted execution record, validating its shape and optional binding.
458
+ * @param cwd - Active checkout whose private receipts are inspected.
459
+ * @param binding - Select a retained source/event result; omitted reads the overall latest result.
460
+ * @returns The matching record, or undefined when absent, malformed or mismatched.
461
+ */
462
+ export async function readLastProjectHookExecution(cwd, binding) {
373
463
  const inspection = await inspectProjectHooks(cwd);
374
- const file = await resolveProjectHookStateFile(inspection.context, 'last-execution.json');
375
- try {
376
- const { HookExecutionReceiptSchema } = await import('@cleocode/contracts/project-hooks.js');
377
- return HookExecutionReceiptSchema.parse(JSON.parse(await readProjectHookRecord(file, MAX_OUTPUT_BYTES)));
378
- }
379
- catch {
380
- return undefined;
381
- }
464
+ const name = binding ? executionRecordName(binding) : 'last-execution.json';
465
+ // The newer of the Git-private record and the sandbox fallback (T13775) is the latest result.
466
+ const candidates = [
467
+ await readReceipt(() => resolveProjectHookStateFile(inspection.context, name)),
468
+ await readReceipt(() => fallbackReceiptFile(inspection.context, name)),
469
+ ].filter((receipt) => receipt !== undefined &&
470
+ (!binding || (receipt.source === binding.source && receipt.event === binding.event)));
471
+ return candidates.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt))[0];
382
472
  }
383
473
  //# sourceMappingURL=project-runner.js.map