helloagents 3.0.12 → 3.0.15-beta.1
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/.claude-plugin/marketplace.json +6 -4
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +169 -30
- package/README_CN.md +169 -30
- package/bootstrap-lite.md +27 -20
- package/bootstrap.md +30 -23
- package/cli.mjs +119 -11
- package/gemini-extension.json +1 -1
- package/install.ps1 +125 -0
- package/install.sh +118 -0
- package/package.json +23 -4
- package/scripts/advisor-state.mjs +36 -63
- package/scripts/capability-registry.mjs +3 -3
- package/scripts/cli-branch.mjs +84 -0
- package/scripts/cli-codex-config.mjs +11 -20
- package/scripts/cli-codex.mjs +32 -38
- package/scripts/cli-doctor-render.mjs +4 -0
- package/scripts/cli-doctor.mjs +40 -30
- package/scripts/cli-host-detect.mjs +0 -1
- package/scripts/cli-hosts.mjs +16 -8
- package/scripts/cli-lifecycle-hosts.mjs +92 -27
- package/scripts/cli-lifecycle.mjs +9 -7
- package/scripts/cli-messages.mjs +34 -16
- package/scripts/cli-runtime-carrier.mjs +36 -0
- package/scripts/cli-runtime-root.mjs +72 -0
- package/scripts/cli-toml.mjs +0 -79
- package/scripts/cli-utils.mjs +30 -4
- package/scripts/closeout-state.mjs +35 -62
- package/scripts/delivery-gate-messages.mjs +70 -0
- package/scripts/delivery-gate.mjs +9 -75
- package/scripts/guard-rules.mjs +42 -42
- package/scripts/guard.mjs +44 -24
- package/scripts/notify-context.mjs +19 -28
- package/scripts/notify-gates.mjs +2 -0
- package/scripts/notify-route.mjs +9 -7
- package/scripts/notify-ui.mjs +46 -33
- package/scripts/notify.mjs +60 -32
- package/scripts/project-storage.mjs +35 -66
- package/scripts/ralph-loop.mjs +36 -31
- package/scripts/replay-state.mjs +31 -128
- package/scripts/review-state.mjs +34 -61
- package/scripts/runtime-artifacts.mjs +95 -0
- package/scripts/runtime-context.mjs +35 -29
- package/scripts/runtime-scope.mjs +313 -0
- package/scripts/session-capsule.mjs +202 -0
- package/scripts/turn-state-cli.mjs +17 -0
- package/scripts/turn-state.mjs +185 -66
- package/scripts/turn-stop-gate.mjs +24 -6
- package/scripts/verify-state.mjs +34 -85
- package/scripts/visual-state.mjs +38 -65
- package/scripts/workflow-core.mjs +2 -2
- package/scripts/workflow-plan-files.mjs +1 -1
- package/scripts/workflow-recommendation.mjs +17 -13
- package/scripts/workflow-state.mjs +5 -5
- package/skills/commands/build/SKILL.md +1 -1
- package/skills/commands/commit/SKILL.md +1 -1
- package/skills/commands/help/SKILL.md +3 -3
- package/skills/commands/loop/SKILL.md +1 -1
- package/skills/commands/plan/SKILL.md +8 -6
- package/skills/commands/prd/SKILL.md +5 -3
- package/skills/commands/verify/SKILL.md +5 -5
- package/skills/hello-debug/SKILL.md +20 -3
- package/skills/hello-review/SKILL.md +2 -2
- package/skills/hello-subagent/SKILL.md +2 -2
- package/skills/hello-test/SKILL.md +6 -2
- package/skills/hello-ui/SKILL.md +4 -4
- package/skills/hello-verify/SKILL.md +10 -7
- package/skills/helloagents/SKILL.md +12 -7
- package/templates/context.md +6 -0
- package/templates/plans/plan.md +3 -0
- package/templates/plans/tasks.md +8 -3
package/scripts/notify.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Zero external dependencies, ES module, cross-platform
|
|
4
4
|
|
|
5
5
|
import { join, dirname } from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { homedir } from 'node:os';
|
|
9
9
|
import { playSound as _playSound, desktopNotify as _desktopNotify } from './notify-ui.mjs';
|
|
@@ -13,10 +13,12 @@ import { shouldIgnoreCodexNotifyClient } from './notify-events.mjs';
|
|
|
13
13
|
import { runGateScript } from './notify-gates.mjs';
|
|
14
14
|
import { handleRouteCommand, resolveBootstrapFile } from './notify-route.mjs';
|
|
15
15
|
import { readSettings, readStdinJson, output, suppressedOutput, emptySuppress } from './notify-shared.mjs';
|
|
16
|
-
import { clearRouteContext, writeRouteContext } from './runtime-context.mjs';
|
|
16
|
+
import { clearRouteContext, getApplicableRouteContext, writeRouteContext } from './runtime-context.mjs';
|
|
17
17
|
import { appendReplayEvent, startReplaySession } from './replay-state.mjs';
|
|
18
18
|
import { clearTurnState, readTurnState } from './turn-state.mjs';
|
|
19
19
|
import { getWorkflowRecommendation } from './workflow-state.mjs';
|
|
20
|
+
import { resolveSessionToken } from './session-token.mjs';
|
|
21
|
+
import { isProjectRuntimeActive } from './runtime-scope.mjs';
|
|
20
22
|
|
|
21
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
22
24
|
const __dirname = dirname(__filename);
|
|
@@ -33,6 +35,7 @@ const EVENT_NAME = {
|
|
|
33
35
|
UserPromptSubmit: IS_GEMINI ? 'BeforeAgent' : 'UserPromptSubmit',
|
|
34
36
|
PreCompact: IS_GEMINI ? 'BeforeAgent' : 'PreCompact',
|
|
35
37
|
};
|
|
38
|
+
const RALPH_LOOP_ROUTE_COMMANDS = new Set(['verify', 'loop']);
|
|
36
39
|
|
|
37
40
|
const playSound = (event) => _playSound(PKG_ROOT, event);
|
|
38
41
|
const desktopNotify = (event, extra) => _desktopNotify(PKG_ROOT, event, extra);
|
|
@@ -64,9 +67,18 @@ function getSettings() {
|
|
|
64
67
|
return readSettings(CONFIG_FILE);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
function
|
|
70
|
+
function shouldRunRalphLoop(cwd, turnState, payload = {}) {
|
|
71
|
+
if (!turnState || turnState.kind !== 'complete') return false;
|
|
72
|
+
if (turnState.requiresDeliveryGate) return true;
|
|
73
|
+
const routeContext = getApplicableRouteContext({ cwd, payload });
|
|
74
|
+
return RALPH_LOOP_ROUTE_COMMANDS.has(routeContext?.skillName);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runRalphLoop(payload, { turnState } = {}) {
|
|
68
78
|
const settings = getSettings();
|
|
69
79
|
if (settings.ralph_loop_enabled === false) return false;
|
|
80
|
+
const cwd = payload.cwd || process.cwd();
|
|
81
|
+
if (!shouldRunRalphLoop(cwd, turnState, payload)) return false;
|
|
70
82
|
return runGateScript({
|
|
71
83
|
payload,
|
|
72
84
|
host: HOST,
|
|
@@ -106,13 +118,24 @@ function runTurnStopGate(payload) {
|
|
|
106
118
|
});
|
|
107
119
|
}
|
|
108
120
|
|
|
109
|
-
function
|
|
110
|
-
const
|
|
121
|
+
function attachTurnSession(payload = {}, cwd = payload.cwd || process.cwd()) {
|
|
122
|
+
const sessionId = resolveSessionToken({
|
|
123
|
+
payload,
|
|
124
|
+
env: process.env,
|
|
125
|
+
ppid: process.ppid,
|
|
126
|
+
allowPpidFallback: !isProjectRuntimeActive(cwd),
|
|
127
|
+
});
|
|
128
|
+
if (!sessionId || payload.sessionId) return payload;
|
|
129
|
+
return { ...payload, sessionId };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function readMainTurnState(cwd, payload = {}) {
|
|
133
|
+
const turnState = readTurnState(cwd, { payload });
|
|
111
134
|
return turnState?.role === 'main' ? turnState : null;
|
|
112
135
|
}
|
|
113
136
|
|
|
114
|
-
function consumeMainTurnState(cwd, turnState) {
|
|
115
|
-
if (turnState?.role === 'main') clearTurnState(cwd);
|
|
137
|
+
function consumeMainTurnState(cwd, turnState, payload = {}) {
|
|
138
|
+
if (turnState?.role === 'main') clearTurnState(cwd, { payload });
|
|
116
139
|
}
|
|
117
140
|
|
|
118
141
|
function shouldProcessCloseout(turnState) {
|
|
@@ -136,6 +159,7 @@ function cmdPreCompact() {
|
|
|
136
159
|
host: HOST,
|
|
137
160
|
event: 'pre_compact_snapshot',
|
|
138
161
|
source: 'pre-compact',
|
|
162
|
+
payload,
|
|
139
163
|
details: {
|
|
140
164
|
bootstrapFile,
|
|
141
165
|
installMode: settings.install_mode || '',
|
|
@@ -146,7 +170,7 @@ function cmdPreCompact() {
|
|
|
146
170
|
|
|
147
171
|
function cmdRoute() {
|
|
148
172
|
const payload = readStdinJson();
|
|
149
|
-
clearTurnState(payload.cwd || process.cwd());
|
|
173
|
+
clearTurnState(payload.cwd || process.cwd(), { payload });
|
|
150
174
|
handleRouteCommand({
|
|
151
175
|
payload,
|
|
152
176
|
host: HOST,
|
|
@@ -181,15 +205,17 @@ function cmdInject() {
|
|
|
181
205
|
source,
|
|
182
206
|
bootstrapFile,
|
|
183
207
|
installMode: settings.install_mode || '',
|
|
208
|
+
payload,
|
|
184
209
|
});
|
|
185
210
|
appendReplayEvent(cwd, {
|
|
186
211
|
host: HOST,
|
|
187
212
|
event: 'session_injected',
|
|
188
213
|
source,
|
|
214
|
+
payload,
|
|
189
215
|
details: {
|
|
190
216
|
bootstrapFile,
|
|
191
217
|
installMode: settings.install_mode || '',
|
|
192
|
-
activatedProject:
|
|
218
|
+
activatedProject: isProjectRuntimeActive(cwd),
|
|
193
219
|
},
|
|
194
220
|
});
|
|
195
221
|
const context = buildInjectContext({
|
|
@@ -201,27 +227,28 @@ function cmdInject() {
|
|
|
201
227
|
cwd,
|
|
202
228
|
payload,
|
|
203
229
|
});
|
|
204
|
-
clearRouteContext();
|
|
205
|
-
clearTurnState(cwd);
|
|
230
|
+
clearRouteContext({ cwd, payload });
|
|
231
|
+
clearTurnState(cwd, { payload });
|
|
206
232
|
suppressedOutput(EVENT_NAME.SessionStart, context || undefined);
|
|
207
233
|
}
|
|
208
234
|
|
|
209
235
|
function cmdStop() {
|
|
210
236
|
const payload = readStdinJson();
|
|
211
237
|
const cwd = payload.cwd || process.cwd();
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
238
|
+
const turnPayload = attachTurnSession(payload, cwd);
|
|
239
|
+
const turnState = readMainTurnState(cwd, turnPayload);
|
|
240
|
+
if (runTurnStopGate(turnPayload)) {
|
|
241
|
+
if (turnState && turnState.kind !== 'complete') consumeMainTurnState(cwd, turnState, turnPayload);
|
|
215
242
|
return;
|
|
216
243
|
}
|
|
217
244
|
const shouldProcess = shouldProcessCloseout(turnState);
|
|
218
|
-
if (shouldProcess && runRalphLoop(
|
|
219
|
-
consumeMainTurnState(cwd, turnState);
|
|
245
|
+
if (shouldProcess && runRalphLoop(turnPayload, { turnState })) {
|
|
246
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
220
247
|
notifyByLevel('warning', buildNotifyExtra(payload));
|
|
221
248
|
return;
|
|
222
249
|
}
|
|
223
|
-
if (shouldProcess && runDeliveryGate(
|
|
224
|
-
consumeMainTurnState(cwd, turnState);
|
|
250
|
+
if (shouldProcess && runDeliveryGate(turnPayload)) {
|
|
251
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
225
252
|
notifyByLevel('warning', buildNotifyExtra(payload));
|
|
226
253
|
return;
|
|
227
254
|
}
|
|
@@ -230,8 +257,8 @@ function cmdStop() {
|
|
|
230
257
|
if (shouldProcess) {
|
|
231
258
|
notifyByLevel('complete', buildNotifyExtra(payload), settings);
|
|
232
259
|
}
|
|
233
|
-
consumeMainTurnState(cwd, turnState);
|
|
234
|
-
clearRouteContext();
|
|
260
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
261
|
+
clearRouteContext({ cwd, payload: turnPayload });
|
|
235
262
|
emptySuppress();
|
|
236
263
|
}
|
|
237
264
|
|
|
@@ -246,6 +273,8 @@ function cmdDesktop() {
|
|
|
246
273
|
function cmdCodexNotify() {
|
|
247
274
|
let data = {};
|
|
248
275
|
try { data = JSON.parse(process.argv[3] || '{}'); } catch {}
|
|
276
|
+
const cwd = data.cwd || process.cwd();
|
|
277
|
+
const turnPayload = attachTurnSession(data, cwd);
|
|
249
278
|
|
|
250
279
|
const type = data.type || '';
|
|
251
280
|
const client = data.client || '';
|
|
@@ -257,34 +286,33 @@ function cmdCodexNotify() {
|
|
|
257
286
|
}
|
|
258
287
|
if (type !== 'agent-turn-complete') return;
|
|
259
288
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (turnState && turnState.kind !== 'complete') consumeMainTurnState(cwd, turnState);
|
|
289
|
+
const turnState = readMainTurnState(cwd, turnPayload);
|
|
290
|
+
if (runTurnStopGate(turnPayload)) {
|
|
291
|
+
if (turnState && turnState.kind !== 'complete') consumeMainTurnState(cwd, turnState, turnPayload);
|
|
264
292
|
return;
|
|
265
293
|
}
|
|
266
294
|
if (!turnState) return;
|
|
267
295
|
if (turnState.kind !== 'complete') {
|
|
268
|
-
consumeMainTurnState(cwd, turnState);
|
|
269
|
-
clearRouteContext();
|
|
296
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
297
|
+
clearRouteContext({ cwd, payload: turnPayload });
|
|
270
298
|
return;
|
|
271
299
|
}
|
|
272
300
|
|
|
273
301
|
const settings = getSettings();
|
|
274
|
-
if (runRalphLoop(
|
|
275
|
-
consumeMainTurnState(cwd, turnState);
|
|
302
|
+
if (runRalphLoop(turnPayload, { turnState })) {
|
|
303
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
276
304
|
notifyByLevel('warning', buildNotifyExtra(data), settings);
|
|
277
305
|
return;
|
|
278
306
|
}
|
|
279
|
-
if (runDeliveryGate(
|
|
280
|
-
consumeMainTurnState(cwd, turnState);
|
|
307
|
+
if (runDeliveryGate(turnPayload)) {
|
|
308
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
281
309
|
notifyByLevel('warning', buildNotifyExtra(data), settings);
|
|
282
310
|
return;
|
|
283
311
|
}
|
|
284
312
|
|
|
285
313
|
notifyByLevel('complete', buildNotifyExtra(data), settings);
|
|
286
|
-
consumeMainTurnState(cwd, turnState);
|
|
287
|
-
clearRouteContext();
|
|
314
|
+
consumeMainTurnState(cwd, turnState, turnPayload);
|
|
315
|
+
clearRouteContext({ cwd, payload: turnPayload });
|
|
288
316
|
}
|
|
289
317
|
|
|
290
318
|
const cmd = process.argv[2] || '';
|
|
@@ -5,13 +5,19 @@ import { homedir } from 'node:os'
|
|
|
5
5
|
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'node:path'
|
|
6
6
|
|
|
7
7
|
import { DEFAULTS } from './cli-config.mjs'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
PROJECT_DIR_NAME,
|
|
10
|
+
getProjectActivationDir,
|
|
11
|
+
getProjectSessionScope,
|
|
12
|
+
normalizeRuntimeOptions,
|
|
13
|
+
} from './runtime-scope.mjs'
|
|
14
|
+
import {
|
|
15
|
+
getSessionArtifactPath,
|
|
16
|
+
getSessionArtifactRelativePath,
|
|
17
|
+
} from './session-capsule.mjs'
|
|
9
18
|
|
|
10
|
-
export const PROJECT_DIR_NAME = '.helloagents'
|
|
11
19
|
const PROJECTS_DIR_NAME = 'projects'
|
|
12
|
-
const PROJECT_SESSIONS_DIR_NAME = 'sessions'
|
|
13
20
|
const PROJECT_STORE_MODES = new Set(['local', 'repo-shared'])
|
|
14
|
-
const DEFAULT_STATE_SESSION_TOKEN = 'default'
|
|
15
21
|
|
|
16
22
|
function safeJson(filePath) {
|
|
17
23
|
try {
|
|
@@ -34,19 +40,6 @@ function runGitRevParse(cwd, args = []) {
|
|
|
34
40
|
}
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
function runGitCommand(cwd, args = []) {
|
|
38
|
-
try {
|
|
39
|
-
return execFileSync('git', args, {
|
|
40
|
-
cwd,
|
|
41
|
-
encoding: 'utf-8',
|
|
42
|
-
timeout: 5_000,
|
|
43
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
44
|
-
}).trim()
|
|
45
|
-
} catch {
|
|
46
|
-
return ''
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
43
|
function resolveGitTopLevel(cwd) {
|
|
51
44
|
const absolute = runGitRevParse(cwd, ['--path-format=absolute', '--show-toplevel'])
|
|
52
45
|
if (absolute) return normalize(absolute)
|
|
@@ -70,16 +63,6 @@ function sanitizeRepoName(value = '') {
|
|
|
70
63
|
return normalized || 'project'
|
|
71
64
|
}
|
|
72
65
|
|
|
73
|
-
function sanitizeStateScopeSegment(value = '', fallback = '') {
|
|
74
|
-
const normalized = String(value)
|
|
75
|
-
.trim()
|
|
76
|
-
.toLowerCase()
|
|
77
|
-
.replace(/[^a-z0-9._-]+/g, '-')
|
|
78
|
-
.replace(/^-+|-+$/g, '')
|
|
79
|
-
.slice(0, 48)
|
|
80
|
-
return normalized || fallback
|
|
81
|
-
}
|
|
82
|
-
|
|
83
66
|
function buildProjectKey(cwd) {
|
|
84
67
|
const repoRoot = resolveGitTopLevel(cwd)
|
|
85
68
|
const commonDir = resolveGitCommonDir(cwd, repoRoot)
|
|
@@ -113,15 +96,6 @@ function formatPromptPath(pathValue = '') {
|
|
|
113
96
|
return pathValue ? normalize(pathValue).replace(/\\/g, '/') : ''
|
|
114
97
|
}
|
|
115
98
|
|
|
116
|
-
function resolveGitBranchName(cwd) {
|
|
117
|
-
const branchName = runGitRevParse(cwd, ['--abbrev-ref', 'HEAD'])
|
|
118
|
-
if (branchName && branchName !== 'HEAD') return branchName
|
|
119
|
-
|
|
120
|
-
const symbolicBranchName = runGitCommand(cwd, ['symbolic-ref', '--quiet', '--short', 'HEAD'])
|
|
121
|
-
if (symbolicBranchName && symbolicBranchName !== 'HEAD') return symbolicBranchName
|
|
122
|
-
return ''
|
|
123
|
-
}
|
|
124
|
-
|
|
125
99
|
export function normalizeProjectStoreMode(value) {
|
|
126
100
|
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
127
101
|
return PROJECT_STORE_MODES.has(normalized) ? normalized : DEFAULTS.project_store_mode
|
|
@@ -136,37 +110,16 @@ export function getProjectStoreMode() {
|
|
|
136
110
|
return normalizeProjectStoreMode(settings.project_store_mode)
|
|
137
111
|
}
|
|
138
112
|
|
|
139
|
-
export function
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function getProjectSessionStateScope(cwd, {
|
|
144
|
-
payload = {},
|
|
145
|
-
env = process.env,
|
|
146
|
-
ppid = process.ppid,
|
|
147
|
-
} = {}) {
|
|
148
|
-
const rawSessionToken = resolveSessionToken({
|
|
149
|
-
payload,
|
|
150
|
-
env,
|
|
151
|
-
ppid,
|
|
152
|
-
allowPpidFallback: false,
|
|
153
|
-
})
|
|
154
|
-
const branchName = sanitizeStateScopeSegment(resolveGitBranchName(cwd), 'detached')
|
|
155
|
-
const sessionToken = sanitizeStateScopeSegment(rawSessionToken, DEFAULT_STATE_SESSION_TOKEN)
|
|
156
|
-
const sessionDir = join(
|
|
157
|
-
getProjectActivationDir(cwd),
|
|
158
|
-
PROJECT_SESSIONS_DIR_NAME,
|
|
159
|
-
branchName,
|
|
160
|
-
sessionToken,
|
|
161
|
-
)
|
|
113
|
+
export function getProjectSessionStateScope(cwd, options = {}) {
|
|
114
|
+
const scope = getProjectSessionScope(cwd, normalizeRuntimeOptions(options))
|
|
162
115
|
|
|
163
116
|
return {
|
|
164
117
|
stateScope: 'session',
|
|
165
|
-
stateSessionToken:
|
|
166
|
-
stateSessionMode:
|
|
167
|
-
stateBranch:
|
|
168
|
-
sessionDir,
|
|
169
|
-
statePath:
|
|
118
|
+
stateSessionToken: scope.session,
|
|
119
|
+
stateSessionMode: scope.sessionMode,
|
|
120
|
+
stateBranch: scope.branch,
|
|
121
|
+
sessionDir: scope.sessionDir,
|
|
122
|
+
statePath: scope.statePath,
|
|
170
123
|
}
|
|
171
124
|
}
|
|
172
125
|
|
|
@@ -174,6 +127,18 @@ export function getProjectStatePath(cwd, options = {}) {
|
|
|
174
127
|
return getProjectSessionStateScope(cwd, options).statePath
|
|
175
128
|
}
|
|
176
129
|
|
|
130
|
+
export function getProjectEvidenceDir(cwd, options = {}) {
|
|
131
|
+
return getProjectSessionScope(cwd, normalizeRuntimeOptions(options)).artifactsDir
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function getProjectEvidencePath(cwd, fileName, options = {}) {
|
|
135
|
+
return getSessionArtifactPath(cwd, fileName, options)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getProjectEvidenceRelativePath(cwd, fileName, options = {}) {
|
|
139
|
+
return getSessionArtifactRelativePath(cwd, fileName, options)
|
|
140
|
+
}
|
|
141
|
+
|
|
177
142
|
export function isRepoSharedProjectStore(cwd) {
|
|
178
143
|
return getProjectStoreMode(cwd) === 'repo-shared'
|
|
179
144
|
}
|
|
@@ -191,6 +156,7 @@ export function getProjectStoreSummary(cwd, options = {}) {
|
|
|
191
156
|
const activationDir = getProjectActivationDir(cwd)
|
|
192
157
|
const storeDir = getProjectStoreDir(cwd)
|
|
193
158
|
const stateScope = getProjectSessionStateScope(cwd, options)
|
|
159
|
+
const artifactsDir = getProjectEvidenceDir(cwd, options)
|
|
194
160
|
const projectKey = buildProjectKey(cwd)
|
|
195
161
|
const projectStoreMode = getProjectStoreMode(cwd)
|
|
196
162
|
|
|
@@ -204,6 +170,7 @@ export function getProjectStoreSummary(cwd, options = {}) {
|
|
|
204
170
|
stateSessionMode: stateScope.stateSessionMode,
|
|
205
171
|
stateBranch: stateScope.stateBranch,
|
|
206
172
|
sessionStateDir: stateScope.sessionDir,
|
|
173
|
+
artifactsDir,
|
|
207
174
|
usesSharedStore: projectStoreMode === 'repo-shared',
|
|
208
175
|
projectKey: projectKey.key,
|
|
209
176
|
repoRoot: projectKey.repoRoot,
|
|
@@ -212,6 +179,7 @@ export function getProjectStoreSummary(cwd, options = {}) {
|
|
|
212
179
|
promptStoreDir: formatPromptPath(storeDir),
|
|
213
180
|
promptStatePath: formatPromptPath(stateScope.statePath),
|
|
214
181
|
promptSessionStateDir: formatPromptPath(stateScope.sessionDir),
|
|
182
|
+
promptArtifactsDir: formatPromptPath(artifactsDir),
|
|
215
183
|
}
|
|
216
184
|
}
|
|
217
185
|
|
|
@@ -282,7 +250,7 @@ export function buildProjectStorageHint(cwd, options = {}) {
|
|
|
282
250
|
hints.push(`当前宿主未提供稳定会话标识,因此使用分支默认位置 \`${summary.stateSessionToken}\``)
|
|
283
251
|
}
|
|
284
252
|
if (summary.usesSharedStore) {
|
|
285
|
-
hints.push(`项目存储:\`project_store_mode=repo-shared
|
|
253
|
+
hints.push(`项目存储:\`project_store_mode=repo-shared\`;本地激活/会话运行态目录仍是 \`${summary.promptActivationDir}\`,知识库/方案目录改为 \`${summary.promptStoreDir}\``)
|
|
286
254
|
}
|
|
287
255
|
return hints.join('。') + (hints.length > 0 ? '。' : '')
|
|
288
256
|
}
|
|
@@ -302,6 +270,7 @@ export function buildProjectStorageBlock(cwd, options = {}) {
|
|
|
302
270
|
state_session_token: summary.stateSessionToken,
|
|
303
271
|
state_session_mode: summary.stateSessionMode,
|
|
304
272
|
session_state_dir: summary.promptSessionStateDir,
|
|
273
|
+
artifacts_dir: summary.promptArtifactsDir,
|
|
305
274
|
knowledge_base_dir: summary.promptStoreDir,
|
|
306
275
|
uses_shared_store: summary.usesSharedStore,
|
|
307
276
|
}
|
|
@@ -312,7 +281,7 @@ export function buildProjectStorageBlock(cwd, options = {}) {
|
|
|
312
281
|
explanations.push('说明:当前宿主未提供稳定会话标识,因此使用分支默认位置。')
|
|
313
282
|
}
|
|
314
283
|
if (summary.usesSharedStore) {
|
|
315
|
-
explanations.push('
|
|
284
|
+
explanations.push('说明:状态文件与会话产物写本地激活目录;`context.md`、`guidelines.md`、`DESIGN.md`、`verify.yaml`、`modules/`、`plans/`、`archive/` 写知识库/方案目录。')
|
|
316
285
|
} else {
|
|
317
286
|
explanations.push('说明:当前使用项目本地 `.helloagents/` 作为激活目录、知识库目录和方案目录。')
|
|
318
287
|
}
|
package/scripts/ralph-loop.mjs
CHANGED
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
* Runs on SubagentStop (Claude Code) and Stop (Codex CLI).
|
|
5
5
|
* Auto-detects lint/test commands and blocks if they fail.
|
|
6
6
|
*/
|
|
7
|
-
import { readFileSync
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { clearVerifyEvidence, detectCommands, hasUnsafeVerifyCommand, writeVerifyEvidence } from './verify-state.mjs';
|
|
12
|
+
import {
|
|
13
|
+
getRuntimeEvidencePath,
|
|
14
|
+
readRuntimeEvidence,
|
|
15
|
+
writeRuntimeEvidence,
|
|
16
|
+
} from './runtime-artifacts.mjs';
|
|
12
17
|
|
|
13
18
|
const CONFIG_FILE = join(homedir(), '.helloagents', 'helloagents.json');
|
|
14
19
|
const CMD_TIMEOUT = 60_000; // 60s
|
|
@@ -27,28 +32,23 @@ function readSettings() {
|
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
// ── Circuit Breaker (consecutive failure tracking) ───────────────────
|
|
30
|
-
const BREAKER_FILE_NAME = '
|
|
35
|
+
const BREAKER_FILE_NAME = 'loop-breaker.json';
|
|
31
36
|
|
|
32
|
-
function getBreakerPath(cwd) {
|
|
33
|
-
return
|
|
37
|
+
function getBreakerPath(cwd, options = {}) {
|
|
38
|
+
return getRuntimeEvidencePath(cwd, BREAKER_FILE_NAME, options);
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
function readBreaker(cwd) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} catch {
|
|
40
|
-
return { consecutive_failures: 0, last_failure: null };
|
|
41
|
-
}
|
|
41
|
+
function readBreaker(cwd, options = {}) {
|
|
42
|
+
return readRuntimeEvidence(cwd, BREAKER_FILE_NAME, options)
|
|
43
|
+
|| { consecutive_failures: 0, last_failure: null };
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function writeBreaker(cwd, state) {
|
|
45
|
-
|
|
46
|
-
try { mkdirSync(dir, { recursive: true }); } catch {}
|
|
47
|
-
writeFileSync(getBreakerPath(cwd), JSON.stringify(state, null, 2));
|
|
46
|
+
function writeBreaker(cwd, state, options = {}) {
|
|
47
|
+
writeRuntimeEvidence(cwd, BREAKER_FILE_NAME, state, options);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function resetBreaker(cwd) {
|
|
51
|
-
writeBreaker(cwd, { consecutive_failures: 0, last_failure: null });
|
|
50
|
+
function resetBreaker(cwd, options = {}) {
|
|
51
|
+
writeBreaker(cwd, { consecutive_failures: 0, last_failure: null }, options);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// ── Progress Detection (git diff check) ──────────────────────────────
|
|
@@ -74,7 +74,7 @@ function runVerify(commands, cwd) {
|
|
|
74
74
|
const failures = [];
|
|
75
75
|
for (const cmd of commands) {
|
|
76
76
|
if (hasUnsafeVerifyCommand([cmd])) {
|
|
77
|
-
failures.push({ cmd, output: '
|
|
77
|
+
failures.push({ cmd, output: '已阻止:验证命令不允许使用 shell 组合操作符' });
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
80
|
try {
|
|
@@ -85,8 +85,8 @@ function runVerify(commands, cwd) {
|
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
87
87
|
let output = ((err.stdout || '') + (err.stderr || '')).trim();
|
|
88
|
-
if (output.length > 1000) output = output.slice(0, 1000) + '\n
|
|
89
|
-
failures.push({ cmd, output: output ||
|
|
88
|
+
if (output.length > 1000) output = output.slice(0, 1000) + '\n…已截断';
|
|
89
|
+
failures.push({ cmd, output: output || `退出码 ${err.status}` });
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
return failures;
|
|
@@ -94,13 +94,13 @@ function runVerify(commands, cwd) {
|
|
|
94
94
|
|
|
95
95
|
// ── Result Handlers ──────────────────────────────────────────────────
|
|
96
96
|
|
|
97
|
-
function handleSuccess(cwd, isSubagent) {
|
|
98
|
-
resetBreaker(cwd);
|
|
97
|
+
function handleSuccess(cwd, isSubagent, options = {}) {
|
|
98
|
+
resetBreaker(cwd, options);
|
|
99
99
|
writeVerifyEvidence(cwd, {
|
|
100
100
|
commands: detectCommands(cwd),
|
|
101
101
|
fastOnly: isSubagent,
|
|
102
102
|
source: isSubagent ? 'subagent' : 'stop',
|
|
103
|
-
});
|
|
103
|
+
}, options);
|
|
104
104
|
|
|
105
105
|
if (isSubagent) {
|
|
106
106
|
process.stdout.write(JSON.stringify({
|
|
@@ -127,12 +127,12 @@ function handleSuccess(cwd, isSubagent) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
function handleFailure(failures, cwd) {
|
|
131
|
-
clearVerifyEvidence(cwd);
|
|
132
|
-
const breaker = readBreaker(cwd);
|
|
130
|
+
function handleFailure(failures, cwd, options = {}) {
|
|
131
|
+
clearVerifyEvidence(cwd, options);
|
|
132
|
+
const breaker = readBreaker(cwd, options);
|
|
133
133
|
breaker.consecutive_failures += 1;
|
|
134
134
|
breaker.last_failure = new Date().toISOString();
|
|
135
|
-
writeBreaker(cwd, breaker);
|
|
135
|
+
writeBreaker(cwd, breaker, options);
|
|
136
136
|
|
|
137
137
|
const breakerWarning = breaker.consecutive_failures >= 3
|
|
138
138
|
? `\n\n⚠️ [断路器] 已连续 ${breaker.consecutive_failures} 次验证失败。当前修复思路可能有误,建议:\n 1. 重新分析根因,不要继续在同一方向上硬修\n 2. 检查是否存在架构层面的问题\n 3. 考虑回退到上一个正常状态重新开始`
|
|
@@ -141,7 +141,7 @@ function handleFailure(failures, cwd) {
|
|
|
141
141
|
const details = failures.map(f => `\u2717 ${f.cmd}\n${f.output}`).join('\n\n');
|
|
142
142
|
process.stdout.write(JSON.stringify({
|
|
143
143
|
decision: 'block',
|
|
144
|
-
reason: `[Ralph Loop]
|
|
144
|
+
reason: `[Ralph Loop] 验证失败:\n\n${details}\n\n请先修复以上问题,再报告完成。${breakerWarning}`,
|
|
145
145
|
suppressOutput: true,
|
|
146
146
|
}));
|
|
147
147
|
}
|
|
@@ -175,6 +175,7 @@ async function main() {
|
|
|
175
175
|
let data = {};
|
|
176
176
|
try { data = JSON.parse(readFileSync(0, 'utf-8')); } catch {}
|
|
177
177
|
const cwd = data.cwd || process.cwd();
|
|
178
|
+
const runtimeOptions = { payload: data };
|
|
178
179
|
|
|
179
180
|
let commands = detectCommands(cwd);
|
|
180
181
|
if (!commands?.length) {
|
|
@@ -188,10 +189,14 @@ async function main() {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
const failures = runVerify(commands, cwd);
|
|
191
|
-
if (failures.length === 0) handleSuccess(cwd, IS_SUBAGENT);
|
|
192
|
-
else handleFailure(failures, cwd);
|
|
192
|
+
if (failures.length === 0) handleSuccess(cwd, IS_SUBAGENT, runtimeOptions);
|
|
193
|
+
else handleFailure(failures, cwd, runtimeOptions);
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
main().catch(() => {
|
|
196
|
-
process.stdout.write(JSON.stringify({
|
|
196
|
+
main().catch((error) => {
|
|
197
|
+
process.stdout.write(JSON.stringify({
|
|
198
|
+
decision: 'block',
|
|
199
|
+
reason: `[Ralph Loop] 验证脚本执行异常,已暂停完成通知。\n原因:${error?.message || error}`,
|
|
200
|
+
suppressOutput: true,
|
|
201
|
+
}));
|
|
197
202
|
});
|