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.
- package/.agents/skills/harness-decisions/SKILL.md +1 -1
- package/.agents/skills/harness-orchestration/SKILL.md +4 -4
- package/.agents/skills/harness-review/SKILL.md +7 -7
- package/.agents/skills/harness-sentrux-setup/SKILL.md +4 -3
- package/.agents/skills/harness-steer/SKILL.md +1 -1
- package/.agents/skills/sentrux/SKILL.md +9 -9
- package/.pi/agents/harness/planning/decompose.md +1 -1
- package/.pi/extensions/00-harness-project-control.ts +133 -0
- package/.pi/extensions/budget-guard.ts +2 -0
- package/.pi/extensions/debate-orchestrator.ts +2 -0
- package/.pi/extensions/harness-ask-user.ts +2 -2
- package/.pi/extensions/harness-debate-tools.ts +2 -2
- package/.pi/extensions/harness-live-widget.ts +33 -2
- package/.pi/extensions/harness-plan-approval.ts +2 -2
- package/.pi/extensions/harness-run-context.ts +180 -12
- package/.pi/extensions/harness-subagent-submit.ts +3 -2
- package/.pi/extensions/harness-subagents.ts +2 -2
- package/.pi/extensions/harness-telemetry.ts +2 -0
- package/.pi/extensions/harness-web-tools.ts +2 -2
- package/.pi/extensions/lib/extension-load-guard.ts +10 -0
- package/.pi/extensions/lib/harness-artifact-gate.ts +5 -15
- package/.pi/extensions/lib/harness-spawn-topology.ts +4 -27
- package/.pi/extensions/lib/harness-subagent-auth.ts +0 -2
- package/.pi/extensions/lib/harness-subagent-policy.ts +5 -5
- package/.pi/extensions/lib/harness-subagent-precheck.ts +3 -3
- package/.pi/extensions/lib/harness-subagent-submit-registry.ts +3 -21
- package/.pi/extensions/lib/plan-approval-readiness.ts +3 -52
- package/.pi/extensions/lib/spawn-policy.ts +3 -3
- package/.pi/extensions/observation-bus.ts +2 -0
- package/.pi/extensions/policy-gate.ts +2 -0
- package/.pi/extensions/review-integrity.ts +91 -10
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/test-diff-integrity.ts +1 -0
- package/.pi/extensions/trace-recorder.ts +2 -0
- package/.pi/harness/agents.manifest.json +23 -31
- package/.pi/harness/corpus/graphify-kb-updater.config.json +55 -0
- package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +2 -1
- package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +3 -2
- package/.pi/harness/docs/adrs/0045-phase-scoped-agent-directories.md +33 -0
- package/.pi/harness/docs/adrs/README.md +1 -0
- package/.pi/harness/docs/graphify-kb-updater-runbook.md +11 -5
- package/.pi/harness/docs/practice-map.md +2 -2
- package/.pi/harness/specs/harness-spawn-context.schema.json +1 -1
- package/.pi/lib/harness-project-config.ts +91 -0
- package/.pi/lib/harness-run-context.ts +1 -1
- package/.pi/lib/harness-ui-state.ts +27 -12
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-critic.md +1 -1
- package/.pi/prompts/harness-plan.md +3 -5
- package/.pi/prompts/harness-review.md +9 -9
- package/.pi/prompts/harness-run.md +7 -7
- package/.pi/prompts/harness-setup.md +5 -4
- package/.pi/prompts/harness-steer.md +2 -2
- package/.pi/scripts/README.md +1 -0
- package/.pi/scripts/graphify-kb-updater.mjs +48 -8
- package/.pi/scripts/harness-agents-manifest.mjs +1 -1
- package/.pi/scripts/harness-project-toggle.mjs +129 -0
- package/.pi/scripts/harness-sentrux-cli.mjs +142 -0
- package/CHANGELOG.md +12 -0
- package/README.md +94 -58
- package/package.json +3 -3
- package/.pi/agents/harness/planning/scout-graphify.md +0 -39
- package/.pi/agents/harness/planning/scout-semantic.md +0 -41
- package/.pi/agents/harness/planning/scout-structure.md +0 -37
- /package/.pi/agents/harness/{adversary.md → reviewing/adversary.md} +0 -0
- /package/.pi/agents/harness/{evaluator.md → reviewing/evaluator.md} +0 -0
- /package/.pi/agents/harness/{tie-breaker.md → reviewing/tie-breaker.md} +0 -0
- /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
|
|
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 +
|
|
37
|
-
2. `sentrux gate
|
|
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
|
|
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
|
|
43
|
-
| Before agent work | `sentrux gate --save
|
|
44
|
-
| After agent work | `sentrux gate
|
|
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
|
|
53
|
-
sentrux gate
|
|
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
|
|
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
|
|
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`,
|
|
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 {
|
|
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 (!
|
|
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 {
|
|
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 (!
|
|
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
|
|
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 {
|
|
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 (!
|
|
55
|
+
if (!claimHarnessGovernanceLoad("harness-plan-approval", MODULE_URL)) return;
|
|
56
56
|
pi.registerMessageRenderer(
|
|
57
57
|
"harness-plan-draft",
|
|
58
58
|
(message, _options, theme) => {
|