gnhf 0.1.39 → 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 +109 -35
- 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
|
}
|
|
@@ -14476,6 +14493,29 @@ var CodexAgent = class {
|
|
|
14476
14493
|
};
|
|
14477
14494
|
//#endregion
|
|
14478
14495
|
//#region src/core/agents/opencode.ts
|
|
14496
|
+
const RETRYABLE_PROVIDER_ERROR_CODES = new Set(["server_is_overloaded"]);
|
|
14497
|
+
const RETRYABLE_PROVIDER_ERROR_TYPES = new Set(["service_unavailable_error", "overloaded_error"]);
|
|
14498
|
+
function extractStreamError(event, sessionId) {
|
|
14499
|
+
if (event.type === "error" && event.error) return event.error;
|
|
14500
|
+
const payload = event.payload;
|
|
14501
|
+
if (!payload) return null;
|
|
14502
|
+
if (payload.type === "error" || payload.type === "session.error") {
|
|
14503
|
+
if (payload.properties?.sessionID !== sessionId) return null;
|
|
14504
|
+
return payload.error ?? payload.properties?.error ?? null;
|
|
14505
|
+
}
|
|
14506
|
+
return null;
|
|
14507
|
+
}
|
|
14508
|
+
function isRetryableProviderError(error) {
|
|
14509
|
+
if (error.code && RETRYABLE_PROVIDER_ERROR_CODES.has(error.code)) return true;
|
|
14510
|
+
if (error.type && RETRYABLE_PROVIDER_ERROR_TYPES.has(error.type)) return true;
|
|
14511
|
+
return false;
|
|
14512
|
+
}
|
|
14513
|
+
function buildProviderErrorMessage(error) {
|
|
14514
|
+
const detail = error.message ?? error.type ?? error.code ?? "unknown error";
|
|
14515
|
+
if (error.code === "server_is_overloaded") return `OpenCode provider overloaded: ${detail}`;
|
|
14516
|
+
if (isRetryableProviderError(error)) return `OpenCode provider error: ${detail}`;
|
|
14517
|
+
return `OpenCode provider error: ${detail}`;
|
|
14518
|
+
}
|
|
14479
14519
|
const BLANKET_PERMISSION_RULESET = [{
|
|
14480
14520
|
permission: "*",
|
|
14481
14521
|
pattern: "*",
|
|
@@ -14855,10 +14895,10 @@ var OpenCodeAgent = class {
|
|
|
14855
14895
|
};
|
|
14856
14896
|
const usageByMessageId = /* @__PURE__ */ new Map();
|
|
14857
14897
|
const textParts = /* @__PURE__ */ new Map();
|
|
14858
|
-
let lastText = null;
|
|
14859
14898
|
let lastFinalAnswerText = null;
|
|
14860
14899
|
let lastUsageSignature = "0:0:0:0";
|
|
14861
14900
|
let structuredOutputFromSSE = null;
|
|
14901
|
+
let streamErrorInfo = null;
|
|
14862
14902
|
const eventCounts = {};
|
|
14863
14903
|
let firstEventAtMs = null;
|
|
14864
14904
|
let lastEventAtMs = null;
|
|
@@ -15000,11 +15040,22 @@ var OpenCodeAgent = class {
|
|
|
15000
15040
|
});
|
|
15001
15041
|
notePhase(phase);
|
|
15002
15042
|
if (!trimmed) return;
|
|
15003
|
-
lastText = nextText;
|
|
15004
15043
|
if (phase === "final_answer") lastFinalAnswerText = nextText;
|
|
15005
15044
|
onMessage?.(trimmed);
|
|
15006
15045
|
};
|
|
15007
15046
|
const handleEvent = (event) => {
|
|
15047
|
+
const errorInfo = extractStreamError(event, sessionId);
|
|
15048
|
+
if (errorInfo) {
|
|
15049
|
+
streamErrorInfo = errorInfo;
|
|
15050
|
+
appendDebugLog("opencode:stream:provider-error", {
|
|
15051
|
+
sessionId,
|
|
15052
|
+
type: errorInfo.type ?? null,
|
|
15053
|
+
code: errorInfo.code ?? null,
|
|
15054
|
+
message: errorInfo.message ?? null,
|
|
15055
|
+
retryable: isRetryableProviderError(errorInfo)
|
|
15056
|
+
});
|
|
15057
|
+
return true;
|
|
15058
|
+
}
|
|
15008
15059
|
const payload = event.payload;
|
|
15009
15060
|
const properties = payload?.properties;
|
|
15010
15061
|
if (!properties || properties.sessionID !== sessionId) return false;
|
|
@@ -15156,20 +15207,20 @@ var OpenCodeAgent = class {
|
|
|
15156
15207
|
usage
|
|
15157
15208
|
};
|
|
15158
15209
|
}
|
|
15159
|
-
|
|
15160
|
-
|
|
15210
|
+
if (streamErrorInfo) throw new Error(buildProviderErrorMessage(streamErrorInfo));
|
|
15211
|
+
const finalOutputText = toNonEmptyString(lastFinalAnswerText);
|
|
15212
|
+
if (finalOutputText === null) {
|
|
15161
15213
|
appendDebugLog("opencode:output:missing", {
|
|
15162
15214
|
sessionId,
|
|
15163
15215
|
hasStructuredOutput: structuredOutputFromSSE !== null
|
|
15164
15216
|
});
|
|
15165
|
-
throw new Error("
|
|
15217
|
+
throw new Error("OpenCode produced no final answer");
|
|
15166
15218
|
}
|
|
15167
|
-
const finalOutputText = outputText;
|
|
15168
15219
|
try {
|
|
15169
15220
|
const output = JSON.parse(finalOutputText);
|
|
15170
15221
|
appendDebugLog("opencode:output:structured", {
|
|
15171
15222
|
sessionId,
|
|
15172
|
-
source:
|
|
15223
|
+
source: "final_answer",
|
|
15173
15224
|
outputTextLength: finalOutputText.length
|
|
15174
15225
|
});
|
|
15175
15226
|
return {
|
|
@@ -15565,6 +15616,7 @@ function buildSystemPrompt(schema) {
|
|
|
15565
15616
|
"When you finish, reply with only valid JSON.",
|
|
15566
15617
|
"Do not wrap the JSON in markdown fences.",
|
|
15567
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.",
|
|
15568
15620
|
`The JSON must match this schema exactly: ${schema}`
|
|
15569
15621
|
].join(" ");
|
|
15570
15622
|
}
|
|
@@ -16053,25 +16105,47 @@ var RovoDevAgent = class {
|
|
|
16053
16105
|
appendDebugLog("rovodev:output:missing", { sessionId });
|
|
16054
16106
|
throw new Error("rovodev returned no text output");
|
|
16055
16107
|
}
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
}
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16066
|
-
|
|
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");
|
|
16067
16126
|
appendDebugLog("rovodev:output:parse-error", {
|
|
16068
16127
|
sessionId,
|
|
16069
16128
|
outputTextLength: finalText.length,
|
|
16070
16129
|
outputTextSample: finalText.slice(0, 512),
|
|
16071
|
-
error: serializeError(
|
|
16130
|
+
error: serializeError(parseError)
|
|
16072
16131
|
});
|
|
16073
|
-
throw new Error(`Failed to parse rovodev output: ${
|
|
16132
|
+
throw new Error(`Failed to parse rovodev output: ${parseError.message}`);
|
|
16074
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
|
+
};
|
|
16075
16149
|
}
|
|
16076
16150
|
async shutdownServer() {
|
|
16077
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.
|