helloagents 3.0.12 → 3.0.16-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 +182 -35
- package/README_CN.md +184 -37
- package/bootstrap-lite.md +32 -26
- package/bootstrap.md +35 -29
- package/cli.mjs +119 -11
- package/gemini-extension.json +1 -1
- package/install.ps1 +128 -0
- package/install.sh +121 -0
- package/package.json +23 -4
- package/scripts/advisor-state.mjs +36 -63
- package/scripts/capability-registry.mjs +4 -4
- package/scripts/cli-branch.mjs +84 -0
- package/scripts/cli-codex-config.mjs +14 -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 +119 -32
- package/scripts/cli-lifecycle.mjs +24 -13
- package/scripts/cli-messages.mjs +34 -16
- package/scripts/cli-runtime-carrier.mjs +15 -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-events.mjs +3 -1
- package/scripts/notify-gates.mjs +2 -0
- package/scripts/notify-route.mjs +9 -7
- package/scripts/notify-ui.mjs +42 -32
- package/scripts/notify.mjs +72 -36
- 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 +3 -3
- 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 +5 -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 +7 -7
- package/skills/hello-verify/SKILL.md +10 -7
- package/skills/helloagents/SKILL.md +14 -9
- package/templates/context.md +6 -0
- package/templates/plans/plan.md +3 -0
- package/templates/plans/tasks.md +8 -3
|
@@ -12,24 +12,13 @@ import { getVisualEvidenceStatus } from './visual-state.mjs'
|
|
|
12
12
|
import { buildDeliveryGateHint, getDeliveryAction, getWorkflowRecommendation, getWorkflowSnapshot } from './workflow-state.mjs'
|
|
13
13
|
import { getReviewEvidenceStatus } from './review-state.mjs'
|
|
14
14
|
import { getVerifyEvidenceStatus } from './verify-state.mjs'
|
|
15
|
+
import { buildDeliveryBlockReason, buildUnderSpecifiedDetails } from './delivery-gate-messages.mjs'
|
|
15
16
|
|
|
16
17
|
function selectGatePlans(snapshot) {
|
|
17
18
|
if (snapshot.activePlans.length > 0) return snapshot.activePlans
|
|
18
19
|
return snapshot.plans
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function buildUnderSpecifiedDetails(entry) {
|
|
22
|
-
return entry.taskSummary.underSpecifiedItems
|
|
23
|
-
.slice(0, 3)
|
|
24
|
-
.map((item) => {
|
|
25
|
-
const missing = []
|
|
26
|
-
if (item.files.length === 0) missing.push('missing files')
|
|
27
|
-
if (!item.acceptance) missing.push('missing acceptance')
|
|
28
|
-
if (!item.validation) missing.push('missing validation')
|
|
29
|
-
return `${item.text} (${missing.join(', ')})`
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
22
|
function collectTaskMetadataIssues(entry, issues) {
|
|
34
23
|
if (entry.taskSummary.underSpecifiedCount === 0) return
|
|
35
24
|
issues.push({
|
|
@@ -48,7 +37,7 @@ function collectPlanIssues(planEntries) {
|
|
|
48
37
|
issues.push({
|
|
49
38
|
type: 'missing-files',
|
|
50
39
|
planName: entry.planName,
|
|
51
|
-
details: entry.missingFiles.map((file) =>
|
|
40
|
+
details: entry.missingFiles.map((file) => `缺少 ${file}`),
|
|
52
41
|
})
|
|
53
42
|
}
|
|
54
43
|
|
|
@@ -64,7 +53,7 @@ function collectPlanIssues(planEntries) {
|
|
|
64
53
|
issues.push({
|
|
65
54
|
type: 'missing-task-checklist',
|
|
66
55
|
planName: entry.planName,
|
|
67
|
-
details: ['tasks.md
|
|
56
|
+
details: ['tasks.md 没有可执行检查项'],
|
|
68
57
|
})
|
|
69
58
|
continue
|
|
70
59
|
}
|
|
@@ -140,65 +129,6 @@ function collectGateIssues(planEntries, verificationStatus, reviewStatus, adviso
|
|
|
140
129
|
return issues
|
|
141
130
|
}
|
|
142
131
|
|
|
143
|
-
function issueHeading(issue) {
|
|
144
|
-
switch (issue.type) {
|
|
145
|
-
case 'missing-files':
|
|
146
|
-
return 'active plan package is missing required artifacts'
|
|
147
|
-
case 'template-placeholders':
|
|
148
|
-
return 'active plan package still contains template placeholders'
|
|
149
|
-
case 'missing-task-checklist':
|
|
150
|
-
return 'active plan package has no executable tasks'
|
|
151
|
-
case 'unfinished-tasks':
|
|
152
|
-
return 'active plan package still has unfinished tasks'
|
|
153
|
-
case 'under-specified-tasks':
|
|
154
|
-
return 'active plan package has under-specified task metadata'
|
|
155
|
-
case 'missing-contract':
|
|
156
|
-
return 'active plan package is missing a trustworthy structured contract'
|
|
157
|
-
case 'missing-verify-evidence':
|
|
158
|
-
return 'current workflow is missing fresh verification evidence'
|
|
159
|
-
case 'missing-review-evidence':
|
|
160
|
-
return 'current workflow is missing fresh review evidence'
|
|
161
|
-
case 'missing-advisor-evidence':
|
|
162
|
-
return 'current workflow is missing fresh advisor evidence'
|
|
163
|
-
case 'missing-visual-evidence':
|
|
164
|
-
return 'current workflow is missing fresh visual validation evidence'
|
|
165
|
-
case 'missing-closeout-evidence':
|
|
166
|
-
return 'current workflow is missing fresh closeout evidence'
|
|
167
|
-
default:
|
|
168
|
-
return 'active plan package is not ready for delivery'
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function buildBlockReason(issues, recommendation, gateHint) {
|
|
173
|
-
const lines = ['[Delivery Gate] Delivery is blocked because the current workflow state is not closed yet:']
|
|
174
|
-
|
|
175
|
-
for (const issue of issues) {
|
|
176
|
-
lines.push(`- ${issue.planName}: ${issueHeading(issue)}`)
|
|
177
|
-
for (const detail of issue.details) {
|
|
178
|
-
lines.push(` - ${detail}`)
|
|
179
|
-
}
|
|
180
|
-
if (issue.extraCount) {
|
|
181
|
-
lines.push(` - ...and ${issue.extraCount} more`)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
lines.push('')
|
|
186
|
-
if (recommendation?.nextPath) {
|
|
187
|
-
lines.push(`Recommended path: ${recommendation.nextPath}`)
|
|
188
|
-
}
|
|
189
|
-
if (issues.some((issue) => issue.type === 'missing-closeout-evidence')) {
|
|
190
|
-
lines.push('Next closeout step: write `.helloagents/.ralph-closeout.json` with `requirementsCoverage` and `deliveryChecklist` before reporting completion.')
|
|
191
|
-
}
|
|
192
|
-
if (issues.some((issue) => issue.type === 'missing-visual-evidence')) {
|
|
193
|
-
lines.push('Next visual step: write `.helloagents/.ralph-visual.json` with `tooling`, `screensChecked`, `statesChecked`, `status`, and `summary` before reporting completion.')
|
|
194
|
-
}
|
|
195
|
-
if (gateHint) {
|
|
196
|
-
lines.push(gateHint)
|
|
197
|
-
}
|
|
198
|
-
lines.push('Do not report completion yet. First finish or explicitly close the remaining tasks, or repair the active plan package so it becomes a trustworthy delivery record.')
|
|
199
|
-
return lines.join('\n')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
132
|
function main() {
|
|
203
133
|
let data = {}
|
|
204
134
|
try {
|
|
@@ -208,11 +138,12 @@ function main() {
|
|
|
208
138
|
const workflowOptions = { payload: data }
|
|
209
139
|
const snapshot = getWorkflowSnapshot(cwd, workflowOptions)
|
|
210
140
|
const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
|
|
211
|
-
const verificationStatus = getVerifyEvidenceStatus(cwd)
|
|
141
|
+
const verificationStatus = getVerifyEvidenceStatus(cwd, workflowOptions)
|
|
212
142
|
const deliveryAction = getDeliveryAction(cwd, workflowOptions)
|
|
213
143
|
const gatePlans = selectGatePlans(snapshot)
|
|
214
144
|
const reviewStatus = getReviewEvidenceStatus(cwd, {
|
|
215
145
|
required: deliveryAction?.phase === 'verify' && deliveryAction?.mode === 'review-first',
|
|
146
|
+
...workflowOptions,
|
|
216
147
|
})
|
|
217
148
|
if (gatePlans.length === 0) {
|
|
218
149
|
process.stdout.write(JSON.stringify({ suppressOutput: true }))
|
|
@@ -223,12 +154,14 @@ function main() {
|
|
|
223
154
|
const advisorStatus = getAdvisorEvidenceStatus(cwd, {
|
|
224
155
|
required: advisorRequirements.some((entry) => entry.required),
|
|
225
156
|
focus: advisorRequirements.flatMap((entry) => entry.focus || []),
|
|
157
|
+
...workflowOptions,
|
|
226
158
|
})
|
|
227
159
|
const visualRequirements = gatePlans.map((entry) => getVisualValidationRequirement(entry.contract))
|
|
228
160
|
const visualStatus = getVisualEvidenceStatus(cwd, {
|
|
229
161
|
required: visualRequirements.some((entry) => entry.required),
|
|
230
162
|
screens: visualRequirements.flatMap((entry) => entry.screens || []),
|
|
231
163
|
states: visualRequirements.flatMap((entry) => entry.states || []),
|
|
164
|
+
...workflowOptions,
|
|
232
165
|
})
|
|
233
166
|
const closeoutRequired = (
|
|
234
167
|
gatePlans.every((entry) => entry.missingFiles.length === 0 && entry.templateIssues.length === 0 && entry.taskSummary.total > 0 && entry.taskSummary.open === 0 && entry.taskSummary.underSpecifiedCount === 0)
|
|
@@ -239,6 +172,7 @@ function main() {
|
|
|
239
172
|
)
|
|
240
173
|
const closeoutStatus = getCloseoutEvidenceStatus(cwd, {
|
|
241
174
|
required: closeoutRequired,
|
|
175
|
+
...workflowOptions,
|
|
242
176
|
})
|
|
243
177
|
|
|
244
178
|
const issues = collectGateIssues(gatePlans, verificationStatus, reviewStatus, advisorStatus, visualStatus, closeoutStatus)
|
|
@@ -249,7 +183,7 @@ function main() {
|
|
|
249
183
|
|
|
250
184
|
process.stdout.write(JSON.stringify({
|
|
251
185
|
decision: 'block',
|
|
252
|
-
reason:
|
|
186
|
+
reason: buildDeliveryBlockReason(issues, recommendation, buildDeliveryGateHint(cwd, workflowOptions)),
|
|
253
187
|
suppressOutput: true,
|
|
254
188
|
}))
|
|
255
189
|
}
|
package/scripts/guard-rules.mjs
CHANGED
|
@@ -2,29 +2,29 @@ import { readFileSync } from 'node:fs'
|
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
export const DANGEROUS_PATTERNS = [
|
|
5
|
-
{ pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?(-[a-zA-Z]*r[a-zA-Z]*\s+)?(\/|~|\*)/, reason: '
|
|
6
|
-
{ pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?(-[a-zA-Z]*f[a-zA-Z]*\s+)?(\/|~|\*)/, reason: '
|
|
7
|
-
{ pattern: /(sudo\s+)?rm\s+--recursive/, reason: '
|
|
8
|
-
{ pattern: /(sudo\s+)?rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\.\.?(\s|$)/, reason: '
|
|
9
|
-
{ pattern: /\bcmd(?:\.exe)?\s*\/c\b/i, reason: '
|
|
10
|
-
{ pattern: /\bStart-Process\s+cmd(?:\.exe)?\b/i, reason: '
|
|
11
|
-
{ pattern: /git\s+push\s+(-f|--force)/, reason: '
|
|
12
|
-
{ pattern: /git\s+reset\s+--hard/, reason: '
|
|
13
|
-
{ pattern: /DROP\s+(DATABASE|TABLE|SCHEMA)/i, reason: '
|
|
14
|
-
{ pattern: /\bTRUNCATE(?:\s+TABLE)?\b/i, reason: '
|
|
15
|
-
{ pattern: /chmod\s+777/, reason: '
|
|
16
|
-
{ pattern: /mkfs\b/, reason: '
|
|
17
|
-
{ pattern: /dd\s+.*of=\/dev\//, reason: '
|
|
18
|
-
{ pattern: /FLUSHALL|FLUSHDB/i, reason: 'Redis
|
|
5
|
+
{ pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?(-[a-zA-Z]*r[a-zA-Z]*\s+)?(\/|~|\*)/, reason: '递归删除关键路径' },
|
|
6
|
+
{ pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?(-[a-zA-Z]*f[a-zA-Z]*\s+)?(\/|~|\*)/, reason: '递归删除关键路径' },
|
|
7
|
+
{ pattern: /(sudo\s+)?rm\s+--recursive/, reason: '递归删除命令' },
|
|
8
|
+
{ pattern: /(sudo\s+)?rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\.\.?(\s|$)/, reason: '递归删除当前目录或父目录' },
|
|
9
|
+
{ pattern: /\bcmd(?:\.exe)?\s*\/c\b/i, reason: '嵌套 cmd 会绕过 PowerShell 安全规则' },
|
|
10
|
+
{ pattern: /\bStart-Process\s+cmd(?:\.exe)?\b/i, reason: '嵌套 cmd 会绕过 PowerShell 安全规则' },
|
|
11
|
+
{ pattern: /git\s+push\s+(-f|--force)/, reason: '强制推送风险高,必须明确分支与授权' },
|
|
12
|
+
{ pattern: /git\s+reset\s+--hard/, reason: '硬重置会丢弃本地变更' },
|
|
13
|
+
{ pattern: /DROP\s+(DATABASE|TABLE|SCHEMA)/i, reason: '数据库破坏性命令' },
|
|
14
|
+
{ pattern: /\bTRUNCATE(?:\s+TABLE)?\b/i, reason: '表数据清空命令' },
|
|
15
|
+
{ pattern: /chmod\s+777/, reason: '全局可写权限风险高' },
|
|
16
|
+
{ pattern: /mkfs\b/, reason: '文件系统格式化命令' },
|
|
17
|
+
{ pattern: /dd\s+.*of=\/dev\//, reason: '直接写入设备' },
|
|
18
|
+
{ pattern: /FLUSHALL|FLUSHDB/i, reason: 'Redis 数据清空命令' },
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
export const HIGH_RISK_COMMAND_PATTERNS = [
|
|
22
|
-
{ pattern: /\bnpm\s+publish\b/i, reason: '
|
|
23
|
-
{ pattern: /\bgh\s+release\s+create\b/i, reason: '
|
|
24
|
-
{ pattern: /\bterraform\s+(apply|destroy)\b/i, reason: '
|
|
25
|
-
{ pattern: /\b(kubectl|helm)\s+(apply|delete|upgrade|rollback|set|rollout)\b/i, reason: '
|
|
26
|
-
{ pattern: /\b(prisma|drizzle-kit|sequelize-cli|typeorm)\b.*\b(migrate|migration)\b/i, reason: '
|
|
27
|
-
{ pattern: /\b(vercel|wrangler|netlify|flyctl|fly)\b.*\b(deploy|publish)\b/i, reason: '
|
|
22
|
+
{ pattern: /\bnpm\s+publish\b/i, reason: '包发布命令', gate: 'post-verify' },
|
|
23
|
+
{ pattern: /\bgh\s+release\s+create\b/i, reason: '发布 release 命令', gate: 'post-verify' },
|
|
24
|
+
{ pattern: /\bterraform\s+(apply|destroy)\b/i, reason: '基础设施变更命令', gate: 'post-verify' },
|
|
25
|
+
{ pattern: /\b(kubectl|helm)\s+(apply|delete|upgrade|rollback|set|rollout)\b/i, reason: '集群变更命令', gate: 'post-verify' },
|
|
26
|
+
{ pattern: /\b(prisma|drizzle-kit|sequelize-cli|typeorm)\b.*\b(migrate|migration)\b/i, reason: '数据库迁移命令', gate: 'plan-first' },
|
|
27
|
+
{ pattern: /\b(vercel|wrangler|netlify|flyctl|fly)\b.*\b(deploy|publish)\b/i, reason: '部署命令', gate: 'post-verify' },
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
export const IDEA_SIDE_EFFECT_COMMAND_PATTERNS = [
|
|
@@ -36,20 +36,20 @@ export const IDEA_SIDE_EFFECT_COMMAND_PATTERNS = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
const SECRET_PATTERNS = [
|
|
39
|
-
{ pattern: /AKIA[0-9A-Z]{16}/, reason: 'AWS Access Key ID
|
|
40
|
-
{ pattern: /ghp_[a-zA-Z0-9]{36}/, reason: 'GitHub Personal Access Token
|
|
41
|
-
{ pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/, reason: 'GitHub Fine-grained PAT
|
|
42
|
-
{ pattern: /sk-[a-zA-Z0-9]{20,}/, reason: 'API secret key
|
|
43
|
-
{ pattern: /key-[a-zA-Z0-9]{20,}/, reason: 'API key
|
|
44
|
-
{ pattern: /-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----/, reason: '
|
|
45
|
-
{ pattern: /password\s*[:=]\s*["'][^"']{4,}["']/i, reason: '
|
|
46
|
-
{ pattern: /secret\s*[:=]\s*["'][^"']{4,}["']/i, reason: '
|
|
47
|
-
{ pattern: /AIza[0-9A-Za-z\-_]{35}/, reason: 'Google API Key
|
|
48
|
-
{ pattern: /xox[bpras]-[0-9a-zA-Z\-]+/, reason: 'Slack Token
|
|
49
|
-
{ pattern: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.+/=]+/, reason: 'JWT token
|
|
50
|
-
{ pattern: /(postgres|mysql|mongodb(\+srv)?):\/\/[^:]+:[^@]+@/i, reason: '
|
|
51
|
-
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, reason: 'Stripe Secret Key
|
|
52
|
-
{ pattern: /sk-ant-[a-zA-Z0-9\-]{20,}/, reason: 'Anthropic API Key
|
|
39
|
+
{ pattern: /AKIA[0-9A-Z]{16}/, reason: '检测到 AWS Access Key ID' },
|
|
40
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/, reason: '检测到 GitHub Personal Access Token' },
|
|
41
|
+
{ pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/, reason: '检测到 GitHub Fine-grained PAT' },
|
|
42
|
+
{ pattern: /sk-[a-zA-Z0-9]{20,}/, reason: '检测到 API secret key(sk-)' },
|
|
43
|
+
{ pattern: /key-[a-zA-Z0-9]{20,}/, reason: '检测到 API key(key-)' },
|
|
44
|
+
{ pattern: /-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----/, reason: '检测到私钥' },
|
|
45
|
+
{ pattern: /password\s*[:=]\s*["'][^"']{4,}["']/i, reason: '检测到硬编码密码' },
|
|
46
|
+
{ pattern: /secret\s*[:=]\s*["'][^"']{4,}["']/i, reason: '检测到硬编码密钥' },
|
|
47
|
+
{ pattern: /AIza[0-9A-Za-z\-_]{35}/, reason: '检测到 Google API Key' },
|
|
48
|
+
{ pattern: /xox[bpras]-[0-9a-zA-Z\-]+/, reason: '检测到 Slack Token' },
|
|
49
|
+
{ pattern: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.+/=]+/, reason: '检测到 JWT token' },
|
|
50
|
+
{ pattern: /(postgres|mysql|mongodb(\+srv)?):\/\/[^:]+:[^@]+@/i, reason: '检测到包含凭据的数据库连接串' },
|
|
51
|
+
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, reason: '检测到 Stripe Secret Key' },
|
|
52
|
+
{ pattern: /sk-ant-[a-zA-Z0-9\-]{20,}/, reason: '检测到 Anthropic API Key' },
|
|
53
53
|
]
|
|
54
54
|
|
|
55
55
|
export function scanForSecrets(content) {
|
|
@@ -81,13 +81,13 @@ export function scanShellSafetyWarnings(command = '') {
|
|
|
81
81
|
.map((entry) => entry.trim())
|
|
82
82
|
.filter(Boolean)
|
|
83
83
|
if (logicalLines.length > 3) {
|
|
84
|
-
warnings.push('PowerShell
|
|
84
|
+
warnings.push('PowerShell 内联脚本超过 3 个逻辑行,建议改用临时 .ps1 文件')
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const fileOps = normalized.match(/\b(remove-item|move-item|copy-item|new-item|set-content|add-content|out-file|mkdir|md|touch|cp|copy|mv|move|ren|rename|del|erase|rm|rmdir)\b/ig) || []
|
|
89
89
|
if (fileOps.length > 1 && /[;\r\n]/.test(normalized)) {
|
|
90
|
-
warnings.push('
|
|
90
|
+
warnings.push('单条 shell 命令串联了多个文件操作,建议拆成独立命令')
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
return warnings
|
|
@@ -99,11 +99,11 @@ export function scanUnrequestedFiles(filePath, toolName) {
|
|
|
99
99
|
const warnings = []
|
|
100
100
|
|
|
101
101
|
const patterns = [
|
|
102
|
-
{ pattern: /^(SUMMARY|NOTES|TODO|SCRATCH|TEMP)\.(md|txt)$/i, reason:
|
|
102
|
+
{ pattern: /^(SUMMARY|NOTES|TODO|SCRATCH|TEMP)\.(md|txt)$/i, reason: `检测到未请求的文件创建:${basename}` },
|
|
103
103
|
{
|
|
104
104
|
pattern: /^README.*\.md$/i,
|
|
105
105
|
matches: () => filePath.replace(/\\/g, '/').split('/').length > 4,
|
|
106
|
-
reason:
|
|
106
|
+
reason: `检测到嵌套路径中的可疑 README 创建:${basename}`,
|
|
107
107
|
},
|
|
108
108
|
]
|
|
109
109
|
|
|
@@ -120,12 +120,12 @@ export function scanDangerousPackages(content, filePath) {
|
|
|
120
120
|
if (filePath.endsWith('package.json')) {
|
|
121
121
|
const dangerousScripts = /("(preinstall|postinstall|preuninstall)")\s*:\s*"[^"]*\b(curl|wget|bash|sh|eval|exec)\b/i
|
|
122
122
|
if (dangerousScripts.test(content)) {
|
|
123
|
-
warnings.push('
|
|
123
|
+
warnings.push('package.json 中存在潜在危险的生命周期脚本(preinstall/postinstall 调用 curl、wget、bash 或 eval)')
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
const unsafeInstall = /npm install\s+[^-].*--ignore-scripts\s*=\s*false|pip install\s+--trusted-host|pip install\s+http:/i
|
|
127
127
|
if (unsafeInstall.test(content)) {
|
|
128
|
-
warnings.push('
|
|
128
|
+
warnings.push('检测到不安全的依赖安装写法')
|
|
129
129
|
}
|
|
130
130
|
return warnings
|
|
131
131
|
}
|
|
@@ -136,12 +136,12 @@ export function scanEnvCoverage(filePath) {
|
|
|
136
136
|
for (let i = 0; i < 10; i += 1) {
|
|
137
137
|
try {
|
|
138
138
|
const gitignore = readFileSync(join(dir, '.gitignore'), 'utf-8')
|
|
139
|
-
return gitignore.includes('.env') ? [] : ['.env
|
|
139
|
+
return gitignore.includes('.env') ? [] : ['写入了 .env 文件,但 .gitignore 未包含 .env 规则']
|
|
140
140
|
} catch {
|
|
141
141
|
const parent = dirname(dir)
|
|
142
142
|
if (parent === dir) break
|
|
143
143
|
dir = parent
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
return ['.env
|
|
146
|
+
return ['写入了 .env 文件,但未找到 .gitignore']
|
|
147
147
|
}
|
package/scripts/guard.mjs
CHANGED
|
@@ -52,13 +52,14 @@ function emitHookPayload(payload) {
|
|
|
52
52
|
process.stdout.write(JSON.stringify(payload))
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function emitGuardEvent(cwd, event, source, reason, details = {}) {
|
|
55
|
+
function emitGuardEvent(cwd, event, source, reason, details = {}, payload = {}) {
|
|
56
56
|
appendReplayEvent(cwd, {
|
|
57
57
|
host: HOST,
|
|
58
58
|
event,
|
|
59
59
|
source,
|
|
60
60
|
reason,
|
|
61
61
|
details,
|
|
62
|
+
payload,
|
|
62
63
|
})
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -67,7 +68,7 @@ function buildHighRiskGate(matches, cwd, payload = {}) {
|
|
|
67
68
|
const stateSyncHint = buildStateSyncHint(cwd, workflowOptions)
|
|
68
69
|
if (stateSyncHint) {
|
|
69
70
|
return {
|
|
70
|
-
reason: `[HelloAGENTS Guard]
|
|
71
|
+
reason: `[HelloAGENTS Guard] 已阻止 T3 命令:项目恢复状态尚未同步。\n${stateSyncHint}`,
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -75,25 +76,26 @@ function buildHighRiskGate(matches, cwd, payload = {}) {
|
|
|
75
76
|
if (!recommendation) return null
|
|
76
77
|
if (matches.some((match) => match.gate === 'post-verify')) {
|
|
77
78
|
return {
|
|
78
|
-
reason: `[HelloAGENTS Guard]
|
|
79
|
+
reason: `[HelloAGENTS Guard] 已阻止 T3 命令:当前工作流尚未进入 VERIFY / CONSOLIDATE。\n当前工作流:${recommendation.summary}\n建议路径:${recommendation.nextPath}\n${recommendation.guidance}`,
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
if (matches.some((match) => match.gate === 'plan-first') && recommendation.nextCommand === 'plan') {
|
|
82
83
|
return {
|
|
83
|
-
reason: `[HelloAGENTS Guard]
|
|
84
|
+
reason: `[HelloAGENTS Guard] 已阻止 T3 命令:高风险 schema 变更前仍需先完成 ~plan。\n当前工作流:${recommendation.summary}\n建议路径:${recommendation.nextPath}\n${recommendation.guidance}`,
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
return null
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
function buildIdeaBoundaryReason(kind) {
|
|
90
|
-
return `[HelloAGENTS Guard]
|
|
91
|
+
return `[HelloAGENTS Guard] 已阻止 ~idea 中的${kind}。\n当前路由:~idea 是只读探索;先停留在比较方案。若要写文件、改代码、创建知识库或执行有副作用的命令,请先升级到 ~plan / ~build / ~prd / ~auto。`
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
function detectIdeaBoundaryContext(data) {
|
|
94
95
|
return getApplicableRouteContext({
|
|
95
96
|
cwd: data.cwd || process.cwd(),
|
|
96
97
|
filePath: data.tool_input?.file_path || '',
|
|
98
|
+
payload: data,
|
|
97
99
|
})
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -106,17 +108,24 @@ function emitIdeaBoundaryBlock(data, kind, target) {
|
|
|
106
108
|
permissionDecisionReason: reason,
|
|
107
109
|
},
|
|
108
110
|
})
|
|
109
|
-
emitGuardEvent(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
emitGuardEvent(
|
|
112
|
+
data.cwd || process.cwd(),
|
|
113
|
+
'guard_blocked',
|
|
114
|
+
kind === 'write' ? 'pre-write' : 'command',
|
|
115
|
+
buildIdeaBoundaryReason(kind),
|
|
116
|
+
{
|
|
117
|
+
command: kind === '有副作用命令' ? target.replace(/^命令:\s*/, '') : '',
|
|
118
|
+
target: kind === '写入操作' ? target.replace(/^目标:\s*/, '') : '',
|
|
119
|
+
guardType: kind === '写入操作' ? 'idea-write-boundary' : 'idea-command-boundary',
|
|
120
|
+
},
|
|
121
|
+
data,
|
|
122
|
+
)
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
function preWriteGuard(data) {
|
|
117
126
|
if (readSettings().guard_enabled === false) return
|
|
118
127
|
if (!detectIdeaBoundaryContext(data)?.zeroSideEffect) return
|
|
119
|
-
emitIdeaBoundaryBlock(data, '
|
|
128
|
+
emitIdeaBoundaryBlock(data, '写入操作', `目标:${data.tool_input?.file_path || '未知文件'}`)
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
function buildPostWriteWarnings(data) {
|
|
@@ -146,23 +155,23 @@ function postWriteScan(data) {
|
|
|
146
155
|
emitGuardEvent(data.cwd || process.cwd(), 'guard_warning', 'post-write', '', {
|
|
147
156
|
warnings,
|
|
148
157
|
guardType: 'post-write-l2',
|
|
149
|
-
})
|
|
158
|
+
}, data)
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
function handleDangerousCommand(data, command) {
|
|
153
162
|
for (const { pattern, reason } of DANGEROUS_PATTERNS) {
|
|
154
163
|
if (!pattern.test(command)) continue
|
|
155
164
|
emitHookPayload({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
hookSpecificOutput: {
|
|
166
|
+
hookEventName: HOOK_EVENT,
|
|
167
|
+
permissionDecision: 'deny',
|
|
168
|
+
permissionDecisionReason: `[HelloAGENTS Guard] 已阻止:${reason}\n命令:${command.slice(0, 200)}`,
|
|
169
|
+
},
|
|
161
170
|
})
|
|
162
171
|
emitGuardEvent(data.cwd || process.cwd(), 'guard_blocked', 'command', reason, {
|
|
163
172
|
command: command.slice(0, 200),
|
|
164
173
|
guardType: 'dangerous-command',
|
|
165
|
-
})
|
|
174
|
+
}, data)
|
|
166
175
|
return true
|
|
167
176
|
}
|
|
168
177
|
return false
|
|
@@ -179,14 +188,14 @@ function handleHighRiskCommand(data, command) {
|
|
|
179
188
|
hookSpecificOutput: {
|
|
180
189
|
hookEventName: HOOK_EVENT,
|
|
181
190
|
permissionDecision: 'deny',
|
|
182
|
-
permissionDecisionReason: `${gate.reason}\
|
|
191
|
+
permissionDecisionReason: `${gate.reason}\n命令:${command.slice(0, 200)}`,
|
|
183
192
|
},
|
|
184
193
|
})
|
|
185
194
|
emitGuardEvent(cwd, 'guard_blocked', 'command', gate.reason, {
|
|
186
195
|
command: command.slice(0, 200),
|
|
187
196
|
guardType: 'high-risk-gate',
|
|
188
197
|
matches: warnings.map((warning) => warning.reason),
|
|
189
|
-
})
|
|
198
|
+
}, data)
|
|
190
199
|
return null
|
|
191
200
|
}
|
|
192
201
|
return warnings.map((warning) => warning.reason)
|
|
@@ -215,14 +224,14 @@ function emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings)
|
|
|
215
224
|
guardType: 'high-risk-warning',
|
|
216
225
|
command: command.slice(0, 200),
|
|
217
226
|
warnings: highRiskWarnings,
|
|
218
|
-
})
|
|
227
|
+
}, data)
|
|
219
228
|
}
|
|
220
229
|
if (shellSafetyWarnings.length > 0) {
|
|
221
230
|
emitGuardEvent(cwd, 'guard_warning', 'command', '', {
|
|
222
231
|
guardType: 'shell-safety-warning',
|
|
223
232
|
command: command.slice(0, 200),
|
|
224
233
|
warnings: shellSafetyWarnings,
|
|
225
|
-
})
|
|
234
|
+
}, data)
|
|
226
235
|
}
|
|
227
236
|
}
|
|
228
237
|
|
|
@@ -236,7 +245,7 @@ function handleShellCommand(data) {
|
|
|
236
245
|
if (detectIdeaBoundaryContext(data)?.zeroSideEffect) {
|
|
237
246
|
for (const pattern of IDEA_SIDE_EFFECT_COMMAND_PATTERNS) {
|
|
238
247
|
if (!pattern.test(command)) continue
|
|
239
|
-
emitIdeaBoundaryBlock(data, '
|
|
248
|
+
emitIdeaBoundaryBlock(data, '有副作用命令', `命令:${command.slice(0, 200)}`)
|
|
240
249
|
return
|
|
241
250
|
}
|
|
242
251
|
}
|
|
@@ -265,4 +274,15 @@ async function main() {
|
|
|
265
274
|
handleShellCommand(data)
|
|
266
275
|
}
|
|
267
276
|
|
|
268
|
-
main().catch(() => {
|
|
277
|
+
main().catch((error) => {
|
|
278
|
+
const reason = `[HelloAGENTS Guard] 守卫脚本执行异常,已阻止本次操作以避免静默放行。\n原因:${error?.message || error}`
|
|
279
|
+
emitHookPayload({
|
|
280
|
+
hookSpecificOutput: {
|
|
281
|
+
hookEventName: HOOK_EVENT,
|
|
282
|
+
permissionDecision: 'deny',
|
|
283
|
+
permissionDecisionReason: reason,
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
process.stderr.write(`${reason}\n`)
|
|
287
|
+
process.exitCode = 1
|
|
288
|
+
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import { homedir } from 'node:os';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
4
3
|
import { buildCommandRouteHint, buildStateSyncHint, buildWorkflowRouteHint, readStateSnapshot } from './workflow-state.mjs';
|
|
5
4
|
import { buildCapabilityHint } from './capability-registry.mjs';
|
|
6
5
|
import {
|
|
@@ -15,35 +14,27 @@ const COMMAND_ALIASES = {
|
|
|
15
14
|
review: 'verify',
|
|
16
15
|
};
|
|
17
16
|
|
|
18
|
-
function
|
|
17
|
+
function buildRuntimeRootBlock(pkgRoot) {
|
|
19
18
|
if (!pkgRoot) return '';
|
|
20
|
-
return `## 当前 HelloAGENTS
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function resolveStandbyHostRoot(host) {
|
|
24
|
-
const home = homedir();
|
|
25
|
-
const map = {
|
|
26
|
-
claude: join(home, '.claude', 'helloagents'),
|
|
27
|
-
codex: join(home, '.codex', 'helloagents'),
|
|
28
|
-
gemini: join(home, '.gemini', 'helloagents'),
|
|
29
|
-
};
|
|
30
|
-
return map[host] || '';
|
|
19
|
+
return `## 当前 HelloAGENTS 运行根目录\n\`\`\`text\n${pkgRoot}\n\`\`\``;
|
|
31
20
|
}
|
|
32
21
|
|
|
33
22
|
function resolveReadRoot({ cwd, pkgRoot, host, settings }) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return { source: 'package', root: pkgRoot };
|
|
23
|
+
void cwd
|
|
24
|
+
void host
|
|
25
|
+
void settings
|
|
26
|
+
return { source: 'runtime-root', root: pkgRoot }
|
|
42
27
|
}
|
|
43
28
|
|
|
44
29
|
function buildReadRootBlock(readRoot) {
|
|
45
30
|
if (!readRoot?.root) return '';
|
|
46
|
-
|
|
31
|
+
const block = {
|
|
32
|
+
...readRoot,
|
|
33
|
+
scriptRoot: join(readRoot.root, 'scripts'),
|
|
34
|
+
turnStateCommand: 'helloagents-turn-state write --kind complete --role main',
|
|
35
|
+
turnStateUsage: '仅在运行时需要识别完成、等待或阻塞时调用;普通问答不调用',
|
|
36
|
+
};
|
|
37
|
+
return `## 本轮 HelloAGENTS 读取根目录\n\`\`\`json\n${JSON.stringify(block, null, 2)}\n\`\`\``;
|
|
47
38
|
}
|
|
48
39
|
|
|
49
40
|
export function resolveCanonicalCommandSkill(skillName) {
|
|
@@ -89,10 +80,10 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
89
80
|
summaryParts.push(bootstrap);
|
|
90
81
|
}
|
|
91
82
|
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
83
|
+
const runtimeRootBlock = buildRuntimeRootBlock(pkgRoot);
|
|
84
|
+
if (runtimeRootBlock) {
|
|
94
85
|
summaryParts.push('');
|
|
95
|
-
summaryParts.push(
|
|
86
|
+
summaryParts.push(runtimeRootBlock);
|
|
96
87
|
}
|
|
97
88
|
|
|
98
89
|
const readRootBlock = buildReadRootBlock(resolveReadRoot({ cwd, pkgRoot, host, settings }));
|
|
@@ -123,7 +114,7 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
123
114
|
|
|
124
115
|
export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host, cwd, payload = {} }) {
|
|
125
116
|
const workflowOptions = { payload };
|
|
126
|
-
const
|
|
117
|
+
const runtimeRootBlock = buildRuntimeRootBlock(pkgRoot);
|
|
127
118
|
const readRootBlock = buildReadRootBlock(resolveReadRoot({ cwd, pkgRoot, host, settings }));
|
|
128
119
|
const workflowHint = buildWorkflowRouteHint(cwd, workflowOptions);
|
|
129
120
|
const capabilityHint = buildCapabilityHint({ cwd, options: workflowOptions });
|
|
@@ -135,7 +126,7 @@ export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host,
|
|
|
135
126
|
: '';
|
|
136
127
|
|
|
137
128
|
let context = bootstrap;
|
|
138
|
-
if (
|
|
129
|
+
if (runtimeRootBlock) context += `\n\n${runtimeRootBlock}`;
|
|
139
130
|
if (readRootBlock) context += `\n\n${readRootBlock}`;
|
|
140
131
|
if (projectStorageBlock) context += `\n\n${projectStorageBlock}`;
|
|
141
132
|
if (workflowHint) context += `\n\n## 当前工作流提示\n${workflowHint}`;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export function shouldIgnoreCodexNotifyClient(client) {
|
|
2
|
-
|
|
2
|
+
if (!client) return false;
|
|
3
|
+
const normalized = String(client).trim().toLowerCase().replace(/[_\s]+/g, '-');
|
|
4
|
+
return normalized !== 'codex' && !normalized.startsWith('codex-');
|
|
3
5
|
}
|
|
4
6
|
|
|
5
7
|
export function shouldIgnoreFormattedSubagent(lastMsg, outputFormatEnabled) {
|
package/scripts/notify-gates.mjs
CHANGED
|
@@ -27,6 +27,7 @@ function emitGateError({
|
|
|
27
27
|
event: 'runtime_gate_error',
|
|
28
28
|
source,
|
|
29
29
|
reason,
|
|
30
|
+
payload,
|
|
30
31
|
})
|
|
31
32
|
output({
|
|
32
33
|
decision: 'block',
|
|
@@ -119,6 +120,7 @@ export function runGateScript({
|
|
|
119
120
|
event: blockEvent,
|
|
120
121
|
source,
|
|
121
122
|
reason: gateOutput.reason || '',
|
|
123
|
+
payload,
|
|
122
124
|
})
|
|
123
125
|
output(gateOutput)
|
|
124
126
|
return true
|
package/scripts/notify-route.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path'
|
|
1
|
+
import { isProjectRuntimeActive } from './runtime-scope.mjs'
|
|
3
2
|
|
|
4
3
|
export function resolveBootstrapFile(cwd, installMode) {
|
|
5
|
-
const isActivated =
|
|
4
|
+
const isActivated = isProjectRuntimeActive(cwd)
|
|
6
5
|
return (installMode === 'global' || isActivated) ? 'bootstrap.md' : 'bootstrap-lite.md'
|
|
7
6
|
}
|
|
8
7
|
|
|
@@ -12,7 +11,7 @@ function shouldBypassRoute(prompt) {
|
|
|
12
11
|
|
|
13
12
|
function buildHelpExtraRules(skillName) {
|
|
14
13
|
if (skillName !== 'help') return ''
|
|
15
|
-
return ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。仅显示 HelloAGENTS
|
|
14
|
+
return ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前会话上下文中已注入的“当前用户设置”、配置文件原始 JSON 或此前读取结果摘要,上下文不存在或缺少要展示的配置项时才读取一次 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式或已激活项目中生效。不要调用宿主 CLI 的帮助工具(如 cli_help 或 /help),不要使用子代理,不要读取项目文件;若受工作区限制无法读取配置,必须明确说明并按已知默认值或已注入设置展示。'
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
function routeExplicitCommand({
|
|
@@ -37,6 +36,7 @@ function routeExplicitCommand({
|
|
|
37
36
|
cwd,
|
|
38
37
|
skillName: canonicalSkillName,
|
|
39
38
|
sourceSkillName: skillName,
|
|
39
|
+
payload,
|
|
40
40
|
})
|
|
41
41
|
appendReplayEvent(cwd, {
|
|
42
42
|
host,
|
|
@@ -44,6 +44,7 @@ function routeExplicitCommand({
|
|
|
44
44
|
source: 'route',
|
|
45
45
|
skillName: canonicalSkillName,
|
|
46
46
|
sourceSkillName: skillName,
|
|
47
|
+
payload,
|
|
47
48
|
})
|
|
48
49
|
suppress(buildRouteInstruction({
|
|
49
50
|
skillName,
|
|
@@ -75,7 +76,7 @@ export function handleRouteCommand({
|
|
|
75
76
|
const prompt = (payload.prompt || '').trim()
|
|
76
77
|
const cwd = payload.cwd || process.cwd()
|
|
77
78
|
if (shouldBypassRoute(prompt)) {
|
|
78
|
-
clearRouteContext()
|
|
79
|
+
clearRouteContext({ cwd, payload })
|
|
79
80
|
emptySuppress()
|
|
80
81
|
return
|
|
81
82
|
}
|
|
@@ -98,17 +99,18 @@ export function handleRouteCommand({
|
|
|
98
99
|
|
|
99
100
|
const bootstrapFile = resolveBootstrapFile(cwd, settings.install_mode)
|
|
100
101
|
if (bootstrapFile === 'bootstrap.md') {
|
|
101
|
-
clearRouteContext()
|
|
102
|
+
clearRouteContext({ cwd, payload })
|
|
102
103
|
appendReplayEvent(cwd, {
|
|
103
104
|
host,
|
|
104
105
|
event: 'semantic_route_prompted',
|
|
105
106
|
source: 'route',
|
|
106
107
|
recommendation: getWorkflowRecommendation(cwd, { payload }),
|
|
108
|
+
payload,
|
|
107
109
|
})
|
|
108
110
|
suppress(buildSemanticRouteInstruction(cwd, payload))
|
|
109
111
|
return
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
clearRouteContext()
|
|
114
|
+
clearRouteContext({ cwd, payload })
|
|
113
115
|
emptySuppress()
|
|
114
116
|
}
|