aidevops 2.52.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 (329) hide show
  1. package/.agent/AGENTS.md +614 -0
  2. package/.agent/accounts.md +65 -0
  3. package/.agent/aidevops/add-new-mcp-to-aidevops.md +456 -0
  4. package/.agent/aidevops/api-integrations.md +335 -0
  5. package/.agent/aidevops/architecture.md +510 -0
  6. package/.agent/aidevops/configs.md +274 -0
  7. package/.agent/aidevops/docs.md +244 -0
  8. package/.agent/aidevops/extension.md +311 -0
  9. package/.agent/aidevops/mcp-integrations.md +340 -0
  10. package/.agent/aidevops/mcp-troubleshooting.md +162 -0
  11. package/.agent/aidevops/memory-patterns.md +172 -0
  12. package/.agent/aidevops/providers.md +217 -0
  13. package/.agent/aidevops/recommendations.md +321 -0
  14. package/.agent/aidevops/requirements.md +301 -0
  15. package/.agent/aidevops/resources.md +214 -0
  16. package/.agent/aidevops/security-requirements.md +174 -0
  17. package/.agent/aidevops/security.md +350 -0
  18. package/.agent/aidevops/service-links.md +400 -0
  19. package/.agent/aidevops/services.md +357 -0
  20. package/.agent/aidevops/setup.md +153 -0
  21. package/.agent/aidevops/troubleshooting.md +389 -0
  22. package/.agent/aidevops.md +124 -0
  23. package/.agent/build-plus.md +244 -0
  24. package/.agent/content/guidelines.md +109 -0
  25. package/.agent/content.md +87 -0
  26. package/.agent/health.md +59 -0
  27. package/.agent/legal.md +59 -0
  28. package/.agent/loop-state/full-loop.local.md +16 -0
  29. package/.agent/loop-state/ralph-loop.local.md +10 -0
  30. package/.agent/marketing.md +440 -0
  31. package/.agent/memory/README.md +260 -0
  32. package/.agent/onboarding.md +796 -0
  33. package/.agent/plan-plus.md +245 -0
  34. package/.agent/research.md +100 -0
  35. package/.agent/sales.md +333 -0
  36. package/.agent/scripts/101domains-helper.sh +701 -0
  37. package/.agent/scripts/add-missing-returns.sh +140 -0
  38. package/.agent/scripts/agent-browser-helper.sh +311 -0
  39. package/.agent/scripts/agno-setup.sh +712 -0
  40. package/.agent/scripts/ahrefs-mcp-wrapper.js +168 -0
  41. package/.agent/scripts/aidevops-update-check.sh +71 -0
  42. package/.agent/scripts/ampcode-cli.sh +522 -0
  43. package/.agent/scripts/auto-version-bump.sh +156 -0
  44. package/.agent/scripts/autogen-helper.sh +512 -0
  45. package/.agent/scripts/beads-sync-helper.sh +596 -0
  46. package/.agent/scripts/closte-helper.sh +5 -0
  47. package/.agent/scripts/cloudron-helper.sh +321 -0
  48. package/.agent/scripts/codacy-cli-chunked.sh +581 -0
  49. package/.agent/scripts/codacy-cli.sh +442 -0
  50. package/.agent/scripts/code-audit-helper.sh +5 -0
  51. package/.agent/scripts/coderabbit-cli.sh +417 -0
  52. package/.agent/scripts/coderabbit-pro-analysis.sh +238 -0
  53. package/.agent/scripts/commands/code-simplifier.md +86 -0
  54. package/.agent/scripts/commands/full-loop.md +246 -0
  55. package/.agent/scripts/commands/postflight-loop.md +103 -0
  56. package/.agent/scripts/commands/recall.md +182 -0
  57. package/.agent/scripts/commands/remember.md +132 -0
  58. package/.agent/scripts/commands/save-todo.md +175 -0
  59. package/.agent/scripts/commands/session-review.md +154 -0
  60. package/.agent/scripts/comprehensive-quality-fix.sh +106 -0
  61. package/.agent/scripts/context-builder-helper.sh +522 -0
  62. package/.agent/scripts/coolify-cli-helper.sh +674 -0
  63. package/.agent/scripts/coolify-helper.sh +380 -0
  64. package/.agent/scripts/crawl4ai-examples.sh +401 -0
  65. package/.agent/scripts/crawl4ai-helper.sh +1078 -0
  66. package/.agent/scripts/crewai-helper.sh +681 -0
  67. package/.agent/scripts/dev-browser-helper.sh +513 -0
  68. package/.agent/scripts/dns-helper.sh +396 -0
  69. package/.agent/scripts/domain-research-helper.sh +917 -0
  70. package/.agent/scripts/dspy-helper.sh +285 -0
  71. package/.agent/scripts/dspyground-helper.sh +291 -0
  72. package/.agent/scripts/eeat-score-helper.sh +1242 -0
  73. package/.agent/scripts/efficient-return-fix.sh +92 -0
  74. package/.agent/scripts/extract-opencode-prompts.sh +128 -0
  75. package/.agent/scripts/find-missing-returns.sh +113 -0
  76. package/.agent/scripts/fix-auth-headers.sh +104 -0
  77. package/.agent/scripts/fix-common-strings.sh +254 -0
  78. package/.agent/scripts/fix-content-type.sh +100 -0
  79. package/.agent/scripts/fix-error-messages.sh +130 -0
  80. package/.agent/scripts/fix-misplaced-returns.sh +74 -0
  81. package/.agent/scripts/fix-remaining-literals.sh +152 -0
  82. package/.agent/scripts/fix-return-statements.sh +41 -0
  83. package/.agent/scripts/fix-s131-default-cases.sh +249 -0
  84. package/.agent/scripts/fix-sc2155-simple.sh +102 -0
  85. package/.agent/scripts/fix-shellcheck-critical.sh +187 -0
  86. package/.agent/scripts/fix-string-literals.sh +273 -0
  87. package/.agent/scripts/full-loop-helper.sh +773 -0
  88. package/.agent/scripts/generate-opencode-agents.sh +497 -0
  89. package/.agent/scripts/generate-opencode-commands.sh +1629 -0
  90. package/.agent/scripts/generate-skills.sh +366 -0
  91. package/.agent/scripts/git-platforms-helper.sh +640 -0
  92. package/.agent/scripts/gitea-cli-helper.sh +743 -0
  93. package/.agent/scripts/github-cli-helper.sh +702 -0
  94. package/.agent/scripts/gitlab-cli-helper.sh +682 -0
  95. package/.agent/scripts/gsc-add-user-helper.sh +325 -0
  96. package/.agent/scripts/gsc-sitemap-helper.sh +678 -0
  97. package/.agent/scripts/hetzner-helper.sh +485 -0
  98. package/.agent/scripts/hostinger-helper.sh +229 -0
  99. package/.agent/scripts/keyword-research-helper.sh +1815 -0
  100. package/.agent/scripts/langflow-helper.sh +544 -0
  101. package/.agent/scripts/linkedin-automation.py +241 -0
  102. package/.agent/scripts/linter-manager.sh +599 -0
  103. package/.agent/scripts/linters-local.sh +434 -0
  104. package/.agent/scripts/list-keys-helper.sh +488 -0
  105. package/.agent/scripts/local-browser-automation.py +339 -0
  106. package/.agent/scripts/localhost-helper.sh +744 -0
  107. package/.agent/scripts/loop-common.sh +806 -0
  108. package/.agent/scripts/mainwp-helper.sh +728 -0
  109. package/.agent/scripts/markdown-formatter.sh +338 -0
  110. package/.agent/scripts/markdown-lint-fix.sh +311 -0
  111. package/.agent/scripts/mass-fix-returns.sh +58 -0
  112. package/.agent/scripts/mcp-diagnose.sh +167 -0
  113. package/.agent/scripts/mcp-inspector-helper.sh +449 -0
  114. package/.agent/scripts/memory-helper.sh +650 -0
  115. package/.agent/scripts/monitor-code-review.sh +255 -0
  116. package/.agent/scripts/onboarding-helper.sh +706 -0
  117. package/.agent/scripts/opencode-github-setup-helper.sh +797 -0
  118. package/.agent/scripts/opencode-test-helper.sh +213 -0
  119. package/.agent/scripts/pagespeed-helper.sh +464 -0
  120. package/.agent/scripts/pandoc-helper.sh +362 -0
  121. package/.agent/scripts/postflight-check.sh +555 -0
  122. package/.agent/scripts/pre-commit-hook.sh +259 -0
  123. package/.agent/scripts/pre-edit-check.sh +169 -0
  124. package/.agent/scripts/qlty-cli.sh +356 -0
  125. package/.agent/scripts/quality-cli-manager.sh +525 -0
  126. package/.agent/scripts/quality-feedback-helper.sh +462 -0
  127. package/.agent/scripts/quality-fix.sh +263 -0
  128. package/.agent/scripts/quality-loop-helper.sh +1108 -0
  129. package/.agent/scripts/ralph-loop-helper.sh +836 -0
  130. package/.agent/scripts/ralph-upstream-check.sh +341 -0
  131. package/.agent/scripts/secretlint-helper.sh +847 -0
  132. package/.agent/scripts/servers-helper.sh +241 -0
  133. package/.agent/scripts/ses-helper.sh +619 -0
  134. package/.agent/scripts/session-review-helper.sh +404 -0
  135. package/.agent/scripts/setup-linters-wizard.sh +379 -0
  136. package/.agent/scripts/setup-local-api-keys.sh +330 -0
  137. package/.agent/scripts/setup-mcp-integrations.sh +472 -0
  138. package/.agent/scripts/shared-constants.sh +246 -0
  139. package/.agent/scripts/site-crawler-helper.sh +1487 -0
  140. package/.agent/scripts/snyk-helper.sh +940 -0
  141. package/.agent/scripts/sonarcloud-autofix.sh +193 -0
  142. package/.agent/scripts/sonarcloud-cli.sh +191 -0
  143. package/.agent/scripts/sonarscanner-cli.sh +455 -0
  144. package/.agent/scripts/spaceship-helper.sh +747 -0
  145. package/.agent/scripts/stagehand-helper.sh +321 -0
  146. package/.agent/scripts/stagehand-python-helper.sh +321 -0
  147. package/.agent/scripts/stagehand-python-setup.sh +441 -0
  148. package/.agent/scripts/stagehand-setup.sh +439 -0
  149. package/.agent/scripts/system-cleanup.sh +340 -0
  150. package/.agent/scripts/terminal-title-helper.sh +388 -0
  151. package/.agent/scripts/terminal-title-setup.sh +549 -0
  152. package/.agent/scripts/test-stagehand-both-integration.sh +317 -0
  153. package/.agent/scripts/test-stagehand-integration.sh +309 -0
  154. package/.agent/scripts/test-stagehand-python-integration.sh +341 -0
  155. package/.agent/scripts/todo-ready.sh +263 -0
  156. package/.agent/scripts/tool-version-check.sh +362 -0
  157. package/.agent/scripts/toon-helper.sh +469 -0
  158. package/.agent/scripts/twilio-helper.sh +917 -0
  159. package/.agent/scripts/updown-helper.sh +279 -0
  160. package/.agent/scripts/validate-mcp-integrations.sh +250 -0
  161. package/.agent/scripts/validate-version-consistency.sh +131 -0
  162. package/.agent/scripts/vaultwarden-helper.sh +597 -0
  163. package/.agent/scripts/vercel-cli-helper.sh +816 -0
  164. package/.agent/scripts/verify-mirrors.sh +169 -0
  165. package/.agent/scripts/version-manager.sh +831 -0
  166. package/.agent/scripts/webhosting-helper.sh +471 -0
  167. package/.agent/scripts/webhosting-verify.sh +238 -0
  168. package/.agent/scripts/wordpress-mcp-helper.sh +508 -0
  169. package/.agent/scripts/worktree-helper.sh +595 -0
  170. package/.agent/scripts/worktree-sessions.sh +577 -0
  171. package/.agent/seo/dataforseo.md +215 -0
  172. package/.agent/seo/domain-research.md +532 -0
  173. package/.agent/seo/eeat-score.md +659 -0
  174. package/.agent/seo/google-search-console.md +366 -0
  175. package/.agent/seo/gsc-sitemaps.md +282 -0
  176. package/.agent/seo/keyword-research.md +521 -0
  177. package/.agent/seo/serper.md +278 -0
  178. package/.agent/seo/site-crawler.md +387 -0
  179. package/.agent/seo.md +236 -0
  180. package/.agent/services/accounting/quickfile.md +159 -0
  181. package/.agent/services/communications/telfon.md +470 -0
  182. package/.agent/services/communications/twilio.md +569 -0
  183. package/.agent/services/crm/fluentcrm.md +449 -0
  184. package/.agent/services/email/ses.md +399 -0
  185. package/.agent/services/hosting/101domains.md +378 -0
  186. package/.agent/services/hosting/closte.md +177 -0
  187. package/.agent/services/hosting/cloudflare.md +251 -0
  188. package/.agent/services/hosting/cloudron.md +478 -0
  189. package/.agent/services/hosting/dns-providers.md +335 -0
  190. package/.agent/services/hosting/domain-purchasing.md +344 -0
  191. package/.agent/services/hosting/hetzner.md +327 -0
  192. package/.agent/services/hosting/hostinger.md +287 -0
  193. package/.agent/services/hosting/localhost.md +419 -0
  194. package/.agent/services/hosting/spaceship.md +353 -0
  195. package/.agent/services/hosting/webhosting.md +330 -0
  196. package/.agent/social-media.md +69 -0
  197. package/.agent/templates/plans-template.md +114 -0
  198. package/.agent/templates/prd-template.md +129 -0
  199. package/.agent/templates/tasks-template.md +108 -0
  200. package/.agent/templates/todo-template.md +89 -0
  201. package/.agent/tools/ai-assistants/agno.md +471 -0
  202. package/.agent/tools/ai-assistants/capsolver.md +326 -0
  203. package/.agent/tools/ai-assistants/configuration.md +221 -0
  204. package/.agent/tools/ai-assistants/overview.md +209 -0
  205. package/.agent/tools/ai-assistants/status.md +171 -0
  206. package/.agent/tools/ai-assistants/windsurf.md +193 -0
  207. package/.agent/tools/ai-orchestration/autogen.md +406 -0
  208. package/.agent/tools/ai-orchestration/crewai.md +445 -0
  209. package/.agent/tools/ai-orchestration/langflow.md +405 -0
  210. package/.agent/tools/ai-orchestration/openprose.md +487 -0
  211. package/.agent/tools/ai-orchestration/overview.md +362 -0
  212. package/.agent/tools/ai-orchestration/packaging.md +647 -0
  213. package/.agent/tools/browser/agent-browser.md +464 -0
  214. package/.agent/tools/browser/browser-automation.md +400 -0
  215. package/.agent/tools/browser/chrome-devtools.md +282 -0
  216. package/.agent/tools/browser/crawl4ai-integration.md +422 -0
  217. package/.agent/tools/browser/crawl4ai-resources.md +277 -0
  218. package/.agent/tools/browser/crawl4ai-usage.md +416 -0
  219. package/.agent/tools/browser/crawl4ai.md +585 -0
  220. package/.agent/tools/browser/dev-browser.md +341 -0
  221. package/.agent/tools/browser/pagespeed.md +260 -0
  222. package/.agent/tools/browser/playwright.md +266 -0
  223. package/.agent/tools/browser/playwriter.md +310 -0
  224. package/.agent/tools/browser/stagehand-examples.md +456 -0
  225. package/.agent/tools/browser/stagehand-python.md +483 -0
  226. package/.agent/tools/browser/stagehand.md +421 -0
  227. package/.agent/tools/build-agent/agent-review.md +224 -0
  228. package/.agent/tools/build-agent/build-agent.md +784 -0
  229. package/.agent/tools/build-mcp/aidevops-plugin.md +476 -0
  230. package/.agent/tools/build-mcp/api-wrapper.md +445 -0
  231. package/.agent/tools/build-mcp/build-mcp.md +240 -0
  232. package/.agent/tools/build-mcp/deployment.md +401 -0
  233. package/.agent/tools/build-mcp/server-patterns.md +632 -0
  234. package/.agent/tools/build-mcp/transports.md +366 -0
  235. package/.agent/tools/code-review/auditing.md +383 -0
  236. package/.agent/tools/code-review/automation.md +219 -0
  237. package/.agent/tools/code-review/best-practices.md +203 -0
  238. package/.agent/tools/code-review/codacy.md +151 -0
  239. package/.agent/tools/code-review/code-simplifier.md +174 -0
  240. package/.agent/tools/code-review/code-standards.md +309 -0
  241. package/.agent/tools/code-review/coderabbit.md +101 -0
  242. package/.agent/tools/code-review/management.md +155 -0
  243. package/.agent/tools/code-review/qlty.md +248 -0
  244. package/.agent/tools/code-review/secretlint.md +565 -0
  245. package/.agent/tools/code-review/setup.md +250 -0
  246. package/.agent/tools/code-review/snyk.md +563 -0
  247. package/.agent/tools/code-review/tools.md +230 -0
  248. package/.agent/tools/content/summarize.md +353 -0
  249. package/.agent/tools/context/augment-context-engine.md +468 -0
  250. package/.agent/tools/context/context-builder-agent.md +76 -0
  251. package/.agent/tools/context/context-builder.md +375 -0
  252. package/.agent/tools/context/context7.md +371 -0
  253. package/.agent/tools/context/dspy.md +302 -0
  254. package/.agent/tools/context/dspyground.md +374 -0
  255. package/.agent/tools/context/llm-tldr.md +219 -0
  256. package/.agent/tools/context/osgrep.md +488 -0
  257. package/.agent/tools/context/prompt-optimization.md +338 -0
  258. package/.agent/tools/context/toon.md +292 -0
  259. package/.agent/tools/conversion/pandoc.md +304 -0
  260. package/.agent/tools/credentials/api-key-management.md +154 -0
  261. package/.agent/tools/credentials/api-key-setup.md +224 -0
  262. package/.agent/tools/credentials/environment-variables.md +180 -0
  263. package/.agent/tools/credentials/vaultwarden.md +382 -0
  264. package/.agent/tools/data-extraction/outscraper.md +974 -0
  265. package/.agent/tools/deployment/coolify-cli.md +388 -0
  266. package/.agent/tools/deployment/coolify-setup.md +353 -0
  267. package/.agent/tools/deployment/coolify.md +345 -0
  268. package/.agent/tools/deployment/vercel.md +390 -0
  269. package/.agent/tools/git/authentication.md +132 -0
  270. package/.agent/tools/git/gitea-cli.md +193 -0
  271. package/.agent/tools/git/github-actions.md +207 -0
  272. package/.agent/tools/git/github-cli.md +223 -0
  273. package/.agent/tools/git/gitlab-cli.md +190 -0
  274. package/.agent/tools/git/opencode-github-security.md +350 -0
  275. package/.agent/tools/git/opencode-github.md +328 -0
  276. package/.agent/tools/git/opencode-gitlab.md +252 -0
  277. package/.agent/tools/git/security.md +196 -0
  278. package/.agent/tools/git.md +207 -0
  279. package/.agent/tools/opencode/oh-my-opencode.md +375 -0
  280. package/.agent/tools/opencode/opencode-anthropic-auth.md +446 -0
  281. package/.agent/tools/opencode/opencode.md +651 -0
  282. package/.agent/tools/social-media/bird.md +437 -0
  283. package/.agent/tools/task-management/beads.md +336 -0
  284. package/.agent/tools/terminal/terminal-title.md +251 -0
  285. package/.agent/tools/ui/shadcn.md +196 -0
  286. package/.agent/tools/ui/ui-skills.md +115 -0
  287. package/.agent/tools/wordpress/localwp.md +311 -0
  288. package/.agent/tools/wordpress/mainwp.md +391 -0
  289. package/.agent/tools/wordpress/scf.md +527 -0
  290. package/.agent/tools/wordpress/wp-admin.md +729 -0
  291. package/.agent/tools/wordpress/wp-dev.md +940 -0
  292. package/.agent/tools/wordpress/wp-preferred.md +398 -0
  293. package/.agent/tools/wordpress.md +95 -0
  294. package/.agent/workflows/branch/bugfix.md +63 -0
  295. package/.agent/workflows/branch/chore.md +95 -0
  296. package/.agent/workflows/branch/experiment.md +115 -0
  297. package/.agent/workflows/branch/feature.md +59 -0
  298. package/.agent/workflows/branch/hotfix.md +98 -0
  299. package/.agent/workflows/branch/refactor.md +92 -0
  300. package/.agent/workflows/branch/release.md +96 -0
  301. package/.agent/workflows/branch.md +347 -0
  302. package/.agent/workflows/bug-fixing.md +267 -0
  303. package/.agent/workflows/changelog.md +129 -0
  304. package/.agent/workflows/code-audit-remote.md +279 -0
  305. package/.agent/workflows/conversation-starter.md +69 -0
  306. package/.agent/workflows/error-feedback.md +578 -0
  307. package/.agent/workflows/feature-development.md +355 -0
  308. package/.agent/workflows/git-workflow.md +702 -0
  309. package/.agent/workflows/multi-repo-workspace.md +268 -0
  310. package/.agent/workflows/plans.md +709 -0
  311. package/.agent/workflows/postflight.md +604 -0
  312. package/.agent/workflows/pr.md +571 -0
  313. package/.agent/workflows/preflight.md +278 -0
  314. package/.agent/workflows/ralph-loop.md +773 -0
  315. package/.agent/workflows/release.md +498 -0
  316. package/.agent/workflows/session-manager.md +254 -0
  317. package/.agent/workflows/session-review.md +311 -0
  318. package/.agent/workflows/sql-migrations.md +631 -0
  319. package/.agent/workflows/version-bump.md +283 -0
  320. package/.agent/workflows/wiki-update.md +333 -0
  321. package/.agent/workflows/worktree.md +477 -0
  322. package/LICENSE +21 -0
  323. package/README.md +1446 -0
  324. package/VERSION +1 -0
  325. package/aidevops.sh +1746 -0
  326. package/bin/aidevops +21 -0
  327. package/package.json +75 -0
  328. package/scripts/npm-postinstall.js +60 -0
  329. package/setup.sh +2366 -0
