ultimate-pi 0.19.0 → 0.20.0

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.
Files changed (85) hide show
  1. package/.agents/skills/web-retrieval/SKILL.md +163 -0
  2. package/.agents/skills/wiki-autoresearch/SKILL.md +6 -6
  3. package/.pi/SYSTEM.md +30 -12
  4. package/.pi/agents/harness/planning/implementation-researcher.md +1 -1
  5. package/.pi/agents/harness/planning/stack-researcher.md +5 -1
  6. package/.pi/agents/harness/running/executor.md +42 -1
  7. package/.pi/agents/harness/web-retrieval/web-answerer.md +35 -0
  8. package/.pi/agents/harness/web-retrieval/web-criteria-verifier.md +28 -0
  9. package/.pi/agents/harness/web-retrieval/web-gap-analyzer.md +31 -0
  10. package/.pi/agents/harness/web-retrieval/web-query-expander-fast.md +34 -0
  11. package/.pi/agents/harness/web-retrieval/web-query-expander.md +60 -0
  12. package/.pi/agents/harness/web-retrieval/web-summarizer.md +18 -0
  13. package/.pi/extensions/harness-anchored-edit.ts +141 -0
  14. package/.pi/extensions/harness-web-guard.ts +2 -1
  15. package/.pi/extensions/harness-web-tools.ts +689 -51
  16. package/.pi/harness/agents.manifest.json +30 -6
  17. package/.pi/harness/agents.policy.yaml +37 -4
  18. package/.pi/harness/docs/adrs/0050-agentic-web-retrieval-stack.md +46 -0
  19. package/.pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md +41 -0
  20. package/.pi/harness/docs/adrs/README.md +2 -0
  21. package/.pi/harness/docs/harness-web-search.md +97 -0
  22. package/.pi/harness/docs/practice-map.md +11 -0
  23. package/.pi/harness/env.harness.template +9 -1
  24. package/.pi/harness/examples/web-heuristic-angles.project.yaml +22 -0
  25. package/.pi/harness/web-heuristic-angles.json +278 -0
  26. package/.pi/harness/web-heuristic-angles.yaml +182 -0
  27. package/.pi/lib/agents-policy.d.mts +4 -0
  28. package/.pi/lib/agents-policy.mjs +49 -1
  29. package/.pi/lib/agents-policy.ts +1 -0
  30. package/.pi/lib/harness-anchored-edit/.hash_anchors +1721 -0
  31. package/.pi/lib/harness-anchored-edit/anchor-state.ts +320 -0
  32. package/.pi/lib/harness-anchored-edit/apply-anchored-edits.ts +161 -0
  33. package/.pi/lib/harness-anchored-edit/edit-executor.ts +146 -0
  34. package/.pi/lib/harness-anchored-edit/index.ts +9 -0
  35. package/.pi/lib/harness-anchored-edit/line-protocol.ts +38 -0
  36. package/.pi/lib/harness-anchored-edit/settings.ts +1 -0
  37. package/.pi/lib/harness-anchored-edit/task-id.ts +8 -0
  38. package/.pi/lib/harness-anchored-edit/types.ts +19 -0
  39. package/.pi/lib/harness-lens/clients/anchored-edit-autopatch.ts +158 -0
  40. package/.pi/lib/harness-lens/index.ts +24 -7
  41. package/.pi/lib/harness-subagent-auth.ts +39 -9
  42. package/.pi/lib/harness-subagents-bridge.ts +24 -1
  43. package/.pi/lib/harness-web/artifacts.ts +200 -0
  44. package/.pi/lib/harness-web/cache.ts +369 -0
  45. package/.pi/lib/harness-web/run-cli.ts +42 -2
  46. package/.pi/prompts/harness-plan.md +1 -0
  47. package/.pi/prompts/harness-setup.md +3 -1
  48. package/.pi/prompts/harness-steer.md +1 -1
  49. package/.pi/scripts/gen-web-heuristic-angles-json.mjs +24 -0
  50. package/.pi/scripts/harness-anchored-edit-smoke.mjs +45 -0
  51. package/.pi/scripts/harness-cli-verify.sh +5 -0
  52. package/.pi/scripts/harness-verify.mjs +145 -0
  53. package/.pi/scripts/harness-web-policy-guard.mjs +1 -1
  54. package/.pi/scripts/harness-web.py +218 -15
  55. package/.pi/scripts/harness_web/deep_search.py +55 -0
  56. package/.pi/scripts/harness_web/evidence_bundle.py +47 -0
  57. package/.pi/scripts/harness_web/find_similar.py +88 -0
  58. package/.pi/scripts/harness_web/heuristic_angles_shipped.py +85 -0
  59. package/.pi/scripts/harness_web/heuristic_config.py +251 -0
  60. package/.pi/scripts/harness_web/highlights.py +47 -0
  61. package/.pi/scripts/harness_web/multi_search.py +59 -0
  62. package/.pi/scripts/harness_web/output.py +24 -0
  63. package/.pi/scripts/harness_web/query_angles.py +116 -0
  64. package/.pi/scripts/harness_web/rank.py +163 -0
  65. package/.pi/scripts/harness_web/scrape.py +30 -0
  66. package/.pi/scripts/run-tests.mjs +64 -0
  67. package/.pi/scripts/tests/test_harness_web_heuristic_config.py +132 -0
  68. package/.pi/scripts/tests/test_harness_web_query_angles.py +45 -0
  69. package/.pi/scripts/tests/test_harness_web_rank.py +56 -0
  70. package/AGENTS.md +2 -2
  71. package/CHANGELOG.md +12 -0
  72. package/THIRD_PARTY_NOTICES.md +7 -0
  73. package/package.json +7 -4
  74. package/vendor/pi-subagents/src/agents.ts +5 -0
  75. package/vendor/pi-subagents/src/subagents.ts +22 -3
  76. package/.agents/skills/scrapling-web/SKILL.md +0 -98
  77. package/.pi/extensions/00-posthog-network-bootstrap.ts +0 -11
  78. package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
  79. package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
  80. package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
  81. package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
  82. package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
  83. package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
  84. package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
  85. package/.pi/scripts/release.sh +0 -338
