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.
Files changed (88) hide show
  1. package/.ultra-template/docs/research/README.md +21 -44
  2. package/.ultra-template/specs/discovery.md +10 -0
  3. package/CHANGELOG.md +95 -1
  4. package/README.md +31 -16
  5. package/adapters/_shared/codex-assets.cjs +23 -163
  6. package/adapters/_shared/handbook.cjs +2 -2
  7. package/adapters/_shared/runtime-assets.cjs +1 -0
  8. package/adapters/_shared/tests/runtime-assets.test.cjs +18 -0
  9. package/adapters/opencode.js +30 -120
  10. package/adapters/tests/claude.test.cjs +3 -2
  11. package/adapters/tests/codex-hook.test.cjs +34 -7
  12. package/adapters/tests/codex.test.cjs +21 -6
  13. package/adapters/tests/kimi-hook.test.cjs +34 -7
  14. package/adapters/tests/opencode.test.cjs +70 -84
  15. package/commands/ultra-init.md +1 -1
  16. package/docs/AGENT-CONTEXT.md +10 -6
  17. package/docs/ARCHITECTURE.md +40 -17
  18. package/docs/PLAN.zh-CN.md +5 -4
  19. package/docs/ROADMAP.md +2 -2
  20. package/docs/RUNTIME-COMPAT-MATRIX.md +4 -4
  21. package/docs/STATE-DB-ACCESS-POLICY.md +3 -3
  22. package/docs/USER-HANDBOOK-CONTRACT.md +5 -4
  23. package/hooks/active_task_context.py +22 -50
  24. package/hooks/context_spine.py +61 -299
  25. package/hooks/health_check.py +19 -17
  26. package/hooks/pre_stop_check.py +28 -33
  27. package/hooks/subagent_tracker.py +13 -19
  28. package/hooks/workflow_checkpoint.py +39 -47
  29. package/hooks/workflow_context.py +4 -9
  30. package/hooks/workflow_resume.py +48 -128
  31. package/mcp-server/breadcrumb.cjs +22 -0
  32. package/mcp-server/lib/archive-journal.cjs +192 -0
  33. package/mcp-server/lib/archive-journal.test.cjs +35 -0
  34. package/mcp-server/lib/baseline-workflow.cjs +404 -38
  35. package/mcp-server/lib/baseline-workflow.test.cjs +252 -0
  36. package/mcp-server/lib/change-workflow.cjs +249 -75
  37. package/mcp-server/lib/change-workflow.test.cjs +235 -1
  38. package/mcp-server/lib/context-spine.test.cjs +5 -0
  39. package/mcp-server/lib/doctor.cjs +14 -1
  40. package/mcp-server/lib/doctor.test.cjs +42 -4
  41. package/mcp-server/lib/init-project.cjs +292 -9
  42. package/mcp-server/lib/prd-parser.cjs +14 -41
  43. package/mcp-server/lib/prd-parser.test.cjs +49 -138
  44. package/mcp-server/lib/project-breadcrumb.cjs +139 -0
  45. package/mcp-server/lib/project-breadcrumb.test.cjs +112 -0
  46. package/mcp-server/lib/spec-learning.test.cjs +1 -1
  47. package/mcp-server/lib/state-authority.cjs +9 -3
  48. package/mcp-server/lib/state-db.cjs +154 -32
  49. package/mcp-server/lib/state-db.test.cjs +136 -4
  50. package/mcp-server/lib/state-ops.cjs +68 -6
  51. package/mcp-server/lib/task-expander.cjs +14 -37
  52. package/mcp-server/lib/task-expander.test.cjs +80 -164
  53. package/mcp-server/server.cjs +34 -37
  54. package/mcp-server/tests/init-project.test.cjs +162 -1
  55. package/mcp-server/tests/migrate.test.cjs +33 -0
  56. package/mcp-server/tests/server-contract.test.cjs +391 -17
  57. package/mcp-server/tests/state-db.test.cjs +14 -2
  58. package/mcp-server/tests/state-ops.test.cjs +108 -0
  59. package/package.json +2 -4
  60. package/skills/cc-collab/SKILL.md +2 -2
  61. package/skills/codex-collab/SKILL.md +2 -3
  62. package/skills/ultra-change/SKILL.md +18 -10
  63. package/skills/ultra-deliver/SKILL.md +13 -9
  64. package/skills/ultra-dev/SKILL.md +6 -4
  65. package/skills/ultra-doctor/SKILL.md +22 -7
  66. package/skills/ultra-init/SKILL.md +104 -59
  67. package/skills/ultra-init/references/brownfield-adoption.md +65 -37
  68. package/skills/ultra-plan/SKILL.md +20 -7
  69. package/skills/ultra-research/SKILL.md +12 -5
  70. package/skills/ultra-status/SKILL.md +6 -4
  71. package/skills/ultra-verify/SKILL.md +21 -30
  72. package/skills/ultra-verify/scripts/verify_wait.py +54 -54
  73. package/spec/cli-protocol.md +11 -1
  74. package/spec/mcp-tools.yaml +133 -19
  75. package/spec/schemas/state-db.sql +11 -0
  76. package/ultra-tools/cli.cjs +9 -5
  77. package/ultra-tools/commands/migrate.cjs +62 -36
  78. package/ultra-tools/commands/status.cjs +18 -9
  79. package/ultra-tools/commands/system.cjs +340 -6
  80. package/ultra-tools/commands/task.cjs +7 -1
  81. package/ultra-tools/status-command.test.cjs +29 -0
  82. package/ultra-tools/system-command.test.cjs +252 -1
  83. package/mcp-server/lib/llm-client.cjs +0 -157
  84. package/mcp-server/lib/llm-client.test.cjs +0 -156
  85. package/orchestrator/planner/expand-prompt.cjs +0 -62
  86. package/orchestrator/planner/expand-prompt.test.cjs +0 -52
  87. package/orchestrator/planner/prd-prompt.cjs +0 -44
  88. package/orchestrator/planner/prd-prompt.test.cjs +0 -30
