ultimate-pi 0.19.1 → 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 (147) 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 +43 -2
  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 +139 -0
  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 +47 -81
  34. package/.pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md +41 -0
  35. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  36. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  37. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  38. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  39. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  40. package/.pi/harness/docs/adrs/README.md +7 -0
  41. package/.pi/harness/docs/practice-map.md +21 -5
  42. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  43. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  44. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  45. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  46. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  47. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  48. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  49. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  50. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  51. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  52. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  53. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  54. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  55. package/.pi/lib/agents-policy.d.mts +26 -47
  56. package/.pi/lib/agents-policy.mjs +84 -29
  57. package/.pi/lib/agents-policy.ts +1 -0
  58. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  59. package/.pi/lib/ask-user/constants.mjs +3 -0
  60. package/.pi/lib/ask-user/constants.ts +4 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  62. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  63. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  64. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  65. package/.pi/lib/ask-user/dialog.ts +2 -314
  66. package/.pi/lib/ask-user/fallback.ts +2 -78
  67. package/.pi/lib/ask-user/format.ts +85 -0
  68. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  69. package/.pi/lib/ask-user/index.ts +114 -0
  70. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  71. package/.pi/lib/ask-user/policy.mjs +43 -0
  72. package/.pi/lib/ask-user/policy.ts +104 -0
  73. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  74. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  75. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  76. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  77. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  78. package/.pi/lib/ask-user/render.ts +40 -9
  79. package/.pi/lib/ask-user/schema.ts +66 -13
  80. package/.pi/lib/ask-user/types.ts +60 -3
  81. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  82. package/.pi/lib/ask-user/validate.ts +53 -34
  83. package/.pi/lib/harness-anchored-edit/.hash_anchors +1721 -0
  84. package/.pi/lib/harness-anchored-edit/anchor-state.ts +320 -0
  85. package/.pi/lib/harness-anchored-edit/apply-anchored-edits.ts +161 -0
  86. package/.pi/lib/harness-anchored-edit/edit-executor.ts +146 -0
  87. package/.pi/lib/harness-anchored-edit/index.ts +9 -0
  88. package/.pi/lib/harness-anchored-edit/line-protocol.ts +38 -0
  89. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  90. package/.pi/lib/harness-anchored-edit/settings.ts +1 -0
  91. package/.pi/lib/harness-anchored-edit/task-id.ts +8 -0
  92. package/.pi/lib/harness-anchored-edit/types.ts +19 -0
  93. package/.pi/lib/harness-artifact-gate.ts +75 -21
  94. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  95. package/.pi/lib/harness-lens/clients/anchored-edit-autopatch.ts +158 -0
  96. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  97. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  98. package/.pi/lib/harness-lens/index.ts +246 -96
  99. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  100. package/.pi/lib/harness-repair-brief.ts +84 -25
  101. package/.pi/lib/harness-run-context.ts +42 -52
  102. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  103. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  104. package/.pi/lib/harness-slash-completions.ts +116 -0
  105. package/.pi/lib/harness-spawn-topology.ts +121 -87
  106. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  107. package/.pi/lib/harness-subagents-bridge.ts +11 -6
  108. package/.pi/lib/harness-ui-state.ts +95 -48
  109. package/.pi/lib/plan-approval/dialog.ts +5 -0
  110. package/.pi/lib/plan-approval/validate.ts +1 -1
  111. package/.pi/lib/plan-approval-readiness.ts +32 -0
  112. package/.pi/lib/plan-debate-gate.ts +154 -114
  113. package/.pi/lib/plan-task-clarification.ts +158 -0
  114. package/.pi/prompts/harness-auto.md +2 -2
  115. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  116. package/.pi/prompts/harness-plan.md +58 -8
  117. package/.pi/prompts/harness-review.md +40 -6
  118. package/.pi/prompts/harness-run.md +33 -11
  119. package/.pi/prompts/harness-setup.md +72 -3
  120. package/.pi/prompts/harness-steer.md +3 -2
  121. package/.pi/prompts/wiki-save.md +5 -4
  122. package/.pi/scripts/README.md +8 -0
  123. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  124. package/.pi/scripts/harness-anchored-edit-smoke.mjs +45 -0
  125. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  126. package/.pi/scripts/harness-cli-verify.sh +47 -0
  127. package/.pi/scripts/harness-git-churn.mjs +77 -0
  128. package/.pi/scripts/harness-git-commit.mjs +173 -0
  129. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  130. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  131. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  132. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  133. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  134. package/.pi/scripts/harness-verify.mjs +347 -117
  135. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  136. package/.pi/scripts/run-tests.mjs +65 -0
  137. package/.pi/settings.example.json +1 -0
  138. package/.sentrux/rules.toml +1 -1
  139. package/AGENTS.md +1 -0
  140. package/CHANGELOG.md +31 -0
  141. package/README.md +13 -4
  142. package/THIRD_PARTY_NOTICES.md +7 -0
  143. package/package.json +8 -3
  144. package/vendor/pi-subagents/src/agents.ts +5 -0
  145. package/vendor/pi-subagents/src/subagents.ts +22 -3
  146. package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
  147. package/.pi/scripts/release.sh +0 -338
