跳到主要内容

灵感与待办整理

收藏0

把零散想法、口述笔记和待办整理成项目、行动清单与待确认问题,保留原意,不擅自执行。

让 AI 安装

github最新版本 0.1.1更新于 2026-10-21下载 3 次2 个版本

scripts/dump_classifier.py

5.8 KB
1#!/usr/bin/env python3
2"""dump_classifier.py — Heuristic classifier for brain-dump lines.
3
4Stdlib-only. Reads a dump file (or stdin) and labels each line as one of:
5 - task (action-oriented, imperative or 'I should X')
6 - decision ('decide between X and Y', 'should we X or Y')
7 - question (ends in '?')
8 - idea (creative spark, 'what if', 'maybe we should X')
9 - project-component (sub-element of a larger project, often noun-phrased)
10 - context (preamble, framing, no actionable content)
11
12The classifier is HEURISTIC. The capture skill uses these labels as a SEED for
13its own structuring — it overrides based on dump-level context. Do not treat
14the labels as authoritative.
15
16NO LLM CALLS. Pure regex + line walking.
17
18Usage:
19 python dump_classifier.py path/to/dump.txt
20 python dump_classifier.py path/to/dump.txt --output json
21 python dump_classifier.py --sample
22"""
23
24import argparse
25import json
26import re
27import sys
28from pathlib import Path
29from typing import Any, Dict, List, Tuple
30
31
32# Pattern → label, ordered by precedence (first match wins per line).
33# Each pattern is a compiled regex.
34PATTERNS: List[Tuple[re.Pattern, str]] = [
35 (re.compile(r"\bdecide\s+(?:between|on|whether)\b", re.IGNORECASE), "decision"),
36 (re.compile(r"^\s*decide\s*:", re.IGNORECASE), "decision"),
37 (re.compile(r"\b(?:should we|do we|are we)\b.*\b(?:or|vs|versus)\b", re.IGNORECASE), "decision"),
38 (re.compile(r"\?\s*$"), "question"),
39 (re.compile(r"^\s*(?:resolve|q)\s*:", re.IGNORECASE), "question"),
40 (re.compile(r"\bwhat\s+if\b", re.IGNORECASE), "idea"),
41 (re.compile(r"\b(?:maybe|might|could)\s+(?:we|i)\s+(?:should\s+)?", re.IGNORECASE), "idea"),
42 (re.compile(r"\bidea\s*:", re.IGNORECASE), "idea"),
43 (re.compile(r"^\s*(?:i\s+(?:should|need\s+to|gotta|have\s+to))\b", re.IGNORECASE), "task"),
44 (re.compile(r"^\s*(?:fix|build|write|draft|email|send|talk to|finish|investigate|research|sketch|scaffold|deploy|push|merge|review|read|call|schedule)\b", re.IGNORECASE), "task"),
45 (re.compile(r"^\s*todo\s*:", re.IGNORECASE), "task"),
46 (re.compile(r"^\s*-\s*(?:fix|build|write|draft|email|send|talk to|finish|investigate)\b", re.IGNORECASE), "task"),
47]
48
49PROJECT_COMPONENT_HINTS = {
50 "module", "feature", "component", "endpoint", "page", "screen", "form",
51 "model", "schema", "migration", "test", "doc", "readme", "config",
52}
53
54
55SAMPLE_DUMP = """Ok dump time.
56
57Q3 launch is approaching - need to nail down pricing.
58Draft the launch email.
59Brief Sarah on the marketing angle.
60
61Decide: launch on July 15 or August 1?
62
63Ferret app keeps nagging me. Should I talk to my cofounder about it?
64What if it's actually a real business?
65Sketch the matching algorithm if it's serious.
66
67Fix the damn auth bug.
68Rewrite the login form because it's ugly.
69Write tests for both.
70Auth: 2fa module.
71
72Do my Q3 OKRs before launch.
73"""
74
75
76def classify_line(raw: str) -> str:
77 line = raw.strip()
78 if not line:
79 return "blank"
80
81 # Strip leading bullet markers for matching, but keep original for output
82 stripped = re.sub(r"^[-*+]\s+", "", line)
83
84 for pattern, label in PATTERNS:
85 if pattern.search(stripped):
86 return label
87
88 # Project-component heuristic: short noun-phrase containing a hint word
89 if len(stripped.split()) <= 6:
90 for hint in PROJECT_COMPONENT_HINTS:
91 if re.search(rf"\b{hint}s?\b", stripped, re.IGNORECASE):
92 return "project-component"
93
94 # Single-noun-phrase or short fragment without verb → likely context or component
95 if len(stripped.split()) <= 4 and not stripped.endswith("?"):
96 return "project-component"
97
98 # Default: treat as context (the skill folds this into project framing)
99 return "context"
100
101
102def classify(text: str) -> Dict[str, Any]:
103 items: List[Dict[str, Any]] = []
104 counts: Dict[str, int] = {}
105 for line_no, raw in enumerate(text.splitlines(), start=1):
106 if not raw.strip():
107 continue
108 label = classify_line(raw)
109 if label == "blank":
110 continue
111 items.append({"line": line_no, "label": label, "text": raw.strip()})
112 counts[label] = counts.get(label, 0) + 1
113 return {"item_count": len(items), "by_label": counts, "items": items}
114
115
116def render_human(result: Dict[str, Any]) -> str:
117 out: List[str] = []
118 out.append(f"Dump classification ({result['item_count']} non-empty items)")
119 out.append("By label:")
120 for label, n in sorted(result["by_label"].items(), key=lambda kv: -kv[1]):
121 out.append(f" {label:<20s} {n}")
122 out.append("")
123 out.append("Per-line labels:")
124 for it in result["items"]:
125 out.append(f" L{it['line']:>3} {it['label']:<20s} {it['text'][:80]}")
126 return "\n".join(out)
127
128
129def main(argv: List[str]) -> int:
130 parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
131 parser.add_argument("path", nargs="?", help="Path to dump file (or omit for --sample)")
132 parser.add_argument("--sample", action="store_true", help="Classify the embedded sample dump")
133 parser.add_argument("--output", choices=["human", "json"], default="human")
134 args = parser.parse_args(argv)
135
136 if args.sample:
137 text = SAMPLE_DUMP
138 elif args.path:
139 p = Path(args.path)
140 if not p.exists():
141 print(f"error: {args.path} not found", file=sys.stderr)
142 return 2
143 text = p.read_text(encoding="utf-8")
144 else:
145 parser.print_help()
146 return 0
147
148 result = classify(text)
149 if args.output == "json":
150 print(json.dumps(result, indent=2))
151 else:
152 print(render_human(result))
153 return 0
154
155
156if __name__ == "__main__":
157 sys.exit(main(sys.argv[1:]))

文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。