gnhf 0.1.40 → 0.1.41

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.
package/README.md CHANGED
@@ -97,6 +97,12 @@ npm run build
97
97
  npm link
98
98
  ```
99
99
 
100
+ ## Agent Skill
101
+
102
+ The npm package includes an agent-facing skill at `skills/gnhf/SKILL.md`. Agents that support local skills can copy or reference this file to learn how to run GNHF in Hands-Off mode for bounded overnight work, or Companion mode when the outer agent should steer and review a long-running GNHF run.
103
+
104
+ After installing from npm, the skill is available under the installed package directory. From a source checkout, use `skills/gnhf/SKILL.md` directly.
105
+
100
106
  ## How It Works
101
107
 
102
108
  ```
package/dist/cli.mjs CHANGED
@@ -13524,16 +13524,15 @@ function createAcpRuntime(options) {
13524
13524
  return new AcpxRuntime(options);
13525
13525
  }
13526
13526
  //#endregion
13527
- //#region src/core/agents/acp.ts
13528
- function buildAcpPrompt(prompt, schema) {
13529
- return `${prompt}
13530
-
13531
- ## gnhf final output contract
13532
-
13533
- When the iteration is complete, your final assistant message must be a single JSON object that matches this JSON Schema. Return only the JSON object. Do not wrap it in Markdown fences. Do not include prose before or after the JSON.
13534
-
13535
- ${JSON.stringify(schema, null, 2)}`;
13536
- }
13527
+ //#region src/core/agents/json-extract.ts
13528
+ /**
13529
+ * Helpers for pulling a JSON object out of an agent's final assistant message.
13530
+ *
13531
+ * Agents are instructed to return JSON only, but several (rovodev, ACP targets)
13532
+ * sometimes prepend prose or wrap the JSON in markdown fences. These helpers
13533
+ * recover the structured payload in those cases without changing behaviour for
13534
+ * the well-formed pure-JSON path.
13535
+ */
13537
13536
  function stripJsonFences(text) {
13538
13537
  const trimmed = text.trim();
13539
13538
  if (!trimmed.startsWith("```")) return trimmed;
@@ -13585,22 +13584,29 @@ function tryExtractBalancedObject(text, start) {
13585
13584
  * Look for a balanced JSON object inside `text`, preferring the rightmost one
13586
13585
  * (since the agent is supposed to end the message with the structured answer).
13587
13586
  */
13588
- function extractLastJsonObject(text) {
13587
+ function extractLastJsonObject(text, accepts) {
13589
13588
  let cursor = text.lastIndexOf("{");
13590
13589
  while (cursor >= 0) {
13591
13590
  const candidate = tryExtractBalancedObject(text, cursor);
13592
- if (candidate !== null) return candidate;
13591
+ if (candidate !== null) {
13592
+ if (!accepts) return candidate;
13593
+ try {
13594
+ if (accepts(JSON.parse(candidate))) return candidate;
13595
+ } catch {}
13596
+ }
13593
13597
  cursor = text.lastIndexOf("{", cursor - 1);
13594
13598
  }
13595
13599
  return null;
13596
13600
  }
13597
- function parseAgentJson(text) {
13601
+ function parseAgentJson(text, accepts) {
13598
13602
  const cleaned = stripJsonFences(text);
13599
13603
  if (!cleaned) return null;
13600
13604
  try {
13601
- return JSON.parse(cleaned);
13605
+ const parsed = JSON.parse(cleaned);
13606
+ if (!accepts || accepts(parsed)) return parsed;
13607
+ return null;
13602
13608
  } catch {}
13603
- const extracted = extractLastJsonObject(cleaned);
13609
+ const extracted = extractLastJsonObject(cleaned, accepts);
13604
13610
  if (!extracted) return null;
13605
13611
  try {
13606
13612
  return JSON.parse(extracted);
@@ -13608,6 +13614,17 @@ function parseAgentJson(text) {
13608
13614
  return null;
13609
13615
  }
13610
13616
  }
13617
+ //#endregion
13618
+ //#region src/core/agents/acp.ts
13619
+ function buildAcpPrompt(prompt, schema) {
13620
+ return `${prompt}
13621
+
13622
+ ## gnhf final output contract
13623
+
13624
+ When the iteration is complete, your final assistant message must be a single JSON object that matches this JSON Schema. Return only the JSON object. Do not wrap it in Markdown fences. Do not include prose before or after the JSON.
13625
+
13626
+ ${JSON.stringify(schema, null, 2)}`;
13627
+ }
13611
13628
  function isAbortError$2(error) {
13612
13629
  return error instanceof Error && (error.name === "AbortError" || error.message === "Agent was aborted");
13613
13630
  }
@@ -15599,6 +15616,7 @@ function buildSystemPrompt(schema) {
15599
15616
  "When you finish, reply with only valid JSON.",
15600
15617
  "Do not wrap the JSON in markdown fences.",
15601
15618
  "Do not include any prose before or after the JSON.",
15619
+ "Your final assistant message must contain the JSON object only - no preamble, no commentary, no build-status lines, nothing else.",
15602
15620
  `The JSON must match this schema exactly: ${schema}`
15603
15621
  ].join(" ");
15604
15622
  }
@@ -16087,25 +16105,47 @@ var RovoDevAgent = class {
16087
16105
  appendDebugLog("rovodev:output:missing", { sessionId });
16088
16106
  throw new Error("rovodev returned no text output");
16089
16107
  }
16090
- try {
16091
- const output = JSON.parse(finalText);
16092
- appendDebugLog("rovodev:output:parsed", {
16093
- sessionId,
16094
- outputTextLength: finalText.length
16095
- });
16096
- return {
16097
- output,
16098
- usage
16099
- };
16100
- } catch (error) {
16108
+ const schema = JSON.parse(readFileSync(this.schemaPath, "utf-8"));
16109
+ const parsed = parseAgentJson(finalText, (value) => {
16110
+ try {
16111
+ validateAgentOutput(value, schema);
16112
+ return true;
16113
+ } catch {
16114
+ return false;
16115
+ }
16116
+ });
16117
+ if (parsed === null) {
16118
+ const fallbackParsed = parseAgentJson(finalText);
16119
+ if (fallbackParsed !== null) try {
16120
+ validateAgentOutput(fallbackParsed, schema);
16121
+ } catch (error) {
16122
+ const message = error instanceof Error ? error.message : String(error);
16123
+ throw new Error(`Failed to parse rovodev output: ${message}`);
16124
+ }
16125
+ const parseError = /* @__PURE__ */ new SyntaxError("rovodev output did not contain a parseable JSON object");
16101
16126
  appendDebugLog("rovodev:output:parse-error", {
16102
16127
  sessionId,
16103
16128
  outputTextLength: finalText.length,
16104
16129
  outputTextSample: finalText.slice(0, 512),
16105
- error: serializeError(error)
16130
+ error: serializeError(parseError)
16106
16131
  });
16107
- throw new Error(`Failed to parse rovodev output: ${error instanceof Error ? error.message : String(error)}`);
16132
+ throw new Error(`Failed to parse rovodev output: ${parseError.message}`);
16108
16133
  }
16134
+ appendDebugLog("rovodev:output:parsed", {
16135
+ sessionId,
16136
+ outputTextLength: finalText.length
16137
+ });
16138
+ let output;
16139
+ try {
16140
+ output = validateAgentOutput(parsed, schema);
16141
+ } catch (error) {
16142
+ const message = error instanceof Error ? error.message : String(error);
16143
+ throw new Error(`Failed to parse rovodev output: ${message}`);
16144
+ }
16145
+ return {
16146
+ output,
16147
+ usage
16148
+ };
16109
16149
  }
16110
16150
  async shutdownServer() {
16111
16151
  if (!this.server || this.server.closed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gnhf",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "Before I go to bed, I tell my agents: good night, have fun",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "files": [
41
41
  "dist",
42
+ "skills",
42
43
  "LICENSE",
43
44
  "README.md"
44
45
  ],
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: gnhf
3
+ description: Use when the user asks to run GNHF, says they are going to sleep or leaving and wants an agent-managed coding run, asks to supervise, steer, or review an active GNHF run, or gives feedback on GNHF results.
4
+ ---
5
+
6
+ # GNHF
7
+
8
+ ## Overview
9
+
10
+ GNHF is an agent orchestrator: it repeatedly calls another coding agent until a natural-language stop condition is met. This skill teaches the host agent to prepare one durable run and, in Companion mode, steer or review it.
11
+
12
+ Core rule: the host agent orchestrates; GNHF executes. Do not manually implement inside the same scope while a GNHF worker is responsible for it unless the user explicitly changes the delegation.
13
+
14
+ In Companion mode, GNHF completion is not user acceptance. "Stop condition met" only means the worker stopped; the host still compares the result to the user's latest requirements and fresh verification.
15
+
16
+ ## Modes
17
+
18
+ Choose exactly one mode for the run.
19
+
20
+ ### Hands-Off
21
+
22
+ Use when the task is bounded, verification is clear, and the user wants one configured run to proceed without steering.
23
+
24
+ - Prepare a precise prompt with constraints, non-goals, verification, and stop condition.
25
+ - Launch GNHF and wait for completion.
26
+ - Intervene early only for hard failure, runaway scope, destructive behavior, or impossible prerequisites.
27
+ - Report the final GNHF status after exit.
28
+
29
+ Examples:
30
+
31
+ - English: "I'm going to bed. Use GNHF with Copilot to keep working on this branch and stop when the test suite passes."
32
+ - Chinese: "我要睡了。用 GNHF 接着跑这个分支,测试都过了就停。"
33
+
34
+ ### Companion
35
+
36
+ Use when the task is uncertain, exploratory, design-heavy, research-heavy, or likely to need course correction.
37
+
38
+ Default to Companion when the user asks to iterate until satisfied, requests multi-round work, provides review findings, asks for design/skill/documentation improvement, or asks for supervision.
39
+
40
+ - Keep a note of original intent, branch, session id, and last known result.
41
+ - Poll the active GNHF process until exit or until an intervention point appears.
42
+ - Intervene when the worker optimizes the wrong thing, repeats failed fixes, skips requested research, drifts scope, or claims success without evidence.
43
+ - Treat review findings as the next acceptance criteria.
44
+ - Prefer a new bounded GNHF prompt over manually taking over implementation.
45
+
46
+ Examples:
47
+
48
+ - English: "Run GNHF for a few rounds on this onboarding flow. Check the diff between rounds and tighten the next prompt if it starts polishing the wrong thing."
49
+ - Chinese: "用 GNHF 多跑几轮这个 onboarding 流程。每轮看一下 diff,如果它开始改偏了,就收窄下一轮 prompt。"
50
+
51
+ ## Launch
52
+
53
+ Check the installed CLI before relying on flags:
54
+
55
+ ```bash
56
+ gnhf --help
57
+ ```
58
+
59
+ Known shape:
60
+
61
+ ```bash
62
+ gnhf \
63
+ --agent <claude|codex|rovodev|opencode|copilot|pi|acp:<target>> \
64
+ --max-iterations <n> \
65
+ --stop-when "<observable completion condition>" \
66
+ --prevent-sleep on \
67
+ "<worker prompt>"
68
+ ```
69
+
70
+ If GNHF has no `--model` flag, put model requirements in the worker prompt or backend config. Do not invent unsupported flags.
71
+
72
+ Before launch:
73
+
74
+ ```bash
75
+ git status --short
76
+ git branch --show-current
77
+ git log --oneline --max-count=5
78
+ ```
79
+
80
+ Prompt skeleton:
81
+
82
+ ```text
83
+ Objective: <one concrete outcome>.
84
+
85
+ Use <agent/model requirement>. Work in this repo. Treat this as a long-running GNHF task.
86
+
87
+ Before coding, inspect the current repo, relevant docs, and recent commits. Preserve user changes. Do not make unrelated refactors.
88
+
89
+ After each meaningful slice, run relevant verification. If blocked, commit no fake success; leave notes with the blocker and evidence.
90
+
91
+ Stop only when: <observable completion condition>.
92
+ ```
93
+
94
+ ## Steer
95
+
96
+ In Companion mode, evaluate after each iteration or meaningful output chunk:
97
+
98
+ | Signal | Action |
99
+ | ------------------------------------------------------------- | -------------------------------------------------------------------- |
100
+ | Worker found a real blocker | Stop or relaunch with blocker-specific instructions |
101
+ | Good partial slice | Let it continue or tighten the next stop condition |
102
+ | Skipped requested research | Relaunch with research as explicit first deliverable |
103
+ | Worker changes unrelated files | Stop and review before continuing |
104
+ | Worker claims success without verification | Review immediately; relaunch only with evidence-based stop condition |
105
+ | Reviewer finds a blocking issue or user says not satisfactory | Relaunch with that finding as the sole bounded correction |
106
+
107
+ Steering prompt:
108
+
109
+ ```text
110
+ Continue from the current repo state. The previous run partially succeeded: <evidence>.
111
+
112
+ Do not redo completed work. Focus only on <bounded correction>.
113
+
114
+ The issue to fix now is <specific observed issue>. Verify with <commands/checks>.
115
+
116
+ Stop only when <observable condition>.
117
+ ```
118
+
119
+ ## Companion Review
120
+
121
+ Use only in Companion mode, when the host is supervising quality or deciding whether to continue with another bounded run.
122
+
123
+ 1. Inspect branch, status, commits, changed files, and diff.
124
+ 2. Read GNHF notes/logs as claims, not evidence.
125
+ 3. Run independent verification: tests, lint, build, typecheck, manual QA, or domain-specific checks.
126
+ 4. Compare the result to the stop condition and the user's latest feedback.
127
+ 5. Decide: **Mergeable**, **Needs follow-up GNHF run**, or **Do not merge**.
128
+
129
+ If the result needs follow-up, continue in Companion mode instead of presenting the run as complete. Do not merge unless explicitly authorized.
130
+
131
+ ## Findings
132
+
133
+ Use when the user provides findings such as "not preserved", "scope drift", "missing requirement", or "why did you stop".
134
+
135
+ 1. Treat the run as Companion mode.
136
+ 2. Convert each finding into an observable correction. Preserve severity, file/line scope, and the user's wording.
137
+ 3. Relaunch on the same candidate branch when salvageable.
138
+ 4. Prompt the worker to fix only the bounded finding, preserve completed valid work, verify, and stop only when the finding is no longer true.
139
+ 5. Review again after the follow-up.
140
+ 6. Repeat until no blocking findings remain, verification passes, or a real blocker is found.
141
+
142
+ ## Morning Review
143
+
144
+ Use when the user returns with "good morning", "how did last night's run go?", or similar after a GNHF run.
145
+
146
+ Do not ask what to review first. Reconstruct state:
147
+
148
+ ```bash
149
+ git status --short
150
+ git branch --show-current
151
+ git log --oneline --decorate --max-count=20
152
+ pgrep -fl 'gnhf|claude|codex|copilot|opencode|rovodev' || true
153
+ ```
154
+
155
+ Inspect likely GNHF branches, notes, logs, terminal sessions, and changed files. If a GNHF process is still running, report that first.
156
+
157
+ Report mode, agent, branch, status, changes, verification, stop-condition result, quality assessment, and recommended next action. Never summarize an overnight run from memory.
158
+
159
+ ## Agent
160
+
161
+ - `copilot`: explicit GitHub Copilot CLI request or local Copilot config.
162
+ - `codex`: repo-aware code work or review-heavy tasks.
163
+ - `claude`: reasoning-heavy implementation or prose-heavy planning when configured.
164
+ - `pi`: explicit Pi request or local Pi configuration.
165
+ - `opencode` / `rovodev`: explicit request or repo-specific setup.
166
+ - `acp:<target>`: explicit ACP target request, or when the user wants to drive a custom ACP-compatible agent through GNHF.
167
+
168
+ ## Safety
169
+
170
+ - Preserve user changes. Never run destructive git commands to clean up a GNHF branch.
171
+ - In Companion mode, do not trust a worker's success summary without fresh verification.
172
+ - Keep prompts outcome-based and evidence-based.
173
+ - Use concrete stop conditions. Bad: "looks good". Good: "the target workflow succeeds, relevant checks pass, and no unrelated files changed."
174
+ - If the user is away, produce branches and a status report, not irreversible changes, unless explicitly authorized.