ultimate-pi 0.18.0 → 0.18.1

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 (68) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +1 -1
  2. package/.agents/skills/harness-orchestration/SKILL.md +4 -4
  3. package/.agents/skills/harness-review/SKILL.md +7 -7
  4. package/.agents/skills/harness-sentrux-setup/SKILL.md +4 -3
  5. package/.agents/skills/harness-steer/SKILL.md +1 -1
  6. package/.agents/skills/sentrux/SKILL.md +9 -9
  7. package/.pi/agents/harness/planning/decompose.md +1 -1
  8. package/.pi/extensions/00-harness-project-control.ts +133 -0
  9. package/.pi/extensions/budget-guard.ts +2 -0
  10. package/.pi/extensions/debate-orchestrator.ts +2 -0
  11. package/.pi/extensions/harness-ask-user.ts +2 -2
  12. package/.pi/extensions/harness-debate-tools.ts +2 -2
  13. package/.pi/extensions/harness-live-widget.ts +33 -2
  14. package/.pi/extensions/harness-plan-approval.ts +2 -2
  15. package/.pi/extensions/harness-run-context.ts +180 -12
  16. package/.pi/extensions/harness-subagent-submit.ts +3 -2
  17. package/.pi/extensions/harness-subagents.ts +2 -2
  18. package/.pi/extensions/harness-telemetry.ts +2 -0
  19. package/.pi/extensions/harness-web-tools.ts +2 -2
  20. package/.pi/extensions/lib/extension-load-guard.ts +10 -0
  21. package/.pi/extensions/lib/harness-artifact-gate.ts +5 -15
  22. package/.pi/extensions/lib/harness-spawn-topology.ts +4 -27
  23. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -2
  24. package/.pi/extensions/lib/harness-subagent-policy.ts +5 -5
  25. package/.pi/extensions/lib/harness-subagent-precheck.ts +3 -3
  26. package/.pi/extensions/lib/harness-subagent-submit-registry.ts +3 -21
  27. package/.pi/extensions/lib/plan-approval-readiness.ts +3 -52
  28. package/.pi/extensions/lib/spawn-policy.ts +3 -3
  29. package/.pi/extensions/observation-bus.ts +2 -0
  30. package/.pi/extensions/policy-gate.ts +2 -0
  31. package/.pi/extensions/review-integrity.ts +91 -10
  32. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  33. package/.pi/extensions/test-diff-integrity.ts +1 -0
  34. package/.pi/extensions/trace-recorder.ts +2 -0
  35. package/.pi/harness/agents.manifest.json +23 -31
  36. package/.pi/harness/corpus/graphify-kb-updater.config.json +55 -0
  37. package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +2 -1
  38. package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +3 -2
  39. package/.pi/harness/docs/adrs/0045-phase-scoped-agent-directories.md +33 -0
  40. package/.pi/harness/docs/adrs/README.md +1 -0
  41. package/.pi/harness/docs/graphify-kb-updater-runbook.md +11 -5
  42. package/.pi/harness/docs/practice-map.md +2 -2
  43. package/.pi/harness/specs/harness-spawn-context.schema.json +1 -1
  44. package/.pi/lib/harness-project-config.ts +91 -0
  45. package/.pi/lib/harness-run-context.ts +1 -1
  46. package/.pi/lib/harness-ui-state.ts +27 -12
  47. package/.pi/prompts/harness-auto.md +2 -2
  48. package/.pi/prompts/harness-critic.md +1 -1
  49. package/.pi/prompts/harness-plan.md +3 -5
  50. package/.pi/prompts/harness-review.md +9 -9
  51. package/.pi/prompts/harness-run.md +7 -7
  52. package/.pi/prompts/harness-setup.md +5 -4
  53. package/.pi/prompts/harness-steer.md +2 -2
  54. package/.pi/scripts/README.md +1 -0
  55. package/.pi/scripts/graphify-kb-updater.mjs +48 -8
  56. package/.pi/scripts/harness-agents-manifest.mjs +1 -1
  57. package/.pi/scripts/harness-project-toggle.mjs +129 -0
  58. package/.pi/scripts/harness-sentrux-cli.mjs +142 -0
  59. package/CHANGELOG.md +12 -0
  60. package/README.md +94 -58
  61. package/package.json +3 -3
  62. package/.pi/agents/harness/planning/scout-graphify.md +0 -39
  63. package/.pi/agents/harness/planning/scout-semantic.md +0 -41
  64. package/.pi/agents/harness/planning/scout-structure.md +0 -37
  65. /package/.pi/agents/harness/{adversary.md → reviewing/adversary.md} +0 -0
  66. /package/.pi/agents/harness/{evaluator.md → reviewing/evaluator.md} +0 -0
  67. /package/.pi/agents/harness/{tie-breaker.md → reviewing/tie-breaker.md} +0 -0
  68. /package/.pi/agents/harness/{executor.md → running/executor.md} +0 -0
