harnery 0.46.0 → 0.47.0

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 (187) hide show
  1. package/README.md +24 -0
  2. package/bin/README.md +10 -0
  3. package/bin/agent-coord.mjs +3 -0
  4. package/bin/agent-hook.mjs +3 -0
  5. package/bin/harn.mjs +3 -0
  6. package/bin/run.mjs +43 -0
  7. package/dist/commander.d.ts +18 -6
  8. package/dist/commander.d.ts.map +1 -1
  9. package/dist/commander.js +74 -2
  10. package/dist/commands/agents.d.ts +25 -0
  11. package/dist/commands/agents.d.ts.map +1 -1
  12. package/dist/commands/agents.js +70 -27
  13. package/dist/commands/autoupdate.d.ts +12 -0
  14. package/dist/commands/autoupdate.d.ts.map +1 -0
  15. package/dist/commands/autoupdate.js +190 -0
  16. package/dist/commands/computer.d.ts +45 -0
  17. package/dist/commands/computer.d.ts.map +1 -0
  18. package/dist/commands/computer.js +509 -0
  19. package/dist/commands/docs.js +3 -1
  20. package/dist/commands/fetch.js +11 -1
  21. package/dist/commands/grep.d.ts +72 -0
  22. package/dist/commands/grep.d.ts.map +1 -1
  23. package/dist/commands/grep.js +200 -47
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +15 -9
  26. package/dist/commands/rm.js +2 -2
  27. package/dist/commands/web.d.ts.map +1 -1
  28. package/dist/commands/web.js +2 -0
  29. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  30. package/dist/core/agents/canonical-emit.js +2 -3
  31. package/dist/core/agents/coord-bin.d.ts +3 -0
  32. package/dist/core/agents/coord-bin.d.ts.map +1 -1
  33. package/dist/core/agents/coord-bin.js +6 -0
  34. package/dist/core/agents/coord-client.d.ts.map +1 -1
  35. package/dist/core/agents/coord-client.js +6 -2
  36. package/dist/core/agents/render/prompt-context.d.ts +55 -7
  37. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  38. package/dist/core/agents/render/prompt-context.js +122 -48
  39. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  40. package/dist/core/agents/rules/claim-conflict.js +36 -16
  41. package/dist/core/agents/rules/stop-hook.d.ts +6 -0
  42. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  43. package/dist/core/agents/rules/stop-hook.js +20 -1
  44. package/dist/core/agents/state/proc-start.d.ts +3 -1
  45. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  46. package/dist/core/agents/state/proc-start.js +26 -4
  47. package/dist/core/autoupdate/engine.d.ts +97 -0
  48. package/dist/core/autoupdate/engine.d.ts.map +1 -0
  49. package/dist/core/autoupdate/engine.js +548 -0
  50. package/dist/core/autoupdate/fingerprint.d.ts +16 -0
  51. package/dist/core/autoupdate/fingerprint.d.ts.map +1 -0
  52. package/dist/core/autoupdate/fingerprint.js +73 -0
  53. package/dist/core/autoupdate/git.d.ts +30 -0
  54. package/dist/core/autoupdate/git.d.ts.map +1 -0
  55. package/dist/core/autoupdate/git.js +82 -0
  56. package/dist/core/autoupdate/index.d.ts +6 -0
  57. package/dist/core/autoupdate/index.d.ts.map +1 -0
  58. package/dist/core/autoupdate/index.js +4 -0
  59. package/dist/core/autoupdate/state.d.ts +59 -0
  60. package/dist/core/autoupdate/state.d.ts.map +1 -0
  61. package/dist/core/autoupdate/state.js +214 -0
  62. package/dist/core/autoupdate/types.d.ts +113 -0
  63. package/dist/core/autoupdate/types.d.ts.map +1 -0
  64. package/dist/core/autoupdate/types.js +35 -0
  65. package/dist/core/config.d.ts +53 -1
  66. package/dist/core/config.d.ts.map +1 -1
  67. package/dist/core/config.js +114 -1
  68. package/dist/core/events/v3/bootstrap.js +1 -0
  69. package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
  70. package/dist/core/events/v3/runtime-build.js +1 -0
  71. package/dist/core/hooks/adapter/wiring.d.ts +12 -2
  72. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  73. package/dist/core/hooks/adapter/wiring.js +38 -8
  74. package/dist/core/hooks/autoupdate-cue.d.ts +29 -0
  75. package/dist/core/hooks/autoupdate-cue.d.ts.map +1 -0
  76. package/dist/core/hooks/autoupdate-cue.js +53 -0
  77. package/dist/core/hooks/cli.js +52 -4
  78. package/dist/core/hooks/effects/index.js +4 -4
  79. package/dist/core/hooks/resolve/coord-root.js +1 -0
  80. package/dist/core/hooks/resolve/transcript.d.ts +12 -0
  81. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  82. package/dist/core/hooks/resolve/transcript.js +54 -0
  83. package/dist/core/semantic/contract.d.ts +3 -3
  84. package/dist/core/semantic/service.d.ts.map +1 -1
  85. package/dist/core/semantic/service.js +4 -2
  86. package/dist/core/storage/modes.d.ts +1 -1
  87. package/dist/core/storage/modes.d.ts.map +1 -1
  88. package/dist/core/storage/modes.js +5 -1
  89. package/dist/core/workflow/durable-record.d.ts +2 -0
  90. package/dist/core/workflow/durable-record.d.ts.map +1 -1
  91. package/dist/core/workflow/durable-record.js +13 -1
  92. package/dist/core/workflow/engine.js +4 -0
  93. package/dist/core/workflow/spawn-claude.d.ts +7 -0
  94. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  95. package/dist/core/workflow/spawn-claude.js +16 -4
  96. package/dist/core/workflow/specialists.d.ts.map +1 -1
  97. package/dist/core/workflow/specialists.js +1 -0
  98. package/dist/core/workflow/types.d.ts +10 -0
  99. package/dist/core/workflow/types.d.ts.map +1 -1
  100. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  101. package/dist/core/workflow/workspaces/leases.js +1 -9
  102. package/dist/lib/computer/cua.d.ts +50 -0
  103. package/dist/lib/computer/cua.d.ts.map +1 -0
  104. package/dist/lib/computer/cua.js +474 -0
  105. package/dist/lib/computer/host.d.ts +19 -0
  106. package/dist/lib/computer/host.d.ts.map +1 -0
  107. package/dist/lib/computer/host.js +72 -0
  108. package/dist/lib/computer/lease.d.ts +13 -0
  109. package/dist/lib/computer/lease.d.ts.map +1 -0
  110. package/dist/lib/computer/lease.js +269 -0
  111. package/dist/lib/computer/types.d.ts +163 -0
  112. package/dist/lib/computer/types.d.ts.map +1 -0
  113. package/dist/lib/computer/types.js +10 -0
  114. package/dist/lib/docs-metadata-v2.d.ts.map +1 -1
  115. package/dist/lib/docs-metadata-v2.js +6 -5
  116. package/dist/lib/exec.d.ts.map +1 -1
  117. package/dist/lib/exec.js +1 -0
  118. package/dist/lib/guarded-remove.d.ts.map +1 -1
  119. package/dist/lib/guarded-remove.js +5 -4
  120. package/dist/lib/headless/index.d.ts +33 -0
  121. package/dist/lib/headless/index.d.ts.map +1 -1
  122. package/dist/lib/headless/index.js +38 -2
  123. package/dist/lib/http/client.d.ts +2 -9
  124. package/dist/lib/http/client.d.ts.map +1 -1
  125. package/dist/lib/http/client.js +30 -9
  126. package/dist/lib/instructions/apply.d.ts +9 -0
  127. package/dist/lib/instructions/apply.d.ts.map +1 -1
  128. package/dist/lib/instructions/apply.js +92 -14
  129. package/dist/lib/instructions/git-hooks.d.ts.map +1 -1
  130. package/dist/lib/instructions/git-hooks.js +8 -4
  131. package/dist/lib/instructions/templates.d.ts +11 -0
  132. package/dist/lib/instructions/templates.d.ts.map +1 -1
  133. package/dist/lib/instructions/templates.js +81 -1
  134. package/package.json +5 -5
  135. package/schemas/config.schema.json +89 -1
  136. package/src/commander.ts +98 -8
  137. package/src/commands/agents.ts +100 -32
  138. package/src/commands/autoupdate.ts +250 -0
  139. package/src/commands/computer.ts +682 -0
  140. package/src/commands/docs.ts +3 -1
  141. package/src/commands/fetch.ts +11 -1
  142. package/src/commands/grep.ts +274 -59
  143. package/src/commands/init.ts +17 -7
  144. package/src/commands/rm.ts +2 -2
  145. package/src/commands/web.ts +2 -0
  146. package/src/core/agents/canonical-emit.ts +2 -3
  147. package/src/core/agents/coord-bin.ts +30 -0
  148. package/src/core/agents/coord-client.ts +6 -2
  149. package/src/core/agents/render/prompt-context.ts +152 -53
  150. package/src/core/agents/rules/claim-conflict.ts +50 -18
  151. package/src/core/agents/rules/stop-hook.ts +28 -1
  152. package/src/core/agents/state/proc-start.ts +29 -5
  153. package/src/core/autoupdate/engine.ts +691 -0
  154. package/src/core/autoupdate/fingerprint.ts +89 -0
  155. package/src/core/autoupdate/git.ts +120 -0
  156. package/src/core/autoupdate/index.ts +29 -0
  157. package/src/core/autoupdate/state.ts +269 -0
  158. package/src/core/autoupdate/types.ts +135 -0
  159. package/src/core/config.ts +142 -1
  160. package/src/core/events/v3/bootstrap.ts +1 -0
  161. package/src/core/events/v3/runtime-build.ts +1 -0
  162. package/src/core/hooks/adapter/wiring.ts +51 -6
  163. package/src/core/hooks/autoupdate-cue.ts +72 -0
  164. package/src/core/hooks/cli.ts +54 -1
  165. package/src/core/hooks/effects/index.ts +4 -4
  166. package/src/core/hooks/resolve/coord-root.ts +1 -0
  167. package/src/core/hooks/resolve/transcript.ts +61 -0
  168. package/src/core/semantic/service.ts +6 -2
  169. package/src/core/storage/modes.ts +7 -1
  170. package/src/core/workflow/durable-record.ts +15 -1
  171. package/src/core/workflow/engine.ts +3 -0
  172. package/src/core/workflow/spawn-claude.ts +16 -4
  173. package/src/core/workflow/specialists.ts +1 -0
  174. package/src/core/workflow/types.ts +10 -0
  175. package/src/core/workflow/workspaces/leases.ts +1 -9
  176. package/src/lib/computer/cua.ts +572 -0
  177. package/src/lib/computer/host.ts +87 -0
  178. package/src/lib/computer/lease.ts +299 -0
  179. package/src/lib/computer/types.ts +177 -0
  180. package/src/lib/docs-metadata-v2.ts +24 -5
  181. package/src/lib/exec.ts +1 -0
  182. package/src/lib/guarded-remove.ts +5 -3
  183. package/src/lib/headless/index.ts +66 -2
  184. package/src/lib/http/client.ts +33 -19
  185. package/src/lib/instructions/apply.ts +100 -12
  186. package/src/lib/instructions/git-hooks.ts +8 -4
  187. package/src/lib/instructions/templates.ts +95 -1
