spec: add BEHAVIOR_SPEC and fix B-01~B-10 (resolved/decay/scoring)

- Add BEHAVIOR_SPEC.md as full system behaviour reference
- B-01: stop auto-archiving resolved buckets in update()
- B-03: keep activation_count as float in calculate_score
- B-04: initialise activation_count=0 on create
- B-05: time score coefficient 0.1 -> 0.02
- B-06: w_time default 2.5 -> 1.5
- B-07: content_weight default 3.0 -> 1.0
- B-08: refresh local meta after auto_resolve
- B-09: user-supplied valence/arousal takes priority over analyze()
- B-10: allow empty domain for feel buckets
- Refresh INTERNALS/README/dashboard accordingly
This commit is contained in:
P0luz
2026-04-21 18:45:52 +08:00
parent c7ddfd46ad
commit ccdffdb626
10 changed files with 1186 additions and 36 deletions

View File

@@ -39,6 +39,8 @@ def _parse_claude_json(data: dict | list) -> list[dict]:
turns = []
conversations = data if isinstance(data, list) else [data]
for conv in conversations:
if not isinstance(conv, dict):
continue
messages = conv.get("chat_messages", conv.get("messages", []))
for msg in messages:
if not isinstance(msg, dict):
@@ -61,18 +63,27 @@ def _parse_chatgpt_json(data: list | dict) -> list[dict]:
turns = []
conversations = data if isinstance(data, list) else [data]
for conv in conversations:
if not isinstance(conv, dict):
continue
mapping = conv.get("mapping", {})
if mapping:
# ChatGPT uses a tree structure with mapping
sorted_nodes = sorted(
mapping.values(),
key=lambda n: n.get("message", {}).get("create_time", 0) or 0,
)
# Filter out None nodes before sorting
valid_nodes = [n for n in mapping.values() if isinstance(n, dict)]
def _node_ts(n):
msg = n.get("message")
if not isinstance(msg, dict):
return 0
return msg.get("create_time") or 0
sorted_nodes = sorted(valid_nodes, key=_node_ts)
for node in sorted_nodes:
msg = node.get("message")
if not msg or not isinstance(msg, dict):
continue
content_parts = msg.get("content", {}).get("parts", [])
content_obj = msg.get("content", {})
content_parts = content_obj.get("parts", []) if isinstance(content_obj, dict) else []
content = " ".join(str(p) for p in content_parts if p)
if not content.strip():
continue
@@ -168,7 +179,7 @@ def detect_and_parse(raw_content: str, filename: str = "") -> list[dict]:
# Single conversation object with role/content messages
if "role" in sample and "content" in sample:
return _parse_claude_json(data)
except (json.JSONDecodeError, KeyError, IndexError):
except (json.JSONDecodeError, KeyError, IndexError, AttributeError, TypeError):
pass
# Fall back to markdown/text