@@ -72,4 +72,4 @@ Parent orchestrator calls **`approve_plan`** with the full `plan_packet` (scroll
72
72
 
73
73
  - **Parent orchestrator** during `/harness-plan` — `ask_user` for clarification; **`approve_plan`** then **`create_plan`** for the plan file.
74
74
  - `harness/planning/*` (scouts, decompose, hypothesis, hypothesis-eval) — JSON only; no `ask_user` / `approve_plan` / `create_plan`.
75
- - `harness/evaluator`, `harness/adversary`, and `harness/tie-breaker` — emit `human_required`; the **parent orchestrator** calls `ask_user`.
75
+ - `harness/reviewing/evaluator`, `harness/reviewing/adversary`, and `harness/reviewing/tie-breaker` — emit `human_required`; the **parent orchestrator** calls `ask_user`.
@@ -40,7 +40,7 @@ Harness subprocesses load **`harness-subagent-submit`** (`PI_HARNESS_SUBPROCESS=
40
40
  | Command | `agent` |
41
41
  |---------|---------|
42
42
  | `/harness-plan` | Parent: planning context (tools) → decompose → hypothesis → Phase 3.5 artifacts → PlanPacket → eligibility + Review Gate → `approve_plan` + `create_plan` |
43
- | `/harness-run` | `harness/executor` (single worker) |
43
+ | `/harness-run` | `harness/running/executor` (single worker) |
44
44
  | `/harness-review` | Parent verify → `evaluator` benchmark → `evaluator` verdict → `adversary` → optional `tie-breaker` (ADR 0039) |
45
45
  | `/harness-eval` | **Deprecated** → `/harness-review` |
46
46
  | `/harness-critic` | **Deprecated** → `/harness-review` |
@@ -48,7 +48,7 @@ Harness subprocesses load **`harness-subagent-submit`** (`PI_HARNESS_SUBPROCESS=
48
48
 
49
49
  ## Review isolation
50
50
 
51
- Spawn `harness/evaluator` / `harness/adversary` via `subagent` in the **same** parent session. `review-integrity` allows `subagent` when `agent` is in the review set.
51
+ Spawn `harness/reviewing/evaluator` / `harness/reviewing/adversary` via `subagent` in the **same** parent session. `review-integrity` allows `subagent` when `agent` is in the review set.
52
52
 
53
53
  ## ask_user policy
54
54
 
@@ -56,8 +56,8 @@ Spawn `harness/evaluator` / `harness/adversary` via `subagent` in the **same** p
56
56
  |------|------------|
57
57
  | Parent orchestrator | Yes (plan clarification, `approve_plan`, router tune) |
58
58
  | `harness/planning/*` | No — `human_required` in output if stuck |
59
- | `harness/evaluator`, `harness/adversary`, `harness/tie-breaker` | `human_required` in subprocess JSON |
60
- | `harness/executor` | No — parent handles governance |
59
+ | `harness/reviewing/evaluator`, `harness/reviewing/adversary`, `harness/reviewing/tie-breaker` | `human_required` in subprocess JSON |
60
+ | `harness/running/executor` | No — parent handles governance |
61
61
 
62
62
  ## Spawn pattern (`/harness-plan`)
63
63
 
@@ -21,20 +21,20 @@ description: >-
21
21
 
22
22
  | Phase | Practice | Actor | Artifact |
23
23
  |-------|----------|-------|----------|
24
- | 1 | Automated QC + Sentrux fitness functions | Parent | `harness-verify.mjs`, `sentrux gate .`, `benchmark-log.yaml`, `sentrux-signal.yaml` |
25
- | 2 | Measure actuals (EVM) | `harness/evaluator` benchmark | `eval-verdict.yaml` |
24
+ | 1 | Automated QC + Sentrux fitness functions | Parent | `harness-verify.mjs`, `harness-sentrux-cli.mjs gate`, `benchmark-log.yaml`, `sentrux-signal.yaml` |
25
+ | 2 | Measure actuals (EVM) | `harness/reviewing/evaluator` benchmark | `eval-verdict.yaml` |
26
26
  | 2b | Controlling | Parent | Write `review-outcome.yaml`; route via `remediation_class` (not fail-fast abort) |
27
27
  | 6 | Outcome | Parent | `review-outcome.yaml` → `/harness-steer` or replan |
28
- | 3 | Policy audit | `harness/evaluator` verdict | same YAML |
29
- | 4 | Red team | `harness/adversary` | `adversary-report.yaml` |
30
- | 5 | Arbitration | `harness/tie-breaker` | only if block + conditional_pass |
28
+ | 3 | Policy audit | `harness/reviewing/evaluator` verdict | same YAML |
29
+ | 4 | Red team | `harness/reviewing/adversary` | `adversary-report.yaml` |
30
+ | 5 | Arbitration | `harness/reviewing/tie-breaker` | only if block + conditional_pass |
31
31
 
32
32
  ## Phase 1 — Sentrux (structural actuals)
33
33
 
34
34
  When `HARNESS_SENTRUX_REQUIRED=true` (default in `.env.example`):
35
35
 
36
- 1. `node "$UP_PKG/.pi/scripts/harness-verify.mjs"` — rules drift + `sentrux check` when CLI installed.
37
- 2. `sentrux gate .` — compare to baseline saved during `/harness-run`.
36
+ 1. `node "$UP_PKG/.pi/scripts/harness-verify.mjs"` — rules drift + Sentrux check when CLI installed.
37
+ 2. `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate` — compare to baseline saved during `/harness-run`.
38
38
  3. Write `artifacts/sentrux-signal.yaml` and append session entry `harness-sentrux-signal` (observation bus / PostHog).
39
39
  4. Optional `artifacts/benchmark-log.yaml` fields: `sentrux_check`, `sentrux_gate`, `harness_verify`.
40
40
 
@@ -18,7 +18,7 @@ description: Bootstrap Sentrux architectural rules for harness projects — seed
18
18
  | **Bootstrap** | `harness/sentrux-bootstrap`, `harness-sentrux-bootstrap.mjs` | Greenfield seed + first sync |
19
19
  | **Steward** | `harness/sentrux-steward`, `/harness-sentrux-steward` | Proposes manifest changes (`artifacts/sentrux-manifest-proposal.yaml`); chair applies |
20
20
  | **Sync** | `sentrux-rules-sync.mjs`, `/harness-sentrux-sync` | Regenerates `rules.toml` from manifest after intent change |
21
- | **Observation** | `/harness-run`, `/harness-review` | `sentrux gate --save` / `check` / `gate` → `artifacts/sentrux-signal.yaml` |
21
+ | **Observation** | `/harness-run`, `/harness-review` | `harness-sentrux-cli.mjs gate --save` / `check` / `gate` → `artifacts/sentrux-signal.yaml` |
22
22
 
23
23
  Never auto-sync manifest from directory trees. Material manifest edits need steward evidence + chair approval (ADR 0009).
24
24
 
@@ -39,6 +39,7 @@ Custom TOML **outside** `# --- harness:managed:start/end ---` is preserved on ev
39
39
  | First-time / harness-setup (idempotent) | `node "$UP_PKG/.pi/scripts/harness-sentrux-bootstrap.mjs"` |
40
40
  | After manifest edits | `node "$UP_PKG/.pi/scripts/harness-sentrux-bootstrap.mjs" --force` |
41
41
  | CI / verify only | `node "$UP_PKG/.pi/scripts/sentrux-rules-sync.mjs" --check` |
42
+ | Run/review Sentrux observation | `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" check` / `gate [--save]` |
42
43
  | In pi session | `/harness-sentrux-sync` (extension; uses `--force`) |
43
44
 
44
45
  **Bootstrap vs `--force`:** Default bootstrap/sync skips rewriting `rules.toml` when the manifest hash is unchanged. Use `--force` (or `/harness-sentrux-sync`) after changing `architecture.manifest.json` or when verify reports drift.
@@ -51,7 +52,7 @@ Custom TOML **outside** `# --- harness:managed:start/end ---` is preserved on ev
51
52
  node "$UP_PKG/.pi/scripts/harness-sentrux-bootstrap.mjs"
52
53
  ```
53
54
  3. Optional: `sentrux plugin add-standard` (language plugins; harness-setup Step 2.8).
54
- 4. `sentrux check .` — fix violations or tune manifest `max_cc` / layers.
55
+ 4. `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" check` — fix violations or tune manifest `max_cc` / layers.
55
56
  5. Commit `.sentrux/rules.toml` and project-specific `architecture.manifest.json`.
56
57
 
57
58
  ## External repos
@@ -63,6 +64,6 @@ Do **not** copy ultimate-pi's layer paths blindly into unrelated layouts — edi
63
64
  ## References
64
65
 
65
66
  - ADR 0009 — `.pi/harness/docs/adrs/0009-sentrux-rules-lifecycle.md`
66
- - Scripts — `.pi/scripts/sentrux-rules-sync.mjs`, `harness-sentrux-bootstrap.mjs`
67
+ - Scripts — `.pi/scripts/sentrux-rules-sync.mjs`, `harness-sentrux-bootstrap.mjs`, `harness-sentrux-cli.mjs`
67
68
  - Agents — `harness/sentrux-bootstrap` (setup), `harness/sentrux-steward` (intent proposals)
68
69
  - Specs — `sentrux-manifest-proposal.schema.json`, `sentrux-signal.schema.json`
@@ -8,7 +8,7 @@ description: Post-review repair loop via harness-steer and executor repair mode
8
8
  Use after `/harness-review` when `artifacts/review-outcome.yaml` has `remediation_class: implementation_gap`.
9
9
 
10
10
  1. Read `repair-brief.yaml` and `plan_packet_path` (paths only).
11
- 2. Set policy phase `execute`; spawn `harness/executor` with `mode: repair`.
11
+ 2. Set policy phase `execute`; spawn `harness/running/executor` with `mode: repair`.
12
12
  3. Always follow with `/harness-review`.
13
13
 
14
14
  See `.pi/prompts/harness-steer.md` and `.pi/harness/docs/adrs/0044-harness-steer-loop.md`.
@@ -35,22 +35,22 @@ sentrux plugin add-standard
35
35
 
36
36
  ## Core workflows (project root)
37
37
 
38
- Run from the **target repo root** (where `.sentrux/rules.toml` lives).
38
+ Run from the **target repo root** (where `.sentrux/rules.toml` lives), or prefer the bundled wrapper when invoked by harness commands from run directories.
39
39
 
40
40
  | When | Command | Notes |
41
41
  |------|---------|-------|
42
- | CI / pre-commit | `sentrux check .` | Exit 0 = pass, 1 = violations |
43
- | Before agent work | `sentrux gate --save .` | Save session baseline |
44
- | After agent work | `sentrux gate .` | Detect degradation vs baseline |
42
+ | CI / pre-commit | `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" check` | Exit 0 = pass, 1 = violations |
43
+ | Before agent work | `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate --save` | Save session baseline |
44
+ | After agent work | `node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate` | Detect degradation vs baseline |
45
45
  | Explore structure | `sentrux` or `sentrux .` | GUI treemap (optional) |
46
46
 
47
47
  Typical agent loop:
48
48
 
49
49
  ```bash
50
- sentrux gate --save .
50
+ node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate --save
51
51
  # … agent edits …
52
- sentrux check . # rules still pass?
53
- sentrux gate . # structural regression?
52
+ node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" check # rules still pass?
53
+ node "$UP_PKG/.pi/scripts/harness-sentrux-cli.mjs" gate # structural regression?
54
54
  ```
55
55
 
56
56
  If `check` fails, fix violations or tune manifest constraints (see **Rules** below). If `gate` reports degradation, inspect changed modules before merging.
@@ -73,7 +73,7 @@ Custom TOML outside `# --- harness:managed:start/end ---` is preserved on sync.
73
73
  |-------|------|
74
74
  | `sentrux-rules-sync` extension | Session start: warns if `rules.toml` drifts; auto-sync after plan/merge phases |
75
75
  | `/harness-sentrux-sync` | Force-regenerate rules from manifest (pi command) |
76
- | `harness-verify.mjs` | Runs `sentrux check .` when rules present |
76
+ | `harness-verify.mjs` | Runs rules sync and Sentrux checks when rules are present |
77
77
  | **observation-bus** | Maps `harness-sentrux-signal` custom entries → evaluator observations |
78
78
  | **harness-eval** | Evaluate phase may require a Sentrux quality signal (stub or future MCP) per ADR 0006 |
79
79
 
@@ -90,7 +90,7 @@ High level: **execute** uses CLI gate/check around edits; **evaluate** consumes
90
90
  - Assume Sentrux **MCP** tools (`scan`, `session_start`, `health`, etc.) exist in **Pi** — they do not; use CLI only
91
91
  - Edit or rely on `.pi/mcp.json` for Pi sessions
92
92
  - Duplicate bootstrap/sync steps from **harness-sentrux-setup**
93
- - Skip `sentrux check .` after large refactors when `.sentrux/rules.toml` exists
93
+ - Skip the root-resolving Sentrux check after large refactors when `.sentrux/rules.toml` exists
94
94
 
95
95
  ## References
96
96
 
@@ -23,7 +23,7 @@ Read `HarnessSpawnContext` and the merged **scout lane JSON** in the spawn promp
23
23
 
24
24
  1. Read Phase 1 reconnaissance from spawn context paths — prefer `artifacts/planning-context.yaml`; legacy `artifacts/scout-*.yaml` lanes are accepted when present.
25
25
  2. Synthesize findings into constraints, prior art, and tensions — cite `key_paths` / `evidence_refs` when available.
26
- 3. **Graphify dedup:** If `planning-context.yaml` has `coverage.architecture.status` of `ok`, or legacy `scout-graphify.yaml` has `status: ok`, do **not** run `graphify query` / `graphify explain` / `graphify path`. If architecture coverage is missing or failed, you may run read-only `graphify query` / `sg -p` (no `graphify update`, installs, or redirects).
26
+ 3. **Graphify dedup:** If `planning-context.yaml` has `coverage.architecture.status` of `ok`, do **not** run `graphify query` / `graphify explain` / `graphify path`. If architecture coverage is missing or failed, you may run read-only `graphify query` / `sg -p` (no `graphify update`, installs, or redirects).
27
27
  4. Do not read `.pi/harness/specs/*.schema.json` from disk.
28
28
 
29
29
  ## Phase 1 — DeepMind-style decomposition
@@ -0,0 +1,133 @@
1
+ /**
2
+ * harness-project-control — always-on enable/disable for harness governance.
3
+ *
4
+ * Writes `.pi/harness/project.json`, blocks workflow slash commands while disabled,
5
+ * and emits `harness-project-enabled:changed` so live TUI surfaces update immediately.
6
+ */
7
+
8
+ import type {
9
+ ExtensionAPI,
10
+ ExtensionCommandContext,
11
+ } from "@earendil-works/pi-coding-agent";
12
+ import {
13
+ isHarnessProjectEnabled,
14
+ isHarnessWorkflowCommand,
15
+ readHarnessProjectConfig,
16
+ writeHarnessProjectEnabled,
17
+ } from "../lib/harness-project-config.js";
18
+ import { parseHarnessSlashInput } from "../lib/harness-run-context.js";
19
+
20
+ function showCommandMessage(
21
+ pi: ExtensionAPI,
22
+ ctx: ExtensionCommandContext,
23
+ text: string,
24
+ ): void {
25
+ if (ctx.hasUI) {
26
+ ctx.ui.notify(text, "info");
27
+ return;
28
+ }
29
+ pi.sendMessage({
30
+ customType: "harness-project-control",
31
+ content: text,
32
+ display: true,
33
+ });
34
+ }
35
+
36
+ function formatStatus(projectRoot: string): string {
37
+ const config = readHarnessProjectConfig(projectRoot);
38
+ const env = process.env.HARNESS_ENABLED?.trim();
39
+ const lines = [
40
+ `Harness governance: ${config.enabled ? "enabled" : "disabled"}`,
41
+ `Config: .pi/harness/project.json`,
42
+ ];
43
+ if (env) {
44
+ lines.push(`Env override: HARNESS_ENABLED=${env}`);
45
+ }
46
+ if (config.updated_at) {
47
+ lines.push(`Updated: ${config.updated_at}`);
48
+ }
49
+ if (!config.enabled) {
50
+ lines.push(
51
+ "Workflow commands (/harness-plan, /harness-run, …) are blocked until you run /harness-enable.",
52
+ );
53
+ } else {
54
+ lines.push("Run /harness-disable to turn governance off.");
55
+ }
56
+ return lines.join("\n");
57
+ }
58
+
59
+ export default function harnessProjectControl(pi: ExtensionAPI) {
60
+ pi.on("input", async (event) => {
61
+ if (event.source === "extension") {
62
+ return { action: "continue" as const };
63
+ }
64
+ const parsed = parseHarnessSlashInput(event.text);
65
+ if (!parsed || !isHarnessWorkflowCommand(parsed.command)) {
66
+ return { action: "continue" as const };
67
+ }
68
+ if (isHarnessProjectEnabled()) {
69
+ return { action: "continue" as const };
70
+ }
71
+ return {
72
+ action: "handled" as const,
73
+ message: [
74
+ `Harness governance is disabled — /${parsed.command} was not started.`,
75
+ "Run /harness-enable to restore the workflow command surface.",
76
+ ].join("\n"),
77
+ };
78
+ });
79
+
80
+ pi.registerCommand("harness-enable", {
81
+ description: "Enable harness governance for this project",
82
+ handler: async (_args, ctx) => {
83
+ const projectRoot = process.cwd();
84
+ const config = writeHarnessProjectEnabled(projectRoot, true);
85
+ const effectiveConfig = readHarnessProjectConfig(projectRoot);
86
+ pi.events.emit("harness-project-enabled:changed", {
87
+ enabled: effectiveConfig.enabled,
88
+ projectRoot,
89
+ updated_at: config.updated_at,
90
+ });
91
+ showCommandMessage(
92
+ pi,
93
+ ctx,
94
+ [
95
+ "Harness governance enabled.",
96
+ `Wrote .pi/harness/project.json (enabled=true, updated ${config.updated_at}).`,
97
+ "Live TUI surfaces were refreshed.",
98
+ ].join("\n"),
99
+ );
100
+ },
101
+ });
102
+
103
+ pi.registerCommand("harness-disable", {
104
+ description: "Disable harness governance for this project",
105
+ handler: async (_args, ctx) => {
106
+ const projectRoot = process.cwd();
107
+ const config = writeHarnessProjectEnabled(projectRoot, false);
108
+ const effectiveConfig = readHarnessProjectConfig(projectRoot);
109
+ pi.events.emit("harness-project-enabled:changed", {
110
+ enabled: effectiveConfig.enabled,
111
+ projectRoot,
112
+ updated_at: config.updated_at,
113
+ });
114
+ showCommandMessage(
115
+ pi,
116
+ ctx,
117
+ [
118
+ "Harness governance disabled.",
119
+ `Wrote .pi/harness/project.json (enabled=false, updated ${config.updated_at}).`,
120
+ "Workflow slash commands are blocked immediately.",
121
+ "Live TUI surfaces were refreshed.",
122
+ ].join("\n"),
123
+ );
124
+ },
125
+ });
126
+
127
+ pi.registerCommand("harness-enabled-status", {
128
+ description: "Show whether harness governance is enabled for this project",
129
+ handler: async (_args, ctx) => {
130
+ showCommandMessage(pi, ctx, formatStatus(process.cwd()));
131
+ },
132
+ });
133
+ }
@@ -12,6 +12,7 @@ import {
12
12
  isHarnessBudgetEnforceOn,
13
13
  shouldEmitBlockingBudgetExhausted,
14
14
  } from "../lib/harness-budget-enforce.js";
15
+ import { isHarnessProjectEnabled } from "../lib/harness-project-config.js";
15
16
  import { getRunIdFromSession } from "../lib/harness-run-context.js";
16
17
 
17
18
  type HarnessPhase = "plan" | "execute" | "evaluate" | "adversary" | "merge";
@@ -203,6 +204,7 @@ async function emitBudgetEvent(
203
204
  const debouncedSoftLimit = new Map<string, boolean>();
204
205
 
205
206
  export default function budgetGuard(pi: ExtensionAPI) {
207
+ if (!isHarnessProjectEnabled()) return;
206
208
  pi.on("tool_call", async (_event, ctx) => {
207
209
  const policy = getPolicyContext(ctx);
208
210
  if (policy.phase === null || policy.budgetBypass) return undefined;
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { join } from "node:path";
8
8
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
+ import { isHarnessProjectEnabled } from "../lib/harness-project-config.js";
9
10
  import { getRunIdFromSession } from "../lib/harness-run-context.js";
10
11
  import {
11
12
  acceptDebateRound,
@@ -32,6 +33,7 @@ function getRunId(ctx: {
32
33
  }
33
34
 
34
35
  export default function debateOrchestrator(pi: ExtensionAPI) {
36
+ if (!isHarnessProjectEnabled()) return;
35
37
  const hooks = {
36
38
  appendEntry: (customType: string, data: unknown) =>
37
39
  pi.appendEntry(customType, data),
@@ -18,13 +18,13 @@ import {
18
18
  toToolDetails,
19
19
  validateAskParams,
20
20
  } from "./lib/ask-user/validate.js";
21
- import { claimExtensionLoad } from "./lib/extension-load-guard.js";
21
+ import { claimHarnessGovernanceLoad } from "./lib/extension-load-guard.js";
22
22
 
23
23
  // @ts-expect-error pi extensions run as ESM
24
24
  const MODULE_URL = import.meta.url;
25
25
 
26
26
  export default function harnessAskUser(pi: ExtensionAPI) {
27
- if (!claimExtensionLoad("harness-ask-user", MODULE_URL)) return;
27
+ if (!claimHarnessGovernanceLoad("harness-ask-user", MODULE_URL)) return;
28
28
  pi.registerTool({
29
29
  name: "ask_user",
30
30
  label: "Ask User",
@@ -24,7 +24,7 @@ import {
24
24
  openDebateBus,
25
25
  } from "./lib/debate-bus-core.js";
26
26
  import { getDebateState } from "./lib/debate-bus-state.js";
27
- import { claimExtensionLoad } from "./lib/extension-load-guard.js";
27
+ import { claimHarnessGovernanceLoad } from "./lib/extension-load-guard.js";
28
28
  import { captureHarnessEvent } from "./lib/harness-posthog.js";
29
29
  import { DEBATE_AGENT_SUBMIT_TOOL } from "./lib/harness-subagent-submit-registry.js";
30
30
  import {
@@ -115,7 +115,7 @@ function subagentResults(
115
115
  const USE_SUBMIT_TOOLS = process.env.HARNESS_SUBMIT_TOOLS !== "0";
116
116
 
117
117
  export default function harnessDebateTools(pi: ExtensionAPI) {
118
- if (!claimExtensionLoad("harness-debate-tools", MODULE_URL)) return;
118
+ if (!claimHarnessGovernanceLoad("harness-debate-tools", MODULE_URL)) return;
119
119
 
120
120
  pi.on("tool_result", async (event, ctx) => {
121
121
  if (event.isError || event.toolName !== "subagent") return;
@@ -2,6 +2,7 @@ import type {
2
2
  ExtensionAPI,
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
+ import { isHarnessProjectEnabled } from "../lib/harness-project-config.js";
5
6
  import { evaluateCrossSessionResume } from "../lib/harness-run-context.js";
6
7
  import {
7
8
  deriveHarnessStatusHint,
@@ -245,7 +246,7 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
245
246
  let mountCtx: ExtensionContext | null = null;
246
247
 
247
248
  function mountHarnessWidget(ctx: ExtensionContext): void {
248
- if (!ctx.hasUI) return;
249
+ if (!ctx.hasUI || !isHarnessProjectEnabled()) return;
249
250
  const state = stateStore.refresh(ctx);
250
251
  lastRenderHash = computeRenderHash(state);
251
252
 
@@ -270,9 +271,21 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
270
271
  updateStatusFallback(ctx, state);
271
272
  }
272
273
 
274
+ function clearHarnessWidget(ctx: ExtensionContext): void {
275
+ if (!ctx.hasUI) return;
276
+ const tui = tuiHandle;
277
+ ctx.ui.setWidget("harness-live", undefined);
278
+ ctx.ui.setStatus("harness-mode", undefined);
279
+ widgetMounted = false;
280
+ tuiHandle = null;
281
+ component = null;
282
+ lastRenderHash = "";
283
+ tui?.requestRender();
284
+ }
285
+
273
286
  function remountHarnessLiveWidget(ctx: ExtensionContext): void {
274
287
  if (!ctx.hasUI || !widgetMounted) return;
275
- ctx.ui.setWidget("harness-live", undefined);
288
+ clearHarnessWidget(ctx);
276
289
  mountHarnessWidget(ctx);
277
290
  }
278
291
 
@@ -300,6 +313,20 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
300
313
  if (mountCtx) scheduleRefresh(mountCtx);
301
314
  });
302
315
 
316
+ pi.events.on("harness-project-enabled:changed", (payload: unknown) => {
317
+ const data =
318
+ payload && typeof payload === "object"
319
+ ? (payload as { enabled?: boolean })
320
+ : null;
321
+ if (!mountCtx || typeof data?.enabled !== "boolean") return;
322
+ if (data.enabled) {
323
+ mountHarnessWidget(mountCtx);
324
+ tuiHandle?.requestRender();
325
+ return;
326
+ }
327
+ clearHarnessWidget(mountCtx);
328
+ });
329
+
303
330
  function updateStatusFallback(
304
331
  ctx: ExtensionContext,
305
332
  state: HarnessUiState,
@@ -330,6 +357,10 @@ export default function harnessLiveWidget(pi: ExtensionAPI) {
330
357
  refreshQueued = true;
331
358
  queueMicrotask(() => {
332
359
  refreshQueued = false;
360
+ if (!isHarnessProjectEnabled()) {
361
+ clearHarnessWidget(ctx);
362
+ return;
363
+ }
333
364
  const state = stateStore.refresh(ctx);
334
365
  const hash = computeRenderHash(state);
335
366
  updateStatusFallback(ctx, state);
@@ -12,7 +12,7 @@ import {
12
12
  parsePlanApprovalFromMessage,
13
13
  planPacketSummary,
14
14
  } from "../lib/harness-run-context.js";
15
- import { claimExtensionLoad } from "./lib/extension-load-guard.js";
15
+ import { claimHarnessGovernanceLoad } from "./lib/extension-load-guard.js";
16
16
  import {
17
17
  CREATE_PLAN_GUIDELINES,
18
18
  CREATE_PLAN_SNIPPET,
@@ -52,7 +52,7 @@ import { validatePlanDebateGate } from "./lib/plan-debate-gate.js";
52
52
  const MODULE_URL = import.meta.url;
53
53
 
54
54
  export default function harnessPlanApproval(pi: ExtensionAPI) {
55
- if (!claimExtensionLoad("harness-plan-approval", MODULE_URL)) return;
55
+ if (!claimHarnessGovernanceLoad("harness-plan-approval", MODULE_URL)) return;
56
56
  pi.registerMessageRenderer(
57
57
  "harness-plan-draft",
58
58
  (message, _options, theme) => {