@mccune1224/pi-pstack 0.1.0 → 0.2.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/README.md +52 -100
- package/agents/comment-sicko.md +1 -1
- package/agents/poteto-agent.md +2 -2
- package/docs/compare.md +122 -0
- package/docs/guide/01-setup.md +37 -12
- package/docs/guide/02-poteto-mode.md +1 -1
- package/docs/guide/03-understand.md +0 -8
- package/docs/guide/05-build-and-clean.md +1 -1
- package/docs/guide/06-verify-and-ship.md +1 -1
- package/docs/guide/07-overnight.md +2 -2
- package/docs/guide/08-principles.md +4 -2
- package/docs/guide/10-recipes-and-pitfalls.md +2 -2
- package/docs/guide/README.md +2 -2
- package/extensions/compaction.test.ts +132 -0
- package/extensions/compaction.ts +183 -0
- package/extensions/context-guard.test.ts +53 -0
- package/extensions/context-guard.ts +65 -0
- package/extensions/doctor.test.ts +85 -0
- package/extensions/doctor.ts +100 -0
- package/extensions/index.ts +289 -26
- package/extensions/settings.test.ts +71 -16
- package/extensions/settings.ts +57 -9
- package/extensions/skill-budget.test.ts +110 -0
- package/extensions/skill-budget.ts +101 -0
- package/package.json +9 -4
- package/scripts/check-pi-isms.mjs +106 -0
- package/scripts/lib/pi-delta-rules.mjs +110 -0
- package/scripts/lib/pi-delta-rules.test.mjs +100 -0
- package/scripts/lib/upstream-vendor.mjs +32 -0
- package/scripts/pi-deltas.json +754 -0
- package/scripts/sync-upstream.mjs +117 -0
- package/scripts/verify-guards.mjs +132 -0
- package/scripts/verify-sync.mjs +206 -0
- package/skills/architect/SKILL.md +10 -10
- package/skills/architect/references/rationale-template.md +2 -2
- package/skills/architect/references/runner-prompt.md +7 -7
- package/skills/arena/SKILL.md +11 -11
- package/skills/automate-me/SKILL.md +21 -26
- package/skills/blast-radius/SKILL.md +5 -5
- package/skills/create-verification-skill/SKILL.md +5 -5
- package/skills/figure-it-out/SKILL.md +11 -13
- package/skills/how/SKILL.md +30 -104
- package/skills/how/references/explainer-prompt.md +6 -6
- package/skills/how/references/explorer-prompt.md +1 -1
- package/skills/interrogate/SKILL.md +13 -14
- package/skills/interrogate/references/code-quality-review.md +1 -1
- package/skills/interrogate/references/lead-judgment.md +1 -1
- package/skills/interrogate/references/rubric.md +2 -2
- package/skills/maintain-verification-skill/SKILL.md +5 -5
- package/skills/no-comments/SKILL.md +3 -3
- package/skills/poteto-mode/SKILL.md +9 -8
- package/skills/poteto-mode/playbooks/authoring-a-skill.md +4 -4
- package/skills/poteto-mode/playbooks/autonomous-run.md +4 -4
- package/skills/poteto-mode/playbooks/autopilot-full.md +9 -9
- package/skills/poteto-mode/playbooks/autopilot-stack.md +8 -8
- package/skills/poteto-mode/playbooks/babysit.md +17 -17
- package/skills/poteto-mode/playbooks/bug-fix.md +6 -6
- package/skills/poteto-mode/playbooks/eval.md +6 -8
- package/skills/poteto-mode/playbooks/feature.md +7 -7
- package/skills/poteto-mode/playbooks/hillclimb.md +10 -10
- package/skills/poteto-mode/playbooks/investigation.md +3 -3
- package/skills/poteto-mode/playbooks/multi-phase-plan.md +26 -25
- package/skills/poteto-mode/playbooks/opening-a-pr.md +17 -13
- package/skills/poteto-mode/playbooks/orchestrate.md +37 -39
- package/skills/poteto-mode/playbooks/pause-safely.md +4 -4
- package/skills/poteto-mode/playbooks/perf-issue.md +9 -9
- package/skills/poteto-mode/playbooks/prototype.md +5 -5
- package/skills/poteto-mode/playbooks/refactoring.md +9 -9
- package/skills/poteto-mode/playbooks/runtime-forensics.md +3 -3
- package/skills/poteto-mode/playbooks/session-pickup.md +4 -6
- package/skills/poteto-mode/playbooks/shipping.md +11 -14
- package/skills/poteto-mode/playbooks/trace-forensics.md +4 -4
- package/skills/poteto-mode/playbooks/visual-parity.md +3 -3
- package/skills/poteto-mode/playbooks/worktree-cleanup.md +4 -4
- package/skills/poteto-mode/references/bugbot-triage.md +3 -3
- package/skills/poteto-mode/scripts/worktree-audit.mjs +0 -7
- package/skills/principle-attack-the-premise/SKILL.md +23 -0
- package/skills/principle-boundary-discipline/SKILL.md +2 -2
- package/skills/principle-build-the-lever/SKILL.md +5 -5
- package/skills/principle-encode-lessons-in-structure/SKILL.md +3 -3
- package/skills/principle-experience-first/SKILL.md +5 -5
- package/skills/principle-fix-root-causes/SKILL.md +4 -4
- package/skills/principle-foundational-thinking/SKILL.md +3 -3
- package/skills/principle-guard-the-context-window/SKILL.md +2 -2
- package/skills/principle-laziness-protocol/SKILL.md +2 -2
- package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -1
- package/skills/principle-minimize-reader-load/SKILL.md +2 -2
- package/skills/principle-model-the-domain/SKILL.md +3 -3
- package/skills/principle-never-block-on-the-human/SKILL.md +4 -5
- package/skills/principle-prove-it-works/SKILL.md +3 -3
- package/skills/principle-redesign-from-first-principles/SKILL.md +3 -3
- package/skills/principle-separate-before-serializing-shared-state/SKILL.md +3 -3
- package/skills/principle-sequence-verifiable-units/SKILL.md +3 -3
- package/skills/principle-subtract-before-you-add/SKILL.md +3 -4
- package/skills/principle-test-behavior-not-implementation/SKILL.md +25 -0
- package/skills/principle-type-system-discipline/SKILL.md +5 -5
- package/skills/recall/SKILL.md +7 -7
- package/skills/reflect/SKILL.md +14 -20
- package/skills/reflect/references/divergent-reviewer.md +3 -3
- package/skills/reflect/references/judgment-reviewer.md +3 -3
- package/skills/reflect/references/synthesizer.md +2 -2
- package/skills/reflect/references/tooling-reviewer.md +4 -6
- package/skills/setup-pstack/SKILL.md +61 -49
- package/skills/show-me-your-work/SKILL.md +17 -17
- package/skills/show-me-your-work/scripts/log.mjs +2 -8
- package/skills/swarm/SKILL.md +6 -6
- package/skills/tdd/SKILL.md +2 -2
- package/skills/teach/SKILL.md +7 -7
- package/skills/technical-writing/SKILL.md +5 -8
- package/skills/typescript-best-practices/SKILL.md +6 -3
- package/skills/typescript-best-practices/references/patterns.md +35 -14
- package/skills/unslop/SKILL.md +8 -20
- package/skills/why/SKILL.md +27 -100
- package/skills/why/references/epistemics.md +6 -6
- package/skills/why/references/investigator-prompt.md +5 -5
- package/skills/why/references/source-playbook.md +1 -1
- package/skills/why/references/sources/databricks.md +7 -7
- package/skills/why/references/sources/datadog.md +5 -5
- package/skills/why/references/sources/incident-postmortem.md +2 -2
- package/skills/why/references/sources/linear.md +2 -2
- package/skills/why/references/sources/notion.md +3 -3
- package/skills/why/references/sources/sentry.md +4 -4
- package/skills/why/references/sources/slack.md +2 -2
- package/skills/why/references/synthesizer-prompt.md +3 -3
- package/skills/how/references/critic-prompt.md +0 -59
- package/skills/how/references/critique-rubric.md +0 -58
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { join, relative, resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { applyRules, globsOf, matchesAny, overrideFor, validateSpec } from "./lib/pi-delta-rules.mjs";
|
|
7
|
+
import { isBinary, readUpstreamTree, upstreamVersion } from "./lib/upstream-vendor.mjs";
|
|
8
|
+
|
|
9
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const VENDORED = ["skills", "agents", "docs"];
|
|
11
|
+
|
|
12
|
+
function arg(name, fallback) {
|
|
13
|
+
const index = process.argv.indexOf(name);
|
|
14
|
+
return index === -1 ? fallback : process.argv[index + 1];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const upstreamDir = resolve(arg("--upstream", "/tmp/upstream-plugins"));
|
|
18
|
+
const outDir = resolve(arg("--out", ROOT));
|
|
19
|
+
const specPath = resolve(arg("--spec", join(ROOT, "scripts", "pi-deltas.json")));
|
|
20
|
+
const checkOnly = process.argv.includes("--check-only");
|
|
21
|
+
|
|
22
|
+
function* walk(dir) {
|
|
23
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
const path = join(dir, entry.name);
|
|
25
|
+
if (entry.isDirectory()) yield* walk(path);
|
|
26
|
+
else if (entry.isFile()) yield path;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const spec = JSON.parse(readFileSync(specPath, "utf8"));
|
|
31
|
+
const problems = validateSpec(spec);
|
|
32
|
+
if (problems.length > 0) {
|
|
33
|
+
console.error("pi-deltas.json is invalid:");
|
|
34
|
+
for (const problem of problems) console.error(` ${problem}`);
|
|
35
|
+
process.exit(2);
|
|
36
|
+
}
|
|
37
|
+
if (!existsSync(join(upstreamDir, ".git"))) {
|
|
38
|
+
console.error(`upstream checkout missing at ${upstreamDir}`);
|
|
39
|
+
console.error("clone it first: git clone --depth 400 https://github.com/cursor/plugins.git /tmp/upstream-plugins");
|
|
40
|
+
process.exit(2);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const version = upstreamVersion(upstreamDir, spec.upstream.ref);
|
|
44
|
+
if (spec.upstream.version !== version) {
|
|
45
|
+
console.error(`pi-deltas.json pins ${spec.upstream.version} but ${spec.upstream.ref} is ${version}`);
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const upstream = readUpstreamTree(upstreamDir, spec.upstream.ref, VENDORED);
|
|
50
|
+
const excludeGlobs = globsOf(spec, "exclude");
|
|
51
|
+
const addGlobs = globsOf(spec, "add");
|
|
52
|
+
|
|
53
|
+
const serialized = new Map();
|
|
54
|
+
for (const [path, content] of upstream) {
|
|
55
|
+
if (matchesAny(excludeGlobs, path)) continue;
|
|
56
|
+
if (overrideFor(spec, path)) continue;
|
|
57
|
+
serialized.set(path, isBinary(content) ? content : applyRules(content.toString("utf8"), path, spec.rules).content);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const preserved = new Set();
|
|
61
|
+
for (const subtree of VENDORED) {
|
|
62
|
+
const dir = join(outDir, subtree);
|
|
63
|
+
if (!existsSync(dir)) continue;
|
|
64
|
+
for (const file of walk(dir)) {
|
|
65
|
+
const path = relative(outDir, file);
|
|
66
|
+
if (matchesAny(addGlobs, path)) preserved.add(path);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const undeclared = [];
|
|
71
|
+
for (const subtree of VENDORED) {
|
|
72
|
+
const dir = join(outDir, subtree);
|
|
73
|
+
if (!existsSync(dir)) continue;
|
|
74
|
+
for (const file of walk(dir)) {
|
|
75
|
+
const path = relative(outDir, file);
|
|
76
|
+
if (serialized.has(path) || preserved.has(path)) continue;
|
|
77
|
+
if (upstream.has(path) && overrideFor(spec, path)) continue;
|
|
78
|
+
undeclared.push(path);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const toDelete = undeclared.filter((path) => upstream.has(path) || matchesAny(excludeGlobs, path));
|
|
82
|
+
const orphaned = undeclared.filter((path) => !toDelete.includes(path));
|
|
83
|
+
|
|
84
|
+
if (!checkOnly) {
|
|
85
|
+
for (const path of toDelete) rmSync(join(outDir, path));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const pending = [...serialized.entries()];
|
|
89
|
+
const changed = [];
|
|
90
|
+
for (const [path, content] of pending) {
|
|
91
|
+
const target = join(outDir, path);
|
|
92
|
+
const before = existsSync(target) ? readFileSync(target) : null;
|
|
93
|
+
const same = before !== null && (typeof content === "string" ? before.toString("utf8") === content : before.equals(content));
|
|
94
|
+
if (same) continue;
|
|
95
|
+
changed.push(path);
|
|
96
|
+
if (checkOnly) continue;
|
|
97
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
98
|
+
writeFileSync(target, content);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`upstream ${version} @ ${spec.upstream.ref.slice(0, 12)}`);
|
|
102
|
+
console.log(` vendored files: ${serialized.size}`);
|
|
103
|
+
console.log(` ours-only preserved: ${preserved.size}`);
|
|
104
|
+
console.log(` removed (upstream dropped them): ${toDelete.length}`);
|
|
105
|
+
console.log(` written: ${changed.length}`);
|
|
106
|
+
if (changed.length > 0) for (const path of changed) console.log(` ${checkOnly ? "would write" : "wrote"} ${path}`);
|
|
107
|
+
if (toDelete.length > 0) for (const path of toDelete) console.log(` ${checkOnly ? "would remove" : "removed"} ${path}`);
|
|
108
|
+
if (orphaned.length > 0) {
|
|
109
|
+
console.log("\nORPHANED (ours only, undeclared; left in place, declare an add rule or delete):");
|
|
110
|
+
for (const path of orphaned) console.log(` ${path}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const verify = spawnSync(process.execPath, [join(ROOT, "scripts", "verify-sync.mjs"), "--upstream", upstreamDir, "--spec", specPath, "--root", outDir], {
|
|
114
|
+
stdio: "inherit",
|
|
115
|
+
});
|
|
116
|
+
const scanner = spawnSync(process.execPath, [join(ROOT, "scripts", "check-pi-isms.mjs"), outDir], { stdio: "inherit" });
|
|
117
|
+
process.exit((verify.status ?? 1) || (scanner.status ?? 1) || (orphaned.length > 0 ? 1 : 0));
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, appendFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const upstream = resolve(process.argv[2] ?? "/tmp/upstream-plugins");
|
|
10
|
+
|
|
11
|
+
if (!existsSync(join(upstream, ".git"))) {
|
|
12
|
+
console.error(`upstream checkout missing at ${upstream}`);
|
|
13
|
+
console.error("clone it first: git clone --depth 400 https://github.com/cursor/plugins.git /tmp/upstream-plugins");
|
|
14
|
+
process.exit(2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const work = mkdtempSync(join(tmpdir(), "pi-pstack-guards-"));
|
|
18
|
+
for (const entry of ["skills", "agents", "docs", "scripts"]) {
|
|
19
|
+
cpSync(join(ROOT, entry), join(work, entry), { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const specPath = join(work, "scripts", "pi-deltas.json");
|
|
23
|
+
|
|
24
|
+
function verifySync() {
|
|
25
|
+
const result = spawnSync(
|
|
26
|
+
process.execPath,
|
|
27
|
+
[join(work, "scripts", "verify-sync.mjs"), "--upstream", upstream, "--spec", specPath, "--root", work],
|
|
28
|
+
{ encoding: "utf8" },
|
|
29
|
+
);
|
|
30
|
+
return { code: result.status ?? 1, out: `${result.stdout ?? ""}${result.stderr ?? ""}` };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readSpec() {
|
|
34
|
+
return JSON.parse(readFileSync(specPath, "utf8"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeSpec(spec) {
|
|
38
|
+
writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function editRule(id, patch) {
|
|
42
|
+
const spec = readSpec();
|
|
43
|
+
const rule = spec.rules.find((candidate) => candidate.id === id);
|
|
44
|
+
if (!rule) throw new Error(`no rule named ${id} to mutate`);
|
|
45
|
+
Object.assign(rule, patch);
|
|
46
|
+
writeSpec(spec);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const guards = [
|
|
50
|
+
{
|
|
51
|
+
name: "a hand edit to a vendored file is caught",
|
|
52
|
+
why: "this is the case that would make the whole check self-certifying if it failed to fail",
|
|
53
|
+
mutate: () => appendFileSync(join(work, "skills", "how", "SKILL.md"), "\nedited by hand\n"),
|
|
54
|
+
expect: (out) => out.includes("skills/how/SKILL.md") && out.includes("FAIL"),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "deleting a rule is caught",
|
|
58
|
+
why: "rules are load-bearing, so removing one has to surface as unexplained content",
|
|
59
|
+
mutate: () => {
|
|
60
|
+
const spec = readSpec();
|
|
61
|
+
spec.rules = spec.rules.filter((rule) => rule.id !== "pi-dir-skills");
|
|
62
|
+
writeSpec(spec);
|
|
63
|
+
},
|
|
64
|
+
expect: (out) => /unexplained:\s+[1-9]/.test(out) && out.includes("FAIL"),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "a rule whose find no longer matches is caught as stale",
|
|
68
|
+
why: "upstream moving the text a rule targets is the failure this port exists to prevent",
|
|
69
|
+
mutate: () => editRule("pi-dir-skills", { find: ".cursor/skills-that-do-not-exist/" }),
|
|
70
|
+
expect: (out) => out.includes("STALE RULES") && out.includes("pi-dir-skills"),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "an override whose upstream baseline moved is caught",
|
|
74
|
+
why: "an override silently reverting our version is the risk that makes overrides safe to use",
|
|
75
|
+
mutate: () => editRule("override-poteto-mode-skill", { basedOn: `sha256:${"0".repeat(64)}` }),
|
|
76
|
+
expect: (out) => out.includes("override-poteto-mode-skill is stale") && out.includes("FAIL"),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "declared excludes stay absent",
|
|
80
|
+
why: "a file we declared out of scope reappearing locally should not pass silently",
|
|
81
|
+
mutate: () => {
|
|
82
|
+
const spec = readSpec();
|
|
83
|
+
const dir = join(work, "skills", "make-bot-ui");
|
|
84
|
+
cpSync(join(work, "skills", "control-ui"), dir, { recursive: true });
|
|
85
|
+
return spec;
|
|
86
|
+
},
|
|
87
|
+
expect: (out) => out.includes("skills/make-bot-ui") && out.includes("FAIL"),
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const restore = () => {
|
|
92
|
+
for (const entry of ["skills", "agents", "docs", "scripts"]) {
|
|
93
|
+
rmSync(join(work, entry), { recursive: true, force: true });
|
|
94
|
+
cpSync(join(ROOT, entry), join(work, entry), { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const baseline = verifySync();
|
|
99
|
+
if (baseline.code !== 0) {
|
|
100
|
+
console.error("baseline is not clean, so no guard can be evaluated:");
|
|
101
|
+
console.error(baseline.out.trim().split("\n").slice(-6).join("\n"));
|
|
102
|
+
rmSync(work, { recursive: true, force: true });
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}
|
|
105
|
+
console.log("baseline clean");
|
|
106
|
+
|
|
107
|
+
let failed = 0;
|
|
108
|
+
for (const guard of guards) {
|
|
109
|
+
guard.mutate();
|
|
110
|
+
const result = verifySync();
|
|
111
|
+
const caught = result.code !== 0 && guard.expect(result.out);
|
|
112
|
+
console.log(`${caught ? "caught " : "MISSED "} ${guard.name}`);
|
|
113
|
+
if (!caught) {
|
|
114
|
+
failed++;
|
|
115
|
+
console.log(` why it matters: ${guard.why}`);
|
|
116
|
+
console.log(` exit ${result.code}, output tail:`);
|
|
117
|
+
console.log(result.out.trim().split("\n").slice(-4).map((line) => ` ${line}`).join("\n"));
|
|
118
|
+
}
|
|
119
|
+
restore();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const final = verifySync();
|
|
123
|
+
const restored = final.code === 0;
|
|
124
|
+
console.log(`${restored ? "restored" : "DIRTY "} tree returns to a clean baseline after the mutations`);
|
|
125
|
+
|
|
126
|
+
rmSync(work, { recursive: true, force: true });
|
|
127
|
+
|
|
128
|
+
if (failed > 0 || !restored) {
|
|
129
|
+
console.error(`\nFAIL: ${failed} guard(s) did not catch their mutation`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
console.log(`\nPASS: all ${guards.length} guards catch their mutation and the tree is unchanged`);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, readdirSync, existsSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, relative, resolve, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { applyRules, globsOf, hashContent, matchGlob, matchesAny, overrideFor, validateSpec } from "./lib/pi-delta-rules.mjs";
|
|
6
|
+
import { isBinary, readUpstreamTree, upstreamVersion } from "./lib/upstream-vendor.mjs";
|
|
7
|
+
|
|
8
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const VENDORED = ["skills", "agents", "docs"];
|
|
10
|
+
|
|
11
|
+
function arg(name, fallback) {
|
|
12
|
+
const index = process.argv.indexOf(name);
|
|
13
|
+
return index === -1 ? fallback : process.argv[index + 1];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const upstreamDir = resolve(arg("--upstream", "/tmp/upstream-plugins"));
|
|
17
|
+
const specPath = resolve(arg("--spec", join(ROOT, "scripts", "pi-deltas.json")));
|
|
18
|
+
const treeRoot = resolve(arg("--root", ROOT));
|
|
19
|
+
const jsonPath = arg("--json", "");
|
|
20
|
+
const acceptOverrides = process.argv.includes("--accept-overrides");
|
|
21
|
+
|
|
22
|
+
if (!existsSync(join(upstreamDir, ".git"))) {
|
|
23
|
+
console.error(`upstream checkout missing at ${upstreamDir}`);
|
|
24
|
+
console.error("clone it first: git clone --depth 400 https://github.com/cursor/plugins.git /tmp/upstream-plugins");
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const spec = JSON.parse(readFileSync(specPath, "utf8"));
|
|
29
|
+
const problems = validateSpec(spec);
|
|
30
|
+
if (problems.length > 0) {
|
|
31
|
+
console.error("pi-deltas.json is invalid:");
|
|
32
|
+
for (const problem of problems) console.error(` ${problem}`);
|
|
33
|
+
process.exit(2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const upstream = readUpstreamTree(upstreamDir, spec.upstream.ref, VENDORED);
|
|
37
|
+
const version = upstreamVersion(upstreamDir, spec.upstream.ref);
|
|
38
|
+
|
|
39
|
+
if (acceptOverrides) {
|
|
40
|
+
let updated = 0;
|
|
41
|
+
for (const rule of spec.rules) {
|
|
42
|
+
if (rule.kind !== "override") continue;
|
|
43
|
+
const source = upstream.get(rule.path);
|
|
44
|
+
if (source === undefined) {
|
|
45
|
+
console.error(`override ${rule.id}: ${rule.path} is not present upstream; declare it as an add instead`);
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
const next = hashContent(source); if (next !== rule.basedOn) {
|
|
49
|
+
rule.basedOn = next;
|
|
50
|
+
updated++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
|
|
54
|
+
console.log(`re-baselined ${updated} override(s) in ${specPath}`);
|
|
55
|
+
console.log("review that you merged the upstream changes into each override before committing.");
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function* walk(dir) {
|
|
60
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
61
|
+
const path = join(dir, entry.name);
|
|
62
|
+
if (entry.isDirectory()) yield* walk(path);
|
|
63
|
+
else if (entry.isFile()) yield path;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const localFiles = new Set();
|
|
68
|
+
for (const subtree of VENDORED) {
|
|
69
|
+
const dir = join(treeRoot, subtree);
|
|
70
|
+
if (!existsSync(dir)) continue;
|
|
71
|
+
for (const file of walk(dir)) localFiles.add(relative(treeRoot, file));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const excludeGlobs = globsOf(spec, "exclude");
|
|
75
|
+
const addGlobs = globsOf(spec, "add");
|
|
76
|
+
const match = [];
|
|
77
|
+
const delta = [];
|
|
78
|
+
const overrides = [];
|
|
79
|
+
const unexplained = [];
|
|
80
|
+
const excluded = [];
|
|
81
|
+
const added = [];
|
|
82
|
+
const usedRuleIds = new Set();
|
|
83
|
+
|
|
84
|
+
for (const [path, upstreamContent] of upstream) {
|
|
85
|
+
if (matchesAny(excludeGlobs, path)) {
|
|
86
|
+
excluded.push(path);
|
|
87
|
+
usedRuleIds.add(spec.rules.find((rule) => rule.kind === "exclude" && matchGlob(rule.path, path)).id);
|
|
88
|
+
if (localFiles.has(path)) unexplained.push({ path, reason: "declared exclude but the file is present locally" });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const override = overrideFor(spec, path);
|
|
92
|
+
if (override) {
|
|
93
|
+
usedRuleIds.add(override.id);
|
|
94
|
+
const localPath = join(treeRoot, path);
|
|
95
|
+
if (!existsSync(localPath)) {
|
|
96
|
+
unexplained.push({ path, reason: `override ${override.id} declares this file, but it is missing locally` });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const actual = hashContent(upstreamContent);
|
|
100
|
+
if (actual === override.basedOn) {
|
|
101
|
+
overrides.push({ path, rules: [override.id] });
|
|
102
|
+
} else {
|
|
103
|
+
unexplained.push({
|
|
104
|
+
path,
|
|
105
|
+
reason: `override ${override.id} is stale: upstream content changed since it was baselined. Merge the upstream edits into the local file, then run --accept-overrides.`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const localPath = join(treeRoot, path);
|
|
111
|
+
if (!existsSync(localPath) || !statSync(localPath).isFile()) {
|
|
112
|
+
unexplained.push({ path, reason: "present upstream, missing locally, not declared as an exclude" });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (isBinary(upstreamContent)) {
|
|
116
|
+
if (readFileSync(localPath).equals(upstreamContent)) match.push(path);
|
|
117
|
+
else unexplained.push({ path, reason: "binary file differs and rules cannot explain it" });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const upstreamText = upstreamContent.toString("utf8");
|
|
121
|
+
const { content, applied } = applyRules(upstreamText, path, spec.rules);
|
|
122
|
+
for (const id of applied) usedRuleIds.add(id);
|
|
123
|
+
const local = readFileSync(localPath, "utf8");
|
|
124
|
+
if (local === content) {
|
|
125
|
+
if (applied.length > 0) delta.push({ path, rules: applied });
|
|
126
|
+
else match.push(path);
|
|
127
|
+
} else {
|
|
128
|
+
unexplained.push({ path, reason: "local content differs and no rule explains it" });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const path of localFiles) {
|
|
133
|
+
if (upstream.has(path)) continue;
|
|
134
|
+
const add = spec.rules.find((rule) => rule.kind === "add" && matchGlob(rule.path, path));
|
|
135
|
+
if (add) {
|
|
136
|
+
usedRuleIds.add(add.id);
|
|
137
|
+
added.push(path);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
unexplained.push({ path, reason: "present locally, absent upstream, not declared as an add" });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const staleRules = spec.rules
|
|
144
|
+
.filter((rule) => rule.kind === "substitute" || rule.kind === "insert")
|
|
145
|
+
.filter((rule) => !usedRuleIds.has(rule.id))
|
|
146
|
+
.map((rule) => rule.id);
|
|
147
|
+
const staleStructural = spec.rules
|
|
148
|
+
.filter((rule) => rule.kind === "exclude" || rule.kind === "add" || rule.kind === "override")
|
|
149
|
+
.filter((rule) => !usedRuleIds.has(rule.id))
|
|
150
|
+
.map((rule) => rule.id);
|
|
151
|
+
|
|
152
|
+
const report = {
|
|
153
|
+
generatedAt: new Date().toISOString(),
|
|
154
|
+
tree: treeRoot,
|
|
155
|
+
upstream: { dir: upstreamDir, ref: spec.upstream.ref, version },
|
|
156
|
+
counts: {
|
|
157
|
+
upstreamFiles: upstream.size,
|
|
158
|
+
match: match.length,
|
|
159
|
+
delta: delta.length,
|
|
160
|
+
override: overrides.length,
|
|
161
|
+
excluded: excluded.length,
|
|
162
|
+
added: added.length,
|
|
163
|
+
unexplained: unexplained.length,
|
|
164
|
+
staleRules: staleRules.length + staleStructural.length,
|
|
165
|
+
},
|
|
166
|
+
delta,
|
|
167
|
+
overrides,
|
|
168
|
+
excluded,
|
|
169
|
+
added,
|
|
170
|
+
unexplained: unexplained.map(({ path, reason }) => ({ path, reason })),
|
|
171
|
+
staleRules: [...staleRules, ...staleStructural],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
console.log(`upstream ${version} @ ${spec.upstream.ref.slice(0, 12)} (${upstreamDir})`);
|
|
175
|
+
console.log(` match (verbatim upstream): ${match.length}`);
|
|
176
|
+
console.log(` pi-delta (rule-explained): ${delta.length}`);
|
|
177
|
+
console.log(` override (ours, baselined): ${overrides.length}`);
|
|
178
|
+
console.log(` excluded (declared): ${excluded.length}`);
|
|
179
|
+
console.log(` added (ours only): ${added.length}`);
|
|
180
|
+
console.log(` unexplained: ${unexplained.length}`);
|
|
181
|
+
console.log(` stale rules: ${report.counts.staleRules}`);
|
|
182
|
+
if (delta.length > 0) {
|
|
183
|
+
console.log("\nrule-explained deltas:");
|
|
184
|
+
for (const entry of delta) console.log(` ${entry.path} <- ${entry.rules.join(", ")}`);
|
|
185
|
+
}
|
|
186
|
+
if (overrides.length > 0) {
|
|
187
|
+
console.log("\noverrides (ours, baselined against upstream):");
|
|
188
|
+
for (const entry of overrides) console.log(` ${entry.path} <- ${entry.rules.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
if (unexplained.length > 0) {
|
|
191
|
+
console.log("\nUNEXPLAINED (declare a rule in scripts/pi-deltas.json, or let the sync vendor it):");
|
|
192
|
+
for (const entry of unexplained) console.log(` ${entry.path}: ${entry.reason}`);
|
|
193
|
+
}
|
|
194
|
+
if (report.counts.staleRules > 0) {
|
|
195
|
+
console.log("\nSTALE RULES (upstream moved; retire or retarget these):");
|
|
196
|
+
for (const id of report.staleRules) console.log(` ${id}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (jsonPath) {
|
|
200
|
+
writeFileSync(resolve(jsonPath), `${JSON.stringify(report, null, 2)}\n`);
|
|
201
|
+
console.log(`\nreport: ${resolve(jsonPath)}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const ok = unexplained.length === 0 && report.counts.staleRules === 0;
|
|
205
|
+
console.log(`\n${ok ? "PASS" : "FAIL"} (scripts/verify-sync.mjs)`);
|
|
206
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -10,7 +10,7 @@ Design before implementing. Sketch types, function signatures, class shapes, and
|
|
|
10
10
|
|
|
11
11
|
## Start
|
|
12
12
|
|
|
13
|
-
Open a todolist with one entry per phase before starting.
|
|
13
|
+
Open a todolist with one entry per phase before starting.
|
|
14
14
|
|
|
15
15
|
1. Ground
|
|
16
16
|
2. Sketch
|
|
@@ -20,7 +20,7 @@ Open a todolist with one entry per phase before starting. Autonomous mode withou
|
|
|
20
20
|
|
|
21
21
|
## Phase A: Ground the problem
|
|
22
22
|
|
|
23
|
-
Build a real mental model of every system the new code touches. Run the **how** skill over the relevant subsystems.
|
|
23
|
+
Build a real mental model of every system the new code touches. Run the **how** skill over the relevant subsystems.
|
|
24
24
|
|
|
25
25
|
Naming a file isn't grounding. Produce the traced model `how` prescribes. If the design redefines ownership or layering, also run the **why** skill on the existing shape so the rationale becomes a constraint, not a guess.
|
|
26
26
|
|
|
@@ -28,9 +28,9 @@ Skip Phase A only when the work is genuinely greenfield with no surrounding syst
|
|
|
28
28
|
|
|
29
29
|
## Phase B: Sketch
|
|
30
30
|
|
|
31
|
-
Run the **arena** skill with the design-sketch task and the Phase A grounding artifacts. Pass `references/runner-prompt.md` as each runner's prompt. Each candidate produces a design package shaped per `references/rationale-template.md
|
|
31
|
+
Run the **arena** skill with the design-sketch task and the Phase A grounding artifacts. Pass `references/runner-prompt.md` as each runner's prompt. Each candidate produces a design package shaped per `references/rationale-template.md`.
|
|
32
32
|
|
|
33
|
-
Use your configured architect runners (
|
|
33
|
+
Use your configured architect runners (default: inherit, the parent session model).
|
|
34
34
|
|
|
35
35
|
Design it twice. Require at least two structurally distinct candidates before synthesis, even when the first looks sufficient. This is the **exhaust-the-design-space** principle skill made concrete. Whole-shape alternatives, not point fixes inside one shape.
|
|
36
36
|
|
|
@@ -46,7 +46,7 @@ Default: proceed directly to implementation with the synthesized design. No huma
|
|
|
46
46
|
|
|
47
47
|
Opt in to a checkpoint when the invoker explicitly asks: "/architect with checkpoint," "stop and show me before implementing," or similar. Then surface the synthesized design and pause for sign-off.
|
|
48
48
|
|
|
49
|
-
The synthesis can ship as its own commit either way
|
|
49
|
+
The synthesis can ship as its own commit either way, as the "scaffold first" mode of the **foundational-thinking** principle skill. Planned and scoped breakage during fill-in is fine, per the **outcome-oriented-execution** principle skill. For adversarial pressure on the design before implementing, run the **interrogate** skill on the synthesized sketch.
|
|
50
50
|
|
|
51
51
|
If the human pushes back on the shape (in a checkpoint or after the fact), treat that as Phase A evidence. Re-ground and re-run Phase B before writing more code.
|
|
52
52
|
|
|
@@ -54,7 +54,7 @@ If the human pushes back on the shape (in a checkpoint or after the fact), treat
|
|
|
54
54
|
|
|
55
55
|
Replace `not implemented` bodies with code, pseudocode with logic. The synthesized sketch is the contract.
|
|
56
56
|
|
|
57
|
-
Deviations from the sketch are signal worth surfacing, not friction to absorb silently. If a function needs a parameter the sketch didn't anticipate, ask whether the sketch was wrong, the requirement was missed, or the implementation is overreaching.
|
|
57
|
+
Deviations from the sketch are signal worth surfacing, not friction to absorb silently. If a function needs a parameter the sketch didn't anticipate, ask whether the sketch was wrong, the requirement was missed, or the implementation is overreaching.
|
|
58
58
|
|
|
59
59
|
## Phase E: Scrap when the architecture is wrong
|
|
60
60
|
|
|
@@ -67,17 +67,17 @@ The signal is a *pattern*, not single instances. Tells:
|
|
|
67
67
|
- Types that need escape hatches (`any`, casts, optional fields always set in practice) to compile.
|
|
68
68
|
- The "we need a lock" reflex when the sketch said the state wasn't shared.
|
|
69
69
|
- Callers having to know the abstraction's internal rules to use it.
|
|
70
|
-
- Two or more independent Phase D deviations of the same shape across the implementation.
|
|
70
|
+
- Two or more independent Phase D deviations of the same shape across the implementation.
|
|
71
71
|
|
|
72
|
-
Use judgment. A few edge cases don't condemn an architecture. Some problems are legitimately complex
|
|
72
|
+
Use judgment. A few edge cases don't condemn an architecture. Some problems are legitimately complex. Complexity in the data is not complexity in the design.
|
|
73
73
|
|
|
74
74
|
When you scrap:
|
|
75
75
|
|
|
76
|
-
1. Re-run the **how** skill over what's been built.
|
|
76
|
+
1. Re-run the **how** skill over what's been built.
|
|
77
77
|
2. Redesign as if the new constraints had been day-one assumptions, per redesign-from-first-principles.
|
|
78
78
|
3. Subtract before adding, per the **subtract-before-you-add** principle skill. The new sketch should be smaller than the old one before it grows.
|
|
79
79
|
4. Return to Phase B and re-run arena.
|
|
80
80
|
|
|
81
81
|
## Outputs
|
|
82
82
|
|
|
83
|
-
The caller's usage is written first and the type sketch derived from it. One file with new types and signatures for small changes
|
|
83
|
+
The caller's usage is written first and the type sketch derived from it. One file with new types and signatures for small changes. Module map plus type definitions for larger work. The rationale ships alongside, shaped per `references/rationale-template.md`, including the usage sketch and the synthesis decision.
|
|
@@ -8,11 +8,11 @@ The prose that ships alongside the type sketch. One page. Sentence-case headings
|
|
|
8
8
|
|
|
9
9
|
## Usage (caller's view)
|
|
10
10
|
|
|
11
|
-
*Write this first, before the type sketch. Show the README or quickstart the consumer reads, plus two or three realistic call sites in their own code. What they import, what they call, what comes back. The type sketch in [Shape](#shape) is derived from this. The two must agree
|
|
11
|
+
*Write this first, before the type sketch. Show the README or quickstart the consumer reads, plus two or three realistic call sites in their own code. What they import, what they call, what comes back. The type sketch in [Shape](#shape) is derived from this. The two must agree. When they diverge, reconcile the sketch to the usage, not the reverse. The caller's experience is the spec. The types serve it.*
|
|
12
12
|
|
|
13
13
|
## Shape
|
|
14
14
|
|
|
15
|
-
*The recommended architecture. Data structures first
|
|
15
|
+
*The recommended architecture. Data structures first. Then how data flows through the signatures. Name the load-bearing decisions. State which invariants are encoded in types, where validation lives, and what the system deliberately does not do. Judge interface depth explicitly. State what complexity the public surface hides, what remains exposed to callers, and why the interface is no larger than needed. Cite the principle behind each decision (e.g., `per boundary-discipline`). Don't restate it.*
|
|
16
16
|
|
|
17
17
|
## Synthesis decision
|
|
18
18
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Architect runner prompt
|
|
2
2
|
|
|
3
|
-
The orchestrator passes this file through to every parallel candidate runner during Phase B and fills in the variable inputs around it: the task, the Phase A grounding artifacts, the isolated working directory, and the path to write outputs. The working directory is a git worktree when available, otherwise a per-runner subdirectory under the sketch dir
|
|
3
|
+
The orchestrator passes this file through to every parallel candidate runner during Phase B and fills in the variable inputs around it: the task, the Phase A grounding artifacts, the isolated working directory, and the path to write outputs. The working directory is a git worktree when available, otherwise a per-runner subdirectory under the sketch dir. What matters is independence between candidates.
|
|
4
4
|
|
|
5
|
-
You are producing one candidate design in architect's parallel exploration. Read the **architect** skill in full first
|
|
5
|
+
You are producing one candidate design in architect's parallel exploration. Read the **architect** skill in full first. That's the workflow you're inside. Output a candidate design package: type sketch, function signatures, module map, and prose rationale shaped per [`rationale-template.md`](rationale-template.md).
|
|
6
6
|
|
|
7
7
|
Apply the following discipline. The orchestrator compares candidates on these axes to pick a base.
|
|
8
8
|
|
|
9
|
-
- Caller's usage first. Write the README-style usage and two or three real call sites before the types, then derive the type sketch from them. The usage is the spec
|
|
10
|
-
- Data structures first. Get the core types right and the code becomes obvious. Trace each dominant access pattern through the proposed structure
|
|
11
|
-
- Interface depth. Compare the capability hidden behind the public surface relative to the size of that surface. Prefer a simple interface that pulls complexity into the callee, even when the implementation becomes less simple. Do not put transport or wire types on the public
|
|
9
|
+
- Caller's usage first. Write the README-style usage and two or three real call sites before the types, then derive the type sketch from them. The usage is the spec. The two must agree, so reconcile the sketch to the usage, not the reverse.
|
|
10
|
+
- Data structures first. Get the core types right and the code becomes obvious. Trace each dominant access pattern through the proposed structure. If the answer is "we'll add a map / index / cache later," the structure is wrong.
|
|
11
|
+
- Interface depth. Compare the capability hidden behind the public surface relative to the size of that surface. Prefer a simple interface that pulls complexity into the callee, even when the implementation becomes less simple. Do not put transport or wire types on the public API. Parse into domain types behind the interface.
|
|
12
12
|
- Shared state: if two actors might both write, ask "what happens?" If the answer isn't "nothing," default to per-actor state with a merge at the read boundary, per the **separate-before-serializing-shared-state** principle skill.
|
|
13
13
|
- Make boundaries visible. `not implemented` errors for bodies, `// TODO` pseudocode for tricky logic, doc comments stating intent and invariants. A reader should trace data from input to output by reading types and signatures alone.
|
|
14
14
|
- Encode invariants in types: hard-to-misuse types > runtime checks > prose comments, per the **encode-lessons-in-structure** principle skill.
|
|
15
|
-
- Validate at boundaries, trust types inside, per the **boundary-discipline** principle skill. Business logic as pure functions
|
|
15
|
+
- Validate at boundaries, trust types inside, per the **boundary-discipline** principle skill. Business logic as pure functions. The shell stays thin.
|
|
16
16
|
- Single source of truth per invariant. Derive instead of sync.
|
|
17
17
|
- Idempotent state transitions where applicable, per the **make-operations-idempotent** principle skill. Ask what happens if the operation runs twice or crashes halfway.
|
|
18
18
|
- Short call chains. If tracing the flow needs more than three files, flatten the hierarchy, per the **laziness-protocol** and **minimize-reader-load** principle skills.
|
|
19
19
|
|
|
20
|
-
You are one of several runners, each on a different model. Produce the best design your model can make
|
|
20
|
+
You are one of several runners, each on a different model. Produce the best design your model can make. Don't hedge against the others. Differences between candidates are the signal used to pick a base and graft. Converging on a safe-looking middle defeats the exploration.
|
package/skills/arena/SKILL.md
CHANGED
|
@@ -10,7 +10,7 @@ Fan out N parallel attempts at the same task. Read every candidate end to end. P
|
|
|
10
10
|
|
|
11
11
|
## Start
|
|
12
12
|
|
|
13
|
-
Open a todolist with one entry per phase before launching anything.
|
|
13
|
+
Open a todolist with one entry per phase before launching anything.
|
|
14
14
|
|
|
15
15
|
1. Frame
|
|
16
16
|
2. Fan out
|
|
@@ -21,32 +21,32 @@ Open a todolist with one entry per phase before launching anything. The arena ru
|
|
|
21
21
|
|
|
22
22
|
## Phase A: Frame
|
|
23
23
|
|
|
24
|
-
The N candidates will receive the same prompt, so the prompt is the contract.
|
|
24
|
+
The N candidates will receive the same prompt, so the prompt is the contract.
|
|
25
25
|
|
|
26
26
|
1. State the artifact each candidate is producing.
|
|
27
|
-
2. Derive the rubric. State what success looks like for *this* task, then turn it into 3-6 concrete gradeable criteria.
|
|
28
|
-
3. Pick the runners. Use
|
|
29
|
-
4. Assign output paths. Each candidate writes to its own location (a git worktree where possible, otherwise `/tmp/arena-<slug>/candidate-<n>/`)
|
|
27
|
+
2. Derive the rubric. State what success looks like for *this* task, then turn it into 3-6 concrete gradeable criteria. The rubric is the picker's tool in Phase D. Candidates only see the task.
|
|
28
|
+
3. Pick the runners. Use `arena runners` from the `subagents.*` keys in Pi settings when present. Otherwise default to one each on your configured panel, one model family per runner. Spawn more when the arena covers multiple design directions. Same model N times when the work is generation-bound rather than judgment-sensitive.
|
|
29
|
+
4. Assign output paths. Each candidate writes to its own location (a git worktree where possible, otherwise `/tmp/arena-<slug>/candidate-<n>/`), per the **separate-before-serializing-shared-state** principle skill.
|
|
30
30
|
|
|
31
31
|
## Phase B: Fan out
|
|
32
32
|
|
|
33
33
|
Spawn all N subagents in one message with `run_in_background: true`, each with the task, the path to the shared grounding, its own output path, and instructions to produce both the artifact and a short rationale.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Each rationale names the alternatives the candidate considered and what it rejected.
|
|
36
36
|
|
|
37
37
|
If a candidate fails to produce output, proceed with N-1 and note the dropout in the synthesis record.
|
|
38
38
|
|
|
39
39
|
## Phase C: Cross-judge
|
|
40
40
|
|
|
41
|
-
After all Phase B candidates complete, choose one model from the `arena cross-judge pool` in Pi settings when present. Otherwise use
|
|
41
|
+
After all Phase B candidates complete, choose one model from the `arena cross-judge pool` in the `subagents.*` keys in Pi settings when present. Otherwise use your configured panel, one model family per runner. Prefer a different model family from the parent's. Spawn one readonly judge subagent on that model. It sees the rubric and the candidates by path label, scores each criterion, and recommends a base with rationale. It runs in parallel with the parent's reading in Phase D, not with the candidates themselves. Don't spawn the judge while candidates are still writing.
|
|
42
42
|
|
|
43
43
|
## Phase D: Pick a base
|
|
44
44
|
|
|
45
|
-
Read every candidate end to end before picking.
|
|
45
|
+
Read every candidate end to end before picking.
|
|
46
46
|
|
|
47
47
|
Score each candidate against the rubric criterion by criterion, not on holistic feel. Compare against the cross-judge. Agreement on the base confirms the pick. Disagreement means one of you is biased or the rubric was ambiguous. Read both rationales before deciding.
|
|
48
48
|
|
|
49
|
-
Pick the base on which candidate a future maintainer can extend most easily without breaking invariants. Prefer the cleaner boundary or smaller
|
|
49
|
+
Pick the base on which candidate a future maintainer can extend most easily without breaking invariants. Prefer the cleaner boundary or smaller API when two feel tied, per the Laziness Protocol.
|
|
50
50
|
|
|
51
51
|
Record the pick and the reason in a short synthesis note alongside the base artifact, including the cross-judge's verdict.
|
|
52
52
|
|
|
@@ -56,13 +56,13 @@ Walk each losing candidate once more and identify what is worth porting into the
|
|
|
56
56
|
|
|
57
57
|
Fold each graft in by hand, per the **redesign-from-first-principles** principle skill. Don't paste mechanically. The result has to remain coherent under one mental model.
|
|
58
58
|
|
|
59
|
-
Record what was grafted, from which candidate, and what was rejected and why.
|
|
59
|
+
Record what was grafted, from which candidate, and what was rejected and why.
|
|
60
60
|
|
|
61
61
|
When N candidates converge on the same shape, that is a strong agreement signal. Note the convergence in the record and ship the consensus shape. No graft is needed. When N candidates wildly diverge, Phase A was under-specified. Reframe and re-run rather than averaging the divergence.
|
|
62
62
|
|
|
63
63
|
## Phase F: Verify
|
|
64
64
|
|
|
65
|
-
The synthesized artifact has to hold up under the same scrutiny as any other output, per the **prove-it-works** principle skill.
|
|
65
|
+
The synthesized artifact has to hold up under the same scrutiny as any other output, per the **prove-it-works** principle skill.
|
|
66
66
|
|
|
67
67
|
If verification surfaces a problem the arena did not catch, either Phase A was wrong (re-frame and re-run) or one candidate caught it and you missed the graft (go back to Phase E). Don't paper over.
|
|
68
68
|
|