@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,122 @@
|
|
|
1
|
+
<#
|
|
2
|
+
query-memory.ps1
|
|
3
|
+
Search memory quickly.
|
|
4
|
+
|
|
5
|
+
Default: grep (Select-String)
|
|
6
|
+
Optional: SQLite FTS (requires python + memory.sqlite), via -UseSqlite
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
powershell -File scripts/memory/query-memory.ps1 -Query "IL2CPP"
|
|
10
|
+
powershell -File scripts/memory/query-memory.ps1 -Query "IL2CPP" -UseSqlite
|
|
11
|
+
powershell -File scripts/memory/query-memory.ps1 -Query "bind" -Format AI
|
|
12
|
+
#>
|
|
13
|
+
|
|
14
|
+
[CmdletBinding()]
|
|
15
|
+
param(
|
|
16
|
+
[Parameter(Mandatory=$true)][string]$Query,
|
|
17
|
+
[ValidateSet("All","HotRules","Active","Memo","Lessons","Journal","Digests")][string]$Area = "All",
|
|
18
|
+
[ValidateSet("Human","AI")][string]$Format = "Human",
|
|
19
|
+
[switch]$UseSqlite
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
Set-StrictMode -Version Latest
|
|
23
|
+
$ErrorActionPreference = "Stop"
|
|
24
|
+
|
|
25
|
+
if ($PSScriptRoot) {
|
|
26
|
+
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
|
27
|
+
} else {
|
|
28
|
+
$RepoRoot = (Get-Location).Path
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function Resolve-MnemoMemoryDir([string]$Root) {
|
|
32
|
+
$candidates = @(
|
|
33
|
+
(Join-Path $Root ".mnemo\memory"),
|
|
34
|
+
(Join-Path $Root ".cursor\memory")
|
|
35
|
+
)
|
|
36
|
+
foreach ($candidate in $candidates) {
|
|
37
|
+
if (Test-Path -LiteralPath $candidate) { return $candidate }
|
|
38
|
+
}
|
|
39
|
+
return $candidates[0]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
$MemoryDir = Resolve-MnemoMemoryDir -Root $RepoRoot
|
|
43
|
+
$LessonsDir = Join-Path $MemoryDir "lessons"
|
|
44
|
+
$SqlitePath = Join-Path $MemoryDir "memory.sqlite"
|
|
45
|
+
|
|
46
|
+
if ($UseSqlite) {
|
|
47
|
+
$pyPath = $null
|
|
48
|
+
foreach ($candidate in @("python","py","python3")) {
|
|
49
|
+
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
|
|
50
|
+
if ($null -eq $cmd) { continue }
|
|
51
|
+
try {
|
|
52
|
+
& $cmd.Source -c "import sys; print(sys.version)" 1>$null 2>$null
|
|
53
|
+
if ($LASTEXITCODE -eq 0) { $pyPath = $cmd.Source; break }
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
$py = Join-Path $RepoRoot "scripts\memory\query-memory-sqlite.py"
|
|
57
|
+
if ($null -ne $pyPath -and (Test-Path $SqlitePath) -and (Test-Path $py)) {
|
|
58
|
+
Write-Host "Using SQLite FTS search..." -ForegroundColor Cyan
|
|
59
|
+
& $pyPath $py --repo $RepoRoot --q $Query --area $Area --format $Format
|
|
60
|
+
exit $LASTEXITCODE
|
|
61
|
+
}
|
|
62
|
+
Write-Host "SQLite mode unavailable. Falling back to grep." -ForegroundColor DarkYellow
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Write-Host "Using file-based search..." -ForegroundColor Cyan
|
|
66
|
+
|
|
67
|
+
$targets = @()
|
|
68
|
+
switch ($Area) {
|
|
69
|
+
"HotRules" { $targets += (Join-Path $MemoryDir "hot-rules.md") }
|
|
70
|
+
"Active" { $targets += (Join-Path $MemoryDir "active-context.md") }
|
|
71
|
+
"Memo" { $targets += (Join-Path $MemoryDir "memo.md") }
|
|
72
|
+
"Lessons" {
|
|
73
|
+
$targets += (Join-Path $LessonsDir "index.md")
|
|
74
|
+
$lessonFiles = Get-ChildItem -Path $LessonsDir -Filter "L-*.md" -ErrorAction SilentlyContinue
|
|
75
|
+
foreach ($lf in $lessonFiles) { $targets += $lf.FullName }
|
|
76
|
+
}
|
|
77
|
+
"Journal" { $targets += (Join-Path $MemoryDir "journal-index.md") }
|
|
78
|
+
"Digests" { $targets += (Join-Path $MemoryDir "digests\*.digest.md") }
|
|
79
|
+
"All" {
|
|
80
|
+
$targets += (Join-Path $MemoryDir "hot-rules.md")
|
|
81
|
+
$targets += (Join-Path $MemoryDir "active-context.md")
|
|
82
|
+
$targets += (Join-Path $MemoryDir "memo.md")
|
|
83
|
+
$targets += (Join-Path $LessonsDir "index.md")
|
|
84
|
+
$targets += (Join-Path $MemoryDir "journal-index.md")
|
|
85
|
+
$targets += (Join-Path $MemoryDir "digests\*.digest.md")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
$allMatches = @()
|
|
90
|
+
foreach ($t in $targets) {
|
|
91
|
+
$results = Select-String -Path $t -Pattern $Query -SimpleMatch -ErrorAction SilentlyContinue
|
|
92
|
+
if ($results) { $allMatches += $results }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$matchCount = @($allMatches).Count
|
|
96
|
+
|
|
97
|
+
if ($Format -eq "AI") {
|
|
98
|
+
if ($matchCount -eq 0) {
|
|
99
|
+
Write-Host "No matches found for: $Query"
|
|
100
|
+
} else {
|
|
101
|
+
$uniqueFiles = $allMatches | ForEach-Object { $_.Path } | Sort-Object -Unique
|
|
102
|
+
Write-Host "Files to read:"
|
|
103
|
+
foreach ($f in $uniqueFiles) {
|
|
104
|
+
$relative = $f.Replace($RepoRoot, "").TrimStart('\','/')
|
|
105
|
+
Write-Host " @$relative"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
Write-Host "Searching: $Query" -ForegroundColor Cyan
|
|
110
|
+
Write-Host "Area: $Area" -ForegroundColor Cyan
|
|
111
|
+
Write-Host ""
|
|
112
|
+
if ($matchCount -eq 0) {
|
|
113
|
+
Write-Host "No matches found." -ForegroundColor Yellow
|
|
114
|
+
} else {
|
|
115
|
+
$grouped = $allMatches | Group-Object Path
|
|
116
|
+
foreach ($g in $grouped) {
|
|
117
|
+
Write-Host "==> $($g.Name)" -ForegroundColor Green
|
|
118
|
+
foreach ($m in $g.Group) { Write-Host " $($m.LineNumber): $($m.Line.Trim())" }
|
|
119
|
+
Write-Host ""
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
<#
|
|
2
|
+
rebuild-memory-index.ps1
|
|
3
|
+
Generates:
|
|
4
|
+
- lessons/index.md + lessons-index.json
|
|
5
|
+
- journal-index.md + journal-index.json
|
|
6
|
+
- digests/YYYY-MM.digest.md
|
|
7
|
+
Optionally:
|
|
8
|
+
- memory.sqlite (if Python exists)
|
|
9
|
+
|
|
10
|
+
Works on PowerShell 5.1+ (no external deps).
|
|
11
|
+
BOM-tolerant parsing.
|
|
12
|
+
#>
|
|
13
|
+
|
|
14
|
+
[CmdletBinding()]
|
|
15
|
+
param([string]$RepoRoot = "")
|
|
16
|
+
|
|
17
|
+
Set-StrictMode -Version Latest
|
|
18
|
+
$ErrorActionPreference = "Stop"
|
|
19
|
+
|
|
20
|
+
if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
|
|
21
|
+
if ($PSScriptRoot) {
|
|
22
|
+
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
|
23
|
+
} else {
|
|
24
|
+
$RepoRoot = (Get-Location).Path
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Write-Utf8NoBom([string]$FilePath, [string]$Content) {
|
|
29
|
+
$dir = Split-Path -Parent $FilePath
|
|
30
|
+
if ($dir -and !(Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
|
31
|
+
$enc = New-Object System.Text.UTF8Encoding($false)
|
|
32
|
+
$normalized = ($Content -replace "`r?`n", "`r`n")
|
|
33
|
+
[System.IO.File]::WriteAllText($FilePath, $normalized, $enc)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function Read-Utf8([string]$Path) {
|
|
37
|
+
$raw = Get-Content -Raw -Encoding UTF8 $Path
|
|
38
|
+
if ($raw.Length -gt 0 -and [int]$raw[0] -eq 0xFEFF) { $raw = $raw.Substring(1) }
|
|
39
|
+
return $raw
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function Parse-Frontmatter([string]$FilePath) {
|
|
43
|
+
$raw = Read-Utf8 $FilePath
|
|
44
|
+
$result = @{}
|
|
45
|
+
$lines = $raw -split "`r?`n"
|
|
46
|
+
|
|
47
|
+
if ($lines.Count -lt 3 -or $lines[0].Trim() -ne "---") { return $null }
|
|
48
|
+
|
|
49
|
+
$i = 1
|
|
50
|
+
$currentListKey = $null
|
|
51
|
+
while ($i -lt $lines.Count) {
|
|
52
|
+
$line = $lines[$i]
|
|
53
|
+
if ($line.Trim() -eq "---") { break }
|
|
54
|
+
if ([string]::IsNullOrWhiteSpace($line) -or $line.TrimStart().StartsWith("#")) { $i++; continue }
|
|
55
|
+
|
|
56
|
+
if ($line -match '^\s*-\s+(.+)$' -and $currentListKey) {
|
|
57
|
+
if (-not ($result.ContainsKey($currentListKey))) { $result[$currentListKey] = @() }
|
|
58
|
+
$result[$currentListKey] += $Matches[1].Trim()
|
|
59
|
+
$i++; continue
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if ($line -match '^\s*([A-Za-z0-9_]+)\s*:\s*(.*)\s*$') {
|
|
63
|
+
$key = $Matches[1].Trim().ToLower()
|
|
64
|
+
$val = $Matches[2]
|
|
65
|
+
if ([string]::IsNullOrWhiteSpace($val)) {
|
|
66
|
+
$result[$key] = @()
|
|
67
|
+
$currentListKey = $key
|
|
68
|
+
$i++; continue
|
|
69
|
+
}
|
|
70
|
+
$currentListKey = $null
|
|
71
|
+
$v = $val.Trim()
|
|
72
|
+
if (($v.StartsWith('"') -and $v.EndsWith('"')) -or ($v.StartsWith("'") -and $v.EndsWith("'"))) {
|
|
73
|
+
$v = $v.Substring(1, $v.Length-2)
|
|
74
|
+
}
|
|
75
|
+
if ($v -match '^\[(.*)\]$') {
|
|
76
|
+
$inner = $Matches[1]
|
|
77
|
+
if ([string]::IsNullOrWhiteSpace($inner)) {
|
|
78
|
+
$result[$key] = @()
|
|
79
|
+
} else {
|
|
80
|
+
$items = $inner -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
|
|
81
|
+
$result[$key] = @($items)
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
$result[$key] = $v
|
|
85
|
+
}
|
|
86
|
+
$i++; continue
|
|
87
|
+
}
|
|
88
|
+
$i++
|
|
89
|
+
}
|
|
90
|
+
return $result
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Resolve-MnemoMemoryDir([string]$Root) {
|
|
94
|
+
$candidates = @(
|
|
95
|
+
(Join-Path $Root ".mnemo\memory"),
|
|
96
|
+
(Join-Path $Root ".cursor\memory")
|
|
97
|
+
)
|
|
98
|
+
foreach ($candidate in $candidates) {
|
|
99
|
+
if (Test-Path -LiteralPath $candidate) { return $candidate }
|
|
100
|
+
}
|
|
101
|
+
return $candidates[0]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
$MemoryDir = Resolve-MnemoMemoryDir -Root $RepoRoot
|
|
105
|
+
$LessonsDir = Join-Path $MemoryDir "lessons"
|
|
106
|
+
$JournalDir = Join-Path $MemoryDir "journal"
|
|
107
|
+
$DigestsDir = Join-Path $MemoryDir "digests"
|
|
108
|
+
|
|
109
|
+
if (!(Test-Path $LessonsDir)) { New-Item -ItemType Directory -Force -Path $LessonsDir | Out-Null }
|
|
110
|
+
if (!(Test-Path $JournalDir)) { throw "Missing: $JournalDir" }
|
|
111
|
+
if (!(Test-Path $DigestsDir)) { New-Item -ItemType Directory -Force -Path $DigestsDir | Out-Null }
|
|
112
|
+
|
|
113
|
+
$lessonFiles = Get-ChildItem -Path $LessonsDir -File -Filter "L-*.md" -ErrorAction SilentlyContinue | Sort-Object Name
|
|
114
|
+
$lessons = @()
|
|
115
|
+
|
|
116
|
+
foreach ($lf in $lessonFiles) {
|
|
117
|
+
$yaml = Parse-Frontmatter $lf.FullName
|
|
118
|
+
if ($null -eq $yaml) { continue }
|
|
119
|
+
if (-not $yaml.ContainsKey("id")) { continue }
|
|
120
|
+
|
|
121
|
+
$id = [string]$yaml["id"]
|
|
122
|
+
$num = 0
|
|
123
|
+
if ($id -match 'L-(\d+)') { $num = [int]$Matches[1] }
|
|
124
|
+
|
|
125
|
+
$title = if ($yaml.ContainsKey("title")) { [string]$yaml["title"] } else { $lf.BaseName }
|
|
126
|
+
$status = if ($yaml.ContainsKey("status")) { [string]$yaml["status"] } else { "Active" }
|
|
127
|
+
$introduced = if ($yaml.ContainsKey("introduced")) { [string]$yaml["introduced"] } else { "" }
|
|
128
|
+
$tags = @()
|
|
129
|
+
if ($yaml.ContainsKey("tags")) { $tags = @($yaml["tags"]) }
|
|
130
|
+
$applies = @()
|
|
131
|
+
if ($yaml.ContainsKey("applies_to")) { $applies = @($yaml["applies_to"]) }
|
|
132
|
+
$rule = ""
|
|
133
|
+
if ($yaml.ContainsKey("rule")) { $rule = [string]$yaml["rule"] } else { $rule = $title }
|
|
134
|
+
|
|
135
|
+
$lessons += [pscustomobject]@{
|
|
136
|
+
Id = $id; Num = $num; Title = $title; Status = $status; Introduced = $introduced
|
|
137
|
+
Tags = $tags; AppliesTo = $applies; Rule = $rule; File = $lf.Name
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$lessons = $lessons | Sort-Object Num
|
|
142
|
+
$gen = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssK")
|
|
143
|
+
$idx = @()
|
|
144
|
+
$idx += "# Lessons Index (generated)"; $idx += ""
|
|
145
|
+
$idx += "Generated: $gen"; $idx += ""
|
|
146
|
+
$idx += "Format: ID | [Tags] | AppliesTo | Rule | File"; $idx += ""
|
|
147
|
+
|
|
148
|
+
if (-not $lessons -or @($lessons).Count -eq 0) {
|
|
149
|
+
$idx += "(No lessons yet.)"
|
|
150
|
+
} else {
|
|
151
|
+
foreach ($l in $lessons) {
|
|
152
|
+
$tagText = ($l.Tags | ForEach-Object { "[$_]" }) -join ""
|
|
153
|
+
$appliesText = "(any)"
|
|
154
|
+
if ($l.AppliesTo -and @($l.AppliesTo).Count -gt 0) { $appliesText = ($l.AppliesTo -join ", ") }
|
|
155
|
+
$idx += "$($l.Id) | $tagText | $appliesText | $($l.Rule) | ``$($l.File)``"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Write-Utf8NoBom (Join-Path $LessonsDir "index.md") ($idx -join "`n")
|
|
160
|
+
$lessonsJson = if (-not $lessons -or @($lessons).Count -eq 0) { "[]" } else { ConvertTo-Json -InputObject @($lessons) -Depth 6 }
|
|
161
|
+
Write-Utf8NoBom (Join-Path $MemoryDir "lessons-index.json") $lessonsJson
|
|
162
|
+
|
|
163
|
+
$journalFiles = Get-ChildItem -Path $JournalDir -File | Where-Object {
|
|
164
|
+
$_.Name -match '^\d{4}-\d{2}\.md$' -and $_.Name -ne 'README.md'
|
|
165
|
+
} | Sort-Object Name
|
|
166
|
+
|
|
167
|
+
$journalEntries = New-Object System.Collections.Generic.List[object]
|
|
168
|
+
|
|
169
|
+
foreach ($jf in $journalFiles) {
|
|
170
|
+
$text = Read-Utf8 $jf.FullName
|
|
171
|
+
$datePattern = '(?m)^##\s+(\d{4}-\d{2}-\d{2}).*$'
|
|
172
|
+
$dateMatches = [regex]::Matches($text, $datePattern)
|
|
173
|
+
|
|
174
|
+
for ($i=0; $i -lt $dateMatches.Count; $i++) {
|
|
175
|
+
$date = $dateMatches[$i].Groups[1].Value
|
|
176
|
+
$start = $dateMatches[$i].Index
|
|
177
|
+
$end = if ($i -lt $dateMatches.Count - 1) { $dateMatches[$i+1].Index } else { $text.Length }
|
|
178
|
+
$block = $text.Substring($start, $end - $start)
|
|
179
|
+
|
|
180
|
+
$entryPattern = '(?m)^-\s+(\[[^\]]+\]){1,}.*$'
|
|
181
|
+
$entryMatches = [regex]::Matches($block, $entryPattern)
|
|
182
|
+
|
|
183
|
+
for ($j=0; $j -lt $entryMatches.Count; $j++) {
|
|
184
|
+
$eStart = $entryMatches[$j].Index
|
|
185
|
+
$eEnd = if ($j -lt $entryMatches.Count - 1) { $entryMatches[$j+1].Index } else { $block.Length }
|
|
186
|
+
$eBlock = $block.Substring($eStart, $eEnd - $eStart).Trim()
|
|
187
|
+
$firstLine = ($eBlock -split "`r?`n")[0].Trim()
|
|
188
|
+
|
|
189
|
+
$tags = @()
|
|
190
|
+
foreach ($tm in [regex]::Matches($firstLine, '\[([^\]]+)\]')) { $tags += $tm.Groups[1].Value.Trim() }
|
|
191
|
+
$title = ($firstLine -replace '^-\s+(\[[^\]]+\])+\s*', '').Trim()
|
|
192
|
+
|
|
193
|
+
$files = @()
|
|
194
|
+
foreach ($fm in [regex]::Matches($eBlock, '`([^`]+)`')) {
|
|
195
|
+
$v = $fm.Groups[1].Value.Trim()
|
|
196
|
+
if ($v -match '[/\\]' -or $v -match '\.(cs|md|mdx|yml|yaml|csproj|ps1|ts|tsx|json|py)$') {
|
|
197
|
+
$files += $v
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
$files = $files | Select-Object -Unique
|
|
201
|
+
|
|
202
|
+
$journalEntries.Add([pscustomobject]@{
|
|
203
|
+
MonthFile = $jf.Name; Date = $date; Tags = $tags; Title = $title; Files = $files
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
$monthName = [System.IO.Path]::GetFileNameWithoutExtension($jf.Name)
|
|
209
|
+
$digestPath = Join-Path $DigestsDir "$monthName.digest.md"
|
|
210
|
+
|
|
211
|
+
$digest = @()
|
|
212
|
+
$digest += "# Monthly Digest - $monthName (generated)"; $digest += ""
|
|
213
|
+
$digest += "Generated: $gen"; $digest += ""
|
|
214
|
+
$digest += "Token-cheap summary. See ``.mnemo/memory/journal/$($jf.Name)`` for details."; $digest += ""
|
|
215
|
+
|
|
216
|
+
$dates = [regex]::Matches($text, '(?m)^##\s+(\d{4}-\d{2}-\d{2}).*$') | ForEach-Object { $_.Groups[1].Value }
|
|
217
|
+
$uniqueDates = $dates | Select-Object -Unique
|
|
218
|
+
foreach ($d in $uniqueDates) {
|
|
219
|
+
$digest += "## $d"; $digest += ""
|
|
220
|
+
$entriesForDay = $journalEntries | Where-Object { $_.MonthFile -eq $jf.Name -and $_.Date -eq $d }
|
|
221
|
+
foreach ($e in $entriesForDay) {
|
|
222
|
+
$tagText = ($e.Tags | ForEach-Object { "[$_]" }) -join ""
|
|
223
|
+
$digest += "- $tagText $($e.Title)"
|
|
224
|
+
}
|
|
225
|
+
$digest += ""
|
|
226
|
+
}
|
|
227
|
+
Write-Utf8NoBom $digestPath ($digest -join "`n")
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
$ji = @()
|
|
231
|
+
$ji += "# Journal Index (generated)"; $ji += ""
|
|
232
|
+
$ji += "Generated: $gen"; $ji += ""
|
|
233
|
+
$ji += "Format: YYYY-MM-DD | [Tags] | Title | Files"; $ji += ""
|
|
234
|
+
|
|
235
|
+
foreach ($e in ($journalEntries | Sort-Object Date, Title)) {
|
|
236
|
+
$tagText = ($e.Tags | ForEach-Object { "[$_]" }) -join ""
|
|
237
|
+
$fileText = "-"
|
|
238
|
+
if ($e.Files -and @($e.Files).Count -gt 0) { $fileText = ($e.Files -join ", ") }
|
|
239
|
+
$ji += "$($e.Date) | $tagText | $($e.Title) | $fileText"
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
Write-Utf8NoBom (Join-Path $MemoryDir "journal-index.md") ($ji -join "`n")
|
|
243
|
+
$journalJson = if (-not $journalEntries -or $journalEntries.Count -eq 0) { "[]" } else { ConvertTo-Json -InputObject @($journalEntries.ToArray()) -Depth 6 }
|
|
244
|
+
Write-Utf8NoBom (Join-Path $MemoryDir "journal-index.json") $journalJson
|
|
245
|
+
|
|
246
|
+
function Resolve-PythonCommand {
|
|
247
|
+
$candidates = @(
|
|
248
|
+
@{ Kind = "python"; Args = @() },
|
|
249
|
+
@{ Kind = "py"; Args = @("-3") },
|
|
250
|
+
@{ Kind = "py"; Args = @() },
|
|
251
|
+
@{ Kind = "python3"; Args = @() }
|
|
252
|
+
)
|
|
253
|
+
foreach ($c in $candidates) {
|
|
254
|
+
$cmd = Get-Command $c.Kind -ErrorAction SilentlyContinue
|
|
255
|
+
if ($null -eq $cmd) { continue }
|
|
256
|
+
try {
|
|
257
|
+
& $cmd.Source @($c.Args) -c "import sys; print(sys.version)" 1>$null 2>$null
|
|
258
|
+
if ($LASTEXITCODE -eq 0) { return @{ Path = $cmd.Source; Args = @($c.Args) } }
|
|
259
|
+
} catch {}
|
|
260
|
+
}
|
|
261
|
+
return $null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
$py = Resolve-PythonCommand
|
|
265
|
+
$pyScript = Join-Path $RepoRoot "scripts\memory\build-memory-sqlite.py"
|
|
266
|
+
if ($null -ne $py -and (Test-Path $pyScript)) {
|
|
267
|
+
Write-Host "Python detected; building SQLite FTS index..." -ForegroundColor Cyan
|
|
268
|
+
& $py.Path @($py.Args) $pyScript --repo $RepoRoot | Out-Host
|
|
269
|
+
} else {
|
|
270
|
+
Write-Host "Python not found (or not runnable); skipping SQLite build." -ForegroundColor DarkYellow
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
$hotFiles = @(
|
|
274
|
+
(Join-Path $MemoryDir "hot-rules.md"),
|
|
275
|
+
(Join-Path $MemoryDir "active-context.md"),
|
|
276
|
+
(Join-Path $MemoryDir "memo.md")
|
|
277
|
+
)
|
|
278
|
+
$totalChars = 0
|
|
279
|
+
foreach ($hf in $hotFiles) {
|
|
280
|
+
if (Test-Path $hf) {
|
|
281
|
+
$t = Get-Content -Raw -ErrorAction SilentlyContinue $hf
|
|
282
|
+
if ($t) { $totalChars += $t.Length }
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
$estimatedTokens = [math]::Round($totalChars / 4)
|
|
286
|
+
Write-Host ""
|
|
287
|
+
if ($totalChars -gt 8000) {
|
|
288
|
+
Write-Host "WARNING: Always-read layer is $totalChars chars (~$estimatedTokens tokens)" -ForegroundColor Yellow
|
|
289
|
+
} else {
|
|
290
|
+
Write-Host "Always-read layer: $totalChars chars (~$estimatedTokens tokens) - Healthy" -ForegroundColor Green
|
|
291
|
+
}
|
|
292
|
+
Write-Host ""
|
|
293
|
+
Write-Host "Rebuild complete." -ForegroundColor Green
|