keep-skill 0.4.2__py3-none-any.whl → 0.6.0__py3-none-any.whl
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.
- keep/__init__.py +3 -2
- keep/api.py +171 -41
- keep/cli.py +90 -36
- keep/config.py +11 -0
- keep/data/__init__.py +1 -0
- keep/data/system/__init__.py +1 -0
- keep/data/system/conversations.md +299 -0
- keep/data/system/domains.md +179 -0
- keep/data/system/now.md +19 -0
- keep/types.py +4 -0
- {keep_skill-0.4.2.dist-info → keep_skill-0.6.0.dist-info}/METADATA +1 -1
- {keep_skill-0.4.2.dist-info → keep_skill-0.6.0.dist-info}/RECORD +15 -10
- {keep_skill-0.4.2.dist-info → keep_skill-0.6.0.dist-info}/WHEEL +0 -0
- {keep_skill-0.4.2.dist-info → keep_skill-0.6.0.dist-info}/entry_points.txt +0 -0
- {keep_skill-0.4.2.dist-info → keep_skill-0.6.0.dist-info}/licenses/LICENSE +0 -0
keep/__init__.py
CHANGED
|
@@ -38,13 +38,14 @@ if not os.environ.get("KEEP_VERBOSE"):
|
|
|
38
38
|
os.environ.setdefault("HF_HUB_DISABLE_SYMLINKS_WARNING", "1")
|
|
39
39
|
|
|
40
40
|
from .api import Keeper, NOWDOC_ID
|
|
41
|
-
from .types import Item, filter_non_system_tags, SYSTEM_TAG_PREFIX
|
|
41
|
+
from .types import Item, filter_non_system_tags, SYSTEM_TAG_PREFIX, INTERNAL_TAGS
|
|
42
42
|
|
|
43
|
-
__version__ = "0.
|
|
43
|
+
__version__ = "0.6.0"
|
|
44
44
|
__all__ = [
|
|
45
45
|
"Keeper",
|
|
46
46
|
"Item",
|
|
47
47
|
"NOWDOC_ID",
|
|
48
48
|
"filter_non_system_tags",
|
|
49
49
|
"SYSTEM_TAG_PREFIX",
|
|
50
|
+
"INTERNAL_TAGS",
|
|
50
51
|
]
|
keep/api.py
CHANGED
|
@@ -9,6 +9,7 @@ This is the minimal working implementation focused on:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import hashlib
|
|
12
|
+
import importlib.resources
|
|
12
13
|
import logging
|
|
13
14
|
import re
|
|
14
15
|
from datetime import datetime, timezone, timedelta
|
|
@@ -100,6 +101,24 @@ def _filter_by_date(items: list, since: str) -> list:
|
|
|
100
101
|
if item.tags.get("_updated_date", "0000-00-00") >= cutoff
|
|
101
102
|
]
|
|
102
103
|
|
|
104
|
+
|
|
105
|
+
def _record_to_item(rec, score: float = None) -> "Item":
|
|
106
|
+
"""
|
|
107
|
+
Convert a DocumentRecord to an Item with timestamp tags.
|
|
108
|
+
|
|
109
|
+
Adds _updated, _created, _updated_date from the record's columns
|
|
110
|
+
to ensure consistent timestamp exposure across all retrieval methods.
|
|
111
|
+
"""
|
|
112
|
+
from .types import Item
|
|
113
|
+
tags = {
|
|
114
|
+
**rec.tags,
|
|
115
|
+
"_updated": rec.updated_at,
|
|
116
|
+
"_created": rec.created_at,
|
|
117
|
+
"_updated_date": rec.updated_at[:10] if rec.updated_at else "",
|
|
118
|
+
}
|
|
119
|
+
return Item(id=rec.id, summary=rec.summary, tags=tags, score=score)
|
|
120
|
+
|
|
121
|
+
|
|
103
122
|
import os
|
|
104
123
|
import subprocess
|
|
105
124
|
import sys
|
|
@@ -135,8 +154,44 @@ ENV_TAG_PREFIX = "KEEP_TAG_"
|
|
|
135
154
|
# Fixed ID for the current working context (singleton)
|
|
136
155
|
NOWDOC_ID = "_now:default"
|
|
137
156
|
|
|
157
|
+
|
|
158
|
+
def _get_system_doc_dir() -> Path:
|
|
159
|
+
"""
|
|
160
|
+
Get path to system docs, works in both dev and installed environments.
|
|
161
|
+
|
|
162
|
+
Tries in order:
|
|
163
|
+
1. Package data via importlib.resources (installed packages)
|
|
164
|
+
2. Relative path inside package (development)
|
|
165
|
+
3. Legacy path outside package (backwards compatibility)
|
|
166
|
+
"""
|
|
167
|
+
# Try package data first (works for installed packages)
|
|
168
|
+
try:
|
|
169
|
+
with importlib.resources.as_file(
|
|
170
|
+
importlib.resources.files("keep.data.system")
|
|
171
|
+
) as path:
|
|
172
|
+
if path.exists():
|
|
173
|
+
return path
|
|
174
|
+
except (ModuleNotFoundError, TypeError):
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
# Fallback to relative path inside package (development)
|
|
178
|
+
dev_path = Path(__file__).parent / "data" / "system"
|
|
179
|
+
if dev_path.exists():
|
|
180
|
+
return dev_path
|
|
181
|
+
|
|
182
|
+
# Legacy fallback (old structure)
|
|
183
|
+
return Path(__file__).parent.parent / "docs" / "system"
|
|
184
|
+
|
|
185
|
+
|
|
138
186
|
# Path to system documents
|
|
139
|
-
SYSTEM_DOC_DIR =
|
|
187
|
+
SYSTEM_DOC_DIR = _get_system_doc_dir()
|
|
188
|
+
|
|
189
|
+
# Stable IDs for system documents (path-independent)
|
|
190
|
+
SYSTEM_DOC_IDS = {
|
|
191
|
+
"now.md": "_system:now",
|
|
192
|
+
"conversations.md": "_system:conversations",
|
|
193
|
+
"domains.md": "_system:domains",
|
|
194
|
+
}
|
|
140
195
|
|
|
141
196
|
|
|
142
197
|
def _load_frontmatter(path: Path) -> tuple[str, dict[str, str]]:
|
|
@@ -295,32 +350,88 @@ class Keeper:
|
|
|
295
350
|
embedding_dimension=embedding_dim,
|
|
296
351
|
)
|
|
297
352
|
|
|
298
|
-
#
|
|
299
|
-
self.
|
|
353
|
+
# Migrate and ensure system documents (idempotent)
|
|
354
|
+
self._migrate_system_documents()
|
|
300
355
|
|
|
301
|
-
def
|
|
356
|
+
def _migrate_system_documents(self) -> dict:
|
|
302
357
|
"""
|
|
303
|
-
|
|
358
|
+
Migrate system documents to stable IDs and current version.
|
|
304
359
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
360
|
+
Handles:
|
|
361
|
+
- Migration from old file:// URIs to _system:{name} IDs
|
|
362
|
+
- Fresh creation for new stores
|
|
363
|
+
- Version upgrades when bundled content changes
|
|
364
|
+
- Cleanup of old file:// URIs (from before path was changed)
|
|
308
365
|
|
|
309
366
|
Called during init. Only loads docs that don't already exist,
|
|
310
|
-
so user modifications are preserved
|
|
311
|
-
|
|
367
|
+
so user modifications are preserved. Updates config version
|
|
368
|
+
after successful migration.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Dict with migration stats: created, migrated, skipped, cleaned
|
|
312
372
|
"""
|
|
373
|
+
from .config import SYSTEM_DOCS_VERSION, save_config
|
|
374
|
+
|
|
375
|
+
stats = {"created": 0, "migrated": 0, "skipped": 0, "cleaned": 0}
|
|
376
|
+
|
|
377
|
+
# Skip if already at current version
|
|
378
|
+
if self._config.system_docs_version >= SYSTEM_DOCS_VERSION:
|
|
379
|
+
return stats
|
|
380
|
+
|
|
381
|
+
# Build reverse lookup: filename -> new stable ID
|
|
382
|
+
filename_to_id = {name: doc_id for name, doc_id in SYSTEM_DOC_IDS.items()}
|
|
383
|
+
|
|
384
|
+
# First pass: clean up old file:// URIs with category=system tag
|
|
385
|
+
# These may have different paths than current SYSTEM_DOC_DIR
|
|
386
|
+
try:
|
|
387
|
+
old_system_docs = self.query_tag("category", "system")
|
|
388
|
+
for doc in old_system_docs:
|
|
389
|
+
if doc.id.startswith("file://") and doc.id.endswith(".md"):
|
|
390
|
+
# Extract filename from path
|
|
391
|
+
filename = Path(doc.id.replace("file://", "")).name
|
|
392
|
+
new_id = filename_to_id.get(filename)
|
|
393
|
+
if new_id and not self.exists(new_id):
|
|
394
|
+
# Migrate content to new ID
|
|
395
|
+
self.remember(doc.summary, id=new_id, tags=doc.tags)
|
|
396
|
+
self.delete(doc.id)
|
|
397
|
+
stats["migrated"] += 1
|
|
398
|
+
logger.info("Migrated system doc: %s -> %s", doc.id, new_id)
|
|
399
|
+
elif new_id:
|
|
400
|
+
# New ID already exists, just clean up old one
|
|
401
|
+
self.delete(doc.id)
|
|
402
|
+
stats["cleaned"] += 1
|
|
403
|
+
logger.info("Cleaned up old system doc: %s", doc.id)
|
|
404
|
+
except Exception as e:
|
|
405
|
+
logger.debug("Error scanning old system docs: %s", e)
|
|
406
|
+
|
|
407
|
+
# Second pass: create any missing system docs from bundled content
|
|
313
408
|
for path in SYSTEM_DOC_DIR.glob("*.md"):
|
|
409
|
+
new_id = SYSTEM_DOC_IDS.get(path.name)
|
|
410
|
+
if new_id is None:
|
|
411
|
+
logger.debug("Skipping unknown system doc: %s", path.name)
|
|
412
|
+
continue
|
|
413
|
+
|
|
414
|
+
# Skip if already exists
|
|
415
|
+
if self.exists(new_id):
|
|
416
|
+
stats["skipped"] += 1
|
|
417
|
+
continue
|
|
418
|
+
|
|
314
419
|
try:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
420
|
+
content, tags = _load_frontmatter(path)
|
|
421
|
+
tags["category"] = "system"
|
|
422
|
+
self.remember(content, id=new_id, tags=tags)
|
|
423
|
+
stats["created"] += 1
|
|
424
|
+
logger.info("Created system doc: %s", new_id)
|
|
320
425
|
except FileNotFoundError:
|
|
321
426
|
# System file missing - skip silently
|
|
322
427
|
pass
|
|
323
428
|
|
|
429
|
+
# Update config version
|
|
430
|
+
self._config.system_docs_version = SYSTEM_DOCS_VERSION
|
|
431
|
+
save_config(self._config)
|
|
432
|
+
|
|
433
|
+
return stats
|
|
434
|
+
|
|
324
435
|
def _get_embedding_provider(self) -> EmbeddingProvider:
|
|
325
436
|
"""
|
|
326
437
|
Get embedding provider, creating it lazily on first use.
|
|
@@ -581,12 +692,8 @@ class Keeper:
|
|
|
581
692
|
|
|
582
693
|
# Return the stored item
|
|
583
694
|
doc_record = self._document_store.get(coll, id)
|
|
584
|
-
return
|
|
585
|
-
|
|
586
|
-
summary=doc_record.summary,
|
|
587
|
-
tags=doc_record.tags,
|
|
588
|
-
)
|
|
589
|
-
|
|
695
|
+
return _record_to_item(doc_record)
|
|
696
|
+
|
|
590
697
|
def remember(
|
|
591
698
|
self,
|
|
592
699
|
content: str,
|
|
@@ -759,11 +866,7 @@ class Keeper:
|
|
|
759
866
|
|
|
760
867
|
# Return the stored item
|
|
761
868
|
doc_record = self._document_store.get(coll, id)
|
|
762
|
-
return
|
|
763
|
-
id=doc_record.id,
|
|
764
|
-
summary=doc_record.summary,
|
|
765
|
-
tags=doc_record.tags,
|
|
766
|
-
)
|
|
869
|
+
return _record_to_item(doc_record)
|
|
767
870
|
|
|
768
871
|
# -------------------------------------------------------------------------
|
|
769
872
|
# Query Operations
|
|
@@ -1055,7 +1158,7 @@ class Keeper:
|
|
|
1055
1158
|
docs = self._document_store.query_by_tag_key(
|
|
1056
1159
|
coll, key, limit=limit, since_date=since_date
|
|
1057
1160
|
)
|
|
1058
|
-
return [
|
|
1161
|
+
return [_record_to_item(d) for d in docs]
|
|
1059
1162
|
|
|
1060
1163
|
# Build tag filter from positional or keyword args
|
|
1061
1164
|
tag_filter = {}
|
|
@@ -1129,11 +1232,7 @@ class Keeper:
|
|
|
1129
1232
|
# Try document store first (canonical)
|
|
1130
1233
|
doc_record = self._document_store.get(coll, id)
|
|
1131
1234
|
if doc_record:
|
|
1132
|
-
return
|
|
1133
|
-
id=doc_record.id,
|
|
1134
|
-
summary=doc_record.summary,
|
|
1135
|
-
tags=doc_record.tags,
|
|
1136
|
-
)
|
|
1235
|
+
return _record_to_item(doc_record)
|
|
1137
1236
|
|
|
1138
1237
|
# Fall back to ChromaDB for legacy data
|
|
1139
1238
|
result = self._store.get(coll, id)
|
|
@@ -1271,7 +1370,7 @@ class Keeper:
|
|
|
1271
1370
|
|
|
1272
1371
|
A singleton document representing what you're currently working on.
|
|
1273
1372
|
If it doesn't exist, creates one with default content and tags from
|
|
1274
|
-
|
|
1373
|
+
the bundled system now.md file.
|
|
1275
1374
|
|
|
1276
1375
|
Returns:
|
|
1277
1376
|
The current context Item (never None - auto-creates if missing)
|
|
@@ -1328,6 +1427,44 @@ class Keeper:
|
|
|
1328
1427
|
"""
|
|
1329
1428
|
return self.query_tag("category", "system", collection=collection)
|
|
1330
1429
|
|
|
1430
|
+
def reset_system_documents(self) -> dict:
|
|
1431
|
+
"""
|
|
1432
|
+
Force reload all system documents from bundled content.
|
|
1433
|
+
|
|
1434
|
+
This overwrites any user modifications to system documents.
|
|
1435
|
+
Use with caution - primarily for recovery or testing.
|
|
1436
|
+
|
|
1437
|
+
Returns:
|
|
1438
|
+
Dict with stats: reset count
|
|
1439
|
+
"""
|
|
1440
|
+
from .config import SYSTEM_DOCS_VERSION, save_config
|
|
1441
|
+
|
|
1442
|
+
stats = {"reset": 0}
|
|
1443
|
+
|
|
1444
|
+
for path in SYSTEM_DOC_DIR.glob("*.md"):
|
|
1445
|
+
new_id = SYSTEM_DOC_IDS.get(path.name)
|
|
1446
|
+
if new_id is None:
|
|
1447
|
+
continue
|
|
1448
|
+
|
|
1449
|
+
try:
|
|
1450
|
+
content, tags = _load_frontmatter(path)
|
|
1451
|
+
tags["category"] = "system"
|
|
1452
|
+
|
|
1453
|
+
# Delete existing (if any) and create fresh
|
|
1454
|
+
self.delete(new_id)
|
|
1455
|
+
self.remember(content, id=new_id, tags=tags)
|
|
1456
|
+
stats["reset"] += 1
|
|
1457
|
+
logger.info("Reset system doc: %s", new_id)
|
|
1458
|
+
|
|
1459
|
+
except FileNotFoundError:
|
|
1460
|
+
logger.warning("System doc file not found: %s", path)
|
|
1461
|
+
|
|
1462
|
+
# Update config version
|
|
1463
|
+
self._config.system_docs_version = SYSTEM_DOCS_VERSION
|
|
1464
|
+
save_config(self._config)
|
|
1465
|
+
|
|
1466
|
+
return stats
|
|
1467
|
+
|
|
1331
1468
|
def tag(
|
|
1332
1469
|
self,
|
|
1333
1470
|
id: str,
|
|
@@ -1432,14 +1569,7 @@ class Keeper:
|
|
|
1432
1569
|
coll = self._resolve_collection(collection)
|
|
1433
1570
|
records = self._document_store.list_recent(coll, limit)
|
|
1434
1571
|
|
|
1435
|
-
return [
|
|
1436
|
-
Item(
|
|
1437
|
-
id=rec.id,
|
|
1438
|
-
summary=rec.summary,
|
|
1439
|
-
tags=rec.tags,
|
|
1440
|
-
score=None,
|
|
1441
|
-
)
|
|
1442
|
-
for rec in records
|
|
1572
|
+
return [_record_to_item(rec) for rec in records
|
|
1443
1573
|
]
|
|
1444
1574
|
|
|
1445
1575
|
def embedding_cache_stats(self) -> dict:
|
keep/cli.py
CHANGED
|
@@ -20,6 +20,10 @@ from typing_extensions import Annotated
|
|
|
20
20
|
# Pattern for version identifier suffix: @V{N} where N is digits only
|
|
21
21
|
VERSION_SUFFIX_PATTERN = re.compile(r'@V\{(\d+)\}$')
|
|
22
22
|
|
|
23
|
+
# URI scheme pattern per RFC 3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
|
24
|
+
# Used to distinguish URIs from plain text in the update command
|
|
25
|
+
_URI_SCHEME_PATTERN = re.compile(r'^[a-zA-Z][a-zA-Z0-9+.-]*://')
|
|
26
|
+
|
|
23
27
|
from .api import Keeper, _text_content_id
|
|
24
28
|
from .document_store import VersionInfo
|
|
25
29
|
from .types import Item
|
|
@@ -81,6 +85,23 @@ app = typer.Typer(
|
|
|
81
85
|
)
|
|
82
86
|
|
|
83
87
|
|
|
88
|
+
# -----------------------------------------------------------------------------
|
|
89
|
+
# Output Formatting
|
|
90
|
+
#
|
|
91
|
+
# Three output formats, controlled by global flags:
|
|
92
|
+
# --ids: versioned ID only (id@V{N})
|
|
93
|
+
# --full: YAML frontmatter with tags, similar items, version nav
|
|
94
|
+
# default: summary line (id@V{N} date summary)
|
|
95
|
+
#
|
|
96
|
+
# JSON output (--json) works with any of the above.
|
|
97
|
+
# -----------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def _filter_display_tags(tags: dict) -> dict:
|
|
100
|
+
"""Filter out internal-only tags for display."""
|
|
101
|
+
from .types import INTERNAL_TAGS
|
|
102
|
+
return {k: v for k, v in tags.items() if k not in INTERNAL_TAGS}
|
|
103
|
+
|
|
104
|
+
|
|
84
105
|
def _format_yaml_frontmatter(
|
|
85
106
|
item: Item,
|
|
86
107
|
version_nav: Optional[dict[str, list[VersionInfo]]] = None,
|
|
@@ -105,8 +126,9 @@ def _format_yaml_frontmatter(
|
|
|
105
126
|
lines = ["---", f"id: {item.id}"]
|
|
106
127
|
if viewing_offset is not None:
|
|
107
128
|
lines.append(f"version: {viewing_offset}")
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
display_tags = _filter_display_tags(item.tags)
|
|
130
|
+
if display_tags:
|
|
131
|
+
tag_items = ", ".join(f"{k}: {v}" for k, v in sorted(display_tags.items()))
|
|
110
132
|
lines.append(f"tags: {{{tag_items}}}")
|
|
111
133
|
if item.score is not None:
|
|
112
134
|
lines.append(f"score: {item.score:.3f}")
|
|
@@ -157,6 +179,31 @@ def _format_yaml_frontmatter(
|
|
|
157
179
|
return "\n".join(lines)
|
|
158
180
|
|
|
159
181
|
|
|
182
|
+
def _format_summary_line(item: Item) -> str:
|
|
183
|
+
"""Format item as single summary line: id@version date summary"""
|
|
184
|
+
# Get version-scoped ID
|
|
185
|
+
base_id = item.tags.get("_base_id", item.id)
|
|
186
|
+
version = item.tags.get("_version", "0")
|
|
187
|
+
versioned_id = f"{base_id}@V{{{version}}}"
|
|
188
|
+
|
|
189
|
+
# Get date (from _updated_date or _updated or _created)
|
|
190
|
+
date = item.tags.get("_updated_date") or item.tags.get("_updated", "")[:10] or item.tags.get("_created", "")[:10] or ""
|
|
191
|
+
|
|
192
|
+
# Truncate summary to ~60 chars, collapse newlines
|
|
193
|
+
summary = item.summary.replace("\n", " ")
|
|
194
|
+
if len(summary) > 60:
|
|
195
|
+
summary = summary[:57].rsplit(" ", 1)[0] + "..."
|
|
196
|
+
|
|
197
|
+
return f"{versioned_id} {date} {summary}"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _format_versioned_id(item: Item) -> str:
|
|
201
|
+
"""Format item ID with version suffix: id@V{N}"""
|
|
202
|
+
base_id = item.tags.get("_base_id", item.id)
|
|
203
|
+
version = item.tags.get("_version", "0")
|
|
204
|
+
return f"{base_id}@V{{{version}}}"
|
|
205
|
+
|
|
206
|
+
|
|
160
207
|
@app.callback(invoke_without_command=True)
|
|
161
208
|
def main_callback(
|
|
162
209
|
ctx: typer.Context,
|
|
@@ -242,10 +289,6 @@ SinceOption = Annotated[
|
|
|
242
289
|
]
|
|
243
290
|
|
|
244
291
|
|
|
245
|
-
# -----------------------------------------------------------------------------
|
|
246
|
-
# Output Helpers
|
|
247
|
-
# -----------------------------------------------------------------------------
|
|
248
|
-
|
|
249
292
|
def _format_item(
|
|
250
293
|
item: Item,
|
|
251
294
|
as_json: bool = False,
|
|
@@ -255,27 +298,30 @@ def _format_item(
|
|
|
255
298
|
similar_offsets: Optional[dict[str, int]] = None,
|
|
256
299
|
) -> str:
|
|
257
300
|
"""
|
|
258
|
-
Format
|
|
301
|
+
Format a single item for display.
|
|
259
302
|
|
|
260
|
-
|
|
261
|
-
|
|
303
|
+
Output selection:
|
|
304
|
+
--ids: versioned ID only
|
|
305
|
+
--full or version_nav/similar_items present: YAML frontmatter
|
|
306
|
+
default: summary line (id@V{N} date summary)
|
|
262
307
|
|
|
263
308
|
Args:
|
|
264
309
|
item: The item to format
|
|
265
310
|
as_json: Output as JSON
|
|
266
|
-
version_nav:
|
|
267
|
-
viewing_offset:
|
|
268
|
-
similar_items:
|
|
269
|
-
similar_offsets: Version offsets for similar items
|
|
311
|
+
version_nav: Version navigation info (triggers full format)
|
|
312
|
+
viewing_offset: Version offset if viewing old version (triggers full format)
|
|
313
|
+
similar_items: Similar items to display (triggers full format)
|
|
314
|
+
similar_offsets: Version offsets for similar items
|
|
270
315
|
"""
|
|
271
316
|
if _get_ids_output():
|
|
272
|
-
|
|
317
|
+
versioned_id = _format_versioned_id(item)
|
|
318
|
+
return json.dumps(versioned_id) if as_json else versioned_id
|
|
273
319
|
|
|
274
320
|
if as_json:
|
|
275
321
|
result = {
|
|
276
322
|
"id": item.id,
|
|
277
323
|
"summary": item.summary,
|
|
278
|
-
"tags": item.tags,
|
|
324
|
+
"tags": _filter_display_tags(item.tags),
|
|
279
325
|
"score": item.score,
|
|
280
326
|
}
|
|
281
327
|
if viewing_offset is not None:
|
|
@@ -318,13 +364,18 @@ def _format_item(
|
|
|
318
364
|
result["version_nav"]["next"] = [{"offset": 0, "vid": f"{item.id}@V{{0}}", "label": "current"}]
|
|
319
365
|
return json.dumps(result)
|
|
320
366
|
|
|
321
|
-
|
|
367
|
+
# Full format when:
|
|
368
|
+
# - --full flag is set
|
|
369
|
+
# - version navigation or similar items are provided (can't display in summary)
|
|
370
|
+
if _get_full_output() or version_nav or similar_items or viewing_offset is not None:
|
|
371
|
+
return _format_yaml_frontmatter(item, version_nav, viewing_offset, similar_items, similar_offsets)
|
|
372
|
+
return _format_summary_line(item)
|
|
322
373
|
|
|
323
374
|
|
|
324
375
|
def _format_items(items: list[Item], as_json: bool = False) -> str:
|
|
325
376
|
"""Format multiple items for display."""
|
|
326
377
|
if _get_ids_output():
|
|
327
|
-
ids = [item
|
|
378
|
+
ids = [_format_versioned_id(item) for item in items]
|
|
328
379
|
return json.dumps(ids) if as_json else "\n".join(ids)
|
|
329
380
|
|
|
330
381
|
if as_json:
|
|
@@ -332,15 +383,20 @@ def _format_items(items: list[Item], as_json: bool = False) -> str:
|
|
|
332
383
|
{
|
|
333
384
|
"id": item.id,
|
|
334
385
|
"summary": item.summary,
|
|
335
|
-
"tags": item.tags,
|
|
386
|
+
"tags": _filter_display_tags(item.tags),
|
|
336
387
|
"score": item.score,
|
|
337
388
|
}
|
|
338
389
|
for item in items
|
|
339
390
|
], indent=2)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
391
|
+
|
|
392
|
+
if not items:
|
|
393
|
+
return "No results."
|
|
394
|
+
|
|
395
|
+
# Full format: YAML frontmatter with double-newline separator
|
|
396
|
+
# Default: summary lines with single-newline separator
|
|
397
|
+
if _get_full_output():
|
|
398
|
+
return "\n\n".join(_format_yaml_frontmatter(item) for item in items)
|
|
399
|
+
return "\n".join(_format_summary_line(item) for item in items)
|
|
344
400
|
|
|
345
401
|
|
|
346
402
|
def _get_keeper(store: Optional[Path], collection: str) -> Keeper:
|
|
@@ -457,22 +513,11 @@ def list_recent(
|
|
|
457
513
|
"""
|
|
458
514
|
List recent items by update time.
|
|
459
515
|
|
|
460
|
-
|
|
516
|
+
Default: summary lines. Use --ids for IDs only, --full for YAML.
|
|
461
517
|
"""
|
|
462
518
|
kp = _get_keeper(store, collection)
|
|
463
519
|
results = kp.list_recent(limit=limit)
|
|
464
|
-
|
|
465
|
-
# Determine output mode: --full > --ids > command default (IDs for list)
|
|
466
|
-
if _get_json_output():
|
|
467
|
-
# JSON always outputs full items
|
|
468
|
-
typer.echo(_format_items(results, as_json=True))
|
|
469
|
-
elif _get_full_output():
|
|
470
|
-
# --full flag: full YAML output
|
|
471
|
-
typer.echo(_format_items(results, as_json=False))
|
|
472
|
-
else:
|
|
473
|
-
# Default for list: IDs only (composable)
|
|
474
|
-
for item in results:
|
|
475
|
-
typer.echo(item.id)
|
|
520
|
+
typer.echo(_format_items(results, as_json=_get_json_output()))
|
|
476
521
|
|
|
477
522
|
|
|
478
523
|
@app.command()
|
|
@@ -643,7 +688,7 @@ def update(
|
|
|
643
688
|
# Use content-addressed ID for stdin text (enables versioning)
|
|
644
689
|
doc_id = id or _text_content_id(content)
|
|
645
690
|
item = kp.remember(content, id=doc_id, summary=summary, tags=parsed_tags or None, lazy=lazy)
|
|
646
|
-
elif source and
|
|
691
|
+
elif source and _URI_SCHEME_PATTERN.match(source):
|
|
647
692
|
# URI mode: fetch from URI (ID is the URI itself)
|
|
648
693
|
item = kp.update(source, tags=parsed_tags or None, summary=summary, lazy=lazy)
|
|
649
694
|
elif source:
|
|
@@ -1045,6 +1090,10 @@ def list_collections(
|
|
|
1045
1090
|
|
|
1046
1091
|
@app.command()
|
|
1047
1092
|
def init(
|
|
1093
|
+
reset_system_docs: Annotated[bool, typer.Option(
|
|
1094
|
+
"--reset-system-docs",
|
|
1095
|
+
help="Force reload system documents from bundled content (overwrites modifications)"
|
|
1096
|
+
)] = False,
|
|
1048
1097
|
store: StoreOption = None,
|
|
1049
1098
|
collection: CollectionOption = "default",
|
|
1050
1099
|
):
|
|
@@ -1053,6 +1102,11 @@ def init(
|
|
|
1053
1102
|
"""
|
|
1054
1103
|
kp = _get_keeper(store, collection)
|
|
1055
1104
|
|
|
1105
|
+
# Handle reset if requested
|
|
1106
|
+
if reset_system_docs:
|
|
1107
|
+
stats = kp.reset_system_documents()
|
|
1108
|
+
typer.echo(f"Reset {stats['reset']} system documents")
|
|
1109
|
+
|
|
1056
1110
|
# Show config and store paths
|
|
1057
1111
|
config = kp._config
|
|
1058
1112
|
config_path = config.config_path if config else None
|
keep/config.py
CHANGED
|
@@ -19,6 +19,7 @@ import tomli_w
|
|
|
19
19
|
|
|
20
20
|
CONFIG_FILENAME = "keep.toml"
|
|
21
21
|
CONFIG_VERSION = 3 # Bumped for document versioning support
|
|
22
|
+
SYSTEM_DOCS_VERSION = 1 # Increment when bundled system docs content changes
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@dataclass
|
|
@@ -88,6 +89,9 @@ class StoreConfig:
|
|
|
88
89
|
# Maximum length for summaries (used for smart remember and validation)
|
|
89
90
|
max_summary_length: int = 500
|
|
90
91
|
|
|
92
|
+
# System docs version (tracks which bundled docs have been applied to this store)
|
|
93
|
+
system_docs_version: int = 0
|
|
94
|
+
|
|
91
95
|
@property
|
|
92
96
|
def config_path(self) -> Path:
|
|
93
97
|
"""Path to the TOML config file."""
|
|
@@ -351,6 +355,9 @@ def load_config(config_dir: Path) -> StoreConfig:
|
|
|
351
355
|
# Parse max_summary_length (default 500)
|
|
352
356
|
max_summary_length = data.get("store", {}).get("max_summary_length", 500)
|
|
353
357
|
|
|
358
|
+
# Parse system_docs_version (default 0 for stores that predate this feature)
|
|
359
|
+
system_docs_version = data.get("store", {}).get("system_docs_version", 0)
|
|
360
|
+
|
|
354
361
|
return StoreConfig(
|
|
355
362
|
path=actual_store,
|
|
356
363
|
config_dir=config_dir,
|
|
@@ -363,6 +370,7 @@ def load_config(config_dir: Path) -> StoreConfig:
|
|
|
363
370
|
embedding_identity=parse_embedding_identity(data.get("embedding_identity")),
|
|
364
371
|
default_tags=default_tags,
|
|
365
372
|
max_summary_length=max_summary_length,
|
|
373
|
+
system_docs_version=system_docs_version,
|
|
366
374
|
)
|
|
367
375
|
|
|
368
376
|
|
|
@@ -404,6 +412,9 @@ def save_config(config: StoreConfig) -> None:
|
|
|
404
412
|
# Only write max_summary_length if not default
|
|
405
413
|
if config.max_summary_length != 500:
|
|
406
414
|
store_section["max_summary_length"] = config.max_summary_length
|
|
415
|
+
# Write system_docs_version if set (tracks migration state)
|
|
416
|
+
if config.system_docs_version > 0:
|
|
417
|
+
store_section["system_docs_version"] = config.system_docs_version
|
|
407
418
|
|
|
408
419
|
data = {
|
|
409
420
|
"store": store_section,
|
keep/data/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Package data for keep
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# System documents for keep
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags:
|
|
3
|
+
category: system
|
|
4
|
+
context: practice
|
|
5
|
+
---
|
|
6
|
+
# Conversation Patterns for Process Knowledge
|
|
7
|
+
|
|
8
|
+
This document describes patterns for recognizing, tracking, and learning from
|
|
9
|
+
the structure of work — not *what* we know, but *how work proceeds*.
|
|
10
|
+
|
|
11
|
+
These patterns are grounded in the Language-Action Perspective (Winograd & Flores),
|
|
12
|
+
which treats work as networks of commitments made through language.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Decision Matrix: What Kind of Conversation Is This?
|
|
17
|
+
|
|
18
|
+
| Signal | Conversation Type | Your Stance | Don't Do This |
|
|
19
|
+
|--------|-------------------|-------------|---------------|
|
|
20
|
+
| "Can you...", "Please...", "I need..." | **Action** | Clarify → Promise → Deliver | Promise before understanding |
|
|
21
|
+
| "What if...", "Imagine...", "Could we..." | **Possibility** | Explore, generate options, hold lightly | Commit prematurely |
|
|
22
|
+
| "What is...", "How does...", "Why..." | **Clarification** | Explain until understanding lands | Over-answer; assume you know the real question |
|
|
23
|
+
| "I'm trying to understand...", "Context is..." | **Orientation** | Listen, reflect back, surface assumptions | Jump to solutions |
|
|
24
|
+
| "Here's what I found...", "Status update..." | **Report** | Acknowledge, ask what's needed next | Assume it's a request |
|
|
25
|
+
|
|
26
|
+
**Transition signals** (conversation type is shifting):
|
|
27
|
+
- Possibility → Action: "Let's do X" / "I want to go with..."
|
|
28
|
+
- Clarification → Action: "Ok, so please..." / "Now that I understand..."
|
|
29
|
+
- Orientation → Clarification: "So what does X mean?" / "Help me understand..."
|
|
30
|
+
|
|
31
|
+
**When uncertain:** Ask. "Are you exploring options, or would you like me to do something?"
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Core Insight
|
|
36
|
+
|
|
37
|
+
Work is not information processing. Work is **commitment management**.
|
|
38
|
+
|
|
39
|
+
When an agent assists with a task, it participates in a conversation with structure:
|
|
40
|
+
- Requests create openings
|
|
41
|
+
- Promises create obligations
|
|
42
|
+
- Completion is declared, not merely achieved
|
|
43
|
+
- Satisfaction closes the loop
|
|
44
|
+
|
|
45
|
+
Understanding *where we are* in this structure is as important as understanding
|
|
46
|
+
*what we know* about the subject matter.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## The Basic Conversation for Action
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Customer Performer
|
|
54
|
+
│ │
|
|
55
|
+
│──── 1. Request ────────────────→│
|
|
56
|
+
│ │
|
|
57
|
+
│←─── 2. Promise (or Counter) ────│
|
|
58
|
+
│ │
|
|
59
|
+
│ [ work happens ] │
|
|
60
|
+
│ │
|
|
61
|
+
│←─── 3. Declare Complete ────────│
|
|
62
|
+
│ │
|
|
63
|
+
│──── 4. Declare Satisfied ──────→│
|
|
64
|
+
│ │
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
At any point: Withdraw, Decline, Renegotiate.
|
|
68
|
+
|
|
69
|
+
**For agents:** Recognizing this structure helps answer:
|
|
70
|
+
- "What has been asked of me?"
|
|
71
|
+
- "What have I committed to?"
|
|
72
|
+
- "What does 'done' look like?"
|
|
73
|
+
- "Who needs to be satisfied?"
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Conversation Types
|
|
78
|
+
|
|
79
|
+
### Request for Action
|
|
80
|
+
Someone asks for something to be done.
|
|
81
|
+
|
|
82
|
+
**Recognize by:** Imperative language, "can you", "please", "I need"
|
|
83
|
+
|
|
84
|
+
**Track:**
|
|
85
|
+
- What specifically was requested?
|
|
86
|
+
- Any conditions or constraints?
|
|
87
|
+
- Implicit quality criteria?
|
|
88
|
+
- Who is the customer (who declares satisfaction)?
|
|
89
|
+
|
|
90
|
+
**Completion:** Customer declares satisfaction, not performer declares done.
|
|
91
|
+
|
|
92
|
+
### Request for Information
|
|
93
|
+
Someone asks to know something.
|
|
94
|
+
|
|
95
|
+
**Recognize by:** Questions, "what is", "how does", "why"
|
|
96
|
+
|
|
97
|
+
**Track:**
|
|
98
|
+
- What gap in understanding prompted this?
|
|
99
|
+
- What level of detail is appropriate?
|
|
100
|
+
- What would make this answer useful?
|
|
101
|
+
|
|
102
|
+
**Completion:** Questioner indicates understanding (often implicit).
|
|
103
|
+
|
|
104
|
+
### Offer
|
|
105
|
+
Someone proposes to do something.
|
|
106
|
+
|
|
107
|
+
**Recognize by:** "I could", "would you like me to", "I suggest"
|
|
108
|
+
|
|
109
|
+
**Track:**
|
|
110
|
+
- What conditions on acceptance?
|
|
111
|
+
- What's the scope of the offer?
|
|
112
|
+
|
|
113
|
+
**Completion:** Offer accepted → becomes promise. Offer declined → closed.
|
|
114
|
+
|
|
115
|
+
### Report / Declaration
|
|
116
|
+
Someone asserts a state of affairs.
|
|
117
|
+
|
|
118
|
+
**Recognize by:** Statements of fact, status updates, "I found that"
|
|
119
|
+
|
|
120
|
+
**Track:**
|
|
121
|
+
- What changed as a result of this declaration?
|
|
122
|
+
- Who needs to know?
|
|
123
|
+
|
|
124
|
+
**Completion:** Acknowledgment (may trigger new conversations).
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Conversations for Possibility
|
|
129
|
+
|
|
130
|
+
Possibility conversations explore "what could be" — no commitment yet.
|
|
131
|
+
|
|
132
|
+
**Recognize by:** "What if", "imagine", "we could", "brainstorm", "options"
|
|
133
|
+
|
|
134
|
+
**Your stance:**
|
|
135
|
+
- Generate, don't filter prematurely
|
|
136
|
+
- Hold ideas lightly — nothing is promised
|
|
137
|
+
- Expand the space before narrowing
|
|
138
|
+
- Name options without advocating
|
|
139
|
+
|
|
140
|
+
**Track:**
|
|
141
|
+
- Options generated
|
|
142
|
+
- Constraints surfaced
|
|
143
|
+
- Energy/interest signals ("that's interesting" vs "hmm")
|
|
144
|
+
|
|
145
|
+
**Completion:** Not satisfaction — rather, either:
|
|
146
|
+
- Transition to Action ("let's do X")
|
|
147
|
+
- Explicit close ("good to know our options")
|
|
148
|
+
- Energy dissipates naturally
|
|
149
|
+
|
|
150
|
+
**Critical:** Don't promise during possibility. "I could do X" is an option, not a commitment. The transition to action must be explicit.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Index possibility exploration
|
|
154
|
+
keep update "Explored three auth options: OAuth2, API keys, magic links. \
|
|
155
|
+
User showed interest in magic links for UX simplicity. No decision yet." \
|
|
156
|
+
--tag type=possibility --tag topic=authentication --tag status=open
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Breakdowns: Where Learning Happens
|
|
162
|
+
|
|
163
|
+
A **breakdown** occurs when the normal flow is interrupted:
|
|
164
|
+
- Expected response doesn't come
|
|
165
|
+
- Declared completion isn't satisfactory
|
|
166
|
+
- Preconditions weren't met
|
|
167
|
+
- Ambiguity surfaces mid-work
|
|
168
|
+
|
|
169
|
+
**Breakdowns are valuable.** They reveal assumptions that were invisible.
|
|
170
|
+
|
|
171
|
+
**Pattern for breakdown learning:**
|
|
172
|
+
```
|
|
173
|
+
1. Notice: "This isn't going as expected"
|
|
174
|
+
2. Articulate: "The assumption was X, but actually Y"
|
|
175
|
+
3. Repair: Complete the immediate conversation
|
|
176
|
+
4. Record: Capture the learning for future conversations of this type
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
When indexing a breakdown:
|
|
180
|
+
```bash
|
|
181
|
+
keep update "Assumption: user wanted full rewrite. Actually: wanted minimal patch." \
|
|
182
|
+
--tag type=breakdown --tag conversation_type=code_change_request
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Nested and Linked Conversations
|
|
188
|
+
|
|
189
|
+
Real work involves conversations within conversations:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
User requests feature
|
|
193
|
+
└─ Agent promises feature
|
|
194
|
+
└─ Agent requests clarification (sub-conversation)
|
|
195
|
+
│ └─ User provides clarification
|
|
196
|
+
│ └─ Agent acknowledges (closes sub-conversation)
|
|
197
|
+
└─ Agent declares complete
|
|
198
|
+
└─ User requests revision (linked follow-on)
|
|
199
|
+
└─ ...
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Track parent/child relationships** to understand scope:
|
|
203
|
+
- Completing a sub-conversation doesn't complete the parent
|
|
204
|
+
- Breakdowns in child conversations may propagate up
|
|
205
|
+
- Context flows down (child inherits parent context)
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Recurrent Patterns by Domain
|
|
210
|
+
|
|
211
|
+
### Software Development
|
|
212
|
+
|
|
213
|
+
| Pattern | Structure | Completion Condition |
|
|
214
|
+
|---------|-----------|---------------------|
|
|
215
|
+
| Bug report | Request → Diagnosis → Fix → Verify | User confirms fix works |
|
|
216
|
+
| Feature request | Request → Clarify → Implement → Review → Accept | Stakeholder accepts |
|
|
217
|
+
| Code review | Offer(changes) → Review → Request(revisions) → Update → Approve | Reviewer approves |
|
|
218
|
+
| Refactor | Offer → Scope agreement → Execute → Verify behavior preserved | Tests pass, reviewer approves |
|
|
219
|
+
|
|
220
|
+
### Research / Analysis
|
|
221
|
+
|
|
222
|
+
| Pattern | Structure | Completion Condition |
|
|
223
|
+
|---------|-----------|---------------------|
|
|
224
|
+
| Question | Request(info) → Search → Synthesize → Present | Questioner satisfied |
|
|
225
|
+
| Hypothesis test | Declare(hypothesis) → Design test → Execute → Report | Evidence evaluated |
|
|
226
|
+
| Literature review | Request → Gather → Synthesize → Summarize | Comprehensive & relevant |
|
|
227
|
+
|
|
228
|
+
### Planning / Coordination
|
|
229
|
+
|
|
230
|
+
| Pattern | Structure | Completion Condition |
|
|
231
|
+
|---------|-----------|---------------------|
|
|
232
|
+
| Task breakdown | Request(outcome) → Decompose → Commit to parts → Track → Integrate | Outcome achieved |
|
|
233
|
+
| Decision | Present options → Evaluate → Declare choice | Commitment to proceed |
|
|
234
|
+
| Handoff | Declare(status) → Transfer commitments → Acknowledge | Receiving agent accepts |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Agent Handoff as Commitment Transfer
|
|
239
|
+
|
|
240
|
+
When one agent hands off to another:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Outgoing agent records state
|
|
244
|
+
keep now "User requested OAuth2 implementation. I promised and partially delivered. \
|
|
245
|
+
Token acquisition works. Refresh flow incomplete. User awaiting completion." \
|
|
246
|
+
--tag topic=authentication --tag state=handoff
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Incoming agent reads this and knows:
|
|
250
|
+
- There's an open promise to the user
|
|
251
|
+
- What "done" looks like
|
|
252
|
+
- Where to pick up
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Incoming agent checks context
|
|
256
|
+
keep now
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Learning New Patterns
|
|
262
|
+
|
|
263
|
+
Agents can recognize and record new conversation patterns:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
keep update "Pattern: Incremental Specification. \
|
|
267
|
+
When requirements are vague, don't promise immediately. \
|
|
268
|
+
Propose interpretation → get correction → repeat until clear. \
|
|
269
|
+
Only then commit to action. Breakdown risk: Promising too early leads to rework." \
|
|
270
|
+
--tag type=conversation_pattern --tag domain=general
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Using Patterns in Practice
|
|
276
|
+
|
|
277
|
+
**At task start:**
|
|
278
|
+
1. What kind of conversation is this?
|
|
279
|
+
2. What's my role (customer or performer)?
|
|
280
|
+
3. What does completion look like?
|
|
281
|
+
4. Have I seen breakdowns in this pattern before?
|
|
282
|
+
|
|
283
|
+
**Mid-task:**
|
|
284
|
+
1. Where are we in the conversation structure?
|
|
285
|
+
2. Have any sub-conversations opened?
|
|
286
|
+
3. Are there signs of breakdown?
|
|
287
|
+
|
|
288
|
+
**At task end:**
|
|
289
|
+
1. Was satisfaction declared (not just completion)?
|
|
290
|
+
2. Any learnings to record?
|
|
291
|
+
3. Open commitments to hand off?
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## References
|
|
296
|
+
|
|
297
|
+
- Winograd, T. & Flores, F. (1986). *Understanding Computers and Cognition*
|
|
298
|
+
- Denning, P. & Medina-Mora, R. (1995). "Completing the Loops"
|
|
299
|
+
- Flores, F. et al. (1988). "Computer Systems and the Design of Organizational Interaction"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags:
|
|
3
|
+
category: system
|
|
4
|
+
context: domains
|
|
5
|
+
---
|
|
6
|
+
# Domain Patterns
|
|
7
|
+
|
|
8
|
+
This document describes common organization patterns for different use cases.
|
|
9
|
+
These are suggestions, not requirements — adapt them to your needs.
|
|
10
|
+
|
|
11
|
+
**See also:** [conversations.md](conversations.md) for process knowledge —
|
|
12
|
+
understanding *how work proceeds*, not just *what we know*.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Software Development
|
|
17
|
+
|
|
18
|
+
**Collections:**
|
|
19
|
+
| Collection | Purpose |
|
|
20
|
+
|------------|---------|
|
|
21
|
+
| `code` | Source files, organized by the codebase |
|
|
22
|
+
| `docs` | Documentation, READMEs, architecture decisions |
|
|
23
|
+
| `issues` | Bugs, errors, stack traces, problems encountered |
|
|
24
|
+
| `decisions` | "Why did we do X?" — architectural reasoning |
|
|
25
|
+
|
|
26
|
+
**Suggested tags:**
|
|
27
|
+
- `language` — python, typescript, rust, etc.
|
|
28
|
+
- `layer` — frontend, backend, infra, database, cli
|
|
29
|
+
- `module` — auth, api, ui, core, tests
|
|
30
|
+
- `status` — working, broken, needs_review, deprecated
|
|
31
|
+
|
|
32
|
+
**Common conversation patterns:** (see conversations.md)
|
|
33
|
+
- Bug report → Diagnosis → Fix → Verify
|
|
34
|
+
- Feature request → Clarify → Implement → Review → Accept
|
|
35
|
+
- Code review → Revisions → Approve
|
|
36
|
+
|
|
37
|
+
**Agent guidance:**
|
|
38
|
+
- Index every file you read or modify
|
|
39
|
+
- When encountering an error, index it with the error message as content
|
|
40
|
+
- Before searching the filesystem, check `keep find` — you may already know about it
|
|
41
|
+
- Use `keep find --id` to discover related code when working on a feature
|
|
42
|
+
- Record breakdowns: "Assumption X was wrong, actually Y"
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Market Research
|
|
47
|
+
|
|
48
|
+
**Collections:**
|
|
49
|
+
| Collection | Purpose |
|
|
50
|
+
|------------|---------|
|
|
51
|
+
| `sources` | Primary research — articles, reports, filings |
|
|
52
|
+
| `competitors` | Competitor-specific intelligence |
|
|
53
|
+
| `insights` | Synthesized findings and conclusions |
|
|
54
|
+
| `data` | Quantitative sources, datasets, statistics |
|
|
55
|
+
|
|
56
|
+
**Suggested tags:**
|
|
57
|
+
- `company` — specific company names
|
|
58
|
+
- `market` — b2b_saas, consumer_fintech, healthcare, etc.
|
|
59
|
+
- `source_type` — article, report, interview, sec_filing, press_release
|
|
60
|
+
- `credibility` — high, medium, low, unverified
|
|
61
|
+
- `region` — north_america, europe, apac
|
|
62
|
+
|
|
63
|
+
**Agent guidance:**
|
|
64
|
+
- Always index sources you fetch, even if they seem tangential
|
|
65
|
+
- Tag competitor information consistently for later cross-reference
|
|
66
|
+
- Create insights entries to capture your synthesized conclusions
|
|
67
|
+
- Use semantic search to find connections across different sources
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Personal Reflection & Growth
|
|
72
|
+
|
|
73
|
+
**Collections:**
|
|
74
|
+
| Collection | Purpose |
|
|
75
|
+
|------------|---------|
|
|
76
|
+
| `journal` | Reflections, conversations, daily entries |
|
|
77
|
+
| `goals` | Active goals and progress tracking |
|
|
78
|
+
| `feedback` | External feedback received |
|
|
79
|
+
| `patterns` | Recurring themes you've noticed |
|
|
80
|
+
|
|
81
|
+
**Suggested tags:**
|
|
82
|
+
- `life_area` — career, health, relationships, learning, finance, creativity
|
|
83
|
+
- `emotion` — confident, anxious, grateful, frustrated, energized
|
|
84
|
+
- `theme` — boundaries, communication, procrastination, perfectionism
|
|
85
|
+
- `energy` — high, low, recovering
|
|
86
|
+
|
|
87
|
+
**Agent guidance:**
|
|
88
|
+
- Index conversations about personal topics as journal entries
|
|
89
|
+
- Look for patterns when the user reports similar feelings repeatedly
|
|
90
|
+
- Connect current challenges to past insights
|
|
91
|
+
- Use `keep find --id` to surface "you've felt this way before"
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Healthcare Tracking
|
|
96
|
+
|
|
97
|
+
**Collections:**
|
|
98
|
+
| Collection | Purpose |
|
|
99
|
+
|------------|---------|
|
|
100
|
+
| `symptoms` | Symptom reports and tracking |
|
|
101
|
+
| `medications` | Current and historical medications |
|
|
102
|
+
| `appointments` | Visit notes, provider interactions |
|
|
103
|
+
| `research` | Health information gathered |
|
|
104
|
+
|
|
105
|
+
**Suggested tags:**
|
|
106
|
+
- `body_system` — cardiovascular, digestive, musculoskeletal, neurological
|
|
107
|
+
- `symptom` — headache, fatigue, pain, nausea (specific symptoms)
|
|
108
|
+
- `severity` — mild, moderate, severe
|
|
109
|
+
- `provider` — dr_smith, clinic_name
|
|
110
|
+
- `medication` — specific medication names
|
|
111
|
+
|
|
112
|
+
**Agent guidance:**
|
|
113
|
+
- Always index symptom reports with dates for timeline tracking
|
|
114
|
+
- Cross-reference symptoms with medication changes
|
|
115
|
+
- Index appointment outcomes for continuity
|
|
116
|
+
- Be precise with medical terminology in tags for accurate retrieval
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Cross-Domain Patterns
|
|
121
|
+
|
|
122
|
+
These patterns apply regardless of domain:
|
|
123
|
+
|
|
124
|
+
**Conversation indexing:**
|
|
125
|
+
```bash
|
|
126
|
+
# Index the current conversation
|
|
127
|
+
keep update "User asked about X, we discussed Y, decided Z" \
|
|
128
|
+
--tag session=abc123
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Context tracking:**
|
|
132
|
+
```bash
|
|
133
|
+
# Record current focus for handoff
|
|
134
|
+
keep now "Working on feature X" --tag topic=feature_x
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Breakdown learning:**
|
|
138
|
+
```bash
|
|
139
|
+
# When something goes wrong, capture the learning
|
|
140
|
+
keep update "Assumed user wanted full rewrite, actually wanted minimal fix. \
|
|
141
|
+
Ask about scope before large changes." \
|
|
142
|
+
--tag type=breakdown --tag conversation_type=code_change_request
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Temporal queries using system tags:**
|
|
146
|
+
```bash
|
|
147
|
+
# Find items updated today
|
|
148
|
+
keep tag _updated_date=2026-01-30
|
|
149
|
+
|
|
150
|
+
# Find all inline content (from remember)
|
|
151
|
+
keep tag _source=inline
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Progressive refinement:**
|
|
155
|
+
```bash
|
|
156
|
+
# Start broad, then narrow
|
|
157
|
+
keep find "authentication"
|
|
158
|
+
keep tag module=auth
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Process Knowledge
|
|
164
|
+
|
|
165
|
+
Beyond subject-matter knowledge, index *how to work effectively*:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Index a learned pattern
|
|
169
|
+
keep update "Pattern: Incremental Specification. \
|
|
170
|
+
When requirements are vague, don't promise immediately. \
|
|
171
|
+
Propose interpretation → get correction → repeat until clear. \
|
|
172
|
+
Only then commit to action. Breakdown risk: Promising too early leads to rework." \
|
|
173
|
+
--tag type=conversation_pattern --tag domain=general
|
|
174
|
+
|
|
175
|
+
# Later, retrieve it
|
|
176
|
+
keep tag type=conversation_pattern
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
See [conversations.md](conversations.md) for the full framework.
|
keep/data/system/now.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags:
|
|
3
|
+
category: system
|
|
4
|
+
context: now
|
|
5
|
+
---
|
|
6
|
+
# Now
|
|
7
|
+
|
|
8
|
+
This is top-of-mind right now. Reminders for essential practice.
|
|
9
|
+
(Keep this updated as you need)
|
|
10
|
+
|
|
11
|
+
**Before acting:**
|
|
12
|
+
- Where are we in the conversation?
|
|
13
|
+
- What do I know? `keep now`
|
|
14
|
+
- What else is skillful? `keep find ...`
|
|
15
|
+
|
|
16
|
+
**After acting:**
|
|
17
|
+
- What happened? `keep update "what I learned" -t context=learning`
|
|
18
|
+
- What do I save? `keep update`
|
|
19
|
+
- What now? `keep now "Current focus: ..."`
|
keep/types.py
CHANGED
|
@@ -9,6 +9,10 @@ from typing import Optional
|
|
|
9
9
|
# System tag prefix - tags starting with this are managed by the system
|
|
10
10
|
SYSTEM_TAG_PREFIX = "_"
|
|
11
11
|
|
|
12
|
+
# Tags used internally but hidden from display output
|
|
13
|
+
# These exist for efficient queries/sorting but aren't user-facing
|
|
14
|
+
INTERNAL_TAGS = frozenset({"_updated_date"})
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
def filter_non_system_tags(tags: dict[str, str]) -> dict[str, str]:
|
|
14
18
|
"""
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
keep/__init__.py,sha256=
|
|
1
|
+
keep/__init__.py,sha256=lAm_iUUxz76EanZYE5UviCD1p2OSQwUag4Q67EP4oKE,1617
|
|
2
2
|
keep/__main__.py,sha256=3Uu70IhIDIjh8OW6jp9jQQ3dF2lKdJWi_3FtRIQMiMY,104
|
|
3
|
-
keep/api.py,sha256=
|
|
3
|
+
keep/api.py,sha256=Ja_9zKr0bDL2UyBDZ03HrQHYkI9mdOMxuQlstyxJzmo,64477
|
|
4
4
|
keep/chunking.py,sha256=neAXOLSvVwbUxapbqq7nZrbSNSzMXuhxj-ODoOSodsU,11830
|
|
5
|
-
keep/cli.py,sha256=
|
|
6
|
-
keep/config.py,sha256=
|
|
5
|
+
keep/cli.py,sha256=P7oKtv3TFL5BZNEfwh6kAFuqoeeqFxfVYssjdg0RMLc,44761
|
|
6
|
+
keep/config.py,sha256=gw_QMY0VkCVF6xi-B9zGvZSza4z2QtiJucqgZJF3Xqg,16227
|
|
7
7
|
keep/context.py,sha256=CNpjmrv6eW2kV1E0MO6qAQfhYKRlfzAL--6v4Mj1nFY,71
|
|
8
8
|
keep/document_store.py,sha256=UswqKIGSc5E-r7Tg9k0g5-byYnuar3e9FieQ7WNod9k,29109
|
|
9
9
|
keep/errors.py,sha256=G9e5FbdfeugyfHOuL_SPZlM5jgWWnwsX4hM7IzanBZc,857
|
|
@@ -12,7 +12,12 @@ keep/logging_config.py,sha256=IGwkgIyg-TfYaT4MnoCXfmjeHAe_wsB_XQ1QhVT_ro8,3503
|
|
|
12
12
|
keep/paths.py,sha256=Dv7pM6oo2QgjL6sj5wPjhuMOK2wqUkfd4Kz08TwJ1ps,3331
|
|
13
13
|
keep/pending_summaries.py,sha256=_irGe7P1Lmog2c5cEgx-BElpq4YJW-tEmF5A3IUZQbQ,5727
|
|
14
14
|
keep/store.py,sha256=SBc2QdTyApdDDVjm2uZQI6tGbV5Hurfetgj7dyTO65o,17881
|
|
15
|
-
keep/types.py,sha256=
|
|
15
|
+
keep/types.py,sha256=_ytiG1O-9fY39o_TOktzYaFxHZBcIfuYgGI_vKsEx30,2316
|
|
16
|
+
keep/data/__init__.py,sha256=C1YARrudHwK2Bmlxkh7dZlIaNJ5m5WrSTglCdG8e3T0,24
|
|
17
|
+
keep/data/system/__init__.py,sha256=Rp92_sBO3kscuWXJomo0HKeHfU-N4BgBeT3-5El0Mcg,28
|
|
18
|
+
keep/data/system/conversations.md,sha256=jE53wYSUyu5uPFNtO1Tu6w4f5QxqLei7muxLF_kZE2s,9837
|
|
19
|
+
keep/data/system/domains.md,sha256=OCRAGvB0EaphsEammxLmx7L-orw2OHzgF6GwAZ8ztUs,5556
|
|
20
|
+
keep/data/system/now.md,sha256=vsPCNy-9k3g5KKIgG0MYL21m8qYV3tI0hMa9co6hffc,442
|
|
16
21
|
keep/providers/__init__.py,sha256=GFX_12g9OdjmpFUkTekOQBOWvcRW2Ae6yidfVVW2SiI,1095
|
|
17
22
|
keep/providers/base.py,sha256=7Ug4Kj9fK2Dq4zDcZjn-GKsoZBOAlB9b-FMk969ER-g,14590
|
|
18
23
|
keep/providers/documents.py,sha256=EXeSy5i3RUL0kciIC6w3ldAEfbTIyC5fgfzC_WAI0iY,8211
|
|
@@ -21,8 +26,8 @@ keep/providers/embeddings.py,sha256=zi8GyitKexdbCJyU1nLrUhGt_zzPn3udYrrPZ5Ak8Wo,
|
|
|
21
26
|
keep/providers/llm.py,sha256=BxROKOklKbkGsHcSADPNNgWQExgSN6Bg4KPQIxVuB3U,12441
|
|
22
27
|
keep/providers/mlx.py,sha256=aNl00r9tGi5tCGj2ArYH7CmDHtL1jLjVzb1rofU1DAo,9050
|
|
23
28
|
keep/providers/summarization.py,sha256=MlVTcYipaqp2lT-QYnznp0AMuPVG36QfcTQnvY7Gb-Q,3409
|
|
24
|
-
keep_skill-0.
|
|
25
|
-
keep_skill-0.
|
|
26
|
-
keep_skill-0.
|
|
27
|
-
keep_skill-0.
|
|
28
|
-
keep_skill-0.
|
|
29
|
+
keep_skill-0.6.0.dist-info/METADATA,sha256=lTAvkxt0X4pgtkLVfqZMBOdeGKz_tqO6KMm-nM2egjU,6606
|
|
30
|
+
keep_skill-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
31
|
+
keep_skill-0.6.0.dist-info/entry_points.txt,sha256=W8yiI4kNeW0IC8ji4EHRWrvdhFxzaqTIePUhJAJAMOo,39
|
|
32
|
+
keep_skill-0.6.0.dist-info/licenses/LICENSE,sha256=zsm0tpvtyUkevcjn5BIvs9jAho8iwxq3Ax9647AaOSg,1086
|
|
33
|
+
keep_skill-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|