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.
Files changed (130) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +68 -2
  2. package/.agents/skills/harness-git-commit/SKILL.md +72 -0
  3. package/.agents/skills/harness-governor/SKILL.md +2 -2
  4. package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
  5. package/.agents/skills/harness-plan/SKILL.md +13 -11
  6. package/.agents/skills/harness-review/SKILL.md +1 -1
  7. package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
  8. package/.agents/skills/sentrux/SKILL.md +4 -2
  9. package/.agents/skills/wiki-save/SKILL.md +1 -1
  10. package/.pi/PACKAGING.md +6 -0
  11. package/.pi/SYSTEM.md +21 -3
  12. package/.pi/agents/harness/ls-lint-steward.md +49 -0
  13. package/.pi/agents/harness/planning/decompose.md +4 -4
  14. package/.pi/agents/harness/reviewing/evaluator.md +1 -1
  15. package/.pi/agents/harness/running/executor.md +1 -1
  16. package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
  17. package/.pi/agents/pi-pi/prompt-expert.md +17 -2
  18. package/.pi/auto-commit.json +9 -2
  19. package/.pi/extensions/debate-orchestrator.ts +3 -0
  20. package/.pi/extensions/harness-anchored-edit.ts +7 -9
  21. package/.pi/extensions/harness-ask-user.ts +13 -34
  22. package/.pi/extensions/harness-debate-tools.ts +43 -4
  23. package/.pi/extensions/harness-live-widget.ts +28 -19
  24. package/.pi/extensions/harness-run-context.ts +278 -115
  25. package/.pi/extensions/harness-web-tools.ts +598 -471
  26. package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
  27. package/.pi/extensions/observation-bus.ts +4 -0
  28. package/.pi/extensions/policy-gate.ts +270 -229
  29. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  30. package/.pi/extensions/soundboard.ts +48 -48
  31. package/.pi/harness/README.md +4 -0
  32. package/.pi/harness/agents.manifest.json +15 -7
  33. package/.pi/harness/agents.policy.yaml +49 -82
  34. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  35. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  36. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  37. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  38. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  39. package/.pi/harness/docs/adrs/README.md +5 -0
  40. package/.pi/harness/docs/practice-map.md +10 -5
  41. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  42. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  43. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  44. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  45. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  46. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  47. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  48. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  49. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  50. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  51. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  52. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  53. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  54. package/.pi/lib/agents-policy.d.mts +26 -51
  55. package/.pi/lib/agents-policy.mjs +41 -28
  56. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  57. package/.pi/lib/ask-user/constants.mjs +3 -0
  58. package/.pi/lib/ask-user/constants.ts +4 -0
  59. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  60. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  62. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  63. package/.pi/lib/ask-user/dialog.ts +2 -314
  64. package/.pi/lib/ask-user/fallback.ts +2 -78
  65. package/.pi/lib/ask-user/format.ts +85 -0
  66. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  67. package/.pi/lib/ask-user/index.ts +114 -0
  68. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  69. package/.pi/lib/ask-user/policy.mjs +43 -0
  70. package/.pi/lib/ask-user/policy.ts +104 -0
  71. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  72. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  73. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  74. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  75. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  76. package/.pi/lib/ask-user/render.ts +40 -9
  77. package/.pi/lib/ask-user/schema.ts +66 -13
  78. package/.pi/lib/ask-user/types.ts +60 -3
  79. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  80. package/.pi/lib/ask-user/validate.ts +53 -34
  81. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  82. package/.pi/lib/harness-artifact-gate.ts +75 -21
  83. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  84. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  85. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  86. package/.pi/lib/harness-lens/index.ts +241 -108
  87. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  88. package/.pi/lib/harness-repair-brief.ts +84 -25
  89. package/.pi/lib/harness-run-context.ts +42 -52
  90. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  91. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  92. package/.pi/lib/harness-slash-completions.ts +116 -0
  93. package/.pi/lib/harness-spawn-topology.ts +121 -87
  94. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  95. package/.pi/lib/harness-subagents-bridge.ts +4 -1
  96. package/.pi/lib/harness-ui-state.ts +95 -48
  97. package/.pi/lib/plan-approval/dialog.ts +5 -0
  98. package/.pi/lib/plan-approval/validate.ts +1 -1
  99. package/.pi/lib/plan-approval-readiness.ts +32 -0
  100. package/.pi/lib/plan-debate-gate.ts +154 -114
  101. package/.pi/lib/plan-task-clarification.ts +158 -0
  102. package/.pi/prompts/harness-auto.md +2 -2
  103. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  104. package/.pi/prompts/harness-plan.md +58 -8
  105. package/.pi/prompts/harness-review.md +40 -6
  106. package/.pi/prompts/harness-run.md +33 -11
  107. package/.pi/prompts/harness-setup.md +72 -3
  108. package/.pi/prompts/harness-steer.md +2 -1
  109. package/.pi/prompts/wiki-save.md +5 -4
  110. package/.pi/scripts/README.md +8 -0
  111. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  112. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  113. package/.pi/scripts/harness-cli-verify.sh +47 -0
  114. package/.pi/scripts/harness-git-churn.mjs +77 -0
  115. package/.pi/scripts/harness-git-commit.mjs +173 -0
  116. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  117. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  118. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  119. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  120. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  121. package/.pi/scripts/harness-verify.mjs +288 -125
  122. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  123. package/.pi/scripts/run-tests.mjs +1 -0
  124. package/.pi/settings.example.json +1 -0
  125. package/.sentrux/rules.toml +1 -1
  126. package/AGENTS.md +1 -0
  127. package/CHANGELOG.md +25 -0
  128. package/README.md +13 -4
  129. package/package.json +5 -1
  130. 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
+ });
@@ -10,6 +10,7 @@ const NODE_ONLY_TESTS = [
10
10
  'harness-subagents-loader.test.mjs',
11
11
  'harness-subagent-precheck.test.mjs',
12
12
  'sentrux-rules-sync.test.mjs',
13
+ 'ls-lint-rules-sync.test.mjs',
13
14
  'harness-budget-guard.test.mjs',
14
15
  ];
15
16
 
@@ -3,5 +3,6 @@
3
3
  "enabled": true,
4
4
  "maxRetries": 3
5
5
  },
6
+ "autocompleteMaxVisible": 12,
6
7
  "packages": ["npm:ultimate-pi", "npm:@posthog/pi", "npm:context-mode"]
7
8
  }
@@ -12,7 +12,7 @@
12
12
  [constraints]
13
13
  max_cycles = 0
14
14
  max_coupling = "B"
15
- max_cc = 35
15
+ max_cc = 25
16
16
  no_god_files = true
17
17
 
18
18
  [[layers]]
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 Sentrux artifacts. |
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.20.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
- const lastComp = [...branchEntries].reverse().find((e: any) => e.type === "compaction");
153
- const lastCompIdx = lastComp ? (branchEntries as any[]).indexOf(lastComp) : -1;
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
- counts: {
183
- total: branchEntries.length,
184
- messages: (branchEntries as any[]).filter((e: any) => e.type === "message").length,
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;