@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.
- package/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/VERSION +1 -0
- package/bin/mnemo.js +139 -0
- package/memory.ps1 +178 -0
- package/memory_mac.sh +2447 -0
- package/package.json +36 -0
- package/scripts/memory/installer/bootstrap.ps1 +21 -0
- package/scripts/memory/installer/core/bridge.ps1 +285 -0
- package/scripts/memory/installer/core/io.ps1 +110 -0
- package/scripts/memory/installer/core/paths.ps1 +83 -0
- package/scripts/memory/installer/features/gitignore_setup.ps1 +80 -0
- package/scripts/memory/installer/features/hooks_setup.ps1 +157 -0
- package/scripts/memory/installer/features/mcp_setup.ps1 +87 -0
- package/scripts/memory/installer/features/memory_scaffold.ps1 +541 -0
- package/scripts/memory/installer/features/vector_setup.ps1 +103 -0
- package/scripts/memory/installer/templates/add-journal-entry.ps1 +122 -0
- package/scripts/memory/installer/templates/add-lesson.ps1 +151 -0
- package/scripts/memory/installer/templates/autonomy/__init__.py +6 -0
- package/scripts/memory/installer/templates/autonomy/context_safety.py +181 -0
- package/scripts/memory/installer/templates/autonomy/entity_resolver.py +215 -0
- package/scripts/memory/installer/templates/autonomy/ingest_pipeline.py +252 -0
- package/scripts/memory/installer/templates/autonomy/lifecycle_engine.py +254 -0
- package/scripts/memory/installer/templates/autonomy/policies.yaml +59 -0
- package/scripts/memory/installer/templates/autonomy/reranker.py +220 -0
- package/scripts/memory/installer/templates/autonomy/retrieval_router.py +148 -0
- package/scripts/memory/installer/templates/autonomy/runner.py +272 -0
- package/scripts/memory/installer/templates/autonomy/schema.py +150 -0
- package/scripts/memory/installer/templates/autonomy/vault_policy.py +205 -0
- package/scripts/memory/installer/templates/build-memory-sqlite.py +111 -0
- package/scripts/memory/installer/templates/clear-active.ps1 +55 -0
- package/scripts/memory/installer/templates/customization.md +84 -0
- package/scripts/memory/installer/templates/lint-memory.ps1 +217 -0
- package/scripts/memory/installer/templates/mnemo_vector.py +556 -0
- package/scripts/memory/installer/templates/query-memory-sqlite.py +95 -0
- package/scripts/memory/installer/templates/query-memory.ps1 +122 -0
- 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
|
+
}
|