ultimate-pi 0.20.0 → 0.22.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 +68 -2
- package/.agents/skills/harness-git-commit/SKILL.md +72 -0
- package/.agents/skills/harness-governor/SKILL.md +2 -2
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
- package/.agents/skills/harness-plan/SKILL.md +13 -11
- package/.agents/skills/harness-review/SKILL.md +1 -1
- package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
- package/.agents/skills/sentrux/SKILL.md +4 -2
- package/.agents/skills/wiki-save/SKILL.md +1 -1
- package/.pi/PACKAGING.md +6 -0
- package/.pi/SYSTEM.md +21 -3
- package/.pi/agents/harness/ls-lint-steward.md +49 -0
- package/.pi/agents/harness/planning/decompose.md +4 -4
- package/.pi/agents/harness/reviewing/evaluator.md +1 -1
- package/.pi/agents/harness/running/executor.md +1 -1
- package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
- package/.pi/agents/pi-pi/prompt-expert.md +17 -2
- package/.pi/auto-commit.json +9 -2
- package/.pi/extensions/debate-orchestrator.ts +3 -0
- package/.pi/extensions/harness-anchored-edit.ts +7 -9
- package/.pi/extensions/harness-ask-user.ts +13 -34
- package/.pi/extensions/harness-debate-tools.ts +43 -4
- package/.pi/extensions/harness-live-widget.ts +28 -19
- package/.pi/extensions/harness-run-context.ts +278 -115
- package/.pi/extensions/harness-web-tools.ts +598 -471
- package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
- package/.pi/extensions/observation-bus.ts +4 -0
- package/.pi/extensions/policy-gate.ts +270 -229
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/soundboard.ts +48 -48
- package/.pi/harness/README.md +4 -0
- package/.pi/harness/agents.manifest.json +15 -7
- package/.pi/harness/agents.policy.yaml +49 -82
- package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
- package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
- package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
- package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
- package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
- package/.pi/harness/docs/adrs/README.md +5 -0
- package/.pi/harness/docs/practice-map.md +10 -5
- package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
- package/.pi/harness/evolution/self-healing-rules.json +16 -0
- package/.pi/harness/ls-lint/naming.manifest.json +128 -0
- package/.pi/harness/sentrux/architecture.manifest.json +1 -1
- package/.pi/harness/specs/auto-commit.schema.json +63 -0
- package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
- package/.pi/harness/specs/naming-manifest.schema.json +54 -0
- package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
- package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
- package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
- package/.pi/harness/specs/sentrux-report.schema.json +119 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
- package/.pi/lib/agents-policy.d.mts +26 -51
- package/.pi/lib/agents-policy.mjs +41 -28
- package/.pi/lib/agt/build-evaluation-context.ts +136 -64
- package/.pi/lib/ask-user/constants.mjs +3 -0
- package/.pi/lib/ask-user/constants.ts +4 -0
- package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
- package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
- package/.pi/lib/ask-user/dialog.ts +2 -314
- package/.pi/lib/ask-user/fallback.ts +2 -78
- package/.pi/lib/ask-user/format.ts +85 -0
- package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
- package/.pi/lib/ask-user/index.ts +114 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
- package/.pi/lib/ask-user/policy.mjs +43 -0
- package/.pi/lib/ask-user/policy.ts +104 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
- package/.pi/lib/ask-user/presenters/headless.ts +131 -0
- package/.pi/lib/ask-user/presenters/select.ts +60 -0
- package/.pi/lib/ask-user/presenters/tui.ts +373 -0
- package/.pi/lib/ask-user/presenters/types.ts +13 -0
- package/.pi/lib/ask-user/render.ts +40 -9
- package/.pi/lib/ask-user/schema.ts +66 -13
- package/.pi/lib/ask-user/types.ts +60 -3
- package/.pi/lib/ask-user/validate-core.mjs +193 -7
- package/.pi/lib/ask-user/validate.ts +53 -34
- package/.pi/lib/harness-anchored-edit/package.json +3 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -21
- package/.pi/lib/harness-auto-commit-config.mjs +321 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +241 -108
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
- package/.pi/lib/harness-repair-brief.ts +84 -25
- package/.pi/lib/harness-run-context.ts +42 -52
- package/.pi/lib/harness-sentrux-parse.mjs +272 -0
- package/.pi/lib/harness-sentrux-root.mjs +78 -0
- package/.pi/lib/harness-slash-completions.ts +116 -0
- package/.pi/lib/harness-spawn-topology.ts +121 -87
- package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
- package/.pi/lib/harness-subagents-bridge.ts +4 -1
- package/.pi/lib/harness-ui-state.ts +95 -48
- package/.pi/lib/plan-approval/dialog.ts +5 -0
- package/.pi/lib/plan-approval/validate.ts +1 -1
- package/.pi/lib/plan-approval-readiness.ts +32 -0
- package/.pi/lib/plan-debate-gate.ts +154 -114
- package/.pi/lib/plan-task-clarification.ts +158 -0
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-ls-lint-steward.md +43 -0
- package/.pi/prompts/harness-plan.md +58 -8
- package/.pi/prompts/harness-review.md +40 -6
- package/.pi/prompts/harness-run.md +33 -11
- package/.pi/prompts/harness-setup.md +72 -3
- package/.pi/prompts/harness-steer.md +2 -1
- package/.pi/prompts/wiki-save.md +5 -4
- package/.pi/scripts/README.md +8 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
- package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
- package/.pi/scripts/harness-cli-verify.sh +47 -0
- package/.pi/scripts/harness-git-churn.mjs +77 -0
- package/.pi/scripts/harness-git-commit.mjs +173 -0
- package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
- package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
- package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
- package/.pi/scripts/harness-sentrux-report.mjs +256 -0
- package/.pi/scripts/harness-verify.mjs +288 -125
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +1 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +25 -0
- package/README.md +13 -4
- package/package.json +5 -1
- package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sync .ls-lint.yml from .pi/harness/ls-lint/naming.manifest.json.
|
|
4
|
+
* Preserves user content outside the harness managed block.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node .pi/scripts/ls-lint-rules-sync.mjs [--check] [--force] [PROJECT_ROOT]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile, writeFile, mkdir, access } from "node:fs/promises";
|
|
10
|
+
import { constants } from "node:fs";
|
|
11
|
+
import { join, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { spawn, execSync } from "node:child_process";
|
|
15
|
+
|
|
16
|
+
const UP_PKG = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17
|
+
/** Target project root (consumer repo). Default: process.cwd(). */
|
|
18
|
+
const PROJECT_ROOT =
|
|
19
|
+
process.argv.find((a, i) => i >= 2 && !a.startsWith("-")) || process.cwd();
|
|
20
|
+
const MANIFEST = join(
|
|
21
|
+
PROJECT_ROOT,
|
|
22
|
+
".pi",
|
|
23
|
+
"harness",
|
|
24
|
+
"ls-lint",
|
|
25
|
+
"naming.manifest.json",
|
|
26
|
+
);
|
|
27
|
+
const MANIFEST_TEMPLATE = join(
|
|
28
|
+
UP_PKG,
|
|
29
|
+
".pi",
|
|
30
|
+
"harness",
|
|
31
|
+
"ls-lint",
|
|
32
|
+
"naming.manifest.json",
|
|
33
|
+
);
|
|
34
|
+
const RULES_PATH = join(PROJECT_ROOT, ".ls-lint.yml");
|
|
35
|
+
const META_PATH = join(PROJECT_ROOT, ".ls-lint", ".harness-naming-meta.json");
|
|
36
|
+
|
|
37
|
+
const MANAGED_START = "# --- harness:managed:start ---";
|
|
38
|
+
const MANAGED_END = "# --- harness:managed:end ---";
|
|
39
|
+
|
|
40
|
+
function fail(msg) {
|
|
41
|
+
console.error(`ls-lint-rules-sync: ${msg}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hashContent(text) {
|
|
46
|
+
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function yamlScalar(value) {
|
|
50
|
+
const s = String(value);
|
|
51
|
+
if (/^[a-zA-Z0-9_.|]+$/.test(s) && !s.includes("regex:")) {
|
|
52
|
+
return s;
|
|
53
|
+
}
|
|
54
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function renderRulesBlock(rules, indent) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
const pad = " ".repeat(indent);
|
|
60
|
+
for (const [key, value] of Object.entries(rules)) {
|
|
61
|
+
lines.push(`${pad}${key}: ${yamlScalar(value)}`);
|
|
62
|
+
}
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function renderManagedBlock(manifest) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
lines.push(MANAGED_START);
|
|
69
|
+
lines.push(
|
|
70
|
+
"# Generated from .pi/harness/ls-lint/naming.manifest.json",
|
|
71
|
+
);
|
|
72
|
+
lines.push(`# Project: ${manifest.project ?? "unknown"}`);
|
|
73
|
+
lines.push(`# Schema: ${manifest.schema_version}`);
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push("ls:");
|
|
76
|
+
|
|
77
|
+
const globalRules = manifest.global_rules ?? {};
|
|
78
|
+
for (const [key, value] of Object.entries(globalRules)) {
|
|
79
|
+
lines.push(` ${key}: ${yamlScalar(value)}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const scoped of manifest.scoped_rules ?? []) {
|
|
83
|
+
const pathKey = scoped.path;
|
|
84
|
+
const pathYaml = /^[a-zA-Z_][\w/-]*$/.test(pathKey)
|
|
85
|
+
? pathKey
|
|
86
|
+
: yamlScalar(pathKey);
|
|
87
|
+
lines.push(` ${pathYaml}:`);
|
|
88
|
+
lines.push(renderRulesBlock(scoped.rules ?? {}, 4));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push("ignore:");
|
|
93
|
+
for (const item of manifest.ignores ?? []) {
|
|
94
|
+
lines.push(` - ${yamlScalar(item)}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lines.push(MANAGED_END);
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function mergeRules(existing, managedBlock) {
|
|
102
|
+
const header = `# ls-lint — ${new Date().toISOString().slice(0, 10)}
|
|
103
|
+
# Docs: https://ls-lint.org/
|
|
104
|
+
# Sync: node $UP_PKG/.pi/scripts/ls-lint-rules-sync.mjs --force (see .pi/scripts/README.md for UP_PKG) or /harness-ls-lint-sync in pi
|
|
105
|
+
#
|
|
106
|
+
# Custom rules: add YAML below the managed block; they are preserved on sync.
|
|
107
|
+
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
if (!existing || !existing.includes(MANAGED_START)) {
|
|
111
|
+
return `${header}${managedBlock}\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const start = existing.indexOf(MANAGED_START);
|
|
115
|
+
const end = existing.indexOf(MANAGED_END);
|
|
116
|
+
if (start === -1 || end === -1 || end < start) {
|
|
117
|
+
return `${header}${managedBlock}\n`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const before = existing.slice(0, start);
|
|
121
|
+
const after = existing.slice(end + MANAGED_END.length);
|
|
122
|
+
return `${before}${managedBlock}${after}`.replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function fileExists(path) {
|
|
126
|
+
try {
|
|
127
|
+
await access(path, constants.R_OK);
|
|
128
|
+
return true;
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function countViolations(output) {
|
|
135
|
+
const matches = output.match(/\d+\s+violations?/i);
|
|
136
|
+
if (matches) {
|
|
137
|
+
const n = Number.parseInt(matches[0], 10);
|
|
138
|
+
if (!Number.isNaN(n)) return n;
|
|
139
|
+
}
|
|
140
|
+
if (/no\s+violations/i.test(output) || /0\s+violations/i.test(output)) {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
const lineMatches = output.match(/✖|✗|error|violation/gi);
|
|
144
|
+
return lineMatches ? lineMatches.length : 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function lintPathEnv() {
|
|
148
|
+
const extra = [
|
|
149
|
+
process.env.PATH,
|
|
150
|
+
`${process.env.HOME || ""}/.local/bin`,
|
|
151
|
+
].filter(Boolean);
|
|
152
|
+
try {
|
|
153
|
+
const npmBin = execSync("npm prefix -g", { encoding: "utf-8" }).trim();
|
|
154
|
+
extra.push(`${npmBin}/bin`);
|
|
155
|
+
} catch {
|
|
156
|
+
/* ignore */
|
|
157
|
+
}
|
|
158
|
+
return { ...process.env, PATH: extra.join(":") };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function runLsLint() {
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
const child = spawn("ls-lint", [], {
|
|
164
|
+
cwd: PROJECT_ROOT,
|
|
165
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
166
|
+
env: lintPathEnv(),
|
|
167
|
+
});
|
|
168
|
+
let out = "";
|
|
169
|
+
child.stdout?.on("data", (d) => {
|
|
170
|
+
out += d.toString();
|
|
171
|
+
});
|
|
172
|
+
child.stderr?.on("data", (d) => {
|
|
173
|
+
out += d.toString();
|
|
174
|
+
});
|
|
175
|
+
child.on("close", (code) => {
|
|
176
|
+
resolve({ code: code ?? 1, out: out.trim() });
|
|
177
|
+
});
|
|
178
|
+
child.on("error", () => resolve({ code: 127, out: "ls-lint not installed" }));
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
const checkOnly = process.argv.includes("--check");
|
|
184
|
+
const force = process.argv.includes("--force");
|
|
185
|
+
const strict = process.argv.includes("--strict");
|
|
186
|
+
|
|
187
|
+
if (!(await fileExists(MANIFEST))) {
|
|
188
|
+
if (await fileExists(MANIFEST_TEMPLATE)) {
|
|
189
|
+
await mkdir(dirname(MANIFEST), { recursive: true });
|
|
190
|
+
await writeFile(
|
|
191
|
+
MANIFEST,
|
|
192
|
+
await readFile(MANIFEST_TEMPLATE, "utf-8"),
|
|
193
|
+
"utf-8",
|
|
194
|
+
);
|
|
195
|
+
console.log(
|
|
196
|
+
`ls-lint-rules-sync: seeded manifest from package -> ${MANIFEST}`,
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
fail(`missing manifest ${MANIFEST} (and no template in package)`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const manifestRaw = await readFile(MANIFEST, "utf-8");
|
|
204
|
+
const manifest = JSON.parse(manifestRaw);
|
|
205
|
+
const manifestHash = hashContent(manifestRaw);
|
|
206
|
+
const managedBlock = renderManagedBlock(manifest);
|
|
207
|
+
|
|
208
|
+
let existing = "";
|
|
209
|
+
if (await fileExists(RULES_PATH)) {
|
|
210
|
+
existing = await readFile(RULES_PATH, "utf-8");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let meta = { manifest_hash: null, synced_at: null };
|
|
214
|
+
if (await fileExists(META_PATH)) {
|
|
215
|
+
try {
|
|
216
|
+
meta = JSON.parse(await readFile(META_PATH, "utf-8"));
|
|
217
|
+
} catch {
|
|
218
|
+
/* ignore */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const unchanged =
|
|
223
|
+
meta.manifest_hash === manifestHash &&
|
|
224
|
+
(await fileExists(RULES_PATH)) &&
|
|
225
|
+
existing.includes(MANAGED_START);
|
|
226
|
+
|
|
227
|
+
if (unchanged && !force) {
|
|
228
|
+
console.log("ls-lint-rules-sync: .ls-lint.yml already up to date");
|
|
229
|
+
if (checkOnly) process.exit(0);
|
|
230
|
+
} else if (checkOnly) {
|
|
231
|
+
fail(
|
|
232
|
+
'.ls-lint.yml out of date — run node "$UP_PKG/.pi/scripts/ls-lint-rules-sync.mjs" --force (see .pi/scripts/README.md for UP_PKG)',
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
await mkdir(join(PROJECT_ROOT, ".ls-lint"), { recursive: true });
|
|
236
|
+
const next = mergeRules(existing, managedBlock);
|
|
237
|
+
await writeFile(RULES_PATH, next, "utf-8");
|
|
238
|
+
meta = {
|
|
239
|
+
manifest_hash: manifestHash,
|
|
240
|
+
synced_at: new Date().toISOString(),
|
|
241
|
+
manifest_path: ".pi/harness/ls-lint/naming.manifest.json",
|
|
242
|
+
};
|
|
243
|
+
await writeFile(META_PATH, `${JSON.stringify(meta, null, 2)}\n`, "utf-8");
|
|
244
|
+
console.log(`ls-lint-rules-sync: wrote ${RULES_PATH}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { code, out } = await runLsLint();
|
|
248
|
+
if (code === 127) {
|
|
249
|
+
console.log(
|
|
250
|
+
"ls-lint-rules-sync: ls-lint CLI not found — install via harness-setup §2.9",
|
|
251
|
+
);
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
if (code !== 0) {
|
|
255
|
+
console.warn(out || "ls-lint: violations (update manifest or fix paths)");
|
|
256
|
+
if (strict || checkOnly) process.exit(code);
|
|
257
|
+
process.exit(0);
|
|
258
|
+
}
|
|
259
|
+
console.log(out || "ls-lint: pass");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
main().catch((err) => {
|
|
263
|
+
console.error(err);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
});
|
package/.sentrux/rules.toml
CHANGED
package/AGENTS.md
CHANGED
|
@@ -37,6 +37,7 @@ Created: 2026-05-14
|
|
|
37
37
|
- `graphify update .` after significant code changes
|
|
38
38
|
- ast-grep (`sg`) is the default code search tool — use `sg -p 'pattern'` for structural search, never grep for code
|
|
39
39
|
- Non-API web: invoke **`web-retrieval`** skill (WRS tiers; default `tier=deep` with `web-query-expander` → `anglesFile`). CLI: `python3 "$UP_PKG/.pi/scripts/harness-web.py"`
|
|
40
|
+
- Git commits: invoke **`harness-git-commit`** skill — `node "$UP_PKG/.pi/scripts/harness-git-commit.mjs"` (config: `.pi/auto-commit.json`)
|
|
40
41
|
|
|
41
42
|
## graphify
|
|
42
43
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [v0.22.0] — 2026-05-27
|
|
6
|
+
|
|
7
|
+
### ✨ Features
|
|
8
|
+
|
|
9
|
+
- OSS Sentrux structured repair without Pro or MCP
|
|
10
|
+
|
|
11
|
+
### 📖 Documentation
|
|
12
|
+
|
|
13
|
+
- split repo docs by audience
|
|
14
|
+
|
|
15
|
+
### 🔧 Chores
|
|
16
|
+
|
|
17
|
+
- apply pending harness and ask-user updates
|
|
18
|
+
- remove obsolete PostHog plan latency dashboard doc
|
|
19
|
+
|
|
5
20
|
## [Unreleased]
|
|
6
21
|
|
|
7
22
|
### ✨ Features
|
|
@@ -9,6 +24,16 @@ All notable changes to this project are documented in this file.
|
|
|
9
24
|
- **Harness lens:** Integrate selected pi-lens capabilities through a harness-owned extension, store lens state under `.pi/harness/.lens`, and route lens findings through harness PostHog telemetry instead of standalone lens health/telemetry surfaces.
|
|
10
25
|
- **Graphify KB updater:** Productize conservative daily discovery/promotion with explicit repo/release taxonomy, allowlist source-class gates, operator review queue reporting, scheduler smoke validation, and safe Graphify refresh controls.
|
|
11
26
|
|
|
27
|
+
## [v0.21.0] — 2026-05-26
|
|
28
|
+
|
|
29
|
+
### ✨ Features
|
|
30
|
+
|
|
31
|
+
- **Harness:** Add ls-lint filename fitness (manifest, sync, steward agent, harness-verify) and plan-phase task clarification gate before execution.
|
|
32
|
+
|
|
33
|
+
### 🔧 Chores
|
|
34
|
+
|
|
35
|
+
- Add `agents-policy.d.mts` and hash-anchored edit typecheck fixes so `npm run check:ts` passes on main.
|
|
36
|
+
|
|
12
37
|
## [v0.20.0] — 2026-05-26
|
|
13
38
|
|
|
14
39
|
### ✨ Features
|
package/README.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
`ultimate-pi` adds a governed coding workflow to Pi: bootstrap the repo, plan with evidence, execute only against an approved PlanPacket, then run an independent review gate before merge.
|
|
6
6
|
|
|
7
|
+
## Documentation Paths
|
|
8
|
+
|
|
9
|
+
Use [`DOCS_BY_AUDIENCE.md`](./DOCS_BY_AUDIENCE.md) as the routing source of truth.
|
|
10
|
+
This README stays focused on the package itself, not on repeating the full doc map.
|
|
11
|
+
|
|
7
12
|
## Quick start
|
|
8
13
|
|
|
9
14
|
**Requirements:** Node 18+, npm 9+, git, and Pi.
|
|
@@ -64,9 +69,9 @@ If `/harness-review` returns `implementation_gap`, run:
|
|
|
64
69
|
|
|
65
70
|
| Command | Purpose |
|
|
66
71
|
|---|---|
|
|
67
|
-
| `/harness-setup [--skip-graphify] [--skip-tools] [--non-interactive] [--force]` | Idempotent project bootstrap: Graphify, harness-web/Scrapling, CLI tools, settings, contracts, Sentrux, harness lens, and verification. |
|
|
72
|
+
| `/harness-setup [--skip-graphify] [--skip-tools] [--non-interactive] [--force]` | Idempotent project bootstrap: Graphify, harness-web/Scrapling, CLI tools, settings, contracts, Sentrux, ls-lint naming, harness lens, and verification. |
|
|
68
73
|
| `/harness-auto "<task>" [--quick] [--risk low\|med\|high]` | Strict full pipeline: plan, execute, review, steer when appropriate. |
|
|
69
|
-
| `/harness-plan "<task>" [--risk low\|med\|high] [--quick]` | PM-grade planning: reconnaissance, decomposition, hypothesis, external research, ExecutionPlan, DAG validation, Review Gate debate, `approve_plan`, `create_plan`. |
|
|
74
|
+
| `/harness-plan "<task>" [--risk low\|med\|high] [--quick]` | PM-grade planning: clarifies the task with you first, then reconnaissance, decomposition, hypothesis, external research, ExecutionPlan, DAG validation, Review Gate debate, `approve_plan`, `create_plan`. |
|
|
70
75
|
| `/harness-run` | Executes the approved active PlanPacket by spawning `harness/running/executor`; no inline implementation. |
|
|
71
76
|
| `/harness-review [--run <id>] [--quick] [--readonly] [--trace <ref>]` | Post-run verification gate: deterministic checks, benchmark evaluator, policy verdict, adversary, optional tie-breaker. |
|
|
72
77
|
| `/harness-steer [--attempt N]` | Post-review repair pass for `implementation_gap`; executor reads `repair-brief.yaml`, then you re-run `/harness-review`. |
|
|
@@ -74,6 +79,8 @@ If `/harness-review` returns `implementation_gap`, run:
|
|
|
74
79
|
| `/harness-trace [--run <id>] [--phase plan\|execute\|evaluate\|adversary\|merge]` | Summarizes run traces and artifact handoffs for replay/forensics. |
|
|
75
80
|
| `/harness-incident --trigger <reason> [--run <id>] [--severity low\|med\|high\|critical]` | Records incident, rollback, and override trail for harness failures. |
|
|
76
81
|
| `/harness-sentrux-steward [--run <id>]` | Ad-hoc architectural intent review for Sentrux manifest/rule alignment. |
|
|
82
|
+
| `/harness-ls-lint-sync` | Regenerate `.ls-lint.yml` from `.pi/harness/ls-lint/naming.manifest.json`. |
|
|
83
|
+
| `/harness-ls-lint-steward [--run <id>]` | Ad-hoc naming-intent review for ls-lint manifest/rule alignment. |
|
|
77
84
|
| `/graphify [directory]` | Bootstraps or updates the Graphify knowledge graph. |
|
|
78
85
|
| `/wiki-autoresearch [topic]` | Runs autonomous web research and builds a Graphify-backed research wiki. |
|
|
79
86
|
| `/wiki-save` | Saves the current conversation or insight as a structured wiki note. |
|
|
@@ -84,7 +91,7 @@ If `/harness-review` returns `implementation_gap`, run:
|
|
|
84
91
|
- **Planning** uses agents under `.pi/agents/harness/planning/` plus parent-led Graphify → `sg` → `ccc` reconnaissance. Legacy tool-tied `planning/scout-*` agents have been removed; planning context is captured in `artifacts/planning-context.yaml`.
|
|
85
92
|
- **Running** uses `.pi/agents/harness/running/executor.md` via agent id `harness/running/executor`.
|
|
86
93
|
- **Reviewing** uses `.pi/agents/harness/reviewing/` via `harness/reviewing/evaluator`, `harness/reviewing/adversary`, and `harness/reviewing/tie-breaker`.
|
|
87
|
-
- **Support agents** such as `harness/incident-recorder`, `harness/sentrux-steward`, and `harness/trace-librarian` remain under `.pi/agents/harness/`.
|
|
94
|
+
- **Support agents** such as `harness/incident-recorder`, `harness/sentrux-steward`, `harness/ls-lint-steward`, and `harness/trace-librarian` remain under `.pi/agents/harness/`.
|
|
88
95
|
|
|
89
96
|
Subagents run isolated from the parent session. They persist canonical YAML through `submit_*` tools; the parent gates with `harness_artifact_ready` and writes only orchestrator-owned merge artifacts.
|
|
90
97
|
|
|
@@ -95,7 +102,7 @@ Subagents run isolated from the parent session. They persist canonical YAML thro
|
|
|
95
102
|
| `.pi/harness/active-run.json` | Active run pointer for happy-path commands. |
|
|
96
103
|
| `.pi/harness/runs/<run_id>/plan-packet.yaml` | Approved execution baseline. |
|
|
97
104
|
| `.pi/harness/runs/<run_id>/research-brief.yaml` | Planning evidence and research merge. |
|
|
98
|
-
| `.pi/harness/runs/<run_id>/artifacts/` | Planning context, decomposition, research, benchmark, verdict, adversary, repair, and
|
|
105
|
+
| `.pi/harness/runs/<run_id>/artifacts/` | Planning context, decomposition, research, benchmark, verdict, adversary, repair, Sentrux, and ls-lint signal artifacts. |
|
|
99
106
|
| `.pi/harness/runs/<run_id>/handoff/executor-summary.yaml` | Executor handoff written by `submit_executor_handoff`. |
|
|
100
107
|
| `.pi/harness/incidents/` | Incident records and rollback/override trail. |
|
|
101
108
|
| `.pi/harness/docs/adrs/` | Harness architectural decisions. |
|
|
@@ -109,6 +116,7 @@ Subagents run isolated from the parent session. They persist canonical YAML thro
|
|
|
109
116
|
- **No inline review:** `/harness-review` delegates verdicts to isolated reviewing agents.
|
|
110
117
|
- **No auto-merge:** final merge remains a human/operator decision.
|
|
111
118
|
- **Sentrux is the architecture signal:** structural baselines and gates inform review; executor does not optimize metrics as a goal.
|
|
119
|
+
- **ls-lint is the naming signal:** manifest-driven `.ls-lint.yml` enforces filesystem conventions; steward evolves rules via chair-approved proposals (ADR 0052).
|
|
112
120
|
- **pi-lens is edit-time diagnostics:** LSP/lint/format/ast feedback complements Sentrux and does not replace architecture gating.
|
|
113
121
|
|
|
114
122
|
## Troubleshooting
|
|
@@ -122,6 +130,7 @@ Subagents run isolated from the parent session. They persist canonical YAML thro
|
|
|
122
130
|
| Review says `implementation_gap` | Run `/harness-steer`, then `/harness-review`. |
|
|
123
131
|
| Review says `plan_gap` | Revise with `/harness-plan "<updated task>"`. |
|
|
124
132
|
| Sentrux missing | Install/configure Sentrux or keep it skipped; harness verification still reports the status. |
|
|
133
|
+
| ls-lint failures | Run `node .pi/scripts/harness-ls-lint-cli.mjs`, fix paths or propose manifest changes via `/harness-ls-lint-steward`. |
|
|
125
134
|
|
|
126
135
|
Optional integrations can be configured by copying `.env.example` to `.env`; `/harness-setup` appends missing keys without overwriting existing values.
|
|
127
136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultimate-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Ultimate AI coding harness for pi.dev — extensible skills, Obsidian wiki knowledge layer, compressed context, deterministic output",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
".agents",
|
|
39
39
|
".sentrux",
|
|
40
|
+
".pi/harness/ls-lint",
|
|
40
41
|
".pi/extensions",
|
|
41
42
|
".pi/prompts",
|
|
42
43
|
"!.pi/prompts/release.md",
|
|
@@ -91,6 +92,9 @@
|
|
|
91
92
|
"test:vcc": "npx -y tsx --test vendor/pi-vcc/tests/*.test.ts",
|
|
92
93
|
"harness:sentrux-bootstrap": "node .pi/scripts/harness-sentrux-bootstrap.mjs",
|
|
93
94
|
"harness:sentrux-sync": "node .pi/scripts/sentrux-rules-sync.mjs --force",
|
|
95
|
+
"harness:ls-lint-bootstrap": "node .pi/scripts/harness-ls-lint-bootstrap.mjs",
|
|
96
|
+
"harness:ls-lint-sync": "node .pi/scripts/ls-lint-rules-sync.mjs --force",
|
|
97
|
+
"harness:git-commit": "node .pi/scripts/harness-git-commit.mjs",
|
|
94
98
|
"test:integration": "npx -y tsx --test test/cursor-sdk-provider.integration.test.ts"
|
|
95
99
|
},
|
|
96
100
|
"devDependencies": {
|
|
@@ -137,6 +137,87 @@ const REASON_MESSAGES: Record<OwnCutCancelReason, string> = {
|
|
|
137
137
|
too_few_live_messages: "pi-vcc: Too few messages to compact",
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
+
|
|
141
|
+
function collectLiveRolesForDiagnostics(
|
|
142
|
+
branchEntries: any[],
|
|
143
|
+
lastCompIdx: number,
|
|
144
|
+
lastKeptId: string | undefined,
|
|
145
|
+
): string[] {
|
|
146
|
+
const hasPriorCompaction = lastCompIdx >= 0;
|
|
147
|
+
const hasValidKeptId = !!lastKeptId && branchEntries.some((e: any) => e.id === lastKeptId);
|
|
148
|
+
const diagOrphan = hasPriorCompaction && !hasValidKeptId;
|
|
149
|
+
const liveRoles: string[] = [];
|
|
150
|
+
if (diagOrphan) {
|
|
151
|
+
for (let i = lastCompIdx + 1; i < branchEntries.length; i++) {
|
|
152
|
+
const e = branchEntries[i];
|
|
153
|
+
if (e.type === "compaction") continue;
|
|
154
|
+
if (e.type === "message" && e.message) liveRoles.push(e.message.role);
|
|
155
|
+
}
|
|
156
|
+
return liveRoles;
|
|
157
|
+
}
|
|
158
|
+
let foundKept = !lastKeptId;
|
|
159
|
+
for (const e of branchEntries) {
|
|
160
|
+
if (!foundKept && e.id === lastKeptId) foundKept = true;
|
|
161
|
+
if (!foundKept || e.type === "compaction") continue;
|
|
162
|
+
if (e.type === "message" && e.message) liveRoles.push(e.message.role);
|
|
163
|
+
}
|
|
164
|
+
return liveRoles;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleOwnCutCancellation(args: {
|
|
168
|
+
ownCut: Extract<OwnCutResult, { ok: false }>;
|
|
169
|
+
isPiVcc: boolean;
|
|
170
|
+
settings: PiVccSettings;
|
|
171
|
+
branchEntries: any[];
|
|
172
|
+
ctx: any;
|
|
173
|
+
}): { cancel: true } {
|
|
174
|
+
const { ownCut, isPiVcc, settings, branchEntries, ctx } = args;
|
|
175
|
+
const lastComp = [...branchEntries].reverse().find((e: any) => e.type === "compaction");
|
|
176
|
+
const lastCompIdx = lastComp ? branchEntries.indexOf(lastComp) : -1;
|
|
177
|
+
const lastKeptId: string | undefined = lastComp?.firstKeptEntryId;
|
|
178
|
+
const liveRoles = collectLiveRolesForDiagnostics(branchEntries, lastCompIdx, lastKeptId);
|
|
179
|
+
const userIndices = liveRoles.reduce<number[]>((acc, r, i) => (r === "user" ? (acc.push(i), acc) : acc), []);
|
|
180
|
+
|
|
181
|
+
dbg(settings, {
|
|
182
|
+
cancelled: true,
|
|
183
|
+
reason: ownCut.reason,
|
|
184
|
+
isPiVcc,
|
|
185
|
+
counts: {
|
|
186
|
+
total: branchEntries.length,
|
|
187
|
+
messages: branchEntries.filter((e: any) => e.type === "message").length,
|
|
188
|
+
compactions: branchEntries.filter((e: any) => e.type === "compaction").length,
|
|
189
|
+
entriesAfterLastCompaction: lastCompIdx >= 0 ? branchEntries.length - lastCompIdx - 1 : null,
|
|
190
|
+
},
|
|
191
|
+
liveMessages: {
|
|
192
|
+
count: liveRoles.length,
|
|
193
|
+
userCount: userIndices.length,
|
|
194
|
+
firstUserIdx: userIndices[0] ?? null,
|
|
195
|
+
lastUserIdx: userIndices[userIndices.length - 1] ?? null,
|
|
196
|
+
roleSequence: liveRoles.length <= 30
|
|
197
|
+
? liveRoles
|
|
198
|
+
: [...liveRoles.slice(0, 10), "...", ...liveRoles.slice(-10)],
|
|
199
|
+
},
|
|
200
|
+
lastCompaction: lastComp
|
|
201
|
+
? {
|
|
202
|
+
hasFirstKeptEntryId: !!lastComp.firstKeptEntryId,
|
|
203
|
+
foundInBranch: lastComp.firstKeptEntryId
|
|
204
|
+
? branchEntries.some((e: any) => e.id === lastComp.firstKeptEntryId)
|
|
205
|
+
: null,
|
|
206
|
+
}
|
|
207
|
+
: null,
|
|
208
|
+
tail: branchEntries.slice(-5).map((e: any) => ({
|
|
209
|
+
type: e.type,
|
|
210
|
+
role: e.type === "message" ? e.message?.role : undefined,
|
|
211
|
+
hasContent: e.type === "message" ? e.message?.content != null : undefined,
|
|
212
|
+
})),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
ctx?.ui?.notify?.(REASON_MESSAGES[ownCut.reason], "warning");
|
|
217
|
+
} catch {}
|
|
218
|
+
return { cancel: true };
|
|
219
|
+
}
|
|
220
|
+
|
|
140
221
|
export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
141
222
|
pi.on("session_before_compact", (event, ctx) => {
|
|
142
223
|
const { preparation, branchEntries, customInstructions } = event;
|
|
@@ -149,68 +230,13 @@ export const registerBeforeCompactHook = (pi: ExtensionAPI) => {
|
|
|
149
230
|
|
|
150
231
|
const ownCut = buildOwnCut(branchEntries as any[]);
|
|
151
232
|
if (!ownCut.ok) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// Recompute liveMessages view (same logic as buildOwnCut) for diagnostic
|
|
156
|
-
const lastKeptId: string | undefined = lastComp?.firstKeptEntryId;
|
|
157
|
-
const hasPriorCompaction = lastCompIdx >= 0;
|
|
158
|
-
const hasValidKeptId = !!lastKeptId && (branchEntries as any[]).some((e: any) => e.id === lastKeptId);
|
|
159
|
-
const diagOrphan = hasPriorCompaction && !hasValidKeptId;
|
|
160
|
-
const liveRoles: string[] = [];
|
|
161
|
-
if (diagOrphan) {
|
|
162
|
-
for (let i = lastCompIdx + 1; i < branchEntries.length; i++) {
|
|
163
|
-
const e = (branchEntries as any[])[i];
|
|
164
|
-
if (e.type === "compaction") continue;
|
|
165
|
-
if (e.type === "message" && e.message) liveRoles.push(e.message.role);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
let foundKept = !lastKeptId;
|
|
169
|
-
for (const e of branchEntries as any[]) {
|
|
170
|
-
if (!foundKept && e.id === lastKeptId) foundKept = true;
|
|
171
|
-
if (!foundKept) continue;
|
|
172
|
-
if (e.type === "compaction") continue;
|
|
173
|
-
if (e.type === "message" && e.message) liveRoles.push(e.message.role);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const userIndices = liveRoles.reduce<number[]>((acc, r, i) => (r === "user" ? (acc.push(i), acc) : acc), []);
|
|
177
|
-
|
|
178
|
-
dbg(settings, {
|
|
179
|
-
cancelled: true,
|
|
180
|
-
reason: ownCut.reason,
|
|
233
|
+
return handleOwnCutCancellation({
|
|
234
|
+
ownCut,
|
|
181
235
|
isPiVcc,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
compactions: (branchEntries as any[]).filter((e: any) => e.type === "compaction").length,
|
|
186
|
-
entriesAfterLastCompaction: lastCompIdx >= 0 ? branchEntries.length - lastCompIdx - 1 : null,
|
|
187
|
-
},
|
|
188
|
-
liveMessages: {
|
|
189
|
-
count: liveRoles.length,
|
|
190
|
-
userCount: userIndices.length,
|
|
191
|
-
firstUserIdx: userIndices[0] ?? null,
|
|
192
|
-
lastUserIdx: userIndices[userIndices.length - 1] ?? null,
|
|
193
|
-
roleSequence: liveRoles.length <= 30
|
|
194
|
-
? liveRoles
|
|
195
|
-
: [...liveRoles.slice(0, 10), "...", ...liveRoles.slice(-10)],
|
|
196
|
-
},
|
|
197
|
-
lastCompaction: lastComp ? {
|
|
198
|
-
hasFirstKeptEntryId: !!lastComp.firstKeptEntryId,
|
|
199
|
-
foundInBranch: lastComp.firstKeptEntryId
|
|
200
|
-
? (branchEntries as any[]).some((e: any) => e.id === lastComp.firstKeptEntryId)
|
|
201
|
-
: null,
|
|
202
|
-
} : null,
|
|
203
|
-
tail: (branchEntries as any[]).slice(-5).map((e: any) => ({
|
|
204
|
-
type: e.type,
|
|
205
|
-
role: e.type === "message" ? e.message?.role : undefined,
|
|
206
|
-
hasContent: e.type === "message" ? e.message?.content != null : undefined,
|
|
207
|
-
})),
|
|
236
|
+
settings,
|
|
237
|
+
branchEntries: branchEntries as any[],
|
|
238
|
+
ctx,
|
|
208
239
|
});
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
ctx?.ui?.notify?.(REASON_MESSAGES[ownCut.reason], "warning");
|
|
212
|
-
} catch {}
|
|
213
|
-
return { cancel: true };
|
|
214
240
|
}
|
|
215
241
|
|
|
216
242
|
const agentMessages = ownCut.messages;
|