harnery 0.5.0 → 0.7.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 (176) hide show
  1. package/README.md +16 -6
  2. package/dist/commander.d.ts +29 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +4 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +94 -33
  7. package/dist/commands/browse-ai.js +1 -1
  8. package/dist/commands/browse.d.ts.map +1 -1
  9. package/dist/commands/browse.js +41 -9
  10. package/dist/commands/cookies.js +1 -1
  11. package/dist/commands/decision.d.ts +4 -0
  12. package/dist/commands/decision.d.ts.map +1 -0
  13. package/dist/commands/decision.js +354 -0
  14. package/dist/commands/deinit.d.ts.map +1 -1
  15. package/dist/commands/deinit.js +4 -0
  16. package/dist/commands/devtools.d.ts +4 -0
  17. package/dist/commands/devtools.d.ts.map +1 -0
  18. package/dist/commands/devtools.js +239 -0
  19. package/dist/commands/docs.d.ts.map +1 -1
  20. package/dist/commands/docs.js +74 -2
  21. package/dist/commands/doctor.js +12 -4
  22. package/dist/commands/env.d.ts.map +1 -1
  23. package/dist/commands/env.js +3 -63
  24. package/dist/commands/fetch.js +1 -1
  25. package/dist/commands/init.d.ts +1 -0
  26. package/dist/commands/init.d.ts.map +1 -1
  27. package/dist/commands/init.js +54 -14
  28. package/dist/commands/scratch.js +1 -1
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +273 -62
  31. package/dist/commands/web-fetch.js +1 -1
  32. package/dist/core/agents/coord-client.d.ts.map +1 -1
  33. package/dist/core/agents/coord-client.js +32 -8
  34. package/dist/core/agents/events/consume.d.ts +25 -2
  35. package/dist/core/agents/events/consume.d.ts.map +1 -1
  36. package/dist/core/agents/events/consume.js +55 -7
  37. package/dist/core/agents/events/emit.d.ts +2 -1
  38. package/dist/core/agents/events/emit.d.ts.map +1 -1
  39. package/dist/core/agents/events/emit.js +6 -1
  40. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  41. package/dist/core/agents/rules/claim-conflict.js +16 -5
  42. package/dist/core/agents/state/scratch.d.ts +1 -1
  43. package/dist/core/agents/state/scratch.js +2 -2
  44. package/dist/core/config.d.ts +10 -0
  45. package/dist/core/config.d.ts.map +1 -1
  46. package/dist/core/config.js +13 -0
  47. package/dist/core/hooks/cli.js +3 -3
  48. package/dist/core/hooks/effects/index.d.ts +11 -7
  49. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  50. package/dist/core/hooks/effects/index.js +15 -17
  51. package/dist/core/hooks/events/emit.d.ts.map +1 -1
  52. package/dist/core/hooks/events/emit.js +4 -0
  53. package/dist/core/hooks/events/rotate.d.ts +43 -0
  54. package/dist/core/hooks/events/rotate.d.ts.map +1 -0
  55. package/dist/core/hooks/events/rotate.js +142 -0
  56. package/dist/core/hooks/harness/events.d.ts +11 -1
  57. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  58. package/dist/core/hooks/harness/events.js +22 -3
  59. package/dist/core/hooks/harness/wiring.d.ts +8 -0
  60. package/dist/core/hooks/harness/wiring.d.ts.map +1 -1
  61. package/dist/core/hooks/harness/wiring.js +34 -5
  62. package/dist/core/scratch/index.d.ts.map +1 -0
  63. package/dist/{lib → core}/scratch/index.js +2 -2
  64. package/dist/lib/agent-browser/client.js +1 -1
  65. package/dist/lib/browser/client.d.ts +14 -0
  66. package/dist/lib/browser/client.d.ts.map +1 -1
  67. package/dist/lib/browser/client.js +20 -0
  68. package/dist/lib/browser/index.d.ts +1 -0
  69. package/dist/lib/browser/index.d.ts.map +1 -1
  70. package/dist/lib/browser/runts.d.ts +44 -0
  71. package/dist/lib/browser/runts.d.ts.map +1 -0
  72. package/dist/lib/browser/runts.js +193 -0
  73. package/dist/lib/completion/walk.js +1 -1
  74. package/dist/lib/cookies/client.d.ts +1 -1
  75. package/dist/lib/cookies/client.d.ts.map +1 -1
  76. package/dist/lib/cookies/client.js +1 -1
  77. package/dist/lib/decision/index.d.ts +212 -0
  78. package/dist/lib/decision/index.d.ts.map +1 -0
  79. package/dist/lib/decision/index.js +523 -0
  80. package/dist/lib/devtools.d.ts +178 -0
  81. package/dist/lib/devtools.d.ts.map +1 -0
  82. package/dist/lib/devtools.js +1328 -0
  83. package/dist/lib/docs-frontmatter-migrate.d.ts +33 -0
  84. package/dist/lib/docs-frontmatter-migrate.d.ts.map +1 -0
  85. package/dist/lib/docs-frontmatter-migrate.js +364 -0
  86. package/dist/lib/docs-frontmatter.d.ts +33 -0
  87. package/dist/lib/docs-frontmatter.d.ts.map +1 -0
  88. package/dist/lib/docs-frontmatter.js +130 -0
  89. package/dist/lib/docs-index.d.ts +1 -0
  90. package/dist/lib/docs-index.d.ts.map +1 -1
  91. package/dist/lib/docs-index.js +4 -5
  92. package/dist/lib/docs-lint.d.ts +3 -0
  93. package/dist/lib/docs-lint.d.ts.map +1 -1
  94. package/dist/lib/docs-lint.js +67 -12
  95. package/dist/lib/docs-meta.d.ts +14 -0
  96. package/dist/lib/docs-meta.d.ts.map +1 -0
  97. package/dist/lib/docs-meta.js +34 -0
  98. package/dist/lib/docs-sweep.d.ts +12 -0
  99. package/dist/lib/docs-sweep.d.ts.map +1 -1
  100. package/dist/lib/docs-sweep.js +98 -103
  101. package/dist/lib/format.js +2 -2
  102. package/dist/lib/http/index.d.ts +1 -0
  103. package/dist/lib/http/index.d.ts.map +1 -1
  104. package/dist/lib/http/index.js +1 -0
  105. package/dist/lib/http/request.d.ts +77 -0
  106. package/dist/lib/http/request.d.ts.map +1 -0
  107. package/dist/lib/http/request.js +105 -0
  108. package/dist/lib/instructions/apply.d.ts +63 -0
  109. package/dist/lib/instructions/apply.d.ts.map +1 -0
  110. package/dist/lib/instructions/apply.js +255 -0
  111. package/dist/lib/instructions/splice.d.ts +73 -0
  112. package/dist/lib/instructions/splice.d.ts.map +1 -0
  113. package/dist/lib/instructions/splice.js +118 -0
  114. package/dist/lib/instructions/templates.d.ts +45 -0
  115. package/dist/lib/instructions/templates.d.ts.map +1 -0
  116. package/dist/lib/instructions/templates.js +258 -0
  117. package/dist/lib/tunnel/gate.d.ts +1 -0
  118. package/dist/lib/tunnel/gate.d.ts.map +1 -1
  119. package/dist/lib/tunnel/gate.js +15 -10
  120. package/dist/lib/tunnel/state.d.ts +11 -1
  121. package/dist/lib/tunnel/state.d.ts.map +1 -1
  122. package/dist/lib/tunnel/state.js +8 -3
  123. package/package.json +9 -6
  124. package/src/commander.ts +35 -0
  125. package/src/commands/agents.ts +97 -29
  126. package/src/commands/browse-ai.ts +1 -1
  127. package/src/commands/browse.ts +63 -8
  128. package/src/commands/cookies.ts +1 -1
  129. package/src/commands/decision.ts +438 -0
  130. package/src/commands/deinit.ts +5 -0
  131. package/src/commands/devtools.ts +284 -0
  132. package/src/commands/docs.ts +86 -2
  133. package/src/commands/doctor.ts +13 -4
  134. package/src/commands/env.ts +11 -77
  135. package/src/commands/fetch.ts +1 -1
  136. package/src/commands/init.ts +66 -15
  137. package/src/commands/scratch.ts +1 -1
  138. package/src/commands/tunnel.ts +316 -65
  139. package/src/commands/web-fetch.ts +1 -1
  140. package/src/core/agents/coord-client.ts +34 -7
  141. package/src/core/agents/events/consume.ts +65 -7
  142. package/src/core/agents/events/emit.ts +7 -1
  143. package/src/core/agents/rules/claim-conflict.ts +17 -6
  144. package/src/core/agents/state/scratch.ts +2 -2
  145. package/src/core/config.ts +15 -1
  146. package/src/core/hooks/cli.ts +3 -3
  147. package/src/core/hooks/effects/index.ts +23 -16
  148. package/src/core/hooks/events/emit.ts +5 -0
  149. package/src/core/hooks/events/rotate.ts +151 -0
  150. package/src/core/hooks/harness/events.ts +30 -3
  151. package/src/core/hooks/harness/wiring.ts +46 -5
  152. package/src/{lib → core}/scratch/index.ts +2 -2
  153. package/src/lib/agent-browser/client.ts +1 -1
  154. package/src/lib/browser/client.ts +28 -0
  155. package/src/lib/browser/index.ts +4 -0
  156. package/src/lib/browser/runts.ts +218 -0
  157. package/src/lib/completion/walk.ts +1 -1
  158. package/src/lib/cookies/client.ts +2 -2
  159. package/src/lib/decision/index.ts +685 -0
  160. package/src/lib/devtools.ts +1653 -0
  161. package/src/lib/docs-frontmatter-migrate.ts +427 -0
  162. package/src/lib/docs-frontmatter.ts +151 -0
  163. package/src/lib/docs-index.ts +4 -5
  164. package/src/lib/docs-lint.ts +61 -11
  165. package/src/lib/docs-meta.ts +44 -0
  166. package/src/lib/docs-sweep.ts +104 -102
  167. package/src/lib/format.ts +2 -2
  168. package/src/lib/http/index.ts +1 -0
  169. package/src/lib/http/request.ts +154 -0
  170. package/src/lib/instructions/apply.ts +318 -0
  171. package/src/lib/instructions/splice.ts +148 -0
  172. package/src/lib/instructions/templates.ts +295 -0
  173. package/src/lib/tunnel/gate.ts +15 -10
  174. package/src/lib/tunnel/state.ts +19 -4
  175. package/dist/lib/scratch/index.d.ts.map +0 -1
  176. /package/dist/{lib → core}/scratch/index.d.ts +0 -0