@@ -7,6 +7,11 @@ import * as nodeFs from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
10
+ import { anchoredEditTaskId } from "../harness-anchored-edit/task-id.js";
11
+ import {
12
+ applyAnchoredEditAutopatch,
13
+ isAnchoredEditToolInput,
14
+ } from "./clients/anchored-edit-autopatch.js";
10
15
  import {
11
16
  tryCorrectIndentationMismatch,
12
17
  tryCorrectIndentationMismatchFromContent,
@@ -331,57 +336,101 @@ function applyEditAutopatch(
331
336
  return undefined;
332
337
  }
333
338
 
334
- export default function harnessLensExtension(pi: ExtensionAPI): void {
335
- initLensEvents(pi);
336
- const globalConfig = loadPiLensGlobalConfig();
337
- let lensEnabled = !globalConfig.noLens;
338
-
339
- type PiWithFlags = ExtensionAPI & {
340
- getFlag?: (name: string) => boolean | undefined;
341
- };
342
- const piFlags = pi as PiWithFlags;
343
- const readCliFlag = (name: string): boolean | undefined => {
344
- if (typeof piFlags.getFlag === "function") return piFlags.getFlag(name);
345
- return process.argv.includes(`--${name}`) ? true : undefined;
346
- };
347
- const getFlag = (name: string) =>
348
- getLensFlag(name, readCliFlag, globalConfig);
349
-
339
+ function registerLensRuntimePart1(
340
+ pi: ExtensionAPI,
341
+ getFlag: (name: string) => boolean | string | undefined,
342
+ runtime: RuntimeCoordinator,
343
+ lensEnabledRef: { current: boolean },
344
+ ) {
350
345
  pi.registerFlag("no-lens", {
351
346
  description: "Disable harness-lens for this session.",
352
347
  type: "boolean",
353
348
  default: false,
354
349
  });
350
+ }
351
+
352
+ function registerLensRuntimePart2(
353
+ pi: ExtensionAPI,
354
+ getFlag: (name: string) => boolean | string | undefined,
355
+ runtime: RuntimeCoordinator,
356
+ lensEnabledRef: { current: boolean },
357
+ ) {
355
358
  pi.registerFlag("no-lsp", {
356
359
  description: "Disable LSP auto-touch and lsp_* tools backing servers.",
357
360
  type: "boolean",
358
361
  default: false,
359
362
  });
363
+ }
364
+
365
+ function registerLensRuntimePart3(
366
+ pi: ExtensionAPI,
367
+ getFlag: (name: string) => boolean | string | undefined,
368
+ runtime: RuntimeCoordinator,
369
+ lensEnabledRef: { current: boolean },
370
+ ) {
360
371
  pi.registerFlag("no-autoformat", {
361
372
  description: "Disable auto-format on write/edit.",
362
373
  type: "boolean",
363
374
  default: false,
364
375
  });
376
+ }
377
+
378
+ function registerLensRuntimePart4(
379
+ pi: ExtensionAPI,
380
+ getFlag: (name: string) => boolean | string | undefined,
381
+ runtime: RuntimeCoordinator,
382
+ lensEnabledRef: { current: boolean },
383
+ ) {
365
384
  pi.registerFlag("immediate-format", {
366
385
  description: "Format during tool_result instead of deferring to agent_end.",
367
386
  type: "boolean",
368
387
  default: false,
369
388
  });
389
+ }
390
+
391
+ function registerLensRuntimePart5(
392
+ pi: ExtensionAPI,
393
+ getFlag: (name: string) => boolean | string | undefined,
394
+ runtime: RuntimeCoordinator,
395
+ lensEnabledRef: { current: boolean },
396
+ ) {
370
397
  pi.registerFlag("lens-guard", {
371
398
  description: "Block git commit/push while unresolved lens blockers exist.",
372
399
  type: "boolean",
373
400
  default: false,
374
401
  });
402
+ }
375
403
 
404
+ function registerLensRuntimePart6(
405
+ pi: ExtensionAPI,
406
+ getFlag: (name: string) => boolean | string | undefined,
407
+ runtime: RuntimeCoordinator,
408
+ lensEnabledRef: { current: boolean },
409
+ ) {
376
410
  pi.registerTool(createLspDiagnosticsTool());
377
- pi.registerTool(createLspNavigationTool());
411
+ }
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
+ }
378
421
 
422
+ function registerLensRuntimePart8(
423
+ pi: ExtensionAPI,
424
+ getFlag: (name: string) => boolean | string | undefined,
425
+ runtime: RuntimeCoordinator,
426
+ lensEnabledRef: { current: boolean },
427
+ ) {
379
428
  pi.on("session_start", async (_event, ctx) => {
380
429
  if (getFlag("no-lens")) {
381
- lensEnabled = false;
430
+ lensEnabledRef.current = false;
382
431
  return;
383
432
  }
384
- lensEnabled = true;
433
+ lensEnabledRef.current = true;
385
434
  runtime.resetForSession();
386
435
  clearWidgetState();
387
436
  resetFormatService();
@@ -411,15 +460,126 @@ export default function harnessLensExtension(pi: ExtensionAPI): void {
411
460
  });
412
461
  }
413
462
  });
