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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,WAA6C,GACpD,MAAM,CA+CR;AAID,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAsKD,eAAO,MAAM,MAAM,EAAE,aAAa,EA4BjC,CAAC"}
@@ -0,0 +1,258 @@
1
+ /**
2
+ * The agent-facing content harnery ships into a consumer: one orientation block
3
+ * for `AGENTS.md` and the generic skills. Everything here is engine mechanics
4
+ * only — no triage rubric, no escalation targets, no host doc-layout policy
5
+ * (those stay host-authored, per ADR 0007's portability split). Every command
6
+ * string renders through `binName`; a template that only reads for `harn` is a
7
+ * bug the portability guard exists to catch.
8
+ *
9
+ * Content, not paths: templates are TS string builders (not shipped `.md`
10
+ * files), so they compile into `dist/` and resolve identically under Bun and
11
+ * Node — no `files`-field copy or package-path guesswork.
12
+ */
13
+ import { buildOwnedSkill } from "./splice.js";
14
+ /** Managed-region name for the AGENTS.md orientation block. */
15
+ export const INSTRUCTIONS_REGION = "instructions";
16
+ /** Managed-region name for the CLAUDE.md `@AGENTS.md` import shim. */
17
+ export const IMPORT_REGION = "import";
18
+ /**
19
+ * The always-on orientation spliced into `AGENTS.md`. Target ≤ 80 rendered
20
+ * lines: it costs every agent context on every turn, so it states that each
21
+ * surface *exists* and gives one line of *when* — the *how* lives in the skills
22
+ * and each command's `--help`. Skill names are fixed (`harn-decide`,
23
+ * `harn-council`) even for a renamed bin; only command strings track `binName`.
24
+ *
25
+ * The block only points at a skill that actually exists here: a host that
26
+ * excludes one via `skills.exclude`, or a harness with no skill primitive
27
+ * (cursor/codex get the block but no skill files), gets a `--help` pointer
28
+ * instead of a dangling reference to a skill it doesn't have.
29
+ */
30
+ export function renderInstructionsBlock(binName, skills = { decide: true, council: true }) {
31
+ const b = binName;
32
+ const named = [skills.decide && "`harn-decide`", skills.council && "`harn-council`"].filter(Boolean);
33
+ const deeper = named.length > 0
34
+ ? `Procedures for the deeper flows live in the ${named.join(" and ")} skill${named.length > 1 ? "s" : ""}.`
35
+ : `See \`${b} decision --help\` and \`${b} council --help\` for the deeper procedures.`;
36
+ const decidePointer = skills.decide
37
+ ? "The `harn-decide` skill has the file / claim / resolve-with-evidence procedure."
38
+ : `See \`${b} decision --help\` for the file / claim / resolve-with-evidence procedure.`;
39
+ const councilPointer = skills.council
40
+ ? "The `harn-council` skill has the steward and member flow."
41
+ : `See \`${b} council --help\` for the steward and member flow.`;
42
+ return `## harnery coordination
43
+
44
+ This project runs [harnery](https://harnery.com) for multi-agent coordination.
45
+ You share this checkout with other agents; the surfaces below keep you oriented
46
+ and out of each other's way. Run \`${b} <command> --help\` for any command's full
47
+ surface. ${deeper}
48
+
49
+ **Identity + peers.** You are one of several agents in this repo.
50
+ \`${b} agents whoami\` is you; \`${b} agents status\` shows your session plus the
51
+ active peers and the files they've claimed; \`${b} agents set-task "<focus>"\`
52
+ declares your current focus so peers can see it. Check for peers before editing
53
+ widely-shared files.
54
+
55
+ **Declare intent on shell commands.** Every command you run is captured to the
56
+ coordination ledger. Lead a shell command with a \`# intent: <why>\` comment (or set
57
+ the tool's description) so the recorded event carries a reason instead of
58
+ \`(no intent)\`.
59
+
60
+ **Scratch journal.** \`${b} scratch add <category> "<text>"\` (category = note, plan,
61
+ decision, blocker, or handoff) leaves breadcrumbs that survive context compaction;
62
+ \`${b} scratch read\` reads yours, \`${b} scratch read --name <peer>\` reads a peer's.
63
+ Use it for anything future-you or a peer will need to pick up your thread.
64
+
65
+ **Decision docket.** When you would otherwise stop to ask a human a decision you
66
+ can't resolve from the repo, file it instead. \`${b} decision file "<question>"\`
67
+ records it and lets you proceed on a stated default; \`${b} decision search "<terms>"\`
68
+ surfaces prior decisions, so check for precedent before re-deciding. ${decidePointer}
69
+
70
+ **Councils.** For a hard or contested decision, convene a council of agents.
71
+ \`${b} council create "<objective>"\` runs structured rounds toward a decision. ${councilPointer}`;
72
+ }
73
+ function decideBody(b) {
74
+ return `The decision docket is a persistent queue for decisions you would otherwise
75
+ route to a human. It's built on the \`${b} decision\` engine. This skill is the
76
+ mechanics — file, find precedent, claim, and resolve with evidence. *When* a
77
+ decision needs a human at all (versus one you settle yourself) is host policy;
78
+ if this project defines that rubric, follow it.
79
+
80
+ ## Modes
81
+
82
+ - **A decision you're facing (default)** → capture: record it and proceed.
83
+ - **\`resolve <id>\`** → pick up an open decision, research it, resolve it.
84
+ - **\`review\`** → surface resolved-but-unreviewed decisions for a human to skim.
85
+
86
+ ## Capture (default)
87
+
88
+ 1. **Check precedent first.** \`${b} decision search "<key terms>"\`. If a resolved
89
+ decision already answers this, cite it — don't re-litigate.
90
+ 2. **File it** when the choice has a second consumer (a human will want to see it,
91
+ or a future agent will face it again) or reversal is expensive. Skip the
92
+ docket for pure local mechanics (a variable name, one of two equivalent idioms).
93
+
94
+ \`\`\`bash
95
+ ${b} decision file "<the decision as a clear question>" \\
96
+ --context "why it matters / what's blocked / the options you see" \\
97
+ --default-taken "<what you're proceeding with>"
98
+ \`\`\`
99
+
100
+ For a decision with real substance, write a brief to a file and pass
101
+ \`--brief <path>\` so the reviewer sees options + evidence, not a cold prompt.
102
+ 3. **Proceed on your default.** Filing does not mean blocking — note the id in your
103
+ reply and keep working.
104
+
105
+ ## Resolve (\`resolve <id>\`)
106
+
107
+ \`\`\`bash
108
+ ${b} decision show <id> # read the question + context + any brief
109
+ ${b} decision claim <id> # mark it deliberating (claimed by you)
110
+ \`\`\`
111
+
112
+ Research it for real — run the queries, read the files, compute the costs. Then
113
+ resolve with **cited evidence** (the engine rejects an evidence-free resolution):
114
+
115
+ \`\`\`bash
116
+ ${b} decision resolve <id> \\
117
+ --recommendation "<the call>" \\
118
+ --evidence "<a fact you established: a query run, a file read, a cost computed>" \\
119
+ --evidence "<another>" \\
120
+ --reversal-cost "<cost to undo if wrong>" \\
121
+ --wrong-if "<what would make this wrong>" \\
122
+ --revisit-when "<trigger to revisit>"
123
+ \`\`\`
124
+
125
+ If the decision is genuinely hard or contested, escalate to a council
126
+ (\`${b} council create "<objective>"\`) and link it rather than forcing a thin
127
+ single-agent resolution.
128
+
129
+ ## Review (\`review\`)
130
+
131
+ \`\`\`bash
132
+ ${b} decision list --status resolved
133
+ \`\`\`
134
+
135
+ For each one a human reacts to, record the verdict so triage self-corrects:
136
+
137
+ \`\`\`bash
138
+ ${b} decision review <id> --verdict ratified # agreed; no action
139
+ ${b} decision review <id> --verdict overridden --note "…" # disagreed
140
+ ${b} decision review <id> --verdict wrong-tier-low # "didn't need to see this"
141
+ ${b} decision review <id> --verdict wrong-tier-high # "should have seen it sooner"
142
+ \`\`\``;
143
+ }
144
+ function councilBody(b) {
145
+ return `A council convenes several agents to deliberate a hard or contested decision
146
+ over structured rounds. This skill wraps the \`${b} council\` surface with the
147
+ guardrails a router needs when passing prompts between agents. It's asymmetric on
148
+ purpose: it does the most for **contributors** (refusing a misrouted prompt) and
149
+ the least for **stewards**.
150
+
151
+ Every mode starts by running \`${b} agents whoami --json\` and, when an id is given,
152
+ \`${b} council show <id> --json\`, so the logic works against typed data, not text.
153
+
154
+ ## Modes
155
+
156
+ - **No argument** → list councils you're a member of; surface what waits on you.
157
+ - **\`create <objective>\`** → open the web member-picker with the objective filled.
158
+ - **\`contribute <id>\`** → the guarded contribution flow (below).
159
+ - **\`prompts <id>\`** → steward: draft each pending member's routing prompt.
160
+ - **\`show <id>\`** → render council state verbatim.
161
+
162
+ ## List (no arg)
163
+
164
+ \`\`\`bash
165
+ ${b} agents whoami --json
166
+ ${b} council list --mine --json
167
+ \`\`\`
168
+
169
+ One section per council you're a member of: id + objective, round N (open /
170
+ collected) with N/M contributors, and your status (awaiting prompt / prompt ready
171
+ / already contributed) with the next command to run. Stop after listing — don't
172
+ auto-route into contribute.
173
+
174
+ ## Create (\`create <objective>\`)
175
+
176
+ The web UI is the member + steward picker; don't create from the CLI (that skips
177
+ the steward choice). Emit the link with the objective URL-encoded:
178
+
179
+ \`\`\`
180
+ http://localhost:9000/councils/new?objective=<encoded>
181
+ \`\`\`
182
+
183
+ If the dev server isn't up, start it with \`${b} web up\`.
184
+
185
+ ## Contribute (\`contribute <id>\`) — the guarded flow
186
+
187
+ Run these checks in order; refuse with a specific reason if any fails.
188
+
189
+ 1. **Membership.** If your \`whoami\` name isn't in \`manifest.members\`, refuse — the
190
+ router likely meant a different agent's session.
191
+ 2. **Already contributed.** If you're in \`current_round_contributors\`, refuse —
192
+ wait for the steward to advance the round.
193
+ 3. **Prompt routing.** Find your entry in \`current_round_prompts\`. If none is
194
+ drafted for you, refuse (the steward must write one first). If the routed body
195
+ carries a \`<!-- council-route … member: <name> -->\` header naming a *different*
196
+ agent, refuse — the wrong prompt was pasted into your session.
197
+ 4. **Compose** per your prompt (read \`manifest.target_doc\` in full if set; strip
198
+ the route header before treating the body as instructions).
199
+ 5. **Submit:**
200
+
201
+ \`\`\`bash
202
+ ${b} council contribute <id> --message "<status-line>" --file <path>
203
+ \`\`\`
204
+
205
+ Use \`--file\` when you edited a target doc (captures the post-edit state);
206
+ \`--message\` for a prose-only contribution. If the prompt asks for a
207
+ substantive/trivial classification, end with the literal \`<substantive>\` or
208
+ \`<trivial>\` angle-bracket tag on the final line (the exit-criterion parser keys
209
+ on it). When in doubt, lean \`<trivial>\`.
210
+
211
+ ## Prompts (\`prompts <id>\`) — steward
212
+
213
+ 1. **Authority.** If your \`whoami\` name ≠ the council's \`steward\`, stop.
214
+ 2. **Plan the round** from the target doc + prior rounds. Round 1 must include a
215
+ completeness critic — assign one member the explicit charge: "What important
216
+ thing is NOT in this document at all — a missing dimension, not a flaw in what's
217
+ written?" Lens-scoped reviewers reliably miss whole absent dimensions.
218
+ 3. **Draft + write** one prompt per member missing from \`current_round_prompts\`:
219
+
220
+ \`\`\`bash
221
+ ${b} council prompt <id> agent-<Name> --message "..." # or --file <path>
222
+ \`\`\`
223
+
224
+ The CLI auto-prepends the \`<!-- council-route … -->\` header — never write it by
225
+ hand. Every prompt must instruct the member to end with the literal
226
+ \`<substantive>\` / \`<trivial>\` tag.
227
+
228
+ ## Refusal style
229
+
230
+ Lead with what you are versus what the council expects, cite the structural fact
231
+ that triggered the refusal (member list, contributors, prompt absence, route
232
+ mismatch), and offer the right next step. Don't propose a workaround that bypasses
233
+ the guard.`;
234
+ }
235
+ export const SKILLS = [
236
+ {
237
+ id: "harn-decide",
238
+ relPath: "harn-decide/SKILL.md",
239
+ render: (binName) => buildOwnedSkill({
240
+ name: "harn-decide",
241
+ description: "File a decision into the docket instead of blocking on a human — search precedent, file it, and proceed on a reversible default; or pick up and resolve an open decision with cited evidence. Use whenever you're about to ask a human a decision-shaped question you could resolve yourself.",
242
+ argumentHint: "[<the decision / question you're facing> | resolve <id> | review]",
243
+ binName,
244
+ body: decideBody(binName),
245
+ }),
246
+ },
247
+ {
248
+ id: "harn-council",
249
+ relPath: "harn-council/SKILL.md",
250
+ render: (binName) => buildOwnedSkill({
251
+ name: "harn-council",
252
+ description: "Interact with the multi-agent council system: list / create / show / prompts (steward) / contribute (member). Guards against misrouting — refuses to contribute when you aren't a member, have already contributed, or weren't routed a prompt.",
253
+ argumentHint: "[<id-or-fragment> | create <objective> | contribute <id> | prompts <id> | show <id>]",
254
+ binName,
255
+ body: councilBody(binName),
256
+ }),
257
+ },
258
+ ];
@@ -3,6 +3,7 @@ declare const ALLOW: Set<string>;
3
3
  declare const TARGET: string;
