ultimate-pi 0.2.4 → 0.2.6
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/.pi/PACKAGING.md +35 -0
- package/.pi/extensions/lib/harness-paths.ts +8 -0
- package/.pi/extensions/sentrux-rules-sync.ts +2 -8
- package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +1 -1
- package/.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md +2 -2
- package/.pi/harness/sentrux/architecture.manifest.json +3 -3
- package/.pi/prompts/harness-setup.md +61 -24
- package/.pi/scripts/README.md +17 -0
- package/{scripts → .pi/scripts}/harness-verify.mjs +3 -3
- package/{scripts → .pi/scripts}/sentrux-rules-sync.mjs +2 -2
- package/.pi/{settings.json → settings.example.json} +1 -1
- package/.sentrux/.harness-rules-meta.json +2 -2
- package/.sentrux/rules.toml +3 -3
- package/CHANGELOG.md +17 -0
- package/package.json +47 -8
- package/.ckignore +0 -41
- package/.codex/hooks.json +0 -15
- package/.env.example +0 -21
- package/.gitattributes +0 -1
- package/.github/banner-v2.png +0 -0
- package/.github/workflows/lint.yml +0 -33
- package/.github/workflows/publish-github-packages.yml +0 -35
- package/.github/workflows/publish-npm.yml +0 -32
- package/.pi/harness/browser.json +0 -1
- package/.pi/harness/router/README.md +0 -35
- package/.pi/harness/router/apply-router-proposal.mjs +0 -153
- package/.pi/harness/router/propose-router-tuning.mjs +0 -149
- package/.pi/npm/.gitignore +0 -2
- package/CONTRIBUTING.md +0 -166
- package/lefthook.yml +0 -9
- package/scripts/__pycache__/merge_graphify_corpora.cpython-314.pyc +0 -0
- package/scripts/index_youtube_urls.py +0 -376
- package/scripts/merge_graphify_corpora.py +0 -398
- package/scripts/regen_graphify_html.py +0 -46
- package/test/harness-verify.test.mjs +0 -33
- /package/{scripts → .pi/scripts}/harness-cli-verify.sh +0 -0
- /package/{scripts → .pi/scripts}/harness-graphify-bootstrap.sh +0 -0
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
name: Publish to GitHub Packages
|
|
2
|
-
run-name: Publish GitHub package from ${{ github.ref_name }}
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
push:
|
|
6
|
-
tags:
|
|
7
|
-
- 'v*'
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
publish-github-packages:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
permissions:
|
|
14
|
-
contents: read
|
|
15
|
-
packages: write
|
|
16
|
-
steps:
|
|
17
|
-
- name: Checkout
|
|
18
|
-
uses: actions/checkout@v4
|
|
19
|
-
|
|
20
|
-
- name: Setup Node.js for GitHub Packages
|
|
21
|
-
uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: '22.14.0'
|
|
24
|
-
registry-url: 'https://npm.pkg.github.com'
|
|
25
|
-
scope: '@aryaniyaps'
|
|
26
|
-
|
|
27
|
-
- name: Prepare scoped package manifest for GitHub Packages
|
|
28
|
-
run: |
|
|
29
|
-
npm pkg set name='@aryaniyaps/ultimate-pi'
|
|
30
|
-
npm pkg set publishConfig.registry='https://npm.pkg.github.com'
|
|
31
|
-
|
|
32
|
-
- name: Publish package to GitHub Packages
|
|
33
|
-
run: npm publish --ignore-scripts
|
|
34
|
-
env:
|
|
35
|
-
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
run-name: Publish npm from ${{ github.ref_name }}
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
push:
|
|
6
|
-
tags:
|
|
7
|
-
- 'v*'
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
publish:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
permissions:
|
|
14
|
-
contents: read
|
|
15
|
-
id-token: write
|
|
16
|
-
steps:
|
|
17
|
-
- name: Checkout
|
|
18
|
-
uses: actions/checkout@v4
|
|
19
|
-
|
|
20
|
-
- name: Setup Node.js
|
|
21
|
-
uses: actions/setup-node@v4
|
|
22
|
-
with:
|
|
23
|
-
node-version: '22.14.0'
|
|
24
|
-
|
|
25
|
-
- name: Ensure npm trusted publishing minimum version
|
|
26
|
-
run: |
|
|
27
|
-
npm i -g npm@^11.5.1
|
|
28
|
-
node -v
|
|
29
|
-
npm -v
|
|
30
|
-
|
|
31
|
-
- name: Publish package
|
|
32
|
-
run: npm publish --provenance --access public --ignore-scripts
|
package/.pi/harness/browser.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"headless": true, "timeout": 30000, "viewport": {"width": 1280, "height": 720}}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Router Tuning Flow
|
|
2
|
-
|
|
3
|
-
Router tuning is intentionally split into two steps:
|
|
4
|
-
|
|
5
|
-
1. **Propose** (`propose-router-tuning.mjs`)
|
|
6
|
-
2. **Approve + apply** (`apply-router-proposal.mjs`)
|
|
7
|
-
|
|
8
|
-
Blind writes to `.pi/model-router.json` are prohibited by design.
|
|
9
|
-
|
|
10
|
-
## Proposal
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
node .pi/harness/router/propose-router-tuning.mjs \
|
|
14
|
-
--evidence /path/to/evidence.json \
|
|
15
|
-
--candidate /path/to/candidate-router.json \
|
|
16
|
-
--proposal-out .pi/harness/router/proposals/proposal-001.json
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Apply (requires explicit human approval + justification)
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
node .pi/harness/router/apply-router-proposal.mjs \
|
|
23
|
-
--proposal .pi/harness/router/proposals/proposal-001.json \
|
|
24
|
-
--approve-by "human.name" \
|
|
25
|
-
--justification "why this is safe" \
|
|
26
|
-
--write
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Safety checks
|
|
30
|
-
|
|
31
|
-
- Evidence threshold must pass (`sample_count >= min_sample_count`)
|
|
32
|
-
- Regression guard must pass
|
|
33
|
-
- Base router hash in proposal must match current `.pi/model-router.json`
|
|
34
|
-
- Apply requires explicit approver and justification
|
|
35
|
-
- Current router file is backed up before write
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
const ROUTER_PATH = ".pi/model-router.json";
|
|
8
|
-
const BACKUP_DIR = ".pi/harness/router/backups";
|
|
9
|
-
|
|
10
|
-
function fail(message) {
|
|
11
|
-
process.stderr.write(`Error: ${message}\n`);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function parseArgs(argv) {
|
|
16
|
-
const args = {};
|
|
17
|
-
for (let i = 0; i < argv.length; i++) {
|
|
18
|
-
const token = argv[i];
|
|
19
|
-
if (!token.startsWith("--")) continue;
|
|
20
|
-
const key = token.slice(2);
|
|
21
|
-
const value = argv[i + 1];
|
|
22
|
-
if (!value || value.startsWith("--")) {
|
|
23
|
-
args[key] = true;
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
args[key] = value;
|
|
27
|
-
i++;
|
|
28
|
-
}
|
|
29
|
-
return args;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readJson(filePath, label) {
|
|
33
|
-
if (!fs.existsSync(filePath)) fail(`${label} not found: ${filePath}`);
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
36
|
-
} catch (error) {
|
|
37
|
-
fail(`${label} is not valid JSON (${filePath}): ${error.message}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function sha256FromJson(value) {
|
|
42
|
-
const canonical = `${JSON.stringify(value, null, 2)}\n`;
|
|
43
|
-
return crypto.createHash("sha256").update(canonical).digest("hex");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function validateProposal(proposal) {
|
|
47
|
-
if (proposal.status !== "proposed") {
|
|
48
|
-
fail(`proposal status must be 'proposed', got '${proposal.status}'`);
|
|
49
|
-
}
|
|
50
|
-
if (proposal.router_path !== ROUTER_PATH) {
|
|
51
|
-
fail(`proposal router_path must be '${ROUTER_PATH}'`);
|
|
52
|
-
}
|
|
53
|
-
const evidence = proposal.evidence ?? {};
|
|
54
|
-
if (
|
|
55
|
-
!Number.isInteger(evidence.sample_count) ||
|
|
56
|
-
!Number.isInteger(evidence.min_sample_count)
|
|
57
|
-
) {
|
|
58
|
-
fail("proposal evidence sample counts are invalid");
|
|
59
|
-
}
|
|
60
|
-
if (evidence.sample_count < evidence.min_sample_count) {
|
|
61
|
-
fail("proposal evidence does not meet minimum sample threshold");
|
|
62
|
-
}
|
|
63
|
-
if (evidence.regression_guard_passed !== true) {
|
|
64
|
-
fail("proposal regression guard is not passing");
|
|
65
|
-
}
|
|
66
|
-
if (!proposal.candidate_router || typeof proposal.candidate_router !== "object") {
|
|
67
|
-
fail("proposal missing candidate_router object");
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const args = parseArgs(process.argv.slice(2));
|
|
72
|
-
|
|
73
|
-
if (args.help || args.h) {
|
|
74
|
-
process.stdout.write(
|
|
75
|
-
[
|
|
76
|
-
"Usage:",
|
|
77
|
-
" node .pi/harness/router/apply-router-proposal.mjs \\",
|
|
78
|
-
" --proposal <proposal.json> \\",
|
|
79
|
-
" --approve-by <human> \\",
|
|
80
|
-
" --justification <reason> \\",
|
|
81
|
-
" --write",
|
|
82
|
-
"",
|
|
83
|
-
"Behavior:",
|
|
84
|
-
" - validates proposal status and evidence",
|
|
85
|
-
" - verifies base router hash matches current router file",
|
|
86
|
-
" - creates backup before atomic write",
|
|
87
|
-
" - refuses write unless explicit --write is provided",
|
|
88
|
-
].join("\n"),
|
|
89
|
-
);
|
|
90
|
-
process.exit(0);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!args.proposal) fail("missing --proposal");
|
|
94
|
-
if (!args["approve-by"]) fail("missing --approve-by");
|
|
95
|
-
if (!args.justification) fail("missing --justification");
|
|
96
|
-
if (!args.write) {
|
|
97
|
-
fail("missing --write (blind writes and implicit applies are disallowed)");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const proposalPath = path.resolve(args.proposal);
|
|
101
|
-
const proposal = readJson(proposalPath, "proposal");
|
|
102
|
-
const currentRouter = readJson(ROUTER_PATH, "current router");
|
|
103
|
-
|
|
104
|
-
validateProposal(proposal);
|
|
105
|
-
|
|
106
|
-
const currentHash = sha256FromJson(currentRouter);
|
|
107
|
-
if (currentHash !== proposal.base_router_sha256) {
|
|
108
|
-
fail(
|
|
109
|
-
[
|
|
110
|
-
"base router hash mismatch; refusing apply.",
|
|
111
|
-
`current: ${currentHash}`,
|
|
112
|
-
`proposal: ${proposal.base_router_sha256}`,
|
|
113
|
-
].join("\n"),
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const candidateHash = sha256FromJson(proposal.candidate_router);
|
|
118
|
-
if (candidateHash !== proposal.candidate_router_sha256) {
|
|
119
|
-
fail("proposal candidate_router hash mismatch; artifact may be tampered");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const now = new Date().toISOString();
|
|
123
|
-
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
124
|
-
const backupPath = path.join(
|
|
125
|
-
BACKUP_DIR,
|
|
126
|
-
`model-router.${now.replace(/[:.]/g, "-")}.json`,
|
|
127
|
-
);
|
|
128
|
-
fs.copyFileSync(ROUTER_PATH, backupPath);
|
|
129
|
-
|
|
130
|
-
const routerTemp = `${ROUTER_PATH}.tmp`;
|
|
131
|
-
fs.writeFileSync(routerTemp, `${JSON.stringify(proposal.candidate_router, null, 2)}\n`);
|
|
132
|
-
fs.renameSync(routerTemp, ROUTER_PATH);
|
|
133
|
-
|
|
134
|
-
proposal.status = "approved_applied";
|
|
135
|
-
proposal.approval = {
|
|
136
|
-
required: true,
|
|
137
|
-
approved_by: args["approve-by"],
|
|
138
|
-
approved_at: now,
|
|
139
|
-
justification: args.justification,
|
|
140
|
-
};
|
|
141
|
-
proposal.applied_router_sha256 = candidateHash;
|
|
142
|
-
proposal.backup_router_path = backupPath;
|
|
143
|
-
proposal.applied_at = now;
|
|
144
|
-
fs.writeFileSync(proposalPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
145
|
-
|
|
146
|
-
process.stdout.write(
|
|
147
|
-
[
|
|
148
|
-
"Router proposal applied safely.",
|
|
149
|
-
`proposal: ${proposalPath}`,
|
|
150
|
-
`backup: ${backupPath}`,
|
|
151
|
-
`router: ${ROUTER_PATH}`,
|
|
152
|
-
].join("\n") + "\n",
|
|
153
|
-
);
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
const ROUTER_PATH = ".pi/model-router.json";
|
|
8
|
-
|
|
9
|
-
function fail(message) {
|
|
10
|
-
process.stderr.write(`Error: ${message}\n`);
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseArgs(argv) {
|
|
15
|
-
const args = {};
|
|
16
|
-
for (let i = 0; i < argv.length; i++) {
|
|
17
|
-
const token = argv[i];
|
|
18
|
-
if (!token.startsWith("--")) continue;
|
|
19
|
-
const key = token.slice(2);
|
|
20
|
-
const value = argv[i + 1];
|
|
21
|
-
if (!value || value.startsWith("--")) {
|
|
22
|
-
args[key] = true;
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
args[key] = value;
|
|
26
|
-
i++;
|
|
27
|
-
}
|
|
28
|
-
return args;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function readJson(filePath, label) {
|
|
32
|
-
if (!fs.existsSync(filePath)) {
|
|
33
|
-
fail(`${label} not found: ${filePath}`);
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
37
|
-
} catch (error) {
|
|
38
|
-
fail(`${label} is not valid JSON (${filePath}): ${error.message}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function sha256FromJson(value) {
|
|
43
|
-
const canonical = `${JSON.stringify(value, null, 2)}\n`;
|
|
44
|
-
return crypto.createHash("sha256").update(canonical).digest("hex");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function ensureEvidence(evidence) {
|
|
48
|
-
const required = [
|
|
49
|
-
"sample_count",
|
|
50
|
-
"min_sample_count",
|
|
51
|
-
"success_rate_delta",
|
|
52
|
-
"cost_per_task_delta",
|
|
53
|
-
"regression_guard_passed",
|
|
54
|
-
"trace_refs",
|
|
55
|
-
];
|
|
56
|
-
for (const field of required) {
|
|
57
|
-
if (!(field in evidence)) fail(`evidence missing required field: ${field}`);
|
|
58
|
-
}
|
|
59
|
-
if (!Number.isInteger(evidence.sample_count) || evidence.sample_count < 1) {
|
|
60
|
-
fail("evidence.sample_count must be an integer >= 1");
|
|
61
|
-
}
|
|
62
|
-
if (
|
|
63
|
-
!Number.isInteger(evidence.min_sample_count) ||
|
|
64
|
-
evidence.min_sample_count < 1
|
|
65
|
-
) {
|
|
66
|
-
fail("evidence.min_sample_count must be an integer >= 1");
|
|
67
|
-
}
|
|
68
|
-
if (evidence.sample_count < evidence.min_sample_count) {
|
|
69
|
-
fail(
|
|
70
|
-
`insufficient sample_count (${evidence.sample_count} < ${evidence.min_sample_count})`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
if (typeof evidence.success_rate_delta !== "number") {
|
|
74
|
-
fail("evidence.success_rate_delta must be numeric");
|
|
75
|
-
}
|
|
76
|
-
if (typeof evidence.cost_per_task_delta !== "number") {
|
|
77
|
-
fail("evidence.cost_per_task_delta must be numeric");
|
|
78
|
-
}
|
|
79
|
-
if (evidence.regression_guard_passed !== true) {
|
|
80
|
-
fail("evidence.regression_guard_passed must be true");
|
|
81
|
-
}
|
|
82
|
-
if (!Array.isArray(evidence.trace_refs) || evidence.trace_refs.length === 0) {
|
|
83
|
-
fail("evidence.trace_refs must be a non-empty array");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const args = parseArgs(process.argv.slice(2));
|
|
88
|
-
|
|
89
|
-
if (args.help || args.h) {
|
|
90
|
-
process.stdout.write(
|
|
91
|
-
[
|
|
92
|
-
"Usage:",
|
|
93
|
-
" node .pi/harness/router/propose-router-tuning.mjs \\",
|
|
94
|
-
" --evidence <evidence.json> \\",
|
|
95
|
-
" --candidate <candidate-router.json> \\",
|
|
96
|
-
" --proposal-out <proposal.json>",
|
|
97
|
-
"",
|
|
98
|
-
"Behavior:",
|
|
99
|
-
" - validates evidence thresholds",
|
|
100
|
-
" - captures base/candidate router hashes",
|
|
101
|
-
" - emits proposal artifact without changing .pi/model-router.json",
|
|
102
|
-
].join("\n"),
|
|
103
|
-
);
|
|
104
|
-
process.exit(0);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!args.evidence) fail("missing --evidence");
|
|
108
|
-
if (!args.candidate) fail("missing --candidate");
|
|
109
|
-
if (!args["proposal-out"]) fail("missing --proposal-out");
|
|
110
|
-
|
|
111
|
-
const baseRouter = readJson(ROUTER_PATH, "base router");
|
|
112
|
-
const candidateRouter = readJson(args.candidate, "candidate router");
|
|
113
|
-
const evidence = readJson(args.evidence, "evidence");
|
|
114
|
-
|
|
115
|
-
ensureEvidence(evidence);
|
|
116
|
-
|
|
117
|
-
const now = new Date().toISOString();
|
|
118
|
-
const proposalId = `router-tune-${now.replace(/[:.]/g, "-")}`;
|
|
119
|
-
|
|
120
|
-
const proposal = {
|
|
121
|
-
schema_version: "1.0.0",
|
|
122
|
-
proposal_id: proposalId,
|
|
123
|
-
created_at: now,
|
|
124
|
-
router_path: ROUTER_PATH,
|
|
125
|
-
base_router_sha256: sha256FromJson(baseRouter),
|
|
126
|
-
candidate_router_sha256: sha256FromJson(candidateRouter),
|
|
127
|
-
evidence,
|
|
128
|
-
status: "proposed",
|
|
129
|
-
approval: {
|
|
130
|
-
required: true,
|
|
131
|
-
approved_by: null,
|
|
132
|
-
approved_at: null,
|
|
133
|
-
justification: null,
|
|
134
|
-
},
|
|
135
|
-
candidate_router: candidateRouter,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const outputPath = path.resolve(args["proposal-out"]);
|
|
139
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
140
|
-
fs.writeFileSync(outputPath, `${JSON.stringify(proposal, null, 2)}\n`);
|
|
141
|
-
|
|
142
|
-
process.stdout.write(
|
|
143
|
-
[
|
|
144
|
-
"Router tuning proposal created.",
|
|
145
|
-
`proposal_id: ${proposal.proposal_id}`,
|
|
146
|
-
`output: ${outputPath}`,
|
|
147
|
-
"status: proposed (no router write performed)",
|
|
148
|
-
].join("\n") + "\n",
|
|
149
|
-
);
|
package/.pi/npm/.gitignore
DELETED
package/CONTRIBUTING.md
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# Contributing to ultimate-pi
|
|
2
|
-
|
|
3
|
-
## Local development setup
|
|
4
|
-
|
|
5
|
-
1. Clone and install dependencies:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
git clone https://github.com/aryaniyaps/ultimate-pi.git
|
|
9
|
-
cd ultimate-pi
|
|
10
|
-
npm install
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
`npm install` automatically sets up pre-commit hooks via [Lefthook](https://github.com/evilmartians/lefthook).
|
|
14
|
-
|
|
15
|
-
2. Install the package locally into PI:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
pi install . -l
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Then restart PI or run `/reload`.
|
|
22
|
-
|
|
23
|
-
## Linting & formatting
|
|
24
|
-
|
|
25
|
-
Uses [Biome](https://biomejs.dev) for linting, formatting, and import sorting.
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm run lint # check lint + format errors
|
|
29
|
-
npm run lint:fix # auto-fix lint + format errors
|
|
30
|
-
npm run format # format all files
|
|
31
|
-
npm run format:check # check formatting without writing
|
|
32
|
-
npm run check:ts # typecheck extensions
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Pre-commit hooks run `biome check` and `tsc` on staged files automatically.
|
|
36
|
-
|
|
37
|
-
## Sentrux (architectural quality gate)
|
|
38
|
-
|
|
39
|
-
[Sentrux](https://github.com/sentrux/sentrux) provides real-time structural quality metrics for AI-agent-written code. It acts as a feedback loop sensor — scanning codebase architecture, detecting degradation, and enforcing rules via MCP.
|
|
40
|
-
|
|
41
|
-
### Quick start
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
# Install (macOS / Linux / Windows)
|
|
45
|
-
curl -fsSL https://raw.githubusercontent.com/sentrux/sentrux/main/install.sh | sh
|
|
46
|
-
|
|
47
|
-
# Install all 52 language plugins
|
|
48
|
-
sentrux plugin add-standard
|
|
49
|
-
|
|
50
|
-
# Run a quality scan
|
|
51
|
-
sentrux check .
|
|
52
|
-
|
|
53
|
-
# Save baseline before agent session
|
|
54
|
-
sentrux gate --save .
|
|
55
|
-
|
|
56
|
-
# Compare after — catches degradation
|
|
57
|
-
sentrux gate .
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### MCP Integration
|
|
61
|
-
|
|
62
|
-
The sentrux MCP server is configured in `.pi/mcp.json`. Agents can use tools like `scan`, `session_start`, `session_end`, `check_rules`, `health`, and `evolution` to monitor code quality during development.
|
|
63
|
-
|
|
64
|
-
### Rules Engine
|
|
65
|
-
|
|
66
|
-
Create `.sentrux/rules.toml` to define architectural constraints:
|
|
67
|
-
|
|
68
|
-
```toml
|
|
69
|
-
[constraints]
|
|
70
|
-
max_cycles = 0
|
|
71
|
-
max_coupling = "B"
|
|
72
|
-
max_cc = 25
|
|
73
|
-
no_god_files = true
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Firecrawl (self-hosted web scraping)
|
|
77
|
-
|
|
78
|
-
The Firecrawl skill depends on a Firecrawl instance. This repo includes a self-hosted setup powered by Docker.
|
|
79
|
-
|
|
80
|
-
### Quick start
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
cd firecrawl
|
|
84
|
-
cp .env.template .env # first time only — edit if needed
|
|
85
|
-
docker compose up -d # pulls pre-built GHCR images automatically
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Firecrawl API is now at `http://localhost:3002`. Admin UI at `http://localhost:3002/admin/<BULL_AUTH_KEY>/queues`.
|
|
89
|
-
|
|
90
|
-
### Services
|
|
91
|
-
|
|
92
|
-
| Service | Image | Port |
|
|
93
|
-
|---------|-------|------|
|
|
94
|
-
| `api` | `ghcr.io/firecrawl/firecrawl` | 3002 |
|
|
95
|
-
| `playwright-service` | `ghcr.io/firecrawl/playwright-service:latest` | 3000 (internal) |
|
|
96
|
-
| `nuq-postgres` | `ghcr.io/firecrawl/nuq-postgres:latest` | 5432 (internal) |
|
|
97
|
-
| `redis` | `redis:alpine` | 6379 (internal) |
|
|
98
|
-
| `rabbitmq` | `rabbitmq:3-management` | 5672 (internal) |
|
|
99
|
-
| `searxng` | `searxng/searxng:latest` | 8080 |
|
|
100
|
-
|
|
101
|
-
### Configuration
|
|
102
|
-
|
|
103
|
-
All options live in `firecrawl/.env`. See `firecrawl/.env.template` for the full reference. Key env vars:
|
|
104
|
-
|
|
105
|
-
- `PORT` — API port (default: `3002`)
|
|
106
|
-
- `SEARXNG_ENDPOINT` — enables `/search` API (default: `http://searxng:8080`)
|
|
107
|
-
- `OPENAI_API_KEY` — enables AI features (JSON formatting, `/extract` API)
|
|
108
|
-
- `BULL_AUTH_KEY` — admin UI access key (default: `CHANGEME` — change in production)
|
|
109
|
-
|
|
110
|
-
See `firecrawl/README.md` for detailed docs and SDK usage examples.
|
|
111
|
-
|
|
112
|
-
## Extensions
|
|
113
|
-
|
|
114
|
-
### Dotenv loader
|
|
115
|
-
|
|
116
|
-
`.pi/extensions/dotenv-loader.ts` — loads `.env` files into `process.env` on session start.
|
|
117
|
-
|
|
118
|
-
Configurable via env vars (set before launching pi):
|
|
119
|
-
|
|
120
|
-
| Variable | Default | Description |
|
|
121
|
-
|---|---|---|
|
|
122
|
-
| `ENV_LOADER_FILES` | `.env` | Comma-separated list of `.env` file paths (relative to cwd). |
|
|
123
|
-
| `ENV_LOADER_OVERRIDE` | `false` | Set to `true` to overwrite existing env vars. |
|
|
124
|
-
| `ENV_LOADER_SILENT` | `false` | Set to `true` to suppress startup logs. |
|
|
125
|
-
| `ENV_LOADER_ENCODING` | `utf-8` | File encoding for `.env` files. |
|
|
126
|
-
|
|
127
|
-
- Supports variable expansion (`$VAR` and `${VAR}`).
|
|
128
|
-
- Reloads on `/reload`.
|
|
129
|
-
- Status command: `/env-loader-status`
|
|
130
|
-
|
|
131
|
-
### Harness governance extensions
|
|
132
|
-
|
|
133
|
-
These Pi extensions are loaded from `.pi/extensions/` via the root `package.json`
|
|
134
|
-
`pi.extensions` manifest (no extra registration needed):
|
|
135
|
-
|
|
136
|
-
- `.pi/extensions/policy-gate.ts` — plan-before-mutate + phase enforcement
|
|
137
|
-
- `.pi/extensions/budget-guard.ts` — budget hard-stop and `budget_exhausted` events
|
|
138
|
-
- `.pi/extensions/trace-recorder.ts` — run trace artifacts in `.pi/harness/runs/`
|
|
139
|
-
- `.pi/extensions/review-integrity.ts` — evaluator/adversary session isolation checks
|
|
140
|
-
- `.pi/extensions/test-diff-integrity.ts` — suspicious test diff detection/escalation
|
|
141
|
-
- `.pi/extensions/debate-orchestrator.ts` — headless debate bus + consensus packets
|
|
142
|
-
|
|
143
|
-
### PostHog analytics
|
|
144
|
-
|
|
145
|
-
`@posthog/pi` — wraps the upstream [posthog-pi](https://github.com/PostHog/posthog-pi) extension to capture AI generation spans, tool spans, and traces in [PostHog](https://posthog.com). Install via `pi install @posthog/pi`. See the upstream repo for configuration and env vars.
|
|
146
|
-
|
|
147
|
-
## Skill sources
|
|
148
|
-
|
|
149
|
-
| Skill | Upstream |
|
|
150
|
-
|---|---|
|
|
151
|
-
| caveman | [juliusbrussee/caveman](https://github.com/juliusbrussee/caveman) |
|
|
152
|
-
| context7-cli | [upstash/context7](https://github.com/upstash/context7) |
|
|
153
|
-
| find-skills | bundled (context7-compatible discovery) |
|
|
154
|
-
| firecrawl (13 skills) | [firecrawl](https://firecrawl.dev) |
|
|
155
|
-
| obsidian/wiki skills (11 skills) | [AgriciDaniel/claude-obsidian](https://github.com/AgriciDaniel/claude-obsidian) |
|
|
156
|
-
| posthog-analyst | bundled (PostHog MCP integration) |
|
|
157
|
-
|
|
158
|
-
### Firecrawl sub-skills
|
|
159
|
-
|
|
160
|
-
`firecrawl-search`, `firecrawl-scrape`, `firecrawl-crawl`, `firecrawl-map`, `firecrawl-download`, `firecrawl-parse`, `firecrawl-interact`, `firecrawl-agent`, `firecrawl-build-scrape`, `firecrawl-build-search`, `firecrawl-build-onboarding`, `firecrawl-build-interact`
|
|
161
|
-
|
|
162
|
-
### Wiki sub-skills
|
|
163
|
-
|
|
164
|
-
`wiki`, `wiki-save`, `wiki-query`, `wiki-ingest`, `wiki-lint`, `wiki-fold`, `autoresearch`, `canvas`, `obsidian-markdown`, `obsidian-bases`
|
|
165
|
-
|
|
166
|
-
> `context-mode` is installed as a separate pi package (`npm:context-mode`) — not bundled as a skill.
|
package/lefthook.yml
DELETED
|
Binary file
|