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.
- package/CHANGELOG.md +45 -1
- package/README.md +1 -1
- package/adapters/_shared/codex-assets.cjs +23 -341
- package/adapters/_shared/runtime-assets.cjs +24 -0
- package/adapters/_shared/tests/runtime-assets.test.cjs +11 -4
- package/adapters/claude.js +28 -5
- package/adapters/kimi.js +15 -138
- package/adapters/opencode.js +14 -47
- package/adapters/tests/claude.test.cjs +22 -8
- package/adapters/tests/codex.test.cjs +19 -6
- package/adapters/tests/kimi.test.cjs +11 -11
- package/adapters/tests/opencode.test.cjs +11 -7
- package/agents/code-reviewer.md +24 -85
- package/agents/debugger.md +30 -132
- package/agents/review-code.md +24 -104
- package/agents/review-comments.md +20 -83
- package/agents/review-coordinator.md +40 -137
- package/agents/review-design.md +21 -118
- package/agents/review-errors.md +22 -70
- package/agents/review-spec.md +36 -0
- package/agents/review-tests.md +20 -76
- package/agents/tdd-runner.md +26 -84
- package/commands/learn.md +5 -36
- package/commands/ultra-change.md +5 -43
- package/commands/ultra-deliver.md +4 -46
- package/commands/ultra-dev.md +4 -48
- package/commands/ultra-doctor.md +4 -30
- package/commands/ultra-init.md +5 -47
- package/commands/ultra-plan.md +5 -48
- package/commands/ultra-research.md +5 -54
- package/commands/ultra-status.md +5 -44
- package/commands/ultra-test.md +5 -45
- package/commands/ultra-think.md +5 -40
- package/docs/PLAN.zh-CN.md +3 -3
- package/docs/ROADMAP.md +1 -1
- package/docs/SKILL-AUTHORING.md +75 -0
- package/package.json +3 -2
- package/skills/cc-collab/SKILL.md +16 -18
- package/skills/code-review-expert/SKILL.md +39 -114
- package/skills/code-review-expert/references/correctness-reliability.md +30 -0
- package/skills/code-review-expert/references/design-boundaries.md +26 -0
- package/skills/code-review-expert/references/removal-plan.md +17 -45
- package/skills/codex-collab/SKILL.md +23 -28
- package/skills/codex-collab/evals/evals.json +18 -23
- package/skills/integration-rules/SKILL.md +28 -67
- package/skills/learn/SKILL.md +46 -106
- package/skills/security-rules/SKILL.md +34 -56
- package/skills/testing-rules/SKILL.md +28 -52
- package/skills/ultra-change/SKILL.md +57 -114
- package/skills/ultra-deliver/SKILL.md +49 -87
- package/skills/ultra-dev/SKILL.md +42 -105
- package/skills/ultra-doctor/SKILL.md +39 -72
- package/skills/ultra-init/SKILL.md +59 -140
- package/skills/ultra-plan/SKILL.md +48 -88
- package/skills/ultra-research/SKILL.md +59 -152
- package/skills/ultra-research/references/architecture.md +38 -0
- package/skills/ultra-research/references/problem-product.md +39 -0
- package/skills/ultra-research/references/quality-delivery.md +34 -0
- package/skills/ultra-research/references/synthesis.md +38 -0
- package/skills/ultra-research/references/users-scope.md +40 -0
- package/skills/ultra-review/SKILL.md +54 -98
- package/skills/ultra-review/references/unified-schema.md +76 -101
- package/skills/ultra-review/scripts/review_wait.py +242 -77
- package/skills/ultra-status/SKILL.md +29 -51
- package/skills/ultra-test/SKILL.md +37 -72
- package/skills/ultra-think/SKILL.md +43 -102
- package/skills/ultra-verify/SKILL.md +44 -84
- package/skills/ultra-verify/evals/evals.json +24 -27
- package/spec/schemas/command-manifest.schema.json +7 -25
- package/spec/schemas/skill-manifest.schema.json +9 -79
- package/skills/code-review-expert/references/code-quality-checklist.md +0 -112
- package/skills/code-review-expert/references/integration-checklist.md +0 -46
- package/skills/code-review-expert/references/security-checklist.md +0 -100
- package/skills/code-review-expert/references/solid-checklist.md +0 -65
- package/skills/ultra-research/steps/step-00-problem-validation.md +0 -154
- package/skills/ultra-research/steps/step-01-opportunity-discovery.md +0 -172
- package/skills/ultra-research/steps/step-02-market-assessment.md +0 -195
- package/skills/ultra-research/steps/step-03-competitive-landscape.md +0 -197
- package/skills/ultra-research/steps/step-04-product-strategy.md +0 -196
- package/skills/ultra-research/steps/step-05-assumptions-validation.md +0 -234
- package/skills/ultra-research/steps/step-10-user-personas.md +0 -169
- package/skills/ultra-research/steps/step-11-user-scenarios.md +0 -181
- package/skills/ultra-research/steps/step-20-user-stories.md +0 -156
- package/skills/ultra-research/steps/step-21-features-scope.md +0 -157
- package/skills/ultra-research/steps/step-22-success-metrics.md +0 -195
- package/skills/ultra-research/steps/step-30-architecture-context.md +0 -154
- package/skills/ultra-research/steps/step-31-solution-strategy.md +0 -172
- package/skills/ultra-research/steps/step-32-building-blocks.md +0 -188
- package/skills/ultra-research/steps/step-40-deployment.md +0 -123
- package/skills/ultra-research/steps/step-41-quality-risks.md +0 -135
- package/skills/ultra-research/steps/step-99-synthesis.md +0 -188
- package/skills/ultra-review/scripts/review_verdict_update.py +0 -99
|
@@ -1,88 +1,254 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
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 <
|
|
9
|
-
python3 review_wait.py <session_path> summary
|
|
5
|
+
python3 review_wait.py <session_path> agents <artifact-stem> [<artifact-stem> ...]
|
|
6
|
+
python3 review_wait.py <session_path> summary
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
|
|
42
|
-
if len(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
""
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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:
|
|
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
|
-
#
|
|
6
|
+
# Route from authoritative status
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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
|
|
27
|
-
|
|
28
|
-
3. Call `task.list` and `session.list` for
|
|
29
|
-
4. Read
|
|
30
|
-
|
|
31
|
-
5. Read
|
|
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
|
-
|
|
34
|
-
|
|
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
|
|
25
|
+
## Routing
|
|
37
26
|
|
|
38
|
-
|
|
27
|
+
Follow the breadcrumb's primary route and return one action, not a menu:
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: <
|
|
51
|
+
Next: <one exact action>
|
|
67
52
|
```
|
|
68
53
|
|
|
69
|
-
|
|
70
|
-
|
|
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:
|
|
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
|
-
#
|
|
6
|
+
# Verify delivery readiness
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
22
|
-
2. Call `change.list` and bind
|
|
23
|
-
|
|
24
|
-
3. For a change, compile `change.context`
|
|
25
|
-
|
|
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
|
|
21
|
+
Never use generated task JSON as authority or reuse the implementer's full
|
|
29
22
|
conversation as test context.
|
|
30
23
|
|
|
31
|
-
##
|
|
24
|
+
## Evidence matrix
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
Map each acceptance claim to observable evidence:
|
|
34
27
|
|
|
35
|
-
| Check |
|
|
28
|
+
| Check | Evidence | Blocking result |
|
|
36
29
|
|---|---|---|
|
|
37
|
-
| Feedback loop | exact command and observed
|
|
38
|
-
| Public seam | reachable entry-to-consumer path | orphan
|
|
39
|
-
| Regression | focused and adjacent suites | failure or
|
|
40
|
-
|
|
|
41
|
-
| Error/recovery | failure
|
|
42
|
-
| Security | input
|
|
43
|
-
| Docs/spec | delivered behavior matches declared delta | drift or unknown impact |
|
|
44
|
-
|
|
45
|
-
Use repository-native commands and real boundaries
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
88
|
-
and at least one public seam is verified.
|
|
89
|
-
|
|
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
|
|
94
|
-
|
|
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.
|