@wipcomputer/wip-ldm-os 0.4.38 → 0.4.41
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/SKILL.md +1 -1
- package/bin/ldm.js +97 -0
- package/docs/recall/TECHNICAL.md +6 -0
- package/docs/total-recall/README.md +84 -0
- package/docs/total-recall/TECHNICAL.md +124 -0
- package/package.json +3 -1
- package/scripts/backfill-summaries.sh +112 -0
- package/scripts/ldm-backup.sh +251 -0
- package/scripts/ldm-restore.sh +224 -0
- package/scripts/ldm-summary.sh +239 -0
- package/shared/prompts/daily-agent-summary.md +13 -0
- package/shared/prompts/daily-dev.md +12 -0
- package/shared/prompts/monthly-agent-summary.md +13 -0
- package/shared/prompts/org-daily-team.md +12 -0
- package/shared/prompts/quarterly-agent-summary.md +14 -0
- package/shared/prompts/weekly-agent-summary.md +14 -0
- package/shared/rules/git-conventions.md +29 -0
- package/shared/rules/release-pipeline.md +25 -0
- package/shared/rules/security.md +17 -0
- package/shared/rules/workspace-boundaries.md +21 -0
- package/shared/rules/writing-style.md +5 -0
package/SKILL.md
CHANGED
package/bin/ldm.js
CHANGED
|
@@ -261,9 +261,42 @@ async function cmdInit() {
|
|
|
261
261
|
join(LDM_ROOT, 'messages'),
|
|
262
262
|
join(LDM_ROOT, 'shared', 'boot'),
|
|
263
263
|
join(LDM_ROOT, 'shared', 'cron'),
|
|
264
|
+
join(LDM_ROOT, 'shared', 'rules'),
|
|
265
|
+
join(LDM_ROOT, 'shared', 'prompts'),
|
|
264
266
|
join(LDM_ROOT, 'hooks'),
|
|
265
267
|
];
|
|
266
268
|
|
|
269
|
+
// Scaffold per-agent memory dirs
|
|
270
|
+
try {
|
|
271
|
+
const config = JSON.parse(readFileSync(join(LDM_ROOT, 'config.json'), 'utf8'));
|
|
272
|
+
const agents = config.agents || [];
|
|
273
|
+
const agentList = Array.isArray(agents) ? agents : Object.keys(agents);
|
|
274
|
+
for (const agentId of agentList) {
|
|
275
|
+
for (const sub of ['memory/daily', 'memory/journals', 'memory/sessions', 'memory/transcripts']) {
|
|
276
|
+
dirs.push(join(LDM_ROOT, 'agents', agentId, sub));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Scaffold workspace output dirs if workspace is configured
|
|
281
|
+
const workspace = config.workspace;
|
|
282
|
+
if (workspace && existsSync(workspace)) {
|
|
283
|
+
// Per-agent workspace dirs
|
|
284
|
+
const agentNameMap = { 'cc-mini': 'cc-mini', 'cc-air': 'cc-air', 'oc-lesa-mini': 'Lēsa' };
|
|
285
|
+
for (const agentId of agentList) {
|
|
286
|
+
const teamName = agentNameMap[agentId] || agentId;
|
|
287
|
+
for (const sub of ['journals', 'automated/memory/summaries/daily', 'automated/memory/summaries/weekly', 'automated/memory/summaries/monthly', 'automated/memory/summaries/quarterly']) {
|
|
288
|
+
dirs.push(join(workspace, 'team', teamName, sub));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Org-wide dirs
|
|
292
|
+
for (const track of ['team', 'dev']) {
|
|
293
|
+
for (const cadence of ['daily', 'weekly', 'monthly', 'quarterly']) {
|
|
294
|
+
dirs.push(join(workspace, 'operations', 'updates', track, cadence));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch {} // config.json may not exist on first init
|
|
299
|
+
|
|
267
300
|
const existing = existsSync(VERSION_PATH);
|
|
268
301
|
|
|
269
302
|
if (DRY_RUN) {
|
|
@@ -379,6 +412,70 @@ async function cmdInit() {
|
|
|
379
412
|
chmodSync(restoreDest, 0o755);
|
|
380
413
|
console.log(` + ldm-restore.sh deployed to ~/.ldm/bin/`);
|
|
381
414
|
}
|
|
415
|
+
const summarySrc = join(__dirname, '..', 'scripts', 'ldm-summary.sh');
|
|
416
|
+
const summaryDest = join(LDM_ROOT, 'bin', 'ldm-summary.sh');
|
|
417
|
+
if (existsSync(summarySrc)) {
|
|
418
|
+
cpSync(summarySrc, summaryDest);
|
|
419
|
+
chmodSync(summaryDest, 0o755);
|
|
420
|
+
console.log(` + ldm-summary.sh deployed to ~/.ldm/bin/`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Deploy shared rules to ~/.ldm/shared/rules/ and to harnesses
|
|
424
|
+
const rulesSrc = join(__dirname, '..', 'shared', 'rules');
|
|
425
|
+
const rulesDest = join(LDM_ROOT, 'shared', 'rules');
|
|
426
|
+
if (existsSync(rulesSrc)) {
|
|
427
|
+
mkdirSync(rulesDest, { recursive: true });
|
|
428
|
+
let rulesCount = 0;
|
|
429
|
+
for (const file of readdirSync(rulesSrc)) {
|
|
430
|
+
if (!file.endsWith('.md')) continue;
|
|
431
|
+
cpSync(join(rulesSrc, file), join(rulesDest, file));
|
|
432
|
+
rulesCount++;
|
|
433
|
+
}
|
|
434
|
+
if (rulesCount > 0) {
|
|
435
|
+
console.log(` + ${rulesCount} shared rules deployed to ~/.ldm/shared/rules/`);
|
|
436
|
+
|
|
437
|
+
// Deploy to Claude Code harness (~/.claude/rules/)
|
|
438
|
+
const claudeRules = join(HOME, '.claude', 'rules');
|
|
439
|
+
if (existsSync(join(HOME, '.claude'))) {
|
|
440
|
+
mkdirSync(claudeRules, { recursive: true });
|
|
441
|
+
for (const file of readdirSync(rulesDest)) {
|
|
442
|
+
if (!file.endsWith('.md')) continue;
|
|
443
|
+
cpSync(join(rulesDest, file), join(claudeRules, file));
|
|
444
|
+
}
|
|
445
|
+
console.log(` + rules deployed to ~/.claude/rules/`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Deploy to OpenClaw harness (~/.openclaw/workspace/DEV-RULES.md)
|
|
449
|
+
const ocWorkspace = join(HOME, '.openclaw', 'workspace');
|
|
450
|
+
if (existsSync(ocWorkspace)) {
|
|
451
|
+
let combined = '# Dev Rules (deployed by ldm install)\n\n';
|
|
452
|
+
combined += '> Do not edit this file. It is regenerated by `ldm install`.\n';
|
|
453
|
+
combined += '> Source: ~/.ldm/shared/rules/\n\n';
|
|
454
|
+
for (const file of readdirSync(rulesDest).sort()) {
|
|
455
|
+
if (!file.endsWith('.md')) continue;
|
|
456
|
+
combined += readFileSync(join(rulesDest, file), 'utf8') + '\n\n---\n\n';
|
|
457
|
+
}
|
|
458
|
+
writeFileSync(join(ocWorkspace, 'DEV-RULES.md'), combined);
|
|
459
|
+
console.log(` + rules deployed to ~/.openclaw/workspace/DEV-RULES.md`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Deploy shared prompts to ~/.ldm/shared/prompts/
|
|
465
|
+
const promptsSrc = join(__dirname, '..', 'shared', 'prompts');
|
|
466
|
+
const promptsDest = join(LDM_ROOT, 'shared', 'prompts');
|
|
467
|
+
if (existsSync(promptsSrc)) {
|
|
468
|
+
mkdirSync(promptsDest, { recursive: true });
|
|
469
|
+
let promptsCount = 0;
|
|
470
|
+
for (const file of readdirSync(promptsSrc)) {
|
|
471
|
+
if (!file.endsWith('.md')) continue;
|
|
472
|
+
cpSync(join(promptsSrc, file), join(promptsDest, file));
|
|
473
|
+
promptsCount++;
|
|
474
|
+
}
|
|
475
|
+
if (promptsCount > 0) {
|
|
476
|
+
console.log(` + ${promptsCount} shared prompts deployed to ~/.ldm/shared/prompts/`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
382
479
|
|
|
383
480
|
console.log('');
|
|
384
481
|
console.log(` LDM OS v${PKG_VERSION} initialized at ${LDM_ROOT}`);
|
package/docs/recall/TECHNICAL.md
CHANGED
|
@@ -39,3 +39,9 @@ The hook runs with a 15-second timeout. If any file is missing, it's skipped sil
|
|
|
39
39
|
## Session Registration
|
|
40
40
|
|
|
41
41
|
On boot, Recall also registers the session in the Agent Register (`~/.ldm/sessions/`). This enables other sessions to discover this one via `ldm sessions`.
|
|
42
|
+
|
|
43
|
+
## Connection to Total Recall
|
|
44
|
+
|
|
45
|
+
Recall loads context at session start. [Total Recall](../total-recall/TECHNICAL.md) fills the memory that Recall serves. Total Recall imports historical conversations, generates multi-cadence summaries (daily/weekly/monthly/quarterly), and writes everything to Memory Crystal. On the next session start, Recall picks up the new data automatically.
|
|
46
|
+
|
|
47
|
+
Without Total Recall, Recall only has what was captured going forward. With Total Recall, Recall has the complete history.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
###### WIP Computer
|
|
2
|
+
|
|
3
|
+
# Total Recall
|
|
4
|
+
|
|
5
|
+
## Connect your AI accounts. Bring every memory home. Never lose a conversation again.
|
|
6
|
+
|
|
7
|
+
Total Recall is LDM OS's memory import and consolidation system. It connects to AI platforms, imports conversation history, and uses Dream Weaver to consolidate raw data into searchable, structured memories in Memory Crystal.
|
|
8
|
+
|
|
9
|
+
It also generates multi-cadence summaries (daily, weekly, monthly, quarterly) for every agent.
|
|
10
|
+
|
|
11
|
+
## The Pipeline
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
1. CONNECT -> Sign into your AI accounts (Anthropic, OpenAI, xAI, etc.)
|
|
15
|
+
2. IMPORT -> Pull every conversation (API, data export, or automation)
|
|
16
|
+
3. RELIVE -> Dream Weaver processes raw conversations into memories
|
|
17
|
+
4. CRYSTAL -> Consolidated memories stored in Memory Crystal
|
|
18
|
+
5. SUMMARIZE -> Multi-cadence summaries (daily/weekly/monthly/quarterly)
|
|
19
|
+
6. MONITOR -> System check alerts when any agent stops capturing
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Two Modes
|
|
23
|
+
|
|
24
|
+
### Going Forward (daily)
|
|
25
|
+
|
|
26
|
+
Each agent writes its own daily summary. Persistent agents (OpenClaw, Letta) write from their own context. Ephemeral agents (Claude Code, Codex) get script-generated summaries from crystal + daily logs. Org-wide summaries combine all agents.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
~/.ldm/bin/ldm-summary.sh daily # today
|
|
30
|
+
~/.ldm/bin/ldm-summary.sh daily --team-only # team track only
|
|
31
|
+
~/.ldm/bin/ldm-summary.sh daily --dev-only # dev track only
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Backfill (historical)
|
|
35
|
+
|
|
36
|
+
Import and summarize everything from day 1. Uses `--force` to generate for all agents regardless of harness type.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
~/.ldm/bin/ldm-summary.sh daily --date 2026-02-10 --force # one day
|
|
40
|
+
bash scripts/backfill-summaries.sh # all days
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Output Locations
|
|
44
|
+
|
|
45
|
+
### Per-agent
|
|
46
|
+
```
|
|
47
|
+
~/wipcomputerinc/team/{agent}/automated/memory/summaries/
|
|
48
|
+
daily/YYYY-MM-DD.md
|
|
49
|
+
weekly/YYYY-MM-DD.md
|
|
50
|
+
monthly/YYYY-MM.md
|
|
51
|
+
quarterly/YYYY-QX.md
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Org-wide
|
|
55
|
+
```
|
|
56
|
+
~/wipcomputerinc/operations/updates/
|
|
57
|
+
team/daily/YYYY-MM-DD.md <- conversations, decisions, insights
|
|
58
|
+
dev/daily/YYYY-MM-DD.md <- code shipped, PRs, releases
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Recursive Consolidation
|
|
62
|
+
|
|
63
|
+
Each level reads the level below:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Transcripts + Crystal -> Daily summaries
|
|
67
|
+
7 dailies -> Weekly summary
|
|
68
|
+
4 weeklies -> Monthly summary
|
|
69
|
+
3 monthlies -> Quarterly summary
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This is the Dream Weaver paper's consolidation architecture applied at four cadences.
|
|
73
|
+
|
|
74
|
+
## Connection to Recall
|
|
75
|
+
|
|
76
|
+
[Recall](../recall/README.md) loads context at session start. Total Recall fills the memory that Recall serves. Without Total Recall, Recall only has what was captured going forward. With Total Recall, Recall has the complete history.
|
|
77
|
+
|
|
78
|
+
## Part of LDM OS
|
|
79
|
+
|
|
80
|
+
Total Recall is included with LDM OS. Summaries activate after `ldm init`. External imports are opt-in per platform.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
[Technical Reference](./TECHNICAL.md)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Total Recall ... Technical Reference
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
~/.ldm/agents/{agentId}/memory/ <- SOURCE (raw data per agent)
|
|
7
|
+
daily/ <- daily logs
|
|
8
|
+
journals/ <- Dream Weaver journals
|
|
9
|
+
sessions/ <- session exports (.md)
|
|
10
|
+
transcripts/ <- raw JSONL
|
|
11
|
+
|
|
|
12
|
+
v
|
|
13
|
+
Dream Weaver (prompts at ~/.ldm/shared/prompts/)
|
|
14
|
+
|
|
|
15
|
+
v
|
|
16
|
+
~/wipcomputerinc/team/{agent}/automated/memory/summaries/
|
|
17
|
+
daily/ weekly/ monthly/ quarterly/ <- per-agent summaries
|
|
18
|
+
|
|
|
19
|
+
v (combine all agents)
|
|
20
|
+
~/wipcomputerinc/operations/updates/
|
|
21
|
+
team/ daily/ weekly/ monthly/ quarterly/ <- org-wide team
|
|
22
|
+
dev/ daily/ weekly/ monthly/ quarterly/ <- org-wide dev (from git)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Harness Logic
|
|
26
|
+
|
|
27
|
+
Each agent in `~/.ldm/config.json` has a harness type:
|
|
28
|
+
|
|
29
|
+
| Harness | Behavior | Examples |
|
|
30
|
+
|---------|----------|---------|
|
|
31
|
+
| Persistent (openclaw, letta, hermes) | Agent writes own summaries via prompt | Lesa |
|
|
32
|
+
| Ephemeral (claude-code, codex) | Script generates from crystal + daily logs | CC |
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
for each agent:
|
|
36
|
+
if persistent AND summary file exists: skip (agent wrote it)
|
|
37
|
+
if persistent AND file missing AND --force: generate via script
|
|
38
|
+
if ephemeral: always generate via script
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Key Files
|
|
42
|
+
|
|
43
|
+
| File | What |
|
|
44
|
+
|------|------|
|
|
45
|
+
| `scripts/ldm-summary.sh` | Orchestrator. Per-agent search, org-wide combine. |
|
|
46
|
+
| `scripts/backfill-summaries.sh` | Loop: dailies -> weeklies -> monthlies -> quarterly |
|
|
47
|
+
| `shared/prompts/daily-agent-summary.md` | Prompt for daily per-agent summary |
|
|
48
|
+
| `shared/prompts/weekly-agent-summary.md` | Prompt for weekly (reads 7 dailies) |
|
|
49
|
+
| `shared/prompts/monthly-agent-summary.md` | Prompt for monthly (reads 4 weeklies) |
|
|
50
|
+
| `shared/prompts/quarterly-agent-summary.md` | Prompt for quarterly (reads 3 monthlies) |
|
|
51
|
+
| `shared/prompts/org-daily-team.md` | Prompt for combining agent summaries |
|
|
52
|
+
| `shared/prompts/daily-dev.md` | Prompt for git log summary |
|
|
53
|
+
| `bin/ldm.js` | Installer. Deploys scripts + prompts. Scaffolds dirs. |
|
|
54
|
+
|
|
55
|
+
## Data Sources
|
|
56
|
+
|
|
57
|
+
### Team track (conversations, decisions)
|
|
58
|
+
|
|
59
|
+
- `~/.ldm/agents/{agentId}/memory/daily/{date}.md` ... raw daily log
|
|
60
|
+
- `crystal search --agent {id} --since {date} --until {date+1}` ... crystal chunks for that day
|
|
61
|
+
- Both fed to Dream Weaver prompt to produce the summary
|
|
62
|
+
|
|
63
|
+
### Dev track (code shipped)
|
|
64
|
+
|
|
65
|
+
- `git log --since={date} --until={date+1} --oneline --all` across all repos in workspace
|
|
66
|
+
- Fed to prompt to produce factual dev summary
|
|
67
|
+
|
|
68
|
+
## Config
|
|
69
|
+
|
|
70
|
+
`~/wipcomputerinc/settings/config.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
"summaries": {
|
|
74
|
+
"cadences": ["daily", "weekly", "monthly", "quarterly"],
|
|
75
|
+
"tracks": ["team", "dev"],
|
|
76
|
+
"schedule": {
|
|
77
|
+
"daily": "06:00",
|
|
78
|
+
"weekly": "Monday 07:00",
|
|
79
|
+
"monthly": "1st 08:00",
|
|
80
|
+
"quarterly": "1st of Q 09:00"
|
|
81
|
+
},
|
|
82
|
+
"perAgent": "team/{agent}/automated/memory/summaries/",
|
|
83
|
+
"orgWide": "operations/updates/"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`~/.ldm/config.json`:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
"agents": ["cc-mini", "oc-lesa-mini"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Schedule
|
|
94
|
+
|
|
95
|
+
Cron via LDM Dev Tools.app:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
0 6 * * * ldm-summary.sh daily
|
|
99
|
+
0 7 * * 1 ldm-summary.sh weekly (Monday)
|
|
100
|
+
0 8 1 * * ldm-summary.sh monthly (1st of month)
|
|
101
|
+
0 9 1 1,4,7,10 * ldm-summary.sh quarterly (1st of quarter)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## External Imports (Future)
|
|
105
|
+
|
|
106
|
+
Total Recall will also import from external platforms:
|
|
107
|
+
|
|
108
|
+
| Platform | Method | Status |
|
|
109
|
+
|----------|--------|--------|
|
|
110
|
+
| Anthropic (Claude) | API / data export | Planned |
|
|
111
|
+
| OpenAI (ChatGPT) | API / data export | Planned |
|
|
112
|
+
| xAI (Grok) | API | Planned |
|
|
113
|
+
| X (Twitter) | Full archive import | Planned |
|
|
114
|
+
| Apple Music | Listening history | Planned |
|
|
115
|
+
| Browser | Chrome/Safari extension | Planned |
|
|
116
|
+
|
|
117
|
+
Each import follows the same pipeline: CONNECT -> IMPORT -> RELIVE -> CRYSTAL.
|
|
118
|
+
|
|
119
|
+
## Connection to Other Components
|
|
120
|
+
|
|
121
|
+
- **[Recall](../recall/TECHNICAL.md)** ... loads context at session start. Total Recall fills what Recall serves.
|
|
122
|
+
- **[Bridge](../bridge/TECHNICAL.md)** ... agent-to-agent communication. Summaries are shared via the workspace, not Bridge.
|
|
123
|
+
- **[Memory Crystal](https://github.com/wipcomputer/memory-crystal)** ... storage + search. Total Recall writes to Crystal. Crystal search provides data for summaries.
|
|
124
|
+
- **[Dream Weaver](https://github.com/wipcomputer/dream-weaver-protocol)** ... consolidation engine. Prompts invoke Dream Weaver via `claude -p`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wipcomputer/wip-ldm-os",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"engines": {
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"dist/bridge/",
|
|
38
38
|
"templates/",
|
|
39
39
|
"docs/",
|
|
40
|
+
"shared/",
|
|
41
|
+
"scripts/",
|
|
40
42
|
"catalog.json",
|
|
41
43
|
"SKILL.md"
|
|
42
44
|
],
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# backfill-summaries.sh — Generate all historical summaries from day 1.
|
|
3
|
+
# Part of Total Recall. Uses ldm-summary.sh with --force --date.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# backfill-summaries.sh # full backfill (Feb 5 to yesterday)
|
|
7
|
+
# backfill-summaries.sh --dry-run # preview
|
|
8
|
+
# backfill-summaries.sh --from 2026-03-01 # partial backfill
|
|
9
|
+
#
|
|
10
|
+
# Order: dailies first, then weeklies, monthlies, quarterly.
|
|
11
|
+
# Each level reads the level below. Must complete in order.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
|
|
15
|
+
|
|
16
|
+
SUMMARY_SCRIPT="$HOME/.ldm/bin/ldm-summary.sh"
|
|
17
|
+
DRY_RUN=false
|
|
18
|
+
FROM_DATE="2026-02-05"
|
|
19
|
+
|
|
20
|
+
while [[ $# -gt 0 ]]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
23
|
+
--from) FROM_DATE="$2"; shift 2 ;;
|
|
24
|
+
*) echo "Unknown: $1" >&2; exit 1 ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
if [ ! -f "$SUMMARY_SCRIPT" ]; then
|
|
29
|
+
echo "ERROR: ldm-summary.sh not found. Run ldm install first." >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
YESTERDAY=$(python3 -c "from datetime import datetime, timedelta; print((datetime.now()-timedelta(days=1)).strftime('%Y-%m-%d'))")
|
|
34
|
+
FLAGS="--force"
|
|
35
|
+
[ "$DRY_RUN" = true ] && FLAGS="$FLAGS --dry-run"
|
|
36
|
+
|
|
37
|
+
echo "=== Total Recall: Backfill Summaries ==="
|
|
38
|
+
echo " From: $FROM_DATE"
|
|
39
|
+
echo " To: $YESTERDAY"
|
|
40
|
+
echo " Mode: $([ "$DRY_RUN" = true ] && echo 'DRY RUN' || echo 'LIVE')"
|
|
41
|
+
echo ""
|
|
42
|
+
|
|
43
|
+
# ── Step 1: Dailies ──
|
|
44
|
+
|
|
45
|
+
echo "=== STEP 1: Daily summaries ==="
|
|
46
|
+
DAILY_COUNT=0
|
|
47
|
+
for date in $(python3 -c "
|
|
48
|
+
from datetime import datetime, timedelta
|
|
49
|
+
d = datetime.strptime('$FROM_DATE', '%Y-%m-%d')
|
|
50
|
+
end = datetime.strptime('$YESTERDAY', '%Y-%m-%d')
|
|
51
|
+
while d <= end:
|
|
52
|
+
print(d.strftime('%Y-%m-%d'))
|
|
53
|
+
d += timedelta(days=1)
|
|
54
|
+
"); do
|
|
55
|
+
echo "--- $date ---"
|
|
56
|
+
bash "$SUMMARY_SCRIPT" daily --date "$date" $FLAGS 2>&1 | grep -E "->|DRY RUN|No data|No git"
|
|
57
|
+
DAILY_COUNT=$((DAILY_COUNT + 1))
|
|
58
|
+
done
|
|
59
|
+
echo " Dailies: $DAILY_COUNT days"
|
|
60
|
+
echo ""
|
|
61
|
+
|
|
62
|
+
# ── Step 2: Weeklies (Sunday to Saturday) ──
|
|
63
|
+
|
|
64
|
+
echo "=== STEP 2: Weekly summaries ==="
|
|
65
|
+
WEEKLY_COUNT=0
|
|
66
|
+
for date in $(python3 -c "
|
|
67
|
+
from datetime import datetime, timedelta
|
|
68
|
+
# Start from first Sunday >= FROM_DATE
|
|
69
|
+
d = datetime.strptime('$FROM_DATE', '%Y-%m-%d')
|
|
70
|
+
while d.weekday() != 6: d += timedelta(days=1) # find Sunday
|
|
71
|
+
end = datetime.now()
|
|
72
|
+
while d <= end:
|
|
73
|
+
# Use the Saturday (end of week) as the date
|
|
74
|
+
sat = d + timedelta(days=6)
|
|
75
|
+
print(sat.strftime('%Y-%m-%d'))
|
|
76
|
+
d += timedelta(days=7)
|
|
77
|
+
"); do
|
|
78
|
+
echo "--- Week ending $date ---"
|
|
79
|
+
bash "$SUMMARY_SCRIPT" weekly --date "$date" $FLAGS 2>&1 | grep -E "->|DRY RUN|No "
|
|
80
|
+
WEEKLY_COUNT=$((WEEKLY_COUNT + 1))
|
|
81
|
+
done
|
|
82
|
+
echo " Weeklies: $WEEKLY_COUNT weeks"
|
|
83
|
+
echo ""
|
|
84
|
+
|
|
85
|
+
# ── Step 3: Monthlies ──
|
|
86
|
+
|
|
87
|
+
echo "=== STEP 3: Monthly summaries ==="
|
|
88
|
+
for date in $(python3 -c "
|
|
89
|
+
from datetime import datetime
|
|
90
|
+
import calendar
|
|
91
|
+
d = datetime.strptime('$FROM_DATE', '%Y-%m-%d')
|
|
92
|
+
now = datetime.now()
|
|
93
|
+
while d <= now:
|
|
94
|
+
last_day = calendar.monthrange(d.year, d.month)[1]
|
|
95
|
+
print(f'{d.year}-{d.month:02d}-{last_day:02d}')
|
|
96
|
+
if d.month == 12: d = d.replace(year=d.year+1, month=1, day=1)
|
|
97
|
+
else: d = d.replace(month=d.month+1, day=1)
|
|
98
|
+
"); do
|
|
99
|
+
echo "--- Month ending $date ---"
|
|
100
|
+
bash "$SUMMARY_SCRIPT" monthly --date "$date" $FLAGS 2>&1 | grep -E "->|DRY RUN|No "
|
|
101
|
+
done
|
|
102
|
+
echo ""
|
|
103
|
+
|
|
104
|
+
# ── Step 4: Quarterly ──
|
|
105
|
+
|
|
106
|
+
echo "=== STEP 4: Quarterly summary ==="
|
|
107
|
+
bash "$SUMMARY_SCRIPT" quarterly --date "$(date +%Y-%m-%d)" $FLAGS 2>&1 | grep -E "->|DRY RUN|No "
|
|
108
|
+
|
|
109
|
+
echo ""
|
|
110
|
+
echo "=== Backfill complete ==="
|
|
111
|
+
echo " $DAILY_COUNT dailies processed"
|
|
112
|
+
echo " $WEEKLY_COUNT weeklies processed"
|