gnhf 0.1.0 → 0.1.3

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 (3) hide show
  1. package/README.md +20 -14
  2. package/dist/cli.mjs +32 -18
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -1,4 +1,8 @@
1
- <h1 align="center">gnhf</h1>
1
+ # gnhf
2
+
3
+ <p align="center">Before I go to bed, I tell my agents:</p>
4
+ <h1 align="center">good night, have fun</h1>
5
+
2
6
  <p align="center">
3
7
  <a href="https://www.npmjs.com/package/gnhf"
4
8
  ><img
@@ -33,14 +37,15 @@
33
37
  /></a>
34
38
  </p>
35
39
 
36
- <h3 align="center">Before I go to bed, I tell my agents: good night, have fun.</h3>
37
-
38
- You have a backlog of improvements you'll never get to. Tests that need writing, complexity that needs reducing, types that need fixing. You could spend a Saturday on it, or you could hand it to a coding agent and go to sleep.
40
+ <p align="center">
41
+ <img src="docs/splash.png" alt="gnhf — Good Night, Have Fun" width="800">
42
+ </p>
39
43
 
40
- gnhf is an orchestrator that runs your coding agent in a loop — each iteration makes one small, committed, documented change towards an objective. You wake up to a branch full of clean work and a log of everything that happened.
44
+ gnhf is a [ralph loop](https://ghuntley.com/ralph/), autoresearch-style orchestrator that keeps your agents running while you sleep — each iteration makes one small, committed, documented change towards an objective.
45
+ You wake up to a branch full of clean work and a log of everything that happened.
41
46
 
42
- - **Set it and forget it** — one command starts an autonomous loop that runs until you Ctrl+C
43
- - **Safe by design** — each iteration is committed on success, rolled back on failure, with exponential backoff and auto-abort after consecutive failures
47
+ - **Dead simple** — one command starts an autonomous loop that runs until you Ctrl+C
48
+ - **Long running** — each iteration is committed on success, rolled back on failure, with sensible retries and exponential backoff
44
49
  - **Agent-agnostic** — works with Claude Code or Codex out of the box
45
50
 
46
51
  ## Quick Start
@@ -105,7 +110,7 @@ npm link
105
110
  │ ┌──────────┘ │
106
111
  ▼ ▼ │
107
112
  ┌────────────┐ yes ┌──────────┐ │
108
- 5 consec. ├─────────►│ abort │ │
113
+ 3 consec. ├─────────►│ abort │ │
109
114
  │ failures? │ └──────────┘ │
110
115
  └─────┬──────┘ │
111
116
  no │ │
@@ -118,11 +123,12 @@ npm link
118
123
 
119
124
  ## CLI Reference
120
125
 
121
- | Command | Description |
122
- | ----------------------- | ----------------------------------------------- |
123
- | `gnhf "<prompt>"` | Start a new run with the given objective |
124
- | `gnhf` | Resume a run (when on an existing gnhf/ branch) |
125
- | `echo "prompt" \| gnhf` | Pipe prompt via stdin |
126
+ | Command | Description |
127
+ | ------------------------- | ----------------------------------------------- |
128
+ | `gnhf "<prompt>"` | Start a new run with the given objective |
129
+ | `gnhf` | Resume a run (when on an existing gnhf/ branch) |
130
+ | `echo "<prompt>" \| gnhf` | Pipe prompt via stdin |
131
+ | `cat prd.md \| gnhf` | Pipe a large spec or PRD via stdin |
126
132
 
127
133
  ### Flags
128
134
 
@@ -140,7 +146,7 @@ Config lives at `~/.gnhf/config.yml`:
140
146
  agent: claude
141
147
 
142
148
  # Abort after this many consecutive failures
143
- maxConsecutiveFailures: 5
149
+ maxConsecutiveFailures: 3
144
150
  ```
145
151
 
146
152
  CLI flags override config file values.
package/dist/cli.mjs CHANGED
@@ -12,7 +12,7 @@ import { createHash } from "node:crypto";
12
12
  //#region src/core/config.ts
13
13
  const DEFAULT_CONFIG = {
14
14
  agent: "claude",
15
- maxConsecutiveFailures: 5
15
+ maxConsecutiveFailures: 3
16
16
  };
17
17
  function loadConfig(overrides) {
18
18
  const configPath = join(homedir(), ".gnhf", "config.yml");
@@ -401,11 +401,18 @@ This is iteration ${params.n} of an ongoing loop to fully accomplish the objecti
401
401
  ## Instructions
402
402
 
403
403
  1. Read .gnhf/runs/${params.runId}/notes.md first to understand what has been done in previous iterations.
404
- 2. Focus on one smallest logical unit of work that's individually testable and would make incremental progress towards the objective. Do NOT try to do everything at once.
405
- 3. Run build/tests/linters/formatters if available to validate your work.
404
+ 2. Focus on the next smallest logical unit of work that's individually testable and would make incremental progress towards the objective - that's the scope of this iteration.
405
+ 3. If you made code changes, run build/tests/linters/formatters if available to validate your work.
406
406
  4. Do NOT make any git commits. Commits will be handled automatically by the gnhf orchestrator.
407
407
  5. When you are done, respond with a JSON object according to the provided schema.
408
408
 
409
+ ## Output
410
+
411
+ - success: whether you were able to complete your iteration. set to false only if something made it impossible for you to do your work
412
+ - summary: a concise one-sentence summary of the accomplishment in this iteration
413
+ - key_changes_made: an array of descriptions for key changes you made. don't group this by file - group by logical units of work. don't describe activities - describe material outcomes
414
+ - key_learnings: an array of new learnings that were surprising and weren't captured by previous notes
415
+
409
416
  ## Objective
410
417
 
411
418
  ${params.prompt}`;
@@ -767,24 +774,30 @@ function formatTokens(count) {
767
774
  //#region src/utils/wordwrap.ts
768
775
  function wordWrap(text, width, maxLines) {
769
776
  if (!text) return [];
770
- const words = text.split(/\s+/);
771
777
  const lines = [];
772
- let current = "";
773
- for (const word of words) {
774
- if (word.length > width) {
775
- if (current) {
776
- lines.push(current);
777
- current = "";
778
- }
779
- for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
778
+ for (const paragraph of text.split("\n")) {
779
+ const words = paragraph.split(/\s+/).filter(Boolean);
780
+ if (words.length === 0) {
781
+ lines.push("");
780
782
  continue;
781
783
  }
782
- if (current && current.length + 1 + word.length > width) {
783
- lines.push(current);
784
- current = word;
785
- } else current = current ? current + " " + word : word;
784
+ let current = "";
785
+ for (const word of words) {
786
+ if (word.length > width) {
787
+ if (current) {
788
+ lines.push(current);
789
+ current = "";
790
+ }
791
+ for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
792
+ continue;
793
+ }
794
+ if (current && current.length + 1 + word.length > width) {
795
+ lines.push(current);
796
+ current = word;
797
+ } else current = current ? current + " " + word : word;
798
+ }
799
+ if (current) lines.push(current);
786
800
  }
787
- if (current) lines.push(current);
788
801
  if (maxLines && lines.length > maxLines) {
789
802
  const capped = lines.slice(0, maxLines);
790
803
  const last = capped[maxLines - 1];
@@ -1142,6 +1155,7 @@ function slugifyPrompt(prompt) {
1142
1155
  }
1143
1156
  //#endregion
1144
1157
  //#region src/cli.ts
1158
+ const packageVersion = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8")).version;
1145
1159
  function ask(question) {
1146
1160
  const rl = createInterface({
1147
1161
  input: process$1.stdin,
@@ -1155,7 +1169,7 @@ function ask(question) {
1155
1169
  });
1156
1170
  }
1157
1171
  const program = new Command();
1158
- program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version("0.1.0").argument("[prompt]", "The objective for the coding agent").option("--agent <agent>", "Agent to use (claude or codex)", "claude").option("--mock", "", false).action(async (promptArg, options) => {
1172
+ program.name("gnhf").description("Before I go to bed, I tell my agents: good night, have fun").version(packageVersion).argument("[prompt]", "The objective for the coding agent").option("--agent <agent>", "Agent to use (claude or codex)", "claude").option("--mock", "", false).action(async (promptArg, options) => {
1159
1173
  if (options.mock) {
1160
1174
  const mock = new MockOrchestrator();
1161
1175
  enterAltScreen();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Before I go to bed, I tell my agents: good night, have fun",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,6 +40,10 @@
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/kunchenguid/gnhf"
46
+ },
43
47
  "engines": {
44
48
  "node": ">=20"
45
49
  }