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
@@ -1132,13 +1132,81 @@ export interface AutofixPolicyContext {
1132
1132
  hasBiomeConfig?: boolean;
1133
1133
  }
1134
1134
 
1135
+ const JS_TS_EXTENSIONS = new Set([
1136
+ ".js",
1137
+ ".jsx",
1138
+ ".ts",
1139
+ ".tsx",
1140
+ ".mjs",
1141
+ ".cjs",
1142
+ ]);
1143
+ const PY_EXTENSIONS = new Set([".py", ".pyi"]);
1144
+ const KOTLIN_EXTENSIONS = new Set([".kt", ".kts"]);
1145
+
1146
+ function staticLinterPolicy(
1147
+ runner: LintRunnerName,
1148
+ gate: ToolGate = "smart-default",
1149
+ defaultWhenUnconfigured = true,
1150
+ ): LinterPolicy {
1151
+ return {
1152
+ runnerNames: [runner],
1153
+ preferredRunners: [runner],
1154
+ defaultRunner: runner,
1155
+ defaultWhenUnconfigured,
1156
+ gate,
1157
+ };
1158
+ }
1159
+
1160
+ const STATIC_LINTER_POLICY_BY_EXTENSION = new Map<string, LinterPolicy>([
1161
+ [".css", staticLinterPolicy("stylelint")],
1162
+ [".scss", staticLinterPolicy("stylelint")],
1163
+ [".sass", staticLinterPolicy("stylelint")],
1164
+ [".less", staticLinterPolicy("stylelint")],
1165
+ [".sql", staticLinterPolicy("sqlfluff")],
1166
+ [".rb", staticLinterPolicy("rubocop")],
1167
+ [".rake", staticLinterPolicy("rubocop")],
1168
+ [".gemspec", staticLinterPolicy("rubocop")],
1169
+ [".ru", staticLinterPolicy("rubocop")],
1170
+ [".yaml", staticLinterPolicy("yamllint")],
1171
+ [".yml", staticLinterPolicy("yamllint")],
1172
+ [".md", staticLinterPolicy("markdownlint")],
1173
+ [".mdx", staticLinterPolicy("markdownlint")],
1174
+ [".html", staticLinterPolicy("htmlhint")],
1175
+ [".htm", staticLinterPolicy("htmlhint")],
1176
+ [".toml", staticLinterPolicy("taplo")],
1177
+ [".rs", staticLinterPolicy("rust-clippy")],
1178
+ [".sh", staticLinterPolicy("shellcheck")],
1179
+ [".bash", staticLinterPolicy("shellcheck")],
1180
+ [".fish", staticLinterPolicy("fish-indent")],
1181
+ [".tf", staticLinterPolicy("tflint")],
1182
+ [".tfvars", staticLinterPolicy("tflint")],
1183
+ [".ex", staticLinterPolicy("credo")],
1184
+ [".exs", staticLinterPolicy("credo")],
1185
+ [".eex", staticLinterPolicy("credo")],
1186
+ [".heex", staticLinterPolicy("credo")],
1187
+ [".leex", staticLinterPolicy("credo")],
1188
+ [".c", staticLinterPolicy("cpp-check")],
1189
+ [".cc", staticLinterPolicy("cpp-check")],
1190
+ [".cpp", staticLinterPolicy("cpp-check")],
1191
+ [".cxx", staticLinterPolicy("cpp-check")],
1192
+ [".h", staticLinterPolicy("cpp-check")],
1193
+ [".hpp", staticLinterPolicy("cpp-check")],
1194
+ [".ino", staticLinterPolicy("cpp-check")],
1195
+ [".dart", staticLinterPolicy("dart-analyze")],
1196
+ [".gleam", staticLinterPolicy("gleam-check")],
1197
+ [".ps1", staticLinterPolicy("psscriptanalyzer")],
1198
+ [".psm1", staticLinterPolicy("psscriptanalyzer")],
1199
+ [".psd1", staticLinterPolicy("psscriptanalyzer")],
1200
+ [".prisma", staticLinterPolicy("prisma-validate")],
1201
+ ]);
1202
+
1135
1203
  export function getLinterPolicyForFile(
1136
1204
  filePath: string,
1137
1205
  context: LinterPolicyContext = {},
1138
1206
  ): LinterPolicy | undefined {
1139
1207
  const ext = path.extname(filePath).toLowerCase();
1140
1208
 
1141
- if ([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"].includes(ext)) {
1209
+ if (JS_TS_EXTENSIONS.has(ext)) {
1142
1210
  const policy = getJstsLintPolicy({
1143
1211
  hasEslintConfig: context.hasEslintConfig,
1144
1212
  hasOxlintConfig: context.hasOxlintConfig,
@@ -1154,7 +1222,7 @@ export function getLinterPolicyForFile(
1154
1222
  };
1155
1223
  }
1156
1224
 
1157
- if ([".py", ".pyi"].includes(ext)) {
1225
+ if (PY_EXTENSIONS.has(ext)) {
1158
1226
  const preferredRunners: LintRunnerName[] = ["ruff-lint"];
1159
1227
  if (context.hasMypyConfig) preferredRunners.push("mypy");
1160
1228
  return {
@@ -1166,77 +1234,11 @@ export function getLinterPolicyForFile(
1166
1234
  };
1167
1235
  }
1168
1236
 
1169
- if ([".css", ".scss", ".sass", ".less"].includes(ext)) {
1170
- return {
1171
- runnerNames: ["stylelint"],
1172
- preferredRunners: ["stylelint"],
1173
- defaultRunner: "stylelint",
1174
- defaultWhenUnconfigured: true,
1175
- gate: "smart-default",
1176
- };
1177
- }
1178
-
1179
- if (ext === ".sql") {
1180
- return {
1181
- runnerNames: ["sqlfluff"],
1182
- preferredRunners: ["sqlfluff"],
1183
- defaultRunner: "sqlfluff",
1184
- defaultWhenUnconfigured: true,
1185
- gate: "smart-default",
1186
- };
1187
- }
1188
-
1189
- if ([".rb", ".rake", ".gemspec", ".ru"].includes(ext)) {
1190
- return {
1191
- runnerNames: ["rubocop"],
1192
- preferredRunners: ["rubocop"],
1193
- defaultRunner: "rubocop",
1194
- defaultWhenUnconfigured: true,
1195
- gate: "smart-default",
1196
- };
1197
- }
1198
-
1199
- if ([".yaml", ".yml"].includes(ext)) {
1200
- return {
1201
- runnerNames: ["yamllint"],
1202
- preferredRunners: ["yamllint"],
1203
- defaultRunner: "yamllint",
1204
- defaultWhenUnconfigured: true,
1205
- gate: "smart-default",
1206
- };
1207
- }
1208
-
1209
- if ([".md", ".mdx"].includes(ext)) {
1210
- return {
1211
- runnerNames: ["markdownlint"],
1212
- preferredRunners: ["markdownlint"],
1213
- defaultRunner: "markdownlint",
1214
- defaultWhenUnconfigured: true,
1215
- gate: "smart-default",
1216
- };
1217
- }
1218
-
1219
- if ([".html", ".htm"].includes(ext)) {
1220
- return {
1221
- runnerNames: ["htmlhint"],
1222
- preferredRunners: ["htmlhint"],
1223
- defaultRunner: "htmlhint",
1224
- defaultWhenUnconfigured: true,
1225
- gate: "smart-default",
1226
- };
1227
- }
1228
-
1229
1237
  if (path.basename(filePath).toLowerCase() === "dockerfile") {
1230
- return {
1231
- runnerNames: ["hadolint"],
1232
- preferredRunners: ["hadolint"],
1233
- defaultRunner: "hadolint",
1234
- defaultWhenUnconfigured: true,
1235
- gate: "smart-default",
1236
- };
1238
+ return staticLinterPolicy("hadolint");
1237
1239
  }
1238
1240
 
1239
- if ([".kt", ".kts"].includes(ext)) {
1241
+ if (KOTLIN_EXTENSIONS.has(ext)) {
1240
1242
  const preferredRunners: LintRunnerName[] = ["ktlint"];
1241
1243
  if (context.hasDetektConfig) preferredRunners.push("detekt");
1242
1244
  return {
@@ -1248,16 +1250,6 @@ export function getLinterPolicyForFile(
1248
1250
  };
1249
1251
  }
1250
1252
 
1251
- if (ext === ".toml") {
1252
- return {
1253
- runnerNames: ["taplo"],
1254
- preferredRunners: ["taplo"],
1255
- defaultRunner: "taplo",
1256
- defaultWhenUnconfigured: true,
1257
- gate: "smart-default",
1258
- };
1259
- }
1260
-
1261
1253
  if (ext === ".go") {
1262
1254
  return {
1263
1255
  runnerNames: ["golangci-lint"],
@@ -1278,107 +1270,7 @@ export function getLinterPolicyForFile(
1278
1270
  };
1279
1271
  }
1280
1272
 
1281
- if (ext === ".rs") {
1282
- return {
1283
- runnerNames: ["rust-clippy"],
1284
- preferredRunners: ["rust-clippy"],
1285
- defaultRunner: "rust-clippy",
1286
- defaultWhenUnconfigured: true,
1287
- gate: "smart-default",
1288
- };
1289
- }
1290
-
1291
- if ([".sh", ".bash"].includes(ext)) {
1292
- return {
1293
- runnerNames: ["shellcheck"],
1294
- preferredRunners: ["shellcheck"],
1295
- defaultRunner: "shellcheck",
1296
- defaultWhenUnconfigured: true,
1297
- gate: "smart-default",
1298
- };
1299
- }
1300
-
1301
- if (ext === ".fish") {
1302
- return {
1303
- runnerNames: ["fish-indent"],
1304
- preferredRunners: ["fish-indent"],
1305
- defaultRunner: "fish-indent",
1306
- defaultWhenUnconfigured: true,
1307
- gate: "smart-default",
1308
- };
1309
- }
1310
-
1311
- if ([".tf", ".tfvars"].includes(ext)) {
1312
- return {
1313
- runnerNames: ["tflint"],
1314
- preferredRunners: ["tflint"],
1315
- defaultRunner: "tflint",
1316
- defaultWhenUnconfigured: true,
1317
- gate: "smart-default",
1318
- };
1319
- }
1320
-
1321
- if ([".ex", ".exs", ".eex", ".heex", ".leex"].includes(ext)) {
1322
- return {
1323
- runnerNames: ["credo"],
1324
- preferredRunners: ["credo"],
1325
- defaultRunner: "credo",
1326
- defaultWhenUnconfigured: true,
1327
- gate: "smart-default",
1328
- };
1329
- }
1330
-
1331
- if ([".c", ".cc", ".cpp", ".cxx", ".h", ".hpp", ".ino"].includes(ext)) {
1332
- return {
1333
- runnerNames: ["cpp-check"],
1334
- preferredRunners: ["cpp-check"],
1335
- defaultRunner: "cpp-check",
1336
- defaultWhenUnconfigured: true,
1337
- gate: "smart-default",
1338
- };
1339
- }
1340
-
1341
- if (ext === ".dart") {
1342
- return {
1343
- runnerNames: ["dart-analyze"],
1344
- preferredRunners: ["dart-analyze"],
1345
- defaultRunner: "dart-analyze",
1346
- defaultWhenUnconfigured: true,
1347
- gate: "smart-default",
1348
- };
1349
- }
1350
-
1351
- if (ext === ".gleam") {
1352
- return {
1353
- runnerNames: ["gleam-check"],
1354
- preferredRunners: ["gleam-check"],
1355
- defaultRunner: "gleam-check",
1356
- defaultWhenUnconfigured: true,
1357
- gate: "smart-default",
1358
- };
1359
- }
1360
-
1361
- if ([".ps1", ".psm1", ".psd1"].includes(ext)) {
1362
- return {
1363
- runnerNames: ["psscriptanalyzer"],
1364
- preferredRunners: ["psscriptanalyzer"],
1365
- defaultRunner: "psscriptanalyzer",
1366
- defaultWhenUnconfigured: true,
1367
- gate: "smart-default",
1368
- };
1369
- }
1370
-
1371
- if (ext === ".prisma") {
1372
- return {
1373
- runnerNames: ["prisma-validate"],
1374
- preferredRunners: ["prisma-validate"],
1375
- defaultRunner: "prisma-validate",
1376
- defaultWhenUnconfigured: true,
1377
- gate: "smart-default",
1378
- };
1379
- }
1380
-
1381
- return undefined;
1273
+ return STATIC_LINTER_POLICY_BY_EXTENSION.get(ext);
1382
1274
  }
1383
1275
 
1384
1276
  export function getLinterPolicyForCwd(
@@ -336,57 +336,101 @@ function applyEditAutopatch(
336
336
  return undefined;
337
337
  }
338
338
 
339
- export default function harnessLensExtension(pi: ExtensionAPI): void {
340
- initLensEvents(pi);
341
- const globalConfig = loadPiLensGlobalConfig();
342
- let lensEnabled = !globalConfig.noLens;
343
-
344
- type PiWithFlags = ExtensionAPI & {
345
- getFlag?: (name: string) => boolean | undefined;
346
- };
347
- const piFlags = pi as PiWithFlags;
348
- const readCliFlag = (name: string): boolean | undefined => {
349
- if (typeof piFlags.getFlag === "function") return piFlags.getFlag(name);
350
- return process.argv.includes(`--${name}`) ? true : undefined;
351
- };
352
- const getFlag = (name: string) =>
353
- getLensFlag(name, readCliFlag, globalConfig);
354
-
339
+ function registerLensRuntimePart1(
340
+ pi: ExtensionAPI,
341
+ getFlag: (name: string) => boolean | string | undefined,
342
+ runtime: RuntimeCoordinator,
343
+ lensEnabledRef: { current: boolean },
344
+ ) {
355
345
  pi.registerFlag("no-lens", {
356
346
  description: "Disable harness-lens for this session.",
357
347
  type: "boolean",
358
348
  default: false,
359
349
  });
350
+ }
351
+
352
+ function registerLensRuntimePart2(
353
+ pi: ExtensionAPI,
354
+ getFlag: (name: string) => boolean | string | undefined,
355
+ runtime: RuntimeCoordinator,
356
+ lensEnabledRef: { current: boolean },
357
+ ) {
360
358
  pi.registerFlag("no-lsp", {
361
359
  description: "Disable LSP auto-touch and lsp_* tools backing servers.",
362
360
  type: "boolean",
363
361
  default: false,
364
362
  });
363
+ }
364
+
365
+ function registerLensRuntimePart3(
366
+ pi: ExtensionAPI,
367
+ getFlag: (name: string) => boolean | string | undefined,
368
+ runtime: RuntimeCoordinator,
369
+ lensEnabledRef: { current: boolean },
370
+ ) {
365
371
  pi.registerFlag("no-autoformat", {
366
372
  description: "Disable auto-format on write/edit.",
367
373
  type: "boolean",
368
374
  default: false,
369
375
  });
376
+ }
377
+
378
+ function registerLensRuntimePart4(
379
+ pi: ExtensionAPI,
380
+ getFlag: (name: string) => boolean | string | undefined,
381
+ runtime: RuntimeCoordinator,
382
+ lensEnabledRef: { current: boolean },
383
+ ) {
370
384
  pi.registerFlag("immediate-format", {
371
385
  description: "Format during tool_result instead of deferring to agent_end.",
372
386
  type: "boolean",
373
387
  default: false,
374
388
  });
389
+ }
390
+
391
+ function registerLensRuntimePart5(
392
+ pi: ExtensionAPI,
393
+ getFlag: (name: string) => boolean | string | undefined,
394
+ runtime: RuntimeCoordinator,
395
+ lensEnabledRef: { current: boolean },
396
+ ) {
375
397
  pi.registerFlag("lens-guard", {
376
398
  description: "Block git commit/push while unresolved lens blockers exist.",
377
399
  type: "boolean",
378
400
  default: false,
379
401
  });
402
+ }
380
403
 
404
+ function registerLensRuntimePart6(
405
+ pi: ExtensionAPI,
406
+ getFlag: (name: string) => boolean | string | undefined,
407
+ runtime: RuntimeCoordinator,
408
+ lensEnabledRef: { current: boolean },
409
+ ) {
381
410
  pi.registerTool(createLspDiagnosticsTool());
382
- pi.registerTool(createLspNavigationTool());
411
+ }
383
412
 
413
+ function registerLensRuntimePart7(
414
+ pi: ExtensionAPI,
415
+ getFlag: (name: string) => boolean | string | undefined,
416
+ runtime: RuntimeCoordinator,
417
+ lensEnabledRef: { current: boolean },
418
+ ) {
419
+ pi.registerTool(createLspNavigationTool(getFlag));
420
+ }
421
+
422
+ function registerLensRuntimePart8(
423
+ pi: ExtensionAPI,
424
+ getFlag: (name: string) => boolean | string | undefined,
425
+ runtime: RuntimeCoordinator,
426
+ lensEnabledRef: { current: boolean },
427
+ ) {
384
428
  pi.on("session_start", async (_event, ctx) => {
385
429
  if (getFlag("no-lens")) {
386
- lensEnabled = false;
430
+ lensEnabledRef.current = false;
387
431
  return;
388
432
  }
389
- lensEnabled = true;
433
+ lensEnabledRef.current = true;
390
434
  runtime.resetForSession();
391
435
  clearWidgetState();
392
436
  resetFormatService();
@@ -416,15 +460,126 @@ export default function harnessLensExtension(pi: ExtensionAPI): void {
416
460
  });
417
461
  }
418
462
  });
463
+ }
419
464
 
465
+ function registerLensRuntimePart9(
466
+ pi: ExtensionAPI,
467
+ getFlag: (name: string) => boolean | string | undefined,
468
+ runtime: RuntimeCoordinator,
469
+ lensEnabledRef: { current: boolean },
470
+ ) {
420
471
  pi.on("turn_start", () => {
421
- if (!lensEnabled) return;
472
+ if (!lensEnabledRef.current) return;
422
473
  runtime.beginTurn();
423
474
  clearLastAnalyzedStateCache();
424
475
  });
476
+ }
477
+
478
+ async function ensureToolCallLspConfig(args: {
479
+ getFlag: (name: string) => boolean | string | undefined;
480
+ filePath: string | undefined;
481
+ ctx: any;
482
+ runtime: RuntimeCoordinator;
483
+ }): Promise<void> {
484
+ if (args.getFlag("no-lsp")) return;
485
+ try {
486
+ await ensureLSPConfigInitialized(
487
+ args.filePath
488
+ ? path.dirname(args.filePath)
489
+ : (args.ctx.cwd ?? args.runtime.projectRoot),
490
+ );
491
+ } catch (err) {
492
+ dbg(`tool_call lsp config init failed: ${err}`);
493
+ }
494
+ }
495
+
496
+ function maybeAutoTouchLspOnToolCall(args: {
497
+ getFlag: (name: string) => boolean | string | undefined;
498
+ toolName: string;
499
+ filePath: string;
500
+ runtime: RuntimeCoordinator;
501
+ }): void {
502
+ if (
503
+ args.getFlag("no-lsp") ||
504
+ !isLspCapableFile(args.filePath) ||
505
+ shouldSkipLspAutoTouch(args.filePath, args.runtime.projectRoot)
506
+ ) {
507
+ return;
508
+ }
509
+ const shouldWarmRead =
510
+ args.toolName === "read" && args.runtime.shouldWarmLspOnRead(args.filePath);
511
+ const shouldTouch =
512
+ args.toolName === "write" ||
513
+ args.toolName === "edit" ||
514
+ args.toolName === "lsp_navigation" ||
515
+ shouldWarmRead;
516
+ if (!shouldTouch) return;
517
+ try {
518
+ const content = nodeFs.readFileSync(args.filePath, "utf-8");
519
+ if (args.toolName === "read")
520
+ args.runtime.markLspReadWarmStarted(args.filePath);
521
+ void getLSPService()
522
+ .touchFile(args.filePath, content, {
523
+ diagnostics: "none",
524
+ source: `tool_call:${args.toolName}`,
525
+ })
526
+ .then((result) => {
527
+ if (args.toolName !== "read") return;
528
+ if (result === undefined)
529
+ args.runtime.clearLspReadWarmState(args.filePath);
530
+ else args.runtime.markLspReadWarmCompleted(args.filePath);
531
+ })
532
+ .catch((err) => {
533
+ if (args.toolName === "read") {
534
+ args.runtime.clearLspReadWarmState(args.filePath);
535
+ }
536
+ dbg(`lsp auto-touch failed: ${err}`);
537
+ });
538
+ } catch {
539
+ if (args.toolName === "read")
540
+ args.runtime.clearLspReadWarmState(args.filePath);
541
+ }
542
+ }
425
543
 
544
+ function applyEditAutopatchForToolCall(
545
+ filePath: string,
546
+ event: unknown,
547
+ ctx: unknown,
548
+ ) {
549
+ if (
550
+ !isToolCallEventType(
551
+ "edit",
552
+ event as Parameters<typeof isToolCallEventType>[1],
553
+ )
554
+ ) {
555
+ return undefined;
556
+ }
557
+ const editInput = (event as { input?: unknown }).input;
558
+ if (isAnchoredEditToolInput(editInput)) {
559
+ return applyAnchoredEditAutopatch(
560
+ filePath,
561
+ editInput,
562
+ anchoredEditTaskId({
563
+ sessionId: (ctx as { sessionId?: string }).sessionId,
564
+ }),
565
+ );
566
+ }
567
+ const legacyInput = editInput as {
568
+ oldText?: string;
569
+ newText?: string;
570
+ edits?: Array<{ oldText?: string; newText?: string }>;
571
+ };
572
+ return applyEditAutopatch(filePath, legacyInput);
573
+ }
574
+
575
+ function registerLensRuntimePart10(
576
+ pi: ExtensionAPI,
577
+ getFlag: (name: string) => boolean | string | undefined,
578
+ runtime: RuntimeCoordinator,
579
+ lensEnabledRef: { current: boolean },
580
+ ) {
426
581
  pi.on("tool_call", async (event, ctx) => {
427
- if (!lensEnabled) return;
582
+ if (!lensEnabledRef.current) return;
428
583
 
429
584
  const toolName = (event as { toolName?: string }).toolName ?? "";
430
585
  if (
@@ -442,100 +597,33 @@ export default function harnessLensExtension(pi: ExtensionAPI): void {
442
597
  runtime.projectRoot,
443
598
  );
444
599
 
445
- if (!getFlag("no-lsp")) {
446
- try {
447
- await ensureLSPConfigInitialized(
448
- filePath ? path.dirname(filePath) : (ctx.cwd ?? runtime.projectRoot),
449
- );
450
- } catch (err) {
451
- dbg(`tool_call lsp config init failed: ${err}`);
452
- }
453
- }
454
-
600
+ await ensureToolCallLspConfig({ getFlag, filePath, ctx, runtime });
455
601
  if (!filePath || !nodeFs.existsSync(filePath)) return;
456
602
  if (isPathIgnoredByProject(filePath, runtime.projectRoot, false)) return;
457
603
 
458
- if (
459
- !getFlag("no-lsp") &&
460
- isLspCapableFile(filePath) &&
461
- !shouldSkipLspAutoTouch(filePath, runtime.projectRoot)
462
- ) {
463
- const shouldWarmRead =
464
- toolName === "read" && runtime.shouldWarmLspOnRead(filePath);
465
- const shouldTouch =
466
- toolName === "write" ||
467
- toolName === "edit" ||
468
- toolName === "lsp_navigation" ||
469
- shouldWarmRead;
470
- if (shouldTouch) {
471
- try {
472
- const content = nodeFs.readFileSync(filePath, "utf-8");
473
- if (toolName === "read") runtime.markLspReadWarmStarted(filePath);
474
- void getLSPService()
475
- .touchFile(filePath, content, {
476
- diagnostics: "none",
477
- source: `tool_call:${toolName}`,
478
- })
479
- .then((result) => {
480
- if (toolName === "read") {
481
- if (result === undefined) {
482
- runtime.clearLspReadWarmState(filePath);
483
- } else {
484
- runtime.markLspReadWarmCompleted(filePath);
485
- }
486
- }
487
- })
488
- .catch((err) => {
489
- if (toolName === "read") runtime.clearLspReadWarmState(filePath);
490
- dbg(`lsp auto-touch failed: ${err}`);
491
- });
492
- } catch {
493
- if (toolName === "read") runtime.clearLspReadWarmState(filePath);
494
- }
495
- }
496
- }
497
-
498
- if (
499
- isToolCallEventType(
500
- "edit",
501
- event as Parameters<typeof isToolCallEventType>[1],
502
- )
503
- ) {
504
- const editInput = (event as { input?: unknown }).input;
505
- if (isAnchoredEditToolInput(editInput)) {
506
- const block = applyAnchoredEditAutopatch(
507
- filePath,
508
- editInput,
509
- anchoredEditTaskId({
510
- sessionId: (ctx as { sessionId?: string }).sessionId,
511
- }),
512
- );
513
- if (block) return block;
514
- } else {
515
- const legacyInput = editInput as {
516
- oldText?: string;
517
- newText?: string;
518
- edits?: Array<{ oldText?: string; newText?: string }>;
519
- };
520
- const block = applyEditAutopatch(filePath, legacyInput);
521
- if (block) return block;
522
- }
523
- }
604
+ maybeAutoTouchLspOnToolCall({ getFlag, toolName, filePath, runtime });
605
+ const block = applyEditAutopatchForToolCall(filePath, event, ctx);
606
+ if (block) return block;
524
607
  });
608
+ }
525
609
 
526
- pi.on("tool_result", async (event, _ctx) => {
527
- if (!lensEnabled) return;
528
- return handleToolResult({
529
- event: event as Parameters<typeof handleToolResult>[0]["event"],
530
- getFlag,
531
- dbg,
532
- runtime,
533
- resetLSPService,
534
- });
535
- });
610
+ function registerLensRuntimePart11(
611
+ pi: ExtensionAPI,
612
+ getFlag: (name: string) => boolean | string | undefined,
613
+ runtime: RuntimeCoordinator,
614
+ lensEnabledRef: { current: boolean },
615
+ ) {
616
+ pi as any;
617
+ }
536
618
 
619
+ function registerLensRuntimePart12(
620
+ pi: ExtensionAPI,
621
+ getFlag: (name: string) => boolean | string | undefined,
622
+ runtime: RuntimeCoordinator,
623
+ lensEnabledRef: { current: boolean },
624
+ ) {
537
625
  pi.on("agent_end", async (_event, ctx) => {
538
- if (!lensEnabled) return;
626
+ if (!lensEnabledRef.current) return;
539
627
  await handleAgentEnd({
540
628
  ctxCwd: ctx.cwd,
541
629
  getFlag,
@@ -547,3 +635,48 @@ export default function harnessLensExtension(pi: ExtensionAPI): void {
547
635
  });
548
636
  });
549
637
  }
638
+
639
+ function registerHarnessLensRuntime(
640
+ pi: ExtensionAPI,
641
+ args: {
642
+ getFlag: (name: string) => boolean | string | undefined;
643
+ runtime: RuntimeCoordinator;
644
+ lensEnabledRef: { current: boolean };
645
+ },
646
+ ) {
647
+ const { getFlag, runtime, lensEnabledRef } = args;
648
+ registerLensRuntimePart1(pi, getFlag, runtime, lensEnabledRef);
649
+ registerLensRuntimePart2(pi, getFlag, runtime, lensEnabledRef);
650
+ registerLensRuntimePart3(pi, getFlag, runtime, lensEnabledRef);
651
+ registerLensRuntimePart4(pi, getFlag, runtime, lensEnabledRef);
652
+ registerLensRuntimePart5(pi, getFlag, runtime, lensEnabledRef);
653
+ registerLensRuntimePart6(pi, getFlag, runtime, lensEnabledRef);
654
+ registerLensRuntimePart7(pi, getFlag, runtime, lensEnabledRef);
655
+ registerLensRuntimePart8(pi, getFlag, runtime, lensEnabledRef);
656
+ registerLensRuntimePart9(pi, getFlag, runtime, lensEnabledRef);
657
+ registerLensRuntimePart10(pi, getFlag, runtime, lensEnabledRef);
658
+ registerLensRuntimePart11(pi, getFlag, runtime, lensEnabledRef);
659
+ registerLensRuntimePart12(pi, getFlag, runtime, lensEnabledRef);
660
+ }
661
+
662
+ export default function harnessLensExtension(pi: ExtensionAPI): void {
663
+ initLensEvents(pi);
664
+ const globalConfig = loadPiLensGlobalConfig();
665
+ const lensEnabledRef = { current: !globalConfig.noLens };
666
+
667
+ type PiWithFlags = ExtensionAPI & {
668
+ getFlag?: (name: string) => boolean | string | undefined;
669
+ };
670
+ const piFlags = pi as PiWithFlags;
671
+ const readCliFlag = (name: string): boolean | undefined => {
672
+ if (typeof piFlags.getFlag === "function") {
673
+ const flag = piFlags.getFlag(name);
674
+ return typeof flag === "boolean" ? flag : undefined;
675
+ }
676
+ return process.argv.includes(`--${name}`) ? true : undefined;
677
+ };
678
+ const getFlag = (name: string) =>
679
+ getLensFlag(name, readCliFlag, globalConfig);
680
+
681
+ registerHarnessLensRuntime(pi, { getFlag, runtime, lensEnabledRef });
682
+ }