@@ -22,7 +22,7 @@ import { dirname, relative, resolve } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
  import type { Command } from "commander";
24
24
  import type { EmitContext } from "../commander.ts";
25
- import { DEFAULT_BIN_NAME, stripJsonComments } from "../core/config.ts";
25
+ import { DEFAULT_BIN_NAME, pinnedBinName, stripJsonComments } from "../core/config.ts";
26
26
  import { HARNESS_SPECS, type HarnessId, type HarnessSpec } from "../core/hooks/harness/events.ts";
27
27
  import {
28
28
  commandWiresSubcommand,
@@ -31,6 +31,7 @@ import {
31
31
  makeEntry,
32
32
  type SettingsFile,
33
33
  } from "../core/hooks/harness/wiring.ts";
34
+ import { applyInstructions, checkInstructions } from "../lib/instructions/apply.ts";
34
35
 
35
36
  // Re-exported for back-compat: callers (deinit, tests) import these names
36
37
  // from init.ts. The definitions now live in core/hooks/harness/wiring.ts.
@@ -42,6 +43,7 @@ const HARNERY_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", ".."
42
43
  interface InitOpts {
43
44
  harness: string;
44
45
  dryRun?: boolean;
46
+ check?: boolean;
45
47
  projectRoot?: string;
46
48
  }
47
49
 
@@ -49,11 +51,14 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
49
51
  program
50
52
  .command("init")
51
53
  .description(
52
- "Bootstrap harnery in this project: create .harnery/ and wire the harness " +
53
- "hooks (idempotent; safe to re-run). Use --dry-run to preview.",
54
+ "Bootstrap harnery in this project: create .harnery/, wire the harness " +
55
+ "hooks, and inject the agent-facing instructions block + skills " +
56
+ "(idempotent; safe to re-run). Use --dry-run to preview, --check to " +
57
+ "report drift without writing (exit 0 fresh / 2 drift / 1 error).",
54
58
  )
55
59
  .option("--harness <id>", "claude-code | cursor | codex", "claude-code")
56
60
  .option("--dry-run", "Show what would change without writing")
61
+ .option("--check", "Report instructions/skills drift without writing; exit 0/2/1")
57
62
  .option("--project-root <path>", "Project root (default: git toplevel, else cwd)")
58
63
  .action((opts: InitOpts) => {
59
64
  const harness = opts.harness as HarnessId;
@@ -65,6 +70,28 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
65
70
  }
66
71
 
67
72
  const projectRoot = resolve(opts.projectRoot ?? gitTopLevel() ?? process.cwd());
73
+ // A binName already pinned in this project's config.jsonc beats the
74
+ // invoking CLI's name: the pin is a committed, deliberate declaration;
75
+ // the invoking bin is circumstantial (any embedding host's CLI can run
76
+ // init inside any checkout). Without this, re-running init from a host
77
+ // CLI re-stamps the host's name into committed agent-facing surfaces.
78
+ const bin = pinnedBinName(projectRoot) ?? (binName?.trim() ? binName : DEFAULT_BIN_NAME);
79
+
80
+ // ── --check: read-only drift report on the block + skills ──────────────
81
+ if (opts.check === true) {
82
+ const { status, issues } = checkInstructions(projectRoot, { binName: bin, harness });
83
+ const head =
84
+ status === "fresh"
85
+ ? "harn init --check: instructions + skills are current"
86
+ : status === "drift"
87
+ ? "harn init --check: drift found (re-run `init` to refresh)"
88
+ : "harn init --check: error";
89
+ const lines = issues.length ? `\n${issues.map((i) => ` ✗ ${i}`).join("\n")}` : "";
90
+ emit.text(`${head}${lines}`);
91
+ emit.setExitCode(status === "fresh" ? 0 : status === "drift" ? 2 : 1);
92
+ return;
93
+ }
94
+
68
95
  const dryRun = opts.dryRun === true;
69
96
  const actions: string[] = [];
70
97
 
@@ -85,10 +112,11 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
85
112
  // The coord binaries (agent-hook/agent-coord) and web UI run as harnery
86
113
  // itself, so they can't see a consumer CLI's name; they read it back from
87
114
  // config.jsonc. Standalone `harn` is the resolver's default, so only a
88
- // consumer (binName ≠ "harn") needs the stamp.
89
- if (binName && binName !== DEFAULT_BIN_NAME) {
115
+ // consumer (bin ≠ "harn") needs the stamp. `bin` already honors an
116
+ // existing pin, so this never overwrites a deliberate config value.
117
+ if (bin !== DEFAULT_BIN_NAME) {
90
118
  const configPath = resolve(coordDir, "config.jsonc");
91
- const stamp = stampBinName(configPath, binName, dryRun);
119
+ const stamp = stampBinName(configPath, bin, dryRun);
92
120
  if (stamp) actions.push(stamp);
93
121
  }
94
122
 
@@ -111,25 +139,31 @@ export function registerInitCommand(program: Command, emit: EmitContext, binName
111
139
  } else {
112
140
  settings = {};
113
141
  }
114
- const { wired, already } = wireHooks(settings, spec, agentHook, harness);
142
+ const { wired, already, removed } = wireHooks(settings, spec, agentHook, harness);
115
143
 
116
- if (wired === 0) {
144
+ if (wired === 0 && removed === 0) {
117
145
  actions.push(
118
146
  `· all ${spec.events.length} ${harness} hooks already wired in ${rel(projectRoot, settingsPath)}`,
119
147
  );
120
148
  } else if (dryRun) {
121
149
  actions.push(
122
- `+ would wire ${wired} hook(s) into ${rel(projectRoot, settingsPath)} (${already} already present)`,
150
+ `+ would wire ${wired} hook(s) and remove ${removed} legacy hook(s) in ` +
151
+ `${rel(projectRoot, settingsPath)} (${already} already present)`,
123
152
  );
124
153
  } else {
125
154
  mkdirSync(dirname(settingsPath), { recursive: true });
126
155
  writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
127
156
  actions.push(
128
- `+ wired ${wired} hook(s) into ${rel(projectRoot, settingsPath)} (${already} already present)`,
157
+ `+ wired ${wired} hook(s) and removed ${removed} legacy hook(s) in ` +
158
+ `${rel(projectRoot, settingsPath)} (${already} already present)`,
129
159
  );
130
160
  }
131
161
 
132
- emit.text(render(projectRoot, dryRun, actions));
162
+ // ── 3. agent-facing instructions block + skills ────────────────────────
163
+ const applied = applyInstructions(projectRoot, { binName: bin, harness, dryRun });
164
+ actions.push(...applied.actions);
165
+
166
+ emit.text(render(projectRoot, dryRun, actions, applied.warnings));
133
167
  });
134
168
  }
135
169
 
@@ -149,13 +183,24 @@ export function wireHooks(
149
183
  spec: HarnessSpec,
150
184
  agentHookPath: string,
151
185
  harness: HarnessId,
152
- ): { wired: number; already: number } {
186
+ ): { wired: number; already: number; removed: number } {
153
187
  if (spec.rootVersion !== undefined && settings.version === undefined) {
154
188
  settings.version = spec.rootVersion;
155
189
  }
156
190
  if (!settings.hooks) settings.hooks = {};
157
191
  let wired = 0;
158
192
  let already = 0;
193
+ let removed = 0;
194
+ for (const { settingsKey, subcommand } of spec.legacyEvents ?? []) {
195
+ const groups = settings.hooks[settingsKey] ?? [];
196
+ const kept = groups.filter(
197
+ (group) =>
198
+ !groupCommands(group).some((command) => commandWiresSubcommand(command, subcommand)),
199
+ );
200
+ removed += groups.length - kept.length;
201
+ if (kept.length === 0) delete settings.hooks[settingsKey];
202
+ else settings.hooks[settingsKey] = kept;
203
+ }
159
204
  for (const { settingsKey, subcommand } of spec.events) {
160
205
  const command = `bash ${agentHookPath} ${subcommand} --harness ${harness}`;
161
206
  const groups = settings.hooks[settingsKey] ?? [];
@@ -170,7 +215,7 @@ export function wireHooks(
170
215
  settings.hooks[settingsKey] = groups;
171
216
  wired++;
172
217
  }
173
- return { wired, already };
218
+ return { wired, already, removed };
174
219
  }
175
220
 
176
221
  /**
@@ -288,12 +333,18 @@ function firstBraceIndex(raw: string): number {
288
333
  return -1;
289
334
  }
290
335
 
291
- function render(projectRoot: string, dryRun: boolean, actions: string[]): string {
336
+ function render(
337
+ projectRoot: string,
338
+ dryRun: boolean,
339
+ actions: string[],
340
+ warnings: string[] = [],
341
+ ): string {
292
342
  const head = dryRun ? "harn init (dry run): no changes written" : "harn init";
343
+ const warnBlock = warnings.length ? `\n${warnings.map((w) => ` ! ${w}`).join("\n")}` : "";
293
344
  const tail = dryRun
294
345
  ? "\nRe-run without --dry-run to apply."
295
346
  : "\nDone. Start a session and check `harn agents whoami`.";
296
- return `${head}\n root: ${projectRoot}\n${actions.map((a) => ` ${a}`).join("\n")}${tail}`;
347
+ return `${head}\n root: ${projectRoot}\n${actions.map((a) => ` ${a}`).join("\n")}${warnBlock}${tail}`;
297
348
  }
298
349
 
299
350
  function gitTopLevel(): string | null {
@@ -20,7 +20,7 @@ import {
20
20
  scratchDir,
21
21
  scratchPath,
22
22
  sweepOrphanScratchpads,
23
- } from "../lib/scratch/index.ts";
23
+ } from "../core/scratch/index.ts";
24
24
 
25
25
  /**
26
26
  * `harn scratch`: per-agent markdown journal at `.harnery/scratch/<instance_id>.md`.