helloagents 3.0.19 → 3.0.20-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.
Files changed (38) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +24 -20
  4. package/README_CN.md +22 -18
  5. package/bootstrap-lite.md +3 -2
  6. package/bootstrap.md +3 -2
  7. package/gemini-extension.json +1 -1
  8. package/package.json +1 -1
  9. package/scripts/cli-doctor-codex.mjs +4 -4
  10. package/scripts/cli-doctor.mjs +2 -2
  11. package/scripts/cli-messages.mjs +2 -2
  12. package/scripts/notify-context.mjs +1 -1
  13. package/scripts/notify.mjs +3 -1
  14. package/scripts/project-session-cleanup.mjs +106 -0
  15. package/scripts/project-storage.mjs +5 -5
  16. package/scripts/runtime-scope.mjs +132 -62
  17. package/scripts/runtime-user-cleanup.mjs +63 -0
  18. package/scripts/session-capsule.mjs +29 -2
  19. package/scripts/session-token.mjs +0 -2
  20. package/scripts/workflow-core.mjs +1 -1
  21. package/scripts/workflow-plan-files.mjs +1 -1
  22. package/skills/commands/auto/SKILL.md +3 -3
  23. package/skills/commands/build/SKILL.md +5 -5
  24. package/skills/commands/clean/SKILL.md +5 -5
  25. package/skills/commands/commit/SKILL.md +3 -3
  26. package/skills/commands/init/SKILL.md +3 -3
  27. package/skills/commands/loop/SKILL.md +2 -2
  28. package/skills/commands/plan/SKILL.md +7 -7
  29. package/skills/commands/prd/SKILL.md +7 -6
  30. package/skills/commands/verify/SKILL.md +1 -1
  31. package/skills/commands/wiki/SKILL.md +1 -1
  32. package/skills/hello-arch/SKILL.md +2 -2
  33. package/skills/hello-errors/SKILL.md +1 -1
  34. package/skills/hello-subagent/SKILL.md +1 -1
  35. package/skills/hello-ui/SKILL.md +4 -4
  36. package/skills/hello-verify/SKILL.md +1 -1
  37. package/skills/hello-write/SKILL.md +1 -1
  38. package/skills/helloagents/SKILL.md +5 -5
@@ -1,20 +1,22 @@
1
1
  import { execFileSync } from 'node:child_process'
2
2
  import { createHash, randomUUID } from 'node:crypto'
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
3
+ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs'
4
4
  import { dirname, join, normalize, resolve } from 'node:path'
5
5
  import { homedir } from 'node:os'
6
6
 
7
7
  import { resolveSessionToken } from './session-token.mjs'
8
8
  import { USER_RUNTIME_MAX_AGE_MS } from './runtime-ttl.mjs'
9
+ import { cleanupUserRuntimeRoot, getUserRuntimeRoot } from './runtime-user-cleanup.mjs'
9
10
 
10
11
  export const PROJECT_DIR_NAME = '.helloagents'
11
12
  export const PROJECT_SESSIONS_DIR_NAME = 'sessions'
12
13
  export const PROJECT_ARTIFACTS_DIR_NAME = 'artifacts'
13
14
  export const CAPSULE_FILE_NAME = 'capsule.json'
14
15
  export const EVENTS_FILE_NAME = 'events.jsonl'
16
+ export const ACTIVE_SESSION_FILE_NAME = 'active.json'
15
17
  export const DEFAULT_STATE_SESSION_TOKEN = 'default'
16
18
  export const USER_RUNTIME_DIR_NAME = 'runtime'
17
- export { USER_RUNTIME_MAX_AGE_MS }
19
+ export { cleanupUserRuntimeRoot, getUserRuntimeRoot, USER_RUNTIME_MAX_AGE_MS }
18
20
 