@@ -1,65 +1,93 @@
1
1
  # Brownfield adoption
2
2
 
3
- Use this procedure only after `task.init_project` selects `brownfield`, or when an
4
- existing baseline remains `adopting` or `blocked`.
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
- ## Establish the snapshot boundary
6
+ ## Bind the repository boundary
7
7
 
8
- Bind the repository root, current Git HEAD when available, included paths, excluded
9
- generated or vendored paths, project manifests, and the user-visible or public seams.
10
- Use the revision returned by `baseline.record`; non-Git projects receive a deterministic
11
- workspace revision from the recorded evidence.
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
- ## Read current behavior
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
- Inspect only the evidence needed to explain the maintained system:
18
+ ## Build the evidence model
16
19
 
17
- - entry points and consumers;
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
- Classify every statement as observed, verified, decided, or unknown. Source and runtime
25
- evidence outrank stale prose. External memory and code-graph providers may supply
26
- references, but their content remains outside Ultra state.
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
- ## Capture the baseline
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
- Update `.ultra/specs/product.md` with confirmed behavior, actors, entry points, failures,
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
- Do not redesign the project, normalize it to a preferred architecture, manufacture
36
- personas or alternatives, or convert every source file into documentation.
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. Mark an unknown blocking when it can change accepted
41
- behavior, authority, security, data integrity, or the next implementation plan.
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
- The server computes specification digests. Do not send prompt, transcript, memory, or
53
- code-graph payloads.
80
+ Use the server-returned revision, branch, worktree digest, and specification digests.
54
81
 
55
- ## Converge
82
+ ## Obtain approval and converge
56
83
 