@@ -0,0 +1,250 @@
1
+ // `autoupdate`: inspect and drive project self-update (ADR 0229). The engine
2
+ // lives in src/core/autoupdate/; this command owns flag parsing, human/JSON
3
+ // rendering, and exit codes. `run --background` is what the activity tick
4
+ // launches; people and agents use `status`, `check`, and `run`.
5
+
6
+ import type { Command } from "commander";
7
+ import type { EmitContext } from "../commander.ts";
8
+ import { resolveCoordRoot } from "../core/agents/coord-client.ts";
9
+ import {
10
+ type AutoUpdateDeps,
11
+ autoUpdateStatus,
12
+ checkForUpdate,
13
+ runAutoUpdate,
14
+ } from "../core/autoupdate/engine.ts";
15
+ import { readAutoUpdateState } from "../core/autoupdate/state.ts";
16
+ import {
17
+ AUTOUPDATE_DEFAULTS,
18
+ EXIT_NEEDS_PERSON,
19
+ type RunOutcome,
20
+ } from "../core/autoupdate/types.ts";
21
+ import { readAutoUpdateConfig } from "../core/config.ts";
22
+
23
+ const DAY_MS = 24 * 60 * 60 * 1000;
24
+
25
+ export interface AutoUpdateCommandOptions {
26
+ /** Project root override (tests). */
27
+ root?: () => string;
28
+ deps?: AutoUpdateDeps;
29
+ }
30
+
31
+ function emitLine(emit: EmitContext, s: string): void {
32
+ emit.text(s.endsWith("\n") ? s : `${s}\n`);
33
+ }
34
+
35
+ function short(sha: string | null | undefined): string {
36
+ return sha ? sha.slice(0, 7) : "none";
37
+ }
38
+
39
+ function formatDuration(ms: number): string {
40
+ const minutes = Math.round(ms / 60_000);
41
+ if (minutes < 1) return "under a minute";
42
+ if (minutes < 120) return `${minutes} min`;
43
+ return `${Math.round(minutes / 60)} h`;
44
+ }
45
+
46
+ export function exitCodeForOutcome(outcome: RunOutcome): number {
47
+ if (outcome === "failed") return 1;
48
+ if (outcome === "blocked") return EXIT_NEEDS_PERSON;
49
+ return 0;
50
+ }
51
+
52
+ export function registerAutoUpdateCommand(
53
+ program: Command,
54
+ emit: EmitContext,
55
+ binName: string,
56
+ options: AutoUpdateCommandOptions = {},
57
+ ): void {
58
+ const root = options.root ?? (() => resolveCoordRoot() ?? process.cwd());
59
+ const deps = options.deps ?? {};
60
+
61
+ const guard =
62
+ <A extends unknown[]>(fn: (...args: A) => void | Promise<void>) =>
63
+ async (...args: A) => {
64
+ try {
65
+ await fn(...args);
66
+ } catch (err) {
67
+ emit.error({ code: "autoupdate_error", message: (err as Error)?.message ?? String(err) });
68
+ emit.setExitCode(1);
69
+ }
70
+ };
71
+
72
+ const cmd = program
73
+ .command("autoupdate")
74
+ .description(
75
+ "Project self-update: activity checks the upstream branch and watched paths, and runs " +
76
+ "the steps configured in .harnery/config.jsonc `autoUpdate` only when something changed.",
77
+ )
78
+ .addHelpText(
79
+ "after",
80
+ `\nEnable per project with "autoUpdate": { "enabled": true, "steps": [[...]] } in .harnery/config.jsonc.\n` +
81
+ `Exit codes: 0 current/updated · 1 failed · ${EXIT_NEEDS_PERSON} blocked (needs a person).\n` +
82
+ `Start with \`${binName} autoupdate status\`.`,
83
+ );
84
+
85
+ cmd
86
+ .command("status")
87
+ .description(
88
+ "Show configuration, the last run, the converged head, notices, and the next check.",
89
+ )
90
+ .option("--json", "Emit the status as JSON.")
91
+ .action(
92
+ guard((opts: { json?: boolean }) => {
93
+ const status = autoUpdateStatus(root(), deps);
94
+ const now = (deps.now ?? Date.now)();
95
+ const recent = status.state.notices.filter((n) => now - Date.parse(n.at) < DAY_MS).length;
96
+ if (opts.json) {
97
+ emit.data({
98
+ enabled: status.config?.enabled ?? false,
99
+ config: status.config,
100
+ lastRun: status.state.lastRun ?? null,
101
+ converged: status.state.converged ?? null,
102
+ dailyAt: status.state.dailyAt ?? null,
103
+ cleanupAt: status.state.cleanupAt ?? null,
104
+ notices: status.state.notices.length,
105
+ recentNotices: recent,
106
+ stampAgeMs: status.stampAge,
107
+ nextCheckMs: status.nextCheckMs,
108
+ lock: status.locked.held
109
+ ? {
110
+ owner: status.locked.owner,
111
+ stale: status.locked.stale,
112
+ ageMs: status.locked.ageMs,
113
+ }
114
+ : null,
115
+ });
116
+ return;
117
+ }
118
+ const cfg = status.config;
119
+ const lines: string[] = [];
120
+ if (!cfg) {
121
+ lines.push(
122
+ "Auto-update is not configured (no `autoUpdate` section in .harnery/config.jsonc).",
123
+ );
124
+ emitLine(emit, lines.join("\n"));
125
+ return;
126
+ }
127
+ lines.push(`Enabled: ${cfg.enabled ? "yes" : "no"}`);
128
+ lines.push(`Check every: ${cfg.checkMinutes} min`);
129
+ lines.push(
130
+ `Steps: ${cfg.steps.length ? cfg.steps.map((s) => s.join(" ")).join("; ") : "none"}`,
131
+ );
132
+ if (cfg.dailySteps.length) {
133
+ lines.push(`Daily steps: ${cfg.dailySteps.map((s) => s.join(" ")).join("; ")}`);
134
+ }
135
+ const last = status.state.lastRun;
136
+ if (last) {
137
+ lines.push(
138
+ `Last run: ${last.outcome} (${last.reason}) at ${last.finishedAt}, ${short(last.fromHead)} -> ${short(last.toHead)}`,
139
+ );
140
+ if (last.error) lines.push(`Last error: ${last.error}`);
141
+ } else {
142
+ lines.push("Last run: never");
143
+ }
144
+ lines.push(`Converged head: ${short(status.state.converged?.head)}`);
145
+ lines.push(`Notices in the last 24 h: ${recent}`);
146
+ if (status.locked.held) {
147
+ const owner = status.locked.owner;
148
+ lines.push(
149
+ `Running now: ${owner ? `pid ${owner.pid} since ${owner.at}` : "lock held"}${status.locked.stale ? " (stale)" : ""}`,
150
+ );
151
+ }
152
+ if (cfg.enabled) {
153
+ lines.push(
154
+ `Next check: ${status.nextCheckMs ? `in ${formatDuration(status.nextCheckMs)}` : "due on the next activity"}`,
155
+ );
156
+ }
157
+ emitLine(emit, lines.join("\n"));
158
+ }),
159
+ );
160
+
161
+ cmd
162
+ .command("check")
163
+ .description("Decide whether an update is needed without running any step.")
164
+ .option("--json", "Emit the decision as JSON.")
165
+ .action(
166
+ guard((opts: { json?: boolean }) => {
167
+ const projectRoot = root();
168
+ const cfg = (deps.readConfig ?? readAutoUpdateConfig)(projectRoot);
169
+ if (!cfg) {
170
+ if (opts.json) emit.data({ needed: false, reason: "disabled" });
171
+ else emitLine(emit, "Auto-update is not configured.");
172
+ return;
173
+ }
174
+ const decision = checkForUpdate(projectRoot, cfg, readAutoUpdateState(projectRoot), deps);
175
+ if (opts.json) {
176
+ emit.data(decision);
177
+ return;
178
+ }
179
+ emitLine(
180
+ emit,
181
+ [
182
+ `${decision.needed ? "Update needed" : "No update needed"}: ${decision.reason}${decision.detail ? ` (${decision.detail})` : ""}`,
183
+ `Head: ${short(decision.head)}${decision.upstream ? ` · upstream ${decision.upstream} at ${short(decision.remoteSha)}` : ""}`,
184
+ ...(cfg.enabled ? [] : ["Auto-update is disabled; activity will not run it."]),
185
+ ].join("\n"),
186
+ );
187
+ }),
188
+ );
189
+
190
+ cmd
191
+ .command("run")
192
+ .description("Run the configured steps now if an update is needed (or always with --force).")
193
+ .option("--force", "Run the steps even when the checkout is current.")
194
+ .option("--background", "Write output to the log only (used by the activity tick).")
195
+ .option("--json", "Emit the run result as JSON.")
196
+ .action(
197
+ guard(async (opts: { force?: boolean; background?: boolean; json?: boolean }) => {
198
+ const projectRoot = root();
199
+ const result = await runAutoUpdate(projectRoot, {
200
+ force: opts.force,
201
+ background: opts.background,
202
+ deps,
203
+ onOutput: opts.background
204
+ ? undefined
205
+ : (chunk, stream) => {
206
+ // Keep stdout clean for --json.
207
+ const out = opts.json || stream === "stderr" ? process.stderr : process.stdout;
208
+ out.write(chunk);
209
+ },
210
+ });
211
+ emit.setExitCode(exitCodeForOutcome(result.outcome));
212
+ if (opts.json) {
213
+ emit.data({
214
+ outcome: result.outcome,
215
+ decision: result.decision,
216
+ record: result.record,
217
+ notices: result.notices,
218
+ ...(result.outcome === "skipped-locked" ? { lockOwner: result.lockOwner ?? null } : {}),
219
+ });
220
+ return;
221
+ }
222
+ if (result.outcome === "skipped-locked") {
223
+ const owner = result.lockOwner;
224
+ emitLine(
225
+ emit,
226
+ `Another auto-update run holds the lock${owner ? ` (pid ${owner.pid} since ${owner.at})` : ""}; skipped.`,
227
+ );
228
+ return;
229
+ }
230
+ if (!result.record) {
231
+ emitLine(
232
+ emit,
233
+ `Auto-update is disabled; pass --force to run the steps anyway or enable it in .harnery/config.jsonc.`,
234
+ );
235
+ return;
236
+ }
237
+ const r = result.record;
238
+ const lines = [
239
+ `Auto-update ${r.outcome} (${r.reason}): ${short(r.fromHead)} -> ${short(r.toHead)}, ${r.steps.length} step(s).`,
240
+ ];
241
+ for (const n of result.notices) lines.push(`notice [${n.kind}]: ${n.text}`);
242
+ if (r.outcome === "failed") {
243
+ lines.push(
244
+ `Next automatic attempt after ${AUTOUPDATE_DEFAULTS.failureBackoffMinutes} min; run \`${binName} autoupdate run\` to retry now.`,
245
+ );
246
+ }
247
+ emitLine(emit, lines.join("\n"));
248
+ }),
249
+ );
250
+ }