跳到主要内容

灵感与待办整理

收藏0

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

让 AI 安装

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

scripts/complexity_estimator.py

7.1 KB
1#!/usr/bin/env python3
2"""complexity_estimator.py — Recommend full-4-section vs compressed output.
3
4Stdlib-only. Counts non-empty items in a dump, detects clustering signal
5(repeated keywords across items), and recommends one of:
6
7 format=full → use the full Projects/Tasks/Connections/How-I-Can-Help
8 4-section format (8+ items AND clustering signal)
9 format=compressed → use the compressed What-I-heard / How-I-can-help
10 format (≤5 items OR no clustering signal)
11
12The recommendation is HEURISTIC. The capture skill applies judgment on top
13based on full dump context. Use this as the seed.
14
15NO LLM CALLS. Pure word counting + frequency analysis.
16
17Usage:
18 python complexity_estimator.py path/to/dump.txt
19 python complexity_estimator.py path/to/dump.txt --output json
20 python complexity_estimator.py --sample
21"""
22
23import argparse
24import json
25import re
26import sys
27from collections import Counter
28from pathlib import Path
29from typing import Any, Dict, List
30
31
32SAMPLE_DUMP_LARGE = """Ok dump time.
33Q3 launch needs pricing nailed down.
34Draft the Q3 launch email.
35Brief Sarah on the Q3 marketing angle.
36Decide: launch July 15 or August 1?
37Ferret app idea keeps nagging me.
38Should I talk to my cofounder about ferret app?
39Sketch the ferret matching algorithm if serious.
40Decide: ferret app serious or shelf?
41Fix the auth bug.
42Rewrite the login form (ugly).
43Write tests for auth + login.
44Add 2fa module to auth.
45Do my Q3 OKRs before launch.
46"""
47
48SAMPLE_DUMP_SMALL = """Email Sarah.
49Fix the flaky test.
50Decide: Postgres or Mongo for new service.
51Dentist appointment.
52Finish reading the RAG article.
53"""
54
55
56# Stop-words to exclude from clustering detection
57STOP_WORDS = {
58 "a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
59 "to", "of", "in", "on", "at", "for", "with", "by", "from", "up", "down",
60 "and", "or", "but", "if", "then", "else", "so", "as",
61 "i", "you", "he", "she", "it", "we", "they", "me", "my", "your", "our",
62 "this", "that", "these", "those", "do", "did", "have", "has", "had",
63 "will", "would", "should", "could", "can", "may", "might",
64 "what", "when", "where", "why", "how", "who", "which",
65 "go", "get", "got", "make", "made", "let", "let's", "yeah", "ok", "well",
66 "just", "really", "very", "much", "more", "most", "some", "any",
67 "not", "no", "yes", "now", "before", "after",
68}
69
70
71def extract_items(text: str) -> List[str]:
72 """Return non-empty stripped lines (each line = one item)."""
73 return [line.strip() for line in text.splitlines() if line.strip()]
74
75
76def extract_keywords(items: List[str]) -> List[str]:
77 """Return all alphabetic tokens >= 3 chars, lowercased, stop-words removed."""
78 tokens: List[str] = []
79 for it in items:
80 for tok in re.findall(r"\b[A-Za-z][a-zA-Z]{2,}\b", it):
81 t = tok.lower()
82 if t in STOP_WORDS:
83 continue
84 tokens.append(t)
85 return tokens
86
87
88def detect_clusters(items: List[str], min_cluster_size: int) -> List[Dict[str, Any]]:
89 """A 'cluster' is a keyword that appears in min_cluster_size+ different items."""
90 keyword_to_item_indices: Dict[str, List[int]] = {}
91 for i, it in enumerate(items):
92 seen_in_item: set = set()
93 for tok in re.findall(r"\b[A-Za-z][a-zA-Z]{2,}\b", it):
94 t = tok.lower()
95 if t in STOP_WORDS or t in seen_in_item:
96 continue
97 seen_in_item.add(t)
98 keyword_to_item_indices.setdefault(t, []).append(i)
99 clusters: List[Dict[str, Any]] = []
100 for kw, idxs in keyword_to_item_indices.items():
101 if len(idxs) >= min_cluster_size:
102 clusters.append({"keyword": kw, "item_indices": idxs, "size": len(idxs)})
103 clusters.sort(key=lambda c: (-c["size"], c["keyword"]))
104 return clusters
105
106
107def estimate(text: str, min_cluster_size: int = 3) -> Dict[str, Any]:
108 items = extract_items(text)
109 item_count = len(items)
110 clusters = detect_clusters(items, min_cluster_size)
111 cluster_count = len(clusters)
112
113 # Decision logic per references/complexity_matching.md
114 if item_count >= 8 and cluster_count >= 1:
115 recommendation = "full"
116 rationale = f"{item_count} items with {cluster_count} cluster(s) of {min_cluster_size}+ → full 4-section format"
117 elif item_count >= 8 and cluster_count == 0:
118 recommendation = "compressed"
119 rationale = f"{item_count} items but no clustering signal → compressed (with 'no clusters' note)"
120 elif 5 <= item_count <= 7 and cluster_count >= 1:
121 recommendation = "full"
122 rationale = f"{item_count} items with clustering signal → judgment call, defaulting full"
123 elif 5 <= item_count <= 7 and cluster_count == 0:
124 recommendation = "compressed"
125 rationale = f"{item_count} items, no clustering → compressed"
126 elif item_count <= 5:
127 recommendation = "compressed"
128 rationale = f"{item_count} items (small dump) → compressed"
129 else:
130 recommendation = "compressed"
131 rationale = "fallback → compressed"
132
133 return {
134 "item_count": item_count,
135 "cluster_count": cluster_count,
136 "clusters": clusters[:5], # top 5 only in output for readability
137 "recommendation": recommendation,
138 "rationale": rationale,
139 }
140
141
142def render_human(result: Dict[str, Any]) -> str:
143 out: List[str] = []
144 out.append(f"Item count: {result['item_count']}")
145 out.append(f"Cluster count: {result['cluster_count']}")
146 out.append(f"Recommendation: format={result['recommendation']}")
147 out.append(f"Rationale: {result['rationale']}")
148 if result["clusters"]:
149 out.append("")
150 out.append("Top clusters (keyword → items containing it):")
151 for c in result["clusters"]:
152 out.append(f" - '{c['keyword']}' in {c['size']} items: lines {c['item_indices']}")
153 return "\n".join(out)
154
155
156def main(argv: List[str]) -> int:
157 parser = argparse.ArgumentParser(description=__doc__.split("\n")[0])
158 parser.add_argument("path", nargs="?", help="Path to dump file")
159 parser.add_argument("--sample", choices=["large", "small"], help="Estimate the embedded sample dump (large or small)")
160 parser.add_argument("--min-cluster-size", type=int, default=3, help="Minimum items sharing a keyword to count as a cluster (default: 3)")
161 parser.add_argument("--output", choices=["human", "json"], default="human")
162 args = parser.parse_args(argv)
163
164 if args.sample:
165 text = SAMPLE_DUMP_LARGE if args.sample == "large" else SAMPLE_DUMP_SMALL
166 elif args.path:
167 p = Path(args.path)
168 if not p.exists():
169 print(f"error: {args.path} not found", file=sys.stderr)
170 return 2
171 text = p.read_text(encoding="utf-8")
172 else:
173 parser.print_help()
174 return 0
175
176 result = estimate(text, args.min_cluster_size)
177 if args.output == "json":
178 print(json.dumps(result, indent=2))
179 else:
180 print(render_human(result))
181 return 0
182
183
184if __name__ == "__main__":
185 sys.exit(main(sys.argv[1:]))

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