57
- Present the observed behavior, known-red verification, blocking unknowns, and material
58
- decisions to the project owner. After explicit approval, call `baseline.converge` with
59
- the recorded revision, approver, approval note, and `accept_known_red` only when the
60
- owner accepted those failures.
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` and `system.doctor`
64
- report the baseline ready and current. Route the first new outcome to `ultra-change`, or
65
- route planning of already accepted work to `ultra-plan`.
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. Convergence still requires approved
20
- adoption, and archive atomically reconciles and verifies revision/spec drift.
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. Call `task.parse_prd` with `dry_run: true`.
67
- 2. Show the proposed slices, topology, conflict surface, and cost estimate.
68
- 3. Require explicit approval; rejection performs no state write.
69
- 4. On approval, repeat with `dry_run: false`, export the plan, and verify that the
70
- dry-run and persisted task identities agree.
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. Call `baseline.record` with the complete current specification list, bounded evidence
55
- references, actual verification results, provider metadata, and explicit unknowns.
56
- Use the returned repository revision for convergence.
57
- 8. Present the baseline and any known-red verification to the owner. After explicit
58
- approval, call `baseline.converge`. Resolve deterministic blockers before routing.
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, specification
16
- digest health, known warnings, and adoption blockers.
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
- - missing baseline evidence with no active change: `ultra-init`, then
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 a read-only external advisor, then verify and synthesize both evidence sets. Use only when the user explicitly requests cross-model verification.
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
- Claude Code writes the first analysis, verifies consequential claims, and owns the
9
- final answer. Codex is an untrusted read-only advisor.
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. Confirm the advisor CLI and authentication. Define the exact workspace, question,
21
- evidence standard, and response shape. Exclude secrets and unrelated files.
22
- 2. Create `.ultra/collab/<session-id>/` and write the primary host's evidence-backed
23
- analysis to `claude-analysis.md` before reading advisor output.
24
- 3. Launch Codex with the raw bounded question and evidence in a read-only sandbox. Do
25
- not include the primary conclusion when independence matters:
26
-
27
- ```bash
28
- codex exec -s read-only \
29
- -a never \
30
- --ephemeral \
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 both analyses against the current checkout, tests, runtime, or primary
42
- documentation. Explain scope, version, evidence, and assumption differences before
43
- judging disagreement.
44
- 7. Write session metadata and a synthesis artifact, then return one host-owned
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 does
52
- not block the user's underlying task.
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
- """File-based completion waiter for the Ultra Verify advisor.
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
- ADVISOR = "codex"
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 _check_output(session_path: Path) -> dict:
27
- output = session_path / OUTPUT_FILE
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": ADVISOR, "status": "complete", "file": str(output)}
30
- return {"name": ADVISOR, "status": "pending", "file": None}
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 / OUTPUT_FILE
35
- error = session_path / f"{ADVISOR}-error.log"
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": ADVISOR, "status": "complete", "file": str(output)}
58
+ return {"name": advisor, "status": "complete", "file": str(output)}
39
59
  if _file_size(error) > 0:
40
- return {"name": ADVISOR, "status": "failed", "file": str(error)}
60
+ return {"name": advisor, "status": "failed", "file": str(error)}
41
61
  if output_size == 0:
42
- return {"name": ADVISOR, "status": "empty", "file": str(output)}
43
- return {"name": ADVISOR, "status": "pending", "file": None}
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
- if len(sys.argv) < 2:
48
- print(__doc__, file=sys.stderr)
49
- sys.exit(2)
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(session_path / OUTPUT_FILE)
75
- advisor = _check_output(session_path)
76
- stable = advisor["status"] == "complete" and current_size == previous_size
77
- if stable:
78
- elapsed = int(timeout - (deadline - time.monotonic()))
79
- print(json.dumps({"status": "complete", ADVISOR: advisor, "elapsed_seconds": elapsed}))
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] {ADVISOR}:{state} ({remaining}s remaining) ")
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({"status": "timeout", ADVISOR: advisor, "elapsed_seconds": timeout}))
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__":
@@ -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