ultra-builder-pro-cli 0.10.0 → 0.11.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 (92) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +1 -1
  3. package/adapters/_shared/codex-assets.cjs +23 -341
  4. package/adapters/_shared/runtime-assets.cjs +24 -0
  5. package/adapters/_shared/tests/runtime-assets.test.cjs +11 -4
  6. package/adapters/claude.js +28 -5
  7. package/adapters/kimi.js +15 -138
  8. package/adapters/opencode.js +14 -47
  9. package/adapters/tests/claude.test.cjs +22 -8
  10. package/adapters/tests/codex.test.cjs +19 -6
  11. package/adapters/tests/kimi.test.cjs +11 -11
  12. package/adapters/tests/opencode.test.cjs +11 -7
  13. package/agents/code-reviewer.md +24 -85
  14. package/agents/debugger.md +30 -132
  15. package/agents/review-code.md +24 -104
  16. package/agents/review-comments.md +20 -83
  17. package/agents/review-coordinator.md +40 -137
  18. package/agents/review-design.md +21 -118
  19. package/agents/review-errors.md +22 -70
  20. package/agents/review-spec.md +36 -0
  21. package/agents/review-tests.md +20 -76
  22. package/agents/tdd-runner.md +26 -84
  23. package/commands/learn.md +5 -36
  24. package/commands/ultra-change.md +5 -43
  25. package/commands/ultra-deliver.md +4 -46
  26. package/commands/ultra-dev.md +4 -48
  27. package/commands/ultra-doctor.md +4 -30
  28. package/commands/ultra-init.md +5 -47
  29. package/commands/ultra-plan.md +5 -48
  30. package/commands/ultra-research.md +5 -54
  31. package/commands/ultra-status.md +5 -44
  32. package/commands/ultra-test.md +5 -45
  33. package/commands/ultra-think.md +5 -40
  34. package/docs/PLAN.zh-CN.md +3 -3
  35. package/docs/ROADMAP.md +1 -1
  36. package/docs/SKILL-AUTHORING.md +75 -0
  37. package/package.json +3 -2
  38. package/skills/cc-collab/SKILL.md +16 -18
  39. package/skills/code-review-expert/SKILL.md +39 -114
  40. package/skills/code-review-expert/references/correctness-reliability.md +30 -0
  41. package/skills/code-review-expert/references/design-boundaries.md +26 -0
  42. package/skills/code-review-expert/references/removal-plan.md +17 -45
  43. package/skills/codex-collab/SKILL.md +23 -28
  44. package/skills/codex-collab/evals/evals.json +18 -23
  45. package/skills/integration-rules/SKILL.md +28 -67
  46. package/skills/learn/SKILL.md +46 -106
  47. package/skills/security-rules/SKILL.md +34 -56
  48. package/skills/testing-rules/SKILL.md +28 -52
  49. package/skills/ultra-change/SKILL.md +57 -114
  50. package/skills/ultra-deliver/SKILL.md +49 -87
  51. package/skills/ultra-dev/SKILL.md +42 -105
  52. package/skills/ultra-doctor/SKILL.md +39 -72
  53. package/skills/ultra-init/SKILL.md +59 -140
  54. package/skills/ultra-plan/SKILL.md +48 -88
  55. package/skills/ultra-research/SKILL.md +59 -152
  56. package/skills/ultra-research/references/architecture.md +38 -0
  57. package/skills/ultra-research/references/problem-product.md +39 -0
  58. package/skills/ultra-research/references/quality-delivery.md +34 -0
  59. package/skills/ultra-research/references/synthesis.md +38 -0
  60. package/skills/ultra-research/references/users-scope.md +40 -0
  61. package/skills/ultra-review/SKILL.md +54 -98
  62. package/skills/ultra-review/references/unified-schema.md +76 -101
  63. package/skills/ultra-review/scripts/review_wait.py +242 -77
  64. package/skills/ultra-status/SKILL.md +29 -51
  65. package/skills/ultra-test/SKILL.md +37 -72
  66. package/skills/ultra-think/SKILL.md +43 -102
  67. package/skills/ultra-verify/SKILL.md +44 -84
  68. package/skills/ultra-verify/evals/evals.json +24 -27
  69. package/spec/schemas/command-manifest.schema.json +7 -25
  70. package/spec/schemas/skill-manifest.schema.json +9 -79
  71. package/skills/code-review-expert/references/code-quality-checklist.md +0 -112
  72. package/skills/code-review-expert/references/integration-checklist.md +0 -46
  73. package/skills/code-review-expert/references/security-checklist.md +0 -100
  74. package/skills/code-review-expert/references/solid-checklist.md +0 -65
  75. package/skills/ultra-research/steps/step-00-problem-validation.md +0 -154
  76. package/skills/ultra-research/steps/step-01-opportunity-discovery.md +0 -172
  77. package/skills/ultra-research/steps/step-02-market-assessment.md +0 -195
  78. package/skills/ultra-research/steps/step-03-competitive-landscape.md +0 -197
  79. package/skills/ultra-research/steps/step-04-product-strategy.md +0 -196
  80. package/skills/ultra-research/steps/step-05-assumptions-validation.md +0 -234
  81. package/skills/ultra-research/steps/step-10-user-personas.md +0 -169
  82. package/skills/ultra-research/steps/step-11-user-scenarios.md +0 -181
  83. package/skills/ultra-research/steps/step-20-user-stories.md +0 -156
  84. package/skills/ultra-research/steps/step-21-features-scope.md +0 -157
  85. package/skills/ultra-research/steps/step-22-success-metrics.md +0 -195
  86. package/skills/ultra-research/steps/step-30-architecture-context.md +0 -154
  87. package/skills/ultra-research/steps/step-31-solution-strategy.md +0 -172
  88. package/skills/ultra-research/steps/step-32-building-blocks.md +0 -188
  89. package/skills/ultra-research/steps/step-40-deployment.md +0 -123
  90. package/skills/ultra-research/steps/step-41-quality-risks.md +0 -135
  91. package/skills/ultra-research/steps/step-99-synthesis.md +0 -188
  92. package/skills/ultra-review/scripts/review_verdict_update.py +0 -99
