@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.2
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/INSTALL.md +25 -0
- package/PANEL.md +92 -0
- package/README.md +286 -2
- package/RELEASE-NOTES.md +732 -0
- package/bin/bc-acceptance-full.mjs +91 -0
- package/bin/bc-acceptance-tight.mjs +66 -0
- package/bin/bc-acceptance.mjs +22 -0
- package/bin/bc-add-keywords.mjs +21 -0
- package/bin/bc-demo.mjs +47 -0
- package/bin/bc-expand-hint.mjs +179 -0
- package/bin/bc-ingest-index.mjs +35 -0
- package/bin/bc-ingest.mjs +31 -0
- package/bin/bc-label-demo.mjs +18 -0
- package/bin/bc-relabel.mjs +150 -0
- package/bin/bc-sessionize.mjs +59 -0
- package/bin/coref_pipeline.py +47 -0
- package/bin/coref_service.py +57 -0
- package/bin/debug_ckip.py +16 -0
- package/bin/debug_ner_zh.py +18 -0
- package/bin/doctor.mjs +275 -0
- package/bin/en_ner_error_audit.py +33 -0
- package/bin/eval-label-rules.mjs +342 -0
- package/bin/export-turns.py +68 -0
- package/bin/extract-user-paragraph.mjs +14 -0
- package/bin/extraction-composite-smoke.mjs +28 -0
- package/bin/extraction-latency.mjs +58 -0
- package/bin/extraction-user-smoke.mjs +28 -0
- package/bin/extraction_service.py +164 -0
- package/bin/facet-active-experiment.mjs +110 -0
- package/bin/facet-covid.mjs +8 -0
- package/bin/facet-curve.mjs +27 -0
- package/bin/facet-embedding-train.mjs +20 -0
- package/bin/facet-embedding-validate.mjs +83 -0
- package/bin/facet-eval-roleplay.mjs +42 -0
- package/bin/facet-eval.mjs +38 -0
- package/bin/facet-fold.mjs +31 -0
- package/bin/facet-load.mjs +16 -0
- package/bin/facet-mlp-scan.mjs +45 -0
- package/bin/facet-mvp.mjs +34 -0
- package/bin/facet-online-experiments.mjs +78 -0
- package/bin/facet-online-race.mjs +99 -0
- package/bin/facet-online-validate.mjs +51 -0
- package/bin/facet-panel.mjs +22 -0
- package/bin/facet-passage.mjs +41 -0
- package/bin/facet-random-validate.mjs +48 -0
- package/bin/facet-roleplay.mjs +33 -0
- package/bin/facet-scan.mjs +56 -0
- package/bin/facet-segment.mjs +34 -0
- package/bin/facet-train.mjs +19 -0
- package/bin/facet-validate.mjs +103 -0
- package/bin/facet-zeroshot.mjs +14 -0
- package/bin/install-templates.mjs +72 -0
- package/bin/memory-bench.mjs +53 -0
- package/bin/memory-build-summary.mjs +43 -0
- package/bin/memory-build-vector.mjs +31 -0
- package/bin/memory-build.mjs +41 -0
- package/bin/memory-calibration.mjs +70 -0
- package/bin/memory-closed-loop.mjs +119 -0
- package/bin/memory-dry-run.mjs +70 -0
- package/bin/memory-eval.mjs +104 -0
- package/bin/memory-event-alias-test.mjs +89 -0
- package/bin/memory-extraction-bench.mjs +58 -0
- package/bin/memory-load-gate-replay.mjs +137 -0
- package/bin/memory-loop-test.mjs +90 -0
- package/bin/memory-pre-turn-harness.mjs +63 -0
- package/bin/memory-query.mjs +39 -0
- package/bin/memory-replay-hints.mjs +84 -0
- package/bin/memory-scale.mjs +58 -0
- package/bin/memory-scenario-test.mjs +146 -0
- package/bin/memory-user-loop.mjs +95 -0
- package/bin/ner_bench_tiny_zh.py +59 -0
- package/bin/ner_bench_zh.py +60 -0
- package/bin/ner_conll_en.py +139 -0
- package/bin/ner_direct_en.py +123 -0
- package/bin/ner_direct_zh.py +100 -0
- package/bin/ner_pipeline.py +208 -0
- package/bin/noise-selfcheck.mjs +50 -0
- package/bin/pack-integrity.mjs +47 -0
- package/bin/panel-check.mjs +57 -0
- package/bin/plugin-integrity.mjs +74 -0
- package/bin/preview-live.mjs +13 -0
- package/bin/public-install-check.mjs +222 -0
- package/bin/relabel-apply.mjs +272 -0
- package/bin/relabel-dry-run.mjs +381 -0
- package/bin/relik_pipeline.py +39 -0
- package/bin/relik_service.py +52 -0
- package/bin/srl_pipeline.py +55 -0
- package/bin/srl_service.py +68 -0
- package/bin/summary-index.mjs +151 -0
- package/bin/summary-query.mjs +79 -0
- package/bin/vec-check.mjs +27 -0
- package/bin/zh_ner_error_audit.py +36 -0
- package/bin/zh_ner_slot_eval.py +38 -0
- package/client.js +760 -0
- package/cordis.patch.yml +378 -0
- package/docs/API-NOTES.md +1743 -0
- package/docs/ARCHITECTURE.md +355 -0
- package/docs/CACHE.md +253 -0
- package/docs/CONDUCT.md +30 -0
- package/docs/CREATOR-HANDOFF.md +102 -0
- package/docs/EXPERIMENT.md +136 -0
- package/docs/FACET-MVP-REPORT.md +473 -0
- package/docs/GUIDE-0.4.66.md +195 -0
- package/docs/GUIDE.md +127 -0
- package/docs/MECHANISM.md +243 -0
- package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
- package/docs/README.md +37 -0
- package/docs/SESSION-LAYOUT.md +151 -0
- package/docs/history-map.md +249 -0
- package/docs/relabel-migration-wiring.md +366 -0
- package/guides/00-READ-FIRST.md +206 -0
- package/guides/01-INSTALL.md +207 -0
- package/guides/02-TABLES.md +225 -0
- package/guides/03-AUDIT.md +201 -0
- package/guides/04-REBUILD.md +306 -0
- package/guides/05-FIRST-RUN.md +191 -0
- package/guides/06-EMBEDDING.md +146 -0
- package/guides/07-PTC-AND-ASSETS.md +221 -0
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/models/README.md +13 -0
- package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
- package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
- package/package.json +139 -4
- package/src/asset-tool.mjs +237 -0
- package/src/assets.mjs +244 -0
- package/src/backfill.mjs +74 -0
- package/src/backup.mjs +80 -0
- package/src/deadend.mjs +95 -0
- package/src/deleted-store.mjs +35 -0
- package/src/engine.mjs +3831 -0
- package/src/facets/classifier.mjs +146 -0
- package/src/facets/embedding-service.mjs +53 -0
- package/src/facets/embedding-worker.mjs +27 -0
- package/src/facets/embedding.mjs +163 -0
- package/src/facets/extract.mjs +80 -0
- package/src/facets/fold-plan.mjs +84 -0
- package/src/facets/hybrid.mjs +29 -0
- package/src/facets/index.mjs +113 -0
- package/src/facets/linear-probe.mjs +43 -0
- package/src/facets/load.mjs +37 -0
- package/src/facets/mlp-probe.mjs +54 -0
- package/src/facets/mlp-service.mjs +53 -0
- package/src/facets/mlp-worker.mjs +26 -0
- package/src/facets/online-knn.mjs +58 -0
- package/src/facets/online-models.mjs +227 -0
- package/src/facets/panel-state.mjs +23 -0
- package/src/facets/panel.mjs +73 -0
- package/src/facets/random.mjs +26 -0
- package/src/facets/scanner.mjs +35 -0
- package/src/facets/schema.mjs +75 -0
- package/src/facets/seed-examples.mjs +58 -0
- package/src/facets/sensitivity.mjs +98 -0
- package/src/facets/service.mjs +53 -0
- package/src/facets/store.mjs +39 -0
- package/src/facets/worker.mjs +23 -0
- package/src/folds-store.mjs +109 -0
- package/src/groups.mjs +167 -0
- package/src/handoff-tool.mjs +51 -0
- package/src/handoff.mjs +127 -0
- package/src/index.mjs +649 -0
- package/src/inject-policy.mjs +17 -0
- package/src/learning.mjs +88 -0
- package/src/memory/alias-proposal.mjs +40 -0
- package/src/memory/bc-audit.mjs +245 -0
- package/src/memory/bc-index-updater.mjs +404 -0
- package/src/memory/bc-index.mjs +48 -0
- package/src/memory/bc-label.mjs +220 -0
- package/src/memory/bc-panel-state.mjs +107 -0
- package/src/memory/bc-pre-step.mjs +463 -0
- package/src/memory/bc-recall.mjs +470 -0
- package/src/memory/bc-shift.mjs +123 -0
- package/src/memory/bc-store.mjs +41 -0
- package/src/memory/bm25.mjs +112 -0
- package/src/memory/calibration.mjs +54 -0
- package/src/memory/chatter.mjs +29 -0
- package/src/memory/cmap-file.mjs +107 -0
- package/src/memory/concepts-file.mjs +41 -0
- package/src/memory/coref-slots.mjs +40 -0
- package/src/memory/event-alias-ledger.mjs +87 -0
- package/src/memory/event-log.mjs +95 -0
- package/src/memory/event-model.mjs +132 -0
- package/src/memory/event-segmenter.mjs +36 -0
- package/src/memory/event-summary-tree.mjs +122 -0
- package/src/memory/extraction-adapter.mjs +76 -0
- package/src/memory/extraction-composite.mjs +75 -0
- package/src/memory/extraction-core.mjs +76 -0
- package/src/memory/extraction-pre-step.mjs +161 -0
- package/src/memory/extraction-provider.mjs +300 -0
- package/src/memory/extraction-score.mjs +36 -0
- package/src/memory/facet-stabilizer.mjs +59 -0
- package/src/memory/history-map.mjs +139 -0
- package/src/memory/language-router.mjs +30 -0
- package/src/memory/load-gate.mjs +161 -0
- package/src/memory/mechanical-note.mjs +104 -0
- package/src/memory/onboarding.mjs +163 -0
- package/src/memory/persona.mjs +78 -0
- package/src/memory/planner.mjs +249 -0
- package/src/memory/pre-turn.mjs +80 -0
- package/src/memory/query-aliases.mjs +39 -0
- package/src/memory/recall-cue.mjs +43 -0
- package/src/memory/recall.mjs +65 -0
- package/src/memory/referent-candidates.mjs +55 -0
- package/src/memory/referent-ledger.mjs +86 -0
- package/src/memory/relabel-migrate.mjs +376 -0
- package/src/memory/relik-adapter.mjs +25 -0
- package/src/memory/special-keywords.mjs +60 -0
- package/src/memory/structured-adapters.mjs +75 -0
- package/src/memory/summary-index.mjs +335 -0
- package/src/memory/summary-tree.mjs +110 -0
- package/src/memory/term-extractor.mjs +44 -0
- package/src/memory/tokenize.mjs +9 -0
- package/src/memory/vector-index.mjs +54 -0
- package/src/memory/window-summary.mjs +133 -0
- package/src/mood.mjs +133 -0
- package/src/peer-tool.mjs +63 -0
- package/src/peer.mjs +144 -0
- package/src/policy.mjs +170 -0
- package/src/preflight.mjs +113 -0
- package/src/provenance.mjs +94 -0
- package/src/range.mjs +75 -0
- package/src/recall-exact-tool.mjs +267 -0
- package/src/recall-tool.mjs +521 -0
- package/src/recheck.mjs +99 -0
- package/src/save-start.mjs +29 -0
- package/src/session-guard.mjs +140 -0
- package/src/session-log-tool.mjs +322 -0
- package/src/session-log.mjs +606 -0
- package/src/skills.mjs +111 -0
- package/src/split-range.mjs +115 -0
- package/src/state.mjs +198 -0
- package/src/t0.mjs +149 -0
- package/src/tag-confirm.mjs +78 -0
- package/src/tag-detect.mjs +42 -0
- package/src/tag-maintenance.mjs +285 -0
- package/src/tag-reject.mjs +73 -0
- package/src/tags-store.mjs +94 -0
- package/src/tags.mjs +692 -0
- package/src/threads-backend.mjs +26 -0
- package/src/threads-core.mjs +1887 -0
- package/src/threads-store.mjs +207 -0
- package/src/threads-tool.mjs +522 -0
- package/src/threads.mjs +929 -0
- package/src/tool-preview.mjs +427 -0
- package/src/topic.mjs +63 -0
- package/src/training.mjs +75 -0
- package/src/turn-tag.mjs +62 -0
- package/src/user-turn.mjs +39 -0
- package/templates/cmap.md +11 -0
- package/templates/concepts.md +12 -0
- package/templates/habits.md +40 -0
- package/templates/keywords.md +6 -0
- package/templates/rebuild.md +134 -0
- package/templates/rules-safe.md +184 -0
- package/templates/rules.md +177 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable Continuity thread state, partitioned by session.
|
|
3
|
+
*
|
|
4
|
+
* The session log remains the source of truth for what the model saw; this
|
|
5
|
+
* file only stores the thread index and the active pointer. Rules/habits are
|
|
6
|
+
* read per session and seeded from the plugin's root templates.
|
|
7
|
+
*
|
|
8
|
+
* Defaults: ~/.dsh/continuity/threads.json and
|
|
9
|
+
* ~/.dsh/continuity/sessions/<id>/{rules,habits}.md.
|
|
10
|
+
* DSH_CONTINUITY_HOME overrides the directory for tests and isolated profiles.
|
|
11
|
+
*/
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { mkdir, copyFile, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { normalizeThread } from './threads.mjs';
|
|
17
|
+
|
|
18
|
+
let writeQueue = Promise.resolve();
|
|
19
|
+
|
|
20
|
+
export function continuityHome() {
|
|
21
|
+
const override = process.env.DSH_CONTINUITY_HOME;
|
|
22
|
+
return override !== undefined && override.length > 0
|
|
23
|
+
? override
|
|
24
|
+
: join(homedir(), '.dsh', 'continuity');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function threadsFile() {
|
|
28
|
+
return join(continuityHome(), 'threads.json');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function rulesFile(sessionId = null) {
|
|
32
|
+
return sessionId === null ? join(continuityHome(), 'rules.md') : join(continuityHome(), 'sessions', String(sessionId), 'rules.md');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function habitsFile(sessionId = null) {
|
|
36
|
+
return sessionId === null ? join(continuityHome(), 'habits.md') : join(continuityHome(), 'sessions', String(sessionId), 'habits.md');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function templateFile(name) {
|
|
40
|
+
return fileURLToPath(new URL(`../templates/${name}`, import.meta.url));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Seed a session's rules/habits from the plugin root template, never from another session. */
|
|
44
|
+
async function ensureSessionFile(sessionId, name) {
|
|
45
|
+
const target = name === 'rules.md' ? rulesFile(sessionId) : habitsFile(sessionId);
|
|
46
|
+
try {
|
|
47
|
+
await readFile(target, 'utf8');
|
|
48
|
+
return target;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
51
|
+
}
|
|
52
|
+
await mkdir(join(continuityHome(), 'sessions', String(sessionId)), { recursive: true });
|
|
53
|
+
try {
|
|
54
|
+
const source = templateFile(name);
|
|
55
|
+
await copyFile(source, target);
|
|
56
|
+
} catch {
|
|
57
|
+
await writeFile(target, '', 'utf8');
|
|
58
|
+
}
|
|
59
|
+
return target;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Read the whole state file; a missing file is an empty state, never an error. */
|
|
63
|
+
export async function readState() {
|
|
64
|
+
try {
|
|
65
|
+
const raw = await readFile(threadsFile(), 'utf8');
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
68
|
+
throw new Error('threads.json must contain an object');
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
version: 1,
|
|
72
|
+
sessions: parsed.sessions !== null && typeof parsed.sessions === 'object' && !Array.isArray(parsed.sessions)
|
|
73
|
+
? parsed.sessions
|
|
74
|
+
: {},
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error?.code === 'ENOENT') return { version: 1, sessions: {} };
|
|
78
|
+
throw new Error(`continuity: cannot read ${threadsFile()}: ${error?.message ?? error}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function writeState(state) {
|
|
83
|
+
const dir = continuityHome();
|
|
84
|
+
await mkdir(dir, { recursive: true });
|
|
85
|
+
const file = threadsFile();
|
|
86
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
87
|
+
try {
|
|
88
|
+
await writeFile(temp, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
89
|
+
await rename(temp, file);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
try { await unlink(temp); } catch { /* best effort */ }
|
|
92
|
+
throw new Error(`continuity: cannot write ${file}: ${error?.message ?? error}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** One session's state, or an empty shell. */
|
|
97
|
+
export async function getSession(sessionId) {
|
|
98
|
+
const id = String(sessionId);
|
|
99
|
+
const state = await readState();
|
|
100
|
+
const session = state.sessions[id];
|
|
101
|
+
if (session === null || session === undefined || typeof session !== 'object') {
|
|
102
|
+
return { threads: [], activeId: null, pending: null };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
threads: Array.isArray(session.threads) ? session.threads.map((thread) => normalizeThread(thread)) : [],
|
|
106
|
+
activeId: typeof session.activeId === 'string' ? session.activeId : null,
|
|
107
|
+
pending: session.pending !== null && typeof session.pending === 'object' && !Array.isArray(session.pending)
|
|
108
|
+
? session.pending
|
|
109
|
+
: null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Serialize a read-modify-write against one session. `mutate` receives a deep
|
|
115
|
+
* copy and returns the next session (or mutates and returns undefined).
|
|
116
|
+
*/
|
|
117
|
+
export async function updateSession(sessionId, mutate) {
|
|
118
|
+
const id = String(sessionId);
|
|
119
|
+
const operation = writeQueue.then(async () => {
|
|
120
|
+
const state = await readState();
|
|
121
|
+
const current = state.sessions[id];
|
|
122
|
+
const base = current !== null && current !== undefined && typeof current === 'object'
|
|
123
|
+
? {
|
|
124
|
+
threads: Array.isArray(current.threads) ? current.threads.map((thread) => normalizeThread(thread)) : [],
|
|
125
|
+
activeId: typeof current.activeId === 'string' ? current.activeId : null,
|
|
126
|
+
pending: current.pending !== null && typeof current.pending === 'object' && !Array.isArray(current.pending)
|
|
127
|
+
? current.pending
|
|
128
|
+
: null,
|
|
129
|
+
}
|
|
130
|
+
: { threads: [], activeId: null, pending: null };
|
|
131
|
+
const working = structuredClone(base);
|
|
132
|
+
const next = await mutate(working);
|
|
133
|
+
const value = next !== null && next !== undefined && typeof next === 'object' ? next : working;
|
|
134
|
+
state.sessions[id] = {
|
|
135
|
+
threads: Array.isArray(value.threads) ? value.threads.map((thread) => normalizeThread(thread)) : [],
|
|
136
|
+
activeId: typeof value.activeId === 'string' ? value.activeId : null,
|
|
137
|
+
pending: value.pending !== null && typeof value.pending === 'object' && !Array.isArray(value.pending)
|
|
138
|
+
? value.pending
|
|
139
|
+
: null,
|
|
140
|
+
updatedAt: new Date().toISOString(),
|
|
141
|
+
};
|
|
142
|
+
await writeState(state);
|
|
143
|
+
return state.sessions[id];
|
|
144
|
+
});
|
|
145
|
+
writeQueue = operation.catch(() => {});
|
|
146
|
+
return operation;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* One-shot read-time normalization migration. Older backfill rows stored the
|
|
151
|
+
* write moment in `at`; normalize them on disk too, so direct threads.json
|
|
152
|
+
* consumers see the same content time as the plugin.
|
|
153
|
+
*/
|
|
154
|
+
export async function migrateThreadsFile({ log = null } = {}) {
|
|
155
|
+
const state = await readState();
|
|
156
|
+
const changes = [];
|
|
157
|
+
for (const [sessionId, session] of Object.entries(state.sessions)) {
|
|
158
|
+
if (!Array.isArray(session?.threads) || session.threads.length === 0) continue;
|
|
159
|
+
const normalized = session.threads.map((thread) => normalizeThread(thread));
|
|
160
|
+
if (JSON.stringify(normalized) !== JSON.stringify(session.threads)) {
|
|
161
|
+
changes.push({ sessionId, normalized });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (changes.length === 0) return { changed: 0, backup: null };
|
|
165
|
+
|
|
166
|
+
const file = threadsFile();
|
|
167
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
168
|
+
const backup = `${file}.bak-normalize-${stamp}`;
|
|
169
|
+
try { await copyFile(file, backup); } catch { /* a backup failure must not block normalization */ }
|
|
170
|
+
|
|
171
|
+
for (const change of changes) {
|
|
172
|
+
await updateSession(change.sessionId, (current) => {
|
|
173
|
+
current.threads = change.normalized;
|
|
174
|
+
return current;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
try { log?.info?.(`[dsh-continuity] normalized ${changes.length} session(s) on disk; backup=${backup}`); } catch { /* logging only */ }
|
|
178
|
+
return { changed: changes.length, backup };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Read the user's rules file verbatim. It is optional and has no default
|
|
183
|
+
* content; the plugin never creates or writes it.
|
|
184
|
+
* @returns {Promise<{ text: string, missing: boolean, path: string }>}
|
|
185
|
+
*/
|
|
186
|
+
export async function readRules(sessionId = null) {
|
|
187
|
+
const path = sessionId === null ? rulesFile() : await ensureSessionFile(sessionId, 'rules.md');
|
|
188
|
+
try {
|
|
189
|
+
const text = await readFile(path, 'utf8');
|
|
190
|
+
return { text, missing: false, path };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error?.code === 'ENOENT') return { text: '', missing: true, path };
|
|
193
|
+
throw new Error(`continuity: cannot read ${path}: ${error?.message ?? error}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Soft preferences: optional, same read-only contract as rules. */
|
|
198
|
+
export async function readHabits(sessionId = null) {
|
|
199
|
+
const path = sessionId === null ? habitsFile() : await ensureSessionFile(sessionId, 'habits.md');
|
|
200
|
+
try {
|
|
201
|
+
const text = await readFile(path, 'utf8');
|
|
202
|
+
return { text, missing: false, path };
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error?.code === 'ENOENT') return { text: '', missing: true, path };
|
|
205
|
+
throw new Error(`continuity: cannot read ${path}: ${error?.message ?? error}`);
|
|
206
|
+
}
|
|
207
|
+
}
|