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
@@ -20,10 +20,25 @@ You are a prompt templates expert for the Pi coding agent. You know EVERYTHING a
20
20
  ```markdown
21
21
  ---
22
22
  description: What this template does
23
+ argument-hint: "<required>" [optional flags]
23
24
  ---
24
25
  Your prompt content here with $1 and $@ arguments
25
26
  ```
26
27
 
28
+ ### Autocomplete (`description` + `argument-hint`)
29
+
30
+ Pi shows both in the `/` menu ([prompt-templates.md](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/prompt-templates.md)):
31
+
32
+ - `description` — what the command does (required for shipped ultimate-pi prompts).
33
+ - `argument-hint` — shown **before** the description in the menu.
34
+ - `<angle brackets>` — required arguments
35
+ - `[square brackets]` — optional arguments
36
+ - Omit `argument-hint` entirely when the command takes no user arguments (do not use `argument-hint: ""`).
37
+
38
+ Example menu line: `→ plan "<task>" [--quick] — PM-grade harness plan…`
39
+
40
+ **Extension-only commands** (no `.md` template) use `pi.registerCommand({ getArgumentCompletions })` — see `.pi/lib/harness-slash-completions.ts` and [extensions.md](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/extensions.md). `harness-verify` enforces prompt frontmatter on shipped `.pi/prompts/*.md`.
41
+
27
42
  ### Arguments
28
43
 
29
44
  - `$1`, `$2`, ... — positional arguments
@@ -61,8 +76,8 @@ Your prompt content here with $1 and $@ arguments
61
76
 
62
77
  ### Description
63
78
 
64
- - Optional frontmatter field
65
- - If missing, first non-empty line is used as description
79
+ - Required on ultimate-pi shipped prompts (`harness-verify` checks)
80
+ - If missing upstream, Pi falls back to the first non-empty body line
66
81
  - Shown in autocomplete when typing `/`
67
82
 
68
83
  ## CRITICAL: First Action
@@ -2,7 +2,8 @@
2
2
  "dryRun": false,
3
3
  "coAuthor": {
4
4
  "login": "pi-mono",
5
- "email": "261679550+pi-mono@users.noreply.github.com"
5
+ "email": "261679550+pi-mono@users.noreply.github.com",
6
+ "required": true
6
7
  },
7
8
  "branch": {
8
9
  "strategy": "auto-feature-branch",
@@ -15,6 +16,12 @@
15
16
  "ignore": true
16
17
  },
17
18
  "message": {
18
- "scopeDefault": "harness"
19
+ "template": "{type}({scope}): {subject}",
20
+ "templateNoScope": "{type}: {subject}",
21
+ "typeDefault": "chore",
22
+ "scopeDefault": "harness",
23
+ "bodySeparator": "\n\n",
24
+ "coAuthorTrailer": "Co-authored-by: {login} <{email}>",
25
+ "maxSubjectLength": 72
19
26
  }
20
27
  }
@@ -18,6 +18,7 @@ import {
18
18
  } from "../lib/debate-bus-state.js";
19
19
  import { isHarnessProjectEnabled } from "../lib/harness-project-config.js";
20
20
  import { getRunIdFromSession } from "../lib/harness-run-context.js";
21
+ import { completeDebateOpen } from "../lib/harness-slash-completions.js";
21
22
  import { normalizePlanDebateId } from "../lib/plan-debate-id.js";
22
23
  import { initPlanMessenger } from "../lib/plan-messenger.js";
23
24
 
