ultra-builder-pro-cli 0.5.3 → 0.7.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.
- package/.ultra-template/changes/active/.gitkeep +1 -0
- package/.ultra-template/changes/archive/.gitkeep +1 -0
- package/CHANGELOG.md +90 -14
- package/README.md +38 -32
- package/adapters/_shared/codex-assets.cjs +32 -151
- package/adapters/_shared/frontmatter.cjs +2 -2
- package/adapters/_shared/handbook.cjs +10 -10
- package/adapters/_shared/path-rewrite.cjs +0 -3
- package/adapters/_shared/runtime-assets.cjs +13 -4
- package/adapters/_shared/tests/handbook.test.cjs +4 -1
- package/adapters/_shared/tests/path-rewrite.test.cjs +1 -3
- package/adapters/_shared/tests/resolve-target.test.cjs +1 -11
- package/adapters/_shared/tests/runtime-assets.test.cjs +7 -6
- package/adapters/opencode.js +82 -26
- package/adapters/tests/claude.test.cjs +1 -4
- package/adapters/tests/codex.test.cjs +9 -8
- package/adapters/tests/opencode.test.cjs +17 -11
- package/bin/install.js +6 -28
- package/bin/orchestrator.js +12 -30
- package/commands/ultra-change.md +47 -0
- package/commands/ultra-deliver.md +14 -7
- package/commands/ultra-dev.md +6 -3
- package/commands/ultra-doctor.md +36 -0
- package/commands/ultra-init.md +4 -3
- package/commands/ultra-status.md +7 -5
- package/commands/ultra-test.md +2 -1
- package/docs/AGENT-CONTEXT.md +35 -28
- package/docs/ARCHITECTURE.md +49 -21
- package/docs/PLAN.zh-CN.md +67 -68
- package/docs/ROADMAP.md +17 -17
- package/docs/RUNTIME-COMPAT-MATRIX.md +63 -62
- package/hooks/README.md +8 -7
- package/hooks/active_task_context.py +22 -15
- package/hooks/health_check.py +136 -38
- package/hooks/workflow_context.py +120 -30
- package/mcp-server/lib/change-workflow.cjs +556 -0
- package/mcp-server/lib/change-workflow.test.cjs +259 -0
- package/mcp-server/lib/doctor.cjs +112 -0
- package/mcp-server/lib/doctor.test.cjs +86 -0
- package/mcp-server/lib/init-project.cjs +32 -6
- package/mcp-server/lib/pricing.cjs +0 -6
- package/mcp-server/lib/runtime-state.cjs +222 -0
- package/mcp-server/lib/runtime-state.test.cjs +92 -0
- package/mcp-server/lib/state-db.cjs +31 -8
- package/mcp-server/lib/state-ops.cjs +24 -3
- package/mcp-server/lib/telemetry.cjs +4 -0
- package/mcp-server/server.cjs +102 -10
- package/mcp-server/tests/db-ops.test.cjs +5 -1
- package/mcp-server/tests/init-project.test.cjs +45 -0
- package/mcp-server/tests/server-contract.test.cjs +140 -1
- package/mcp-server/tests/state-db.test.cjs +44 -7
- package/mcp-server/tests/state-ops.test.cjs +42 -0
- package/mcp-server/tests/telemetry.test.cjs +14 -0
- package/orchestrator/daemon.cjs +39 -0
- package/orchestrator/dispatch-rules.cjs +15 -4
- package/orchestrator/tests/daemon.test.cjs +63 -12
- package/orchestrator/tests/dispatch-rules.test.cjs +12 -10
- package/package.json +5 -7
- package/settings.json +1 -2
- package/skills/ultra-change/SKILL.md +140 -0
- package/skills/ultra-deliver/SKILL.md +81 -15
- package/skills/ultra-dev/SKILL.md +32 -12
- package/skills/ultra-doctor/SKILL.md +90 -0
- package/skills/ultra-init/SKILL.md +8 -6
- package/skills/ultra-review/SKILL.md +16 -0
- package/skills/ultra-status/SKILL.md +30 -7
- package/skills/ultra-test/SKILL.md +14 -2
- package/skills/ultra-verify/SKILL.md +22 -41
- package/skills/ultra-verify/evals/evals.json +22 -24
- package/skills/ultra-verify/references/confidence-system.md +15 -32
- package/skills/ultra-verify/references/cross-verify-modes.md +14 -59
- package/skills/ultra-verify/scripts/verify_wait.py +44 -81
- package/spec/cli-protocol.md +12 -1
- package/spec/mcp-tools.yaml +340 -4
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/skill-manifest.schema.json +2 -2
- package/spec/schemas/state-db.sql +119 -2
- package/ultra-tools/cli.cjs +3 -0
- package/ultra-tools/commands/system.cjs +57 -0
- package/ultra-tools/system-command.test.cjs +53 -0
- package/adapters/_shared/md-to-toml.cjs +0 -54
- package/adapters/_shared/rtk-detect.cjs +0 -86
- package/adapters/_shared/tests/md-to-toml.test.cjs +0 -48
- package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
- package/adapters/gemini.js +0 -204
- package/adapters/tests/gemini.test.cjs +0 -112
- package/docs/LEGACY-HERMES.md +0 -1175
- package/orchestrator/code-graph-watcher.cjs +0 -121
- package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
- package/skills/gemini-collab/SKILL.md +0 -51
- package/skills/gemini-collab/evals/evals.json +0 -42
package/docs/ROADMAP.md
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
# ultra-builder-pro-cli — Roadmap
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
>
|
|
3
|
+
> **Current roadmap**. [`PLAN.zh-CN.md`](./PLAN.zh-CN.md) preserves the original
|
|
4
|
+
> phase plan and decision history. Current executable contracts live in
|
|
5
|
+
> `spec/`, `AGENT-CONTEXT.md`, and `RUNTIME-COMPAT-MATRIX.md`; those sources win
|
|
6
|
+
> when historical plan text differs.
|
|
6
7
|
|
|
7
8
|
**Goal**: distribute the Ultra Builder Pro engineering loop as native Claude
|
|
8
|
-
Code, OpenCode, and Codex plugins,
|
|
9
|
-
|
|
9
|
+
Code, OpenCode, and Codex plugins, and run it with isolated sessions sharing one
|
|
10
|
+
authoritative workflow store.
|
|
10
11
|
|
|
11
12
|
**First-class runtimes**: Claude Code · OpenCode · Codex.
|
|
12
13
|
|
|
13
14
|
**Distribution channels (v1.0)**: npm · Homebrew · pip.
|
|
14
15
|
|
|
15
|
-
**
|
|
16
|
-
|
|
17
|
-
**Timeline**: 14–18 weeks AI-assisted (PLAN §11).
|
|
16
|
+
**Release line**: `0.7.x`; every release is gated by `npm run verify:release`.
|
|
18
17
|
|
|
19
18
|
---
|
|
20
19
|
|
|
@@ -23,7 +22,7 @@ run the loop with isolated sessions sharing one authoritative workflow store.
|
|
|
23
22
|
| Release | Week | Contents |
|
|
24
23
|
|---------|-----:|---------------------------------------------------------------------------|
|
|
25
24
|
| **v0.1**| 8 | Rule layer + execution-lite (session isolation + admission + event subscribe + active-session visibility) — solves the core "independent conversations don't pollute each other" pain |
|
|
26
|
-
| **v0.2**| 11 | Auto-recovery + monitoring +
|
|
25
|
+
| **v0.2**| 11 | Auto-recovery + monitoring + full conformance |
|
|
27
26
|
| **v0.3**| 16 | PRD → execution-plan artifact → parallel dispatch / merge — coding factory |
|
|
28
27
|
| **v1.0**| 17–18| Three-channel publish (npm + Homebrew + pip) |
|
|
29
28
|
|
|
@@ -38,30 +37,31 @@ run the loop with isolated sessions sharing one authoritative workflow store.
|
|
|
38
37
|
| 4 | Cross-runtime distribution + 4.6a smoke flow | ✅ done (D41, `5aa1fd0`) |
|
|
39
38
|
| 4.5 | Execution-lite (session + admission + events) | ✅ done (D42, `0d3e5ed`) — **v0.1 ready** |
|
|
40
39
|
| 5 | Recovery + staleness + auto-routing | ✅ done (D43) → v0.2 |
|
|
41
|
-
| 6 | Monitoring +
|
|
40
|
+
| 6 | Monitoring + telemetry | ✅ done (D44) → v0.2 |
|
|
42
41
|
| 4.6b | Full conformance suite | ✅ done (D45) — 20 conformance + 21 resolveTarget tests |
|
|
43
42
|
| 7 | tagged tasks + skill mining; retired Ultra memory handed to cloud-mem/claude-mem | ✅ superseded by D50 boundary cleanup |
|
|
44
43
|
| 8A | Plan automation (parse / topo / expand + artifact + human gate) | ✅ done (D47, `a932cb8`) → v0.3 |
|
|
45
44
|
| 8B | Execution automation (dispatch / parallel worktree / merge) | ✅ done (D48, `8224159`) — **v0.3 ready** |
|
|
46
|
-
|
|
|
45
|
+
| 8C | Continuous change packets + context compiler + convergence + doctor | ✅ done (D52) |
|
|
46
|
+
| 9 | Release pipeline | npm tag publishing live; Homebrew / pip not implemented |
|
|
47
47
|
|
|
48
48
|
## What is in the repo today
|
|
49
49
|
|
|
50
50
|
```
|
|
51
51
|
spec/ ← Phase 1 single source of truth
|
|
52
|
-
├── mcp-tools.yaml (
|
|
52
|
+
├── mcp-tools.yaml (29 live tools across 5 families)
|
|
53
53
|
├── cli-protocol.md (CLI ↔ MCP mapping table)
|
|
54
54
|
├── schemas/ (state-db.sql + 4 JSON schemas)
|
|
55
55
|
├── fixtures/{valid,invalid}/ (+ v4.4-project for migration)
|
|
56
|
-
└── scripts/test-all.cjs (npm run test:spec —
|
|
56
|
+
└── scripts/test-all.cjs (npm run test:spec — 7 validation stages)
|
|
57
57
|
|
|
58
58
|
mcp-server/ ← Phase 2 authoritative state layer
|
|
59
|
-
├── server.cjs (stdio MCP server,
|
|
59
|
+
├── server.cjs (stdio MCP server, 29 task/session/change/system/plan tools)
|
|
60
60
|
├── lib/
|
|
61
61
|
│ ├── state-db.cjs (SQLite + WAL + pragmas)
|
|
62
62
|
│ ├── state-ops.cjs (full write API, status state machine)
|
|
63
63
|
│ └── projector.cjs (state.db → tasks.json + context md)
|
|
64
|
-
└── tests/ (npm run test:state
|
|
64
|
+
└── tests/ (npm run test:state)
|
|
65
65
|
|
|
66
66
|
ultra-tools/ ← CLI fallback, migration, and diagnostics
|
|
67
67
|
├── cli.cjs
|
|
@@ -72,11 +72,11 @@ ultra-tools/ ← CLI fallback, migration, and diagnostics
|
|
|
72
72
|
|
|
73
73
|
bin/install.js ← multi-runtime installer
|
|
74
74
|
bin/handbook.js ← explicit managed user-handbook sync
|
|
75
|
-
adapters/ ← native Claude/OpenCode/Codex plugin builders
|
|
75
|
+
adapters/ ← native Claude/OpenCode/Codex plugin builders
|
|
76
76
|
skills/ ← allowlisted Ultra workflows, internal rules, and collab companions
|
|
77
77
|
hooks/ ← 7 workflow-only Python hooks; OpenCode uses native JavaScript hooks
|
|
78
78
|
docs/
|
|
79
|
-
├── PLAN.zh-CN.md
|
|
79
|
+
├── PLAN.zh-CN.md historical phase plan + decision log
|
|
80
80
|
├── ARCHITECTURE.md Phase 1 single-page entry point
|
|
81
81
|
├── AGENT-CONTEXT.md Phase 3 canonical runtime context contract
|
|
82
82
|
├── USER-HANDBOOK-CONTRACT.md managed CLAUDE.md / AGENTS.md boundary
|
|
@@ -1,89 +1,88 @@
|
|
|
1
1
|
# Runtime Compatibility Matrix
|
|
2
2
|
|
|
3
3
|
Current presentation contract for Ultra Builder Pro. Claude Code, OpenCode, and
|
|
4
|
-
Codex are
|
|
5
|
-
|
|
6
|
-
`adapters/_shared/runtime-assets.cjs`.
|
|
4
|
+
Codex are the supported native plugin hosts. The canonical asset boundary lives
|
|
5
|
+
in `adapters/_shared/runtime-assets.cjs`.
|
|
7
6
|
|
|
8
|
-
Legend: **FULL** = native host surface, **DEGRADED** =
|
|
7
|
+
Legend: **FULL** = native host surface, **DEGRADED** = supported but missing an
|
|
9
8
|
equivalent lifecycle/control surface, **N/A** = intentionally not installed.
|
|
10
9
|
|
|
11
10
|
## 1. Plugin and workflow surface
|
|
12
11
|
|
|
13
|
-
| Capability | Claude Code | OpenCode | Codex |
|
|
14
|
-
|
|
15
|
-
| Package form | `.claude-plugin` native plugin | native config bundle + JS plugin | personal `.codex-plugin` |
|
|
16
|
-
| Public entry | `/ultra-*`, `/learn` | `/ultra-*`, `/learn` | `$ultra-builder-pro:<skill>` |
|
|
17
|
-
| Public Ultra workflows |
|
|
18
|
-
| Internal agent-rule skills | 4, non-user-facing | 4, non-user-facing | 4 with implicit invocation disabled |
|
|
19
|
-
| Collaboration companions | `codex-collab`, `
|
|
20
|
-
| External browser/deploy/framework skills | N/A | N/A | N/A |
|
|
12
|
+
| Capability | Claude Code | OpenCode | Codex |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| Package form | `.claude-plugin` native plugin | native config bundle + JS plugin | personal `.codex-plugin` |
|
|
15
|
+
| Public entry | `/ultra-*`, `/learn` | `/ultra-*`, `/learn` | `$ultra-builder-pro:<skill>` |
|
|
16
|
+
| Public Ultra workflows | 12 | 12 | 12 |
|
|
17
|
+
| Internal agent-rule skills | 4, non-user-facing | 4, non-user-facing | 4 with implicit invocation disabled |
|
|
18
|
+
| Collaboration companions | `codex-collab`, `ultra-verify` | `cc-collab`, `codex-collab`, `ultra-verify` | `cc-collab`, `ultra-verify` |
|
|
19
|
+
| External browser/deploy/framework skills | N/A | N/A | N/A |
|
|
21
20
|
|
|
22
|
-
Codex
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Codex converts the workflows into namespaced skills and records eleven legacy
|
|
22
|
+
command mappings in `command-map.json`; `ultra-review` remains directly
|
|
23
|
+
invocable as a skill.
|
|
25
24
|
|
|
26
25
|
## 2. Agents and collaboration
|
|
27
26
|
|
|
28
|
-
| Capability | Claude Code | OpenCode | Codex |
|
|
29
|
-
|
|
30
|
-
| Bundled review/debug agents | FULL, native Markdown agents | FULL,
|
|
31
|
-
| Native bounded delegation |
|
|
32
|
-
| Cross-host advisor | explicit
|
|
33
|
-
| Primary-agent ownership | FULL | FULL | FULL |
|
|
27
|
+
| Capability | Claude Code | OpenCode | Codex |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| Bundled review/debug agents | FULL, native Markdown agents | FULL, native agent frontmatter | FULL, nine managed TOML custom agents |
|
|
30
|
+
| Native bounded delegation | native agent surface | native agent surface | native Codex subagents |
|
|
31
|
+
| Cross-host advisor | explicit and read-only | explicit and read-only | explicit and read-only |
|
|
32
|
+
| Primary-agent ownership | FULL | FULL | FULL |
|
|
34
33
|
|
|
35
|
-
No bundled agent
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
No bundled agent owns private persistent memory. It receives current-checkout
|
|
35
|
+
evidence and bounded context from the primary host, then returns a result for
|
|
36
|
+
primary verification.
|
|
38
37
|
|
|
39
38
|
## 3. Workflow hooks
|
|
40
39
|
|
|
41
|
-
| Lifecycle | Claude Code | OpenCode | Codex |
|
|
42
|
-
|
|
43
|
-
| Session context/health | FULL, native `SessionStart` |
|
|
44
|
-
| Active edit boundary | FULL, `PreToolUse Edit|Write` | FULL, `tool.execute.before` rejects projection writes | FULL, `PreToolUse Edit|Write|apply_patch` |
|
|
45
|
-
| Compaction recovery | FULL, `PreCompact` +
|
|
46
|
-
| Incomplete-stop gate | FULL, native blocking `Stop`
|
|
47
|
-
| Subagent lifecycle evidence | FULL | DEGRADED, no equivalent packaged event | FULL |
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
| Lifecycle | Claude Code | OpenCode | Codex |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| Session context/health | FULL, native `SessionStart` | DEGRADED, native context; health via `system.doctor` | FULL, native `SessionStart` |
|
|
43
|
+
| Active edit boundary | FULL, `PreToolUse Edit|Write` | FULL, `tool.execute.before` rejects projection writes | FULL, `PreToolUse Edit|Write|apply_patch` |
|
|
44
|
+
| Compaction recovery | FULL, `PreCompact` + resume matcher | FULL, native compacting context | FULL, `PreCompact` + `PostCompact` |
|
|
45
|
+
| Incomplete-stop gate | FULL, native blocking `Stop` | DEGRADED, no equivalent blocking stop hook | FULL, native blocking `Stop` |
|
|
46
|
+
| Subagent lifecycle evidence | FULL | DEGRADED, no equivalent packaged event | FULL |
|
|
47
|
+
|
|
48
|
+
Health/context hooks inspect initialized projects; projection protection also
|
|
49
|
+
applies at baseline, while compact/stop/subagent enforcement remains active-workflow scoped.
|
|
50
|
+
No host receives prompt capture, transcript
|
|
51
|
+
capture, observation journaling, session-summary memory, generic command
|
|
52
52
|
blocking, or generic post-edit policy.
|
|
53
53
|
|
|
54
54
|
## 4. MCP and state
|
|
55
55
|
|
|
56
|
-
| Capability | Claude Code | OpenCode | Codex |
|
|
57
|
-
|
|
58
|
-
| stdio MCP registration | plugin `.mcp.json` | `opencode.json` local MCP entry | plugin `.mcp.json` |
|
|
59
|
-
| Live contracts |
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
| Ultra memory API | N/A | N/A | N/A | N/A |
|
|
56
|
+
| Capability | Claude Code | OpenCode | Codex |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| stdio MCP registration | plugin `.mcp.json` | `opencode.json` local MCP entry | plugin `.mcp.json` |
|
|
59
|
+
| Live/declared contracts | 29 | 29 | 29 |
|
|
60
|
+
| Host-native review/discovery/ask | native | native | documented in `codex-capability-map.json` |
|
|
61
|
+
| Durable authority | project `.ultra/state.db` | project `.ultra/state.db` | project `.ultra/state.db` |
|
|
62
|
+
| Ultra memory API | N/A | N/A | N/A |
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
All 29 `task.*`, `session.*`, `change.*`, `system.*`, and `plan.*` operations
|
|
65
|
+
registered by `mcp-server/server.cjs` are live. Review, impact discovery, skill
|
|
66
|
+
loading, and user interaction remain host-native surfaces.
|
|
68
67
|
|
|
69
68
|
## 5. User handbook presentation
|
|
70
69
|
|
|
71
|
-
| Capability | Claude Code | OpenCode | Codex |
|
|
72
|
-
|
|
73
|
-
| Durable user file | `~/.claude/CLAUDE.md` | `~/.config/opencode/AGENTS.md` | `~/.codex/AGENTS.md` |
|
|
74
|
-
| Automatic overwrite by plugin install | no | no | no |
|
|
75
|
-
| Explicit managed-block sync | `ubp-handbook --runtime claude` | `ubp-handbook --runtime opencode` | `ubp-handbook --runtime codex` |
|
|
76
|
-
| Backup before change | FULL | FULL | FULL |
|
|
70
|
+
| Capability | Claude Code | OpenCode | Codex |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| Durable user file | `~/.claude/CLAUDE.md` | `~/.config/opencode/AGENTS.md` | `~/.codex/AGENTS.md` |
|
|
73
|
+
| Automatic overwrite by plugin install | no | no | no |
|
|
74
|
+
| Explicit managed-block sync | `ubp-handbook --runtime claude` | `ubp-handbook --runtime opencode` | `ubp-handbook --runtime codex` |
|
|
75
|
+
| Backup before change | FULL | FULL | FULL |
|
|
77
76
|
|
|
78
77
|
## 6. Install, update, and uninstall
|
|
79
78
|
|
|
80
|
-
| Capability | Claude Code | OpenCode | Codex |
|
|
81
|
-
|
|
82
|
-
| Adapter install | FULL | FULL | FULL |
|
|
83
|
-
| Native
|
|
84
|
-
| Removes stale managed assets | FULL | FULL | FULL |
|
|
85
|
-
| Preserves unrelated
|
|
86
|
-
| Uninstall ownership guard | managed plugin root | sentinels + owned MCP entry | managed root/manifest/agent headers |
|
|
79
|
+
| Capability | Claude Code | OpenCode | Codex |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| Adapter install | FULL | FULL | FULL |
|
|
82
|
+
| Native update | next host session | host restart | cachebuster + `codex plugin add`, then new task |
|
|
83
|
+
| Removes stale managed assets | FULL | FULL | FULL |
|
|
84
|
+
| Preserves unrelated config | FULL | FULL | FULL |
|
|
85
|
+
| Uninstall ownership guard | managed plugin root | sentinels + owned MCP entry | managed root/manifest/agent headers |
|
|
87
86
|
|
|
88
87
|
## 7. Verification sources
|
|
89
88
|
|
|
@@ -91,8 +90,10 @@ operations registered by `mcp-server/server.cjs` are advertised as live.
|
|
|
91
90
|
- `tests/conformance/<runtime>/*.test.cjs` verifies command/skill, hook, MCP,
|
|
92
91
|
idempotency, and smoke presentation.
|
|
93
92
|
- `adapters/_shared/tests/runtime-assets.test.cjs` prevents retired or external
|
|
94
|
-
skills and generic/memory hooks from re-entering
|
|
95
|
-
- `
|
|
96
|
-
|
|
93
|
+
skills and generic/memory hooks from re-entering a package.
|
|
94
|
+
- `tests/retired-runtime.test.cjs` prevents retired runtime code or prompt text
|
|
95
|
+
from re-entering active product surfaces.
|
|
96
|
+
- `adapters/_shared/tests/handbook.test.cjs` verifies host rendering, backup,
|
|
97
|
+
migration, and idempotency.
|
|
97
98
|
|
|
98
99
|
Any capability claim elsewhere must match current adapter code and these tests.
|
package/hooks/README.md
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
# Ultra Builder Pro workflow hooks
|
|
2
2
|
|
|
3
|
-
These hooks
|
|
4
|
-
|
|
3
|
+
These hooks observe only Ultra-owned state. Session health/context recognizes an initialized
|
|
4
|
+
`.ultra/state.db` and active continuous changes; edit/compact/stop/subagent enforcement remains
|
|
5
|
+
bounded to the relevant Ultra projection or a non-terminal workflow.
|
|
5
6
|
|
|
6
7
|
| Lifecycle | Hook | Purpose |
|
|
7
8
|
|---|---|---|
|
|
8
|
-
| Session start | `health_check.py` |
|
|
9
|
-
| Session start | `workflow_context.py` | Inject
|
|
10
|
-
| Before edit | `active_task_context.py` |
|
|
9
|
+
| Session start | `health_check.py` | Read-only integrity, incident, projection, session, and change-artifact checks |
|
|
10
|
+
| Session start | `workflow_context.py` | Inject baseline/active workflow/change context and filtered provider metadata references |
|
|
11
|
+
| Before edit | `active_task_context.py` | Protect `tasks.json` projection and restate an active task boundary |
|
|
11
12
|
| Before compact | `workflow_checkpoint.py` | Atomically save a minimal workflow checkpoint |
|
|
12
13
|
| After compact/resume | `workflow_resume.py` | Re-inject the current workflow boundary |
|
|
13
14
|
| Stop | `pre_stop_check.py` | Block once when the Ultra workflow is incomplete |
|
|
14
15
|
| Subagent lifecycle | `subagent_tracker.py` | Append lifecycle evidence under `.ultra/runtime/` |
|
|
15
16
|
|
|
16
17
|
The plugin deliberately does not capture prompts, tool observations, transcripts, summaries, or
|
|
17
|
-
cross-session memory. Generic command blocking and post-edit policy are
|
|
18
|
-
|
|
18
|
+
cross-session memory or code-graph content. Generic command blocking and post-edit policy are
|
|
19
|
+
user/repository governance. Persistent Memory/graph content belongs to separately installed providers.
|
|
19
20
|
|
|
20
21
|
Runtime wiring:
|
|
21
22
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
2
|
+
"""Protect Ultra projections and restate an active task boundary before edits."""
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
@@ -33,9 +33,28 @@ def main() -> None:
|
|
|
33
33
|
data = {}
|
|
34
34
|
start = Path(data.get("cwd") or Path.cwd()).resolve()
|
|
35
35
|
for root in (start, *start.parents):
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
ultra = root / ".ultra"
|
|
37
|
+
state_file = ultra / "workflow-state.json"
|
|
38
|
+
initialized = (
|
|
39
|
+
(ultra / "state.db").is_file()
|
|
40
|
+
or state_file.is_file()
|
|
41
|
+
or (ultra / "changes" / "active").is_dir()
|
|
42
|
+
)
|
|
43
|
+
if not initialized:
|
|
38
44
|
continue
|
|
45
|
+
if targets_tasks_projection(data, root):
|
|
46
|
+
print(json.dumps({"hookSpecificOutput": {
|
|
47
|
+
"hookEventName": "PreToolUse",
|
|
48
|
+
"permissionDecision": "deny",
|
|
49
|
+
"permissionDecisionReason": (
|
|
50
|
+
"Refusing a direct write to the .ultra/tasks/tasks.json projection. "
|
|
51
|
+
".ultra/state.db is authoritative; use the Ultra MCP task tools and "
|
|
52
|
+
"run ultra-doctor when state or projection health is degraded."
|
|
53
|
+
),
|
|
54
|
+
}}))
|
|
55
|
+
return
|
|
56
|
+
if not state_file.is_file():
|
|
57
|
+
break
|
|
39
58
|
try:
|
|
40
59
|
state = json.loads(state_file.read_text(encoding="utf-8"))
|
|
41
60
|
except (OSError, json.JSONDecodeError) as exc:
|
|
@@ -45,18 +64,6 @@ def main() -> None:
|
|
|
45
64
|
break
|
|
46
65
|
task = state.get("task_id", state.get("task", "unknown"))
|
|
47
66
|
step = state.get("step", "unknown")
|
|
48
|
-
if targets_tasks_projection(data, root):
|
|
49
|
-
print(json.dumps({"hookSpecificOutput": {
|
|
50
|
-
"hookEventName": "PreToolUse",
|
|
51
|
-
"permissionDecision": "deny",
|
|
52
|
-
"permissionDecisionReason": (
|
|
53
|
-
"Refusing a direct write to .ultra/tasks/tasks.json during an active Ultra "
|
|
54
|
-
"workflow. .ultra/state.db is authoritative; use the Ultra MCP task tools. "
|
|
55
|
-
"If they report LEGACY_STATE_MIGRATION_REQUIRED, run the v4.4 to v4.5 "
|
|
56
|
-
"migration before continuing."
|
|
57
|
-
),
|
|
58
|
-
}}))
|
|
59
|
-
return
|
|
60
67
|
print(json.dumps({"hookSpecificOutput": {
|
|
61
68
|
"hookEventName": "PreToolUse",
|
|
62
69
|
"additionalContext": (
|
package/hooks/health_check.py
CHANGED
|
@@ -1,59 +1,157 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
No memory, prompt, transcript, or generic host policy is inspected here.
|
|
5
|
-
"""
|
|
2
|
+
"""Fast, read-only Ultra runtime health inspection at session start."""
|
|
6
3
|
|
|
7
4
|
import json
|
|
8
5
|
import sqlite3
|
|
9
6
|
import sys
|
|
10
7
|
from pathlib import Path
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
REQUIRED_TABLES = {
|
|
9
|
+
|
|
10
|
+
REQUIRED_TABLES = {
|
|
11
|
+
"tasks", "events", "sessions", "schema_version", "migration_history",
|
|
12
|
+
"telemetry", "specs_refs", "circuit_breaker", "changes", "artifacts",
|
|
13
|
+
"context_snapshots", "trace_links", "incidents", "projection_jobs",
|
|
14
|
+
"event_consumers",
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
def
|
|
18
|
+
def hook_input() -> dict:
|
|
17
19
|
try:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
except
|
|
20
|
+
value = json.loads(sys.stdin.read() or "{}")
|
|
21
|
+
return value if isinstance(value, dict) else {}
|
|
22
|
+
except json.JSONDecodeError as exc:
|
|
21
23
|
print(f"[health_check] invalid hook input: {exc}", file=sys.stderr)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def find_root(start: Path):
|
|
26
28
|
for candidate in (start, *start.parents):
|
|
27
|
-
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
print(json.dumps({}))
|
|
39
|
-
return
|
|
40
|
-
issues = []
|
|
29
|
+
ultra = candidate / ".ultra"
|
|
30
|
+
if (
|
|
31
|
+
(ultra / "state.db").is_file()
|
|
32
|
+
or (ultra / "workflow-state.json").is_file()
|
|
33
|
+
or (ultra / "changes" / "active").is_dir()
|
|
34
|
+
):
|
|
35
|
+
return candidate
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def inspect(root: Path) -> dict:
|
|
41
40
|
db_path = root / ".ultra" / "state.db"
|
|
41
|
+
report = {"status": "healthy", "project": str(root), "checks": {}}
|
|
42
|
+
issues = []
|
|
42
43
|
if not db_path.is_file():
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
report["checks"]["state_db"] = {"status": "fail", "reason": "missing"}
|
|
45
|
+
report["status"] = "degraded"
|
|
46
|
+
return report
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
uri = f"file:{db_path}?mode=ro"
|
|
50
|
+
with sqlite3.connect(uri, uri=True, timeout=1) as conn:
|
|
51
|
+
quick = conn.execute("PRAGMA quick_check").fetchone()[0]
|
|
52
|
+
tables = {
|
|
53
|
+
row[0] for row in conn.execute(
|
|
48
54
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
49
|
-
)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
50
57
|
missing = sorted(REQUIRED_TABLES - tables)
|
|
58
|
+
report["checks"]["state_db"] = {
|
|
59
|
+
"status": "pass" if quick == "ok" and not missing else "fail",
|
|
60
|
+
"integrity": quick,
|
|
61
|
+
"missing_tables": missing,
|
|
62
|
+
}
|
|
63
|
+
if quick != "ok" or missing:
|
|
64
|
+
issues.append("state_db")
|
|
51
65
|
if missing:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
report["status"] = "degraded"
|
|
67
|
+
return report
|
|
68
|
+
|
|
69
|
+
incidents = [
|
|
70
|
+
{"id": row[0], "code": row[1], "severity": row[2], "retryable": bool(row[3])}
|
|
71
|
+
for row in conn.execute(
|
|
72
|
+
"""SELECT id, code, severity, retryable FROM incidents
|
|
73
|
+
WHERE status = 'open' ORDER BY last_seen_at DESC LIMIT 20"""
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
report["checks"]["incidents"] = {
|
|
77
|
+
"status": "pass" if not incidents else "fail",
|
|
78
|
+
"open": incidents,
|
|
79
|
+
}
|
|
80
|
+
if incidents:
|
|
81
|
+
issues.append("incidents")
|
|
82
|
+
|
|
83
|
+
pending = conn.execute(
|
|
84
|
+
"SELECT COUNT(*) FROM projection_jobs WHERE status = 'pending'"
|
|
85
|
+
).fetchone()[0]
|
|
86
|
+
running = conn.execute(
|
|
87
|
+
"SELECT COUNT(*) FROM projection_jobs WHERE status = 'running'"
|
|
88
|
+
).fetchone()[0]
|
|
89
|
+
failed = conn.execute(
|
|
90
|
+
"SELECT COUNT(*) FROM projection_jobs WHERE status = 'failed'"
|
|
91
|
+
).fetchone()[0]
|
|
92
|
+
event_cursor = conn.execute(
|
|
93
|
+
"SELECT COALESCE(MAX(id), 0) FROM events"
|
|
94
|
+
).fetchone()[0]
|
|
95
|
+
projected_cursor = conn.execute(
|
|
96
|
+
"""SELECT COALESCE(MAX(event_cursor), 0) FROM projection_jobs
|
|
97
|
+
WHERE status = 'completed'"""
|
|
98
|
+
).fetchone()[0]
|
|
99
|
+
projection_ok = (
|
|
100
|
+
pending == 0 and running == 0 and failed == 0
|
|
101
|
+
and event_cursor <= projected_cursor
|
|
102
|
+
)
|
|
103
|
+
report["checks"]["projections"] = {
|
|
104
|
+
"status": "pass" if projection_ok else "fail",
|
|
105
|
+
"pending": pending,
|
|
106
|
+
"running": running,
|
|
107
|
+
"failed": failed,
|
|
108
|
+
"event_cursor": event_cursor,
|
|
109
|
+
"projected_cursor": projected_cursor,
|
|
110
|
+
}
|
|
111
|
+
if not projection_ok:
|
|
112
|
+
issues.append("projections")
|
|
113
|
+
|
|
114
|
+
orphan = conn.execute(
|
|
115
|
+
"SELECT COUNT(*) FROM sessions WHERE status = 'orphan'"
|
|
116
|
+
).fetchone()[0]
|
|
117
|
+
report["checks"]["sessions"] = {
|
|
118
|
+
"status": "pass" if orphan == 0 else "fail", "orphan": orphan,
|
|
119
|
+
}
|
|
120
|
+
if orphan:
|
|
121
|
+
issues.append("sessions")
|
|
122
|
+
|
|
123
|
+
missing_artifacts = []
|
|
124
|
+
for change_id, artifact_root in conn.execute(
|
|
125
|
+
"""SELECT id, artifact_root FROM changes
|
|
126
|
+
WHERE status IN ('active', 'blocked', 'ready')"""
|
|
127
|
+
):
|
|
128
|
+
if artifact_root and not (root / artifact_root).exists():
|
|
129
|
+
missing_artifacts.append(change_id)
|
|
130
|
+
report["checks"]["change_artifacts"] = {
|
|
131
|
+
"status": "pass" if not missing_artifacts else "fail",
|
|
132
|
+
"missing": missing_artifacts,
|
|
133
|
+
}
|
|
134
|
+
if missing_artifacts:
|
|
135
|
+
issues.append("change_artifacts")
|
|
136
|
+
except sqlite3.Error as exc:
|
|
137
|
+
report["checks"]["state_db"] = {"status": "fail", "reason": str(exc)}
|
|
138
|
+
issues.append("state_db")
|
|
139
|
+
|
|
55
140
|
if issues:
|
|
56
|
-
|
|
141
|
+
report["status"] = "degraded"
|
|
142
|
+
return report
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main() -> None:
|
|
146
|
+
data = hook_input()
|
|
147
|
+
start = Path(data.get("cwd") or Path.cwd()).resolve()
|
|
148
|
+
root = find_root(start)
|
|
149
|
+
if root is None:
|
|
150
|
+
print(json.dumps({}))
|
|
151
|
+
return
|
|
152
|
+
report = inspect(root)
|
|
153
|
+
if report["status"] != "healthy":
|
|
154
|
+
print("[Ultra health] " + json.dumps(report, ensure_ascii=False, sort_keys=True), file=sys.stderr)
|
|
57
155
|
print(json.dumps({}))
|
|
58
156
|
|
|
59
157
|
|