463
+ }
414
464
 
465
+ function registerLensRuntimePart9(
466
+ pi: ExtensionAPI,
467
+ getFlag: (name: string) => boolean | string | undefined,
468
+ runtime: RuntimeCoordinator,
469
+ lensEnabledRef: { current: boolean },
470
+ ) {
415
471
  pi.on("turn_start", () => {
416
- if (!lensEnabled) return;
472
+ if (!lensEnabledRef.current) return;
417
473
  runtime.beginTurn();
418
474
  clearLastAnalyzedStateCache();
419
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
+ }
420
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
+ ) {
421
581
  pi.on("tool_call", async (event, ctx) => {
422
- if (!lensEnabled) return;
582
+ if (!lensEnabledRef.current) return;
423
583
 
424
584
  const toolName = (event as { toolName?: string }).toolName ?? "";
425
585
  if (
@@ -437,88 +597,33 @@ export default function harnessLensExtension(pi: ExtensionAPI): void {
437
597
  runtime.projectRoot,
438
598
  );
439
599
 
440
- if (!getFlag("no-lsp")) {
441
- try {
442
- await ensureLSPConfigInitialized(
443
- filePath ? path.dirname(filePath) : (ctx.cwd ?? runtime.projectRoot),
444
- );
445
- } catch (err) {
446
- dbg(`tool_call lsp config init failed: ${err}`);
447
- }
448
- }
449
-
600
+ await ensureToolCallLspConfig({ getFlag, filePath, ctx, runtime });
450
601
  if (!filePath || !nodeFs.existsSync(filePath)) return;
451
602
  if (isPathIgnoredByProject(filePath, runtime.projectRoot, false)) return;
452
603
 
453
- if (
454
- !getFlag("no-lsp") &&
455
- isLspCapableFile(filePath) &&
456
- !shouldSkipLspAutoTouch(filePath, runtime.projectRoot)
457
- ) {
458
- const shouldWarmRead =
459
- toolName === "read" && runtime.shouldWarmLspOnRead(filePath);
460
- const shouldTouch =
461
- toolName === "write" ||
462
- toolName === "edit" ||
463
- toolName === "lsp_navigation" ||
464
- shouldWarmRead;
465
- if (shouldTouch) {
466
- try {
467
- const content = nodeFs.readFileSync(filePath, "utf-8");
468
- if (toolName === "read") runtime.markLspReadWarmStarted(filePath);
469
- void getLSPService()
470
- .touchFile(filePath, content, {
471
- diagnostics: "none",
472
- source: `tool_call:${toolName}`,
473
- })
474
- .then((result) => {
475
- if (toolName === "read") {
476
- if (result === undefined) {
477
- runtime.clearLspReadWarmState(filePath);
478
- } else {
479
- runtime.markLspReadWarmCompleted(filePath);
480
- }
481
- }
482
- })
483
- .catch((err) => {
484
- if (toolName === "read") runtime.clearLspReadWarmState(filePath);
485
- dbg(`lsp auto-touch failed: ${err}`);
486
- });
487
- } catch {
488
- if (toolName === "read") runtime.clearLspReadWarmState(filePath);
489
- }
490
- }
491
- }
492
-
493
- if (
494
- isToolCallEventType(
495
- "edit",
496
- event as Parameters<typeof isToolCallEventType>[1],
497
- )
498
- ) {
499
- const editInput = (event as { input?: unknown }).input as {
500
- oldText?: string;
501
- newText?: string;
502
- edits?: Array<{ oldText?: string; newText?: string }>;
503
- };
504
- const block = applyEditAutopatch(filePath, editInput);
505
- if (block) return block;
506
- }
604
+ maybeAutoTouchLspOnToolCall({ getFlag, toolName, filePath, runtime });
605
+ const block = applyEditAutopatchForToolCall(filePath, event, ctx);
606
+ if (block) return block;
507
607
  });
608
+ }
508
609
 
509
- pi.on("tool_result", async (event, _ctx) => {
510
- if (!lensEnabled) return;
511
- return handleToolResult({
512
- event: event as Parameters<typeof handleToolResult>[0]["event"],
513
- getFlag,
514
- dbg,
515
- runtime,
516
- resetLSPService,
517
- });
518
- });
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
+ }
519
618
 
619
+ function registerLensRuntimePart12(
620
+ pi: ExtensionAPI,
621
+ getFlag: (name: string) => boolean | string | undefined,
622
+ runtime: RuntimeCoordinator,
623
+ lensEnabledRef: { current: boolean },
624
+ ) {
520
625
  pi.on("agent_end", async (_event, ctx) => {
521
- if (!lensEnabled) return;
626
+ if (!lensEnabledRef.current) return;
522
627
  await handleAgentEnd({
523
628
  ctxCwd: ctx.cwd,
524
629
  getFlag,
@@ -530,3 +635,48 @@ export default function harnessLensExtension(pi: ExtensionAPI): void {
530
635
  });
531
636
  });
532
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
+ }
@@ -723,6 +723,15 @@ function buildNavigationOutput(args: {
723
723
  return { output, actionStats, isEmpty, resultCount };
724
724
  }
725
725
 
726
+ function isDirectoryPath(filePath: string): boolean {
727
+ if (!filePath) return false;
728
+ try {
729
+ return nodeFs.statSync(filePath).isDirectory();
730
+ } catch {
731
+ return false;
732
+ }
733
+ }
734
+
726
735
  export function createLspNavigationTool(
727
736
  getFlag: (name: string) => boolean | string | undefined,
728
737
  ) {
@@ -1021,14 +1030,7 @@ export function createLspNavigationTool(
1021
1030
  : path.resolve(ctx.cwd || ".", rawPath)
1022
1031
  : "";
1023
1032
 
1024
- let filePathIsDirectory = false;
1025
- if (filePath) {
1026
- try {
1027
- filePathIsDirectory = nodeFs.statSync(filePath).isDirectory();
1028
- } catch {
1029
- // non-existent path — existing error paths handle this
1030
- }
1031
- }
1033
+ const filePathIsDirectory = isDirectoryPath(filePath);
1032
1034
 
1033
1035
  const lspService = getLSPService();
1034
1036
  if (operation === "workspaceDiagnostics") {
@@ -64,27 +64,23 @@ export async function synthesizeRepairBrief(
64
64
  const planRel =
65
65
  input.planPacketPath?.replace(/\\/g, "/") ?? "plan-packet.yaml";
66
66
  const plan = await readArtifactYaml(runRoot, planRel, "plan-packet");
67
+ const sentruxRepair = await readArtifactYaml(
68
+ runRoot,
69
+ "artifacts/sentrux-repair-plan.yaml",
70
+ "sentrux-repair-plan",
71
+ );
67
72
 
68
73
  const remediation =
69
74
  (typeof review?.remediation_class === "string" &&
70
75
  review.remediation_class) ||
71
76
  "implementation_gap";
72
77
 
73
- const sourceArtifacts: Record<string, string> = {
74
- "review-outcome":
75
- input.reviewOutcomePath ?? "artifacts/review-outcome.yaml",
76
- };
77
- if (evalDoc) {
78
- sourceArtifacts["eval-verdict"] =
79
- input.evalVerdictPath ?? "artifacts/eval-verdict.yaml";
80
- }
81
- if (adversary) {
82
- sourceArtifacts["adversary-report"] =
83
- input.adversaryReportPath ?? "artifacts/adversary-report.yaml";
84
- }
85
- if (plan) {
86
- sourceArtifacts["plan-packet"] = planRel;
87
- }
78
+ const sourceArtifacts = buildSourceArtifacts(input, planRel, {
79
+ evalDoc,
80
+ adversary,
81
+ plan,
82
+ sentruxRepair,
83
+ });
88
84
 
89
85
  const failedIds = [
90
86
  ...stringList(review?.failed_acceptance_check_ids),
@@ -93,7 +89,7 @@ export async function synthesizeRepairBrief(
93
89
  ];
94
90
  const uniqueFailed = [...new Set(failedIds)];
95
91
 
96
- const fixDirectives: string[] = [];
92
+ const fixDirectives: string[] = sentruxFixDirectives(sentruxRepair);
97
93
  for (const key of [
98
94
  "fix_directives",
99
95
  "repair_directives",
@@ -117,15 +113,7 @@ export async function synthesizeRepairBrief(
117
113
  );
118
114
  }
119
115
 
120
- const execPlan = asRecord(plan?.execution_plan);
121
- const priorityLakeIds = stringList(execPlan?.critical_path_lake_ids);
122
- if (priorityLakeIds.length === 0) {
123
- const lakes = Array.isArray(execPlan?.lakes) ? execPlan.lakes : [];
124
- for (const lake of lakes) {
125
- const l = asRecord(lake);
126
- if (l && typeof l.id === "string") priorityLakeIds.push(l.id);
127
- }
128
- }
116
+ const priorityLakeIds = collectPriorityLakeIds(plan);
129
117
 
130
118
  const brief: Record<string, unknown> = {
131
119
  schema_version: REPAIR_BRIEF_SCHEMA,
@@ -143,3 +131,74 @@ export async function synthesizeRepairBrief(
143
131
  }
144
132
  return brief;
145
133
  }
134
+
135
+ function buildSourceArtifacts(
136
+ input: SynthesizeRepairBriefInput,
137
+ planRel: string,
138
+ docs: {
139
+ evalDoc: Record<string, unknown> | null;
140
+ adversary: Record<string, unknown> | null;
141
+ plan: Record<string, unknown> | null;
142
+ sentruxRepair: Record<string, unknown> | null;
143
+ },
144
+ ): Record<string, string> {
145
+ const sourceArtifacts: Record<string, string> = {
146
+ "review-outcome":
147
+ input.reviewOutcomePath ?? "artifacts/review-outcome.yaml",
148
+ };
149
+ if (docs.evalDoc)
150
+ sourceArtifacts["eval-verdict"] =
151
+ input.evalVerdictPath ?? "artifacts/eval-verdict.yaml";
152
+ if (docs.adversary)
153
+ sourceArtifacts["adversary-report"] =
154
+ input.adversaryReportPath ?? "artifacts/adversary-report.yaml";
155
+ if (docs.plan) sourceArtifacts["plan-packet"] = planRel;
156
+ if (docs.sentruxRepair)
157
+ sourceArtifacts["sentrux-repair-plan"] =
158
+ "artifacts/sentrux-repair-plan.yaml";
159
+ return sourceArtifacts;
160
+ }
161
+
162
+ function sentruxFixDirectives(
163
+ sentruxRepair: Record<string, unknown> | null,
164
+ ): string[] {
165
+ if (!sentruxRepair) return [];
166
+ const out: string[] = [];
167
+ const actions = Array.isArray(sentruxRepair.actions)
168
+ ? sentruxRepair.actions
169
+ : [];
170
+ for (const raw of actions) {
171
+ const action = asRecord(raw);
172
+ if (!action) continue;
173
+ const id = typeof action.id === "string" ? action.id : "action";
174
+ const target = typeof action.target === "string" ? action.target : "";
175
+ const instruction =
176
+ typeof action.instruction === "string" ? action.instruction : "";
177
+ if (instruction)
178
+ out.push(`[sentrux:${id}] ${target}: ${instruction}`.trim());
179
+ }
180
+ if (
181
+ typeof sentruxRepair.summary === "string" &&
182
+ sentruxRepair.summary.trim()
183
+ ) {
184
+ out.unshift(`[sentrux] ${sentruxRepair.summary.trim()}`);
185
+ }
186
+ for (const v of stringList(sentruxRepair.verification)) {
187
+ out.push(`[sentrux:verify] ${v}`);
188
+ }
189
+ return out;
190
+ }
191
+
192
+ function collectPriorityLakeIds(
193
+ plan: Record<string, unknown> | null,
194
+ ): string[] {
195
+ const execPlan = asRecord(plan?.execution_plan);
196
+ const ids = stringList(execPlan?.critical_path_lake_ids);
197
+ if (ids.length > 0) return ids;
198
+ const lakes = Array.isArray(execPlan?.lakes) ? execPlan.lakes : [];
199
+ for (const lake of lakes) {
200
+ const record = asRecord(lake);
201
+ if (record && typeof record.id === "string") ids.push(record.id);
202
+ }
203
+ return ids;
204
+ }