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
package/.pi/scripts/README.md
CHANGED
|
@@ -23,7 +23,12 @@ From **Typescript extensions**, use `resolveHarnessScript()` / `getHarnessPackag
|
|
|
23
23
|
| Graphify bootstrap | `bash "$UP_PKG/.pi/scripts/harness-graphify-bootstrap.sh"` |
|
|
24
24
|
| CLI tool install + smoke tests | `bash "$UP_PKG/.pi/scripts/harness-cli-verify.sh"` |
|
|
25
25
|
| Deterministic harness checks | `node "$UP_PKG/.pi/scripts/harness-verify.mjs"` |
|
|
26
|
-
| Sentrux rules
|
|
26
|
+
| Sentrux rules bootstrap (harness-setup) | `node "$UP_PKG/.pi/scripts/harness-sentrux-bootstrap.mjs"` |
|
|
27
|
+
| Sentrux rules re-sync after manifest edit | `node "$UP_PKG/.pi/scripts/harness-sentrux-bootstrap.mjs" --force` or `/harness-sentrux-sync` |
|
|
28
|
+
| Sentrux rules drift check (CI) | `node "$UP_PKG/.pi/scripts/sentrux-rules-sync.mjs" --check` |
|
|
29
|
+
| Resolve package root (`UP_PKG`) | `node "$UP_PKG/.pi/scripts/harness-resolve-up-pkg.mjs"` |
|
|
30
|
+
| Model-router config (Pi auth) | `node "$UP_PKG/.pi/scripts/harness-generate-model-router.mjs"` |
|
|
31
|
+
| Project `.env` (append-only) | `node "$UP_PKG/.pi/scripts/harness-sync-env.mjs"` (`--create-missing` after user confirms) |
|
|
27
32
|
| Model-router / Pi defaults | `harness-sync-model-router.mjs` (Step 3.5 of `/harness-setup`) |
|
|
28
33
|
| Vendor router sync (this repo only) | `bash .pi/scripts/vendor-sync-pi-model-router.sh` or `npm run vendor:sync-router` |
|
|
29
34
|
| Meta-optimizer (JSONL proposals) | `node "$UP_PKG/.pi/harness/evolution/meta-optimizer.mjs"` |
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Regenerate or verify .pi/harness/agents.manifest.json from package agents.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node .pi/scripts/harness-agents-manifest.mjs --write
|
|
7
|
+
* node .pi/scripts/harness-agents-manifest.mjs --check
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
11
|
+
import { join, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import {
|
|
14
|
+
isSafeAgentId,
|
|
15
|
+
sha256Content,
|
|
16
|
+
walkAgentsDir,
|
|
17
|
+
} from "../../test/harness-subagents-loader.core.mjs";
|
|
18
|
+
|
|
19
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
20
|
+
const MANIFEST_PATH = join(ROOT, ".pi", "harness", "agents.manifest.json");
|
|
21
|
+
const PACKAGE_AGENTS = join(ROOT, ".pi", "agents");
|
|
22
|
+
|
|
23
|
+
async function readPackageMeta() {
|
|
24
|
+
const pkg = JSON.parse(
|
|
25
|
+
await readFile(join(ROOT, "package.json"), "utf-8"),
|
|
26
|
+
);
|
|
27
|
+
return { name: pkg.name ?? "ultimate-pi", version: pkg.version ?? "0.0.0" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildManifest(packageFiles, packageName, packageVersion) {
|
|
31
|
+
const agents = {};
|
|
32
|
+
for (const f of packageFiles.values()) {
|
|
33
|
+
agents[f.id] = {
|
|
34
|
+
path: `.pi/agents/${f.id}.md`,
|
|
35
|
+
sha256: sha256Content(f.content),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
schema_version: "1.0.0",
|
|
40
|
+
package: packageName,
|
|
41
|
+
package_version: packageVersion,
|
|
42
|
+
generated_at: new Date().toISOString(),
|
|
43
|
+
agents,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getDriftReport(manifest, packageFiles) {
|
|
48
|
+
const items = [];
|
|
49
|
+
if (!manifest) {
|
|
50
|
+
return { ok: false, items: [{ id: "*", kind: "missing_on_disk" }] };
|
|
51
|
+
}
|
|
52
|
+
for (const [id, file] of packageFiles) {
|
|
53
|
+
const expected = manifest.agents[id];
|
|
54
|
+
const actual = sha256Content(file.content);
|
|
55
|
+
if (!expected) {
|
|
56
|
+
items.push({ id, kind: "missing_in_manifest" });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (expected.sha256 !== actual) {
|
|
60
|
+
items.push({ id, kind: "hash_mismatch", expected: expected.sha256, actual });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const id of Object.keys(manifest.agents)) {
|
|
64
|
+
if (!packageFiles.has(id)) {
|
|
65
|
+
items.push({ id, kind: "missing_on_disk" });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { ok: items.length === 0, items };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function loadPackageFiles() {
|
|
72
|
+
const files = new Map();
|
|
73
|
+
walkAgentsDir(PACKAGE_AGENTS, "package", files);
|
|
74
|
+
for (const id of files.keys()) {
|
|
75
|
+
if (!isSafeAgentId(id)) files.delete(id);
|
|
76
|
+
}
|
|
77
|
+
return files;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
const mode = process.argv.includes("--check") ? "check" : "write";
|
|
82
|
+
const { name, version } = await readPackageMeta();
|
|
83
|
+
const packageFiles = await loadPackageFiles();
|
|
84
|
+
const built = buildManifest(packageFiles, name, version);
|
|
85
|
+
|
|
86
|
+
if (mode === "write") {
|
|
87
|
+
await writeFile(MANIFEST_PATH, `${JSON.stringify(built, null, 2)}\n`, "utf-8");
|
|
88
|
+
console.log(
|
|
89
|
+
`Wrote ${MANIFEST_PATH} (${Object.keys(built.agents).length} agents)`,
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let onDisk;
|
|
95
|
+
try {
|
|
96
|
+
onDisk = JSON.parse(await readFile(MANIFEST_PATH, "utf-8"));
|
|
97
|
+
} catch {
|
|
98
|
+
console.error("agents.manifest.json missing — run with --write");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const drift = getDriftReport(onDisk, packageFiles);
|
|
103
|
+
if (!drift.ok) {
|
|
104
|
+
for (const item of drift.items) {
|
|
105
|
+
console.error(`drift: ${item.id} (${item.kind})`);
|
|
106
|
+
}
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (onDisk.package_version !== version) {
|
|
111
|
+
console.error(
|
|
112
|
+
`package_version mismatch: manifest=${onDisk.package_version} package=${version}`,
|
|
113
|
+
);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(`agents.manifest.json OK (${Object.keys(built.agents).length} agents)`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main().catch((err) => {
|
|
121
|
+
console.error(err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
});
|
|
@@ -153,13 +153,62 @@ verify_agent_browser() {
|
|
|
153
153
|
fi
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
scrapling_installed() {
|
|
157
|
+
command -v scrapling &>/dev/null && return 0
|
|
158
|
+
command -v uv &>/dev/null && uv tool list 2>/dev/null | grep -qE '(^|[[:space:]])scrapling([[:space:]]|$)' && return 0
|
|
159
|
+
return 1
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
install_scrapling() {
|
|
163
|
+
if command -v uv &>/dev/null; then
|
|
164
|
+
log " installing scrapling via uv tool..."
|
|
165
|
+
uv tool install "scrapling[fetchers]" || return 1
|
|
166
|
+
elif command -v pip3 &>/dev/null; then
|
|
167
|
+
log " installing scrapling via pip3 --user..."
|
|
168
|
+
pip3 install --user "scrapling[fetchers]" || return 1
|
|
169
|
+
elif command -v pip &>/dev/null; then
|
|
170
|
+
pip install --user "scrapling[fetchers]" || return 1
|
|
171
|
+
else
|
|
172
|
+
fail "need uv or pip to install scrapling"
|
|
173
|
+
return 1
|
|
174
|
+
fi
|
|
175
|
+
export PATH="${HOME}/.local/bin:${PATH}"
|
|
176
|
+
return 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
verify_scrapling() {
|
|
180
|
+
log "[scrapling / harness-web]"
|
|
181
|
+
ensure_linux_browser_deps 2>/dev/null || true
|
|
182
|
+
if [ "$FORCE" = true ] || ! scrapling_installed; then
|
|
183
|
+
install_scrapling || { fail 'scrapling install (run: uv tool install "scrapling[fetchers]")'; return; }
|
|
184
|
+
fi
|
|
185
|
+
if ! scrapling_installed; then
|
|
186
|
+
fail "scrapling not on PATH after install"
|
|
187
|
+
return
|
|
188
|
+
fi
|
|
189
|
+
if ! scrapling --help &>/dev/null; then
|
|
190
|
+
fail "scrapling --help failed"
|
|
191
|
+
return
|
|
192
|
+
fi
|
|
193
|
+
pass "scrapling CLI"
|
|
194
|
+
if [ "$FORCE" = true ]; then
|
|
195
|
+
scrapling install 2>/dev/null || warn "scrapling install (browsers) failed — use harness-web scrape --fast for smoke"
|
|
196
|
+
fi
|
|
197
|
+
_hw="${ROOT}/.pi/scripts/harness-web.py"
|
|
198
|
+
if [ ! -f "$_hw" ]; then
|
|
199
|
+
warn "harness-web.py missing in package"
|
|
200
|
+
return
|
|
201
|
+
fi
|
|
202
|
+
mkdir -p .web
|
|
203
|
+
if python3 "$_hw" search "ultimate-pi harness" -o .web/verify-search.json --limit 2 2>/dev/null | grep -q wrote; then
|
|
204
|
+
pass "harness-web search smoke"
|
|
205
|
+
else
|
|
206
|
+
fail "harness-web search smoke failed"
|
|
207
|
+
fi
|
|
208
|
+
if python3 "$_hw" scrape "https://example.com" -o .web/verify-page.md --fast 2>/dev/null | grep -q wrote; then
|
|
209
|
+
pass "harness-web scrape --fast smoke"
|
|
161
210
|
else
|
|
162
|
-
|
|
211
|
+
warn "harness-web scrape smoke failed (stealth needs: scrapling install + OS browser libs)"
|
|
163
212
|
fi
|
|
164
213
|
}
|
|
165
214
|
|
|
@@ -259,10 +308,10 @@ verify_sentrux() {
|
|
|
259
308
|
return
|
|
260
309
|
fi
|
|
261
310
|
sentrux plugin add-standard 2>/dev/null || warn "sentrux plugin add-standard skipped"
|
|
262
|
-
|
|
263
|
-
if [ -f "$
|
|
264
|
-
node "$
|
|
265
|
-
warn "sentrux rules
|
|
311
|
+
_bootstrap="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/harness-sentrux-bootstrap.mjs"
|
|
312
|
+
if [ -f "$_bootstrap" ]; then
|
|
313
|
+
node "$_bootstrap" --force 2>/dev/null ||
|
|
314
|
+
warn "sentrux rules bootstrap failed (see harness-sentrux-setup skill)"
|
|
266
315
|
fi
|
|
267
316
|
if sentrux check . &>/dev/null; then
|
|
268
317
|
pass "sentrux $(sentrux --version 2>/dev/null | head -1)"
|
|
@@ -274,7 +323,7 @@ verify_sentrux() {
|
|
|
274
323
|
log "Harness CLI verification (cwd: $ROOT)"
|
|
275
324
|
log ""
|
|
276
325
|
|
|
277
|
-
|
|
326
|
+
verify_scrapling
|
|
278
327
|
verify_ctx7
|
|
279
328
|
verify_agent_browser
|
|
280
329
|
verify_ck
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate `.pi/model-router.json` from Pi's authenticated providers (auth.json + env),
|
|
4
|
+
* not from raw env-var heuristics alone.
|
|
5
|
+
*
|
|
6
|
+
* Uses @mariozechner/pi-coding-agent ModelRegistry.getAvailable() — same source as /login.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node harness-generate-model-router.mjs [--force] [--dry-run]
|
|
9
|
+
* --force overwrite existing .pi/model-router.json
|
|
10
|
+
* --dry-run print JSON to stdout, do not write
|
|
11
|
+
*
|
|
12
|
+
* Requires @mariozechner/pi-coding-agent (peer of ultimate-pi; bundled with pi).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
19
|
+
|
|
20
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const UP_PKG = join(SCRIPT_DIR, "..", "..");
|
|
22
|
+
const OUT_PATH = join(process.cwd(), ".pi", "model-router.json");
|
|
23
|
+
|
|
24
|
+
const PROVIDER_PRIORITY = [
|
|
25
|
+
"opencode-go",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"openai",
|
|
28
|
+
"google",
|
|
29
|
+
"openrouter",
|
|
30
|
+
"groq",
|
|
31
|
+
"mistral",
|
|
32
|
+
"amazon",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Substring hints per tier (first match in available ids wins). */
|
|
36
|
+
const TIER_HINTS = {
|
|
37
|
+
high: [
|
|
38
|
+
"deepseek-v4-pro",
|
|
39
|
+
"gpt-5.4-pro",
|
|
40
|
+
"claude-opus",
|
|
41
|
+
"sonnet-4",
|
|
42
|
+
"gemini-2.5-pro",
|
|
43
|
+
"pro",
|
|
44
|
+
],
|
|
45
|
+
medium: [
|
|
46
|
+
"qwen3.6-plus",
|
|
47
|
+
"kimi-k2.6",
|
|
48
|
+
"gpt-5.4",
|
|
49
|
+
"claude-sonnet",
|
|
50
|
+
"gemini-flash",
|
|
51
|
+
"plus",
|
|
52
|
+
],
|
|
53
|
+
low: [
|
|
54
|
+
"deepseek-v4-flash",
|
|
55
|
+
"gpt-5.4-nano",
|
|
56
|
+
"haiku",
|
|
57
|
+
"flash-lite",
|
|
58
|
+
"flash",
|
|
59
|
+
"mini",
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function fail(msg) {
|
|
64
|
+
console.error(`harness-generate-model-router: ${msg}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function loadPiCodingAgent() {
|
|
69
|
+
const agentRoots = [
|
|
70
|
+
join(UP_PKG, "node_modules", "@mariozechner", "pi-coding-agent"),
|
|
71
|
+
join(UP_PKG, ".pi", "npm", "node_modules", "@mariozechner", "pi-coding-agent"),
|
|
72
|
+
];
|
|
73
|
+
for (const root of agentRoots) {
|
|
74
|
+
const entry = join(root, "dist", "index.js");
|
|
75
|
+
if (existsSync(entry)) {
|
|
76
|
+
return import(pathToFileURL(entry).href);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const base of [UP_PKG, process.cwd()]) {
|
|
80
|
+
try {
|
|
81
|
+
const req = createRequire(join(base, "package.json"));
|
|
82
|
+
return req("@mariozechner/pi-coding-agent");
|
|
83
|
+
} catch {
|
|
84
|
+
/* try next */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
fail(
|
|
88
|
+
"@mariozechner/pi-coding-agent not found (install pi or npm i in ultimate-pi). Peer: @mariozechner/pi-coding-agent",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function canonicalRef(provider, modelId) {
|
|
93
|
+
return `${provider}/${modelId}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function pickTierModel(models, tier) {
|
|
97
|
+
const hints = TIER_HINTS[tier];
|
|
98
|
+
const ids = models.map((m) => m.id);
|
|
99
|
+
for (const hint of hints) {
|
|
100
|
+
const match = models.find((m) => m.id.includes(hint));
|
|
101
|
+
if (match) return canonicalRef(match.provider, match.id);
|
|
102
|
+
}
|
|
103
|
+
if (models.length === 0) return null;
|
|
104
|
+
if (tier === "high") {
|
|
105
|
+
const reasoning = models.find((m) => m.reasoning);
|
|
106
|
+
if (reasoning) return canonicalRef(reasoning.provider, reasoning.id);
|
|
107
|
+
}
|
|
108
|
+
if (tier === "low") {
|
|
109
|
+
return canonicalRef(models[models.length - 1].provider, models[models.length - 1].id);
|
|
110
|
+
}
|
|
111
|
+
return canonicalRef(models[0].provider, models[0].id);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function choosePrimaryProvider(available) {
|
|
115
|
+
const byProvider = new Map();
|
|
116
|
+
for (const m of available) {
|
|
117
|
+
if (!byProvider.has(m.provider)) byProvider.set(m.provider, []);
|
|
118
|
+
byProvider.get(m.provider).push(m);
|
|
119
|
+
}
|
|
120
|
+
for (const p of PROVIDER_PRIORITY) {
|
|
121
|
+
if (byProvider.has(p)) return { provider: p, models: byProvider.get(p) };
|
|
122
|
+
}
|
|
123
|
+
const first = [...byProvider.keys()].sort()[0];
|
|
124
|
+
return { provider: first, models: byProvider.get(first) ?? [] };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildFallbacks(available, primaryProvider, highModel) {
|
|
128
|
+
const fallbacks = [];
|
|
129
|
+
for (const p of ["anthropic", "google", "openai"]) {
|
|
130
|
+
if (p === primaryProvider) continue;
|
|
131
|
+
const alt = available.filter((m) => m.provider === p);
|
|
132
|
+
if (alt.length === 0) continue;
|
|
133
|
+
const ref = pickTierModel(alt, "medium");
|
|
134
|
+
if (ref && ref !== highModel) fallbacks.push(ref);
|
|
135
|
+
}
|
|
136
|
+
return fallbacks.slice(0, 3);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function main() {
|
|
140
|
+
const force = process.argv.includes("--force");
|
|
141
|
+
const dryRun = process.argv.includes("--dry-run");
|
|
142
|
+
|
|
143
|
+
if (existsSync(OUT_PATH) && !force) {
|
|
144
|
+
console.log(
|
|
145
|
+
"✓ .pi/model-router.json already exists — preserving (use --force to regenerate)",
|
|
146
|
+
);
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { AuthStorage, ModelRegistry } = await loadPiCodingAgent();
|
|
151
|
+
const authStorage = AuthStorage.create();
|
|
152
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
153
|
+
const available = await modelRegistry.getAvailable();
|
|
154
|
+
|
|
155
|
+
if (available.length === 0) {
|
|
156
|
+
console.log(
|
|
157
|
+
"✗ No authenticated Pi providers — skip model-router.json",
|
|
158
|
+
);
|
|
159
|
+
console.log(
|
|
160
|
+
" Log in inside pi: /login (or set API keys in ~/.pi/agent/auth.json)",
|
|
161
|
+
);
|
|
162
|
+
const providers = authStorage.list();
|
|
163
|
+
if (providers.length > 0) {
|
|
164
|
+
console.log(
|
|
165
|
+
` Stored providers in auth.json (may need refresh): ${providers.join(", ")}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { provider: primaryProvider, models: primaryModels } =
|
|
172
|
+
choosePrimaryProvider(available);
|
|
173
|
+
|
|
174
|
+
const highModel = pickTierModel(primaryModels, "high");
|
|
175
|
+
const mediumModel = pickTierModel(primaryModels, "medium");
|
|
176
|
+
const lowModel = pickTierModel(primaryModels, "low");
|
|
177
|
+
|
|
178
|
+
if (!highModel || !mediumModel || !lowModel) {
|
|
179
|
+
fail("could not assign tier models from available registry");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const fallbacks = buildFallbacks(available, primaryProvider, highModel);
|
|
183
|
+
|
|
184
|
+
const config = {
|
|
185
|
+
defaultProfile: "auto",
|
|
186
|
+
debug: false,
|
|
187
|
+
classifierModel: mediumModel,
|
|
188
|
+
phaseBias: 0.5,
|
|
189
|
+
maxSessionBudget: 1.0,
|
|
190
|
+
largeContextThreshold: 100000,
|
|
191
|
+
rules: [
|
|
192
|
+
{
|
|
193
|
+
matches: ["deploy", "production", "release"],
|
|
194
|
+
tier: "high",
|
|
195
|
+
reason: "Safety check for production tasks",
|
|
196
|
+
},
|
|
197
|
+
{ matches: "changelog", tier: "low" },
|
|
198
|
+
],
|
|
199
|
+
profiles: {
|
|
200
|
+
auto: {
|
|
201
|
+
high: { model: highModel, thinking: "high", fallbacks },
|
|
202
|
+
medium: { model: mediumModel, thinking: "medium" },
|
|
203
|
+
low: { model: lowModel, thinking: "low" },
|
|
204
|
+
},
|
|
205
|
+
cheap: {
|
|
206
|
+
high: { model: mediumModel, thinking: "low" },
|
|
207
|
+
medium: { model: lowModel, thinking: "off" },
|
|
208
|
+
low: { model: lowModel, thinking: "off" },
|
|
209
|
+
},
|
|
210
|
+
deep: {
|
|
211
|
+
high: { model: highModel, thinking: "xhigh", fallbacks },
|
|
212
|
+
medium: { model: mediumModel, thinking: "medium" },
|
|
213
|
+
low: { model: lowModel, thinking: "low" },
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const json = `${JSON.stringify(config, null, 2)}\n`;
|
|
219
|
+
const providerSet = [...new Set(available.map((m) => m.provider))].sort();
|
|
220
|
+
|
|
221
|
+
if (dryRun) {
|
|
222
|
+
process.stdout.write(json);
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
mkdirSync(dirname(OUT_PATH), { recursive: true });
|
|
227
|
+
writeFileSync(OUT_PATH, json, "utf8");
|
|
228
|
+
|
|
229
|
+
console.log("✓ Generated .pi/model-router.json from Pi authenticated providers:");
|
|
230
|
+
console.log(` Primary provider: ${primaryProvider}`);
|
|
231
|
+
console.log(` Authenticated providers: ${providerSet.join(", ")}`);
|
|
232
|
+
console.log(` Available models: ${available.length}`);
|
|
233
|
+
console.log(` High tier: ${highModel}`);
|
|
234
|
+
console.log(` Medium tier: ${mediumModel}`);
|
|
235
|
+
console.log(` Low tier: ${lowModel}`);
|
|
236
|
+
if (fallbacks.length) console.log(` Fallbacks: ${fallbacks.join(", ")}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
main().catch((err) => {
|
|
240
|
+
console.error(err);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
});
|
|
@@ -58,13 +58,8 @@ install_graphify() {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
graphify_platform_install() {
|
|
61
|
+
# Pi harness only — no codex/cursor installs (codex writes .codex/hooks.json).
|
|
61
62
|
graphify install --platform pi 2>/dev/null || graphify pi install 2>/dev/null || true
|
|
62
|
-
if [ -d .cursor ]; then
|
|
63
|
-
graphify cursor install 2>/dev/null || true
|
|
64
|
-
fi
|
|
65
|
-
if [ -f AGENTS.md ] || [ -d .pi ]; then
|
|
66
|
-
graphify codex install 2>/dev/null || true
|
|
67
|
-
fi
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
graph_is_valid() {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Print absolute path to the installed ultimate-pi package root (UP_PKG).
|
|
4
|
+
* Used by /harness-setup and shell scripts in external repos.
|
|
5
|
+
*
|
|
6
|
+
* Resolution order:
|
|
7
|
+
* 1. ULTIMATE_PI_PKG env override
|
|
8
|
+
* 2. require.resolve('ultimate-pi/package.json') from cwd
|
|
9
|
+
* 3. Global npm prefix: $(npm root -g)/ultimate-pi
|
|
10
|
+
* 4. Script location (this file ships inside the package)
|
|
11
|
+
*
|
|
12
|
+
* Exit 0 and prints path; exit 1 if not found.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createRequire } from "node:module";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
const requireFromCwd = createRequire(join(process.cwd(), "package.json"));
|
|
22
|
+
|
|
23
|
+
const SCRIPT_UP_PKG = join(
|
|
24
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
25
|
+
"..",
|
|
26
|
+
"..",
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
function hasHarnessScripts(root) {
|
|
30
|
+
return existsSync(join(root, ".pi", "scripts", "harness-cli-verify.sh"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function tryResolveUltimatePi() {
|
|
34
|
+
if (process.env.ULTIMATE_PI_PKG) {
|
|
35
|
+
const envRoot = process.env.ULTIMATE_PI_PKG;
|
|
36
|
+
if (hasHarnessScripts(envRoot)) return envRoot;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const pkg = requireFromCwd.resolve("ultimate-pi/package.json");
|
|
41
|
+
const root = dirname(pkg);
|
|
42
|
+
if (hasHarnessScripts(root)) return root;
|
|
43
|
+
} catch {
|
|
44
|
+
/* continue */
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const globalRoot = execSync("npm root -g", {
|
|
49
|
+
encoding: "utf8",
|
|
50
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
51
|
+
}).trim();
|
|
52
|
+
const globalPkg = join(globalRoot, "ultimate-pi");
|
|
53
|
+
if (hasHarnessScripts(globalPkg)) return globalPkg;
|
|
54
|
+
} catch {
|
|
55
|
+
/* continue */
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (hasHarnessScripts(SCRIPT_UP_PKG)) return SCRIPT_UP_PKG;
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const root = tryResolveUltimatePi();
|
|
64
|
+
if (!root) {
|
|
65
|
+
console.error(
|
|
66
|
+
"harness-resolve-up-pkg: ultimate-pi not found. Install: pi install npm:ultimate-pi (or npm i -g ultimate-pi)",
|
|
67
|
+
);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.stdout.write(root);
|
|
@@ -11,16 +11,40 @@
|
|
|
11
11
|
* (the script always lives under the shipped ultimate-pi package).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { copyFile, mkdir, readdir } from "node:fs/promises";
|
|
14
|
+
import { copyFile, mkdir, readdir, access } from "node:fs/promises";
|
|
15
|
+
import { constants } from "node:fs";
|
|
15
16
|
import { join, dirname } from "node:path";
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
18
|
|
|
18
19
|
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const UP_PKG = join(SCRIPT_DIR, "..", "..");
|
|
20
21
|
const SPEC_SRC = join(UP_PKG, ".pi", "harness", "specs");
|
|
22
|
+
const SENTRUX_TEMPLATE = join(
|
|
23
|
+
UP_PKG,
|
|
24
|
+
".pi",
|
|
25
|
+
"harness",
|
|
26
|
+
"sentrux",
|
|
27
|
+
"architecture.manifest.json",
|
|
28
|
+
);
|
|
21
29
|
|
|
22
30
|
const projectRoot = process.argv[2] || process.cwd();
|
|
23
31
|
const specDest = join(projectRoot, ".pi", "harness", "specs");
|
|
32
|
+
const sentruxDest = join(
|
|
33
|
+
projectRoot,
|
|
34
|
+
".pi",
|
|
35
|
+
"harness",
|
|
36
|
+
"sentrux",
|
|
37
|
+
"architecture.manifest.json",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
async function fileExists(path) {
|
|
41
|
+
try {
|
|
42
|
+
await access(path, constants.R_OK);
|
|
43
|
+
return true;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
24
48
|
|
|
25
49
|
async function main() {
|
|
26
50
|
const names = await readdir(SPEC_SRC);
|
|
@@ -41,6 +65,14 @@ async function main() {
|
|
|
41
65
|
console.log(
|
|
42
66
|
`harness-seed-project-contracts: copied ${toCopy.length} file(s) -> ${specDest}`,
|
|
43
67
|
);
|
|
68
|
+
|
|
69
|
+
if (!(await fileExists(sentruxDest)) && (await fileExists(SENTRUX_TEMPLATE))) {
|
|
70
|
+
await mkdir(dirname(sentruxDest), { recursive: true });
|
|
71
|
+
await copyFile(SENTRUX_TEMPLATE, sentruxDest);
|
|
72
|
+
console.log(
|
|
73
|
+
`harness-seed-project-contracts: seeded Sentrux manifest -> ${sentruxDest} (run harness-sentrux-bootstrap.mjs to sync rules.toml)`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
44
76
|
}
|
|
45
77
|
|
|
46
78
|
main().catch((err) => {
|