ultra-builder-pro-cli 0.12.1 → 0.14.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/docs/research/README.md +21 -44
- package/.ultra-template/specs/discovery.md +10 -0
- package/CHANGELOG.md +95 -1
- package/README.md +31 -16
- package/adapters/_shared/codex-assets.cjs +23 -163
- package/adapters/_shared/handbook.cjs +2 -2
- package/adapters/_shared/runtime-assets.cjs +1 -0
- package/adapters/_shared/tests/runtime-assets.test.cjs +18 -0
- package/adapters/opencode.js +30 -120
- package/adapters/tests/claude.test.cjs +3 -2
- package/adapters/tests/codex-hook.test.cjs +34 -7
- package/adapters/tests/codex.test.cjs +21 -6
- package/adapters/tests/kimi-hook.test.cjs +34 -7
- package/adapters/tests/opencode.test.cjs +70 -84
- package/commands/ultra-init.md +1 -1
- package/docs/AGENT-CONTEXT.md +10 -6
- package/docs/ARCHITECTURE.md +40 -17
- package/docs/PLAN.zh-CN.md +5 -4
- package/docs/ROADMAP.md +2 -2
- package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
- package/docs/STATE-DB-ACCESS-POLICY.md +3 -3
- package/docs/USER-HANDBOOK-CONTRACT.md +5 -4
- package/hooks/active_task_context.py +22 -50
- package/hooks/context_spine.py +61 -299
- package/hooks/health_check.py +19 -17
- package/hooks/pre_stop_check.py +28 -33
- package/hooks/subagent_tracker.py +13 -19
- package/hooks/workflow_checkpoint.py +39 -47
- package/hooks/workflow_context.py +4 -9
- package/hooks/workflow_resume.py +48 -128
- package/mcp-server/breadcrumb.cjs +22 -0
- package/mcp-server/lib/archive-journal.cjs +192 -0
- package/mcp-server/lib/archive-journal.test.cjs +35 -0
- package/mcp-server/lib/baseline-workflow.cjs +404 -38
- package/mcp-server/lib/baseline-workflow.test.cjs +252 -0
- package/mcp-server/lib/change-workflow.cjs +249 -75
- package/mcp-server/lib/change-workflow.test.cjs +235 -1
- package/mcp-server/lib/context-spine.test.cjs +5 -0
- package/mcp-server/lib/doctor.cjs +14 -1
- package/mcp-server/lib/doctor.test.cjs +42 -4
- package/mcp-server/lib/init-project.cjs +292 -9
- package/mcp-server/lib/prd-parser.cjs +14 -41
- package/mcp-server/lib/prd-parser.test.cjs +49 -138
- package/mcp-server/lib/project-breadcrumb.cjs +139 -0
- package/mcp-server/lib/project-breadcrumb.test.cjs +112 -0
- package/mcp-server/lib/spec-learning.test.cjs +1 -1
- package/mcp-server/lib/state-authority.cjs +9 -3
- package/mcp-server/lib/state-db.cjs +154 -32
- package/mcp-server/lib/state-db.test.cjs +136 -4
- package/mcp-server/lib/state-ops.cjs +68 -6
- package/mcp-server/lib/task-expander.cjs +14 -37
- package/mcp-server/lib/task-expander.test.cjs +80 -164
- package/mcp-server/server.cjs +34 -37
- package/mcp-server/tests/init-project.test.cjs +162 -1
- package/mcp-server/tests/migrate.test.cjs +33 -0
- package/mcp-server/tests/server-contract.test.cjs +391 -17
- package/mcp-server/tests/state-db.test.cjs +14 -2
- package/mcp-server/tests/state-ops.test.cjs +108 -0
- package/package.json +2 -4
- package/skills/cc-collab/SKILL.md +2 -2
- package/skills/codex-collab/SKILL.md +2 -3
- package/skills/ultra-change/SKILL.md +18 -10
- package/skills/ultra-deliver/SKILL.md +13 -9
- package/skills/ultra-dev/SKILL.md +6 -4
- package/skills/ultra-doctor/SKILL.md +22 -7
- package/skills/ultra-init/SKILL.md +104 -59
- package/skills/ultra-init/references/brownfield-adoption.md +65 -37
- package/skills/ultra-plan/SKILL.md +20 -7
- package/skills/ultra-research/SKILL.md +12 -5
- package/skills/ultra-status/SKILL.md +6 -4
- package/skills/ultra-verify/SKILL.md +21 -30
- package/skills/ultra-verify/scripts/verify_wait.py +54 -54
- package/spec/cli-protocol.md +11 -1
- package/spec/mcp-tools.yaml +133 -19
- package/spec/schemas/state-db.sql +11 -0
- package/ultra-tools/cli.cjs +9 -5
- package/ultra-tools/commands/migrate.cjs +62 -36
- package/ultra-tools/commands/status.cjs +18 -9
- package/ultra-tools/commands/system.cjs +340 -6
- package/ultra-tools/commands/task.cjs +7 -1
- package/ultra-tools/status-command.test.cjs +29 -0
- package/ultra-tools/system-command.test.cjs +252 -1
- package/mcp-server/lib/llm-client.cjs +0 -157
- package/mcp-server/lib/llm-client.test.cjs +0 -156
- package/orchestrator/planner/expand-prompt.cjs +0 -62
- package/orchestrator/planner/expand-prompt.test.cjs +0 -52
- package/orchestrator/planner/prd-prompt.cjs +0 -44
- package/orchestrator/planner/prd-prompt.test.cjs +0 -30
|
@@ -1,65 +1,93 @@
|
|
|
1
1
|
# Brownfield adoption
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
existing
|
|
3
|
+
Execute this procedure for a detected brownfield repository, a migrated compatibility
|
|
4
|
+
baseline, or an existing adoption in `adopting` or `blocked` state.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## Bind the repository boundary
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
generated
|
|
10
|
-
Use
|
|
11
|
-
|
|
8
|
+
Record the repository root, selected scope, workspace roots, branch, HEAD, dirty files,
|
|
9
|
+
generated and vendored exclusions, manifests, verification commands, and public seams.
|
|
10
|
+
Use `.` for a whole-repository adoption. Use selected workspace roots only when the
|
|
11
|
+
user's requested ownership is narrower than the monorepo.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The authoritative worktree snapshot contains only selected-scope paths. Report dirty
|
|
14
|
+
files outside that scope separately as repository context, but do not include them in
|
|
15
|
+
the baseline digest. Require `accept_dirty_worktree: true` only after the owner confirms
|
|
16
|
+
the exact in-scope `worktree_files` returned by the server.
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## Build the evidence model
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
- persisted state and its writers;
|
|
19
|
-
- external interfaces, events, permissions, and failure paths;
|
|
20
|
-
- tests and their live seams;
|
|
21
|
-
- deployment, migration, and recovery paths when present;
|
|
22
|
-
- maintained documents and their relationship to source.
|
|
20
|
+
Inspect the maintained system through its live boundaries:
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
- product behavior, actors, entry points, and public acceptance;
|
|
23
|
+
- runtime modules, consumers, data flow, persistence, and authority;
|
|
24
|
+
- APIs, events, integrations, permissions, security, and failure paths;
|
|
25
|
+
- build, test, lint, typecheck, deployment, migration, observability, and recovery;
|
|
26
|
+
- maintained documents and every detected source-to-document conflict.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Classify each material statement as `Observed`, `Verified`, `Decided`, or `Unknown`.
|
|
29
|
+
Record document/source conflicts as drift. Keep provider payloads outside Ultra and
|
|
30
|
+
record only bounded metadata references.
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
and acceptance. Update `.ultra/specs/architecture.md` with actual boundaries, authority,
|
|
32
|
-
runtime paths, dependencies, permissions, and recovery. Use `discovery.md` only for
|
|
33
|
-
evidence or decisions that affect scope.
|
|
32
|
+
## Write baseline artifacts
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
Update `.ultra/specs/product.md` with current delivered behavior and acceptance. Update
|
|
35
|
+
`.ultra/specs/architecture.md` with real boundaries, owners, writers, consumers,
|
|
36
|
+
consistency rules, permissions, failures, and recovery. Use `discovery.md` for scope
|
|
37
|
+
evidence, decisions, and unresolved questions.
|
|
38
|
+
|
|
39
|
+
Capture current facts without redesigning the application or manufacturing product
|
|
40
|
+
narratives that are absent from evidence.
|
|
41
|
+
|
|
42
|
+
## Run characterization verification
|
|
37
43
|
|
|
38
44
|
Run the repository's existing verification commands. Record each result as `pass`,
|
|
39
45
|
`known_red`, or `not_run`. A known-red item needs evidence and a rationale; a missing
|
|
40
|
-
command is not a passing result.
|
|
41
|
-
|
|
46
|
+
command is not a passing result. Record a characterization gap for a critical public
|
|
47
|
+
seam that has no stable verification signal; create its ordinary task only after the
|
|
48
|
+
baseline is ready.
|
|
49
|
+
|
|
50
|
+
## Maintain the gap ledger
|
|
51
|
+
|
|
52
|
+
Record every adoption gap with a stable id, evidence references, owner, blocking flag,
|
|
53
|
+
and one category:
|
|
54
|
+
|
|
55
|
+
- `baseline_blocker`: prevents trustworthy adoption;
|
|
56
|
+
- `documentation_drift`: maintained prose conflicts with observed behavior;
|
|
57
|
+
- `known_defect`: reproducible incorrect behavior;
|
|
58
|
+
- `technical_debt`: accepted maintainability or quality cost;
|
|
59
|
+
- `unknown`: evidence is insufficient;
|
|
60
|
+
- `future_change`: desired behavior outside the current baseline.
|
|
61
|
+
|
|
62
|
+
Use `open` for unresolved gaps, `accepted` for owner-accepted non-blocking debt,
|
|
63
|
+
`resolved` when evidence closes the gap, and `deferred` for explicitly postponed
|
|
64
|
+
non-blocking work. Every open `baseline_blocker` remains blocking. Do not convert the
|
|
65
|
+
ledger into hundreds of application tasks.
|
|
66
|
+
|
|
67
|
+
## Record authority
|
|
42
68
|
|
|
43
69
|
Call `baseline.record` with full replacement arrays for:
|
|
44
70
|
|
|
45
71
|
- scope and repository revision;
|
|
72
|
+
- repository classification;
|
|
46
73
|
- product and architecture specification paths;
|
|
47
74
|
- bounded source, documentation, runtime, test, deployment, or external references;
|
|
48
75
|
- verification results;
|
|
49
76
|
- blocking and non-blocking unknowns;
|
|
77
|
+
- the complete gap ledger;
|
|
50
78
|
- external provider metadata references, when supplied.
|
|
51
79
|
|
|
52
|
-
|
|
53
|
-
code-graph payloads.
|
|
80
|
+
Use the server-returned revision, branch, worktree digest, and specification digests.
|
|
54
81
|
|
|
55
|
-
##
|
|
82
|
+
## Obtain approval and converge
|
|
56
83
|
|
|
57
|
-
Present
|
|
58
|
-
decisions to the
|
|
59
|
-
|
|
60
|
-
owner accepted
|
|
84
|
+
Present scope, observed behavior, drift, known-red verification, unknowns, gaps, dirty
|
|
85
|
+
worktree state, and material decisions to the owner. Call `baseline.converge` only after
|
|
86
|
+
explicit approval. Set `accept_known_red` and `accept_dirty_worktree` only for the exact
|
|
87
|
+
items the owner accepted.
|
|
61
88
|
|
|
62
89
|
Resolve returned blockers through evidence or an owner decision, record the replacement
|
|
63
|
-
snapshot, and retry. Adoption is complete only when `baseline.get`
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
snapshot, and retry. Adoption is complete only when `baseline.get` is `ready` and
|
|
91
|
+
current, the gap ledger has no open blocker, and project doctor has no authority
|
|
92
|
+
failure. Route new work through `ultra-change`; route already accepted planned work
|
|
93
|
+
through `ultra-plan`.
|
|
@@ -16,8 +16,10 @@ route to `ultra-doctor`. Never read or write `.ultra/tasks/tasks.json` as a fall
|
|
|
16
16
|
Call `baseline.get`. Initial product planning is blocked until the baseline is
|
|
17
17
|
`ready` and current; route its missing, adopting, blocked, or stale state to
|
|
18
18
|
`ultra-init`. Planning an already active bounded change may continue with the
|
|
19
|
-
baseline condition recorded as a warning.
|
|
20
|
-
adoption, and archive atomically reconciles and
|
|
19
|
+
baseline condition recorded as a warning. Ordinary convergence still requires
|
|
20
|
+
approved adoption, and archive atomically reconciles revision and specification
|
|
21
|
+
drift. An approved break-glass incident follows its recorded recovery contract and
|
|
22
|
+
creates a blocking reconciliation gap at archive.
|
|
21
23
|
|
|
22
24
|
Choose one input:
|
|
23
25
|
|
|
@@ -63,11 +65,22 @@ id when applicable.
|
|
|
63
65
|
|
|
64
66
|
## PRD mode
|
|
65
67
|
|
|
66
|
-
1.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
1. Read the approved PRD with the current host model and derive the proposed task
|
|
69
|
+
objects using the `task.parse_prd` input schema. Do not invoke a second model or
|
|
70
|
+
require a provider API key inside Ultra MCP.
|
|
71
|
+
2. Call `task.parse_prd` with those `tasks`, `dry_run: true`, and the active
|
|
72
|
+
`change_id` when applicable. The MCP validates the graph but does not supply
|
|
73
|
+
product judgment.
|
|
74
|
+
3. Show the proposed slices, topology, conflict surface, and cost estimate.
|
|
75
|
+
4. Require explicit approval; rejection performs no state write.
|
|
76
|
+
5. On approval, repeat with `dry_run: false`, the identical task objects, and the
|
|
77
|
+
same change ownership; export
|
|
78
|
+
the plan, and verify that the dry-run and persisted task identities agree.
|
|
79
|
+
|
|
80
|
+
When one approved task is still too broad, derive its complete child graph with the
|
|
81
|
+
current host model and call `task.expand` with the parent `id` and validated
|
|
82
|
+
`children`. The MCP assigns parent, tag, and change ownership atomically. Never ask
|
|
83
|
+
the MCP runtime to generate child content.
|
|
71
84
|
|
|
72
85
|
## Completion gate
|
|
73
86
|
|
|
@@ -51,11 +51,18 @@ default.
|
|
|
51
51
|
5. Validate cross-document consistency and trace each required behavior from problem
|
|
52
52
|
or constraint to acceptance and architecture.
|
|
53
53
|
6. Use `references/synthesis.md` for the completion check.
|
|
54
|
-
7.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
7. Record unresolved evidence as the baseline gap ledger: `baseline_blocker`,
|
|
55
|
+
`documentation_drift`, `known_defect`, `technical_debt`, `unknown`, or
|
|
56
|
+
`future_change`. Keep accepted non-blocking work out of the implementation backlog
|
|
57
|
+
until the owner selects it.
|
|
58
|
+
8. Call `baseline.record` with the complete current specification list, bounded evidence
|
|
59
|
+
references, actual verification results, provider metadata, explicit unknowns,
|
|
60
|
+
classification, and the full gap ledger. Use the returned repository revision for
|
|
61
|
+
convergence.
|
|
62
|
+
9. Present the baseline, open gaps, dirty-worktree state, and any known-red verification
|
|
63
|
+
to the owner. After explicit approval, call `baseline.converge`, setting acceptance
|
|
64
|
+
flags only for the exact known-red or dirty snapshot approved by the owner. Resolve
|
|
65
|
+
deterministic blockers before routing.
|
|
59
66
|
|
|
60
67
|
## Completion gate
|
|
61
68
|
|
|
@@ -12,8 +12,8 @@ authoritative; reports and Markdown are evidence or projections.
|
|
|
12
12
|
|
|
13
13
|
1. Call `system.doctor` in read-only mode. If authority is unhealthy, route only to
|
|
14
14
|
`ultra-doctor` and include the blocking diagnostic.
|
|
15
|
-
2. Call `baseline.get` for mode, lifecycle status, repository revision,
|
|
16
|
-
digest health,
|
|
15
|
+
2. Call `baseline.get` for mode, lifecycle status, repository revision, branch,
|
|
16
|
+
worktree snapshot, specification digest health, gap ledger, and adoption blockers.
|
|
17
17
|
3. Call `change.breadcrumb` for the active change, task, role, gate, readiness,
|
|
18
18
|
blockers, advisory warnings, staleness, and next action.
|
|
19
19
|
4. Call `task.list` and `session.list` for progress and active execution details.
|
|
@@ -30,7 +30,9 @@ Follow the breadcrumb's primary route and return one action, not a menu:
|
|
|
30
30
|
|
|
31
31
|
- unhealthy authority: `ultra-doctor`;
|
|
32
32
|
- missing Ultra project: `ultra-init`;
|
|
33
|
-
-
|
|
33
|
+
- projection-only authority conflict: run the exact supported import command from the
|
|
34
|
+
MCP error, then `ultra-init`;
|
|
35
|
+
- migrated or missing baseline evidence with no active change: `ultra-init`, then
|
|
34
36
|
`ultra-research` or `ultra-plan` according to the gap;
|
|
35
37
|
- baseline or context-budget warning during an active change: keep the current route
|
|
36
38
|
and surface the warning; do not turn it into a refusal;
|
|
@@ -50,7 +52,7 @@ Use a compact shape:
|
|
|
50
52
|
```text
|
|
51
53
|
Ultra: <healthy|degraded> · <branch>@<head> · worktree <clean|dirty>
|
|
52
54
|
Position: change=<id|none> task=<id|none> role=<role> gate=<gate>
|
|
53
|
-
Baseline: <greenfield|brownfield|migrated>/<status> revision=<revision|none>
|
|
55
|
+
Baseline: <greenfield|brownfield|migrated>/<status> revision=<revision|none> gaps=<open>/<blocking>
|
|
54
56
|
Readiness: <ready|blocked|stale> — <blockers or none>
|
|
55
57
|
Warnings: <advisory conditions or none>
|
|
56
58
|
Progress: <completed>/<total>; sessions=<active count>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ultra-verify
|
|
3
|
-
description: Run independent two-model verification with the current host as primary and
|
|
3
|
+
description: Run independent two-model verification with the current host as primary and one installed read-only advisor, then verify and synthesize both evidence sets. Use only when the user explicitly requests cross-model verification.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Run independent cross-model verification
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
final answer.
|
|
8
|
+
Keep the current host responsible for scope, evidence, edits, verification, and the
|
|
9
|
+
final answer. Use one installed collaboration companion that invokes a different CLI;
|
|
10
|
+
never recurse into the current host or treat advisor output as authority.
|
|
10
11
|
|
|
11
12
|
## Modes
|
|
12
13
|
|
|
@@ -17,36 +18,26 @@ final answer. Codex is an untrusted read-only advisor.
|
|
|
17
18
|
|
|
18
19
|
## Workflow
|
|
19
20
|
|
|
20
|
-
1.
|
|
21
|
-
|
|
22
|
-
2. Create `.ultra/collab/<session-id>/` and write the
|
|
23
|
-
analysis to `
|
|
24
|
-
3.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
--ignore-user-config \
|
|
32
|
-
--ignore-rules \
|
|
33
|
-
-o "${SESSION_PATH}/codex-output.md" \
|
|
34
|
-
"<bounded prompt>" \
|
|
35
|
-
2> "${SESSION_PATH}/codex-error.log"
|
|
36
|
-
```
|
|
37
|
-
4. Run `scripts/verify_wait.py` resolved relative to this skill directory. Use a
|
|
38
|
-
yielded or asynchronous shell session and poll at bounded intervals; do not hold a
|
|
39
|
-
blocking tool call longer than one minute.
|
|
21
|
+
1. Define the workspace, question, evidence standard, and response shape. Confirm the
|
|
22
|
+
selected advisor CLI and authentication. Exclude secrets and unrelated files.
|
|
23
|
+
2. Create `.ultra/collab/<session-id>/` and write the current host's independent,
|
|
24
|
+
evidence-backed analysis to `host-analysis.md` before reading advisor output.
|
|
25
|
+
3. Follow the installed collaboration companion's read-only invocation contract. Give
|
|
26
|
+
the advisor the same bounded question and evidence, without the primary conclusion
|
|
27
|
+
when independence matters. Store its result as `advisor-output.md` and diagnostics
|
|
28
|
+
as `advisor-error.log`.
|
|
29
|
+
4. Run `scripts/verify_wait.py <session-path> --advisor advisor --output
|
|
30
|
+
advisor-output.md` from this skill directory. Use a yielded shell session and poll
|
|
31
|
+
in bounded intervals; do not hold one blocking tool call longer than one minute.
|
|
40
32
|
5. Read advisor output only after the waiter reports a complete, non-empty file.
|
|
41
|
-
6. Verify
|
|
42
|
-
documentation. Explain scope, version, evidence, and assumption differences
|
|
43
|
-
|
|
44
|
-
7. Write
|
|
45
|
-
conclusion.
|
|
33
|
+
6. Verify consequential claims against the current checkout, tests, runtime, or
|
|
34
|
+
primary documentation. Explain scope, version, evidence, and assumption differences
|
|
35
|
+
before resolving disagreement.
|
|
36
|
+
7. Write `metadata.json` and `synthesis.md`, then return one host-owned conclusion.
|
|
46
37
|
|
|
47
38
|
Read `references/cross-verify-modes.md` for mode-specific evidence and
|
|
48
39
|
`references/confidence-system.md` when confidence language is useful. Model agreement
|
|
49
40
|
never overrides failing tests or authoritative runtime evidence.
|
|
50
41
|
|
|
51
|
-
Advisor failure degrades to the primary analysis with a single-source warning. It
|
|
52
|
-
|
|
42
|
+
Advisor failure degrades to the primary analysis with a single-source warning. It
|
|
43
|
+
never blocks the user's underlying task.
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
Poll the session directory until the expected output is non-empty and stable, or until timeout.
|
|
5
|
-
The JSON result carries the operational status; timeout is not a process error.
|
|
6
|
-
"""
|
|
2
|
+
"""Wait for one named advisor output file to become non-empty and stable."""
|
|
7
3
|
|
|
4
|
+
import argparse
|
|
8
5
|
import json
|
|
6
|
+
import re
|
|
9
7
|
import sys
|
|
10
8
|
import time
|
|
11
9
|
from pathlib import Path
|
|
12
10
|
|
|
13
11
|
POLL_INTERVAL = 3
|
|
14
12
|
DEFAULT_TIMEOUT = 1200
|
|
15
|
-
|
|
16
|
-
OUTPUT_FILE = "codex-output.md"
|
|
13
|
+
SAFE_ADVISOR = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
def _file_size(path: Path) -> int:
|
|
@@ -23,71 +20,74 @@ def _file_size(path: Path) -> int:
|
|
|
23
20
|
return -1
|
|
24
21
|
|
|
25
22
|
|
|
26
|
-
def
|
|
27
|
-
|
|
23
|
+
def _safe_output(value: str) -> Path:
|
|
24
|
+
result = Path(value)
|
|
25
|
+
if result.is_absolute() or len(result.parts) != 1 or result.name in {"", ".", ".."}:
|
|
26
|
+
raise argparse.ArgumentTypeError("--output must be one file name inside the session directory")
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _arguments():
|
|
31
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
32
|
+
parser.add_argument("session_path", type=Path)
|
|
33
|
+
parser.add_argument("--advisor", default="advisor")
|
|
34
|
+
parser.add_argument("--output", type=_safe_output, default=Path("advisor-output.md"))
|
|
35
|
+
parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT)
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
if not SAFE_ADVISOR.fullmatch(args.advisor):
|
|
38
|
+
parser.error("--advisor must use letters, numbers, underscore, or hyphen")
|
|
39
|
+
if args.timeout < 0:
|
|
40
|
+
parser.error("--timeout must be non-negative")
|
|
41
|
+
if not args.session_path.is_dir():
|
|
42
|
+
parser.error(f"session directory not found: {args.session_path}")
|
|
43
|
+
return args
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _status(session_path: Path, advisor: str, output_name: Path) -> dict:
|
|
47
|
+
output = session_path / output_name
|
|
28
48
|
if _file_size(output) > 0:
|
|
29
|
-
return {"name":
|
|
30
|
-
return {"name":
|
|
49
|
+
return {"name": advisor, "status": "complete", "file": str(output)}
|
|
50
|
+
return {"name": advisor, "status": "pending", "file": None}
|
|
31
51
|
|
|
32
52
|
|
|
33
|
-
def _timeout_status(session_path: Path) -> dict:
|
|
34
|
-
output = session_path /
|
|
35
|
-
error = session_path / f"{
|
|
53
|
+
def _timeout_status(session_path: Path, advisor: str, output_name: Path) -> dict:
|
|
54
|
+
output = session_path / output_name
|
|
55
|
+
error = session_path / f"{advisor}-error.log"
|
|
36
56
|
output_size = _file_size(output)
|
|
37
57
|
if output_size > 0:
|
|
38
|
-
return {"name":
|
|
58
|
+
return {"name": advisor, "status": "complete", "file": str(output)}
|
|
39
59
|
if _file_size(error) > 0:
|
|
40
|
-
return {"name":
|
|
60
|
+
return {"name": advisor, "status": "failed", "file": str(error)}
|
|
41
61
|
if output_size == 0:
|
|
42
|
-
return {"name":
|
|
43
|
-
return {"name":
|
|
62
|
+
return {"name": advisor, "status": "empty", "file": str(output)}
|
|
63
|
+
return {"name": advisor, "status": "pending", "file": None}
|
|
44
64
|
|
|
45
65
|
|
|
46
66
|
def main() -> None:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
session_path = Path(sys.argv[1])
|
|
52
|
-
timeout = DEFAULT_TIMEOUT
|
|
53
|
-
if "--timeout" in sys.argv:
|
|
54
|
-
idx = sys.argv.index("--timeout")
|
|
55
|
-
if idx + 1 >= len(sys.argv):
|
|
56
|
-
print("Error: --timeout requires an integer", file=sys.stderr)
|
|
57
|
-
sys.exit(2)
|
|
58
|
-
try:
|
|
59
|
-
timeout = int(sys.argv[idx + 1])
|
|
60
|
-
except ValueError:
|
|
61
|
-
print(f"Error: --timeout must be integer: {sys.argv[idx + 1]}", file=sys.stderr)
|
|
62
|
-
sys.exit(2)
|
|
63
|
-
if timeout < 0:
|
|
64
|
-
print("Error: --timeout must be non-negative", file=sys.stderr)
|
|
65
|
-
sys.exit(2)
|
|
66
|
-
|
|
67
|
-
if not session_path.is_dir():
|
|
68
|
-
print(f"Error: session directory not found: {session_path}", file=sys.stderr)
|
|
69
|
-
sys.exit(2)
|
|
70
|
-
|
|
71
|
-
deadline = time.monotonic() + timeout
|
|
72
|
-
previous_size = _file_size(session_path / OUTPUT_FILE)
|
|
67
|
+
args = _arguments()
|
|
68
|
+
output = args.session_path / args.output
|
|
69
|
+
deadline = time.monotonic() + args.timeout
|
|
70
|
+
previous_size = _file_size(output)
|
|
73
71
|
while time.monotonic() < deadline:
|
|
74
|
-
current_size = _file_size(
|
|
75
|
-
advisor =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
current_size = _file_size(output)
|
|
73
|
+
advisor = _status(args.session_path, args.advisor, args.output)
|
|
74
|
+
if advisor["status"] == "complete" and current_size == previous_size:
|
|
75
|
+
elapsed = int(args.timeout - (deadline - time.monotonic()))
|
|
76
|
+
print(json.dumps({
|
|
77
|
+
"status": "complete", "advisor": advisor, "elapsed_seconds": elapsed,
|
|
78
|
+
}))
|
|
80
79
|
return
|
|
81
|
-
|
|
82
80
|
state = "stabilizing" if advisor["status"] == "complete" else "waiting"
|
|
83
81
|
remaining = max(0, int(deadline - time.monotonic()))
|
|
84
|
-
sys.stderr.write(f"\r [0/1] {
|
|
82
|
+
sys.stderr.write(f"\r [0/1] {args.advisor}:{state} ({remaining}s remaining) ")
|
|
85
83
|
sys.stderr.flush()
|
|
86
84
|
previous_size = current_size
|
|
87
85
|
time.sleep(POLL_INTERVAL)
|
|
88
86
|
|
|
89
|
-
advisor = _timeout_status(session_path)
|
|
90
|
-
print(json.dumps({
|
|
87
|
+
advisor = _timeout_status(args.session_path, args.advisor, args.output)
|
|
88
|
+
print(json.dumps({
|
|
89
|
+
"status": "timeout", "advisor": advisor, "elapsed_seconds": args.timeout,
|
|
90
|
+
}))
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
if __name__ == "__main__":
|
package/spec/cli-protocol.md
CHANGED
|
@@ -120,10 +120,20 @@ as a fallback unless `ultra-tools <family> --help` lists it.
|
|
|
120
120
|
|
|
121
121
|
Current executable maintenance surfaces are `task init-project`,
|
|
122
122
|
`session close|get|list|admission|heartbeat|subscribe|reap`, `status`, `db`,
|
|
123
|
-
`migrate`, and `legacy-memory`. In particular, `task create|update|list|get`
|
|
123
|
+
`migrate`, `system doctor|restore|rebaseline`, and `legacy-memory`. In particular, `task create|update|list|get`
|
|
124
124
|
and all `change` lifecycle verbs are not CLI fallbacks. Change state must use
|
|
125
125
|
the live MCP server; `system doctor` may additionally be exposed by the
|
|
126
126
|
maintenance CLI because it is the recovery path when MCP startup is degraded.
|
|
127
|
+
`task init-project --resume` preserves existing `.ultra` files and installs only
|
|
128
|
+
missing current scaffold assets. Projection-only state uses the supported
|
|
129
|
+
`migrate --from=4.4 --to=4.5` or `--from=4.5 --to=12.0` transition before resume.
|
|
130
|
+
`system doctor --repair` applies supported schema upgrades with a pre-migration
|
|
131
|
+
backup before mechanical recovery.
|
|
132
|
+
`system restore` requires the exact `REPLACE_CORRUPT_ULTRA_STATE` confirmation and a
|
|
133
|
+
verified database inside `.ultra/backups`; it quarantines the corrupt state before
|
|
134
|
+
atomic replacement. `system rebaseline` requires the exact
|
|
135
|
+
`REBASELINE_CORRUPT_ULTRA_STATE` confirmation and preserves corrupt state plus the
|
|
136
|
+
legacy task projection before starting a new brownfield adoption.
|
|
127
137
|
The full access policy lives in
|
|
128
138
|
`docs/STATE-DB-ACCESS-POLICY.md` (Phase 2.2 / R25).
|
|
129
139
|
|