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
@@ -80,8 +80,12 @@ import {
80
80
  } from "../lib/harness-yaml.js";
81
81
  import { isReviewRoundArtifactPath } from "../lib/plan-debate-gate.js";
82
82
  import { isReviewRoundYamlWriteAllowed } from "../lib/plan-debate-write-guard.js";
83
+ import {
84
+ assertTaskClarificationReadyForPlanWrite,
85
+ readTaskClarificationDoc,
86
+ TASK_CLARIFICATION_ARTIFACT,
87
+ } from "../lib/plan-task-clarification.js";
83
88
 
84
- // @ts-expect-error pi extensions run as ESM
85
89
  const MODULE_URL = import.meta.url;
86
90
 
87
91
  interface SessionEntryLike {
@@ -113,6 +117,7 @@ const PLAN_REVISION_ARTIFACT_FILES = new Set([
113
117
  "plan-phase-status.yaml",
114
118
  "plan-phase-waiver.yaml",
115
119
  "sentrux-manifest-proposal.yaml",
120
+ "ls-lint-manifest-proposal.yaml",
116
121
  ]);
117
122
 
118
123
  const PLAN_REVISION_ARTIFACT_PREFIXES = [
@@ -455,8 +460,14 @@ async function maybeHandleClarificationFollowUp(input: {
455
460
  false,
456
461
  );
457
462
  persistContext(input.pi, input.activeCtx);
463
+ const amendHint = packet
464
+ ? "Reply with clarification answers; the harness will treat this as plan amend."
465
+ : `Reply with clarification answers; the harness will merge them into ${TASK_CLARIFICATION_ARTIFACT} and continue Phase 0 (task contract), not full planning yet.`;
466
+ const planBlock = packet
467
+ ? formatActivePlanBlock(input.activeCtx, "revise", summary)
468
+ : `[HarnessTaskClarification] status=needs_user — complete ${TASK_CLARIFICATION_ARTIFACT} before reconnaissance.`;
458
469
  return {
459
- systemPrompt: `${input.systemPrompt}\n\n${formatPlanContextBlock(input.activeCtx)}\n\n${formatActivePlanBlock(input.activeCtx, "revise", summary)}\n\nReply with clarification answers; the harness will treat this as plan amend.`,
470
+ systemPrompt: `${input.systemPrompt}\n\n${formatPlanContextBlock(input.activeCtx)}\n\n${planBlock}\n\n${amendHint}`,
460
471
  };
461
472
  }
462
473
 
@@ -1212,6 +1223,115 @@ async function resolveCommandRunContext(input: {
1212
1223
  return { activeCtx, resolved, response: null };
1213
1224
  }
1214
1225
 
1226
+ async function handlePreResolvedHarnessCommand(args: {
1227
+ pi: ExtensionAPI;
1228
+ activeCtx: HarnessRunContext | null;
1229
+ command: string;
1230
+ parsedArgs: string;
1231
+ userPrompt: string;
1232
+ systemPrompt: string;
1233
+ sessionId: string;
1234
+ projectRoot: string;
1235
+ entries: unknown[];
1236
+ driftActive: boolean;
1237
+ }): Promise<{
1238
+ activeCtx: HarnessRunContext | null;
1239
+ response: any;
1240
+ handled: boolean;
1241
+ }> {
1242
+ const {
1243
+ pi,
1244
+ activeCtx,
1245
+ command,
1246
+ parsedArgs,
1247
+ userPrompt,
1248
+ systemPrompt,
1249
+ sessionId,
1250
+ projectRoot,
1251
+ entries,
1252
+ driftActive,
1253
+ } = args;
1254
+ if (
1255
+ !isHarnessBootstrapPrompt(userPrompt) &&
1256
+ !hasHarnessAbortSignal(userPrompt)
1257
+ ) {
1258
+ const policyBlock = getPolicyTransitionBlock(userPrompt, entries);
1259
+ if (policyBlock.blocked) {
1260
+ return {
1261
+ activeCtx,
1262
+ response: blockRunContextMessage(
1263
+ policyBlock.message ?? "Harness command blocked by policy phase.",
1264
+ ),
1265
+ handled: true,
1266
+ };
1267
+ }
1268
+ }
1269
+ if (command === "harness-new-run") {
1270
+ const next = createNewRunContextForCommand({
1271
+ pi,
1272
+ activeCtx,
1273
+ sessionId,
1274
+ projectRoot,
1275
+ args: parsedArgs,
1276
+ userPrompt,
1277
+ systemPrompt,
1278
+ });
1279
+ return {
1280
+ activeCtx: next.activeCtx,
1281
+ response: next.response,
1282
+ handled: true,
1283
+ };
1284
+ }
1285
+ if (command === "harness-use-run") {
1286
+ const next = await bindExistingRunForCommand({
1287
+ pi,
1288
+ sessionId,
1289
+ projectRoot,
1290
+ entries,
1291
+ args: parsedArgs,
1292
+ systemPrompt,
1293
+ });
1294
+ return {
1295
+ activeCtx: next.activeCtx ?? activeCtx,
1296
+ response: next.response,
1297
+ handled: true,
1298
+ };
1299
+ }
1300
+ if (command === "harness-run-status") {
1301
+ return { activeCtx, response: undefined, handled: true };
1302
+ }
1303
+ if (
1304
+ command === "harness-plan" &&
1305
+ activeCtx &&
1306
+ isNewTaskPlanBlocked(activeCtx, userPrompt) &&
1307
+ !isAmendPlanAllowed(activeCtx, userPrompt, driftActive)
1308
+ ) {
1309
+ return {
1310
+ activeCtx,
1311
+ response: blockRunContextMessage(
1312
+ "Active harness run in progress. Use /harness-abort or /harness-new-run before starting a new task plan.",
1313
+ ),
1314
+ handled: true,
1315
+ };
1316
+ }
1317
+ return { activeCtx, response: null, handled: false };
1318
+ }
1319
+
1320
+ function blockingRunCommandReason(
1321
+ command: string,
1322
+ activeCtx: HarnessRunContext,
1323
+ ): string | null {
1324
+ if (command !== "harness-run") return null;
1325
+ if (!activeCtx.plan_ready) return "Plan not ready. Run /harness-plan first.";
1326
+ if (
1327
+ activeCtx.last_completed_step === "execute" &&
1328
+ activeCtx.last_outcome === "completed"
1329
+ ) {
1330
+ return "Execute already completed for this run. Next: /harness-review (same session), or /harness-abort to replan.";
1331
+ }
1332
+ return null;
1333
+ }
1334
+
1215
1335
  async function handleBeforeAgentStart(input: {
1216
1336
  pi: ExtensionAPI;
1217
1337
  event: any;
@@ -1260,52 +1380,22 @@ async function handleBeforeAgentStart(input: {
1260
1380
  }
1261
1381
  if (!parsed) return undefined;
1262
1382
  const { command, args } = parsed;
1263
- if (
1264
- !isHarnessBootstrapPrompt(userPrompt) &&
1265
- !hasHarnessAbortSignal(userPrompt)
1266
- ) {
1267
- const policyBlock = getPolicyTransitionBlock(userPrompt, entries);
1268
- if (policyBlock.blocked) {
1269
- return blockRunContextMessage(
1270
- policyBlock.message ?? "Harness command blocked by policy phase.",
1271
- );
1272
- }
1273
- }
1274
- if (command === "harness-new-run") {
1275
- const next = createNewRunContextForCommand({
1276
- pi: input.pi,
1277
- activeCtx,
1278
- sessionId,
1279
- projectRoot,
1280
- args,
1281
- userPrompt,
1282
- systemPrompt: input.event.systemPrompt,
1283
- });
1284
- input.active.set(next.activeCtx);
1285
- return next.response;
1286
- }
1287
- if (command === "harness-use-run") {
1288
- const next = await bindExistingRunForCommand({
1289
- pi: input.pi,
1290
- sessionId,
1291
- projectRoot,
1292
- entries,
1293
- args,
1294
- systemPrompt: input.event.systemPrompt,
1295
- });
1296
- if (next.activeCtx) input.active.set(next.activeCtx);
1297
- return next.response;
1298
- }
1299
- if (command === "harness-run-status") return undefined;
1300
- if (
1301
- command === "harness-plan" &&
1302
- activeCtx &&
1303
- isNewTaskPlanBlocked(activeCtx, userPrompt) &&
1304
- !isAmendPlanAllowed(activeCtx, userPrompt, driftActive)
1305
- ) {
1306
- return blockRunContextMessage(
1307
- "Active harness run in progress. Use /harness-abort or /harness-new-run before starting a new task plan.",
1308
- );
1383
+ const preResolved = await handlePreResolvedHarnessCommand({
1384
+ pi: input.pi,
1385
+ activeCtx,
1386
+ command,
1387
+ parsedArgs: args,
1388
+ userPrompt,
1389
+ systemPrompt: input.event.systemPrompt,
1390
+ sessionId,
1391
+ projectRoot,
1392
+ entries,
1393
+ driftActive,
1394
+ });
1395
+ activeCtx = preResolved.activeCtx;
1396
+ if (preResolved.handled) {
1397
+ input.active.set(activeCtx);
1398
+ return preResolved.response;
1309
1399
  }
1310
1400
  const prepared = await resolveCommandRunContext({
1311
1401
  pi: input.pi,
@@ -1343,18 +1433,8 @@ async function handleBeforeAgentStart(input: {
1343
1433
  return blockRunContextMessage(check.reason ?? "Invalid --plan override");
1344
1434
  activeCtx.plan_packet_path = resolved.planPath;
1345
1435
  }
1346
- if (command === "harness-run" && !activeCtx.plan_ready)
1347
- return blockRunContextMessage("Plan not ready. Run /harness-plan first.");
1348
- if (
1349
- command === "harness-run" &&
1350
- activeCtx.plan_ready &&
1351
- activeCtx.last_completed_step === "execute" &&
1352
- activeCtx.last_outcome === "completed"
1353
- ) {
1354
- return blockRunContextMessage(
1355
- "Execute already completed for this run. Next: /harness-review (same session), or /harness-abort to replan.",
1356
- );
1357
- }
1436
+ const runBlockReason = blockingRunCommandReason(command, activeCtx);
1437
+ if (runBlockReason) return blockRunContextMessage(runBlockReason);
1358
1438
  const { planSummary, planPacketForSpawn } =
1359
1439
  await readPlanSpawnState(activeCtx);
1360
1440
  const { activePlanBlock, planMode, contextSpawnOpts } =
@@ -1490,57 +1570,10 @@ async function handleAgentEnd(input: {
1490
1570
  }
1491
1571
  }
1492
1572
 
1493
- export default function harnessRunContext(pi: ExtensionAPI) {
1494
- if (!claimHarnessGovernanceLoad("harness-run-context", MODULE_URL)) return;
1495
- let activeCtx: HarnessRunContext | null = null;
1496
- const activeAccess: ActiveContextAccess = {
1497
- get: () => activeCtx,
1498
- set: (ctx) => {
1499
- activeCtx = ctx;
1500
- },
1501
- };
1502
-
1503
- pi.on("session_start", async (_event, ctx) => {
1504
- const entries = getEntries(ctx);
1505
- activeCtx = hydrateFromSession(entries);
1506
- const booted = await bootstrapHarnessSubprocessFromEnv(pi, ctx);
1507
- if (booted) activeCtx = booted;
1508
- if (!booted) await offerCrossSessionResume(pi, ctx);
1509
- });
1510
-
1511
- pi.on("input", async (event) => {
1512
- if (event.source === "extension") {
1513
- return { action: "continue" as const };
1514
- }
1515
- const parsed = parseHarnessSlashInput(event.text);
1516
- if (!parsed) {
1517
- return { action: "continue" as const };
1518
- }
1519
- appendHarnessTurn(pi, {
1520
- schema_version: "1.0.0",
1521
- command: parsed.command,
1522
- args: parsed.args,
1523
- source: "slash",
1524
- invoked_at: nowIso(),
1525
- });
1526
- return { action: "continue" as const };
1527
- });
1528
-
1529
- pi.on("before_agent_start", async (event, ctx) =>
1530
- handleBeforeAgentStart({ pi, event, ctx, active: activeAccess }),
1531
- );
1532
-
1533
- pi.on("agent_end", async (_event, ctx) => {
1534
- await handleAgentEnd({ pi, ctx, active: activeAccess });
1535
- });
1536
-
1537
- registerPlanApprovalCapture(pi, activeAccess);
1538
- registerHarnessToolCallGuards(pi, activeAccess);
1539
- registerHarnessRunStatusCommand(pi, activeAccess);
1540
- registerHarnessNewRunCommand(pi, activeAccess);
1541
-
1542
- registerHarnessPlanCommitCommand(pi, activeAccess);
1543
-
1573
+ function registerHarnessRunContextTool1(
1574
+ pi: ExtensionAPI,
1575
+ active: ActiveContextAccess,
1576
+ ) {
1544
1577
  pi.registerTool({
1545
1578
  name: "write_harness_yaml",
1546
1579
  label: "Write Harness YAML",
@@ -1566,7 +1599,7 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1566
1599
  }),
1567
1600
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1568
1601
  const entries = getEntries(ctx);
1569
- const runCtx = getLatestRunContext(entries) ?? activeCtx;
1602
+ const runCtx = getLatestRunContext(entries) ?? active.get();
1570
1603
  if (!runCtx?.run_id) {
1571
1604
  return {
1572
1605
  content: [
@@ -1665,6 +1698,24 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1665
1698
  isError: true,
1666
1699
  };
1667
1700
  }
1701
+ const runRootWrite = join(
1702
+ projectRoot,
1703
+ ".pi",
1704
+ "harness",
1705
+ "runs",
1706
+ runCtx.run_id,
1707
+ );
1708
+ const clarWrite = await assertTaskClarificationReadyForPlanWrite(
1709
+ runRootWrite,
1710
+ relForGate,
1711
+ );
1712
+ if (!clarWrite.ok) {
1713
+ return {
1714
+ content: [{ type: "text", text: clarWrite.message ?? "Blocked." }],
1715
+ details: { path: pathArg },
1716
+ isError: true,
1717
+ };
1718
+ }
1668
1719
  let doc: unknown;
1669
1720
  try {
1670
1721
  doc = parseStructuredDocument(content, pathArg);
@@ -1678,6 +1729,16 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1678
1729
  }
1679
1730
  await mkdir(dirname(absPath), { recursive: true });
1680
1731
  await writeYamlFile(absPath, doc);
1732
+ if (relForGate === TASK_CLARIFICATION_ARTIFACT) {
1733
+ const clarDoc = doc as Record<string, unknown>;
1734
+ if (String(clarDoc.status ?? "").toLowerCase() === "ready") {
1735
+ const clarified = String(clarDoc.clarified_task ?? "").trim();
1736
+ if (clarified) {
1737
+ runCtx.task_summary = clarified;
1738
+ persistContext(pi, runCtx);
1739
+ }
1740
+ }
1741
+ }
1681
1742
  return {
1682
1743
  content: [
1683
1744
  {
@@ -1689,7 +1750,12 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1689
1750
  };
1690
1751
  },
1691
1752
  });
1753
+ }
1692
1754
 
1755
+ function registerHarnessRunContextTool2(
1756
+ pi: ExtensionAPI,
1757
+ active: ActiveContextAccess,
1758
+ ) {
1693
1759
  pi.registerTool({
1694
1760
  name: "merge_harness_yaml",
1695
1761
  label: "Merge Harness YAML",
@@ -1720,7 +1786,7 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1720
1786
  }),
1721
1787
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1722
1788
  const entries = getEntries(ctx);
1723
- const runCtx = getLatestRunContext(entries) ?? activeCtx;
1789
+ const runCtx = getLatestRunContext(entries) ?? active.get();
1724
1790
  if (!runCtx?.run_id) {
1725
1791
  return {
1726
1792
  content: [{ type: "text", text: "No active harness run." }],
@@ -1767,6 +1833,18 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1767
1833
  "runs",
1768
1834
  runCtx.run_id,
1769
1835
  );
1836
+ const relMerge = pathArg.replace(/\\/g, "/");
1837
+ const clarMerge = await assertTaskClarificationReadyForPlanWrite(
1838
+ runRoot,
1839
+ relMerge,
1840
+ );
1841
+ if (!clarMerge.ok) {
1842
+ return {
1843
+ content: [{ type: "text", text: clarMerge.message ?? "Blocked." }],
1844
+ details: { path: pathArg },
1845
+ isError: true,
1846
+ };
1847
+ }
1770
1848
  let existing: Record<string, unknown> = {};
1771
1849
  try {
1772
1850
  const { readYamlFile } = await import("../lib/harness-yaml.js");
@@ -1825,7 +1903,12 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1825
1903
  };
1826
1904
  },
1827
1905
  });
1906
+ }
1828
1907
 
1908
+ function registerHarnessRunContextTool3(
1909
+ pi: ExtensionAPI,
1910
+ active: ActiveContextAccess,
1911
+ ) {
1829
1912
  pi.registerTool({
1830
1913
  name: "harness_synthesize_repair_brief",
1831
1914
  label: "Synthesize Repair Brief",
@@ -1850,7 +1933,7 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1850
1933
  }),
1851
1934
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1852
1935
  const entries = getEntries(ctx);
1853
- const runCtx = getLatestRunContext(entries) ?? activeCtx;
1936
+ const runCtx = getLatestRunContext(entries) ?? active.get();
1854
1937
  if (!runCtx?.run_id) {
1855
1938
  return {
1856
1939
  content: [{ type: "text", text: "No active harness run." }],
@@ -1920,7 +2003,12 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1920
2003
  };
1921
2004
  },
1922
2005
  });
2006
+ }
1923
2007
 
2008
+ function registerHarnessRunContextTool4(
2009
+ pi: ExtensionAPI,
2010
+ active: ActiveContextAccess,
2011
+ ) {
1924
2012
  pi.registerTool({
1925
2013
  name: "harness_artifact_ready",
1926
2014
  label: "Harness Artifact Ready",
@@ -1935,7 +2023,7 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1935
2023
  }),
1936
2024
  async execute(_id, params, _signal, _onUpdate, ctx) {
1937
2025
  const entries = getEntries(ctx);
1938
- const runCtx = getLatestRunContext(entries) ?? activeCtx;
2026
+ const runCtx = getLatestRunContext(entries) ?? active.get();
1939
2027
  if (!runCtx?.run_id) {
1940
2028
  return {
1941
2029
  content: [{ type: "text", text: "No active harness run." }],
@@ -1957,6 +2045,17 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1957
2045
  "../lib/harness-artifact-gate.js"
1958
2046
  );
1959
2047
  const gate = await validateHarnessArtifactPaths(runRoot, paths, specsDir);
2048
+ if (
2049
+ gate.ok &&
2050
+ paths.some((p) => p.replace(/\\/g, "/") === TASK_CLARIFICATION_ARTIFACT)
2051
+ ) {
2052
+ const clarDoc = await readTaskClarificationDoc(runRoot);
2053
+ const clarified = String(clarDoc?.clarified_task ?? "").trim();
2054
+ if (clarified && runCtx.task_summary !== clarified) {
2055
+ runCtx.task_summary = clarified;
2056
+ persistContext(pi, runCtx);
2057
+ }
2058
+ }
1960
2059
  const text = gate.ok
1961
2060
  ? `All ${gate.present.length} artifact(s) present and valid.`
1962
2061
  : [
@@ -1980,6 +2079,70 @@ export default function harnessRunContext(pi: ExtensionAPI) {
1980
2079
  };
1981
2080
  },
1982
2081
  });
2082
+ }
2083
+
2084
+ function registerHarnessRunContextTools(
2085
+ pi: ExtensionAPI,
2086
+ active: ActiveContextAccess,
2087
+ ) {
2088
+ registerHarnessRunContextTool1(pi, active);
2089
+ registerHarnessRunContextTool2(pi, active);
2090
+ registerHarnessRunContextTool3(pi, active);
2091
+ registerHarnessRunContextTool4(pi, active);
2092
+ }
2093
+
2094
+ export default function harnessRunContext(pi: ExtensionAPI) {
2095
+ if (!claimHarnessGovernanceLoad("harness-run-context", MODULE_URL)) return;
2096
+ let activeCtx: HarnessRunContext | null = null;
2097
+ const activeAccess: ActiveContextAccess = {
2098
+ get: () => activeCtx,
2099
+ set: (ctx) => {
2100
+ activeCtx = ctx;
2101
+ },
2102
+ };
2103
+
2104
+ pi.on("session_start", async (_event, ctx) => {
2105
+ const entries = getEntries(ctx);
2106
+ activeCtx = hydrateFromSession(entries);
2107
+ const booted = await bootstrapHarnessSubprocessFromEnv(pi, ctx);
2108
+ if (booted) activeCtx = booted;
2109
+ if (!booted) await offerCrossSessionResume(pi, ctx);
2110
+ });
2111
+
2112
+ pi.on("input", async (event) => {
2113
+ if (event.source === "extension") {
2114
+ return { action: "continue" as const };
2115
+ }
2116
+ const parsed = parseHarnessSlashInput(event.text);
2117
+ if (!parsed) {
2118
+ return { action: "continue" as const };
2119
+ }
2120
+ appendHarnessTurn(pi, {
2121
+ schema_version: "1.0.0",
2122
+ command: parsed.command,
2123
+ args: parsed.args,
2124
+ source: "slash",
2125
+ invoked_at: nowIso(),
2126
+ });
2127
+ return { action: "continue" as const };
2128
+ });
2129
+
2130
+ pi.on("before_agent_start", async (event, ctx) =>
2131
+ handleBeforeAgentStart({ pi, event, ctx, active: activeAccess }),
2132
+ );
2133
+
2134
+ pi.on("agent_end", async (_event, ctx) => {
2135
+ await handleAgentEnd({ pi, ctx, active: activeAccess });
2136
+ });
2137
+
2138
+ registerPlanApprovalCapture(pi, activeAccess);
2139
+ registerHarnessToolCallGuards(pi, activeAccess);
2140
+ registerHarnessRunStatusCommand(pi, activeAccess);
2141
+ registerHarnessNewRunCommand(pi, activeAccess);
2142
+
2143
+ registerHarnessPlanCommitCommand(pi, activeAccess);
2144
+
2145
+ registerHarnessRunContextTools(pi, activeAccess);
1983
2146
 
1984
2147
  registerHarnessUseRunCommand(pi, activeAccess);
1985
2148
  }