4
4
  declare const VHOST: string;
5
5
  declare const PORT: number;
6
+ declare const ACCESS: string;
6
7
  declare const UPSTREAM_HTTP: string;
7
8
  declare const UPSTREAM_WS: string;
8
9
  interface WsData {
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/gate.ts"],"names":[],"mappings":"AAgBA,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlD;AAED,QAAA,MAAM,KAAK,aAKV,CAAC;AACF,QAAA,MAAM,MAAM,QAAwD,CAAC;AACrE,QAAA,MAAM,KAAK,QAAkD,CAAC;AAC9D,QAAA,MAAM,IAAI,QAA0E,CAAC;AAErF,QAAA,MAAM,aAAa,QAAqB,CAAC;AACzC,QAAA,MAAM,WAAW,QAAmB,CAAC;AAErC,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,MAAM,oBAoEV,CAAC"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/gate.ts"],"names":[],"mappings":"AAiBA,iBAAS,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlD;AAED,QAAA,MAAM,KAAK,aAKV,CAAC;AACF,QAAA,MAAM,MAAM,QAAwD,CAAC;AACrE,QAAA,MAAM,KAAK,QAAkD,CAAC;AAC9D,QAAA,MAAM,IAAI,QAA0E,CAAC;AACrF,QAAA,MAAM,MAAM,QAA8D,CAAC;AAE3E,QAAA,MAAM,aAAa,QAAqB,CAAC;AACzC,QAAA,MAAM,WAAW,QAAmB,CAAC;AAErC,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,MAAM,oBAsEV,CAAC"}
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
- // Reverse-proxy worker spawned by `tunnel up`. Listens on 127.0.0.1:<port>,
3
- // checks CF-Connecting-IP against an allowlist passed via env, and forwards
4
- // HTTP + WebSocket requests to an upstream with a Host header rewrite and
5
- // Content-Encoding stripped (Bun's fetch auto-decompresses the body but
6
- // retains the encoding header, which breaks browser decoding downstream).
2
+ // Reverse-proxy worker spawned by `tunnel up`. Listens on 127.0.0.1:<port>
3
+ // and forwards HTTP + WebSocket requests to an upstream with a Host header
4
+ // rewrite and Content-Encoding stripped (Bun's fetch auto-decompresses the
5
+ // body but retains the encoding header, which breaks browser decoding
6
+ // downstream). Cloudflare provider mode checks CF-Connecting-IP against an
7
+ // allowlist; trusted local proxy mode lets the local exposer own access.
7
8
  //
