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 +6 -0
- package/dist/cli.mjs +68 -28
- package/package.json +2 -1
- package/skills/gnhf/SKILL.md +174 -0
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/
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
}
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
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(
|
|
16130
|
+
error: serializeError(parseError)
|
|
16106
16131
|
});
|
|
16107
|
-
throw new Error(`Failed to parse rovodev output: ${
|
|
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.
|
|
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.
|