ultra-builder-pro-cli 0.25.1 → 0.26.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/contexts/TEMPLATE.md +87 -0
- package/.ultra-template/decisions/.gitkeep +1 -0
- package/.ultra-template/evidence/.gitkeep +1 -0
- package/.ultra-template/north-star.md +46 -0
- package/.ultra-template/research/.gitkeep +1 -0
- package/.ultra-template/specs/discovery.md +2 -2
- package/.ultra-template/tasks.json +3 -0
- package/.ultra-template/test-report.json +27 -0
- package/CHANGELOG.md +40 -0
- package/README.md +217 -579
- package/adapters/_shared/delegate-profiles/kimi-read-only.md +15 -0
- package/adapters/_shared/delegate-profiles/kimi-write.md +17 -0
- package/adapters/_shared/file-ops.cjs +76 -2
- package/adapters/_shared/host-profile.cjs +97 -0
- package/adapters/_shared/plugin-core.cjs +204 -0
- package/adapters/_shared/runtime-assets.cjs +31 -96
- package/adapters/claude.js +92 -291
- package/adapters/codex.js +309 -842
- package/adapters/grok.js +143 -1469
- package/adapters/kimi.js +124 -1053
- package/adapters/opencode.js +230 -742
- package/bin/delegate-worker.cjs +323 -0
- package/bin/delegate.cjs +456 -0
- package/bin/install.js +38 -11
- package/docs/ARCHITECTURE.md +122 -216
- package/docs/ARTIFACT-AUTHORITY.md +90 -268
- package/docs/DECISIONS.md +75 -87
- package/docs/PHILOSOPHY.md +177 -0
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +62 -39
- package/docs/RUNTIME-COMPAT-MATRIX.md +99 -156
- package/docs/SKILL-AUTHORING.md +94 -73
- package/docs/WORKFLOW-LIFECYCLE.md +93 -189
- package/hooks/README.md +17 -37
- package/hooks/_common.py +113 -0
- package/hooks/adapters/codex.py +9 -16
- package/hooks/adapters/grok.py +5 -5
- package/hooks/adapters/kimi.py +8 -14
- package/hooks/block_dangerous_commands.py +95 -0
- package/hooks/compact_context.py +65 -0
- package/hooks/mid_workflow_recall.py +25 -0
- package/hooks/post_edit_guard.py +105 -0
- package/hooks/session_context.py +30 -0
- package/package.json +17 -52
- package/skills/ultra-change/SKILL.md +97 -50
- package/skills/ultra-change/references/change-contract.md +40 -1
- package/skills/ultra-delegate/SKILL.md +82 -0
- package/skills/ultra-delegate/references/delegation-contract.md +51 -0
- package/skills/ultra-delegate/scripts/delegate_wait.py +86 -0
- package/skills/ultra-deliver/SKILL.md +61 -42
- package/skills/ultra-deliver/references/baseline-reconciliation.md +39 -20
- package/skills/ultra-dev/SKILL.md +96 -62
- package/skills/ultra-dev/references/debugging.md +22 -0
- package/skills/ultra-domain-modeling/SKILL.md +79 -0
- package/skills/ultra-grilling/SKILL.md +79 -0
- package/skills/ultra-init/SKILL.md +94 -37
- package/skills/ultra-init/references/brownfield-adoption.md +33 -83
- package/skills/ultra-init/scripts/init_project.cjs +84 -0
- package/skills/ultra-plan/SKILL.md +88 -68
- package/skills/ultra-research/SKILL.md +62 -48
- package/skills/ultra-research/references/00-problem-validation.md +5 -5
- package/skills/ultra-research/references/01-opportunity-discovery.md +3 -3
- package/skills/ultra-research/references/02-market-assessment.md +5 -4
- package/skills/ultra-research/references/03-competitive-landscape.md +3 -3
- package/skills/ultra-research/references/04-product-strategy.md +3 -3
- package/skills/ultra-research/references/05-assumptions-validation.md +5 -5
- package/skills/ultra-research/references/10-user-personas.md +3 -2
- package/skills/ultra-research/references/11-user-scenarios.md +3 -3
- package/skills/ultra-research/references/20-user-stories.md +4 -3
- package/skills/ultra-research/references/21-features-scope.md +3 -2
- package/skills/ultra-research/references/22-success-metrics.md +3 -3
- package/skills/ultra-research/references/30-architecture-context.md +3 -3
- package/skills/ultra-research/references/31-solution-strategy.md +3 -3
- package/skills/ultra-research/references/32-building-blocks.md +3 -3
- package/skills/ultra-research/references/40-deployment.md +3 -3
- package/skills/ultra-research/references/41-quality-risks.md +3 -3
- package/skills/ultra-research/references/99-synthesis.md +7 -7
- package/skills/ultra-review/SKILL.md +59 -32
- package/skills/ultra-review/references/code.md +17 -0
- package/skills/ultra-review/references/comments.md +12 -0
- package/skills/ultra-review/references/design.md +12 -0
- package/skills/ultra-review/references/errors.md +13 -0
- package/skills/ultra-review/references/spec.md +13 -0
- package/skills/ultra-review/references/tests.md +12 -0
- package/skills/ultra-review/references/worker-packet.md +40 -0
- package/skills/ultra-review/scripts/review_wait.py +26 -15
- package/skills/ultra-status/SKILL.md +61 -36
- package/skills/ultra-tdd/SKILL.md +81 -0
- package/skills/ultra-tdd/references/templates/README.md +20 -0
- package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +79 -0
- package/skills/ultra-tdd/references/templates/persistence-real.ts +78 -0
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +78 -0
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +73 -0
- package/skills/ultra-tdd/references/templates/vertical-slice.ts +66 -0
- package/skills/ultra-tdd/references/test-execution.md +18 -0
- package/skills/ultra-test/SKILL.md +70 -30
- package/skills/ultra-test/references/export-syntax.md +18 -0
- package/skills/ultra-test/scripts/worktree_digest.cjs +54 -0
- package/skills/ultra-think/SKILL.md +49 -27
- package/skills/ultra-think/references/autonomy-boundary.md +48 -0
- package/skills/ultra-think/references/interaction-boundary.md +18 -33
- package/.ultra-template/docs/research/README.md +0 -55
- package/.ultra-template/reports/templates/delivery-report.json +0 -17
- package/.ultra-template/reports/templates/test-report.json +0 -18
- package/.ultra-template/tasks/tasks.json +0 -15
- package/.ultra-template/templates/task-context.md +0 -19
- package/adapters/_shared/codex-assets.cjs +0 -725
- package/adapters/_shared/codex-ultra-tools-entry.cjs +0 -11
- package/adapters/_shared/interaction-contract.cjs +0 -129
- package/adapters/_shared/path-rewrite.cjs +0 -79
- package/adapters/_shared/runtime-native.cjs +0 -132
- package/adapters/_shared/settings-merge.cjs +0 -76
- package/agents/code-reviewer.md +0 -42
- package/agents/debugger.md +0 -40
- package/agents/review-code.md +0 -49
- package/agents/review-comments.md +0 -38
- package/agents/review-coordinator.md +0 -56
- package/agents/review-design.md +0 -40
- package/agents/review-errors.md +0 -43
- package/agents/review-spec.md +0 -39
- package/agents/review-tests.md +0 -40
- package/agents/tdd-runner.md +0 -38
- package/bin/orchestrator.js +0 -383
- package/commands/ultra-change.md +0 -10
- package/commands/ultra-deliver.md +0 -10
- package/commands/ultra-dev.md +0 -10
- package/commands/ultra-doctor.md +0 -10
- package/commands/ultra-init.md +0 -10
- package/commands/ultra-plan.md +0 -10
- package/commands/ultra-research.md +0 -10
- package/commands/ultra-review.md +0 -10
- package/commands/ultra-status.md +0 -10
- package/commands/ultra-test.md +0 -10
- package/commands/ultra-think.md +0 -10
- package/docs/AGENT-CONTEXT.md +0 -191
- package/docs/COMMIT-HASH-BACKFILL.md +0 -51
- package/docs/LEGACY-CLI-CROSSWALK.md +0 -175
- package/docs/ROADMAP.md +0 -101
- package/docs/STATE-DB-ACCESS-POLICY.md +0 -161
- package/hooks/active_task_context.py +0 -152
- package/hooks/context_envelope.py +0 -100
- package/hooks/health_check.py +0 -196
- package/hooks/pre_stop_check.py +0 -66
- package/hooks/runtime_paths.py +0 -396
- package/hooks/subagent_tracker.py +0 -88
- package/hooks/workflow_checkpoint.py +0 -76
- package/hooks/workflow_context.py +0 -48
- package/hooks/workflow_resume.py +0 -84
- package/mcp-server/hook-context.cjs +0 -38
- package/mcp-server/hook-event.cjs +0 -34
- package/mcp-server/lib/archive-journal.cjs +0 -1119
- package/mcp-server/lib/archive-mutation-worker.py +0 -365
- package/mcp-server/lib/artifact-registry.cjs +0 -1232
- package/mcp-server/lib/baseline-workflow.cjs +0 -1193
- package/mcp-server/lib/canonical-json.cjs +0 -19
- package/mcp-server/lib/change-authority.cjs +0 -26
- package/mcp-server/lib/change-packet.cjs +0 -790
- package/mcp-server/lib/change-workflow.cjs +0 -65
- package/mcp-server/lib/context-envelope.cjs +0 -581
- package/mcp-server/lib/context-paths.cjs +0 -142
- package/mcp-server/lib/decision-records.cjs +0 -423
- package/mcp-server/lib/delivery-transaction.cjs +0 -471
- package/mcp-server/lib/doctor-backup-worker.cjs +0 -328
- package/mcp-server/lib/doctor.cjs +0 -627
- package/mcp-server/lib/git-bootstrap.cjs +0 -651
- package/mcp-server/lib/init-project.cjs +0 -1021
- package/mcp-server/lib/kernel-change-workflow.cjs +0 -2029
- package/mcp-server/lib/legacy-change-workflow.cjs +0 -3032
- package/mcp-server/lib/managed-file-write.cjs +0 -172
- package/mcp-server/lib/plan-checkpoint.cjs +0 -177
- package/mcp-server/lib/plan-publication-worker.cjs +0 -830
- package/mcp-server/lib/plan-store.cjs +0 -794
- package/mcp-server/lib/prd-parser.cjs +0 -83
- package/mcp-server/lib/pricing.cjs +0 -44
- package/mcp-server/lib/project-context.cjs +0 -178
- package/mcp-server/lib/projector.cjs +0 -386
- package/mcp-server/lib/provider-refs.cjs +0 -42
- package/mcp-server/lib/runtime-paths.cjs +0 -1714
- package/mcp-server/lib/runtime-state.cjs +0 -222
- package/mcp-server/lib/safe-project-file.cjs +0 -385
- package/mcp-server/lib/stage-checkpoints.cjs +0 -463
- package/mcp-server/lib/state-authority.cjs +0 -95
- package/mcp-server/lib/state-db.cjs +0 -1580
- package/mcp-server/lib/state-ops.cjs +0 -1428
- package/mcp-server/lib/task-contract.cjs +0 -404
- package/mcp-server/lib/task-expander.cjs +0 -105
- package/mcp-server/lib/task-ledger.cjs +0 -1964
- package/mcp-server/lib/telemetry.cjs +0 -85
- package/mcp-server/lib/topo.cjs +0 -131
- package/mcp-server/lib/ultra-facade.cjs +0 -2684
- package/mcp-server/lib/worker-packet.cjs +0 -358
- package/mcp-server/server.cjs +0 -437
- package/orchestrator/auto-merge.cjs +0 -128
- package/orchestrator/daemon.cjs +0 -264
- package/orchestrator/dispatch-rules.cjs +0 -78
- package/orchestrator/parallel-orchestrator.cjs +0 -903
- package/orchestrator/planner/plan-builder.cjs +0 -125
- package/orchestrator/recovery.cjs +0 -188
- package/orchestrator/session-close-journal-worker.cjs +0 -5
- package/orchestrator/session-close-journal.cjs +0 -1014
- package/orchestrator/session-runner.cjs +0 -1153
- package/orchestrator/worktree-manager.cjs +0 -282
- package/settings.json +0 -7
- package/skills/cc-collab/SKILL.md +0 -42
- package/skills/code-review-expert/SKILL.md +0 -43
- package/skills/code-review-expert/references/correctness-reliability.md +0 -30
- package/skills/code-review-expert/references/design-boundaries.md +0 -26
- package/skills/code-review-expert/references/removal-plan.md +0 -24
- package/skills/codex-collab/SKILL.md +0 -49
- package/skills/codex-collab/evals/evals.json +0 -35
- package/skills/integration-rules/SKILL.md +0 -32
- package/skills/security-rules/SKILL.md +0 -38
- package/skills/testing-rules/SKILL.md +0 -32
- package/skills/ultra-doctor/SKILL.md +0 -41
- package/skills/ultra-plan/references/semantic-preflight.md +0 -20
- package/skills/ultra-verify/SKILL.md +0 -46
- package/skills/ultra-verify/evals/evals.json +0 -46
- package/skills/ultra-verify/references/cross-verify-modes.md +0 -28
- package/skills/ultra-verify/references/evidence-status.md +0 -21
- package/skills/ultra-verify/scripts/verify_wait.py +0 -94
- package/spec/cli-protocol.md +0 -102
- package/spec/mcp-tools.yaml +0 -312
- package/spec/schemas/baseline-reconciliation.v1.schema.json +0 -73
- package/spec/schemas/change-delta.v1.schema.json +0 -153
- package/spec/schemas/command-manifest.schema.json +0 -29
- package/spec/schemas/context-file.v4.5.schema.json +0 -54
- package/spec/schemas/delivery-report.v1.schema.json +0 -66
- package/spec/schemas/documentation-reconciliation.v1.schema.json +0 -106
- package/spec/schemas/mcp-tools.schema.json +0 -136
- package/spec/schemas/skill-manifest.schema.json +0 -24
- package/spec/schemas/state-db.sql +0 -641
- package/spec/schemas/task-ledger.v2.schema.json +0 -296
- package/spec/schemas/tasks.v4.5.schema.json +0 -96
- package/spec/schemas/test-report.v1.schema.json +0 -225
- package/ultra-tools/cli.cjs +0 -129
- package/ultra-tools/commands/db.cjs +0 -226
- package/ultra-tools/commands/legacy-memory.cjs +0 -159
- package/ultra-tools/commands/migrate.cjs +0 -1386
- package/ultra-tools/commands/session.cjs +0 -213
- package/ultra-tools/commands/status.cjs +0 -559
- package/ultra-tools/commands/system.cjs +0 -498
- package/ultra-tools/commands/task.cjs +0 -105
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# `.ultra/.runtime/state.db` access policy
|
|
2
|
-
|
|
3
|
-
> Multi-process access contract. Current authority is defined by
|
|
4
|
-
> [`DECISIONS.md`](./DECISIONS.md) and the live database schema.
|
|
5
|
-
|
|
6
|
-
`.ultra/.runtime/state.db` is the checkout-local persistence and safety authority for
|
|
7
|
-
baselines, Changes, normalized Decision Records, Stage Checkpoints, Context Envelopes,
|
|
8
|
-
Worker Packets, Tasks, events, Sessions, incidents, projections, telemetry, and spec
|
|
9
|
-
references. Registered digest-bound files carry semantic and evidence
|
|
10
|
-
bodies; the DB owns their references and current digests. The tracked
|
|
11
|
-
`.ultra/tasks/tasks.json` is an MCP-published handoff checkpoint, not a parallel live
|
|
12
|
-
writer. Every process that touches the DB must follow the rules below; deviations are
|
|
13
|
-
bugs.
|
|
14
|
-
|
|
15
|
-
## 1. Three-role write matrix
|
|
16
|
-
|
|
17
|
-
| Role | tasks | baselines | change/workflow/artifact state | decisions | events | sessions | telemetry | specs_refs | migration_history |
|
|
18
|
-
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
19
|
-
| **MCP server** (single writer for mutables) | RW | RW | RW | RW | RW | RW | RW | RW | RW |
|
|
20
|
-
| **CLI** (`ultra-tools …`) | R | RW (init only) | R | R | RW (append-only) | R | R | R | RW (migration only) |
|
|
21
|
-
| **Orchestrator daemon** | R | R | R | R | RW | RW | RW | RW | R |
|
|
22
|
-
|
|
23
|
-
- **R** = read-only;
|
|
24
|
-
- **RW** = read + write;
|
|
25
|
-
- A `(append-only)` qualifier means the role may execute `INSERT INTO`
|
|
26
|
-
but never `UPDATE` / `DELETE`.
|
|
27
|
-
|
|
28
|
-
The CLI may indirectly mutate ordinary workflow tables by spawning the MCP server over
|
|
29
|
-
stdio and calling the public `ultra.*` kernel — but the actual
|
|
30
|
-
SQLite write is performed by the MCP server's writer connection. The documented
|
|
31
|
-
exceptions are initial schema/baseline creation, migration, and explicitly
|
|
32
|
-
authorized backup-first doctor recovery. The CLI never provides a parallel raw
|
|
33
|
-
write API for normal task, change, session, or baseline lifecycle updates.
|
|
34
|
-
|
|
35
|
-
The append-only carve-out for `events` exists because `events.id INTEGER
|
|
36
|
-
PRIMARY KEY AUTOINCREMENT` makes concurrent inserts collision-free under
|
|
37
|
-
WAL + `busy_timeout`. This lets hooks and short-lived CLI invocations
|
|
38
|
-
record audit observations without paying the cost of a full MCP round trip.
|
|
39
|
-
It does not grant lifecycle authority: external writers use the published observation
|
|
40
|
-
allowlist, while lifecycle event
|
|
41
|
-
names are emitted only inside the mutation that owns the corresponding state change.
|
|
42
|
-
Consumers must verify mutable rows rather than infer success from an event name.
|
|
43
|
-
Rejected semantic calls are recorded as `ultra_kernel_attempt` observations and shown
|
|
44
|
-
by `ultra.context`; they do not create the rejected row or authorize a transition.
|
|
45
|
-
|
|
46
|
-
## 2. Connection discipline
|
|
47
|
-
|
|
48
|
-
Every process **must**:
|
|
49
|
-
|
|
50
|
-
1. Open the database with WAL + `busy_timeout=5000` + `foreign_keys=ON`.
|
|
51
|
-
Use `mcp-server/lib/state-db.cjs` `openStateDb` so the pragmas are
|
|
52
|
-
applied uniformly.
|
|
53
|
-
2. Wrap every mutable state operation with `state-ops.cjs` `tx(db, fn)`. The
|
|
54
|
-
helper calls better-sqlite3's immediate transaction variant, so the writer
|
|
55
|
-
lock is acquired before user code runs and the whole operation rolls back on
|
|
56
|
-
failure.
|
|
57
|
-
3. Retry the complete transaction up to **10 attempts** on `SQLITE_BUSY`, using
|
|
58
|
-
decorrelated jitter from a 50 ms base with a 2 s cap. Never retry only a
|
|
59
|
-
suffix of a failed transaction or preserve partial state.
|
|
60
|
-
4. Close the connection in a `finally` block. WAL files (`-wal`,
|
|
61
|
-
`-shm`) are reclaimed on the last writer's close.
|
|
62
|
-
|
|
63
|
-
Reads never need explicit transactions; better-sqlite3 reads observe a
|
|
64
|
-
consistent snapshot under WAL.
|
|
65
|
-
|
|
66
|
-
## 3. WAL requirement
|
|
67
|
-
|
|
68
|
-
WAL is part of the authority contract, not an optional performance setting.
|
|
69
|
-
`openStateDb` asks SQLite to enable WAL and checks the actual returned mode. If
|
|
70
|
-
the storage layer returns anything else, startup fails with
|
|
71
|
-
`STATE_DB_WAL_UNAVAILABLE`. Ultra does not silently downgrade to a different
|
|
72
|
-
concurrency model. Move `.ultra/.runtime/state.db` to storage that supports SQLite WAL or
|
|
73
|
-
resolve the mount/runtime constraint before retrying.
|
|
74
|
-
|
|
75
|
-
## 4. Where each table is owned
|
|
76
|
-
|
|
77
|
-
| Table | Writer of record |
|
|
78
|
-
|--------------------|--------------------------------------------------------|
|
|
79
|
-
| `baselines` | MCP server through `ultra.record`, `ultra.checkpoint`, and `ultra.archive`; initialization and legacy migration may create only the first draft or compatibility row |
|
|
80
|
-
| `tasks` | MCP server through `ultra.record`, `ultra.checkpoint`, and `ultra.sync` |
|
|
81
|
-
| `changes` | MCP server through `ultra.record`, `ultra.checkpoint`, and `ultra.archive` |
|
|
82
|
-
| `decision_records` | MCP server through `ultra.record`; normalized accepted intent and applied references are stored, never UI receipts, prompts, or transcripts |
|
|
83
|
-
| `stage_checkpoints` | MCP server through `ultra.checkpoint`; mutable drafts and immutable accepted/superseded revisions, never fixed reasoning steps |
|
|
84
|
-
| `context_envelopes`, `worker_packets` | MCP server through `ultra.context`, `ultra.checkpoint`, and `ultra.session`; exact digest-bound handoff authority |
|
|
85
|
-
| `decision_threads`, `decision_items`, `workflow_runs`, `workflow_steps` | read-only legacy history retained for migration and audit; no current public writer, invalidation mutation, or authorization role |
|
|
86
|
-
| `artifacts`, `artifact_edges` | MCP server through `ultra.record` and checkpoint-owned registration |
|
|
87
|
-
| `context_snapshots`, `spec_learning_candidates`, `trace_links` | MCP server through `ultra.record` and `ultra.checkpoint` |
|
|
88
|
-
| `incidents`, `projection_jobs`, `event_consumers`, `circuit_breaker` | MCP server; backup-first doctor recovery may perform only documented mechanical transitions |
|
|
89
|
-
| `events` | MCP server for lifecycle events; approved processes and `ultra.record` for allowlisted append-only observations |
|
|
90
|
-
| `sessions` | MCP server through `ultra.session`; orchestrator may write status transitions |
|
|
91
|
-
| `telemetry` | MCP server (collected from tool-call wrappers); orchestrator may dump bulk samples |
|
|
92
|
-
| `specs_refs` | MCP server (rebuilt on `spec_changed` event); orchestrator may rebuild |
|
|
93
|
-
| `migration_history`| schema initializer and `ultra-tools migrate` or doctor repair |
|
|
94
|
-
| `schema_version` | schema initializer, `ultra-tools db init`, migration, and doctor repair |
|
|
95
|
-
|
|
96
|
-
## 5. Git team checkpoint
|
|
97
|
-
|
|
98
|
-
Only `ultra.sync { action: publish }` may derive `.ultra/tasks/tasks.json` from SQLite,
|
|
99
|
-
and only `ultra.sync { action: import }` may merge it into SQLite. Server startup performs one import after
|
|
100
|
-
opening project authority; later pulls in a long-running process require an explicit
|
|
101
|
-
import. The same checkpoint is idempotent and must not append an event again.
|
|
102
|
-
|
|
103
|
-
The checkpoint carries portable baseline, Change, and durable task records with
|
|
104
|
-
per-record revisions and digests. It excludes local `in_progress` ownership, sessions,
|
|
105
|
-
leases, worktrees, telemetry, and completion commit hashes. Import validates document
|
|
106
|
-
ancestry plus record-level common bases. It fast-forwards clean records and fails
|
|
107
|
-
before commit on same-record concurrency, parent/owner gaps, non-descendant history, or
|
|
108
|
-
an active-task collision. A Git-ready baseline is downgraded to checkout-local
|
|
109
|
-
revalidation rather than trusted as execution authority.
|
|
110
|
-
|
|
111
|
-
Legacy v4.4/v4.5 task projections are backed up and replaced only when every available
|
|
112
|
-
durable field matches current SQLite. Doctor reports checkpoint condition but never
|
|
113
|
-
performs semantic import, publication, or conflict selection.
|
|
114
|
-
|
|
115
|
-
## 6. Forbidden patterns
|
|
116
|
-
|
|
117
|
-
- **No file copies for state.** Don't `cp .ultra/.runtime/state.db
|
|
118
|
-
somewhere/state.db` and edit; use `ultra-tools db backup`.
|
|
119
|
-
- **No direct checkpoint edits.** Don't merge or repair
|
|
120
|
-
`.ultra/tasks/tasks.json` by hand; use the typed import/publish conflict path.
|
|
121
|
-
- **No committed live projections.** `.ultra/.runtime/projections/**` remains ignored
|
|
122
|
-
checkout-local output.
|
|
123
|
-
- **No long-held writer connections** outside the MCP server. Pop a
|
|
124
|
-
short transaction, finish, close.
|
|
125
|
-
- **No `PRAGMA journal_mode` toggling at runtime** by any process other
|
|
126
|
-
than the orchestrator's boot-time WAL detector.
|
|
127
|
-
- **No raw SQL in command md files.** Commands call MCP tools or
|
|
128
|
-
`ultra-tools …` subcommands; the SQL lives in `mcp-server/lib`.
|
|
129
|
-
- **No legacy authority mode switch.** Public Change callers use the single Kernel
|
|
130
|
-
facade. New workflow-owned artifacts and workflow/dialogue writes are rejected.
|
|
131
|
-
- **No semantic meaning in projection metadata.** `_ultra.projection_commit` means
|
|
132
|
-
only that the generated-view job was durably enqueued and processed; acceptance is
|
|
133
|
-
read from the typed tool result and current authority.
|
|
134
|
-
- **No `vacuum` / `wal_checkpoint(TRUNCATE)` from inside a transaction.**
|
|
135
|
-
Maintenance subcommands open their own connection.
|
|
136
|
-
|
|
137
|
-
## 7. Maintenance windows
|
|
138
|
-
|
|
139
|
-
`ultra-tools db checkpoint` and `ultra-tools db vacuum` are safe to run
|
|
140
|
-
while the MCP server is up (they take their own connection and obey the
|
|
141
|
-
busy timeout). `db backup` uses better-sqlite3's online `.backup` API
|
|
142
|
-
and produces a consistent snapshot without blocking writers.
|
|
143
|
-
|
|
144
|
-
## 8. Verification
|
|
145
|
-
|
|
146
|
-
The contract on this page is enforced by:
|
|
147
|
-
|
|
148
|
-
- `mcp-server/tests/concurrency.test.cjs` — three writer threads
|
|
149
|
-
appending to `events` simultaneously; asserts no `SQLITE_BUSY` escapes
|
|
150
|
-
the retry loop and the resulting `events.id` sequence has no gaps and
|
|
151
|
-
no duplicates.
|
|
152
|
-
- `mcp-server/tests/state-ops.test.cjs` — immediate transactions, rollback,
|
|
153
|
-
status transitions, task contracts, and event coupling.
|
|
154
|
-
- `mcp-server/lib/stage-checkpoints.test.cjs` — mutable drafts, immutable accepted
|
|
155
|
-
revisions, supersession, and report-first diagnostics.
|
|
156
|
-
- `mcp-server/lib/context-envelope.test.cjs` and
|
|
157
|
-
`mcp-server/lib/worker-packet.test.cjs` — content-addressed Context and immutable
|
|
158
|
-
delegated handoff.
|
|
159
|
-
- `mcp-server/lib/task-ledger.test.cjs` — portable-field filtering, digest ancestry,
|
|
160
|
-
idempotent import, baseline/Change/task fast-forward and conflict behavior, legacy
|
|
161
|
-
backup, and active-session protection.
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Protect managed task files and restate the live Ultra Context Envelope."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from context_envelope import (
|
|
9
|
-
ContextEnvelopeError,
|
|
10
|
-
find_root,
|
|
11
|
-
read_context_envelope,
|
|
12
|
-
render_context_envelope,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
TEAM_TASK_LEDGER = Path(".ultra/tasks/tasks.json")
|
|
16
|
-
LIVE_TASK_PROJECTION = Path(".ultra/.runtime/projections/tasks.json")
|
|
17
|
-
PROTECTED_TASK_FILES = (TEAM_TASK_LEDGER, LIVE_TASK_PROJECTION)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def tool_paths(data: dict, start: Path):
|
|
21
|
-
"""Yield absolute tool targets without assuming they belong to ``start``."""
|
|
22
|
-
tool_input = data.get("tool_input")
|
|
23
|
-
if not isinstance(tool_input, dict):
|
|
24
|
-
return
|
|
25
|
-
for key in ("file_path", "filePath", "filepath", "path"):
|
|
26
|
-
value = tool_input.get(key)
|
|
27
|
-
if not isinstance(value, str) or not value.strip():
|
|
28
|
-
continue
|
|
29
|
-
candidate = Path(value)
|
|
30
|
-
yield candidate.resolve() if candidate.is_absolute() else (start / candidate).resolve()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def protected_root_for_target(target: Path) -> Path | None:
|
|
34
|
-
"""Return the project root for an exact MCP-managed task file."""
|
|
35
|
-
parts = target.parts
|
|
36
|
-
for relative in PROTECTED_TASK_FILES:
|
|
37
|
-
suffix = relative.parts
|
|
38
|
-
if len(parts) >= len(suffix) and parts[-len(suffix):] == suffix:
|
|
39
|
-
return Path(*parts[:-len(suffix)])
|
|
40
|
-
return None
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def targets_managed_task_file(data: dict, root: Path) -> bool:
|
|
44
|
-
for resolved in tool_paths(data, root):
|
|
45
|
-
if any(resolved == (root / relative).resolve() for relative in PROTECTED_TASK_FILES):
|
|
46
|
-
return True
|
|
47
|
-
return False
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def managed_file_denial(reason: str) -> dict:
|
|
51
|
-
return {"hookSpecificOutput": {
|
|
52
|
-
"hookEventName": "PreToolUse",
|
|
53
|
-
"permissionDecision": "deny",
|
|
54
|
-
"permissionDecisionReason": reason,
|
|
55
|
-
}}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def conflicted_managed_root(data: dict, start: Path) -> Path | None:
|
|
59
|
-
for candidate in (start, *start.parents):
|
|
60
|
-
if not (candidate / ".ultra").exists():
|
|
61
|
-
continue
|
|
62
|
-
if targets_managed_task_file(data, candidate):
|
|
63
|
-
return candidate
|
|
64
|
-
return None
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def managed_roots(data: dict, start: Path):
|
|
68
|
-
"""Yield initialized project roots derived from every resolved tool target."""
|
|
69
|
-
for target in tool_paths(data, start):
|
|
70
|
-
root = protected_root_for_target(target)
|
|
71
|
-
if root is not None and (root / ".ultra").exists():
|
|
72
|
-
yield root
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def main() -> None:
|
|
76
|
-
try:
|
|
77
|
-
data = json.loads(sys.stdin.read() or "{}")
|
|
78
|
-
data = data if isinstance(data, dict) else {}
|
|
79
|
-
except json.JSONDecodeError as exc:
|
|
80
|
-
print(f"[active_task_context] invalid hook input: {exc}", file=sys.stderr)
|
|
81
|
-
data = {}
|
|
82
|
-
start = Path(data.get("cwd") or Path.cwd()).resolve()
|
|
83
|
-
# An absolute tool target carries its own project boundary. Resolve and
|
|
84
|
-
# protect that authority before consulting cwd, because hooks may run from
|
|
85
|
-
# a host-owned temporary directory or another repository.
|
|
86
|
-
for target_root in managed_roots(data, start):
|
|
87
|
-
try:
|
|
88
|
-
target_authority = find_root(target_root)
|
|
89
|
-
except ContextEnvelopeError as exc:
|
|
90
|
-
print(json.dumps(managed_file_denial(
|
|
91
|
-
"Refusing a direct write to an MCP-managed Ultra task file while the "
|
|
92
|
-
f"runtime authority is conflicted: {exc}. Resolve the "
|
|
93
|
-
"state.db conflict with ultra-doctor before retrying the owning "
|
|
94
|
-
"MCP operation."
|
|
95
|
-
)))
|
|
96
|
-
return
|
|
97
|
-
if target_authority is None:
|
|
98
|
-
continue
|
|
99
|
-
print(json.dumps(managed_file_denial(
|
|
100
|
-
"Refusing a direct write to an MCP-managed Ultra task file. "
|
|
101
|
-
".ultra/tasks/tasks.json is the MCP-published team checkpoint and "
|
|
102
|
-
".ultra/.runtime/projections/tasks.json is the checkout-local DB view. "
|
|
103
|
-
"Use ultra.record for task contracts/outcomes and ultra.sync for the team checkpoint."
|
|
104
|
-
)))
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
root = find_root(start)
|
|
109
|
-
except ContextEnvelopeError as exc:
|
|
110
|
-
print(
|
|
111
|
-
f"[active_task_context] cannot resolve Ultra root: {exc}",
|
|
112
|
-
file=sys.stderr,
|
|
113
|
-
)
|
|
114
|
-
# Ordinary context injection remains fail-open. A direct write to a
|
|
115
|
-
# known managed task file is different: an authority conflict means the
|
|
116
|
-
# hook cannot prove which DB owns the publish or projection operation.
|
|
117
|
-
if conflicted_managed_root(data, start) is not None:
|
|
118
|
-
print(json.dumps(managed_file_denial(
|
|
119
|
-
"Refusing a direct write to an MCP-managed Ultra task file while the "
|
|
120
|
-
f"runtime authority is conflicted: {exc}. Resolve the "
|
|
121
|
-
"state.db conflict with ultra-doctor before retrying the owning "
|
|
122
|
-
"public Ultra operation."
|
|
123
|
-
)))
|
|
124
|
-
return
|
|
125
|
-
root = None
|
|
126
|
-
if root is None:
|
|
127
|
-
print(json.dumps({}))
|
|
128
|
-
return
|
|
129
|
-
if targets_managed_task_file(data, root):
|
|
130
|
-
print(json.dumps(managed_file_denial(
|
|
131
|
-
"Refusing a direct write to an MCP-managed Ultra task file. "
|
|
132
|
-
".ultra/tasks/tasks.json is the MCP-published team checkpoint and "
|
|
133
|
-
".ultra/.runtime/projections/tasks.json is the checkout-local DB view. "
|
|
134
|
-
"Use ultra.record for task contracts/outcomes and ultra.sync for the team checkpoint."
|
|
135
|
-
)))
|
|
136
|
-
return
|
|
137
|
-
try:
|
|
138
|
-
envelope = read_context_envelope(root)
|
|
139
|
-
except ContextEnvelopeError as exc:
|
|
140
|
-
print(f"[active_task_context] cannot inspect Context Envelope: {exc}", file=sys.stderr)
|
|
141
|
-
envelope = None
|
|
142
|
-
if envelope:
|
|
143
|
-
print(json.dumps({"hookSpecificOutput": {
|
|
144
|
-
"hookEventName": "PreToolUse",
|
|
145
|
-
"additionalContext": render_context_envelope(root, envelope),
|
|
146
|
-
}}))
|
|
147
|
-
return
|
|
148
|
-
print(json.dumps({}))
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if __name__ == "__main__":
|
|
152
|
-
main()
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Thin read-only bridge to the bundled Ultra Context Envelope reader."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import shutil
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
from runtime_paths import RuntimePathError, find_project_root
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class ContextEnvelopeError(RuntimeError):
|
|
15
|
-
"""The canonical Context Envelope helper could not be executed or decoded."""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ContextEnvelope(dict):
|
|
19
|
-
"""Dictionary envelope carrying its canonical bounded injection text."""
|
|
20
|
-
|
|
21
|
-
def __init__(self, value: dict, rendered: str):
|
|
22
|
-
super().__init__(value)
|
|
23
|
-
self.rendered = rendered
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def find_root(start: Path):
|
|
27
|
-
try:
|
|
28
|
-
return find_project_root(start)
|
|
29
|
-
except RuntimePathError as exc:
|
|
30
|
-
raise ContextEnvelopeError(str(exc)) from exc
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def find_root_for_hook(start: Path, hook_name: str):
|
|
34
|
-
"""Fail open at a hook boundary while preserving path-conflict evidence."""
|
|
35
|
-
try:
|
|
36
|
-
return find_root(start)
|
|
37
|
-
except ContextEnvelopeError as exc:
|
|
38
|
-
print(f"[{hook_name}] cannot resolve Ultra root: {exc}", file=sys.stderr)
|
|
39
|
-
return None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def _helper_path() -> Path:
|
|
43
|
-
hook_root = Path(__file__).resolve().parent
|
|
44
|
-
installed = hook_root.parent / "runtime" / "hook-context.cjs"
|
|
45
|
-
if installed.is_file():
|
|
46
|
-
return installed
|
|
47
|
-
source = hook_root.parent / "mcp-server" / "hook-context.cjs"
|
|
48
|
-
if source.is_file():
|
|
49
|
-
return source
|
|
50
|
-
raise ContextEnvelopeError("bundled Context Envelope helper is missing")
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _node_binary() -> str:
|
|
54
|
-
configured = os.environ.get("UBP_NODE")
|
|
55
|
-
if configured:
|
|
56
|
-
return configured
|
|
57
|
-
discovered = shutil.which("node")
|
|
58
|
-
if discovered:
|
|
59
|
-
return discovered
|
|
60
|
-
raise ContextEnvelopeError("node executable is unavailable")
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def read_context_envelope(root: Path):
|
|
64
|
-
try:
|
|
65
|
-
result = subprocess.run(
|
|
66
|
-
[_node_binary(), str(_helper_path()), str(root.resolve())],
|
|
67
|
-
text=True,
|
|
68
|
-
capture_output=True,
|
|
69
|
-
check=False,
|
|
70
|
-
timeout=5,
|
|
71
|
-
)
|
|
72
|
-
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
73
|
-
raise ContextEnvelopeError(str(exc)) from exc
|
|
74
|
-
if result.returncode != 0:
|
|
75
|
-
detail = result.stderr.strip() or f"exit {result.returncode}"
|
|
76
|
-
raise ContextEnvelopeError(detail)
|
|
77
|
-
try:
|
|
78
|
-
payload = json.loads(result.stdout)
|
|
79
|
-
except json.JSONDecodeError as exc:
|
|
80
|
-
raise ContextEnvelopeError(f"invalid Context Envelope response: {exc}") from exc
|
|
81
|
-
if not isinstance(payload, dict):
|
|
82
|
-
raise ContextEnvelopeError("Context Envelope response must be an object")
|
|
83
|
-
value = payload.get("context")
|
|
84
|
-
if value is None:
|
|
85
|
-
return None
|
|
86
|
-
rendered = payload.get("text")
|
|
87
|
-
if not isinstance(value, dict) or not isinstance(rendered, str) or not rendered:
|
|
88
|
-
raise ContextEnvelopeError(
|
|
89
|
-
"Context Envelope response is missing canonical state or text"
|
|
90
|
-
)
|
|
91
|
-
return ContextEnvelope(value, rendered)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def render_context_envelope(_root: Path, context: dict) -> str:
|
|
95
|
-
rendered = getattr(context, "rendered", None)
|
|
96
|
-
if not isinstance(rendered, str) or not rendered:
|
|
97
|
-
raise ContextEnvelopeError(
|
|
98
|
-
"Context Envelope was not produced by the canonical reader"
|
|
99
|
-
)
|
|
100
|
-
return rendered
|
package/hooks/health_check.py
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Fast, read-only Ultra runtime health inspection at session start."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import sqlite3
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from context_envelope import (
|
|
10
|
-
ContextEnvelopeError,
|
|
11
|
-
find_root_for_hook,
|
|
12
|
-
read_context_envelope,
|
|
13
|
-
)
|
|
14
|
-
from runtime_paths import state_db_path
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
REQUIRED_TABLES = {
|
|
18
|
-
"baselines",
|
|
19
|
-
"tasks", "events", "sessions", "schema_version", "migration_history",
|
|
20
|
-
"telemetry", "specs_refs", "circuit_breaker", "changes", "artifacts",
|
|
21
|
-
"artifact_edges", "context_snapshots", "spec_learning_candidates", "trace_links",
|
|
22
|
-
"incidents", "projection_jobs", "event_consumers", "stage_checkpoints",
|
|
23
|
-
"decision_records", "context_envelopes", "worker_packets",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def hook_input() -> dict:
|
|
28
|
-
try:
|
|
29
|
-
value = json.loads(sys.stdin.read() or "{}")
|
|
30
|
-
return value if isinstance(value, dict) else {}
|
|
31
|
-
except json.JSONDecodeError as exc:
|
|
32
|
-
print(f"[health_check] invalid hook input: {exc}", file=sys.stderr)
|
|
33
|
-
return {}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def inspect(root: Path) -> dict:
|
|
37
|
-
db_path = state_db_path(root)
|
|
38
|
-
report = {"status": "healthy", "project": str(root), "checks": {}}
|
|
39
|
-
issues = []
|
|
40
|
-
if not db_path.is_file():
|
|
41
|
-
report["checks"]["state_db"] = {"status": "fail", "reason": "missing"}
|
|
42
|
-
report["status"] = "degraded"
|
|
43
|
-
return report
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
context = read_context_envelope(root)
|
|
47
|
-
except ContextEnvelopeError as exc:
|
|
48
|
-
report["checks"]["authority"] = {"status": "fail", "reason": str(exc)}
|
|
49
|
-
report["status"] = "degraded"
|
|
50
|
-
return report
|
|
51
|
-
|
|
52
|
-
try:
|
|
53
|
-
uri = f"file:{db_path}?mode=ro"
|
|
54
|
-
with sqlite3.connect(uri, uri=True, timeout=1) as conn:
|
|
55
|
-
conn.row_factory = sqlite3.Row
|
|
56
|
-
quick = conn.execute("PRAGMA quick_check").fetchone()[0]
|
|
57
|
-
tables = {
|
|
58
|
-
row[0] for row in conn.execute(
|
|
59
|
-
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
missing = sorted(REQUIRED_TABLES - tables)
|
|
63
|
-
report["checks"]["state_db"] = {
|
|
64
|
-
"status": "pass" if quick == "ok" and not missing else "fail",
|
|
65
|
-
"integrity": quick,
|
|
66
|
-
"missing_tables": missing,
|
|
67
|
-
}
|
|
68
|
-
if quick != "ok" or missing:
|
|
69
|
-
issues.append("state_db")
|
|
70
|
-
if missing:
|
|
71
|
-
report["status"] = "degraded"
|
|
72
|
-
return report
|
|
73
|
-
|
|
74
|
-
envelope = context.get("envelope", {}) if context else {}
|
|
75
|
-
diagnostics = envelope.get("diagnostics", {})
|
|
76
|
-
baseline_blockers = [
|
|
77
|
-
str(item.get("code"))
|
|
78
|
-
for item in [
|
|
79
|
-
*diagnostics.get("needs_attention", []),
|
|
80
|
-
*diagnostics.get("hard_conflicts", []),
|
|
81
|
-
]
|
|
82
|
-
if str(item.get("code", "")).startswith("BASELINE_")
|
|
83
|
-
]
|
|
84
|
-
baseline_warnings = [
|
|
85
|
-
str(item.get("code"))
|
|
86
|
-
for item in diagnostics.get("warnings", [])
|
|
87
|
-
if str(item.get("code", "")).startswith("BASELINE_")
|
|
88
|
-
]
|
|
89
|
-
baseline = envelope.get("baseline")
|
|
90
|
-
baseline_is_ready = isinstance(baseline, dict) and baseline.get("status") == "ready"
|
|
91
|
-
baseline_status = (
|
|
92
|
-
"fail" if baseline_blockers and baseline_is_ready
|
|
93
|
-
else "warning" if baseline_blockers or baseline_warnings
|
|
94
|
-
else "pass"
|
|
95
|
-
)
|
|
96
|
-
report["checks"]["baseline"] = {
|
|
97
|
-
"baseline": baseline,
|
|
98
|
-
"blockers": baseline_blockers,
|
|
99
|
-
"warnings": baseline_warnings,
|
|
100
|
-
"readiness": "blocked" if baseline_blockers else "ready",
|
|
101
|
-
"status": baseline_status,
|
|
102
|
-
}
|
|
103
|
-
if baseline_status == "fail":
|
|
104
|
-
issues.append("baseline")
|
|
105
|
-
|
|
106
|
-
incidents = [
|
|
107
|
-
{"id": row[0], "code": row[1], "severity": row[2], "retryable": bool(row[3])}
|
|
108
|
-
for row in conn.execute(
|
|
109
|
-
"""SELECT id, code, severity, retryable FROM incidents
|
|
110
|
-
WHERE status = 'open' ORDER BY last_seen_at DESC LIMIT 20"""
|
|
111
|
-
)
|
|
112
|
-
]
|
|
113
|
-
report["checks"]["incidents"] = {
|
|
114
|
-
"status": "pass" if not incidents else "fail",
|
|
115
|
-
"open": incidents,
|
|
116
|
-
}
|
|
117
|
-
if incidents:
|
|
118
|
-
issues.append("incidents")
|
|
119
|
-
|
|
120
|
-
pending = conn.execute(
|
|
121
|
-
"SELECT COUNT(*) FROM projection_jobs WHERE status = 'pending'"
|
|
122
|
-
).fetchone()[0]
|
|
123
|
-
running = conn.execute(
|
|
124
|
-
"SELECT COUNT(*) FROM projection_jobs WHERE status = 'running'"
|
|
125
|
-
).fetchone()[0]
|
|
126
|
-
failed = conn.execute(
|
|
127
|
-
"SELECT COUNT(*) FROM projection_jobs WHERE status = 'failed'"
|
|
128
|
-
).fetchone()[0]
|
|
129
|
-
event_cursor = conn.execute(
|
|
130
|
-
"SELECT COALESCE(MAX(id), 0) FROM events"
|
|
131
|
-
).fetchone()[0]
|
|
132
|
-
projected_cursor = conn.execute(
|
|
133
|
-
"""SELECT COALESCE(MAX(event_cursor), 0) FROM projection_jobs
|
|
134
|
-
WHERE status = 'completed'"""
|
|
135
|
-
).fetchone()[0]
|
|
136
|
-
projection_ok = (
|
|
137
|
-
pending == 0 and running == 0 and failed == 0
|
|
138
|
-
and event_cursor <= projected_cursor
|
|
139
|
-
)
|
|
140
|
-
report["checks"]["projections"] = {
|
|
141
|
-
"status": "pass" if projection_ok else "fail",
|
|
142
|
-
"pending": pending,
|
|
143
|
-
"running": running,
|
|
144
|
-
"failed": failed,
|
|
145
|
-
"event_cursor": event_cursor,
|
|
146
|
-
"projected_cursor": projected_cursor,
|
|
147
|
-
}
|
|
148
|
-
if not projection_ok:
|
|
149
|
-
issues.append("projections")
|
|
150
|
-
|
|
151
|
-
orphan = conn.execute(
|
|
152
|
-
"SELECT COUNT(*) FROM sessions WHERE status = 'orphan'"
|
|
153
|
-
).fetchone()[0]
|
|
154
|
-
report["checks"]["sessions"] = {
|
|
155
|
-
"status": "pass" if orphan == 0 else "fail", "orphan": orphan,
|
|
156
|
-
}
|
|
157
|
-
if orphan:
|
|
158
|
-
issues.append("sessions")
|
|
159
|
-
|
|
160
|
-
missing_artifacts = []
|
|
161
|
-
for change_id, artifact_root in conn.execute(
|
|
162
|
-
"""SELECT id, artifact_root FROM changes
|
|
163
|
-
WHERE status IN ('active', 'blocked', 'ready')"""
|
|
164
|
-
):
|
|
165
|
-
if artifact_root and not (root / artifact_root).exists():
|
|
166
|
-
missing_artifacts.append(change_id)
|
|
167
|
-
report["checks"]["change_artifacts"] = {
|
|
168
|
-
"status": "pass" if not missing_artifacts else "fail",
|
|
169
|
-
"missing": missing_artifacts,
|
|
170
|
-
}
|
|
171
|
-
if missing_artifacts:
|
|
172
|
-
issues.append("change_artifacts")
|
|
173
|
-
except sqlite3.Error as exc:
|
|
174
|
-
report["checks"]["state_db"] = {"status": "fail", "reason": str(exc)}
|
|
175
|
-
issues.append("state_db")
|
|
176
|
-
|
|
177
|
-
if issues:
|
|
178
|
-
report["status"] = "degraded"
|
|
179
|
-
return report
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def main() -> None:
|
|
183
|
-
data = hook_input()
|
|
184
|
-
start = Path(data.get("cwd") or Path.cwd()).resolve()
|
|
185
|
-
root = find_root_for_hook(start, "health_check")
|
|
186
|
-
if root is None:
|
|
187
|
-
print(json.dumps({}))
|
|
188
|
-
return
|
|
189
|
-
report = inspect(root)
|
|
190
|
-
if report["status"] != "healthy":
|
|
191
|
-
print("[Ultra health] " + json.dumps(report, ensure_ascii=False, sort_keys=True), file=sys.stderr)
|
|
192
|
-
print(json.dumps({}))
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if __name__ == "__main__":
|
|
196
|
-
main()
|
package/hooks/pre_stop_check.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Report unfinished DB-authoritative Ultra work without trapping session stop."""
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import sys
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from context_envelope import (
|
|
9
|
-
ContextEnvelopeError,
|
|
10
|
-
find_root_for_hook,
|
|
11
|
-
read_context_envelope,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def allow_stop() -> None:
|
|
16
|
-
print(json.dumps({}))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def main() -> None:
|
|
20
|
-
try:
|
|
21
|
-
hook_data = json.loads(sys.stdin.read() or "{}")
|
|
22
|
-
hook_data = hook_data if isinstance(hook_data, dict) else {}
|
|
23
|
-
except json.JSONDecodeError as exc:
|
|
24
|
-
print(f"[pre_stop_check] invalid hook input: {exc}", file=sys.stderr)
|
|
25
|
-
allow_stop()
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
if hook_data.get("stop_hook_active", False):
|
|
29
|
-
allow_stop()
|
|
30
|
-
return
|
|
31
|
-
root = find_root_for_hook(
|
|
32
|
-
Path(hook_data.get("cwd") or Path.cwd()).resolve(),
|
|
33
|
-
"pre_stop_check",
|
|
34
|
-
)
|
|
35
|
-
if root is None:
|
|
36
|
-
allow_stop()
|
|
37
|
-
return
|
|
38
|
-
try:
|
|
39
|
-
context = read_context_envelope(root)
|
|
40
|
-
except ContextEnvelopeError as exc:
|
|
41
|
-
print(f"[pre_stop_check] cannot inspect Context Envelope: {exc}", file=sys.stderr)
|
|
42
|
-
allow_stop()
|
|
43
|
-
return
|
|
44
|
-
envelope = context.get("envelope", {}) if context else {}
|
|
45
|
-
change = envelope.get("change") or {}
|
|
46
|
-
task = envelope.get("task") or {}
|
|
47
|
-
if change.get("id") and change.get("status") not in ("archived", "cancelled"):
|
|
48
|
-
attention = envelope.get("diagnostics", {}).get("needs_attention", [])
|
|
49
|
-
warnings = envelope.get("diagnostics", {}).get("warnings", [])
|
|
50
|
-
codes = ", ".join(
|
|
51
|
-
str(item.get("code")) for item in [*attention, *warnings] if item.get("code")
|
|
52
|
-
) or "none"
|
|
53
|
-
print(
|
|
54
|
-
"[Ultra stop advisory] Active Ultra change remains open: "
|
|
55
|
-
f"change={change['id']}, "
|
|
56
|
-
f"task={task.get('id') or 'none'}, "
|
|
57
|
-
f"stage={envelope.get('execution', {}).get('stage') or 'project'}, "
|
|
58
|
-
f"diagnostics={codes}. "
|
|
59
|
-
"Stop is allowed; read ultra.context before resuming or explicitly archive later.",
|
|
60
|
-
file=sys.stderr,
|
|
61
|
-
)
|
|
62
|
-
allow_stop()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if __name__ == "__main__":
|
|
66
|
-
main()
|