19
21
  function normalizePath(filePath = '') {
20
22
  return filePath ? normalize(resolve(filePath)) : ''
@@ -68,6 +70,22 @@ function resolveGitBranchName(cwd) {
68
70
  return symbolicName && symbolicName !== 'HEAD' ? symbolicName : ''
69
71
  }
70
72
 
73
+ function resolveGitShortHead(cwd) {
74
+ return runGit(cwd, ['rev-parse', '--short', 'HEAD'])
75
+ }
76
+
77
+ function resolveWorkspaceName(cwd) {
78
+ const branchName = resolveGitBranchName(cwd)
79
+ if (branchName) return sanitizeRuntimeSegment(branchName, 'workspace')
80
+
81
+ if (resolveGitTopLevel(cwd)) {
82
+ const shortHead = sanitizeRuntimeSegment(resolveGitShortHead(cwd), '')
83
+ return shortHead ? `detached-${shortHead}` : 'detached'
84
+ }
85
+
86
+ return 'workspace'
87
+ }
88
+
71
89
  export function sanitizeRuntimeSegment(value = '', fallback = '') {
72
90
  const normalized = String(value)
73
91
  .trim()
@@ -101,10 +119,6 @@ export function getProjectRoot(cwd) {
101
119
  return activeDir ? dirname(activeDir) : normalizePath(cwd || process.cwd())
102
120
  }
103
121
 
104
- export function getUserRuntimeRoot(home = getHomeDir()) {
105
- return join(home, PROJECT_DIR_NAME, USER_RUNTIME_DIR_NAME)
106
- }
107
-
108
122
  function isUserHomeHelloagentsDir(dirPath) {
109
123
  const homeCandidates = [
110
124
  getHomeDir(),
@@ -151,57 +165,19 @@ function findProjectActivationDir(cwd) {
151
165
  return ''
152
166
  }
153
167
 
154
- function removePathIfExists(filePath, result, bucket) {
155
- if (!existsSync(filePath)) return
156
- try {
157
- rmSync(filePath, { recursive: true, force: true })
158
- result[bucket].push(filePath)
159
- } catch (error) {
160
- result.errors.push(`${filePath}: ${error.message}`)
161
- }
162
- }
163
-
164
- export function cleanupUserRuntimeRoot({
165
- home = getHomeDir(),
166
- now = Date.now(),
167
- maxAgeMs = USER_RUNTIME_MAX_AGE_MS,
168
- } = {}) {
169
- const root = getUserRuntimeRoot(home)
170
- const result = {
171
- root,
172
- removedExpiredDirs: [],
173
- errors: [],
174
- }
175
-
176
- if (!existsSync(root)) return result
177
-
178
- let entries = []
179
- try {
180
- entries = readdirSync(root, { withFileTypes: true })
181
- } catch (error) {
182
- result.errors.push(`${root}: ${error.message}`)
183
- return result
184
- }
185
-
186
- for (const entry of entries) {
187
- if (!entry.isDirectory()) continue
188
- const dirPath = join(root, entry.name)
189
- try {
190
- if (now - statSync(dirPath).mtimeMs > maxAgeMs) {
191
- removePathIfExists(dirPath, result, 'removedExpiredDirs')
192
- }
193
- } catch (error) {
194
- result.errors.push(`${dirPath}: ${error.message}`)
195
- }
196
- }
197
-
198
- return result
168
+ function resolvePayloadSessionToken(payload = {}) {
169
+ if (payload?._helloagentsSessionAlias) return ''
170
+ return resolveSessionToken({
171
+ payload,
172
+ env: {},
173
+ ppid: 0,
174
+ allowPpidFallback: false,
175
+ })
199
176
  }
200
177
 
201
- function resolveStableSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
202
- void ppid
178
+ function resolveEnvSessionToken(env = process.env) {
203
179
  return resolveSessionToken({
204
- payload,
180
+ payload: {},
205
181
  env,
206
182
  ppid: 0,
207
183
  allowPpidFallback: false,
@@ -217,29 +193,122 @@ function resolveTransientSessionToken({ payload = {}, env = process.env, ppid =
217
193
  })
218
194
  }
219
195
 
196
+ function getActiveSessionPath(activationDir) {
197
+ return join(activationDir, PROJECT_SESSIONS_DIR_NAME, ACTIVE_SESSION_FILE_NAME)
198
+ }
199
+
200
+ function resolveActiveSessionToken({ activationDir, projectRoot, workspace, now = Date.now() } = {}) {
201
+ const active = readJsonFile(getActiveSessionPath(activationDir), null)
202
+ if (!active || typeof active !== 'object') return ''
203
+ if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
204
+
205
+ const activeWorkspace = sanitizeRuntimeSegment(active.workspace || active.branch || '', '')
206
+ if (activeWorkspace && activeWorkspace !== workspace) return ''
207
+
208
+ const updatedAt = Date.parse(active.updatedAt || '')
209
+ if (!Number.isFinite(updatedAt) || now - updatedAt > USER_RUNTIME_MAX_AGE_MS) return ''
210
+
211
+ return sanitizeRuntimeSegment(active.session, '')
212
+ }
213
+
214
+ function resolveActiveAliasSession({ activationDir, projectRoot, workspace, alias, now = Date.now() } = {}) {
215
+ if (!alias) return ''
216
+ const active = readJsonFile(getActiveSessionPath(activationDir), null)
217
+ if (!active || typeof active !== 'object') return ''
218
+ if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
219
+
220
+ const activeWorkspace = sanitizeRuntimeSegment(active.workspace || active.branch || '', '')
221
+ if (activeWorkspace && activeWorkspace !== workspace) return ''
222
+
223
+ const updatedAt = Date.parse(active.updatedAt || '')
224
+ if (!Number.isFinite(updatedAt) || now - updatedAt > USER_RUNTIME_MAX_AGE_MS) return ''
225
+
226
+ const aliases = active.aliases && typeof active.aliases === 'object' ? active.aliases : {}
227
+ return sanitizeRuntimeSegment(aliases[alias], '')
228
+ }
229
+
230
+ export function writeActiveProjectSession(scope, { host = '', source = '', env = process.env } = {}) {
231
+ if (!scope?.active || !scope.activationDir || !scope.session) return ''
232
+
233
+ const activePath = getActiveSessionPath(scope.activationDir)
234
+ const current = readJsonFile(activePath, null) || {}
235
+ const aliases = current.aliases && typeof current.aliases === 'object' ? current.aliases : {}
236
+ const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
237
+ if (envToken && envToken !== scope.session) aliases[envToken] = scope.session
238
+
239
+ writeJsonFileAtomic(activePath, {
240
+ version: 1,
241
+ cwd: scope.cwd,
242
+ workspace: scope.workspace || scope.branch,
243
+ session: scope.session,
244
+ sessionMode: scope.sessionMode,
245
+ host,
246
+ source,
247
+ aliases,
248
+ updatedAt: new Date().toISOString(),
249
+ })
250
+ return activePath
251
+ }
252
+
253
+ function chooseProjectSession({ payload, env, activationDir, projectRoot, workspace }) {
254
+ const payloadToken = sanitizeRuntimeSegment(resolvePayloadSessionToken(payload), '')
255
+ if (payloadToken) return { session: payloadToken, sessionMode: 'host-session' }
256
+
257
+ const payloadAlias = sanitizeRuntimeSegment(payload?._helloagentsSessionAlias, '')
258
+ const payloadAliasToken = resolveActiveAliasSession({
259
+ activationDir,
260
+ projectRoot,
261
+ workspace,
262
+ alias: payloadAlias,
263
+ })
264
+ if (payloadAliasToken) return { session: payloadAliasToken, sessionMode: 'active-session' }
265
+
266
+ const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
267
+ const aliasToken = resolveActiveAliasSession({
268
+ activationDir,
269
+ projectRoot,
270
+ workspace,
271
+ alias: envToken,
272
+ })
273
+ if (aliasToken) return { session: aliasToken, sessionMode: 'active-session' }
274
+
275
+ if (envToken) return { session: envToken, sessionMode: 'host-session' }
276
+
277
+ const activeToken = resolveActiveSessionToken({ activationDir, projectRoot, workspace })
278
+ if (activeToken) return { session: activeToken, sessionMode: 'active-session' }
279
+
280
+ return { session: DEFAULT_STATE_SESSION_TOKEN, sessionMode: 'default' }
281
+ }
282
+
220
283
  export function getProjectSessionScope(cwd, options = {}) {
221
284
  const normalizedCwd = normalizePath(cwd || process.cwd())
222
285
  const projectRoot = getProjectRoot(normalizedCwd)
223
- const { payload = {}, env = process.env, ppid = process.ppid } = normalizeRuntimeOptions(options)
224
- const stableToken = resolveStableSessionToken({ payload, env, ppid })
225
- const branch = sanitizeRuntimeSegment(resolveGitBranchName(projectRoot), 'detached')
226
- const session = sanitizeRuntimeSegment(stableToken, DEFAULT_STATE_SESSION_TOKEN)
286
+ const { payload = {}, env = process.env } = normalizeRuntimeOptions(options)
227
287
  const activationDir = getProjectActivationDir(projectRoot)
228
- const sessionDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, branch, session)
288
+ const workspace = resolveWorkspaceName(projectRoot)
289
+ const { session, sessionMode } = chooseProjectSession({
290
+ payload,
291
+ env,
292
+ activationDir,
293
+ projectRoot,
294
+ workspace,
295
+ })
296
+ const sessionDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace, session)
229
297
 
230
298
  return {
231
299
  cwd: projectRoot,
232
300
  active: isProjectRuntimeActive(projectRoot),
233
- branch,
301
+ branch: workspace,
302
+ workspace,
234
303
  session,
235
- sessionMode: stableToken ? 'host-session' : 'default',
304
+ sessionMode,
236
305
  activationDir,
237
306
  sessionDir,
238
307
  statePath: join(sessionDir, 'STATE.md'),
239
308
  capsulePath: join(sessionDir, CAPSULE_FILE_NAME),
240
309
  eventsPath: join(sessionDir, EVENTS_FILE_NAME),
241
310
  artifactsDir: join(sessionDir, PROJECT_ARTIFACTS_DIR_NAME),
242
- key: `${normalizedCwd}::${branch}::${session}`,
311
+ key: `${projectRoot}::${workspace}::${session}`,
243
312
  }
244
313
  }
245
314
 
@@ -259,6 +328,7 @@ function buildTransientRuntimeDir(cwd, options = {}) {
259
328
  return {
260
329
  cwd: normalizedCwd,
261
330
  branch: 'transient',
331
+ workspace: 'transient',
262
332
  session: token,
263
333
  sessionMode: token === DEFAULT_STATE_SESSION_TOKEN ? 'default' : 'transient-session',
264
334
  sessionDir: join(getUserRuntimeRoot(), hash),
@@ -0,0 +1,63 @@
1
+ import { existsSync, readdirSync, rmSync, statSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { homedir } from 'node:os'
4
+
5
+ import { USER_RUNTIME_MAX_AGE_MS } from './runtime-ttl.mjs'
6
+
7
+ const PROJECT_DIR_NAME = '.helloagents'
8
+ const USER_RUNTIME_DIR_NAME = 'runtime'
9
+
10
+ function getHomeDir(env = process.env) {
11
+ return env.HOME || env.USERPROFILE || homedir()
12
+ }
13
+
14
+ export function getUserRuntimeRoot(home = getHomeDir()) {
15
+ return join(home, PROJECT_DIR_NAME, USER_RUNTIME_DIR_NAME)
16
+ }
17
+
18
+ function removePathIfExists(filePath, result, bucket) {
19
+ if (!existsSync(filePath)) return
20
+ try {
21
+ rmSync(filePath, { recursive: true, force: true })
22
+ result[bucket].push(filePath)
23
+ } catch (error) {
24
+ result.errors.push(`${filePath}: ${error.message}`)
25
+ }
26
+ }
27
+
28
+ export function cleanupUserRuntimeRoot({
29
+ home = getHomeDir(),
30
+ now = Date.now(),
31
+ maxAgeMs = USER_RUNTIME_MAX_AGE_MS,
32
+ } = {}) {
33
+ const root = getUserRuntimeRoot(home)
34
+ const result = {
35
+ root,
36
+ removedExpiredDirs: [],
37
+ errors: [],
38
+ }
39
+
40
+ if (!existsSync(root)) return result
41
+
42
+ let entries = []
43
+ try {
44
+ entries = readdirSync(root, { withFileTypes: true })
45
+ } catch (error) {
46
+ result.errors.push(`${root}: ${error.message}`)
47
+ return result
48
+ }
49
+
50
+ for (const entry of entries) {
51
+ if (!entry.isDirectory()) continue
52
+ const dirPath = join(root, entry.name)
53
+ try {
54
+ if (now - statSync(dirPath).mtimeMs > maxAgeMs) {
55
+ removePathIfExists(dirPath, result, 'removedExpiredDirs')
56
+ }
57
+ } catch (error) {
58
+ result.errors.push(`${dirPath}: ${error.message}`)
59
+ }
60
+ }
61
+
62
+ return result
63
+ }
@@ -6,6 +6,7 @@ import {
6
6
  getRuntimeScope,
7
7
  readJsonFile,
8
8
  removeRuntimeFile,
9
+ writeActiveProjectSession,
9
10
  writeJsonFileAtomic,
10
11
  } from './runtime-scope.mjs'
11
12
 
@@ -18,6 +19,7 @@ function buildEmptyCapsule(scope) {
18
19
  key: scope.key,
19
20
  cwd: scope.cwd,
20
21
  branch: scope.branch,
22
+ workspace: scope.workspace || scope.branch,
21
23
  session: scope.session,
22
24
  sessionMode: scope.sessionMode,
23
25
  updatedAt: new Date().toISOString(),
@@ -36,6 +38,10 @@ function normalizeOptions(options = {}) {
36
38
  }
37
39
  }
38
40
 
41
+ function getEventSessionAlias(eventPayload = {}) {
42
+ return eventPayload.sessionId || eventPayload.session_id || eventPayload['session-id'] || ''
43
+ }
44
+
39
45
  function getScope(cwd, options = {}) {
40
46
  const normalizedOptions = normalizeOptions(options)
41
47
  if (normalizedOptions.project === true) {
@@ -66,7 +72,7 @@ export function getSessionArtifactPath(cwd, fileName, options = {}) {
66
72
  export function getSessionArtifactRelativePath(cwd, fileName, options = {}) {
67
73
  const scope = getScope(cwd, options)
68
74
  if (scope.scope === 'project-session') {
69
- return `.helloagents/sessions/${scope.branch}/${scope.session}/artifacts/${fileName}`
75
+ return `.helloagents/sessions/${scope.workspace || scope.branch}/${scope.session}/artifacts/${fileName}`
70
76
  }
71
77
  return `~/.helloagents/runtime/${basename(scope.sessionDir)}/artifacts/${fileName}`
72
78
  }
@@ -82,6 +88,7 @@ export function readSessionCapsule(cwd = process.cwd(), options = {}) {
82
88
  key: scope.key,
83
89
  cwd: scope.cwd,
84
90
  branch: scope.branch,
91
+ workspace: scope.workspace || scope.branch,
85
92
  session: scope.session,
86
93
  sessionMode: scope.sessionMode,
87
94
  }
@@ -96,11 +103,15 @@ export function writeSessionCapsule(cwd, capsule, options = {}) {
96
103
  key: scope.key,
97
104
  cwd: scope.cwd,
98
105
  branch: scope.branch,
106
+ workspace: scope.workspace || scope.branch,
99
107
  session: scope.session,
100
108
  sessionMode: scope.sessionMode,
101
109
  updatedAt: new Date().toISOString(),
102
110
  }
103
111
  writeJsonFileAtomic(scope.capsulePath, nextCapsule)
112
+ writeActiveProjectSession(scope, {
113
+ env: normalizeOptions(options).env,
114
+ })
104
115
  return nextCapsule
105
116
  }
106
117
 
@@ -138,7 +149,18 @@ export function clearCapsuleSection(cwd, section, options = {}) {
138
149
  }
139
150
 
140
151
  export function appendSessionEvent(cwd, eventPayload, options = {}) {
141
- const scope = getScope(cwd, options)
152
+ const normalizedOptions = normalizeOptions(options)
153
+ const sessionAlias = getEventSessionAlias(eventPayload)
154
+ const scopedOptions = sessionAlias
155
+ ? {
156
+ ...normalizedOptions,
157
+ payload: {
158
+ ...(normalizedOptions.payload || {}),
159
+ _helloagentsSessionAlias: sessionAlias,
160
+ },
161
+ }
162
+ : normalizedOptions
163
+ const scope = getScope(cwd, scopedOptions)
142
164
  if (scope.scope === 'project-session' && !scope.active) return ''
143
165
  const eventName = eventPayload?.event || ''
144
166
  if (!eventName) return ''
@@ -155,6 +177,11 @@ export function appendSessionEvent(cwd, eventPayload, options = {}) {
155
177
  encoding: 'utf-8',
156
178
  flag: 'a',
157
179
  })
180
+ writeActiveProjectSession(scope, {
181
+ host: eventPayload.host || '',
182
+ source: eventPayload.source || eventName,
183
+ env: scopedOptions.env,
184
+ })
158
185
  return scope.eventsPath
159
186
  }
160
187
 
@@ -15,8 +15,6 @@ const PAYLOAD_SESSION_KEYS = [
15
15
  'tabId',
16
16
  'tab_id',
17
17
  'tab',
18
- 'requestId',
19
- 'request_id',
20
18
  ]
21
19
 
22
20
  const ENV_SESSION_KEYS = [
@@ -16,7 +16,7 @@ export function getTargetPlans(snapshot) {
16
16
 
17
17
  function describeStateLabel(state) {
18
18
  if (state.stateSessionMode === 'default') {
19
- return '当前分支默认位置的状态文件'
19
+ return '当前工作区默认位置的状态文件'
20
20
  }
21
21
  return '当前会话的状态文件'
22
22
  }
@@ -187,7 +187,7 @@ export function readStateSnapshot(cwd, options = {}) {
187
187
  stateScope: stateScope.stateScope,
188
188
  stateSessionToken: stateScope.stateSessionToken,
189
189
  stateSessionMode: stateScope.stateSessionMode,
190
- stateBranch: stateScope.stateBranch,
190
+ stateWorkspace: stateScope.stateWorkspace,
191
191
  sessionScoped: stateScope.stateScope === 'session',
192
192
  exists,
193
193
  content,
@@ -7,14 +7,14 @@ policy:
7
7
  Trigger: ~auto <任务描述>
8
8
 
9
9
  `~auto` 是自动执行命令。它根据任务类型、复杂度、风险等级与项目状态,在 `~idea`、`~plan`、`~build`、`~verify`、`~prd` 之间选择合适主路径,并连续推进。
10
- `~auto` 不止做一次选路;主路径一旦确定,就按需要继续执行后续阶段,默认持续推进直到完成交付,只有命中 bootstrap 的阻塞判定时才停下。
10
+ `~auto` 不止做一次选路;主路径一旦确定,就按需要继续执行后续阶段,默认持续推进直到完成交付,只有命中 HelloAGENTS 阻塞判定时才停下。
11
11
 
12
12
  ## 铁律
13
13
  - 不为了“自动化”而强行走重流程
14
14
  - 复杂度与风险不足以支撑更重路径时,优先选更轻但能保证质量的路线
15
15
  - `T3` 高风险或不可逆操作默认不直接进入 `~build`;优先先走 `~plan` 或 `~prd`,纯审查/纯验证请求才可先进入 `~verify`
16
16
  - 主路径一旦确定,立即读取对应 command skill,并在阶段完成后继续执行后续阶段,避免同一任务重复探索或重复等待
17
- - 选路不替代授权;涉及外部副作用或高风险不可逆操作时,仍遵守 bootstrap 的阻塞判定与确认规则
17
+ - 选路不替代授权;涉及外部副作用或高风险不可逆操作时,仍遵守 HelloAGENTS 阻塞判定与确认规则
18
18
  - 用户显式使用 `~auto`,表示已授权在当前任务边界内沿选定主路径持续执行;若本轮运行在 Codex `/goal` 下,`/goal` 只提供长程续跑与预算,`~auto` 仍按方案包、`state_path` 与验证契约推进;`~plan` / `~prd` 作为中间阶段时,不再额外询问“是否开始执行”,除非仍有真实阻塞;不得把 `🔄 下一步` 当作阶段交接或继续执行占位
19
19
  - 优先消费当前上下文中已注入的 ROUTE / TIER、当前工作流约束与项目状态;不要在 `~auto` 内另建一套关键词路由表
20
20
 
@@ -69,6 +69,6 @@ Trigger: ~auto <任务描述>
69
69
 
70
70
  ### 5. 何时允许停下
71
71
 
72
- - 仅在 bootstrap 的阻塞判定成立时停下:真实歧义、缺必需信息/文件/凭据、未授权外部副作用、高风险或不可逆操作
72
+ - 仅在 HelloAGENTS 阻塞判定成立时停下:真实歧义、缺必需信息/文件/凭据、未授权外部副作用、高风险或不可逆操作
73
73
  - 不得把 `~plan` / `~prd` 中“是否进入执行”的默认询问原样带入 `~auto` 流程
74
74
  - 不得把“给出方案”“给出任务列表”“未执行修改”“等待下一步确认”“给出建议下一步”当作 `~auto` 的默认完成态;用户显式要求 `~auto` 时,默认目标是把当前任务推进到可交付完成
@@ -7,7 +7,7 @@ policy:
7
7
  Trigger: ~build [description]
8
8
 
9
9
  `~build` 是执行实现命令。它负责读取现有需求、方案包与项目上下文,完成实现、局部验证、修复循环,并把结果交给后续验证与收尾。
10
- 执行 `~build` 时,通用阶段边界按当前已加载 bootstrap 执行;本 skill 负责补充实现前定位、实现约束,以及进入 `~verify` / 收尾前的实现边界。
10
+ 执行 `~build` 时,通用阶段边界按当前已加载的 HelloAGENTS 规则执行;本 skill 负责补充实现前定位、实现约束,以及进入 `~verify` / 收尾前的实现边界。
11
11
  `.helloagents/` 在本 skill 中统一按项目级存储路径理解:状态文件只使用 `state_path`;会话证据使用当前 `state_path` 所在目录下的 `artifacts/*.json`;若 `project_store_mode=repo-shared`,知识库、`DESIGN.md`、`verify.yaml` 与方案包按当前上下文中已注入的项目知识/方案目录解析。
12
12
 
13
13
  ## 铁律
@@ -20,7 +20,7 @@ Trigger: ~build [description]
20
20
 
21
21
  ### 1. 恢复与定位
22
22
 
23
- - 优先按当前已加载 bootstrap 的“.helloagents/ 文件读取优先级”恢复当前任务;若当前消息明确要继续上次任务、会话刚经历恢复 / 压缩,或本轮运行在 Codex active goal 下,先读取 `state_path`,再用当前用户消息、活跃方案包 / PRD 与代码事实确认当前任务
23
+ - 优先按当前已加载的 HelloAGENTS 规则恢复当前任务,并遵循“.helloagents/ 文件读取优先级”;若当前消息明确要继续上次任务、会话刚经历恢复 / 压缩,或本轮运行在 Codex active goal 下,先读取 `state_path`,再用当前用户消息、活跃方案包 / PRD 与代码事实确认当前任务
24
24
  - 若存在最近的活跃方案包,读取对应的:
25
25
  - `requirements.md`
26
26
  - `plan.md`
@@ -30,12 +30,12 @@ Trigger: ~build [description]
30
30
  - `contract.json` 存在时,优先按其中的 `verifyMode`、`reviewerFocus`、`testerFocus` 理解后续验证边界
31
31
  - 若本轮运行在 Codex active goal 下,按 `tasks.md` 未完成项、`contract.json` 与 `state_path` 恢复实现位置;不要自动创建新 goal,也不要把 goal 目标原文替代方案包
32
32
  - 若当前上下文中已注入“当前工作流约束”或“当前建议下一命令”,先服从它;只有推荐仍为 `~build`,或用户明确提出新增实现范围时,才继续 `~build`
33
- - 其余项目知识库与相关代码文件,按 bootstrap 的项目上下文规则按需读取
33
+ - 其余项目知识库与相关代码文件,按 HelloAGENTS 项目上下文要求读取
34
34
  - 若任务涉及 UI,按以下优先级读取并遵循:当前活跃 `plan.md` / PRD 中的 UI 决策 > 逻辑 `.helloagents/DESIGN.md`(实际路径按当前项目存储模式解析) > `hello-ui` 通用规则
35
35
  - 若已激活项目且当前任务属于整页新建、设计系统改造、或跨多个组件的视觉重做,但逻辑 `.helloagents/DESIGN.md` 不存在,先按模板创建最小设计契约,再继续大规模实现
36
36
 
37
37
  如果 `.helloagents/` 不存在:
38
- - 按当前已加载 bootstrap `.helloagents/` 与流程状态规则创建最小项目状态
38
+ - 按当前已加载的 HelloAGENTS 规则创建 `.helloagents/` 与最小流程状态
39
39
  - 仅补足执行当前任务所需的最小状态,不自动展开完整知识库
40
40
 
41
41
  ### 2. 需求与范围确认
@@ -65,5 +65,5 @@ Trigger: ~build [description]
65
65
  - 有方案包时,只同步本次实现直接影响的任务状态;未完成项保持打开
66
66
  - 当前实现已闭合、且需要进入交付或收尾时,转入 `~verify`
67
67
  - 若 Codex active goal 仍有未完成 AFK 任务,继续下一项可执行任务;若目标已满足,先转入 `~verify` 与 HelloAGENTS 收尾,再标记 goal complete
68
- - 状态文件、知识库、`CHANGELOG.md`、modules 文档与归档边界,按当前已加载 bootstrap VERIFY / CONSOLIDATE 规则执行
68
+ - 状态文件、知识库、`CHANGELOG.md`、modules 文档与归档边界,按当前已加载的 HelloAGENTS 规则进入 VERIFY / CONSOLIDATE
69
69
  - 不在 `~build` 内把仍未闭合的方案包整体报告为已完成
@@ -6,17 +6,17 @@ policy:
6
6
  ---
7
7
  Trigger: ~clean
8
8
 
9
- 执行 `~clean` 时,方案包归档、临时文件清理和状态文件更新范围按当前已加载 bootstrap 执行;本命令只负责判定哪些方案包可以清理,以及输出清理摘要。
9
+ 执行 `~clean` 时,方案包归档、临时文件清理和状态文件更新范围按当前已加载的 HelloAGENTS 规则执行;本命令只负责判定哪些方案包可以清理,以及输出清理摘要。
10
10
  `.helloagents/` 在本 skill 中统一按项目级存储路径理解:状态文件只使用 `state_path`,临时运行态文件保持项目本地;若 `project_store_mode=repo-shared`,`plans/` 与 `archive/` 按当前上下文中已注入的项目知识/方案目录解析。
11
11
 
12
12
  ## 流程
13
13
 
14
14
  1. 扫描 `.helloagents/plans/` 下的方案包(按当前项目存储模式解析;`project_store_mode=repo-shared` 时使用共享知识/方案目录)
15
15
  2. 判定完成状态:优先以 tasks.md 中所有任务已标记 [√] 为准;只有任务清单无法判断时,才读取 `state_path` 中与当前方案一致的“主线目标”+“正在做什么”作为辅助信息
16
- 3. 已完成的方案包 → 按 bootstrap 的归档规则移入 `.helloagents/archive/YYYY-MM/`(按当前项目存储模式解析),并同步更新 `.helloagents/archive/_index.md`
17
- 4. 清理 bootstrap 中定义的临时文件
18
- 5. 按 bootstrap 的流程状态规则更新 `state_path`;若当前状态指向已归档方案包,则清空对应方案路径
19
- 7. 输出清理摘要(归档了几个方案包、清理了哪些文件)
16
+ 3. 已完成的方案包 → 按 HelloAGENTS 归档规则移入 `.helloagents/archive/YYYY-MM/`(按当前项目存储模式解析),并同步更新 `.helloagents/archive/_index.md`
17
+ 4. 清理 HelloAGENTS 临时文件
18
+ 5. 按 HelloAGENTS 流程状态规则更新 `state_path`;若当前状态指向已归档方案包,则清空对应方案路径
19
+ 6. 输出清理摘要(归档了几个方案包、清理了哪些文件)
20
20
 
21
21
  ## 不删除
22
22
  - 除按流程状态规则必须重写的 `state_path` 外,不删除流程状态文件
@@ -6,7 +6,7 @@ policy:
6
6
  ---
7
7
  Trigger: ~commit [message]
8
8
 
9
- 执行 `~commit` 时,知识库同步与状态文件更新范围按当前已加载 bootstrap CONSOLIDATE / 流程状态规则执行;本命令只负责生成提交信息、读取提交归属配置并完成提交动作。
9
+ 执行 `~commit` 时,知识库同步与状态文件更新范围按当前已加载的 HelloAGENTS CONSOLIDATE / 流程状态要求执行;本命令只负责生成提交信息、读取提交归属配置并完成提交动作。
10
10
 
11
11
  ## 流程
12
12
 
@@ -20,11 +20,11 @@ Trigger: ~commit [message]
20
20
  - ""(空,默认)→ 不添加归属
21
21
  - 有内容(如 "Co-Authored-By: HelloAGENTS")→ 添加该内容到 commit message
22
22
  6. 执行 git commit
23
- 7. 若 `state_path` 已存在,按 bootstrap 的“已有则更新”规则同步当前已提交状态
23
+ 7. 若 `state_path` 已存在,按 HelloAGENTS“已有则更新”要求同步当前已提交状态
24
24
 
25
25
  ## 知识库同步
26
26
  提交后,继续复用上方已解析的同一份设置获取 `kb_create_mode`,不要再次读取 `~/.helloagents/helloagents.json`:
27
27
  - 0 = 跳过
28
28
  - 1 = 编码任务自动同步(默认)
29
29
  - 2 = 始终同步
30
- 同步范围与更新格式按当前已加载 bootstrap CONSOLIDATE 阶段执行。
30
+ 同步范围与更新格式按当前已加载的 HelloAGENTS CONSOLIDATE 阶段执行。
@@ -7,7 +7,7 @@ policy:
7
7
  Trigger: ~init
8
8
 
9
9
  ~init 是用户显式命令,创建完整知识库,不受 kb_create_mode 限制。
10
- 执行 `~init` 时,`.helloagents/` 目录结构、模板格式和状态文件规则按当前已加载 bootstrap 执行;本命令额外负责项目级规则文件和各宿主项目级 HelloAGENTS 包根链接。
10
+ 执行 `~init` 时,`.helloagents/` 目录结构、模板格式和状态文件规则按当前已加载的 HelloAGENTS 规则执行;本命令额外负责项目级规则文件和各宿主项目级 HelloAGENTS 包根链接。
11
11
  `.helloagents/` 在本 skill 中统一按项目级存储路径理解:项目本地 `.helloagents/` 继续承担激活目录;状态文件只使用 `state_path`;若 `project_store_mode=repo-shared`,知识库、`DESIGN.md` 与方案包按当前上下文中已注入的项目知识/方案目录写入。
12
12
 
13
13
  ## 流程
@@ -15,13 +15,13 @@ Trigger: ~init
15
15
  ### 阶段 1:环境搭建(必做)
16
16
 
17
17
  1. 创建 `.helloagents/` 目录 + `state_path`(按 templates/STATE.md 格式,初始“主线目标”写当前初始化任务,初始状态为空闲)
18
- 2. 定位插件根目录:优先读取当前上下文中已注入的“当前 HelloAGENTS 包根目录”;若上下文未提供,再根据当前已加载的规则文件反推,禁止猜测其他目录
18
+ 2. 定位插件根目录:优先读取当前上下文中已注入的“当前 HelloAGENTS 包根目录”;若上下文未提供,再根据当前已加载的 HelloAGENTS 规则来源反推,禁止猜测其他目录
19
19
  3. 刷新各宿主项目级 HelloAGENTS 包根链接(删除旧的重建):
20
20
  - `.claude/skills/helloagents` symlink → `{插件根目录}/`
21
21
  - `.gemini/skills/helloagents` symlink → `{插件根目录}/`
22
22
  - `.codex/skills/helloagents` symlink → `{插件根目录}/`
23
23
  这些链接用于项目级规则定位 HelloAGENTS 的 `skills/`、`templates/` 和 `scripts/`;宿主若支持递归发现 `SKILL.md`,也可直接识别包内 skills。
24
- 4. 读取 `{插件根目录}/bootstrap.md`,用 `<!-- HELLOAGENTS_START -->` / `<!-- HELLOAGENTS_END -->` 标记包裹后写入:
24
+ 4. 读取 `{插件根目录}` 中的全量规则模板,用 `<!-- HELLOAGENTS_START -->` / `<!-- HELLOAGENTS_END -->` 标记包裹后写入:
25
25
  - `AGENTS.md`(项目根目录,Codex 读取)
26
26
  - `CLAUDE.md`(项目根目录,Claude Code 读取)
27
27
  - `.gemini/GEMINI.md`(Gemini CLI 读取,需先创建 .gemini/ 目录)
@@ -22,7 +22,7 @@ Trigger: ~loop <目标描述> [--iterations N] [--metric "命令"] [--direction
22
22
  2. 确保 `.helloagents/` 目录和 `state_path` 存在;文件不存在时按 `templates/STATE.md` 创建。`~loop` 必须维护这个状态文件,不受 `kb_create_mode` 控制;“主线目标”固定写本次优化目标,避免混入其他任务
23
23
  3. 运行指标命令获取基线值,记录到 results log
24
24
  4. 如有守卫命令,运行确认基线通过
25
- 5. 创建当前会话的 `.helloagents/sessions/{branch}/{session}/artifacts/loop-results.tsv`
25
+ 5. 创建当前会话的 `.helloagents/sessions/{workspace}/{session}/artifacts/loop-results.tsv`
26
26
  6. 根据优化目标标记可能需要的 hello-* 质量技能(如性能优化标记 hello-perf,UI 优化标记 hello-ui)
27
27
  7. 重写 `state_path`:记录主线目标=当前优化目标、基线指标、守卫命令、下一步设为第一轮迭代的具体动作
28
28
 
@@ -36,7 +36,7 @@ iteration commit metric delta guard status description
36
36
  ## 八阶段循环
37
37
 
38
38
  `~loop` 的八阶段循环是统一执行流程(ROUTE/TIER→SPEC→PLAN→BUILD→VERIFY→CONSOLIDATE)在迭代优化场景下的特化形式。每轮迭代的“修改”阶段遵循已标记的 hello-* 质量技能规范,“验证”阶段遵循 hello-verify 的验证规范。
39
- 执行 `~loop` 时,涉及公共阶段边界、阻塞判定与收尾要求的部分,仍按当前已加载 bootstrap 执行;本 skill 负责补充 loop 场景的迭代顺序与回滚规则。
39
+ 执行 `~loop` 时,涉及公共阶段边界、阻塞判定与收尾要求的部分,仍按当前已加载的 HelloAGENTS 规则执行;本 skill 负责补充 loop 场景的迭代顺序与回滚规则。
40
40
  若本轮运行在 Codex `/goal` 下,`/goal` 只作为外层长程续跑与预算控制;`~loop` 仍负责指标、守卫、实验提交、keep/revert、results log、`state_path` 与收尾验证,不把 `/goal` 当成循环逻辑本身。
41
41
 
42
42
  除非达到迭代上限或命中阻塞判定,否则继续执行,不额外询问是否继续,也不把 `🔄 下一步` 当作单轮结果或继续执行占位。