@@ -55,6 +56,8 @@ export default function debateOrchestrator(pi: ExtensionAPI) {
55
56
 
56
57
  pi.registerCommand("harness-debate-open", {
57
58
  description: "Open a headless debate session",
59
+ getArgumentCompletions: (prefix) =>
60
+ completeDebateOpen(prefix, process.cwd()),
58
61
  handler: async (args, ctx) => {
59
62
  const runId = getRunId(ctx);
60
63
  const trimmed = args.trim();
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Hash-anchored read/edit — first-class harness read and edit tools (always on).
3
+ * @see .pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md
4
+ */
5
+ import { resolve } from "node:path";
6
+ import type { TextContent } from "@earendil-works/pi-ai";
7
+ import {
8
+ createReadTool,
9
+ type ExtensionAPI,
10
+ } from "@earendil-works/pi-coding-agent";
11
+ import { Type } from "typebox";
12
+ import {
13
+ anchoredEditTaskId,
14
+ applyAnchoredEditsToFile,
15
+ hashLinesStateful,
16
+ } from "../lib/harness-anchored-edit/index.js";
17
+ import type { AnchoredEdit } from "../lib/harness-anchored-edit/types.js";
18
+
19
+ const anchoredEditEntrySchema = Type.Object({
20
+ anchor: Type.String({
21
+ description:
22
+ "Start anchor from read output, format Word§exact line text (e.g. Apple§const x = 1).",
23
+ }),
24
+ end_anchor: Type.Optional(
25
+ Type.String({
26
+ description: "End anchor for replace ranges (same format as anchor).",
27
+ }),
28
+ ),
29
+ edit_type: Type.Optional(
30
+ Type.Union([
31
+ Type.Literal("replace"),
32
+ Type.Literal("insert_after"),
33
+ Type.Literal("insert_before"),
34
+ ]),
35
+ ),
36
+ text: Type.String({ description: "New text for insert/replace." }),
37
+ });
38
+
39
+ const anchoredEditSchema = Type.Object({
40
+ path: Type.String({ description: "Path to the file to edit." }),
41
+ edits: Type.Array(anchoredEditEntrySchema, {
42
+ description:
43
+ "Batch all edits for this file in one call. Use anchors from the latest read.",
44
+ }),
45
+ });
46
+
47
+ const readSchema = Type.Object({
48
+ path: Type.String({ description: "Path to the file to read." }),
49
+ offset: Type.Optional(Type.Number({ description: "1-based start line." })),
50
+ limit: Type.Optional(Type.Number({ description: "Max lines to read." })),
51
+ });
52
+
53
+ function stripAnchoredFromReadOutput(text: string): string {
54
+ return text
55
+ .split("\n")
56
+ .map((line) => {
57
+ const idx = line.indexOf("§");
58
+ if (idx === -1) return line;
59
+ const prefix = line.slice(0, idx);
60
+ if (/^[A-Z][a-zA-Z]*$/.test(prefix)) {
61
+ return line.slice(idx + 1);
62
+ }
63
+ return line;
64
+ })
65
+ .join("\n");
66
+ }
67
+
68
+ export default function harnessAnchoredEdit(pi: ExtensionAPI): void {
69
+ const readToolByCwd = new Map<string, ReturnType<typeof createReadTool>>();
70
+
71
+ function getReadTool(cwd: string) {
72
+ let tool = readToolByCwd.get(cwd);
73
+ if (!tool) {
74
+ tool = createReadTool(cwd);
75
+ readToolByCwd.set(cwd, tool);
76
+ }
77
+ return tool;
78
+ }
79
+
80
+ pi.registerTool({
81
+ name: "read",
82
+ label: "read",
83
+ description:
84
+ "Read a file; each line is prefixed with a stable anchor (Word§line). Use those anchors in edit.",
85
+ parameters: readSchema,
86
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
87
+ const base = getReadTool(ctx.cwd);
88
+ const result = await base.execute(toolCallId, params, signal, onUpdate);
89
+ const taskId = anchoredEditTaskId({
90
+ sessionId: (ctx as { sessionId?: string }).sessionId,
91
+ });
92
+ const absolutePath = resolve(ctx.cwd, params.path);
93
+ for (const block of result.content) {
94
+ if (block.type !== "text") continue;
95
+ const plain = stripAnchoredFromReadOutput(block.text);
96
+ block.text = hashLinesStateful(absolutePath, plain, taskId);
97
+ }
98
+ return result;
99
+ },
100
+ });
101
+
102
+ pi.registerTool({
103
+ name: "edit",
104
+ label: "edit",
105
+ description:
106
+ "Edit using line anchors from read. Batch multiple edits per file. For replace, set end_anchor (defaults to anchor for single-line replace).",
107
+ parameters: anchoredEditSchema,
108
+ promptGuidelines: [
109
+ "Use anchors from the latest read output (Word§line).",
110
+ "Batch all edits for one file in a single edit call.",
111
+ "For renames across files: sg -p to locate, then minimal anchored edits — do not use replace_symbol tools.",
112
+ ],
113
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
114
+ const absolutePath = resolve(ctx.cwd, params.path);
115
+ const taskId = anchoredEditTaskId({
116
+ sessionId: (ctx as { sessionId?: string }).sessionId,
117
+ });
118
+ const edits = params.edits as AnchoredEdit[];
119
+
120
+ const result = await applyAnchoredEditsToFile(
121
+ absolutePath,
122
+ edits,
123
+ taskId,
124
+ );
125
+
126
+ if (!result.ok) {
127
+ return {
128
+ content: [{ type: "text", text: result.error }] as TextContent[],
129
+ details: { error: true },
130
+ };
131
+ }
132
+
133
+ return {
134
+ content: [{ type: "text", text: result.message }] as TextContent[],
135
+ details: result.details,
136
+ };
137
+ },
138
+ });
139
+ }
@@ -1,23 +1,16 @@
1
1
  /**
2
2
  * harness-ask-user — structured user decisions for harness planning and setup.
3
- * Design references: pi-ask-user, @pi-unipi/ask-user, rpiv-ask-user-question (not vendored).
4
3
  */
5
4
 
6
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
- import { runAskDialog } from "../lib/ask-user/dialog.js";
8
- import { runAskFallback } from "../lib/ask-user/fallback.js";
6
+ import { runAskUser } from "../lib/ask-user/index.js";
9
7
  import { renderAskCall, renderAskResult } from "../lib/ask-user/render.js";
10
8
  import {
11
9
  AskUserParamsSchema,
12
10
  PROMPT_GUIDELINES,
13
11
  PROMPT_SNIPPET,
14
12
  } from "../lib/ask-user/schema.js";
15
- import type { AskUserParams, DialogResult } from "../lib/ask-user/types.js";
16
- import {
17
- formatResultText,
18
- toToolDetails,
19
- validateAskParams,
20
- } from "../lib/ask-user/validate.js";
13
+ import type { AskUserParams } from "../lib/ask-user/types.js";
21
14
  import { claimHarnessGovernanceLoad } from "../lib/extension-load-guard.js";
22
15
 
23
16
  // @ts-expect-error pi extensions run as ESM
@@ -35,36 +28,22 @@ export default function harnessAskUser(pi: ExtensionAPI) {
35
28
  parameters: AskUserParamsSchema,
36
29
 
37
30
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
38
- const validated = validateAskParams(params as AskUserParams);
39
- if (typeof validated === "string") {
31
+ const result = await runAskUser(params as AskUserParams, {
32
+ ui: ctx.ui,
33
+ hasUI: ctx.hasUI,
34
+ sessionName: undefined,
35
+ });
36
+
37
+ if ("error" in result) {
40
38
  return {
41
- content: [{ type: "text", text: validated }],
42
- details: {
43
- question: params.question ?? "",
44
- options: [],
45
- response: null,
46
- cancelled: true,
47
- },
39
+ content: [{ type: "text", text: result.error }],
40
+ details: result.details,
48
41
  };
49
42
  }
50
43
 
51
- let outcome: DialogResult;
52
- if (ctx.hasUI) {
53
- outcome = await runAskDialog(ctx.ui, validated);
54
- } else {
55
- outcome = await runAskFallback(ctx.ui, validated);
56
- }
57
-
58
- const details = toToolDetails(
59
- validated,
60
- outcome.response,
61
- outcome.cancelled,
62
- );
63
- const text = formatResultText(outcome.response, outcome.cancelled);
64
-
65
44
  return {
66
- content: [{ type: "text", text }],
67
- details,
45
+ content: result.content,
46
+ details: result.details,
68
47
  };
69
48
  },
70
49
 
@@ -51,7 +51,6 @@ import {
51
51
  withReviewRoundYamlWrite,
52
52
  } from "../lib/harness-debate-workflow-deps.js";
53
53
 
54
- // @ts-expect-error pi extensions run as ESM
55
54
  const MODULE_URL = import.meta.url;
56
55
 
57
56
  function getRunId(ctx: {
@@ -98,9 +97,7 @@ function subagentResults(
98
97
 
99
98
  const USE_SUBMIT_TOOLS = process.env.HARNESS_SUBMIT_TOOLS !== "0";
100
99
 
101
- export default function harnessDebateTools(pi: ExtensionAPI) {
102
- if (!claimHarnessGovernanceLoad("harness-debate-tools", MODULE_URL)) return;
103
-
100
+ function registerHarnessDebateHandler1(pi: ExtensionAPI) {
104
101
  pi.on("tool_result", async (event, ctx) => {
105
102
  if (event.isError || event.toolName !== "subagent") return;
106
103
  const runId = getRunId(ctx);
@@ -171,7 +168,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
171
168
  details: { applied, status },
172
169
  });
173
170
  });
171
+ }
174
172
 
173
+ function registerHarnessDebateHandler2(pi: ExtensionAPI) {
175
174
  pi.registerTool({
176
175
  name: "harness_plan_debate_eligibility",
177
176
  label: "Plan Debate Eligibility",
@@ -247,7 +246,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
247
246
  };
248
247
  },
249
248
  });
249
+ }
250
250
 
251
+ function registerHarnessDebateHandler3(pi: ExtensionAPI) {
251
252
  pi.registerTool({
252
253
  name: "harness_debate_open",
253
254
  label: "Open Plan Debate",
@@ -332,7 +333,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
332
333
  };
333
334
  },
334
335
  });
336
+ }
335
337
 
338
+ function registerHarnessDebateHandler4(pi: ExtensionAPI) {
336
339
  pi.registerTool({
337
340
  name: "harness_messenger_post",
338
341
  label: "Post Debate Messenger Message",
@@ -397,7 +400,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
397
400
  };
398
401
  },
399
402
  });