@@ -0,0 +1,163 @@
1
+ """URL normalization and RRF fusion for multi-angle SERP results."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from typing import Any
8
+ from urllib.parse import parse_qs, urlparse, urlunparse
9
+
10
+ RRF_K = 60
11
+
12
+ _TRACKING_PARAMS = frozenset(
13
+ {
14
+ "utm_source",
15
+ "utm_medium",
16
+ "utm_campaign",
17
+ "utm_term",
18
+ "utm_content",
19
+ "fbclid",
20
+ "gclid",
21
+ "mc_cid",
22
+ "mc_eid",
23
+ }
24
+ )
25
+
26
+
27
+ @dataclass
28
+ class RankedHit:
29
+ url: str
30
+ title: str
31
+ description: str
32
+ score: float
33
+ angle_ids: list[str] = field(default_factory=list)
34
+ ranks: dict[str, int] = field(default_factory=dict)
35
+
36
+ def to_web_dict(self) -> dict[str, Any]:
37
+ return {
38
+ "url": self.url,
39
+ "title": self.title,
40
+ "description": self.description,
41
+ "score": round(self.score, 6),
42
+ "angle_ids": list(self.angle_ids),
43
+ "ranks": dict(self.ranks),
44
+ }
45
+
46
+
47
+ def normalize_url(url: str) -> str:
48
+ u = url.strip()
49
+ if not u:
50
+ return ""
51
+ parsed = urlparse(u)
52
+ scheme = (parsed.scheme or "https").lower()
53
+ host = (parsed.hostname or "").lower()
54
+ if not host:
55
+ return u
56
+ port = parsed.port
57
+ netloc = host
58
+ if port and not ((scheme == "http" and port == 80) or (scheme == "https" and port == 443)):
59
+ netloc = f"{host}:{port}"
60
+ path = parsed.path or "/"
61
+ if path != "/" and path.endswith("/"):
62
+ path = path.rstrip("/")
63
+ qs = parse_qs(parsed.query, keep_blank_values=False)
64
+ filtered = []
65
+ for key in sorted(qs.keys()):
66
+ if key.lower() in _TRACKING_PARAMS:
67
+ continue
68
+ for val in qs[key]:
69
+ filtered.append(f"{key}={val}")
70
+ query = "&".join(filtered)
71
+ return urlunparse((scheme, netloc, path, "", query, ""))
72
+
73
+
74
+ def tokenize(text: str) -> set[str]:
75
+ return {t for t in re.findall(r"[a-z0-9]{3,}", text.lower()) if len(t) >= 3}
76
+
77
+
78
+ def lexical_rerank(hits: list[RankedHit], intent: str) -> list[RankedHit]:
79
+ """Lightweight O3 boost when HARNESS_WEB_RERANK=lexical."""
80
+ intent_tokens = tokenize(intent)
81
+ if not intent_tokens:
82
+ return hits
83
+
84
+ def lex_score(h: RankedHit) -> float:
85
+ blob = f"{h.title} {h.description}".lower()
86
+ tokens = tokenize(blob)
87
+ if not tokens:
88
+ return 0.0
89
+ overlap = len(intent_tokens & tokens) / max(len(intent_tokens), 1)
90
+ return overlap
91
+
92
+ scored = [(h, h.score + 0.15 * lex_score(h)) for h in hits]
93
+ scored.sort(key=lambda x: x[1], reverse=True)
94
+ out: list[RankedHit] = []
95
+ for h, s in scored:
96
+ out.append(
97
+ RankedHit(
98
+ url=h.url,
99
+ title=h.title,
100
+ description=h.description,
101
+ score=s,
102
+ angle_ids=h.angle_ids,
103
+ ranks=h.ranks,
104
+ )
105
+ )
106
+ return out
107
+
108
+
109
+ def fuse_angle_results(
110
+ per_angle: dict[str, list[dict[str, str]]],
111
+ *,
112
+ final_limit: int = 10,
113
+ intent: str = "",
114
+ rerank_mode: str = "off",
115
+ ) -> list[RankedHit]:
116
+ """Reciprocal Rank Fusion across angle result lists."""
117
+ accum: dict[str, dict[str, Any]] = {}
118
+
119
+ for angle_id, results in per_angle.items():
120
+ for rank_1based, item in enumerate(results, start=1):
121
+ raw_url = (item.get("url") or "").strip()
122
+ norm = normalize_url(raw_url)
123
+ if not norm or not norm.startswith("http"):
124
+ continue
125
+ entry = accum.setdefault(
126
+ norm,
127
+ {
128
+ "url": raw_url,
129
+ "title": "",
130
+ "description": "",
131
+ "score": 0.0,
132
+ "angle_ids": [],
133
+ "ranks": {},
134
+ },
135
+ )
136
+ entry["score"] += 1.0 / (RRF_K + rank_1based)
137
+ if angle_id not in entry["angle_ids"]:
138
+ entry["angle_ids"].append(angle_id)
139
+ entry["ranks"][angle_id] = rank_1based
140
+ title = (item.get("title") or "").strip()
141
+ desc = (item.get("description") or "").strip()
142
+ if title and not entry["title"]:
143
+ entry["title"] = title
144
+ if desc and (not entry["description"] or len(desc) > len(entry["description"])):
145
+ entry["description"] = desc
146
+
147
+ hits = [
148
+ RankedHit(
149
+ url=e["url"],
150
+ title=e["title"],
151
+ description=e["description"],
152
+ score=e["score"],
153
+ angle_ids=e["angle_ids"],
154
+ ranks=e["ranks"],
155
+ )
156
+ for e in accum.values()
157
+ ]
158
+ hits.sort(key=lambda h: (-h.score, -len(h.angle_ids), min(h.ranks.values()) if h.ranks else 999))
159
+
160
+ if rerank_mode == "lexical" and intent:
161
+ hits = lexical_rerank(hits, intent)
162
+
163
+ return hits[:final_limit]
@@ -41,6 +41,36 @@ def scrape_url(
41
41
  write_page_markdown(Path(output), page, main_content_only=True)
42
42
 
43
43
 
44
+ def scrape_url_with_highlights(
45
+ url: str,
46
+ markdown_output: str,
47
+ highlights_output: str | None,
48
+ *,
49
+ config: HarnessWebConfig,
50
+ fast: bool,
51
+ wait_ms: int | None,
52
+ highlight_query: str,
53
+ ) -> None:
54
+ import json
55
+ from pathlib import Path
56
+
57
+ from .highlights import extract_highlights
58
+
59
+ page = fetch_page(url, config=config, fast=fast, wait_ms=wait_ms)
60
+ md_path = Path(markdown_output)
61
+ write_page_markdown(md_path, page, main_content_only=True)
62
+ if highlights_output and highlight_query.strip():
63
+ text = md_path.read_text(encoding="utf-8")
64
+ spans = extract_highlights(text, highlight_query)
65
+ hp = Path(highlights_output)
66
+ hp.parent.mkdir(parents=True, exist_ok=True)
67
+ hp.write_text(
68
+ json.dumps({"url": url, "query": highlight_query, "highlights": spans}, indent=2)
69
+ + "\n",
70
+ encoding="utf-8",
71
+ )
72
+
73
+
44
74
  def map_url(
45
75
  url: str,
46
76
  output: str,
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ import { readdirSync } from 'node:fs';
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ const TEST_DIR = 'test';
6
+
7
+ const NODE_ONLY_TESTS = [
8
+ 'harness-verify.test.mjs',
9
+ 'harness-ask-user.test.mjs',
10
+ 'harness-subagents-loader.test.mjs',
11
+ 'harness-subagent-precheck.test.mjs',
12
+ 'sentrux-rules-sync.test.mjs',
13
+ 'harness-budget-guard.test.mjs',
14
+ ];
15
+
16
+ const DEFAULT_SUITE_EXCLUDES = new Set([
17
+ 'graphify-kb-updater.test.mjs',
18
+ 'harness-artifact-gate.test.mjs',
19
+ 'harness-spawn-critical-path.test.mjs',
20
+ 'harness-yaml.test.mjs',
21
+ 'plan-debate-lanes.test.mjs',
22
+ ]);
23
+
24
+ function listTests() {
25
+ const entries = readdirSync(TEST_DIR, { withFileTypes: true });
26
+ const files = entries.filter((entry) => entry.isFile()).map((entry) => entry.name);
27
+ const nodeOnlySet = new Set(NODE_ONLY_TESTS);
28
+
29
+ const nodeTests = NODE_ONLY_TESTS.filter((name) => files.includes(name)).map((name) => `${TEST_DIR}/${name}`);
30
+ const tsxTests = files
31
+ .filter(
32
+ (name) =>
33
+ (name.endsWith('.test.mjs') || name.endsWith('.test.ts')) &&
34
+ !name.endsWith('.integration.test.ts') &&
35
+ !nodeOnlySet.has(name) &&
36
+ !DEFAULT_SUITE_EXCLUDES.has(name),
37
+ )
38
+ .map((name) => `${TEST_DIR}/${name}`)
39
+ .sort();
40
+
41
+ return { nodeTests, tsxTests };
42
+ }
43
+
44
+ function run(cmd, args) {
45
+ const result = spawnSync(cmd, args, { stdio: 'inherit' });
46
+ if (result.error) {
47
+ throw result.error;
48
+ }
49
+ if (result.status !== 0) {
50
+ process.exit(result.status ?? 1);
51
+ }
52
+ }
53
+
54
+ const { nodeTests, tsxTests } = listTests();
55
+
56
+ if (nodeTests.length > 0) {
57
+ run('node', ['--test', ...nodeTests]);
58
+ }
59
+
60
+ run('node', ['.pi/harness/evals/smoke/smoke-harness-plan.mjs', '--fixture']);
61
+
62
+ if (tsxTests.length > 0) {
63
+ run('npx', ['-y', 'tsx', '--test', ...tsxTests]);
64
+ }
@@ -0,0 +1,132 @@
1
+ """Unit tests for harness_web.heuristic_config."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import tempfile
8
+ import unittest
9
+ from pathlib import Path
10
+
11
+ from harness_web.heuristic_config import (
12
+ _embedded_builtin_dict,
13
+ _merge_config_dict,
14
+ build_heuristic_angles,
15
+ clear_heuristic_config_cache,
16
+ heuristic_config_from_merged,
17
+ load_heuristic_angles_config_cached,
18
+ )
19
+
20
+
21
+ class TestHeuristicConfig(unittest.TestCase):
22
+ def tearDown(self) -> None:
23
+ clear_heuristic_config_cache()
24
+
25
+ def test_builtin_code_includes_stackoverflow(self) -> None:
26
+ cfg = heuristic_config_from_merged(_embedded_builtin_dict())
27
+ angles = build_heuristic_angles("rust async", category="code", config=cfg)
28
+ ids = {a.id for a in angles}
29
+ self.assertIn("stackoverflow", ids)
30
+ self.assertIn("github", ids)
31
+ self.assertTrue(any("site:stackoverflow.com" in a.query for a in angles))
32
+
33
+ def test_shipped_code_includes_mdn_and_registries(self) -> None:
34
+ pkg = Path(__file__).resolve().parents[2] / "harness" / "web-heuristic-angles.yaml"
35
+ if not pkg.is_file():
36
+ self.skipTest("package yaml missing")
37
+ clear_heuristic_config_cache()
38
+ cfg = load_heuristic_angles_config_cached((str(pkg),))
39
+ angles = build_heuristic_angles("websocket api", category="code", config=cfg)
40
+ ids = {a.id for a in angles}
41
+ self.assertIn("mdn", ids)
42
+ self.assertIn("package_registries", ids)
43
+ self.assertLessEqual(len(angles), cfg.max_angles)
44
+
45
+ def test_shipped_security_category(self) -> None:
46
+ pkg = Path(__file__).resolve().parents[2] / "harness" / "web-heuristic-angles.yaml"
47
+ if not pkg.is_file():
48
+ self.skipTest("package yaml missing")
49
+ clear_heuristic_config_cache()
50
+ cfg = load_heuristic_angles_config_cached((str(pkg),))
51
+ angles = build_heuristic_angles("jwt validation", category="security", config=cfg)
52
+ ids = {a.id for a in angles}
53
+ self.assertIn("owasp", ids)
54
+ self.assertIn("cve_nvd", ids)
55
+
56
+ def test_merge_extends_code_category(self) -> None:
57
+ merged = _merge_config_dict(
58
+ _embedded_builtin_dict(),
59
+ {
60
+ "max_angles": 12,
61
+ "categories": {
62
+ "code": [
63
+ {
64
+ "id": "docs_rs",
65
+ "query": "{query} site:docs.rs",
66
+ "rationale": "Rust docs",
67
+ },
68
+ ],
69
+ },
70
+ },
71
+ )
72
+ cfg = heuristic_config_from_merged(merged)
73
+ merged_ids = [a["id"] for a in merged["categories"]["code"]]
74
+ self.assertIn("docs_rs", merged_ids)
75
+ angles = build_heuristic_angles("tokio", category="code", config=cfg)
76
+ ids = {a.id for a in angles}
77
+ self.assertIn("stackoverflow", ids)
78
+ self.assertIn("github", ids)
79
+
80
+ def test_merge_adds_new_category(self) -> None:
81
+ merged = _merge_config_dict(
82
+ _embedded_builtin_dict(),
83
+ {
84
+ "categories": {
85
+ "security": [
86
+ {"id": "cve", "query": "{query} CVE", "rationale": "vulns"},
87
+ {
88
+ "id": "owasp",
89
+ "query": "{query} site:owasp.org",
90
+ "rationale": "guidance",
91
+ },
92
+ ],
93
+ },
94
+ },
95
+ )
96
+ cfg = heuristic_config_from_merged(merged)
97
+ angles = build_heuristic_angles("jwt auth", category="security", config=cfg)
98
+ ids = {a.id for a in angles}
99
+ self.assertIn("cve", ids)
100
+ self.assertIn("owasp", ids)
101
+
102
+ def test_json_project_file_merges(self) -> None:
103
+ with tempfile.TemporaryDirectory() as tmp:
104
+ proj = Path(tmp)
105
+ harness_dir = proj / ".pi" / "harness"
106
+ harness_dir.mkdir(parents=True)
107
+ proj_file = harness_dir / "web-heuristic-angles.json"
108
+ proj_file.write_text(
109
+ json.dumps(
110
+ {
111
+ "categories": {
112
+ "code": [
113
+ {
114
+ "id": "crates_io",
115
+ "query": "{query} site:crates.io",
116
+ "rationale": "crates",
117
+ },
118
+ ],
119
+ },
120
+ }
121
+ ),
122
+ encoding="utf-8",
123
+ )
124
+ clear_heuristic_config_cache()
125
+ cfg = load_heuristic_angles_config_cached((str(proj_file),))
126
+ angles = build_heuristic_angles("serde", category="code", config=cfg)
127
+ ids = {a.id for a in angles}
128
+ self.assertIn("crates_io", ids)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ unittest.main()
@@ -0,0 +1,45 @@
1
+ """Unit tests for harness_web.query_angles (no network)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import tempfile
7
+ import unittest
8
+ from pathlib import Path
9
+
10
+ from harness_web.query_angles import AnglesPlan, load_angles_file, resolve_angles
11
+
12
+
13
+ class TestResolveAngles(unittest.TestCase):
14
+ def test_heuristic_code_category(self) -> None:
15
+ plan = resolve_angles("rust async", expand_heuristic=True, category="code")
16
+ ids = {a.id for a in plan.angles}
17
+ self.assertIn("github", ids)
18
+ self.assertGreaterEqual(len(plan.angles), 2)
19
+ self.assertLessEqual(len(plan.angles), 5)
20
+
21
+
22
+ class TestLoadFile(unittest.TestCase):
23
+ def test_load_json_file(self) -> None:
24
+ with tempfile.TemporaryDirectory() as tmp:
25
+ p = Path(tmp) / "angles.json"
26
+ p.write_text(
27
+ json.dumps(
28
+ {
29
+ "intent": "load test",
30
+ "angles": [
31
+ {"id": "a", "query": "first angle query"},
32
+ {"id": "b", "query": "second angle query"},
33
+ ],
34
+ }
35
+ ),
36
+ encoding="utf-8",
37
+ )
38
+ plan = load_angles_file(p)
39
+ self.assertIsInstance(plan, AnglesPlan)
40
+ self.assertEqual(plan.intent, "load test")
41
+ self.assertEqual(len(plan.angles), 2)
42
+
43
+
44
+ if __name__ == "__main__":
45
+ unittest.main()
@@ -0,0 +1,56 @@
1
+ """Unit tests for harness_web.rank (no network)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import unittest
6
+
7
+ from harness_web.rank import RankedHit, fuse_angle_results, lexical_rerank, normalize_url, tokenize
8
+
9
+
10
+ class TestNormalizeUrl(unittest.TestCase):
11
+ def test_strips_tracking(self) -> None:
12
+ a = normalize_url("https://Example.com/path?utm_source=x&id=1")
13
+ b = normalize_url("https://example.com/path?id=1")
14
+ self.assertEqual(a, b)
15
+
16
+ def test_trailing_slash(self) -> None:
17
+ self.assertEqual(
18
+ normalize_url("https://example.com/foo/"),
19
+ normalize_url("https://example.com/foo"),
20
+ )
21
+
22
+
23
+ class TestRrfFusion(unittest.TestCase):
24
+ def test_merges_duplicate_urls(self) -> None:
25
+ angle_results = {
26
+ "a": [
27
+ {"url": "https://x.com/1", "title": "T1", "description": "d1"},
28
+ {"url": "https://x.com/2", "title": "T2", "description": "d2"},
29
+ ],
30
+ "b": [
31
+ {"url": "https://x.com/1", "title": "T1b", "description": "d1b"},
32
+ ],
33
+ }
34
+ fused = fuse_angle_results(angle_results, final_limit=5)
35
+ self.assertEqual(len(fused), 2)
36
+ top = fused[0]
37
+ self.assertEqual(top.url, "https://x.com/1")
38
+ self.assertIn("a", top.angle_ids)
39
+ self.assertIn("b", top.angle_ids)
40
+ self.assertGreater(top.score, fused[1].score)
41
+
42
+
43
+ class TestLexicalRerank(unittest.TestCase):
44
+ def test_boosts_intent_overlap(self) -> None:
45
+ hits = [
46
+ RankedHit("https://a", "unrelated", "noise", 0.52, ["a"]),
47
+ RankedHit("https://b", "kubernetes architecture", "how kubernetes works", 0.50, ["b"]),
48
+ ]
49
+ reranked = lexical_rerank(hits, "kubernetes architecture")
50
+ self.assertEqual(reranked[0].url, "https://b")
51
+ self.assertGreater(reranked[0].score, reranked[1].score)
52
+
53
+
54
+ class TestTokenize(unittest.TestCase):
55
+ def test_min_length(self) -> None:
56
+ self.assertIn("hello", tokenize("hello hi"))
package/AGENTS.md CHANGED
@@ -16,7 +16,7 @@ Created: 2026-05-14
16
16
  - docs/adr/ → Repo-level Architectural Decision Records