@@ -0,0 +1,650 @@
1
+ #!/usr/bin/env bash
2
+ # memory-helper.sh - Lightweight memory system for aidevops
3
+ # Uses SQLite FTS5 for fast text search without external dependencies
4
+ #
5
+ # Usage:
6
+ # memory-helper.sh store --content "learning" [--type TYPE] [--tags "a,b"] [--session-id ID]
7
+ # memory-helper.sh recall --query "search terms" [--limit 5] [--type TYPE] [--max-age-days 30]
8
+ # memory-helper.sh stats # Show memory statistics
9
+ # memory-helper.sh prune [--older-than-days 90] [--dry-run] # Remove stale entries
10
+ # memory-helper.sh validate # Check for stale/low-quality entries
11
+ # memory-helper.sh export [--format json|toon] # Export all memories
12
+ #
13
+ # Staleness Prevention:
14
+ # - Entries have created_at and last_accessed_at timestamps
15
+ # - Recall updates last_accessed_at (frequently used = valuable)
16
+ # - Prune removes entries older than threshold AND never accessed
17
+ # - Validate warns about potentially stale entries
18
+
19
+ set -euo pipefail
20
+
21
+ # Configuration
22
+ readonly MEMORY_DIR="${AIDEVOPS_MEMORY_DIR:-$HOME/.aidevops/.agent-workspace/memory}"
23
+ readonly MEMORY_DB="$MEMORY_DIR/memory.db"
24
+ readonly DEFAULT_MAX_AGE_DAYS=90
25
+ readonly STALE_WARNING_DAYS=60
26
+
27
+ # Valid learning types (matches documentation and Continuous-Claude-v3)
28
+ readonly VALID_TYPES="WORKING_SOLUTION FAILED_APPROACH CODEBASE_PATTERN USER_PREFERENCE TOOL_CONFIG DECISION CONTEXT ARCHITECTURAL_DECISION ERROR_FIX OPEN_THREAD"
29
+
30
+ # Colors for output
31
+ readonly RED='\033[0;31m'
32
+ readonly GREEN='\033[0;32m'
33
+ readonly YELLOW='\033[0;33m'
34
+ readonly BLUE='\033[0;34m'
35
+ readonly NC='\033[0m' # No Color
36
+
37
+ #######################################
38
+ # Print colored message
39
+ #######################################
40
+ log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
41
+ log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
42
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
43
+ log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
44
+
45
+ #######################################
46
+ # Format JSON results as text (jq fallback)
47
+ # Uses jq if available, otherwise basic parsing
48
+ #######################################
49
+ format_results_text() {
50
+ local input
51
+ input=$(cat)
52
+
53
+ if [[ -z "$input" || "$input" == "[]" ]]; then
54
+ return 0
55
+ fi
56
+
57
+ if command -v jq &>/dev/null; then
58
+ echo "$input" | jq -r '.[] | "[\(.type)] (\(.confidence)) - Score: \(.score // "N/A" | tostring | .[0:6])\n \(.content)\n Tags: \(.tags)\n Created: \(.created_at) | Accessed: \(.access_count)x\n"' 2>/dev/null
59
+ else
60
+ # Basic fallback without jq - parse JSON manually
61
+ echo "$input" | sed 's/},{/}\n{/g' | while read -r line; do
62
+ local type content tags created access_count
63
+ type=$(echo "$line" | sed -n 's/.*"type":"\([^"]*\)".*/\1/p')
64
+ content=$(echo "$line" | sed -n 's/.*"content":"\([^"]*\)".*/\1/p' | head -c 100)
65
+ tags=$(echo "$line" | sed -n 's/.*"tags":"\([^"]*\)".*/\1/p')
66
+ created=$(echo "$line" | sed -n 's/.*"created_at":"\([^"]*\)".*/\1/p')
67
+ access_count=$(echo "$line" | sed -n 's/.*"access_count":\([0-9]*\).*/\1/p')
68
+ [[ -n "$type" ]] && echo "[$type] $content..."
69
+ [[ -n "$tags" ]] && echo " Tags: $tags | Created: $created | Accessed: ${access_count:-0}x"
70
+ echo ""
71
+ done
72
+ fi
73
+ }
74
+
75
+ #######################################
76
+ # Extract IDs from JSON (jq fallback)
77
+ #######################################
78
+ extract_ids_from_json() {
79
+ local input
80
+ input=$(cat)
81
+
82
+ if command -v jq &>/dev/null; then
83
+ echo "$input" | jq -r '.[].id' 2>/dev/null
84
+ else
85
+ # Basic fallback - extract id values
86
+ echo "$input" | grep -o '"id":"[^"]*"' | sed 's/"id":"//g; s/"//g'
87
+ fi
88
+ return 0
89
+ }
90
+
91
+ #######################################
92
+ # Initialize database with FTS5 schema
93
+ #######################################
94
+ init_db() {
95
+ mkdir -p "$MEMORY_DIR"
96
+
97
+ if [[ ! -f "$MEMORY_DB" ]]; then
98
+ log_info "Creating memory database at $MEMORY_DB"
99
+
100
+ sqlite3 "$MEMORY_DB" <<'EOF'
101
+ -- FTS5 virtual table for searchable content
102
+ CREATE VIRTUAL TABLE IF NOT EXISTS learnings USING fts5(
103
+ id UNINDEXED,
104
+ session_id UNINDEXED,
105
+ content,
106
+ type,
107
+ tags,
108
+ confidence UNINDEXED,
109
+ created_at UNINDEXED,
110
+ project_path UNINDEXED,
111
+ source UNINDEXED,
112
+ tokenize='porter unicode61'
113
+ );
114
+
115
+ -- Separate table for access tracking (FTS5 doesn't support UPDATE)
116
+ CREATE TABLE IF NOT EXISTS learning_access (
117
+ id TEXT PRIMARY KEY,
118
+ last_accessed_at TEXT,
119
+ access_count INTEGER DEFAULT 0
120
+ );
121
+ EOF
122
+ log_success "Database initialized"
123
+ fi
124
+ return 0
125
+ }
126
+
127
+ #######################################
128
+ # Generate unique ID
129
+ #######################################
130
+ generate_id() {
131
+ # Use timestamp + random for uniqueness
132
+ echo "mem_$(date +%Y%m%d%H%M%S)_$(head -c 4 /dev/urandom | xxd -p)"
133
+ return 0
134
+ }
135
+
136
+ #######################################
137
+ # Store a learning
138
+ #######################################
139
+ cmd_store() {
140
+ local content=""
141
+ local type="WORKING_SOLUTION"
142
+ local tags=""
143
+ local confidence="medium"
144
+ local session_id=""
145
+ local project_path=""
146
+ local source="manual"
147
+
148
+ while [[ $# -gt 0 ]]; do
149
+ case "$1" in
150
+ --content) content="$2"; shift 2 ;;
151
+ --type) type="$2"; shift 2 ;;
152
+ --tags) tags="$2"; shift 2 ;;
153
+ --confidence) confidence="$2"; shift 2 ;;
154
+ --session-id) session_id="$2"; shift 2 ;;
155
+ --project) project_path="$2"; shift 2 ;;
156
+ --source) source="$2"; shift 2 ;;
157
+ *)
158
+ # Allow content as positional argument
159
+ if [[ -z "$content" ]]; then
160
+ content="$1"
161
+ fi
162
+ shift ;;
163
+ esac
164
+ done
165
+
166
+ # Validate required fields
167
+ if [[ -z "$content" ]]; then
168
+ log_error "Content is required. Use --content \"your learning\""
169
+ return 1
170
+ fi
171
+
172
+ # Validate type
173
+ local type_pattern=" $type "
174
+ if [[ ! " $VALID_TYPES " =~ $type_pattern ]]; then
175
+ log_error "Invalid type: $type"
176
+ log_error "Valid types: $VALID_TYPES"
177
+ return 1
178
+ fi
179
+
180
+ # Validate confidence
181
+ if [[ ! "$confidence" =~ ^(high|medium|low)$ ]]; then
182
+ log_error "Invalid confidence: $confidence (use high, medium, or low)"
183
+ return 1
184
+ fi
185
+
186
+ # Generate session_id if not provided
187
+ if [[ -z "$session_id" ]]; then
188
+ session_id="session_$(date +%Y%m%d_%H%M%S)"
189
+ fi
190
+
191
+ # Get current project path if not provided
192
+ if [[ -z "$project_path" ]]; then
193
+ project_path="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
194
+ fi
195
+
196
+ init_db
197
+
198
+ local id
199
+ id=$(generate_id)
200
+ local created_at
201
+ created_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
202
+
203
+ # Escape single quotes for SQL
204
+ local escaped_content="${content//\'/\'\'}"
205
+ local escaped_tags="${tags//\'/\'\'}"
206
+ local escaped_project="${project_path//\'/\'\'}"
207
+
208
+ sqlite3 "$MEMORY_DB" <<EOF
209
+ INSERT INTO learnings (id, session_id, content, type, tags, confidence, created_at, project_path, source)
210
+ VALUES ('$id', '$session_id', '$escaped_content', '$type', '$escaped_tags', '$confidence', '$created_at', '$escaped_project', '$source');
211
+ EOF
212
+
213
+ log_success "Stored learning: $id"
214
+ echo "$id"
215
+ }
216
+
217
+ #######################################
218
+ # Recall learnings with search
219
+ #######################################
220
+ cmd_recall() {
221
+ local query=""
222
+ local limit=5
223
+ local type_filter=""
224
+ local max_age_days=""
225
+ local project_filter=""
226
+ local format="text"
227
+ local recent_mode=false
228
+
229
+ while [[ $# -gt 0 ]]; do
230
+ case "$1" in
231
+ --query|-q) query="$2"; shift 2 ;;
232
+ --limit|-l) limit="$2"; shift 2 ;;
233
+ --type|-t) type_filter="$2"; shift 2 ;;
234
+ --max-age-days) max_age_days="$2"; shift 2 ;;
235
+ --project|-p) project_filter="$2"; shift 2 ;;
236
+ --recent) recent_mode=true; limit="${2:-10}"; shift; [[ "${1:-}" =~ ^[0-9]+$ ]] && shift ;;
237
+ --format) format="$2"; shift 2 ;;
238
+ --json) format="json"; shift ;;
239
+ --stats) cmd_stats; return 0 ;;
240
+ *)
241
+ # Allow query as positional argument
242
+ if [[ -z "$query" ]]; then
243
+ query="$1"
244
+ fi
245
+ shift ;;
246
+ esac
247
+ done
248
+
249
+ init_db
250
+
251
+ # Handle --recent mode (no query required)
252
+ if [[ "$recent_mode" == true ]]; then
253
+ local results
254
+ results=$(sqlite3 -json "$MEMORY_DB" "SELECT l.id, l.content, l.type, l.tags, l.confidence, l.created_at, COALESCE(a.last_accessed_at, '') as last_accessed_at, COALESCE(a.access_count, 0) as access_count FROM learnings l LEFT JOIN learning_access a ON l.id = a.id ORDER BY l.created_at DESC LIMIT $limit;")
255
+ if [[ "$format" == "json" ]]; then
256
+ echo "$results"
257
+ else
258
+ echo ""
259
+ echo "=== Recent Memories (last $limit) ==="
260
+ echo ""
261
+ echo "$results" | format_results_text
262
+ fi
263
+ return 0
264
+ fi
265
+
266
+ if [[ -z "$query" ]]; then
267
+ log_error "Query is required. Use --query \"search terms\" or --recent"
268
+ return 1
269
+ fi
270
+
271
+ # Escape query for FTS5 - escape both single and double quotes
272
+ local escaped_query="${query//\'/\'\'}"
273
+ escaped_query="${escaped_query//\"/\"\"}"
274
+
275
+ # Build filters with validation
276
+ local extra_filters=""
277
+ if [[ -n "$type_filter" ]]; then
278
+ # Validate type to prevent SQL injection
279
+ local type_pattern=" $type_filter "
280
+ if [[ ! " $VALID_TYPES " =~ $type_pattern ]]; then
281
+ log_error "Invalid type: $type_filter"
282
+ log_error "Valid types: $VALID_TYPES"
283
+ return 1
284
+ fi
285
+ extra_filters="$extra_filters AND type = '$type_filter'"
286
+ fi
287
+ if [[ -n "$max_age_days" ]]; then
288
+ # Validate max_age_days is a positive integer
289
+ if ! [[ "$max_age_days" =~ ^[0-9]+$ ]]; then
290
+ log_error "--max-age-days must be a positive integer"
291
+ return 1
292
+ fi
293
+ extra_filters="$extra_filters AND created_at >= datetime('now', '-$max_age_days days')"
294
+ fi
295
+ if [[ -n "$project_filter" ]]; then
296
+ local escaped_project="${project_filter//\'/\'\'}"
297
+ extra_filters="$extra_filters AND project_path LIKE '%$escaped_project%'"
298
+ fi
299
+
300
+ # Search using FTS5 with BM25 ranking
301
+ # Note: FTS5 tables require special handling - can't use table alias in bm25()
302
+ local results
303
+ results=$(sqlite3 -json "$MEMORY_DB" <<EOF
304
+ SELECT
305
+ learnings.id,
306
+ learnings.content,
307
+ learnings.type,
308
+ learnings.tags,
309
+ learnings.confidence,
310
+ learnings.created_at,
311
+ COALESCE(learning_access.last_accessed_at, '') as last_accessed_at,
312
+ COALESCE(learning_access.access_count, 0) as access_count,
313
+ bm25(learnings) as score
314
+ FROM learnings
315
+ LEFT JOIN learning_access ON learnings.id = learning_access.id
316
+ WHERE learnings MATCH '$escaped_query' $extra_filters
317
+ ORDER BY score
318
+ LIMIT $limit;
319
+ EOF
320
+ )
321
+
322
+ # Update access tracking for returned results (prevents staleness)
323
+ if [[ -n "$results" && "$results" != "[]" ]]; then
324
+ local ids
325
+ ids=$(echo "$results" | extract_ids_from_json)
326
+ if [[ -n "$ids" ]]; then
327
+ while IFS= read -r id; do
328
+ [[ -z "$id" ]] && continue
329
+ sqlite3 "$MEMORY_DB" <<EOF
330
+ INSERT INTO learning_access (id, last_accessed_at, access_count)
331
+ VALUES ('$id', datetime('now'), 1)
332
+ ON CONFLICT(id) DO UPDATE SET
333
+ last_accessed_at = datetime('now'),
334
+ access_count = access_count + 1;
335
+ EOF
336
+ done <<< "$ids"
337
+ fi
338
+ fi
339
+
340
+ # Output based on format
341
+ if [[ "$format" == "json" ]]; then
342
+ echo "$results"
343
+ else
344
+ if [[ -z "$results" || "$results" == "[]" ]]; then
345
+ log_warn "No results found for: $query"
346
+ return 0
347
+ fi
348
+
349
+ echo ""
350
+ echo "=== Memory Recall: \"$query\" ==="
351
+ echo ""
352
+
353
+ echo "$results" | format_results_text
354
+ fi
355
+ }
356
+
357
+ #######################################
358
+ # Show memory statistics
359
+ #######################################
360
+ cmd_stats() {
361
+ init_db
362
+
363
+ echo ""
364
+ echo "=== Memory Statistics ==="
365
+ echo ""
366
+
367
+ sqlite3 "$MEMORY_DB" <<'EOF'
368
+ SELECT 'Total learnings' as metric, COUNT(*) as value FROM learnings
369
+ UNION ALL
370
+ SELECT 'By type: ' || type, COUNT(*) FROM learnings GROUP BY type
371
+ UNION ALL
372
+ SELECT 'Never accessed', COUNT(*) FROM learnings l
373
+ LEFT JOIN learning_access a ON l.id = a.id WHERE a.id IS NULL
374
+ UNION ALL
375
+ SELECT 'High confidence', COUNT(*) FROM learnings WHERE confidence = 'high';
376
+ EOF
377
+
378
+ echo ""
379
+
380
+ # Show age distribution
381
+ echo "Age distribution:"
382
+ sqlite3 "$MEMORY_DB" <<'EOF'
383
+ SELECT
384
+ CASE
385
+ WHEN created_at >= datetime('now', '-7 days') THEN ' Last 7 days'
386
+ WHEN created_at >= datetime('now', '-30 days') THEN ' Last 30 days'
387
+ WHEN created_at >= datetime('now', '-90 days') THEN ' Last 90 days'
388
+ ELSE ' Older than 90 days'
389
+ END as age_bucket,
390
+ COUNT(*) as count
391
+ FROM learnings
392
+ GROUP BY 1
393
+ ORDER BY 1;
394
+ EOF
395
+ return 0
396
+ }
397
+
398
+ #######################################
399
+ # Validate and warn about stale entries
400
+ #######################################
401
+ cmd_validate() {
402
+ init_db
403
+
404
+ echo ""
405
+ echo "=== Memory Validation ==="
406
+ echo ""
407
+
408
+ # Check for stale entries (old + never accessed)
409
+ local stale_count
410
+ stale_count=$(sqlite3 "$MEMORY_DB" "SELECT COUNT(*) FROM learnings l LEFT JOIN learning_access a ON l.id = a.id WHERE l.created_at < datetime('now', '-$STALE_WARNING_DAYS days') AND a.id IS NULL;")
411
+
412
+ if [[ "$stale_count" -gt 0 ]]; then
413
+ log_warn "Found $stale_count potentially stale entries (>$STALE_WARNING_DAYS days old, never accessed)"
414
+ echo ""
415
+ echo "Stale entries:"
416
+ sqlite3 "$MEMORY_DB" <<EOF
417
+ SELECT l.id, l.type, substr(l.content, 1, 60) || '...' as content_preview, l.created_at
418
+ FROM learnings l
419
+ LEFT JOIN learning_access a ON l.id = a.id
420
+ WHERE l.created_at < datetime('now', '-$STALE_WARNING_DAYS days')
421
+ AND a.id IS NULL
422
+ LIMIT 10;
423
+ EOF
424
+ echo ""
425
+ echo "Run 'memory-helper.sh prune --older-than-days $STALE_WARNING_DAYS' to clean up"
426
+ else
427
+ log_success "No stale entries found"
428
+ fi
429
+
430
+ # Check for duplicate content
431
+ local dup_count
432
+ dup_count=$(sqlite3 "$MEMORY_DB" "SELECT COUNT(*) FROM (SELECT content, COUNT(*) as cnt FROM learnings GROUP BY content HAVING cnt > 1);" 2>/dev/null || echo "0")
433
+
434
+ if [[ "$dup_count" -gt 0 ]]; then
435
+ log_warn "Found $dup_count duplicate entries"
436
+ fi
437
+
438
+ # Check database size
439
+ local db_size
440
+ db_size=$(du -h "$MEMORY_DB" | cut -f1)
441
+ log_info "Database size: $db_size"
442
+ return 0
443
+ }
444
+
445
+ #######################################
446
+ # Prune old/stale entries
447
+ #######################################
448
+ cmd_prune() {
449
+ local older_than_days=$DEFAULT_MAX_AGE_DAYS
450
+ local dry_run=false
451
+ local keep_accessed=true
452
+
453
+ while [[ $# -gt 0 ]]; do
454
+ case "$1" in
455
+ --older-than-days) older_than_days="$2"; shift 2 ;;
456
+ --dry-run) dry_run=true; shift ;;
457
+ --include-accessed) keep_accessed=false; shift ;;
458
+ *) shift ;;
459
+ esac
460
+ done
461
+
462
+ init_db
463
+
464
+ # Build query to find stale entries
465
+ local count
466
+ if [[ "$keep_accessed" == true ]]; then
467
+ count=$(sqlite3 "$MEMORY_DB" "SELECT COUNT(*) FROM learnings l LEFT JOIN learning_access a ON l.id = a.id WHERE l.created_at < datetime('now', '-$older_than_days days') AND a.id IS NULL;")
468
+ else
469
+ count=$(sqlite3 "$MEMORY_DB" "SELECT COUNT(*) FROM learnings WHERE created_at < datetime('now', '-$older_than_days days');")
470
+ fi
471
+
472
+ if [[ "$count" -eq 0 ]]; then
473
+ log_success "No entries to prune"
474
+ return 0
475
+ fi
476
+
477
+ if [[ "$dry_run" == true ]]; then
478
+ log_info "[DRY RUN] Would delete $count entries"
479
+ echo ""
480
+ if [[ "$keep_accessed" == true ]]; then
481
+ sqlite3 "$MEMORY_DB" <<EOF
482
+ SELECT l.id, l.type, substr(l.content, 1, 50) || '...' as preview, l.created_at
483
+ FROM learnings l
484
+ LEFT JOIN learning_access a ON l.id = a.id
485
+ WHERE l.created_at < datetime('now', '-$older_than_days days') AND a.id IS NULL
486
+ LIMIT 20;
487
+ EOF
488
+ else
489
+ sqlite3 "$MEMORY_DB" <<EOF
490
+ SELECT id, type, substr(content, 1, 50) || '...' as preview, created_at
491
+ FROM learnings
492
+ WHERE created_at < datetime('now', '-$older_than_days days')
493
+ LIMIT 20;
494
+ EOF
495
+ fi
496
+ else
497
+ # Validate older_than_days is a positive integer
498
+ if ! [[ "$older_than_days" =~ ^[0-9]+$ ]]; then
499
+ log_error "--older-than-days must be a positive integer"
500
+ return 1
501
+ fi
502
+
503
+ # Use efficient single DELETE with subquery
504
+ local subquery
505
+ if [[ "$keep_accessed" == true ]]; then
506
+ subquery="SELECT l.id FROM learnings l LEFT JOIN learning_access a ON l.id = a.id WHERE l.created_at < datetime('now', '-$older_than_days days') AND a.id IS NULL"
507
+ else
508
+ subquery="SELECT id FROM learnings WHERE created_at < datetime('now', '-$older_than_days days')"
509
+ fi
510
+
511
+ # Delete from both tables using the subquery (much faster than loop)
512
+ sqlite3 "$MEMORY_DB" "DELETE FROM learning_access WHERE id IN ($subquery);"
513
+ sqlite3 "$MEMORY_DB" "DELETE FROM learnings WHERE id IN ($subquery);"
514
+
515
+ log_success "Pruned $count stale entries"
516
+
517
+ # Rebuild FTS index
518
+ sqlite3 "$MEMORY_DB" "INSERT INTO learnings(learnings) VALUES('rebuild');"
519
+ log_info "Rebuilt search index"
520
+ fi
521
+ }
522
+
523
+ #######################################
524
+ # Export memories
525
+ #######################################
526
+ cmd_export() {
527
+ local format="json"
528
+
529
+ while [[ $# -gt 0 ]]; do
530
+ case "$1" in
531
+ --format) format="$2"; shift 2 ;;
532
+ *) shift ;;
533
+ esac
534
+ done
535
+
536
+ init_db
537
+
538
+ case "$format" in
539
+ json)
540
+ sqlite3 -json "$MEMORY_DB" "SELECT l.*, COALESCE(a.last_accessed_at, '') as last_accessed_at, COALESCE(a.access_count, 0) as access_count FROM learnings l LEFT JOIN learning_access a ON l.id = a.id ORDER BY l.created_at DESC;"
541
+ ;;
542
+ toon)
543
+ # TOON format for token efficiency
544
+ local count
545
+ count=$(sqlite3 "$MEMORY_DB" "SELECT COUNT(*) FROM learnings;")
546
+ echo "learnings[$count]{id,type,confidence,content,tags,created_at}:"
547
+ sqlite3 -separator ',' "$MEMORY_DB" "SELECT id, type, confidence, content, tags, created_at FROM learnings ORDER BY created_at DESC;" | while read -r line; do
548
+ echo " $line"
549
+ done
550
+ ;;
551
+ *)
552
+ log_error "Unknown format: $format (use json or toon)"
553
+ return 1
554
+ ;;
555
+ esac
556
+ }
557
+
558
+ #######################################
559
+ # Show help
560
+ #######################################
561
+ cmd_help() {
562
+ cat <<'EOF'
563
+ memory-helper.sh - Lightweight memory system for aidevops
564
+
565
+ USAGE:
566
+ memory-helper.sh <command> [options]
567
+
568
+ COMMANDS:
569
+ store Store a new learning
570
+ recall Search and retrieve learnings
571
+ stats Show memory statistics
572
+ validate Check for stale/duplicate entries
573
+ prune Remove old entries
574
+ export Export all memories
575
+ help Show this help
576
+
577
+ STORE OPTIONS:
578
+ --content <text> Learning content (required)
579
+ --type <type> Learning type (default: WORKING_SOLUTION)
580
+ --tags <tags> Comma-separated tags
581
+ --confidence <level> high, medium, or low (default: medium)
582
+ --session-id <id> Session identifier
583
+ --project <path> Project path
584
+
585
+ VALID TYPES:
586
+ WORKING_SOLUTION, FAILED_APPROACH, CODEBASE_PATTERN, USER_PREFERENCE,
587
+ TOOL_CONFIG, DECISION, CONTEXT, ARCHITECTURAL_DECISION, ERROR_FIX, OPEN_THREAD
588
+
589
+ RECALL OPTIONS:
590
+ --query <text> Search query (required unless --recent)
591
+ --limit <n> Max results (default: 5)
592
+ --type <type> Filter by type
593
+ --max-age-days <n> Only recent entries
594
+ --project <path> Filter by project path
595
+ --recent [n] Show n most recent entries (default: 10)
596
+ --stats Show memory statistics
597
+ --json Output as JSON
598
+
599
+ PRUNE OPTIONS:
600
+ --older-than-days <n> Age threshold (default: 90)
601
+ --dry-run Show what would be deleted
602
+ --include-accessed Also prune accessed entries
603
+
604
+ STALENESS PREVENTION:
605
+ - Entries track created_at and last_accessed_at
606
+ - Recall updates last_accessed_at (used = valuable)
607
+ - Prune removes old entries that were never accessed
608
+ - Validate warns about potentially stale entries
609
+
610
+ EXAMPLES:
611
+ # Store a learning
612
+ memory-helper.sh store --content "Use FTS5 for fast search" --type WORKING_SOLUTION
613
+
614
+ # Recall learnings
615
+ memory-helper.sh recall --query "database search" --limit 10
616
+
617
+ # Check for stale entries
618
+ memory-helper.sh validate
619
+
620
+ # Clean up old unused entries
621
+ memory-helper.sh prune --older-than-days 60 --dry-run
622
+ EOF
623
+ return 0
624
+ }
625
+
626
+ #######################################
627
+ # Main entry point
628
+ #######################################
629
+ main() {
630
+ local command="${1:-help}"
631
+ shift || true
632
+
633
+ case "$command" in
634
+ store) cmd_store "$@" ;;
635
+ recall) cmd_recall "$@" ;;
636
+ stats) cmd_stats ;;
637
+ validate) cmd_validate ;;
638
+ prune) cmd_prune "$@" ;;
639
+ export) cmd_export "$@" ;;
640
+ help|--help|-h) cmd_help ;;
641
+ *)
642
+ log_error "Unknown command: $command"
643
+ cmd_help
644
+ return 1
645
+ ;;
646
+ esac
647
+ }
648
+
649
+ main "$@"
650
+ exit $?