ultimate-pi 0.3.1 → 0.4.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.
- package/.agents/skills/harness-decisions/SKILL.md +37 -0
- package/.agents/skills/harness-governor/SKILL.md +1 -1
- package/.agents/skills/harness-orchestration/SKILL.md +54 -0
- package/.agents/skills/harness-plan/SKILL.md +4 -3
- package/.agents/skills/harness-sentrux-setup/SKILL.md +57 -0
- package/.agents/skills/scrapling-web/SKILL.md +93 -0
- package/.pi/PACKAGING.md +2 -2
- package/.pi/SYSTEM.md +13 -15
- package/.pi/agents/harness/adversary.md +3 -0
- package/.pi/agents/harness/evaluator.md +3 -0
- package/.pi/agents/harness/executor.md +4 -1
- package/.pi/agents/harness/meta-optimizer.md +2 -1
- package/.pi/agents/harness/planner.md +22 -1
- package/.pi/agents/harness/sentrux-bootstrap.md +42 -0
- package/.pi/agents/harness/tie-breaker.md +2 -0
- package/.pi/extensions/harness-ask-user.ts +74 -0
- package/.pi/extensions/harness-subagents.ts +9 -0
- package/.pi/extensions/lib/ask-user/dialog.ts +260 -0
- package/.pi/extensions/lib/ask-user/fallback.ts +78 -0
- package/.pi/extensions/lib/ask-user/render.ts +66 -0
- package/.pi/extensions/lib/ask-user/schema.ts +69 -0
- package/.pi/extensions/lib/ask-user/types.ts +41 -0
- package/.pi/extensions/lib/ask-user/validate-core.mjs +79 -0
- package/.pi/extensions/lib/ask-user/validate.ts +92 -0
- package/.pi/extensions/lib/harness-subagents/agent-loader.ts +126 -0
- package/.pi/extensions/lib/harness-subagents/agent-manifest.ts +119 -0
- package/.pi/extensions/lib/harness-subagents/agent-parser.ts +87 -0
- package/.pi/extensions/lib/harness-subagents/blackboard-tool.ts +118 -0
- package/.pi/extensions/lib/harness-subagents/blackboard.ts +175 -0
- package/.pi/extensions/lib/harness-subagents/spawn-policy.ts +27 -0
- package/.pi/extensions/lib/harness-subagents/types-blackboard.ts +27 -0
- package/.pi/extensions/lib/harness-subagents/vendored/agent-manager.ts +553 -0
- package/.pi/extensions/lib/harness-subagents/vendored/agent-runner.ts +637 -0
- package/.pi/extensions/lib/harness-subagents/vendored/agent-types.ts +175 -0
- package/.pi/extensions/lib/harness-subagents/vendored/context.ts +59 -0
- package/.pi/extensions/lib/harness-subagents/vendored/cross-extension-rpc.ts +134 -0
- package/.pi/extensions/lib/harness-subagents/vendored/custom-agents.ts +5 -0
- package/.pi/extensions/lib/harness-subagents/vendored/default-agents.ts +123 -0
- package/.pi/extensions/lib/harness-subagents/vendored/env.ts +43 -0
- package/.pi/extensions/lib/harness-subagents/vendored/group-join.ts +144 -0
- package/.pi/extensions/lib/harness-subagents/vendored/index.ts +2447 -0
- package/.pi/extensions/lib/harness-subagents/vendored/invocation-config.ts +52 -0
- package/.pi/extensions/lib/harness-subagents/vendored/memory.ts +182 -0
- package/.pi/extensions/lib/harness-subagents/vendored/model-resolver.ts +92 -0
- package/.pi/extensions/lib/harness-subagents/vendored/output-file.ts +115 -0
- package/.pi/extensions/lib/harness-subagents/vendored/prompts.ts +103 -0
- package/.pi/extensions/lib/harness-subagents/vendored/schedule-store.ts +177 -0
- package/.pi/extensions/lib/harness-subagents/vendored/schedule.ts +416 -0
- package/.pi/extensions/lib/harness-subagents/vendored/settings.ts +210 -0
- package/.pi/extensions/lib/harness-subagents/vendored/skill-loader.ts +108 -0
- package/.pi/extensions/lib/harness-subagents/vendored/types.ts +187 -0
- package/.pi/extensions/lib/harness-subagents/vendored/ui/agent-widget.ts +637 -0
- package/.pi/extensions/lib/harness-subagents/vendored/ui/conversation-viewer.ts +324 -0
- package/.pi/extensions/lib/harness-subagents/vendored/ui/schedule-menu.ts +110 -0
- package/.pi/extensions/lib/harness-subagents/vendored/usage.ts +71 -0
- package/.pi/extensions/lib/harness-subagents/vendored/worktree.ts +195 -0
- package/.pi/harness/README.md +2 -1
- package/.pi/harness/agents.manifest.json +80 -0
- package/.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md +9 -5
- package/.pi/harness/env.harness.template +28 -0
- package/.pi/harness/sentrux/architecture.manifest.json +6 -1
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-plan.md +2 -2
- package/.pi/prompts/harness-router-tune.md +2 -2
- package/.pi/prompts/harness-run.md +1 -0
- package/.pi/prompts/harness-setup.md +178 -339
- package/.pi/scripts/README.md +6 -1
- package/.pi/scripts/harness-agents-manifest.mjs +123 -0
- package/.pi/scripts/harness-cli-verify.sh +60 -11
- package/.pi/scripts/harness-generate-model-router.mjs +242 -0
- package/.pi/scripts/harness-graphify-bootstrap.sh +1 -6
- package/.pi/scripts/harness-resolve-up-pkg.mjs +71 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +33 -1
- package/.pi/scripts/harness-sentrux-bootstrap.mjs +146 -0
- package/.pi/scripts/harness-sync-env.mjs +148 -0
- package/.pi/scripts/harness-verify.mjs +19 -0
- package/.pi/scripts/harness-web-search.md +33 -0
- package/.pi/scripts/harness-web.py +177 -0
- package/.pi/scripts/harness_web/__init__.py +1 -0
- package/.pi/scripts/harness_web/config.py +80 -0
- package/.pi/scripts/harness_web/output.py +55 -0
- package/.pi/scripts/harness_web/scrape.py +120 -0
- package/.pi/scripts/harness_web/search_ddg.py +106 -0
- package/.pi/scripts/release.sh +338 -0
- package/.pi/scripts/sentrux-rules-sync.mjs +29 -7
- package/.pi/settings.example.json +0 -1
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +12 -0
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/package.json +12 -9
- package/.agents/skills/firecrawl/SKILL.md +0 -150
- package/.agents/skills/firecrawl/rules/install.md +0 -82
- package/.agents/skills/firecrawl/rules/security.md +0 -26
- package/.agents/skills/firecrawl-agent/SKILL.md +0 -57
- package/.agents/skills/firecrawl-build-interact/SKILL.md +0 -67
- package/.agents/skills/firecrawl-build-onboarding/SKILL.md +0 -102
- package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +0 -39
- package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +0 -20
- package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +0 -17
- package/.agents/skills/firecrawl-build-scrape/SKILL.md +0 -68
- package/.agents/skills/firecrawl-build-search/SKILL.md +0 -68
- package/.agents/skills/firecrawl-crawl/SKILL.md +0 -58
- package/.agents/skills/firecrawl-download/SKILL.md +0 -69
- package/.agents/skills/firecrawl-interact/SKILL.md +0 -83
- package/.agents/skills/firecrawl-map/SKILL.md +0 -50
- package/.agents/skills/firecrawl-parse/SKILL.md +0 -61
- package/.agents/skills/firecrawl-scrape/SKILL.md +0 -68
- package/.agents/skills/firecrawl-search/SKILL.md +0 -59
- package/firecrawl/.env.template +0 -62
- package/firecrawl/README.md +0 -49
- package/firecrawl/docker-compose.yaml +0 -201
- package/firecrawl/searxng/searxng.env +0 -3
- package/firecrawl/searxng/settings.yml +0 -85
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Scrape and map URLs via Scrapling fetchers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from urllib.parse import urljoin, urlparse
|
|
7
|
+
|
|
8
|
+
from scrapling.fetchers import Fetcher, StealthyFetcher, StealthySession
|
|
9
|
+
|
|
10
|
+
from .config import HarnessWebConfig
|
|
11
|
+
from .output import write_page_markdown
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _fetch_kwargs(config: HarnessWebConfig, *, wait_ms: int | None = None) -> dict:
|
|
15
|
+
kw: dict = {"timeout": config.timeout_ms}
|
|
16
|
+
if config.proxy:
|
|
17
|
+
kw["proxy"] = config.proxy
|
|
18
|
+
if wait_ms is not None:
|
|
19
|
+
kw["wait"] = wait_ms
|
|
20
|
+
return kw
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def fetch_page(url: str, *, config: HarnessWebConfig, fast: bool, wait_ms: int | None):
|
|
24
|
+
kw = _fetch_kwargs(config, wait_ms=wait_ms)
|
|
25
|
+
if fast:
|
|
26
|
+
return Fetcher.get(url, timeout=config.timeout_sec, proxy=config.proxy)
|
|
27
|
+
return StealthyFetcher.fetch(url, **kw)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def scrape_url(
|
|
31
|
+
url: str,
|
|
32
|
+
output: str,
|
|
33
|
+
*,
|
|
34
|
+
config: HarnessWebConfig,
|
|
35
|
+
fast: bool,
|
|
36
|
+
wait_ms: int | None,
|
|
37
|
+
) -> None:
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
page = fetch_page(url, config=config, fast=fast, wait_ms=wait_ms)
|
|
41
|
+
write_page_markdown(Path(output), page, main_content_only=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def map_url(
|
|
45
|
+
url: str,
|
|
46
|
+
output: str,
|
|
47
|
+
*,
|
|
48
|
+
config: HarnessWebConfig,
|
|
49
|
+
fast: bool,
|
|
50
|
+
limit: int,
|
|
51
|
+
) -> None:
|
|
52
|
+
import json
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
|
|
55
|
+
page = fetch_page(url, config=config, fast=fast, wait_ms=None)
|
|
56
|
+
base = urlparse(url)
|
|
57
|
+
host = (base.hostname or "").lower()
|
|
58
|
+
links: list[dict[str, str]] = []
|
|
59
|
+
seen: set[str] = set()
|
|
60
|
+
|
|
61
|
+
for el in page.css("a[href]"):
|
|
62
|
+
if len(links) >= limit:
|
|
63
|
+
break
|
|
64
|
+
href = (el.attrib.get("href") or "").strip()
|
|
65
|
+
if not href or href.startswith("#") or href.lower().startswith("javascript:"):
|
|
66
|
+
continue
|
|
67
|
+
absolute = urljoin(url, href)
|
|
68
|
+
parsed = urlparse(absolute)
|
|
69
|
+
if (parsed.hostname or "").lower() != host:
|
|
70
|
+
continue
|
|
71
|
+
if absolute in seen:
|
|
72
|
+
continue
|
|
73
|
+
seen.add(absolute)
|
|
74
|
+
title = (el.get_all_text(strip=True) or "").strip()
|
|
75
|
+
links.append({"url": absolute, "title": title})
|
|
76
|
+
|
|
77
|
+
out = Path(output)
|
|
78
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
out.write_text(json.dumps({"url": url, "links": links}, indent=2) + "\n", encoding="utf-8")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def bulk_scrape(
|
|
83
|
+
urls: list[str],
|
|
84
|
+
out_dir: str,
|
|
85
|
+
*,
|
|
86
|
+
config: HarnessWebConfig,
|
|
87
|
+
fast: bool,
|
|
88
|
+
sleep_sec: float,
|
|
89
|
+
) -> list[str]:
|
|
90
|
+
from pathlib import Path
|
|
91
|
+
|
|
92
|
+
root = Path(out_dir)
|
|
93
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
failures: list[str] = []
|
|
95
|
+
|
|
96
|
+
if fast:
|
|
97
|
+
for i, url in enumerate(urls):
|
|
98
|
+
if i and sleep_sec > 0:
|
|
99
|
+
time.sleep(sleep_sec)
|
|
100
|
+
safe = urlparse(url).netloc.replace(".", "_") + ".md"
|
|
101
|
+
dest = root / safe
|
|
102
|
+
try:
|
|
103
|
+
scrape_url(url, str(dest), config=config, fast=True, wait_ms=None)
|
|
104
|
+
except Exception as err: # noqa: BLE001
|
|
105
|
+
failures.append(f"{url}: {err}")
|
|
106
|
+
return failures
|
|
107
|
+
|
|
108
|
+
kw = _fetch_kwargs(config)
|
|
109
|
+
with StealthySession(headless=True, **kw) as session:
|
|
110
|
+
for i, url in enumerate(urls):
|
|
111
|
+
if i and sleep_sec > 0:
|
|
112
|
+
time.sleep(sleep_sec)
|
|
113
|
+
safe = urlparse(url).netloc.replace(".", "_") + ".md"
|
|
114
|
+
dest = root / safe
|
|
115
|
+
try:
|
|
116
|
+
page = session.fetch(url)
|
|
117
|
+
write_page_markdown(dest, page, main_content_only=True)
|
|
118
|
+
except Exception as err: # noqa: BLE001
|
|
119
|
+
failures.append(f"{url}: {err}")
|
|
120
|
+
return failures
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""DuckDuckGo HTML SERP search via HTTP Fetcher."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import parse_qs, unquote, urlparse
|
|
6
|
+
|
|
7
|
+
from scrapling.fetchers import Fetcher, StealthyFetcher
|
|
8
|
+
|
|
9
|
+
from .config import HarnessWebConfig
|
|
10
|
+
|
|
11
|
+
DDG_HTML_URL = "https://html.duckduckgo.com/html/"
|
|
12
|
+
CHALLENGE_MARKERS = (
|
|
13
|
+
"anomaly-modal",
|
|
14
|
+
"bots use duckduckgo",
|
|
15
|
+
"please complete the following challenge",
|
|
16
|
+
"duckduckgo.com/y.js",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _unwrap_ddg_href(href: str) -> str:
|
|
21
|
+
if not href:
|
|
22
|
+
return ""
|
|
23
|
+
if href.startswith("//"):
|
|
24
|
+
href = "https:" + href
|
|
25
|
+
parsed = urlparse(href)
|
|
26
|
+
if "duckduckgo.com" in (parsed.hostname or "") and parsed.path.startswith("/l/"):
|
|
27
|
+
qs = parse_qs(parsed.query)
|
|
28
|
+
if "uddg" in qs and qs["uddg"]:
|
|
29
|
+
return unquote(qs["uddg"][0])
|
|
30
|
+
return href
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _looks_like_challenge(html: str) -> bool:
|
|
34
|
+
lower = html.lower()
|
|
35
|
+
return any(m in lower for m in CHALLENGE_MARKERS)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_serp(page: Any, limit: int) -> list[dict[str, str]]:
|
|
39
|
+
results: list[dict[str, str]] = []
|
|
40
|
+
for block in page.css(".result"):
|
|
41
|
+
if len(results) >= limit:
|
|
42
|
+
break
|
|
43
|
+
links = block.css(".result__a")
|
|
44
|
+
if not links:
|
|
45
|
+
continue
|
|
46
|
+
link = links[0]
|
|
47
|
+
href = _unwrap_ddg_href(link.attrib.get("href", ""))
|
|
48
|
+
if not href.startswith("http"):
|
|
49
|
+
continue
|
|
50
|
+
title = (link.text or "").strip()
|
|
51
|
+
snippet_el = block.css(".result__snippet")
|
|
52
|
+
description = ""
|
|
53
|
+
if snippet_el:
|
|
54
|
+
description = (snippet_el[0].get_all_text(strip=True) or "").strip()
|
|
55
|
+
results.append({"url": href, "title": title, "description": description})
|
|
56
|
+
return results
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def search_ddg(
|
|
60
|
+
query: str,
|
|
61
|
+
*,
|
|
62
|
+
limit: int,
|
|
63
|
+
config: HarnessWebConfig,
|
|
64
|
+
impersonate: bool = True,
|
|
65
|
+
) -> list[dict[str, str]]:
|
|
66
|
+
if config.search_engine != "ddg_html":
|
|
67
|
+
raise SystemExit(
|
|
68
|
+
f"Unsupported HARNESS_WEB_SEARCH_ENGINE={config.search_engine!r} (only ddg_html)"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
kwargs: dict = {
|
|
72
|
+
"params": {"q": query},
|
|
73
|
+
"timeout": config.timeout_sec,
|
|
74
|
+
}
|
|
75
|
+
if config.proxy:
|
|
76
|
+
kwargs["proxy"] = config.proxy
|
|
77
|
+
if impersonate:
|
|
78
|
+
kwargs["impersonate"] = "chrome"
|
|
79
|
+
|
|
80
|
+
page = Fetcher.get(DDG_HTML_URL, **kwargs)
|
|
81
|
+
status = getattr(page, "status", 200)
|
|
82
|
+
html = getattr(page, "html_content", "") or ""
|
|
83
|
+
|
|
84
|
+
if status == 403 or _looks_like_challenge(html):
|
|
85
|
+
page = StealthyFetcher.fetch(
|
|
86
|
+
DDG_HTML_URL,
|
|
87
|
+
params={"q": query},
|
|
88
|
+
timeout=config.timeout_ms,
|
|
89
|
+
proxy=config.proxy,
|
|
90
|
+
headless=True,
|
|
91
|
+
)
|
|
92
|
+
html = getattr(page, "html_content", "") or ""
|
|
93
|
+
if _looks_like_challenge(html):
|
|
94
|
+
raise SystemExit(
|
|
95
|
+
"Search engine blocked (403/challenge page). "
|
|
96
|
+
"Try later, set HARNESS_WEB_PROXY, or reduce query rate."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
parsed = _parse_serp(page, limit)
|
|
100
|
+
if not parsed and html:
|
|
101
|
+
# Empty selector set — likely HTML shape change
|
|
102
|
+
if "result__a" not in html:
|
|
103
|
+
raise SystemExit(
|
|
104
|
+
"Could not parse DuckDuckGo HTML results (page layout may have changed)."
|
|
105
|
+
)
|
|
106
|
+
return parsed
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# release.sh — Version bump, changelog, tag, and push
|
|
4
|
+
# Usage: ./.pi/scripts/release.sh [patch|minor|major] [--dry-run]
|
|
5
|
+
#
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
9
|
+
warn() { echo "⚠ $*" >&2; }
|
|
10
|
+
abort() { echo "✗ $*" >&2; exit 1; }
|
|
11
|
+
ok() { echo "✓ $*"; }
|
|
12
|
+
|
|
13
|
+
# ─── Step 0 — Parse arguments ─────────────────────────────────────────────────
|
|
14
|
+
BUMP_TYPE=""
|
|
15
|
+
DRY_RUN=false
|
|
16
|
+
|
|
17
|
+
for arg in "$@"; do
|
|
18
|
+
case "$arg" in
|
|
19
|
+
patch|minor|major) BUMP_TYPE="$arg" ;;
|
|
20
|
+
--dry-run) DRY_RUN=true ;;
|
|
21
|
+
*) abort "Unknown argument: $arg" ;;
|
|
22
|
+
esac
|
|
23
|
+
done
|
|
24
|
+
|
|
25
|
+
# ─── Step 1 — Infer bump type from commits if not provided ────────────────────
|
|
26
|
+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
27
|
+
|
|
28
|
+
if [ -z "$BUMP_TYPE" ]; then
|
|
29
|
+
ok "No bump type provided. Scanning commits since last tag…"
|
|
30
|
+
|
|
31
|
+
if [ -z "$LAST_TAG" ]; then
|
|
32
|
+
COMMIT_LOG=$(git log --format="%s" HEAD 2>/dev/null || true)
|
|
33
|
+
else
|
|
34
|
+
COMMIT_LOG=$(git log --format="%s" "${LAST_TAG}..HEAD" 2>/dev/null || true)
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [ -z "$COMMIT_LOG" ]; then
|
|
38
|
+
abort "No commits since last tag. Nothing to release."
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Inference rules
|
|
42
|
+
if echo "$COMMIT_LOG" | grep -qE '^feat!:|BREAKING CHANGE'; then
|
|
43
|
+
BUMP_TYPE="major"
|
|
44
|
+
elif echo "$COMMIT_LOG" | grep -qE '^feat:'; then
|
|
45
|
+
BUMP_TYPE="minor"
|
|
46
|
+
else
|
|
47
|
+
BUMP_TYPE="patch"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
ok "Inferred bump type: $BUMP_TYPE"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# ─── Step 2 — Read current version and validate semver ────────────────────────
|
|
54
|
+
CURRENT_VERSION=$(node -e "console.log(require('./package.json').version)" 2>/dev/null) \
|
|
55
|
+
|| abort "Failed to read version from package.json"
|
|
56
|
+
|
|
57
|
+
if ! [[ "$CURRENT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
58
|
+
abort "Invalid semver in package.json: $CURRENT_VERSION"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
NEW_VERSION=$(node -e "
|
|
62
|
+
const [maj, min, pat] = '$CURRENT_VERSION'.split('.').map(Number);
|
|
63
|
+
const bump = '$BUMP_TYPE';
|
|
64
|
+
if (bump === 'major') console.log((maj + 1) + '.0.0');
|
|
65
|
+
else if (bump === 'minor') console.log(maj + '.' + (min + 1) + '.0');
|
|
66
|
+
else console.log(maj + '.' + min + '.' + (pat + 1));
|
|
67
|
+
")
|
|
68
|
+
|
|
69
|
+
ok "Version: $CURRENT_VERSION → $NEW_VERSION"
|
|
70
|
+
|
|
71
|
+
# ─── Step 3 — Pre-flight checks ───────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
# Must be in a git repo
|
|
74
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
|
75
|
+
|| abort "Not a git repository."
|
|
76
|
+
|
|
77
|
+
# Must have origin remote
|
|
78
|
+
git remote -v | grep -q origin \
|
|
79
|
+
|| abort "No 'origin' remote configured."
|
|
80
|
+
|
|
81
|
+
# Must be on a branch (not detached HEAD)
|
|
82
|
+
BRANCH=$(git symbolic-ref -q HEAD 2>/dev/null | sed 's|^refs/heads/||') \
|
|
83
|
+
|| abort "Detached HEAD. Switch to a branch first."
|
|
84
|
+
|
|
85
|
+
# Must have clean working tree (warn only in dry-run)
|
|
86
|
+
git diff --quiet && git diff --cached --quiet
|
|
87
|
+
if [ $? -ne 0 ]; then
|
|
88
|
+
if [ "$DRY_RUN" = true ]; then
|
|
89
|
+
warn "Working tree is dirty — actual release would be blocked."
|
|
90
|
+
else
|
|
91
|
+
abort "Working tree is dirty. Commit or stash changes first."
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# No duplicate tag locally or on remote
|
|
96
|
+
if git rev-parse "v$NEW_VERSION" >/dev/null 2>&1; then
|
|
97
|
+
abort "Tag v$NEW_VERSION already exists locally."
|
|
98
|
+
fi
|
|
99
|
+
if git ls-remote --tags origin "refs/tags/v$NEW_VERSION" >/dev/null 2>&1; then
|
|
100
|
+
abort "Tag v$NEW_VERSION already exists on remote."
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# ─── Step 4 — Gather commits since last tag ───────────────────────────────────
|
|
104
|
+
if [ -z "$LAST_TAG" ]; then
|
|
105
|
+
COMMITS=$(git log --oneline --no-merges HEAD)
|
|
106
|
+
else
|
|
107
|
+
COMMITS=$(git log --oneline --no-merges "${LAST_TAG}..HEAD")
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
COMMIT_COUNT=$(echo "$COMMITS" | grep -c '^' || echo 0)
|
|
111
|
+
|
|
112
|
+
if [ "$COMMIT_COUNT" -eq 0 ]; then
|
|
113
|
+
abort "No commits since last tag. Nothing to release."
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# ─── Step 5 — Generate changelog entry ────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
# Map conventional commit prefix → section
|
|
119
|
+
map_prefix() {
|
|
120
|
+
local msg="$1"
|
|
121
|
+
case "$msg" in
|
|
122
|
+
feat!:*|*"BREAKING CHANGE"*) echo "breaking" ;;
|
|
123
|
+
feat:*) echo "features" ;;
|
|
124
|
+
fix:*) echo "fixes" ;;
|
|
125
|
+
perf:*) echo "perf" ;;
|
|
126
|
+
refactor:*) echo "refactor" ;;
|
|
127
|
+
docs:*) echo "docs" ;;
|
|
128
|
+
style:*) echo "style" ;;
|
|
129
|
+
test:*) echo "tests" ;;
|
|
130
|
+
chore:*) echo "chores" ;;
|
|
131
|
+
ci:*) echo "ci" ;;
|
|
132
|
+
build:*) echo "build" ;;
|
|
133
|
+
*) echo "chores" ;;
|
|
134
|
+
esac
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare -A SECTIONS=(
|
|
138
|
+
[breaking]="⚠️ Breaking Changes"
|
|
139
|
+
[features]="✨ Features"
|
|
140
|
+
[fixes]="🐛 Fixes"
|
|
141
|
+
[perf]="⚡ Performance"
|
|
142
|
+
[refactor]="♻️ Refactoring"
|
|
143
|
+
[docs]="📖 Documentation"
|
|
144
|
+
[style]="🎨 Style"
|
|
145
|
+
[tests]="✅ Tests"
|
|
146
|
+
[chores]="🔧 Chores"
|
|
147
|
+
[ci]="🔄 CI/CD"
|
|
148
|
+
[build]="📦 Build"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Build per-section entries
|
|
152
|
+
entries_breaking=""
|
|
153
|
+
entries_features=""
|
|
154
|
+
entries_fixes=""
|
|
155
|
+
entries_perf=""
|
|
156
|
+
entries_refactor=""
|
|
157
|
+
entries_docs=""
|
|
158
|
+
entries_style=""
|
|
159
|
+
entries_tests=""
|
|
160
|
+
entries_chores=""
|
|
161
|
+
entries_ci=""
|
|
162
|
+
entries_build=""
|
|
163
|
+
|
|
164
|
+
while IFS= read -r line; do
|
|
165
|
+
[ -z "$line" ] && continue
|
|
166
|
+
# Strip the short sha prefix (first word)
|
|
167
|
+
msg="${line#* }"
|
|
168
|
+
# Strip conventional commit prefix for display
|
|
169
|
+
display="$msg"
|
|
170
|
+
display=$(echo "$display" | sed -E 's/^[a-z]+(\([a-z0-9_-]+\))?!?:\s*//')
|
|
171
|
+
prefix=$(map_prefix "$msg")
|
|
172
|
+
case "$prefix" in
|
|
173
|
+
breaking) entries_breaking="${entries_breaking}- $display
|
|
174
|
+
" ;;
|
|
175
|
+
features) entries_features="${entries_features}- $display
|
|
176
|
+
" ;;
|
|
177
|
+
fixes) entries_fixes="${entries_fixes}- $display
|
|
178
|
+
" ;;
|
|
179
|
+
perf) entries_perf="${entries_perf}- $display
|
|
180
|
+
" ;;
|
|
181
|
+
refactor) entries_refactor="${entries_refactor}- $display
|
|
182
|
+
" ;;
|
|
183
|
+
docs) entries_docs="${entries_docs}- $display
|
|
184
|
+
" ;;
|
|
185
|
+
style) entries_style="${entries_style}- $display
|
|
186
|
+
" ;;
|
|
187
|
+
tests) entries_tests="${entries_tests}- $display
|
|
188
|
+
" ;;
|
|
189
|
+
ci) entries_ci="${entries_ci}- $display
|
|
190
|
+
" ;;
|
|
191
|
+
build) entries_build="${entries_build}- $display
|
|
192
|
+
" ;;
|
|
193
|
+
*) entries_chores="${entries_chores}- $display
|
|
194
|
+
" ;;
|
|
195
|
+
esac
|
|
196
|
+
done <<< "$COMMITS"
|
|
197
|
+
|
|
198
|
+
# Assemble the changelog block
|
|
199
|
+
TODAY=$(date +%Y-%m-%d)
|
|
200
|
+
CHANGELOG_BLOCK="## [v$NEW_VERSION] — $TODAY
|
|
201
|
+
"
|
|
202
|
+
|
|
203
|
+
for key in breaking features fixes perf refactor docs style tests ci build chores; do
|
|
204
|
+
eval "content=\"\$entries_$key\""
|
|
205
|
+
if [ -n "$content" ]; then
|
|
206
|
+
CHANGELOG_BLOCK="${CHANGELOG_BLOCK}
|
|
207
|
+
### ${SECTIONS[$key]}
|
|
208
|
+
|
|
209
|
+
$content"
|
|
210
|
+
fi
|
|
211
|
+
done
|
|
212
|
+
|
|
213
|
+
# ─── Step 6 — Dry run check ───────────────────────────────────────────────────
|
|
214
|
+
if [ "$DRY_RUN" = true ]; then
|
|
215
|
+
echo ""
|
|
216
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
217
|
+
echo " DRY RUN — no changes made"
|
|
218
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
219
|
+
echo " Version: $CURRENT_VERSION → $NEW_VERSION"
|
|
220
|
+
echo " Bump: $BUMP_TYPE"
|
|
221
|
+
echo " Commits: $COMMIT_COUNT since ${LAST_TAG:-<none>}"
|
|
222
|
+
echo " Branch: $BRANCH"
|
|
223
|
+
echo ""
|
|
224
|
+
echo " Files that would change:"
|
|
225
|
+
echo " - package.json (version → $NEW_VERSION)"
|
|
226
|
+
echo " - CHANGELOG.md (new entry below)"
|
|
227
|
+
echo ""
|
|
228
|
+
echo " Tag that would be created: v$NEW_VERSION"
|
|
229
|
+
echo ""
|
|
230
|
+
echo " Changelog entry:"
|
|
231
|
+
echo "───────────────────────────────────────────────────────────────"
|
|
232
|
+
echo "$CHANGELOG_BLOCK"
|
|
233
|
+
echo "───────────────────────────────────────────────────────────────"
|
|
234
|
+
exit 0
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# ─── Step 7 — Bump version in package.json ────────────────────────────────────
|
|
238
|
+
npm pkg set version="$NEW_VERSION"
|
|
239
|
+
|
|
240
|
+
node -e "
|
|
241
|
+
const v = require('./package.json').version;
|
|
242
|
+
if (v !== '$NEW_VERSION') {
|
|
243
|
+
console.error('✗ version mismatch: expected $NEW_VERSION, got ' + v);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
console.log('✓ version bumped to $NEW_VERSION');
|
|
247
|
+
"
|
|
248
|
+
|
|
249
|
+
# ─── Step 8 — Write CHANGELOG.md ──────────────────────────────────────────────
|
|
250
|
+
if [ -f CHANGELOG.md ]; then
|
|
251
|
+
# Prepend after the first heading line
|
|
252
|
+
{
|
|
253
|
+
head -n 1 CHANGELOG.md
|
|
254
|
+
echo ""
|
|
255
|
+
echo "$CHANGELOG_BLOCK"
|
|
256
|
+
tail -n +2 CHANGELOG.md
|
|
257
|
+
} > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
|
|
258
|
+
else
|
|
259
|
+
{
|
|
260
|
+
echo "# Changelog"
|
|
261
|
+
echo ""
|
|
262
|
+
echo "All notable changes to this project are documented in this file."
|
|
263
|
+
echo ""
|
|
264
|
+
echo "$CHANGELOG_BLOCK"
|
|
265
|
+
} > CHANGELOG.md
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
ok "CHANGELOG.md updated"
|
|
269
|
+
|
|
270
|
+
# ─── Step 9 — Read co-author config ───────────────────────────────────────────
|
|
271
|
+
CO_AUTHOR="pi-mono <261679550+pi-mono@users.noreply.github.com>"
|
|
272
|
+
if [ -f .pi/auto-commit.json ]; then
|
|
273
|
+
CO_AUTHOR=$(node -e "
|
|
274
|
+
const fs = require('fs');
|
|
275
|
+
const cfg = JSON.parse(fs.readFileSync('.pi/auto-commit.json', 'utf8'));
|
|
276
|
+
const ca = cfg.coAuthor || {};
|
|
277
|
+
console.log((ca.login || 'pi-mono') + ' <' + (ca.email || '261679550+pi-mono@users.noreply.github.com') + '>');
|
|
278
|
+
" 2>/dev/null) || true
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
# ─── Step 10 — Commit ─────────────────────────────────────────────────────────
|
|
282
|
+
git add package.json CHANGELOG.md
|
|
283
|
+
|
|
284
|
+
COMMIT_BODY=$(cat <<EOF
|
|
285
|
+
- Bump version in package.json
|
|
286
|
+
- Add changelog entry for v$NEW_VERSION
|
|
287
|
+
|
|
288
|
+
Commits included:
|
|
289
|
+
$(echo "$COMMITS" | sed 's/^/- /')
|
|
290
|
+
EOF
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
git commit -m "chore(release): bump to v$NEW_VERSION" \
|
|
294
|
+
-m "$COMMIT_BODY" \
|
|
295
|
+
-m "Co-authored-by: $CO_AUTHOR"
|
|
296
|
+
|
|
297
|
+
ok "Committed version bump + changelog"
|
|
298
|
+
|
|
299
|
+
# ─── Step 11 — Create and push tag ────────────────────────────────────────────
|
|
300
|
+
TAG_BODY=$(cat <<EOF
|
|
301
|
+
Release v$NEW_VERSION — $BUMP_TYPE bump
|
|
302
|
+
|
|
303
|
+
$COMMITS
|
|
304
|
+
EOF
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
git tag -a "v$NEW_VERSION" -m "$TAG_BODY"
|
|
308
|
+
ok "Created tag v$NEW_VERSION"
|
|
309
|
+
|
|
310
|
+
git push origin "v$NEW_VERSION"
|
|
311
|
+
ok "Pushed tag v$NEW_VERSION to origin"
|
|
312
|
+
|
|
313
|
+
# ─── Step 12 — Optionally push branch commit ──────────────────────────────────
|
|
314
|
+
echo ""
|
|
315
|
+
read -rp "Push the version-bump commit to the current branch ($BRANCH) too? [Y/n] " PUSH_BRANCH
|
|
316
|
+
if [[ "$PUSH_BRANCH" =~ ^[Yy]?$ ]]; then
|
|
317
|
+
git push origin "$BRANCH"
|
|
318
|
+
ok "Pushed commit to $BRANCH"
|
|
319
|
+
else
|
|
320
|
+
echo "Skipped branch push."
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# ─── Step 13 — Report ─────────────────────────────────────────────────────────
|
|
324
|
+
echo ""
|
|
325
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
326
|
+
echo " ✓ Released v$NEW_VERSION ($BUMP_TYPE)"
|
|
327
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
328
|
+
echo " Tag: v$NEW_VERSION — pushed to origin"
|
|
329
|
+
echo " Commit: $(git rev-parse --short HEAD)"
|
|
330
|
+
echo " Branch: $BRANCH"
|
|
331
|
+
echo ""
|
|
332
|
+
echo " Workflows triggered:"
|
|
333
|
+
echo " - .github/workflows/publish-github-packages.yml"
|
|
334
|
+
echo " - .github/workflows/publish-npm.yml"
|
|
335
|
+
echo ""
|
|
336
|
+
echo " Changelog: CHANGELOG.md updated"
|
|
337
|
+
echo " Monitor: https://github.com/aryaniyaps/ultimate-pi/actions"
|
|
338
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
@@ -13,16 +13,26 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
import { createHash } from "node:crypto";
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const UP_PKG = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17
|
+
/** Target project root (consumer repo). Default: process.cwd(). */
|
|
18
|
+
const PROJECT_ROOT =
|
|
19
|
+
process.argv.find((a, i) => i >= 2 && !a.startsWith("-")) || process.cwd();
|
|
17
20
|
const MANIFEST = join(
|
|
18
|
-
|
|
21
|
+
PROJECT_ROOT,
|
|
19
22
|
".pi",
|
|
20
23
|
"harness",
|
|
21
24
|
"sentrux",
|
|
22
25
|
"architecture.manifest.json",
|
|
23
26
|
);
|
|
24
|
-
const
|
|
25
|
-
|
|
27
|
+
const MANIFEST_TEMPLATE = join(
|
|
28
|
+
UP_PKG,
|
|
29
|
+
".pi",
|
|
30
|
+
"harness",
|
|
31
|
+
"sentrux",
|
|
32
|
+
"architecture.manifest.json",
|
|
33
|
+
);
|
|
34
|
+
const RULES_PATH = join(PROJECT_ROOT, ".sentrux", "rules.toml");
|
|
35
|
+
const META_PATH = join(PROJECT_ROOT, ".sentrux", ".harness-rules-meta.json");
|
|
26
36
|
|
|
27
37
|
const MANAGED_START = "# --- harness:managed:start ---";
|
|
28
38
|
const MANAGED_END = "# --- harness:managed:end ---";
|
|
@@ -113,7 +123,7 @@ async function fileExists(path) {
|
|
|
113
123
|
async function runSentruxCheck() {
|
|
114
124
|
return new Promise((resolve) => {
|
|
115
125
|
const child = spawn("sentrux", ["check", "."], {
|
|
116
|
-
cwd:
|
|
126
|
+
cwd: PROJECT_ROOT,
|
|
117
127
|
stdio: ["ignore", "pipe", "pipe"],
|
|
118
128
|
});
|
|
119
129
|
let out = "";
|
|
@@ -136,7 +146,19 @@ async function main() {
|
|
|
136
146
|
const strict = process.argv.includes("--strict");
|
|
137
147
|
|
|
138
148
|
if (!(await fileExists(MANIFEST))) {
|
|
139
|
-
|
|
149
|
+
if (await fileExists(MANIFEST_TEMPLATE)) {
|
|
150
|
+
await mkdir(dirname(MANIFEST), { recursive: true });
|
|
151
|
+
await writeFile(
|
|
152
|
+
MANIFEST,
|
|
153
|
+
await readFile(MANIFEST_TEMPLATE, "utf-8"),
|
|
154
|
+
"utf-8",
|
|
155
|
+
);
|
|
156
|
+
console.log(
|
|
157
|
+
`sentrux-rules-sync: seeded manifest from package -> ${MANIFEST}`,
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
fail(`missing manifest ${MANIFEST} (and no template in package)`);
|
|
161
|
+
}
|
|
140
162
|
}
|
|
141
163
|
|
|
142
164
|
const manifestRaw = await readFile(MANIFEST, "utf-8");
|
|
@@ -171,7 +193,7 @@ async function main() {
|
|
|
171
193
|
"rules.toml out of date — run node \"$UP_PKG/.pi/scripts/sentrux-rules-sync.mjs\" --force (see .pi/scripts/README.md for UP_PKG)",
|
|
172
194
|
);
|
|
173
195
|
} else {
|
|
174
|
-
await mkdir(join(
|
|
196
|
+
await mkdir(join(PROJECT_ROOT, ".sentrux"), { recursive: true });
|
|
175
197
|
const next = mergeRules(existing, managedBlock);
|
|
176
198
|
await writeFile(RULES_PATH, next, "utf-8");
|
|
177
199
|
meta = {
|
package/.sentrux/rules.toml
CHANGED
|
@@ -35,7 +35,7 @@ order = 2
|
|
|
35
35
|
|
|
36
36
|
[[layers]]
|
|
37
37
|
name = "agents"
|
|
38
|
-
paths = [".pi/agents/*", ".pi/prompts/*", ".agents/skills/*"]
|
|
38
|
+
paths = [".pi/agents/*", ".pi/harness/agents.manifest.json", ".pi/prompts/*", ".agents/skills/*"]
|
|
39
39
|
order = 3
|
|
40
40
|
# Agent definitions, prompts, and skills
|
|
41
41
|
|
package/AGENTS.md
CHANGED
|
@@ -30,7 +30,7 @@ Created: 2026-05-14
|
|
|
30
30
|
- Harness context: **context-mode only** — never lean-ctx on harness paths (see harness-context skill)
|
|
31
31
|
- `graphify update .` after significant code changes
|
|
32
32
|
- ast-grep (`sg`) is the default code search tool — use `sg -p 'pattern'` for structural search, never grep for code
|
|
33
|
-
-
|
|
33
|
+
- Web fetch/search via `python3 "$UP_PKG/.pi/scripts/harness-web.py"` (Scrapling; see scrapling-web skill)
|
|
34
34
|
|
|
35
35
|
## graphify
|
|
36
36
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [v0.4.0] — 2026-05-16
|
|
6
|
+
|
|
7
|
+
### ⚠️ Breaking Changes
|
|
8
|
+
|
|
9
|
+
- **Firecrawl removed**: deleted self-hosted `firecrawl/` Docker stack and all `firecrawl*` agent skills. Web fetch/search now uses **`harness-web`** (Scrapling). Artifact dir is **`.web/`** (not `.firecrawl/`). Remove `FIRECRAWL_API_KEY` / `FIRECRAWL_API_URL`; use `uv tool install "scrapling[fetchers]"` and `scrapling install` per `/harness-setup`.
|
|
10
|
+
- **Harness web CLI**: `python3 "$UP_PKG/.pi/scripts/harness-web.py" search|scrape|map|bulk-scrape` — see `.agents/skills/scrapling-web/SKILL.md`.
|
|
11
|
+
|
|
12
|
+
### ✨ Features
|
|
13
|
+
|
|
14
|
+
- **harness-web**: Scrapling-backed search (DuckDuckGo HTML SERP) and scrape (`StealthyFetcher` default; `--fast` for static HTTP). Modules under `.pi/scripts/harness_web/`.
|
|
15
|
+
- **Harness subagents**: vendored harness-subagents extension and Sentrux bootstrap (`#125`).
|
|
16
|
+
|
|
5
17
|
## [v0.3.1] — 2026-05-15
|
|
6
18
|
|
|
7
19
|
### 🐛 Fixes
|