17
17
  - .pi/harness/docs/adrs/ → Harness ADRs (team-shared; [index](.pi/harness/docs/adrs/README.md))
18
18
  - .pi/harness/docs/practice-map.md → Phase → practice → agent spawn topology for `/harness-plan`, `/harness-run`, `/harness-review`
19
- - .pi/skills/ → Agent skills
19
+ - .pi/skills/ → Agent skills (harness skills symlink to `.agents/skills/`, e.g. `web-retrieval`)
20
20
  - .pi/agents/ → Specialized agents
21
21
 
22
22
  ## Graphify-First Workflow
@@ -36,7 +36,7 @@ Created: 2026-05-14
36
36
  - Harness context: **context-mode only** — never lean-ctx on harness paths (see harness-context skill)
37
37
  - `graphify update .` after significant code changes
38
38
  - ast-grep (`sg`) is the default code search tool — use `sg -p 'pattern'` for structural search, never grep for code
39
- - Web fetch/search via `python3 "$UP_PKG/.pi/scripts/harness-web.py"` (Scrapling; see scrapling-web skill)
39
+ - Non-API web: invoke **`web-retrieval`** skill (WRS tiers; default `tier=deep` with `web-query-expander` → `anglesFile`). CLI: `python3 "$UP_PKG/.pi/scripts/harness-web.py"`
40
40
 