403
+ }
400
404
 
405
+ function registerHarnessDebateHandler5(pi: ExtensionAPI) {
401
406
  pi.registerTool({
402
407
  name: "harness_messenger_read_round",
403
408
  label: "Read Debate Round Transcript",
@@ -422,7 +427,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
422
427
  };
423
428
  },
424
429
  });
430
+ }
425
431
 
432
+ function registerHarnessDebateHandler6(pi: ExtensionAPI) {
426
433
  pi.registerTool({
427
434
  name: "harness_debate_submit_round",
428
435
  label: "Submit Plan Review Round",
@@ -551,7 +558,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
551
558
  };
552
559
  },
553
560
  });
561
+ }
554
562
 
563
+ function registerHarnessDebateHandler7(pi: ExtensionAPI) {
555
564
  pi.registerTool({
556
565
  name: "harness_debate_consensus",
557
566
  label: "Finalize Plan Debate Consensus",
@@ -590,7 +599,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
590
599
  };
591
600
  },
592
601
  });
602
+ }
593
603
 
604
+ function registerHarnessDebateHandler8(pi: ExtensionAPI) {
594
605
  pi.registerTool({
595
606
  name: "harness_debate_apply_lane",
596
607
  label: "Apply Debate Lane YAML + Messenger",
@@ -624,7 +635,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
624
635
  };
625
636
  },
626
637
  });
