@dinasor/mnemo-cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +263 -0
  4. package/VERSION +1 -0
  5. package/bin/mnemo.js +139 -0
  6. package/memory.ps1 +178 -0
  7. package/memory_mac.sh +2447 -0
  8. package/package.json +36 -0
  9. package/scripts/memory/installer/bootstrap.ps1 +21 -0
  10. package/scripts/memory/installer/core/bridge.ps1 +285 -0
  11. package/scripts/memory/installer/core/io.ps1 +110 -0
  12. package/scripts/memory/installer/core/paths.ps1 +83 -0
  13. package/scripts/memory/installer/features/gitignore_setup.ps1 +80 -0
  14. package/scripts/memory/installer/features/hooks_setup.ps1 +157 -0
  15. package/scripts/memory/installer/features/mcp_setup.ps1 +87 -0
  16. package/scripts/memory/installer/features/memory_scaffold.ps1 +541 -0
  17. package/scripts/memory/installer/features/vector_setup.ps1 +103 -0
  18. package/scripts/memory/installer/templates/add-journal-entry.ps1 +122 -0
  19. package/scripts/memory/installer/templates/add-lesson.ps1 +151 -0
  20. package/scripts/memory/installer/templates/autonomy/__init__.py +6 -0
  21. package/scripts/memory/installer/templates/autonomy/context_safety.py +181 -0
  22. package/scripts/memory/installer/templates/autonomy/entity_resolver.py +215 -0
  23. package/scripts/memory/installer/templates/autonomy/ingest_pipeline.py +252 -0
  24. package/scripts/memory/installer/templates/autonomy/lifecycle_engine.py +254 -0
  25. package/scripts/memory/installer/templates/autonomy/policies.yaml +59 -0
  26. package/scripts/memory/installer/templates/autonomy/reranker.py +220 -0
  27. package/scripts/memory/installer/templates/autonomy/retrieval_router.py +148 -0
  28. package/scripts/memory/installer/templates/autonomy/runner.py +272 -0
  29. package/scripts/memory/installer/templates/autonomy/schema.py +150 -0
  30. package/scripts/memory/installer/templates/autonomy/vault_policy.py +205 -0
  31. package/scripts/memory/installer/templates/build-memory-sqlite.py +111 -0
  32. package/scripts/memory/installer/templates/clear-active.ps1 +55 -0
  33. package/scripts/memory/installer/templates/customization.md +84 -0
  34. package/scripts/memory/installer/templates/lint-memory.ps1 +217 -0
  35. package/scripts/memory/installer/templates/mnemo_vector.py +556 -0
  36. package/scripts/memory/installer/templates/query-memory-sqlite.py +95 -0
  37. package/scripts/memory/installer/templates/query-memory.ps1 +122 -0
  38. package/scripts/memory/installer/templates/rebuild-memory-index.ps1 +293 -0