41
41
  ## graphify
42
42
 
package/CHANGELOG.md CHANGED
@@ -9,6 +9,18 @@ All notable changes to this project are documented in this file.
9
9
  - **Harness lens:** Integrate selected pi-lens capabilities through a harness-owned extension, store lens state under `.pi/harness/.lens`, and route lens findings through harness PostHog telemetry instead of standalone lens health/telemetry surfaces.
10
10
  - **Graphify KB updater:** Productize conservative daily discovery/promotion with explicit repo/release taxonomy, allowlist source-class gates, operator review queue reporting, scheduler smoke validation, and safe Graphify refresh controls.
11
11
 
12
+ ## [v0.20.0] — 2026-05-26
13
+
14
+ ### ✨ Features
15
+
16
+ - **Harness:** Add hash-anchored executor edit flow.
17
+
18
+ ## [v0.19.1] — 2026-05-26
19
+
20
+ ### 🔧 Chores
21
+
22
+ - Prepare web retrieval and harness updates for release.
23
+
12
24
  ## [v0.19.0] — 2026-05-24
13
25
 
14
26
  ### ✨ Features
@@ -1,3 +1,10 @@
1
+ ## Dirac hash-anchored edit (vendored subset)
2
+
3
+ - **Project:** https://github.com/dirac-run/dirac
4
+ - **License:** Apache-2.0
5
+ - **Location:** [`.pi/lib/harness-anchored-edit/`](.pi/lib/harness-anchored-edit/) (anchor state, line protocol, edit resolve/apply)
6
+ - **Integration:** [`.pi/extensions/harness-anchored-edit.ts`](.pi/extensions/harness-anchored-edit.ts) — first-class harness `read`/`edit` (always on when harness extensions load). harness-lens autopatches anchored `edit.text` at `tool_call`; native apply via `applyAnchoredEditsToFile`.
7
+
1
8
  ## pi-vcc (vendored)
