chati-dev 4.0.11 → 4.1.0
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/README.md +5 -0
- package/bin/chati.js +16 -0
- package/framework/agents/build/dev.md +43 -10
- package/framework/agents/discover/brief.md +38 -8
- package/framework/agents/discover/brownfield-wu.md +44 -3
- package/framework/agents/discover/greenfield-wu.md +14 -0
- package/framework/agents/plan/architect.md +6 -0
- package/framework/agents/plan/detail.md +25 -0
- package/framework/agents/plan/tasks.md +29 -1
- package/framework/agents/plan/ux-brand-architect.md +215 -0
- package/framework/agents/plan/ux-component-engineer.md +289 -0
- package/framework/agents/plan/ux-researcher.md +166 -0
- package/framework/agents/plan/ux.md +126 -1008
- package/framework/agents/quality/qa-implementation.md +121 -22
- package/framework/agents/quality/qa-planning.md +18 -0
- package/framework/config.yaml +15 -4
- package/framework/constitution.md +8 -2
- package/framework/context/root.md +1 -1
- package/framework/data/entity-registry.yaml +59 -3
- package/framework/hooks/constitution-guard.js +67 -1
- package/framework/hooks/license-guard.js +4 -4
- package/framework/hooks/model-governance.js +2 -1
- package/framework/hooks/prism-engine.js +74 -6
- package/framework/hooks/read-protection.js +1 -1
- package/framework/hooks/session-digest.js +159 -7
- package/framework/hooks/settings.json +16 -4
- package/framework/hooks/style-guard.js +134 -0
- package/framework/hooks/undercover-guard.js +220 -0
- package/framework/intelligence/context-engine.md +21 -0
- package/framework/intelligence/hooks-performance.md +54 -0
- package/framework/intelligence/memory-layer.md +47 -0
- package/framework/migrations/v4.0-to-v4.1.yaml +165 -0
- package/framework/orchestrator/chati.md +327 -1067
- package/framework/templates/brandbook-html-tmpl.md +107 -0
- package/framework/templates/session-memory-tmpl.yaml +68 -0
- package/package.json +1 -1
- package/src/autonomy/build-state.js +1 -1
- package/src/autonomy/worktree-manager.js +13 -13
- package/src/config/agent-customizer.js +1 -1
- package/src/config/gemini-hooks-generator.js +6 -6
- package/src/config/ide-configs.js +1 -1
- package/src/context/bracket-tracker.js +25 -4
- package/src/context/engine.js +37 -7
- package/src/context/formatter.js +45 -1
- package/src/context/index.js +3 -3
- package/src/dashboard/data-reader.js +7 -7
- package/src/decision/engine.js +2 -2
- package/src/decision/registry-healer.js +1 -1
- package/src/decision/registry-updater.js +2 -2
- package/src/extensions/loader.js +1 -1
- package/src/gates/g1-planning-complete.js +1 -1
- package/src/gates/g2-qa-planning.js +1 -1
- package/src/gates/g3-implementation.js +4 -4
- package/src/gates/g4-qa-implementation.js +2 -2
- package/src/health/auto-fix.js +3 -3
- package/src/health/engine.js +2 -2
- package/src/installer/brownfield-upgrader.js +3 -3
- package/src/installer/manifest.js +1 -1
- package/src/installer/transaction.js +1 -1
- package/src/installer/validator.js +2 -2
- package/src/intelligence/decision-engine.js +1 -1
- package/src/intelligence/memory-manager.js +1 -1
- package/src/intelligence/registry-manager.js +2 -2
- package/src/intelligence/timeline.js +1 -1
- package/src/license/client.js +1 -1
- package/src/license/commands.js +2 -2
- package/src/memory/agent-memory.js +3 -3
- package/src/memory/daily-digest.js +170 -0
- package/src/memory/dream.js +254 -0
- package/src/memory/gotchas.js +2 -2
- package/src/memory/index.js +18 -0
- package/src/memory/magic-docs.js +98 -0
- package/src/memory/memory-extractor.js +163 -0
- package/src/memory/session-digest.js +144 -6
- package/src/merger/yaml-merger.js +1 -1
- package/src/orchestrator/cli.js +980 -0
- package/src/orchestrator/handoff-engine.js +25 -4
- package/src/orchestrator/index.js +9 -0
- package/src/orchestrator/session-manager.js +1 -1
- package/src/preview/detector.js +3 -3
- package/src/preview/launcher.js +2 -2
- package/src/quality/metrics-collector.js +1 -1
- package/src/quality/test-runner.js +2 -4
- package/src/scanning/density-scanner.js +51 -0
- package/src/scanning/env-scanner.js +97 -0
- package/src/scanning/index.js +7 -0
- package/src/scanning/leakage-scanner.js +54 -0
- package/src/scanning/placeholder-scanner.js +40 -0
- package/src/scanning/security-scanner.js +94 -0
- package/src/security/bash-security.js +335 -0
- package/src/security/index.js +9 -0
- package/src/telemetry/config.js +3 -3
- package/src/telemetry/sender.js +1 -1
- package/src/terminal/cli-registry.js +1 -1
- package/src/terminal/cost-tracker.js +1 -2
- package/src/terminal/prompt-builder.js +11 -27
- package/src/terminal/rate-limiter.js +1 -2
- package/src/terminal/run-agent.js +3 -3
- package/src/terminal/spawner.js +41 -4
- package/src/upgrade/checker.js +2 -2
- package/src/upgrade/migrator.js +1 -1
- package/src/utils/config-parser.js +1 -1
- package/src/utils/file-lock.js +3 -3
- package/src/utils/provider-limits.js +35 -4
- package/src/wizard/i18n.js +2 -2
- package/src/wizard/index.js +2 -1
- package/src/wizard/questions.js +4 -2
package/src/extensions/loader.js
CHANGED
|
@@ -89,7 +89,7 @@ export class ImplementationGate extends GateBase {
|
|
|
89
89
|
} else {
|
|
90
90
|
evidence.lintPasses = null; // Ambiguous — file exists but no clear verdict
|
|
91
91
|
}
|
|
92
|
-
} catch {
|
|
92
|
+
} catch { /* expected: file may not exist */
|
|
93
93
|
evidence.lintPasses = null;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -112,7 +112,7 @@ export class ImplementationGate extends GateBase {
|
|
|
112
112
|
} else {
|
|
113
113
|
evidence.securityClean = null;
|
|
114
114
|
}
|
|
115
|
-
} catch {
|
|
115
|
+
} catch { /* expected: file may not exist */
|
|
116
116
|
evidence.securityClean = null;
|
|
117
117
|
}
|
|
118
118
|
break;
|
|
@@ -126,7 +126,7 @@ export class ImplementationGate extends GateBase {
|
|
|
126
126
|
try {
|
|
127
127
|
const entries = readdirSync(srcDir, { recursive: false });
|
|
128
128
|
evidence.sourceFilesCreated = entries.length > 0;
|
|
129
|
-
} catch {
|
|
129
|
+
} catch { /* expected: directory may not exist */
|
|
130
130
|
// Ignore
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -138,7 +138,7 @@ export class ImplementationGate extends GateBase {
|
|
|
138
138
|
try {
|
|
139
139
|
const entries = readdirSync(testDir, { recursive: false });
|
|
140
140
|
evidence.testFilesCreated = entries.length > 0;
|
|
141
|
-
} catch {
|
|
141
|
+
} catch { /* expected: directory may not exist */
|
|
142
142
|
// Ignore
|
|
143
143
|
}
|
|
144
144
|
}
|
|
@@ -87,7 +87,7 @@ export class QAImplementationGate extends GateBase {
|
|
|
87
87
|
} else {
|
|
88
88
|
evidence.performanceBenchmarksMet = null; // Ambiguous
|
|
89
89
|
}
|
|
90
|
-
} catch {
|
|
90
|
+
} catch { /* expected: file may not exist */
|
|
91
91
|
evidence.performanceBenchmarksMet = null;
|
|
92
92
|
}
|
|
93
93
|
break;
|
|
@@ -114,7 +114,7 @@ export class QAImplementationGate extends GateBase {
|
|
|
114
114
|
// File exists but has no clear pass/fail indicators — ambiguous — not clean by default
|
|
115
115
|
evidence.securityScanClean = null;
|
|
116
116
|
}
|
|
117
|
-
} catch {
|
|
117
|
+
} catch { /* expected: operation may fail gracefully */
|
|
118
118
|
// File exists but cannot be read — treat as not available
|
|
119
119
|
evidence.securityScanClean = null;
|
|
120
120
|
}
|
package/src/health/auto-fix.js
CHANGED
|
@@ -129,7 +129,7 @@ function fixStaleLocks(projectDir) {
|
|
|
129
129
|
actions.push({ action: `Failed to check lock ${lockFile}`, success: false, error: err.message });
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
-
} catch {
|
|
132
|
+
} catch { /* expected: path may already be removed */
|
|
133
133
|
// Directory read failed — skip
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -199,11 +199,11 @@ function fixOrphanedWorktrees(projectDir) {
|
|
|
199
199
|
actions.push({ action: `Removed empty worktree dir: ${entry}`, success: true });
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
-
} catch {
|
|
202
|
+
} catch { /* expected: directory may not exist */
|
|
203
203
|
// Skip entries we can't read
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
-
} catch {
|
|
206
|
+
} catch { /* expected: directory may not exist */
|
|
207
207
|
// Directory read failed
|
|
208
208
|
}
|
|
209
209
|
|
package/src/health/engine.js
CHANGED
|
@@ -167,14 +167,14 @@ export async function checkDependencies() {
|
|
|
167
167
|
if (major < 20) {
|
|
168
168
|
issues.push(`Node.js ${nodeVersion} < required v20`);
|
|
169
169
|
}
|
|
170
|
-
} catch {
|
|
170
|
+
} catch { /* expected: content may be malformed */
|
|
171
171
|
issues.push('Node.js not found');
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// Check git
|
|
175
175
|
try {
|
|
176
176
|
execSync('git --version', { stdio: 'ignore' });
|
|
177
|
-
} catch {
|
|
177
|
+
} catch { /* expected: git command may fail */
|
|
178
178
|
issues.push('git not found');
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -186,8 +186,8 @@ export function upgradeInstallation(targetDir, newFrameworkDir, newVersion = '0.
|
|
|
186
186
|
result.preserved.push(filePath);
|
|
187
187
|
result.skipped.push(filePath);
|
|
188
188
|
}
|
|
189
|
-
} catch {
|
|
190
|
-
|
|
189
|
+
} catch (err) {
|
|
190
|
+
process.stderr.write(`[chati] upgrader smart-merge: ${err.message}\n`);
|
|
191
191
|
result.preserved.push(filePath);
|
|
192
192
|
result.skipped.push(filePath);
|
|
193
193
|
}
|
|
@@ -231,7 +231,7 @@ function collectRelativePaths(rootDir, base = '') {
|
|
|
231
231
|
let entries;
|
|
232
232
|
try {
|
|
233
233
|
entries = readdirSync(join(rootDir, base), { withFileTypes: true });
|
|
234
|
-
} catch {
|
|
234
|
+
} catch { /* expected: directory may not exist */
|
|
235
235
|
return paths;
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -16,7 +16,7 @@ let SIGNING_PUBLIC_KEY = null;
|
|
|
16
16
|
try {
|
|
17
17
|
const pemPath = join(__dirname, 'signing-public-key.pem');
|
|
18
18
|
SIGNING_PUBLIC_KEY = createPublicKey(readFileSync(pemPath));
|
|
19
|
-
} catch {
|
|
19
|
+
} catch { /* expected: file may not exist */
|
|
20
20
|
// Public key not available (dev environment without key generation)
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -203,7 +203,7 @@ export class InstallTransaction {
|
|
|
203
203
|
try {
|
|
204
204
|
rmSync(reverseOp.absPath, { recursive: true, force: true });
|
|
205
205
|
rolledBack++;
|
|
206
|
-
} catch {
|
|
206
|
+
} catch { /* expected: path may already be removed */
|
|
207
207
|
// Directory may not be empty due to other changes; skip
|
|
208
208
|
}
|
|
209
209
|
}
|
|
@@ -222,8 +222,8 @@ export async function validateInstallation(targetDir) {
|
|
|
222
222
|
|
|
223
223
|
results.context.details.push({ overlays: overlayDetails });
|
|
224
224
|
}
|
|
225
|
-
} catch {
|
|
226
|
-
|
|
225
|
+
} catch (err) {
|
|
226
|
+
process.stderr.write(`[chati] validator config-parse: ${err.message}\n`);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -118,7 +118,7 @@ export function runHealthCheck(targetDir) {
|
|
|
118
118
|
try {
|
|
119
119
|
JSON.parse(readFileSync(schemaPath, 'utf-8'));
|
|
120
120
|
validSchemas++;
|
|
121
|
-
} catch {
|
|
121
|
+
} catch { /* expected: file may not exist */
|
|
122
122
|
// Invalid JSON
|
|
123
123
|
}
|
|
124
124
|
}
|
|
@@ -176,7 +176,7 @@ function loadRegistry(registryPath) {
|
|
|
176
176
|
try {
|
|
177
177
|
const content = readFileSync(registryPath, 'utf-8');
|
|
178
178
|
return yaml.load(content);
|
|
179
|
-
} catch {
|
|
179
|
+
} catch { /* expected: file may not exist */
|
|
180
180
|
return null;
|
|
181
181
|
}
|
|
182
182
|
}
|
package/src/license/client.js
CHANGED
|
@@ -142,7 +142,7 @@ export async function deactivateLicense(_projectDir) {
|
|
|
142
142
|
body: JSON.stringify({ key, machine_id: machineId }),
|
|
143
143
|
signal: AbortSignal.timeout(5000),
|
|
144
144
|
});
|
|
145
|
-
} catch {
|
|
145
|
+
} catch { /* expected: network may be unavailable */
|
|
146
146
|
// Fail silently — local deactivation still proceeds
|
|
147
147
|
}
|
|
148
148
|
}
|
package/src/license/commands.js
CHANGED
|
@@ -79,8 +79,8 @@ export async function runLicenseStatus(projectDir) {
|
|
|
79
79
|
let status;
|
|
80
80
|
try {
|
|
81
81
|
status = await validateLicense(projectDir);
|
|
82
|
-
} catch {
|
|
83
|
-
|
|
82
|
+
} catch (err) {
|
|
83
|
+
process.stderr.write(`[chati] license validate: ${err.message}\n`);
|
|
84
84
|
status = getLicenseStatus(projectDir) ?? { status: 'UNKNOWN' };
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -123,7 +123,7 @@ export function readAgentMemory(projectDir, agentName) {
|
|
|
123
123
|
entries,
|
|
124
124
|
raw,
|
|
125
125
|
};
|
|
126
|
-
} catch {
|
|
126
|
+
} catch { /* expected: file may not exist */
|
|
127
127
|
return {
|
|
128
128
|
loaded: false,
|
|
129
129
|
entries: [],
|
|
@@ -208,7 +208,7 @@ export function searchAgentMemories(projectDir, query) {
|
|
|
208
208
|
}
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
|
-
} catch {
|
|
211
|
+
} catch { /* expected: operation may fail gracefully */
|
|
212
212
|
return [];
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -267,7 +267,7 @@ export function getAgentMemoryStats(projectDir) {
|
|
|
267
267
|
categories: [...new Set(memory.entries.map(e => e.category))],
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
|
-
} catch {
|
|
270
|
+
} catch { /* expected: operation may fail gracefully */
|
|
271
271
|
return { byAgent: {} };
|
|
272
272
|
}
|
|
273
273
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daily Digest — KAIROS Lite session summary system.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Claude Code's KAIROS daemon (append-only daily logs),
|
|
5
|
+
* but implemented as a lightweight non-daemon approach that generates
|
|
6
|
+
* daily summaries on demand or at session end.
|
|
7
|
+
*
|
|
8
|
+
* Creates daily digest files at .chati/memories/shared/daily/YYYY-MM-DD.md
|
|
9
|
+
* with cumulative session activity: tasks completed/failed, decisions,
|
|
10
|
+
* gotchas, agent scores, and timing.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
|
|
16
|
+
const DAILY_DIR = '.chati/memories/shared/daily';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build a daily digest entry from the current session state.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} projectDir - Project root
|
|
22
|
+
* @param {object} sessionState - Current session.yaml data
|
|
23
|
+
* @param {object} [extras={}] - Additional context
|
|
24
|
+
* @param {string[]} [extras.decisions] - Decisions made
|
|
25
|
+
* @param {string[]} [extras.gotchas] - Gotchas found
|
|
26
|
+
* @param {number} [extras.duration] - Session duration in minutes
|
|
27
|
+
* @returns {object} Digest entry
|
|
28
|
+
*/
|
|
29
|
+
export function buildDailyEntry(projectDir, sessionState, extras = {}) {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const agents = sessionState.agents || {};
|
|
32
|
+
|
|
33
|
+
// Compute agent stats
|
|
34
|
+
const completed = [];
|
|
35
|
+
const failed = [];
|
|
36
|
+
const scores = {};
|
|
37
|
+
|
|
38
|
+
for (const [name, data] of Object.entries(agents)) {
|
|
39
|
+
if (data.status === 'completed') {
|
|
40
|
+
completed.push(name);
|
|
41
|
+
if (data.validation_score) scores[name] = data.validation_score;
|
|
42
|
+
} else if (data.status === 'failed') {
|
|
43
|
+
failed.push(name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const avgScore = Object.values(scores).length > 0
|
|
48
|
+
? Math.round(Object.values(scores).reduce((a, b) => a + b, 0) / Object.values(scores).length)
|
|
49
|
+
: null;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
time: now.toISOString().split('T')[1].split('.')[0],
|
|
53
|
+
phase: sessionState.project?.state || sessionState.mode || 'unknown',
|
|
54
|
+
current_agent: sessionState.current_agent || 'none',
|
|
55
|
+
tasks_completed: completed.length,
|
|
56
|
+
tasks_failed: failed.length,
|
|
57
|
+
agents_completed: completed,
|
|
58
|
+
agents_failed: failed,
|
|
59
|
+
avg_score: avgScore,
|
|
60
|
+
scores,
|
|
61
|
+
decisions: extras.decisions || [],
|
|
62
|
+
gotchas: extras.gotchas || [],
|
|
63
|
+
duration_min: extras.duration || null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Append an entry to today's daily digest file.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} projectDir - Project root
|
|
71
|
+
* @param {object} entry - Digest entry from buildDailyEntry()
|
|
72
|
+
* @returns {{ saved: boolean, path: string }}
|
|
73
|
+
*/
|
|
74
|
+
export function appendDailyDigest(projectDir, entry) {
|
|
75
|
+
const dir = join(projectDir, DAILY_DIR);
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
|
|
78
|
+
const dateStr = new Date().toISOString().split('T')[0];
|
|
79
|
+
const filePath = join(dir, `${dateStr}.md`);
|
|
80
|
+
|
|
81
|
+
// Build markdown entry
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push(`### ${entry.time} — ${entry.phase}`);
|
|
84
|
+
lines.push(`- **Agent**: ${entry.current_agent}`);
|
|
85
|
+
lines.push(`- **Completed**: ${entry.tasks_completed} | **Failed**: ${entry.tasks_failed}`);
|
|
86
|
+
if (entry.avg_score) lines.push(`- **Avg Score**: ${entry.avg_score}%`);
|
|
87
|
+
if (entry.duration_min) lines.push(`- **Duration**: ${entry.duration_min} min`);
|
|
88
|
+
|
|
89
|
+
if (entry.agents_completed.length > 0) {
|
|
90
|
+
lines.push(`- **Agents done**: ${entry.agents_completed.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
if (entry.agents_failed.length > 0) {
|
|
93
|
+
lines.push(`- **Agents failed**: ${entry.agents_failed.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
if (entry.decisions.length > 0) {
|
|
96
|
+
lines.push('- **Decisions**:');
|
|
97
|
+
for (const d of entry.decisions.slice(0, 5)) {
|
|
98
|
+
lines.push(` - ${d}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (entry.gotchas.length > 0) {
|
|
102
|
+
lines.push('- **Gotchas**:');
|
|
103
|
+
for (const g of entry.gotchas.slice(0, 5)) {
|
|
104
|
+
lines.push(` - ${g}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
lines.push('');
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
let existing = '';
|
|
111
|
+
if (existsSync(filePath)) {
|
|
112
|
+
existing = readFileSync(filePath, 'utf-8');
|
|
113
|
+
} else {
|
|
114
|
+
// Create header for new daily file
|
|
115
|
+
existing = `# Daily Digest — ${dateStr}\n\n`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
writeFileSync(filePath, existing + lines.join('\n') + '\n', 'utf-8');
|
|
119
|
+
return { saved: true, path: filePath };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
process.stderr.write(`[chati] daily-digest append: ${err.message}\n`);
|
|
122
|
+
return { saved: false, path: filePath };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Load today's daily digest.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} projectDir - Project root
|
|
130
|
+
* @returns {{ loaded: boolean, content: string|null, path: string }}
|
|
131
|
+
*/
|
|
132
|
+
export function loadTodayDigest(projectDir) {
|
|
133
|
+
const dateStr = new Date().toISOString().split('T')[0];
|
|
134
|
+
const filePath = join(projectDir, DAILY_DIR, `${dateStr}.md`);
|
|
135
|
+
|
|
136
|
+
if (!existsSync(filePath)) {
|
|
137
|
+
return { loaded: false, content: null, path: filePath };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
142
|
+
return { loaded: true, content, path: filePath };
|
|
143
|
+
} catch (err) {
|
|
144
|
+
process.stderr.write(`[chati] daily-digest load today: ${err.message}\n`);
|
|
145
|
+
return { loaded: false, content: null, path: filePath };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Load a specific date's digest.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} projectDir - Project root
|
|
153
|
+
* @param {string} dateStr - Date in YYYY-MM-DD format
|
|
154
|
+
* @returns {{ loaded: boolean, content: string|null, path: string }}
|
|
155
|
+
*/
|
|
156
|
+
export function loadDigestByDate(projectDir, dateStr) {
|
|
157
|
+
const filePath = join(projectDir, DAILY_DIR, `${dateStr}.md`);
|
|
158
|
+
|
|
159
|
+
if (!existsSync(filePath)) {
|
|
160
|
+
return { loaded: false, content: null, path: filePath };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
165
|
+
return { loaded: true, content, path: filePath };
|
|
166
|
+
} catch (err) {
|
|
167
|
+
process.stderr.write(`[chati] daily-digest load date: ${err.message}\n`);
|
|
168
|
+
return { loaded: false, content: null, path: filePath };
|
|
169
|
+
}
|
|
170
|
+
}
|