@@ -1,88 +1,254 @@
1
1
  #!/usr/bin/env python3
2
- """Review Wait - File-based completion waiter for /ultra-review pipeline.
3
-
4
- Polls the session directory for expected review JSON files and/or SUMMARY.json.
5
- Returns a one-line verdict on stdout. Blocks until complete or timeout.
2
+ """Wait for and validate Ultra review v2 artifacts.
6
3
 
7
4
  Usage:
8
- python3 review_wait.py <session_path> agents <count> # Wait for N review-*.json files
9
- python3 review_wait.py <session_path> summary # Wait for SUMMARY.json only
5
+ python3 review_wait.py <session_path> agents <artifact-stem> [<artifact-stem> ...]
6
+ python3 review_wait.py <session_path> summary
10
7
 
11
- Exit codes:
12
- 0 - All expected files found
13
- 1 - Timeout (default 5 minutes)
14
- 2 - Invalid arguments
8
+ The agents mode validates the exact named JSON files. The summary mode validates the
9
+ two-axis verdict contract. Timeout and poll intervals can be shortened in tests with
10
+ UBP_REVIEW_WAIT_TIMEOUT and UBP_REVIEW_WAIT_POLL.
15
11
  """
16
12
 
17
- import glob
18
13
  import json
14
+ import os
15
+ import re
19
16
  import sys
20
17
  import time
21
18
  from pathlib import Path
22
19
 
23
- POLL_INTERVAL = 2 # seconds
24
- DEFAULT_TIMEOUT = 300 # 5 minutes
20
+ FINDINGS_SCHEMA = "ultra-review-findings-v2"
21
+ SUMMARY_SCHEMA = "ultra-review-summary-v2"
22
+ AXES = {"spec_fidelity", "engineering_standards"}
23
+ SEVERITIES = {"P0", "P1", "P2", "P3"}
24
+ AXIS_VERDICTS = {"PASS", "FAIL", "INCOMPLETE"}
25
+ OVERALL_VERDICTS = {"APPROVE", "REQUEST_CHANGES", "INCOMPLETE"}
26
+ ARTIFACT_STEM = re.compile(r"^[a-z][a-z0-9-]*$")
27
+ FINDING_FIELDS = {
28
+ "id", "axis", "severity", "category", "title", "file", "line", "trigger",
29
+ "impact", "evidence", "suggestion",
30
+ }
31
+
32
+
33
+ def env_seconds(name: str, default: float) -> float:
34
+ try:
35
+ value = float(os.environ.get(name, default))
36
+ except (TypeError, ValueError):
37
+ return default
38
+ return value if value > 0 else default
39
+
40
+
41
+ DEFAULT_TIMEOUT = env_seconds("UBP_REVIEW_WAIT_TIMEOUT", 300.0)
42
+ POLL_INTERVAL = env_seconds("UBP_REVIEW_WAIT_POLL", 2.0)
43
+
44
+
45
+ def read_json(path: Path):
46
+ try:
47
+ data = json.loads(path.read_text(encoding="utf-8"))
48
+ except (OSError, json.JSONDecodeError) as error:
49
+ return None, f"unreadable JSON: {error}"
50
+ if not isinstance(data, dict):
51
+ return None, "top-level value must be an object"
52
+ return data, None
53
+
54
+
55
+ def nonempty_string(value) -> bool:
56
+ return isinstance(value, str) and bool(value.strip())
57
+
58
+
59
+ def validate_finding(finding, artifact_axis: str, seen_ids):
60
+ if not isinstance(finding, dict):
61
+ return "finding must be an object"
62
+ missing = sorted(FINDING_FIELDS - set(finding))
63
+ if missing:
64
+ return f"finding missing fields: {', '.join(missing)}"
65
+ if not nonempty_string(finding.get("id")):
66
+ return "finding id must be a non-empty string"
67
+ if finding["id"] in seen_ids:
68
+ return f"duplicate finding id: {finding['id']}"
69
+ seen_ids.add(finding["id"])
70
+ if finding.get("axis") != artifact_axis:
71
+ return "finding axis must match artifact axis"
72
+ if finding.get("severity") not in SEVERITIES:
73
+ return "finding severity must be P0, P1, P2, or P3"
74
+ for field in ["category", "title", "file", "trigger", "impact", "evidence", "suggestion"]:
75
+ if not nonempty_string(finding.get(field)):
76
+ return f"finding {field} must be a non-empty string"
77
+ if not isinstance(finding.get("line"), int) or finding["line"] < 1:
78
+ return "finding line must be a positive integer"
79
+ line_end = finding.get("line_end")
80
+ if line_end is not None and (not isinstance(line_end, int) or line_end < finding["line"]):
81
+ return "finding line_end must be at or after line"
82
+ return None
83
+
84
+
85
+ def validate_specialist(data):
86
+ if data.get("$schema") != FINDINGS_SCHEMA:
87
+ return f"$schema must be {FINDINGS_SCHEMA}"
88
+ if not nonempty_string(data.get("agent")):
89
+ return "agent must be a non-empty string"
90
+ axis = data.get("axis")
91
+ if axis not in AXES:
92
+ return "axis must be spec_fidelity or engineering_standards"
93
+ for field in ["session", "timestamp"]:
94
+ if not nonempty_string(data.get(field)):
95
+ return f"{field} must be a non-empty string"
96
+ scope = data.get("scope")
97
+ if not isinstance(scope, dict):
98
+ return "scope must be an object"
99
+ for field in ["head", "range"]:
100
+ if not nonempty_string(scope.get(field)):
101
+ return f"scope.{field} must be a non-empty string"
102
+ if not isinstance(scope.get("files_analyzed"), list) or not all(
103
+ nonempty_string(item) for item in scope["files_analyzed"]
104
+ ):
105
+ return "scope.files_analyzed must be a string array"
106
+ if not isinstance(scope.get("diff_only"), bool):
107
+ return "scope.diff_only must be boolean"
108
+ if data.get("status") != "complete":
109
+ return "status must be complete"
110
+ findings = data.get("findings")
111
+ if not isinstance(findings, list):
112
+ return "findings must be an array"
113
+ seen_ids = set()
114
+ for finding in findings:
115
+ error = validate_finding(finding, axis, seen_ids)
116
+ if error:
117
+ return error
118
+ for field in ["positive_observations", "limitations"]:
119
+ if not isinstance(data.get(field), list):
120
+ return f"{field} must be an array"
121
+ return None
25
122
 
26
123
 
27
- def wait_for_agents(session_path: Path, expected_count: int, timeout: int) -> bool:
28
- """Wait for expected_count review-*.json files to appear.
124
+ def evaluate_artifacts(session_path: Path, expected):
125
+ done = []
126
+ missing = []
127
+ invalid = []
128
+ errors = {}
129
+ for stem in expected:
130
+ path = session_path / f"{stem}.json"
131
+ if not path.exists():
132
+ missing.append(stem)
133
+ continue
134
+ data, error = read_json(path)
135
+ if error is None:
136
+ error = validate_specialist(data)
137
+ if error:
138
+ invalid.append(stem)
139
+ errors[stem] = error
140
+ else:
141
+ done.append(stem)
142
+ return done, missing, invalid, errors
29
143
 