638
+ }
627
639
 
640
+ function registerHarnessDebateHandler9(pi: ExtensionAPI) {
628
641
  pi.registerTool({
629
642
  name: "harness_debate_round_status",
630
643
  label: "Plan Debate Round Status",
@@ -669,7 +682,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
669
682
  };
670
683
  },
671
684
  });
685
+ }
672
686
 
687
+ function registerHarnessDebateHandler10(pi: ExtensionAPI) {
673
688
  pi.registerTool({
674
689
  name: "harness_debate_focus_coverage",
675
690
  label: "Plan Debate Focus Coverage",
@@ -712,7 +727,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
712
727
  };
713
728
  },
714
729
  });
730
+ }
715
731
 
732
+ function registerHarnessDebateHandler11(pi: ExtensionAPI) {
716
733
  pi.registerTool({
717
734
  name: "harness_debate_advance_thread",
718
735
  label: "Advance Plan Debate Thread",
@@ -746,7 +763,9 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
746
763
  };
747
764
  },
748
765
  });
766
+ }
749
767
 
768
+ function registerHarnessDebateHandler12(pi: ExtensionAPI) {
750
769
  pi.registerTool({
751
770
  name: "harness_plan_scope_check",
752
771
  label: "Plan Scope Drift Check",
@@ -771,3 +790,23 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
771
790
  },
772
791
  });