2
9
 
3
10
  - **Project:** https://github.com/sting8k/pi-vcc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-pi",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Ultimate AI coding harness for pi.dev — extensible skills, Obsidian wiki knowledge layer, compressed context, deterministic output",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -56,7 +56,10 @@
56
56
  ".pi/harness/agents.manifest.json",
57
57
  ".pi/harness/agents.policy.yaml",
58
58
  ".pi/harness/examples",
59
+ ".pi/harness/web-heuristic-angles.yaml",
60
+ ".pi/harness/web-heuristic-angles.json",
59
61
  ".pi/lib/harness-lens",
62
+ ".pi/lib/harness-anchored-edit",
60
63
  ".pi/harness/README.md",
61
64
  ".pi/npm/package.json",
62
65
  ".pi/npm/.gitignore",
@@ -76,16 +79,15 @@
76
79
  "@earendil-works/pi-coding-agent": "*"
77
80
  },
78
81
  "scripts": {
79
- "check:ts": "tsc --noEmit --target ES2023 --lib ES2023 --moduleResolution nodenext --module nodenext --skipLibCheck .pi/lib/agt/config.ts .pi/lib/agt/policy-engine.ts .pi/lib/agt/build-evaluation-context.ts .pi/lib/agt/evaluate-policy.ts .pi/lib/agt/legacy-evaluate.ts .pi/lib/agt/identity-registry.ts .pi/lib/agt/delegation.ts .pi/lib/agt/trust-run-store.ts .pi/lib/agt/audit-run-sink.ts .pi/lib/agt/rings.ts .pi/lib/agt/workflow-history.ts .pi/lib/agt/sre-hooks.ts .pi/lib/agt/kill-switch-state.ts .pi/lib/agt/index.ts .pi/extensions/agt-prompt-guard.ts .pi/extensions/agt-kill-switch.ts .pi/extensions/harness-subagent-governance.ts .pi/lib/harness-agt-tool-guard.ts .pi/lib/harness-subagent-submit-register.ts .pi/extensions/00-harness-project-control.ts .pi/extensions/custom-system-prompt.ts .pi/lib/harness-run-context.ts .pi/lib/harness-spawn-policy.ts .pi/lib/harness-context-mode-policy.ts .pi/lib/harness-ui-state.ts .pi/extensions/harness-run-context.ts .pi/lib/harness-vcc-settings.ts .pi/extensions/dotenv-loader.ts .pi/extensions/00-posthog-network-bootstrap.ts .pi/lib/posthog-client.ts .pi/lib/posthog-node.d.ts .pi/lib/harness-posthog.ts .pi/lib/harness-paths.ts .pi/extensions/provider-payload-sanitize.ts .pi/extensions/harness-telemetry.ts .pi/extensions/harness-ask-user.ts .pi/extensions/harness-plan-approval.ts .pi/lib/ask-user/schema.ts .pi/lib/ask-user/types.ts .pi/lib/ask-user/validate.ts .pi/lib/ask-user/dialog.ts .pi/lib/ask-user/fallback.ts .pi/lib/ask-user/render.ts .pi/lib/plan-approval/types.ts .pi/lib/plan-approval/schema.ts .pi/lib/plan-approval/validate.ts .pi/lib/plan-approval/format-plan.ts .pi/lib/plan-approval/dialog.ts .pi/lib/plan-approval/render.ts .pi/lib/plan-approval/create-plan.ts .pi/extensions/harness-subagents.ts .pi/lib/harness-subagents-bridge.ts .pi/lib/harness-cocoindex-refresh.ts .pi/lib/harness-subagent-auth.ts .pi/lib/agents-policy.ts .pi/lib/agt-governance-active.ts .pi/extensions/subagent-governance.ts .pi/lib/agt-tool-guard.ts .pi/lib/harness-subagent-precheck.ts .pi/lib/harness-spawn-budget.ts vendor/pi-subagents/src/agents.ts vendor/pi-subagents/src/subagents.ts .pi/extensions/review-integrity.ts .pi/extensions/trace-recorder.ts .pi/extensions/observation-bus.ts .pi/extensions/drift-monitor.ts .pi/extensions/policy-gate.ts .pi/extensions/budget-guard.ts .pi/extensions/debate-orchestrator.ts .pi/extensions/harness-debate-tools.ts .pi/lib/debate-bus-core.ts .pi/lib/debate-bus-state.ts .pi/lib/plan-debate-gate.ts .pi/lib/plan-debate-id.ts .pi/lib/plan-messenger.ts .pi/lib/plan-debate-envelope.ts .pi/lib/plan-review-integrator-rules.ts .pi/lib/plan-scope-guard.ts .pi/lib/plan-debate-write-guard.ts .pi/lib/plan-debate-lane.ts .pi/lib/plan-debate-round-status.ts .pi/extensions/harness-live-widget.ts .pi/extensions/sentrux-rules-sync.ts .pi/extensions/custom-header.ts .pi/extensions/harness-web-tools.ts .pi/extensions/harness-web-guard.ts .pi/lib/harness-web/run-cli.ts",
82
+ "check:ts": "tsc -p tsconfig.check.json",
80
83
  "vendor:sync-vcc": "bash .pi/scripts/vendor-sync-pi-vcc.sh",
81
84
  "vendor:sync-subagents": "bash .pi/scripts/vendor-sync-pi-subagents.sh",
82
- "release": "bash .pi/scripts/release.sh",
83
85
  "lint": "biome check",
84
86
  "lint:fix": "biome check --fix",
85
87
  "format": "biome format --write",
86
88
  "format:check": "biome format",
87
89
  "prepare": "lefthook install",
88
- "test": "node --test test/harness-verify.test.mjs test/harness-ask-user.test.mjs test/harness-subagents-loader.test.mjs test/harness-subagent-precheck.test.mjs test/sentrux-rules-sync.test.mjs test/harness-budget-guard.test.mjs && node .pi/harness/evals/smoke/smoke-harness-plan.mjs --fixture && npx -y tsx --test test/posthog-client.test.mjs test/harness-agt-policy-load.test.mjs test/harness-agt-policy-matrix.test.mjs test/harness-agt-policy-parity.test.mjs test/harness-agt-packaging.test.mjs test/harness-tool-call-hook-chain.test.mjs test/harness-vcc-settings.test.ts test/harness-run-context-postrun.test.mjs test/harness-tool-payload.test.mjs test/harness-live-widget-status.test.ts test/harness-project-toggle-tui.test.ts test/harness-plan-phase-policy.test.mjs test/harness-context-mode-policy.test.mjs test/harness-subprocess-bootstrap.test.mjs test/harness-subagent-policy.test.mjs test/harness-subagent-precheck-topology.test.mjs test/plan-approval-readiness.test.mjs test/harness-spawn-budget.test.mjs test/harness-spawn-parse.test.mjs test/harness-schema-validate.test.mjs test/harness-turn-routing.test.mjs test/harness-budget-enforce.test.mjs test/harness-submit-policy.test.mjs test/harness-project-agents-policy.test.mjs test/plan-approval-format.test.mjs test/plan-approval-dialog.test.mjs test/plan-approval-sync.test.mjs test/plan-create-plan.test.mjs test/plan-review-format.test.mjs test/debate-plan-phase.test.mjs test/plan-debate-eligibility.test.mjs test/plan-messenger-gate.test.mjs test/plan-debate-lane-apply.test.mjs test/review-integrity-revise-handoff.test.mjs test/harness-plan-revise-reset.test.mjs",
90
+ "test": "node .pi/scripts/run-tests.mjs",
89
91
  "test:vcc": "npx -y tsx --test vendor/pi-vcc/tests/*.test.ts",
90
92
  "harness:sentrux-bootstrap": "node .pi/scripts/harness-sentrux-bootstrap.mjs",
91
93
  "harness:sentrux-sync": "node .pi/scripts/sentrux-rules-sync.mjs --force",
@@ -109,6 +111,7 @@
109
111
  "ajv": "^8.17.1",
110
112
  "ajv-formats": "^3.0.1",
111
113
  "croner": "^9.0.0",
114
+ "diff": "^8.0.4",
112
115
  "jimp": "^1.6.1",
113
116
  "minimatch": "^10.2.5",
114
117
  "nanoid": "^5.1.5",
@@ -19,6 +19,11 @@ export interface AgentConfig {
19
19
  thinking?: string;
20
20
  maxTurns?: number;
21
21
  extensionsOff?: boolean;
22
+ /** Curated subprocess extensions (agents.policy.yaml `extension_bundle`). */
23
+ extensionBundle?: string;
24
+ extensionsFull?: boolean;
25
+ /** When true, subprocess uses --no-builtin-tools so extension read/edit replace builtins. */
26
+ noBuiltinTools?: boolean;
22
27
  skillsOff?: boolean;
23
28
  systemPrompt: string;
24
29
  source: AgentSource;
@@ -46,6 +46,10 @@ export interface HarnessSubagentsOptions {
46
46
  subprocessGovernanceExtensionPath?: string;
47
47
  /** @deprecated Use subprocessGovernanceExtensionPath */
48
48
  harnessSubprocessExtensionPath?: string;
49
+ /** Resolve curated `-e` extension paths for agents.policy `extension_bundle`. */
50
+ resolveExtensionBundlePaths?: (
51
+ bundleName: string,
52
+ ) => string[];
49
53
  /** Extra env vars per subprocess (e.g. HARNESS_RUN_ID, HARNESS_RUN_DIR). */
50
54
  resolveSubprocessEnv?: (
51
55
  task: string,
@@ -453,13 +457,28 @@ function buildAgentArgs(
453
457
  agent.extensionsOff &&
454
458
  (subagentsOptions?.subprocessGovernanceExtensionPath ??
455
459
  subagentsOptions?.harnessSubprocessExtensionPath);
456
- if (agent.extensionsOff) {
460
+ const bundlePaths =
461
+ agent.extensionBundle && subagentsOptions?.resolveExtensionBundlePaths
462
+ ? subagentsOptions.resolveExtensionBundlePaths(agent.extensionBundle)
463
+ : [];
464
+
465
+ if (agent.extensionBundle && bundlePaths.length > 0) {
466
+ args.push("--no-extensions");
467
+ for (const extPath of bundlePaths) {
468
+ args.push("-e", extPath);
469
+ }
470
+ if (agent.skillsOff) args.push("--no-skills");
471
+ } else if (agent.extensionsOff) {
457
472
  args.push("--no-extensions");
458
473
  if (governanceExt) args.push("-e", governanceExt);
459
474
  if (agent.skillsOff) args.push("--no-skills");
460
475
  }
461
- if (agent.tools?.length) args.push("--tools", agent.tools.join(","));
462
- else if (agent.extensionsOff) args.push("--no-tools");
476
+ if (agent.tools?.length) {
477
+ args.push("--tools", agent.tools.join(","));
478
+ if (agent.noBuiltinTools) args.push("--no-builtin-tools");
479
+ } else if (agent.extensionsOff || agent.extensionBundle) {
480
+ args.push("--no-tools");
481
+ }
463
482
  return args;
464
483
  }
465
484