@@ -0,0 +1,217 @@
1
+ <#
2
+ lint-memory.ps1
3
+ Validates memory health:
4
+ - lesson YAML frontmatter required fields
5
+ - unique lesson IDs
6
+ - tags must exist in tag-vocabulary.md
7
+ - journal date headings should not repeat within a month file
8
+ - token budget check
9
+ #>
10
+
11
+ [CmdletBinding()]
12
+ param([string]$RepoRoot = "")
13
+
14
+ Set-StrictMode -Version Latest
15
+ $ErrorActionPreference = "Stop"
16
+
17
+ if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
18
+ if ($PSScriptRoot) {
19
+ $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
20
+ } else {
21
+ $RepoRoot = (Get-Location).Path
22
+ }
23
+ }
24
+
25
+ $mem = Join-Path $RepoRoot ".cursor\memory"
26
+ $lessonsDir = Join-Path $mem "lessons"
27
+ $journalDir = Join-Path $mem "journal"
28
+ $tagVocabPath = Join-Path $mem "tag-vocabulary.md"
29
+
30
+ $errors = @()
31
+ $warnings = @()
32
+
33
+ function Fail([string]$msg) { $script:errors += $msg }
34
+ function Warn([string]$msg) { $script:warnings += $msg }
35
+
36
+ function ReadText([string]$p) {
37
+ $t = Get-Content -Raw -Encoding UTF8 -ErrorAction Stop $p
38
+ if ($t.Length -gt 0 -and [int]$t[0] -eq 0xFEFF) { $t = $t.Substring(1) }
39
+ return $t
40
+ }
41
+
42
+ Write-Host "Linting Cursor Memory System..." -ForegroundColor Cyan
43
+ Write-Host ""
44
+
45
+ $allowed = @{}
46
+ if (Test-Path $tagVocabPath) {
47
+ $tv = ReadText $tagVocabPath
48
+ foreach ($m in [regex]::Matches($tv, '(?m)^\-\s+\[([^\]]+)\]')) {
49
+ $allowed[$m.Groups[1].Value.Trim()] = $true
50
+ }
51
+ } else {
52
+ Warn "Missing tag vocabulary: $tagVocabPath"
53
+ }
54
+
55
+ Write-Host "Checking lessons..." -ForegroundColor White
56
+
57
+ function ParseFrontmatter([string]$file) {
58
+ $raw = ReadText $file
59
+ $lines = $raw -split "`r?`n"
60
+ if ($lines.Count -lt 3 -or $lines[0].Trim() -ne "---") { return $null }
61
+
62
+ $result = @{}
63
+ $i = 1
64
+ $currentListKey = $null
65
+ while ($i -lt $lines.Count) {
66
+ $line = $lines[$i]
67
+ if ($line.Trim() -eq "---") { break }
68
+ if ([string]::IsNullOrWhiteSpace($line) -or $line.TrimStart().StartsWith("#")) { $i++; continue }
69
+
70
+ if ($line -match '^\s*-\s+(.+)$' -and $currentListKey) {
71
+ $result[$currentListKey] += @($Matches[1].Trim())
72
+ $i++; continue
73
+ }
74
+
75
+ if ($line -match '^\s*([A-Za-z0-9_]+)\s*:\s*(.*)\s*$') {
76
+ $key = $Matches[1].Trim().ToLower()
77
+ $val = $Matches[2]
78
+ if ([string]::IsNullOrWhiteSpace($val)) {
79
+ $result[$key] = @()
80
+ $currentListKey = $key
81
+ $i++; continue
82
+ }
83
+ $currentListKey = $null
84
+ $v = $val.Trim()
85
+ if (($v.StartsWith('"') -and $v.EndsWith('"')) -or ($v.StartsWith("'") -and $v.EndsWith("'"))) {
86
+ $v = $v.Substring(1, $v.Length-2)
87
+ }
88
+ if ($v -match '^\[(.*)\]$') {
89
+ $inner = $Matches[1]
90
+ $items = @()
91
+ if (-not [string]::IsNullOrWhiteSpace($inner)) {
92
+ $items = $inner -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
93
+ }
94
+ $result[$key] = @($items)
95
+ } else {
96
+ $result[$key] = $v
97
+ }
98
+ $i++; continue
99
+ }
100
+ $i++
101
+ }
102
+ return $result
103
+ }
104
+
105
+ $ids = @{}
106
+ $lessonFiles = Get-ChildItem -Path $lessonsDir -File -Filter "L-*.md" -ErrorAction SilentlyContinue
107
+ $lessonCount = if ($lessonFiles) { @($lessonFiles).Count } else { 0 }
108
+
109
+ foreach ($lf in $lessonFiles) {
110
+ $yaml = ParseFrontmatter $lf.FullName
111
+ if ($null -eq $yaml) { Fail "[$($lf.Name)] Missing YAML frontmatter"; continue }
112
+
113
+ foreach ($req in @("id","title","status","tags","introduced","rule")) {
114
+ if (-not $yaml.ContainsKey($req) -or [string]::IsNullOrWhiteSpace([string]$yaml[$req])) {
115
+ Fail "[$($lf.Name)] Missing required field: $req"
116
+ }
117
+ }
118
+
119
+ $id = [string]$yaml["id"]
120
+ if ($id -notmatch '^L-\d{3}$') { Warn "[$($lf.Name)] ID '$id' doesn't match format L-XXX (3 digits)" }
121
+ if ($ids.ContainsKey($id)) { Fail "[$($lf.Name)] Duplicate lesson ID $id (also in $($ids[$id]))" }
122
+ else { $ids[$id] = $lf.Name }
123
+
124
+ $tags = @($yaml["tags"])
125
+ foreach ($t in $tags) {
126
+ $tag = [string]$t
127
+ if ($allowed.Count -gt 0 -and -not $allowed.ContainsKey($tag)) {
128
+ Fail "[$($lf.Name)] Unknown tag [$tag]. Add it to tag-vocabulary.md or fix the lesson."
129
+ }
130
+ }
131
+
132
+ $expectedPrefix = $id.ToLower()
133
+ if (-not $lf.Name.ToLower().StartsWith($expectedPrefix)) {
134
+ Warn "[$($lf.Name)] Filename doesn't start with ID '$id'"
135
+ }
136
+ }
137
+
138
+ Write-Host " Found $lessonCount lesson files" -ForegroundColor Gray
139
+
140
+ Write-Host "Checking journals..." -ForegroundColor White
141
+ $journalFiles = Get-ChildItem -Path $journalDir -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^\d{4}-\d{2}\.md$' }
142
+ $journalCount = if ($journalFiles) { @($journalFiles).Count } else { 0 }
143
+
144
+ foreach ($jf in $journalFiles) {
145
+ $txt = ReadText $jf.FullName
146
+ $dates = @([regex]::Matches($txt, '(?m)^##\s+(\d{4}-\d{2}-\d{2})') | ForEach-Object { $_.Groups[1].Value })
147
+ if ($dates -and $dates.Count -gt 0) {
148
+ $g = $dates | Group-Object | Where-Object { $_.Count -gt 1 }
149
+ foreach ($dup in $g) {
150
+ Fail "[$($jf.Name)] Duplicate date heading $($dup.Name) x$($dup.Count). Merge into one section."
151
+ }
152
+ }
153
+ }
154
+
155
+ Write-Host " Found $journalCount journal files" -ForegroundColor Gray
156
+
157
+ Write-Host "Checking token budget..." -ForegroundColor White
158
+ $hotFiles = @(
159
+ (Join-Path $mem "hot-rules.md"),
160
+ (Join-Path $mem "active-context.md"),
161
+ (Join-Path $mem "memo.md")
162
+ )
163
+ $totalChars = 0
164
+ foreach ($hf in $hotFiles) {
165
+ if (Test-Path $hf) {
166
+ $chars = (Get-Content $hf -Raw -ErrorAction SilentlyContinue).Length
167
+ $totalChars += $chars
168
+ if ($chars -gt 3000) {
169
+ Warn "[$(Split-Path $hf -Leaf)] File is $chars chars (~$([math]::Round($chars/4)) tokens) - consider trimming"
170
+ }
171
+ }
172
+ }
173
+
174
+ $estimatedTokens = [math]::Round($totalChars / 4)
175
+ Write-Host " Always-read layer: $totalChars chars (~$estimatedTokens tokens)" -ForegroundColor Gray
176
+
177
+ if ($totalChars -gt 8000) {
178
+ Fail "[Token Budget] Always-read layer exceeds 8000 chars (~2000 tokens)"
179
+ } elseif ($totalChars -gt 6000) {
180
+ Warn "[Token Budget] Always-read layer is $totalChars chars - approaching limit"
181
+ }
182
+
183
+ Write-Host "Checking for orphans..." -ForegroundColor White
184
+ if (-not (Test-Path (Join-Path $lessonsDir "index.md"))) {
185
+ Warn "[lessons/index.md] Missing - run rebuild-memory-index.ps1"
186
+ }
187
+ if (-not (Test-Path (Join-Path $mem "journal-index.md"))) {
188
+ Warn "[journal-index.md] Missing - run rebuild-memory-index.ps1"
189
+ }
190
+
191
+ Write-Host ""
192
+ Write-Host "====== LINT RESULTS ======" -ForegroundColor White
193
+
194
+ $errorCount = @($errors).Count
195
+ $warningCount = @($warnings).Count
196
+
197
+ if ($errorCount -eq 0 -and $warningCount -eq 0) {
198
+ Write-Host "All checks passed!" -ForegroundColor Green
199
+ } else {
200
+ if ($errorCount -gt 0) {
201
+ Write-Host ""; Write-Host "ERRORS ($errorCount):" -ForegroundColor Red
202
+ foreach ($e in $errors) { Write-Host " ERROR: $e" -ForegroundColor Red }
203
+ }
204
+ if ($warningCount -gt 0) {
205
+ Write-Host ""; Write-Host "WARNINGS ($warningCount):" -ForegroundColor Yellow
206
+ foreach ($w in $warnings) { Write-Host " WARN: $w" -ForegroundColor Yellow }
207
+ }
208
+ }
209
+
210
+ Write-Host ""
211
+ if ($errorCount -gt 0) {
212
+ Write-Host "Lint FAILED with $errorCount error(s)" -ForegroundColor Red
213
+ exit 1
214
+ } else {
215
+ Write-Host "Lint passed" -ForegroundColor Green
216
+ exit 0
217
+ }