helloagents 3.0.2-beta.1 → 3.0.7

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 (72) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/README.md +147 -45
  5. package/README_CN.md +148 -46
  6. package/bootstrap-lite.md +104 -46
  7. package/bootstrap.md +143 -112
  8. package/cli.mjs +80 -427
  9. package/gemini-extension.json +1 -1
  10. package/hooks/hooks-claude.json +10 -0
  11. package/hooks/hooks.json +10 -0
  12. package/package.json +2 -12
  13. package/scripts/advisor-state.mjs +222 -0
  14. package/scripts/capability-registry.mjs +59 -0
  15. package/scripts/cli-codex-backup.mjs +59 -0
  16. package/scripts/cli-codex-config.mjs +100 -0
  17. package/scripts/cli-codex.mjs +34 -156
  18. package/scripts/cli-config.mjs +1 -0
  19. package/scripts/cli-doctor-render.mjs +28 -0
  20. package/scripts/cli-doctor.mjs +367 -0
  21. package/scripts/cli-host-detect.mjs +94 -0
  22. package/scripts/cli-lifecycle-hosts.mjs +123 -0
  23. package/scripts/cli-lifecycle.mjs +213 -0
  24. package/scripts/cli-messages.mjs +76 -52
  25. package/scripts/closeout-state.mjs +213 -0
  26. package/scripts/delivery-gate.mjs +256 -0
  27. package/scripts/guard-rules.mjs +122 -0
  28. package/scripts/guard.mjs +190 -168
  29. package/scripts/notify-context.mjs +77 -17
  30. package/scripts/notify-events.mjs +5 -1
  31. package/scripts/notify-route.mjs +111 -0
  32. package/scripts/notify-shared.mjs +0 -2
  33. package/scripts/notify-source.mjs +113 -0
  34. package/scripts/notify-ui.mjs +40 -6
  35. package/scripts/notify.mjs +120 -59
  36. package/scripts/plan-contract.mjs +210 -0
  37. package/scripts/project-storage.mjs +235 -0
  38. package/scripts/ralph-loop.mjs +9 -58
  39. package/scripts/replay-state.mjs +210 -0
  40. package/scripts/review-state.mjs +220 -0
  41. package/scripts/runtime-context.mjs +74 -0
  42. package/scripts/verify-state.mjs +226 -0
  43. package/scripts/visual-state.mjs +244 -0
  44. package/scripts/workflow-core.mjs +165 -0
  45. package/scripts/workflow-plan-files.mjs +249 -0
  46. package/scripts/workflow-recommendation.mjs +335 -0
  47. package/scripts/workflow-state.mjs +113 -0
  48. package/skills/commands/auto/SKILL.md +37 -71
  49. package/skills/commands/build/SKILL.md +67 -0
  50. package/skills/commands/clean/SKILL.md +10 -8
  51. package/skills/commands/commit/SKILL.md +8 -4
  52. package/skills/commands/help/SKILL.md +19 -11
  53. package/skills/commands/idea/SKILL.md +55 -0
  54. package/skills/commands/init/SKILL.md +6 -3
  55. package/skills/commands/loop/SKILL.md +6 -5
  56. package/skills/commands/plan/SKILL.md +116 -0
  57. package/skills/commands/prd/SKILL.md +20 -15
  58. package/skills/commands/verify/SKILL.md +32 -9
  59. package/skills/commands/wiki/SKILL.md +59 -0
  60. package/skills/hello-review/SKILL.md +9 -0
  61. package/skills/hello-subagent/SKILL.md +4 -3
  62. package/skills/hello-ui/SKILL.md +36 -8
  63. package/skills/hello-verify/SKILL.md +10 -2
  64. package/skills/helloagents/SKILL.md +24 -13
  65. package/templates/DESIGN.md +25 -4
  66. package/templates/STATE.md +3 -0
  67. package/templates/plans/contract.json +48 -0
  68. package/templates/plans/plan.md +23 -0
  69. package/templates/plans/tasks.md +3 -3
  70. package/skills/commands/design/SKILL.md +0 -108
  71. package/skills/commands/review/SKILL.md +0 -16
  72. package/templates/plans/design.md +0 -14
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * HelloAGENTS Delivery Gate — workflow-aware completion gate
4
+ * Blocks "done" style close-out messages when the active plan package is still open
5
+ * or when the plan artifacts are incomplete enough that delivery is not trustworthy.
6
+ */
7
+ import { readFileSync } from 'node:fs'
8
+ import { getAdvisorEvidenceStatus } from './advisor-state.mjs'
9
+ import { getCloseoutEvidenceStatus } from './closeout-state.mjs'
10
+ import { getAdvisorRequirement, getVisualValidationRequirement } from './plan-contract.mjs'
11
+ import { getVisualEvidenceStatus } from './visual-state.mjs'
12
+ import { buildDeliveryGateHint, getDeliveryAction, getWorkflowRecommendation, getWorkflowSnapshot } from './workflow-state.mjs'
13
+ import { getReviewEvidenceStatus } from './review-state.mjs'
14
+ import { getVerifyEvidenceStatus } from './verify-state.mjs'
15
+
16
+ function selectGatePlans(snapshot) {
17
+ if (snapshot.activePlans.length > 0) return snapshot.activePlans
18
+ return snapshot.plans
19
+ }
20
+
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
+ function collectTaskMetadataIssues(entry, issues) {
34
+ if (entry.taskSummary.underSpecifiedCount === 0) return
35
+ issues.push({
36
+ type: 'under-specified-tasks',
37
+ planName: entry.planName,
38
+ details: buildUnderSpecifiedDetails(entry),
39
+ extraCount: Math.max(entry.taskSummary.underSpecifiedCount - 3, 0),
40
+ })
41
+ }
42
+
43
+ function collectPlanIssues(planEntries) {
44
+ const issues = []
45
+
46
+ for (const entry of planEntries) {
47
+ if (entry.missingFiles.length > 0) {
48
+ issues.push({
49
+ type: 'missing-files',
50
+ planName: entry.planName,
51
+ details: entry.missingFiles.map((file) => `missing ${file}`),
52
+ })
53
+ }
54
+
55
+ if (entry.templateIssues.length > 0) {
56
+ issues.push({
57
+ type: 'template-placeholders',
58
+ planName: entry.planName,
59
+ details: entry.templateIssues,
60
+ })
61
+ }
62
+
63
+ if (entry.taskSummary.total === 0) {
64
+ issues.push({
65
+ type: 'missing-task-checklist',
66
+ planName: entry.planName,
67
+ details: ['tasks.md does not contain any checklist items'],
68
+ })
69
+ continue
70
+ }
71
+
72
+ if (entry.taskSummary.open > 0) {
73
+ issues.push({
74
+ type: 'unfinished-tasks',
75
+ planName: entry.planName,
76
+ details: entry.taskSummary.items
77
+ .filter((item) => item.status === 'open')
78
+ .slice(0, 3)
79
+ .map((item) => item.text),
80
+ extraCount: Math.max(entry.taskSummary.open - 3, 0),
81
+ })
82
+ }
83
+ collectTaskMetadataIssues(entry, issues)
84
+
85
+ if (entry.contractIssues.length > 0) {
86
+ issues.push({
87
+ type: 'missing-contract',
88
+ planName: entry.planName,
89
+ details: entry.contractIssues,
90
+ })
91
+ }
92
+ }
93
+
94
+ return issues
95
+ }
96
+
97
+ function collectEvidenceIssues(issues, verificationStatus, reviewStatus, advisorStatus, visualStatus, closeoutStatus) {
98
+ if (verificationStatus?.required && verificationStatus.status !== 'valid') {
99
+ issues.push({
100
+ type: 'missing-verify-evidence',
101
+ planName: 'delivery',
102
+ details: verificationStatus.details,
103
+ })
104
+ }
105
+
106
+ if (reviewStatus?.required && reviewStatus.status !== 'valid') {
107
+ issues.push({
108
+ type: 'missing-review-evidence',
109
+ planName: 'delivery',
110
+ details: reviewStatus.details,
111
+ })
112
+ }
113
+ if (advisorStatus?.required && advisorStatus.status !== 'valid') {
114
+ issues.push({
115
+ type: 'missing-advisor-evidence',
116
+ planName: 'delivery',
117
+ details: advisorStatus.details,
118
+ })
119
+ }
120
+ if (visualStatus?.required && visualStatus.status !== 'valid') {
121
+ issues.push({
122
+ type: 'missing-visual-evidence',
123
+ planName: 'delivery',
124
+ details: visualStatus.details,
125
+ })
126
+ }
127
+
128
+ if (issues.length === 0 && closeoutStatus?.required && closeoutStatus.status !== 'valid') {
129
+ issues.push({
130
+ type: 'missing-closeout-evidence',
131
+ planName: 'delivery',
132
+ details: closeoutStatus.details,
133
+ })
134
+ }
135
+ }
136
+
137
+ function collectGateIssues(planEntries, verificationStatus, reviewStatus, advisorStatus, visualStatus, closeoutStatus) {
138
+ const issues = collectPlanIssues(planEntries)
139
+ collectEvidenceIssues(issues, verificationStatus, reviewStatus, advisorStatus, visualStatus, closeoutStatus)
140
+ return issues
141
+ }
142
+
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
+ function main() {
203
+ let data = {}
204
+ try {
205
+ data = JSON.parse(readFileSync(0, 'utf-8'))
206
+ } catch {}
207
+ const cwd = data.cwd || process.cwd()
208
+ const snapshot = getWorkflowSnapshot(cwd)
209
+ const recommendation = getWorkflowRecommendation(cwd)
210
+ const verificationStatus = getVerifyEvidenceStatus(cwd)
211
+ const deliveryAction = getDeliveryAction(cwd)
212
+ const gatePlans = selectGatePlans(snapshot)
213
+ const reviewStatus = getReviewEvidenceStatus(cwd, {
214
+ required: deliveryAction?.phase === 'verify' && deliveryAction?.mode === 'review-first',
215
+ })
216
+ if (gatePlans.length === 0) {
217
+ process.stdout.write(JSON.stringify({ suppressOutput: true }))
218
+ return
219
+ }
220
+
221
+ const advisorRequirements = gatePlans.map((entry) => getAdvisorRequirement(entry.contract))
222
+ const advisorStatus = getAdvisorEvidenceStatus(cwd, {
223
+ required: advisorRequirements.some((entry) => entry.required),
224
+ focus: advisorRequirements.flatMap((entry) => entry.focus || []),
225
+ })
226
+ const visualRequirements = gatePlans.map((entry) => getVisualValidationRequirement(entry.contract))
227
+ const visualStatus = getVisualEvidenceStatus(cwd, {
228
+ required: visualRequirements.some((entry) => entry.required),
229
+ screens: visualRequirements.flatMap((entry) => entry.screens || []),
230
+ states: visualRequirements.flatMap((entry) => entry.states || []),
231
+ })
232
+ const closeoutRequired = (
233
+ gatePlans.every((entry) => entry.missingFiles.length === 0 && entry.templateIssues.length === 0 && entry.taskSummary.total > 0 && entry.taskSummary.open === 0 && entry.taskSummary.underSpecifiedCount === 0)
234
+ && (!verificationStatus.required || verificationStatus.status === 'valid')
235
+ && (!reviewStatus.required || reviewStatus.status === 'valid')
236
+ && (!advisorStatus.required || advisorStatus.status === 'valid')
237
+ && (!visualStatus.required || visualStatus.status === 'valid')
238
+ )
239
+ const closeoutStatus = getCloseoutEvidenceStatus(cwd, {
240
+ required: closeoutRequired,
241
+ })
242
+
243
+ const issues = collectGateIssues(gatePlans, verificationStatus, reviewStatus, advisorStatus, visualStatus, closeoutStatus)
244
+ if (issues.length === 0) {
245
+ process.stdout.write(JSON.stringify({ suppressOutput: true }))
246
+ return
247
+ }
248
+
249
+ process.stdout.write(JSON.stringify({
250
+ decision: 'block',
251
+ reason: buildBlockReason(issues, recommendation, buildDeliveryGateHint(cwd)),
252
+ suppressOutput: true,
253
+ }))
254
+ }
255
+
256
+ main()
@@ -0,0 +1,122 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+
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: 'Recursive delete of critical path' },
6
+ { pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?(-[a-zA-Z]*f[a-zA-Z]*\s+)?(\/|~|\*)/, reason: 'Recursive delete of critical path' },
7
+ { pattern: /(sudo\s+)?rm\s+--recursive/, reason: 'Recursive delete (long option)' },
8
+ { pattern: /(sudo\s+)?rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\.\.?(\s|$)/, reason: 'Recursive delete of current/parent directory' },
9
+ { pattern: /git\s+push\s+(-f|--force)/, reason: 'Force push (specify branch explicitly)' },
10
+ { pattern: /git\s+reset\s+--hard/, reason: 'Hard reset (destructive operation)' },
11
+ { pattern: /DROP\s+(DATABASE|TABLE|SCHEMA)/i, reason: 'Database destruction command' },
12
+ { pattern: /TRUNCATE\s+TABLE/i, reason: 'Table truncation' },
13
+ { pattern: /chmod\s+777/, reason: 'World-writable permissions' },
14
+ { pattern: /mkfs\b/, reason: 'Filesystem format command' },
15
+ { pattern: /dd\s+.*of=\/dev\//, reason: 'Direct device write' },
16
+ { pattern: /FLUSHALL|FLUSHDB/i, reason: 'Redis data flush' },
17
+ ]
18
+
19
+ export const HIGH_RISK_COMMAND_PATTERNS = [
20
+ { pattern: /\bnpm\s+publish\b/i, reason: 'Package publish command', gate: 'post-verify' },
21
+ { pattern: /\bgh\s+release\s+create\b/i, reason: 'Release publication command', gate: 'post-verify' },
22
+ { pattern: /\bterraform\s+(apply|destroy)\b/i, reason: 'Infrastructure apply/destroy command', gate: 'post-verify' },
23
+ { pattern: /\b(kubectl|helm)\s+(apply|delete|upgrade|rollback|set|rollout)\b/i, reason: 'Cluster deployment command', gate: 'post-verify' },
24
+ { pattern: /\b(prisma|drizzle-kit|sequelize-cli|typeorm)\b.*\b(migrate|migration)\b/i, reason: 'Database migration command', gate: 'plan-first' },
25
+ { pattern: /\b(vercel|wrangler|netlify|flyctl|fly)\b.*\b(deploy|publish)\b/i, reason: 'Deployment command', gate: 'post-verify' },
26
+ ]
27
+
28
+ export const IDEA_SIDE_EFFECT_COMMAND_PATTERNS = [
29
+ /\b(git\s+(add|commit|merge|rebase|cherry-pick|push|pull|stash|restore|checkout|switch))\b/i,
30
+ /\b(npm|pnpm|yarn|bun)\s+(install|add|remove|uninstall|update|up|upgrade|publish|version)\b/i,
31
+ /\b(mkdir|md|touch|cp|copy|mv|move|ren|rename|del|erase|rm|rmdir)\b/i,
32
+ /\b(new-item|copy-item|move-item|remove-item|rename-item|set-content|add-content|out-file)\b/i,
33
+ /(^|[^\w])>>?($|[^\w])/,
34
+ ]
35
+
36
+ const SECRET_PATTERNS = [
37
+ { pattern: /AKIA[0-9A-Z]{16}/, reason: 'AWS Access Key ID detected' },
38
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, reason: 'GitHub Personal Access Token detected' },
39
+ { pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/, reason: 'GitHub Fine-grained PAT detected' },
40
+ { pattern: /sk-[a-zA-Z0-9]{20,}/, reason: 'API secret key pattern detected (sk-)' },
41
+ { pattern: /key-[a-zA-Z0-9]{20,}/, reason: 'API key pattern detected (key-)' },
42
+ { pattern: /-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----/, reason: 'Private key detected' },
43
+ { pattern: /password\s*[:=]\s*["'][^"']{4,}["']/i, reason: 'Hardcoded password detected' },
44
+ { pattern: /secret\s*[:=]\s*["'][^"']{4,}["']/i, reason: 'Hardcoded secret detected' },
45
+ { pattern: /AIza[0-9A-Za-z\-_]{35}/, reason: 'Google API Key detected' },
46
+ { pattern: /xox[bpras]-[0-9a-zA-Z\-]+/, reason: 'Slack Token detected' },
47
+ { pattern: /eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.+/=]+/, reason: 'JWT token detected' },
48
+ { pattern: /(postgres|mysql|mongodb(\+srv)?):\/\/[^:]+:[^@]+@/i, reason: 'Database connection string with credentials detected' },
49
+ { pattern: /sk_live_[a-zA-Z0-9]{24,}/, reason: 'Stripe Secret Key detected' },
50
+ { pattern: /sk-ant-[a-zA-Z0-9\-]{20,}/, reason: 'Anthropic API Key detected' },
51
+ ]
52
+
53
+ export function scanForSecrets(content) {
54
+ const warnings = []
55
+ for (const { pattern, reason } of SECRET_PATTERNS) {
56
+ if (pattern.test(content)) warnings.push(reason)
57
+ }
58
+ return warnings
59
+ }
60
+
61
+ export function scanHighRiskCommands(command) {
62
+ const warnings = []
63
+ for (const entry of HIGH_RISK_COMMAND_PATTERNS) {
64
+ if (entry.pattern.test(command)) {
65
+ warnings.push({ ...entry })
66
+ }
67
+ }
68
+ return warnings
69
+ }
70
+
71
+ export function scanUnrequestedFiles(filePath, toolName) {
72
+ if (!filePath || toolName?.toLowerCase() !== 'write') return []
73
+ const basename = filePath.split(/[/\\]/).pop() || ''
74
+ const warnings = []
75
+
76
+ const patterns = [
77
+ { pattern: /^(SUMMARY|NOTES|TODO|SCRATCH|TEMP)\.(md|txt)$/i, reason: `Unrequested file creation: ${basename}` },
78
+ {
79
+ pattern: /^README.*\.md$/i,
80
+ matches: () => filePath.replace(/\\/g, '/').split('/').length > 4,
81
+ reason: `Suspicious README creation in nested path: ${basename}`,
82
+ },
83
+ ]
84
+
85
+ for (const entry of patterns) {
86
+ if (entry.pattern.test(basename) && (!entry.matches || entry.matches())) {
87
+ warnings.push(entry.reason)
88
+ }
89
+ }
90
+ return warnings
91
+ }
92
+
93
+ export function scanDangerousPackages(content, filePath) {
94
+ const warnings = []
95
+ if (filePath.endsWith('package.json')) {
96
+ const dangerousScripts = /("(preinstall|postinstall|preuninstall)")\s*:\s*"[^"]*\b(curl|wget|bash|sh|eval|exec)\b/i
97
+ if (dangerousScripts.test(content)) {
98
+ warnings.push('Potentially dangerous lifecycle script in package.json (preinstall/postinstall with curl/wget/bash/eval)')
99
+ }
100
+ }
101
+ const unsafeInstall = /npm install\s+[^-].*--ignore-scripts\s*=\s*false|pip install\s+--trusted-host|pip install\s+http:/i
102
+ if (unsafeInstall.test(content)) {
103
+ warnings.push('Unsafe dependency installation pattern detected')
104
+ }
105
+ return warnings
106
+ }
107
+
108
+ export function scanEnvCoverage(filePath) {
109
+ if (!filePath.endsWith('.env') && !filePath.includes('.env.')) return []
110
+ let dir = dirname(filePath)
111
+ for (let i = 0; i < 10; i += 1) {
112
+ try {
113
+ const gitignore = readFileSync(join(dir, '.gitignore'), 'utf-8')
114
+ return gitignore.includes('.env') ? [] : ['.env file written but .gitignore does not contain .env pattern']
115
+ } catch {
116
+ const parent = dirname(dir)
117
+ if (parent === dir) break
118
+ dir = parent
119
+ }
120
+ }
121
+ return ['.env file written but no .gitignore found']
122
+ }