30
- Returns structured JSON on stdout:
31
- - status: "complete" (all agents) or "partial" (>=1 agent on timeout)
32
- - agents_done / agents_missing: lists of agent names
33
- - count: number of completed agents
34
144
 
35
- Exit code: 0 if all complete OR partial (>=1), 1 if 0 agents.
36
- """
37
- all_agents = ["review-code", "review-tests", "review-errors",
38
- "review-design", "review-comments"]
145
+ def wait_for_agents(session_path: Path, expected, timeout: float) -> bool:
39
146
  deadline = time.monotonic() + timeout
40
- while time.monotonic() < deadline:
41
- found = glob.glob(str(session_path / "review-*.json"))
42
- if len(found) >= expected_count:
43
- names = [Path(f).stem for f in sorted(found)]
44
- result = {"status": "complete", "agents_done": names, "agents_missing": [], "count": len(found)}
45
- print(json.dumps(result))
147
+ while True:
148
+ done, missing, invalid, errors = evaluate_artifacts(session_path, expected)
149
+ if len(done) == len(expected):
150
+ print(json.dumps({
151
+ "status": "complete",
152
+ "artifacts_done": done,
153
+ "artifacts_missing": [],
154
+ "artifacts_invalid": [],
155
+ "errors": {},
156
+ "count": len(done),
157
+ }, sort_keys=True))
46
158
  return True
47
- remaining = int(deadline - time.monotonic())
48
- sys.stderr.write(f"\r Waiting: {len(found)}/{expected_count} ({remaining}s remaining)")
49
- sys.stderr.flush()
50
- time.sleep(POLL_INTERVAL)
51
-
52
- # Timeout — report partial results as structured JSON
53
- found = glob.glob(str(session_path / "review-*.json"))
54
- found_names = [Path(f).stem for f in sorted(found)]
55
- missing = [a for a in all_agents if a not in found_names]
56
- result = {"status": "partial", "agents_done": found_names, "agents_missing": missing, "count": len(found)}
57
- print(json.dumps(result))
58
- return len(found) >= 1 # At least 1 agent = partial success
59
-
60
-
61
- def wait_for_summary(session_path: Path, timeout: int) -> bool:
62
- """Wait for SUMMARY.json to appear."""
159
+ if time.monotonic() >= deadline:
160
+ print(json.dumps({
161
+ "status": "incomplete",
162
+ "artifacts_done": done,
163
+ "artifacts_missing": missing,
164
+ "artifacts_invalid": invalid,
165
+ "errors": errors,
166
+ "count": len(done),
167
+ }, sort_keys=True))
168
+ return False
169
+ time.sleep(min(POLL_INTERVAL, max(0.0, deadline - time.monotonic())))
170
+
171
+
172
+ def expected_overall_verdict(data) -> str:
173
+ verdicts = {data["axes"][axis]["verdict"] for axis in AXES}
174
+ severities = {finding["severity"] for finding in data["findings"]}
175
+ if "INCOMPLETE" in verdicts:
176
+ return "INCOMPLETE"
177
+ if "FAIL" in verdicts or severities.intersection({"P0", "P1"}):
178
+ return "REQUEST_CHANGES"
179
+ return "APPROVE"
180
+
181
+
182
+ def validate_summary(data):
183
+ if data.get("$schema") != SUMMARY_SCHEMA:
184
+ return f"$schema must be {SUMMARY_SCHEMA}"
185
+ for field in ["session", "head"]:
186
+ if not nonempty_string(data.get(field)):
187
+ return f"{field} must be a non-empty string"
188
+ if data.get("status") != "complete":
189
+ return "status must be complete"
190
+ if data.get("verdict") not in OVERALL_VERDICTS:
191
+ return "invalid overall verdict"
192
+ axes = data.get("axes")
193
+ if not isinstance(axes, dict) or set(axes) != AXES:
194
+ return "axes must contain exactly spec_fidelity and engineering_standards"
195
+ for axis in AXES:
196
+ item = axes[axis]
197
+ if not isinstance(item, dict) or item.get("verdict") not in AXIS_VERDICTS:
198
+ return f"invalid {axis} verdict"
199
+ refs = item.get("evidence_refs")
200
+ if not isinstance(refs, list) or not refs or not all(nonempty_string(ref) for ref in refs):
201
+ return f"{axis}.evidence_refs must be a non-empty string array"
202
+ workers = data.get("workers")
203
+ if not isinstance(workers, dict):
204
+ return "workers must be an object"
205
+ for field in ["completed", "failed", "skipped"]:
206
+ if not isinstance(workers.get(field), list):
207
+ return f"workers.{field} must be an array"
208
+ findings = data.get("findings")
209
+ if not isinstance(findings, list):
210
+ return "findings must be an array"
211
+ seen_ids = set()
212
+ for finding in findings:
213
+ axis = finding.get("axis") if isinstance(finding, dict) else None
214
+ if axis not in AXES:
215
+ return "summary finding has invalid axis"
216
+ error = validate_finding(finding, axis, seen_ids)
217
+ if error:
218
+ return error
219
+ for field in ["positive_observations", "limitations"]:
220
+ if not isinstance(data.get(field), list):
221
+ return f"{field} must be an array"
222
+ expected = expected_overall_verdict(data)
223
+ if data["verdict"] != expected:
224
+ return f"verdict {data['verdict']} conflicts with evidence; expected {expected}"
225
+ return None
226
+
227
+
228
+ def wait_for_summary(session_path: Path, timeout: float) -> bool:
63
229
  summary_path = session_path / "SUMMARY.json"
64
230
  deadline = time.monotonic() + timeout
65
- while time.monotonic() < deadline:
66
- if summary_path.exists() and summary_path.stat().st_size > 0:
67
- try:
68
- data = json.loads(summary_path.read_text(encoding="utf-8"))
69
- verdict = data.get("verdict", "UNKNOWN")
70
- p0 = data.get("p0", 0)
71
- p1 = data.get("p1", 0)
72
- total = data.get("total", 0)
73
- print(f"Review complete: {verdict} (P0:{p0} P1:{p1} total:{total})")
231
+ last_error = "SUMMARY.json is missing"
232
+ while True:
233
+ if summary_path.exists():
234
+ data, error = read_json(summary_path)
235
+ if error is None:
236
+ error = validate_summary(data)
237
+ if error is None:
238
+ counts = {severity: 0 for severity in sorted(SEVERITIES)}
239
+ for finding in data["findings"]:
240
+ counts[finding["severity"]] += 1
241
+ print(
242
+ f"Review complete: {data['verdict']} "
243
+ f"(P0:{counts['P0']} P1:{counts['P1']} P2:{counts['P2']} "
244
+ f"P3:{counts['P3']} total:{len(data['findings'])})"
245
+ )
74
246
  return True
75
- except (json.JSONDecodeError, KeyError):
76
- pass # File still being written
77
- remaining = int(deadline - time.monotonic())
78
- print(
79
- f"\r Waiting for coordinator ({remaining}s remaining)",
80
- end="", flush=True
81
- )
82
- time.sleep(POLL_INTERVAL)
83
-
84
- print("\nTimeout: coordinator did not produce SUMMARY.json")
85
- return False
247
+ last_error = error
248
+ if time.monotonic() >= deadline:
249
+ print(json.dumps({"status": "incomplete", "error": last_error}, sort_keys=True))
250
+ return False
251
+ time.sleep(min(POLL_INTERVAL, max(0.0, deadline - time.monotonic())))
86
252
 
87
253
 
88
254
  def main():
@@ -92,28 +258,27 @@ def main():
92
258
 
93
259
  session_path = Path(sys.argv[1])
94
260
  mode = sys.argv[2]
95
-
96
261
  if not session_path.is_dir():
97
262
  print(f"Error: session directory not found: {session_path}", file=sys.stderr)
98
263
  sys.exit(2)
99
264
 
100
- timeout = DEFAULT_TIMEOUT
101
-
102
265
  if mode == "agents":
103
- if len(sys.argv) < 4:
104
- print("Error: agents mode requires <count> argument", file=sys.stderr)
266
+ expected = sys.argv[3:]
267
+ if not expected or len(expected) != len(set(expected)) or not all(
268
+ ARTIFACT_STEM.fullmatch(stem) for stem in expected
269
+ ):
270
+ print("Error: agents mode requires unique safe artifact stems", file=sys.stderr)
105
271
  sys.exit(2)
106
- expected = int(sys.argv[3])
107
- ok = wait_for_agents(session_path, expected, timeout)
108
- sys.exit(0 if ok else 1)
109
-
272
+ ok = wait_for_agents(session_path, expected, DEFAULT_TIMEOUT)
110
273
  elif mode == "summary":
111
- ok = wait_for_summary(session_path, timeout)
112
- sys.exit(0 if ok else 1)
113
-
274
+ if len(sys.argv) != 3:
275
+ print("Error: summary mode accepts no additional arguments", file=sys.stderr)
276
+ sys.exit(2)
277
+ ok = wait_for_summary(session_path, DEFAULT_TIMEOUT)
114
278
  else:
115
279
  print(f"Error: unknown mode '{mode}'. Use 'agents' or 'summary'.", file=sys.stderr)
116
280
  sys.exit(2)
281
+ sys.exit(0 if ok else 1)
117
282
 
118
283
 
119
284
  if __name__ == "__main__":
@@ -1,61 +1,46 @@
1
1
  ---
2
2
  name: ultra-status
3
- description: "Route the project from its authoritative Ultra breadcrumb, readiness gates, task state, and current verification evidence."
4
- user-invocable: true
5
- runtime: all
6
- mcp_tools_required:
7
- - change.breadcrumb
8
- - change.list
9
- - task.list
10
- - task.get
11
- - session.list
12
- - system.doctor
13
- cli_fallback: "status --cost --json"
3
+ description: Report authoritative Ultra health, position, readiness, evidence freshness, and one deterministic next action. Use when the user asks where an Ultra project stands or what should happen next.
14
4
  ---
15
5
 
16
- # ultra-status — One Router, One Next Action
6
+ # Route from authoritative status
17
7
 
18
- Use this workflow to answer “where are we?” and “what should happen next?” without
19
- reconstructing project state from prose. `.ultra/state.db` is authoritative; reports
20
- and Markdown are evidence or projections.
8
+ Answer status questions without reconstructing state from prose. `.ultra/state.db` is
9
+ authoritative; reports and Markdown are evidence or projections.
21
10
 
22
11
  ## Read order
23
12
 
24
- 1. Call `system.doctor { repair: false }`. If authority is unhealthy, route only to
13
+ 1. Call `system.doctor` in read-only mode. If authority is unhealthy, route only to
25
14
  `ultra-doctor` and include the blocking diagnostic.
26
- 2. Call `change.breadcrumb`. It returns the active change, task, role, gate,
27
- readiness, blockers, stale status, and one deterministic next action.
28
- 3. Call `task.list` and `session.list` for counts and active execution detail.
29
- 4. Read `.ultra/test-report.json` and `.ultra/delivery-report.json` only when they
30
- exist. Treat a report from a different HEAD as stale.
31
- 5. Read git branch, HEAD, and worktree status. Do not mutate anything.
15
+ 2. Call `change.breadcrumb` for the active change, task, role, gate, readiness,
16
+ blockers, staleness, and next action.
17
+ 3. Call `task.list` and `session.list` for progress and active execution details.
18
+ 4. Read test and delivery reports only when present, and mark a report stale when its
19
+ HEAD differs from the current checkout.
20
+ 5. Read Git branch, HEAD, and worktree status without mutation.
32
21
 
33
- Never fall back to `.ultra/tasks/tasks.json`, context Markdown frontmatter, or raw
34
- SQLite when MCP task state is unavailable. Label that panel unavailable.
22
+ If MCP task or change state is unavailable, label that panel unavailable. Never fall
23
+ back to generated task JSON, context frontmatter, or raw SQLite.
35
24
 
36
- ## Routing rules
25
+ ## Routing
37
26
 
38
- The breadcrumb owns the primary route. Do not print a menu of every Ultra command.
27
+ Follow the breadcrumb's primary route and return one action, not a menu:
39
28
 
40
- | State | Route |
41
- |---|---|
42
- | doctor unhealthy | `ultra-doctor` |
43
- | no Ultra project | `ultra-init` |
44
- | no baseline specs or tasks | `ultra-research` or `ultra-plan`, matching the missing artifact |
45
- | readiness blocked or context stale | recompile through `ultra-change`, `ultra-plan`, or `ultra-dev` as named by the breadcrumb |
46
- | implementation task ready | `ultra-dev <task-id>` |
47
- | all scoped tasks complete, test report missing/stale | `ultra-test` |
48
- | review or convergence incomplete | `ultra-review all` or `ultra-deliver`, matching the gate |
49
- | ready change | `ultra-deliver` |
50
- | archived change and clean HEAD | report complete; do not invent more work |
29
+ - unhealthy authority: `ultra-doctor`;
30
+ - missing Ultra project: `ultra-init`;
31
+ - missing baseline evidence: `ultra-research` or `ultra-plan`, matching the gap;
32
+ - blocked or stale context: the workflow named by the breadcrumb;
33
+ - ready implementation task: `ultra-dev <task-id>`;
34
+ - completed tasks with missing or stale test evidence: `ultra-test`;
35
+ - incomplete review or convergence: `ultra-review` or `ultra-deliver`;
36
+ - ready change: `ultra-deliver`;
37
+ - archived change with fresh evidence: report completion without inventing more work.
51
38
 
52
- When there is no active change, derive a single baseline route from tasks and reports.
53
- If several changes are active or blocked, report ambiguity and require an explicit
54
- change id instead of guessing.
39
+ Require an explicit change id when several active changes make the route ambiguous.
55
40
 
56
41
  ## Output
57
42
 
58
- Keep the answer compact:
43
+ Use a compact shape:
59
44
 
60
45
  ```text
61
46
  Ultra: <healthy|degraded> · <branch>@<head> · worktree <clean|dirty>
@@ -63,15 +48,8 @@ Position: change=<id|none> task=<id|none> role=<role> gate=<gate>
63
48
  Readiness: <ready|blocked|stale> — <blockers or none>
64
49
  Progress: <completed>/<total>; sessions=<active count>
65
50
  Evidence: tests=<fresh|stale|missing>; delivery=<fresh|stale|missing>
66
- Next: <exactly one action, including the task/change id when known>
51
+ Next: <one exact action>
67
52
  ```
68
53
 
69
- Separate verified facts from unavailable panels. Do not hide a stale context or stale
70
- test report behind an overall “healthy” label.
71
-
72
- ## Boundaries
73
-
74
- - Read-only: this workflow does not repair, compile context, update tasks, or release.
75
- - Cost data may use the declared CLI fallback, but task/change authority may not.
76
- - External memory and code graph providers are optional metadata references; their
77
- absence does not change Ultra authority.
54
+ This workflow is read-only. It does not repair state, compile context, update tasks, or
55
+ release software.
@@ -1,94 +1,59 @@
1
1
  ---
2
2
  name: ultra-test
3
- description: "Run the pre-delivery check gate from a fresh role context and record exact feedback-loop, wiring, recovery, and acceptance evidence."
4
- user-invocable: true
5
- runtime: all
6
- mcp_tools_required:
7
- - task.list
8
- - change.list
9
- - change.context
10
- - change.breadcrumb
3
+ description: Run an independent pre-delivery verification gate and record exact feedback-loop, reachability, recovery, and acceptance evidence. Use when scoped implementation tasks are complete and delivery readiness must be established.
11
4
  ---
12
5
 
13
- # ultra-test — Independent Check Context
6
+ # Verify delivery readiness
14
7
 
15
- This is the project/change pre-delivery gate, not a replacement for the task-level TDD
16
- loop. It produces `.ultra/test-report.json`; task and change authority remain in
17
- `.ultra/state.db`.
8
+ Run this gate from a fresh checking context. It complements task-level development
9
+ tests and writes `.ultra/test-report.json`; it does not replace authoritative task or
10
+ change state.
18
11
 
19
12
  ## Entry gate
20
13
 
21
- 1. Call `task.list`; require at least one completed task in scope.
22
- 2. Call `change.list` and bind to exactly one relevant active/blocked/ready change, or
23
- explicitly record `change_id: null` for an initial baseline.
24
- 3. For a change, compile `change.context` with `role=check`, `gate=verification`, only
25
- the tests/specs/source seams needed to verify it, and the same execution contract.
14
+ 1. Call `task.list` and require at least one completed task in scope.
15
+ 2. Call `change.list` and bind exactly one relevant change, or record a null change id
16
+ for an initial baseline.
17
+ 3. For a change, compile `change.context` for the checking role and verification gate
18
+ with only the specifications, tests, and source seams needed for acceptance.
26
19
  4. Call `change.breadcrumb`; stop when context is stale or readiness is blocked.
27
20
 
28
- Never fall back to `.ultra/tasks/tasks.json`. Do not reuse the implementer's full
21
+ Never use generated task JSON as authority or reuse the implementer's full
29
22
  conversation as test context.
30
23
 
31
- ## Verification matrix
24
+ ## Evidence matrix
32
25
 
33
- Build a matrix from acceptance claims to observable evidence:
26
+ Map each acceptance claim to observable evidence:
34
27
 
35
- | Check | Required evidence | Blocking condition |
28
+ | Check | Evidence | Blocking result |
36
29
  |---|---|---|
37
- | Feedback loop | exact command and observed green | command fails or no prior red for fix/incident |
38
- | Public seam | reachable entry-to-consumer path | orphan/stub/unwired behavior |
39
- | Regression | focused and adjacent suites | failure or meaningful skipped assertion |
40
- | Build/static | typecheck/lint/build as applicable | non-zero result |
41
- | Error/recovery | failure mode and recovery path | silent/swallowed/unrecoverable critical path |
42
- | Security | input/auth/secrets/dependency checks in scope | high-severity issue |
43
- | Docs/spec | delivered behavior matches declared delta | drift or unknown impact |
44
-
45
- Use repository-native commands and real boundaries where practical. Test doubles are
46
- acceptable only at costly or nondeterministic external boundaries and must be named.
47
-
48
- ## Quality checks
49
-
50
- - Detect tautologies, empty tests, weakened assertions, and core-domain over-mocking.
51
- - Find changed exports without meaningful test coverage.
52
- - Trace changed source into non-test consumers; flag orphan modules and placeholder
53
- handlers/components.
54
- - Exercise key API/UI/CLI flows when the project exposes them.
55
- - Inspect error handling, authorization, migrations, idempotency, and rollback in the
56
- affected risk surface.
57
- - Compare current HEAD and context digests with the implementation evidence.
58
-
59
- Do not mechanically require every possible test category. Mark a check
60
- `not_applicable` only with a concrete scope reason.
30
+ | Feedback loop | exact command and observed result | failure or missing baseline signal for a fix |
31
+ | Public seam | reachable entry-to-consumer path | orphan, stub, or unwired behavior |
32
+ | Regression | focused and adjacent suites | failure or weakened assertion |
33
+ | Static/build | applicable type, lint, and build commands | non-zero result |
34
+ | Error/recovery | exercised failure and recovery path | silent or unrecoverable critical path |
35
+ | Security | relevant input, authorization, secret, and dependency checks | high-impact exploitable issue |
36
+ | Docs/spec | delivered behavior matches the declared baseline or delta | drift or unknown impact |
37
+
38
+ Use repository-native commands and real boundaries when practical. Use test doubles
39
+ only at costly or nondeterministic external boundaries, and state why they preserve
40
+ the contract being tested.
41
+
42
+ Inspect tests for tautologies, empty assertions, hidden skips, excessive boundary
43
+ mocking, changed exports without behavioral coverage, and code with no non-test
44
+ consumer. Apply checks by risk and scope rather than forcing every category.
61
45
 
62
46
  ## Report contract
63
47
 
64
- Write `.ultra/test-report.json` atomically with at least:
65
-
66
- ```json
67
- {
68
- "schema_version": "2.0",
69
- "change_id": "<id-or-null>",
70
- "git_commit": "<full-head>",
71
- "context_manifest_hash": "<hash-or-null>",
72
- "passed": true,
73
- "feedback_loop": {
74
- "command": "<exact command>",
75
- "expected_red": "<failure contract or not-applicable reason>",
76
- "observed_red": true,
77
- "observed_green": true,
78
- "deterministic": true
79
- },
80
- "public_seams": ["<verified seam>"],
81
- "checks": [],
82
- "blocking_issues": [],
83
- "commands": []
84
- }
85
- ```
48
+ Write the report atomically with the current full HEAD, change id, context manifest
49
+ hash, exact commands, baseline and final signals, verified public seams, checks, and
50
+ blocking issues. Preserve concise failure excerpts instead of full logs.
86
51
 
87
- Set `passed=true` only when every required check passes, the report HEAD is current,
88
- and at least one public seam is verified. Preserve exact commands, exit results, and
89
- concise failure excerpts; do not paste full logs.
52
+ Set `passed: true` only when every required check passes, the report HEAD is current,
53
+ and at least one declared public seam is verified. A not-applicable check needs a
54
+ specific scope reason.
90
55
 
91
56
  ## Exit
92
57
 
93
- Call `change.breadcrumb` and route one next action: fix a named blocker, run
94
- `/ultra-review all`, or run `/ultra-deliver`. A stale report never routes to delivery.
58
+ Call `change.breadcrumb` and return one next action: fix a named blocker, run
59
+ `ultra-review`, or run `ultra-deliver`. A stale report never routes to delivery.