773
792
  }
793
+
794
+ function registerHarnessDebateToolHandlers(pi: ExtensionAPI) {
795
+ registerHarnessDebateHandler1(pi);
796
+ registerHarnessDebateHandler2(pi);
797
+ registerHarnessDebateHandler3(pi);
798
+ registerHarnessDebateHandler4(pi);
799
+ registerHarnessDebateHandler5(pi);
800
+ registerHarnessDebateHandler6(pi);
801
+ registerHarnessDebateHandler7(pi);
802
+ registerHarnessDebateHandler8(pi);
803
+ registerHarnessDebateHandler9(pi);
804
+ registerHarnessDebateHandler10(pi);
805
+ registerHarnessDebateHandler11(pi);
806
+ registerHarnessDebateHandler12(pi);
807
+ }
808
+
809
+ export default function harnessDebateTools(pi: ExtensionAPI) {
810
+ if (!claimHarnessGovernanceLoad("harness-debate-tools", MODULE_URL)) return;
811
+ registerHarnessDebateToolHandlers(pi);
812
+ }
@@ -84,28 +84,37 @@ function visibleWidth(input: string): number {
84
84
  return width;
85
85
  }
86
86
 
87
+ const WIDE_SINGLE_CODE_POINTS = new Set([0x2329, 0x232a]);
88
+ const WIDE_RANGES: ReadonlyArray<readonly [number, number]> = [
89
+ [0x1100, 0x115f],
90
+ [0x2e80, 0xa4cf],
91
+ [0xac00, 0xd7a3],
92
+ [0xf900, 0xfaff],
93
+ [0xfe10, 0xfe19],
94
+ [0xfe30, 0xfe6f],
95
+ [0xff00, 0xff60],
96
+ [0xffe0, 0xffe6],
97
+ [0x1f300, 0x1faff],
98
+ [0x20000, 0x3fffd],
99
+ ];
100
+
101
+ function inRange(codePoint: number, start: number, end: number): boolean {
102
+ return codePoint >= start && codePoint <= end;
103
+ }
104
+
105
+ function isWideCodePoint(codePoint: number): boolean {
106
+ if (WIDE_SINGLE_CODE_POINTS.has(codePoint)) return true;
107
+ if (inRange(codePoint, 0x2e80, 0xa4cf) && codePoint === 0x303f) return false;
108
+ return WIDE_RANGES.some(([start, end]) => inRange(codePoint, start, end));
109
+ }
110
+
87
111
  function charDisplayWidth(char: string): number {
88
112
  const codePoint = char.codePointAt(0);
89
113
  if (codePoint == null) return 0;
90
- if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f)) return 0;
91
- if (codePoint >= 0x300 && codePoint <= 0x36f) return 0;
92
- if (
93
- codePoint >= 0x1100 &&
94
- (codePoint <= 0x115f ||
95
- codePoint === 0x2329 ||
96
- codePoint === 0x232a ||
97
- (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
98
- (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
99
- (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
100
- (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
101
- (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
102
- (codePoint >= 0xff00 && codePoint <= 0xff60) ||
103
- (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
104
- (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
105
- (codePoint >= 0x20000 && codePoint <= 0x3fffd))
106
- ) {
107
- return 2;
108
- }
114
+ if (inRange(codePoint, 0x00, 0x1f) || inRange(codePoint, 0x7f, 0x9f))
115
+ return 0;
116
+ if (inRange(codePoint, 0x300, 0x36f)) return 0;
117
+ if (isWideCodePoint(codePoint)) return 2;
109
118
  return 1;
110
119
  }
111
120