8
9
  // Runs detached, outside the CLI command framework; no command context is
9
10
  // available; stdout/stderr is captured into .cache/tunnel/gate.log by the
@@ -25,6 +26,7 @@ const ALLOW = new Set((process.env.HARNERY_TUNNEL_ALLOW ?? "")
25
26
  const TARGET = process.env.HARNERY_TUNNEL_TARGET ?? "127.0.0.1:8001";
26
27
  const VHOST = process.env.HARNERY_TUNNEL_VHOST ?? "localhost";
27
28
  const PORT = Number(process.env.HARNERY_TUNNEL_PORT ?? argvFlag("--port") ?? "9001");
29
+ const ACCESS = process.env.HARNERY_TUNNEL_ACCESS ?? "cloudflare-allowlist";
28
30
  const UPSTREAM_HTTP = `http://${TARGET}`;
29
31
  const UPSTREAM_WS = `ws://${TARGET}`;
30
32
  const server = Bun.serve({
@@ -36,9 +38,11 @@ const server = Bun.serve({
36
38
  // Bun's max, and beyond any realistic single-turn delay.
37
39
  idleTimeout: 255,
38
40
  async fetch(req, server) {
39
- const ip = req.headers.get("cf-connecting-ip") ?? "";
40
- if (!ALLOW.has(ip)) {
41
- return new Response("403 Forbidden\n", { status: 403 });
41
+ if (ACCESS === "cloudflare-allowlist") {
42
+ const ip = req.headers.get("cf-connecting-ip") ?? "";
43
+ if (!ALLOW.has(ip)) {
44
+ return new Response("403 Forbidden\n", { status: 403 });
45
+ }
42
46
  }
43
47
  const url = new URL(req.url);
44
48
  if (req.headers.get("upgrade")?.toLowerCase() === "websocket") {
@@ -97,5 +101,6 @@ const server = Bun.serve({
97
101
  // This worker runs detached via `bun run gate.ts`, outside the CLI command
98
102
  // framework, so no AsyncLocalStorage context is available. stdout/stderr is
99
103
  // captured into .cache/tunnel/gate.log by the spawning command.
100
- console.log(`bp-tunnel-gate :${server.port} -> ${UPSTREAM_HTTP} (Host: ${VHOST})`); // lint-ok-emission: detached worker, see file note above
101
- console.log(`allow: ${[...ALLOW].join(", ") || "(empty, denies all)"}`); // lint-ok-emission: detached worker, see file note above
104
+ console.log(`harn-tunnel-gate :${server.port} -> ${UPSTREAM_HTTP} (Host: ${VHOST})`); // lint-ok-emission: detached worker, see file note above
105
+ console.log(`access: ${ACCESS}`); // lint-ok-emission: detached worker, see file note above
106
+ console.log(`allow: ${[...ALLOW].join(", ") || "(empty, not used outside allowlist mode)"}`); // lint-ok-emission: detached worker, see file note above
@@ -2,18 +2,28 @@
2
2
  export declare const DEFAULT_INSTANCE = "default";
3
3
  export declare function gateLogFile(name: string): string;
4
4
  export declare function cfdLogFile(name: string): string;
5
+ export declare function providerLogFile(name: string, provider: TunnelProvider): string;
5
6
  export interface TunnelConfig {
6
7
  allowed_ips: string[];
7
8
  }
9
+ export type TunnelProvider = "cloudflare" | "tailscale";
10
+ export type TailscaleMode = "serve" | "funnel";
8
11
  export interface TunnelState {
9
12
  name: string;
13
+ provider: TunnelProvider;
10
14
  url: string;
11
15
  gate_pid: number;
12
- cloudflared_pid: number;
16
+ /** Present for Cloudflare quick tunnels; absent for Tailscale Serve/Funnel. */
17
+ cloudflared_pid?: number;
18
+ /** Optional provider-side process when a provider owns one. */
19
+ provider_pid?: number;
13
20
  started_at: string;
14
21
  target: string;
15
22
  vhost: string;
16
23
  gate_port: number;
24
+ tailscale_mode?: TailscaleMode;
25
+ tailscale_path?: string;
26
+ tailscale_https_port?: number;
17
27
  }
18
28
  export declare function readConfig(): TunnelConfig;
19
29
  export declare function writeConfig(cfg: TunnelConfig): void;
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/state.ts"],"names":[],"mappings":"AAuBA,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAW1C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAgBD,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAOD,wBAAgB,UAAU,IAAI,YAAY,CAWzC;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEnD;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAAyB,GAAG,WAAW,GAAG,IAAI,CAQ7E;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAEnD;AAED,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAyB,GAAG,IAAI,CAGhE;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,WAAW,EAAE,CAW1C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAuB1C"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/lib/tunnel/state.ts"],"names":[],"mappings":"AAuBA,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAW1C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,CAG9E;AAgBD,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,WAAW,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAOD,wBAAgB,UAAU,IAAI,YAAY,CAWzC;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEnD;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAAyB,GAAG,WAAW,GAAG,IAAI,CAQ7E;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAEnD;AAED,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAyB,GAAG,IAAI,CAGhE;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,WAAW,EAAE,CAW1C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAuB1C"}
@@ -1,5 +1,5 @@
1
- // Tunnel config + state persistence + cloudflared-install helper. Lives
2
- // under <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
1
+ // Tunnel config + state persistence + provider helpers. Lives under
2
+ // <cwd>/.cache/tunnel/; gitignored, so the allowlist is per-machine.
3
3
  import { execSync } from "node:child_process";
4
4
  import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
5
5
  import { resolve } from "node:path";
@@ -27,6 +27,11 @@ export function gateLogFile(name) {
27
27
  export function cfdLogFile(name) {
28
28
  return name === DEFAULT_INSTANCE ? "cloudflared.log" : `cloudflared-${name}.log`;
29
29
  }
30
+ export function providerLogFile(name, provider) {
31
+ if (provider === "cloudflare")
32
+ return cfdLogFile(name);
33
+ return name === DEFAULT_INSTANCE ? "tailscale.log" : `tailscale-${name}.log`;
34
+ }
30
35
  /** Map a state filename back to its instance name (inverse of stateFile). */
31
36
  function nameFromStateFile(file) {
32
37
  if (file === "state.json")
@@ -42,7 +47,7 @@ const DEFAULT_CONFIG = {
42
47
  };
43
48
  /** Normalize a parsed state blob; supply `name` for pre-multi-instance files. */
44
49
  function normalizeState(raw, fallbackName) {
45
- return { ...raw, name: raw.name ?? fallbackName };
50
+ return { ...raw, name: raw.name ?? fallbackName, provider: raw.provider ?? "cloudflare" };
46
51
  }
47
52
  export function readConfig() {
48
53
  const p = cachePath("tunnel", CONFIG_FILE);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harnery",
3
- "version": "0.5.0",
4
- "description": "Multi-agent coordination + harness adapters + portable CLI utilities for Claude Code / Cursor / Codex.",
3
+ "version": "0.7.0",
4
+ "description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Kelly",
7
7
  "homepage": "https://harnery.com",
@@ -87,10 +87,10 @@
87
87
  "types": "./dist/lib/readability/index.d.ts",
88
88
  "import": "./dist/lib/readability/index.js"
89
89
  },
90
- "./lib/scratch": {
91
- "bun": "./src/lib/scratch/index.ts",
92
- "types": "./dist/lib/scratch/index.d.ts",
93
- "import": "./dist/lib/scratch/index.js"
90
+ "./core/scratch": {
91
+ "bun": "./src/core/scratch/index.ts",
92
+ "types": "./dist/core/scratch/index.d.ts",
93
+ "import": "./dist/core/scratch/index.js"
94
94
  },
95
95
  "./lib/tunnel/state": {
96
96
  "bun": "./src/lib/tunnel/state.ts",
@@ -128,6 +128,9 @@
128
128
  "prepublishOnly": "npm run clean && npm run build",
129
129
  "typecheck": "bun x tsc --noEmit",
130
130
  "lint": "bun x @biomejs/biome check src",
131
+ "check:portability": "bun run scripts/check-portability.ts",
132
+ "check:layering": "bun run scripts/check-layering.ts",
133
+ "lint:fix": "bun x @biomejs/biome check --write src",
131
134
  "format": "bun x @biomejs/biome format --write src",
132
135
  "test": "bun test src tests",
133
136
  "test:web": "cd web && bun install && bun test",
package/src/commander.ts CHANGED
@@ -27,7 +27,9 @@ import { registerCompletionCommand } from "./commands/completion.ts";
27
27
  import { registerConfigGetCommand } from "./commands/config-get.ts";
28
28
  import { registerContextCommand } from "./commands/context.ts";
29
29
  import { registerCookiesCommand } from "./commands/cookies.ts";
30
+ import { registerDecisionCommand } from "./commands/decision.ts";
30
31
  import { registerDeinitCommand } from "./commands/deinit.ts";
32
+ import { registerDevtoolsCommand } from "./commands/devtools.ts";
31
33
  import { registerDocsCommand } from "./commands/docs.ts";
32
34
  import { registerDoctorCommand } from "./commands/doctor.ts";
33
35
  import { registerEditBatchCommand } from "./commands/edit-batch.ts";
@@ -80,6 +82,20 @@ export interface HarneryContextOpts {
80
82
  skipCommands?: readonly string[];
81
83
  }
82
84
 
85
+ /** One row in a `harn env` section report. */
86
+ export interface EnvCheck {
87
+ label: string;
88
+ value: string;
89
+ status?: "ok" | "missing" | "warn" | "info";
90
+ }
91
+
92
+ /**
93
+ * A `harn env` section: an async probe returning its rows. Hosts register extra
94
+ * sections (e.g. cloud-provider connectivity) via `context.envSections`; harnery
95
+ * core ships only the generic sections (runtimes, docker, git).
96
+ */
97
+ export type EnvSection = () => Promise<EnvCheck[]>;
98
+
83
99
  export interface HarneryProgramContext {
84
100
  /** Project name (e.g., "my-monorepo"). Used in user-facing log lines + telemetry tags. */
85
101
  projectName?: string;
@@ -97,6 +113,13 @@ export interface HarneryProgramContext {
97
113
  * submodule row.
98
114
  */
99
115
  submodules?: readonly string[];
116
+ /**
117
+ * Extra `harn env` sections keyed by name (e.g. `{ gcp, bq }`). Merged into
118
+ * the built-in generic sections (runtimes, docker, git), so a host can expose
119
+ * `harn env <name>` for its own environment probes without harnery core
120
+ * carrying provider-specific checks. harn standalone ships none.
121
+ */
122
+ envSections?: Record<string, EnvSection>;
100
123
  /**
101
124
  * Optional callback that returns extra HTTP headers to attach to outbound
102
125
  * `fetch` calls based on the target URL. Useful for consumers that need
@@ -129,6 +152,16 @@ export interface HarneryProgramContext {
129
152
  * exclusions (`.claude/`, `.harnery/`, `.codex/`, `.cursor/`).
130
153
  */
131
154
  extraDocsExcludedPrefixes?: readonly string[];
155
+ /**
156
+ * Filenames permitted at the host project's `docs/` root (parent repo only).
157
+ * When set, `harn docs lint` flags any other `.md`/`.json` file sitting
158
+ * loose at `docs/` root (rule `docs-root-file`) — topic docs belong in
159
+ * `docs/<topic>/` subdirs. Names are matched exactly (basename). When
160
+ * omitted or empty, the rule is a no-op, so standalone `harn` and consumers
161
+ * that don't opt in are unaffected. Submodule `docs/` roots are never
162
+ * checked (their entry tiers differ from the parent's).
163
+ */
164
+ docsRootAllowlist?: readonly string[];
132
165
  /**
133
166
  * Default Host header for `tunnel up` when `--vhost` is omitted: a literal
134
167
  * host, or a resolver evaluated at start time (e.g. read a dev stack's
@@ -225,6 +258,8 @@ export function createHarneryProgram(opts: HarneryContextOpts = {}): Command {
225
258
  registerCompletionCommand(program, emit, opts.context);
226
259
  registerContextCommand(program, emit, opts.context);
227
260
  registerScratchCommand(program, emit);
261
+ registerDecisionCommand(program, emit);
262
+ registerDevtoolsCommand(program, emit);
228
263
  registerTunnelCommand(program, emit, opts.context);
229
264
  registerDocsCommand(program, emit, opts.context);
230
265
  registerAgentsCommand(program, emit);