ultra-builder-pro-cli 0.26.1 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ultra-template/contexts/TEMPLATE.md +64 -29
- package/.ultra-template/north-star.md +36 -30
- package/.ultra-template/project-brief.md +28 -0
- package/.ultra-template/specs/product.md +4 -0
- package/.ultra-template/specs/research-distillate.md +8 -0
- package/.ultra-template/tasks.json +1 -0
- package/.ultra-template/test-report.json +18 -1
- package/CHANGELOG.md +227 -1
- package/README.md +161 -59
- package/adapters/_shared/host-profile.cjs +68 -12
- package/adapters/_shared/plugin-core.cjs +4 -5
- package/adapters/_shared/runtime-assets.cjs +11 -2
- package/adapters/zcode.js +375 -0
- package/bin/delegate-worker.cjs +20 -0
- package/bin/delegate.cjs +94 -25
- package/bin/install.js +11 -6
- package/docs/ARCHITECTURE.md +109 -25
- package/docs/ARTIFACT-AUTHORITY.md +204 -30
- package/docs/DECISIONS.md +141 -10
- package/docs/PHILOSOPHY.md +61 -8
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -0
- package/docs/RUNTIME-COMPAT-MATRIX.md +106 -13
- package/docs/SKILL-AUTHORING.md +14 -9
- package/docs/WORKFLOW-LIFECYCLE.md +211 -53
- package/docs/evals/adversarial-review-2026-08-14.md +84 -0
- package/docs/evals/zcode-automation-2026-08-14.md +103 -0
- package/hooks/README.md +31 -3
- package/hooks/_common.py +1699 -33
- package/hooks/adapters/zcode.py +227 -0
- package/hooks/compact_context.py +176 -17
- package/hooks/mid_workflow_recall.py +27 -5
- package/hooks/post_edit_guard.py +133 -23
- package/hooks/session_context.py +722 -11
- package/package.json +6 -3
- package/skills/ultra-change/SKILL.md +84 -64
- package/skills/ultra-change/references/change-contract.md +130 -32
- package/skills/ultra-change/references/execution-grant.md +109 -0
- package/skills/ultra-change/references/primary-transfer.md +273 -0
- package/skills/ultra-change/scripts/validate_primary_transfer.cjs +1928 -0
- package/skills/ultra-delegate/SKILL.md +38 -5
- package/skills/ultra-delegate/references/delegation-contract.md +21 -1
- package/skills/ultra-deliver/SKILL.md +123 -44
- package/skills/ultra-deliver/references/baseline-reconciliation.md +5 -3
- package/skills/ultra-dev/SKILL.md +173 -43
- package/skills/ultra-grilling/SKILL.md +19 -16
- package/skills/ultra-grilling/references/reframing.md +7 -3
- package/skills/ultra-init/SKILL.md +99 -87
- package/skills/ultra-init/references/brownfield-adoption.md +23 -40
- package/skills/ultra-init/scripts/init_project.cjs +968 -21
- package/skills/ultra-plan/SKILL.md +83 -46
- package/skills/ultra-plan/references/task-evidence-v2.md +300 -0
- package/skills/ultra-plan/scripts/validate_task_evidence.cjs +859 -0
- package/skills/ultra-research/SKILL.md +119 -68
- package/skills/ultra-research/references/{03-competitive-landscape.md → 03-alternatives.md} +6 -6
- package/skills/ultra-research/references/04-product-strategy.md +11 -2
- package/skills/ultra-research/references/21-features-scope.md +8 -0
- package/skills/ultra-research/references/22-success-metrics.md +9 -4
- package/skills/ultra-research/references/99-synthesis.md +25 -1
- package/skills/ultra-research/references/north-star-v2.md +168 -0
- package/skills/ultra-research/references/wayfinding.md +50 -0
- package/skills/ultra-research/scripts/validate_north_star.cjs +1174 -0
- package/skills/ultra-review/SKILL.md +201 -24
- package/skills/ultra-review/references/adversarial-evaluation.md +31 -0
- package/skills/ultra-review/references/spec.md +5 -0
- package/skills/ultra-review/references/unified-schema.md +146 -11
- package/skills/ultra-review/references/worker-packet.md +151 -8
- package/skills/ultra-review/scripts/review_wait.py +2653 -63
- package/skills/ultra-status/SKILL.md +146 -32
- package/skills/ultra-tdd/SKILL.md +14 -4
- package/skills/ultra-tdd/references/test-execution.md +6 -1
- package/skills/ultra-test/SKILL.md +157 -15
- package/skills/ultra-test/scripts/validate_review_transport.cjs +549 -0
- package/skills/ultra-test/scripts/worktree_digest.cjs +551 -29
- package/skills/ultra-think/SKILL.md +5 -10
- package/skills/ultra-think/references/interaction-boundary.md +2 -1
package/hooks/session_context.py
CHANGED
|
@@ -1,7 +1,579 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Inject the
|
|
2
|
+
"""Inject the accepted North Star or its pre-Research fallback plus task acceptance."""
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from _common import (
|
|
15
|
+
RESUME_NAVIGATION_LIMITATION,
|
|
16
|
+
current_task_selection,
|
|
17
|
+
emit_context,
|
|
18
|
+
find_north_star_validator,
|
|
19
|
+
markdown_section,
|
|
20
|
+
project_root,
|
|
21
|
+
read_payload,
|
|
22
|
+
read_stable_project_file_snapshot,
|
|
23
|
+
render_task_diagnostics,
|
|
24
|
+
task_sections,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
NORTH_STAR_SESSIONSTART_BYTE_CEILING = 32 * 1024
|
|
29
|
+
NORTH_STAR_SNAPSHOT_MAX_BYTES = 8 * 1024 * 1024
|
|
30
|
+
PROJECT_BRIEF_SNAPSHOT_MAX_BYTES = 8 * 1024 * 1024
|
|
31
|
+
NORTH_STAR_VALIDATOR_TIMEOUT_SECONDS = 10.0
|
|
32
|
+
NORTH_STAR_VALIDATOR_MAX_OUTPUT_BYTES = 256 * 1024
|
|
33
|
+
ACCEPTED_NORTH_STAR_SECTIONS = (
|
|
34
|
+
"Acceptance and Revision",
|
|
35
|
+
"Problem Reality",
|
|
36
|
+
"First-Principle Propositions",
|
|
37
|
+
"Value Causal Chain",
|
|
38
|
+
"North Star Outcomes",
|
|
39
|
+
"Hard Constraints",
|
|
40
|
+
"Explicit Exclusions",
|
|
41
|
+
"Uncertainties and Revisit Triggers",
|
|
42
|
+
"Research Trace",
|
|
43
|
+
)
|
|
44
|
+
ACCEPTED_ID_SECTIONS = {
|
|
45
|
+
"FP": "First-Principle Propositions",
|
|
46
|
+
"NS": "North Star Outcomes",
|
|
47
|
+
"HC": "Hard Constraints",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def settled_section(text: str) -> str:
|
|
52
|
+
"""Return an explicitly populated section, not a packaged unresolved marker."""
|
|
53
|
+
if (
|
|
54
|
+
not text
|
|
55
|
+
or "[NEEDS CLARIFICATION]" in text
|
|
56
|
+
or "[NEEDS RESEARCH" in text
|
|
57
|
+
or "not yet defined" in text.lower()
|
|
58
|
+
):
|
|
59
|
+
return ""
|
|
60
|
+
return text.strip()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _validator_failure(code: str, message: str, north_star_file: Path) -> dict[str, Any]:
|
|
64
|
+
return {
|
|
65
|
+
"$schema": "ultra-north-star-validation-v1",
|
|
66
|
+
"path": str(north_star_file.absolute()),
|
|
67
|
+
"kind": "unknown",
|
|
68
|
+
"status": None,
|
|
69
|
+
"classification": "unknown",
|
|
70
|
+
"valid": False,
|
|
71
|
+
"ids": {"FP": [], "NS": [], "HC": []},
|
|
72
|
+
"sections": {},
|
|
73
|
+
"input": None,
|
|
74
|
+
"diagnostics": [{
|
|
75
|
+
"code": code,
|
|
76
|
+
"severity": "error",
|
|
77
|
+
"message": message,
|
|
78
|
+
"location": None,
|
|
79
|
+
}],
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _accepted_report_has_structural_projection(
|
|
84
|
+
report: dict[str, Any],
|
|
85
|
+
north_star_bytes: bytes,
|
|
86
|
+
) -> bool:
|
|
87
|
+
"""Validate the accepted report's exact byte projection, not its semantics."""
|
|
88
|
+
ids = report.get("ids")
|
|
89
|
+
sections = report.get("sections")
|
|
90
|
+
if (
|
|
91
|
+
not isinstance(ids, dict)
|
|
92
|
+
or set(ids) != set(ACCEPTED_ID_SECTIONS)
|
|
93
|
+
or not isinstance(sections, dict)
|
|
94
|
+
or set(sections) != set(ACCEPTED_NORTH_STAR_SECTIONS)
|
|
95
|
+
):
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
section_bodies: dict[str, str] = {}
|
|
99
|
+
previous_end = 0
|
|
100
|
+
for heading in ACCEPTED_NORTH_STAR_SECTIONS:
|
|
101
|
+
span = sections.get(heading)
|
|
102
|
+
if not isinstance(span, dict) or set(span) != {"body_start", "body_end"}:
|
|
103
|
+
return False
|
|
104
|
+
start = span.get("body_start")
|
|
105
|
+
end = span.get("body_end")
|
|
106
|
+
if (
|
|
107
|
+
not isinstance(start, int)
|
|
108
|
+
or isinstance(start, bool)
|
|
109
|
+
or not isinstance(end, int)
|
|
110
|
+
or isinstance(end, bool)
|
|
111
|
+
or start < previous_end
|
|
112
|
+
or end <= start
|
|
113
|
+
or end > len(north_star_bytes)
|
|
114
|
+
):
|
|
115
|
+
return False
|
|
116
|
+
try:
|
|
117
|
+
section_bodies[heading] = north_star_bytes[start:end].decode("utf-8")
|
|
118
|
+
except UnicodeDecodeError:
|
|
119
|
+
return False
|
|
120
|
+
previous_end = end
|
|
121
|
+
|
|
122
|
+
for kind, heading in ACCEPTED_ID_SECTIONS.items():
|
|
123
|
+
projected_ids = ids.get(kind)
|
|
124
|
+
if not isinstance(projected_ids, list) or not projected_ids:
|
|
125
|
+
return False
|
|
126
|
+
resolved_ids = re.findall(
|
|
127
|
+
rf"^### ({kind}-[A-Za-z0-9][A-Za-z0-9._-]*)\b[^\n]*$",
|
|
128
|
+
section_bodies[heading],
|
|
129
|
+
re.MULTILINE,
|
|
130
|
+
)
|
|
131
|
+
if projected_ids != resolved_ids or len(projected_ids) != len(set(projected_ids)):
|
|
132
|
+
return False
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _accepted_report_is_mechanically_bound(
|
|
137
|
+
report: dict[str, Any],
|
|
138
|
+
north_star_bytes: bytes,
|
|
139
|
+
expected_digest: str,
|
|
140
|
+
) -> bool:
|
|
141
|
+
"""Check the exact accepted-publication fields without judging their meaning."""
|
|
142
|
+
revision = report.get("revision")
|
|
143
|
+
binding = report.get("acceptance_binding")
|
|
144
|
+
observations = report.get("source_observations")
|
|
145
|
+
if (
|
|
146
|
+
report.get("kind") != "north-star-v2"
|
|
147
|
+
or report.get("status") != "accepted"
|
|
148
|
+
or report.get("classification") != "accepted"
|
|
149
|
+
or not isinstance(revision, str)
|
|
150
|
+
or not revision
|
|
151
|
+
or revision.strip() != revision
|
|
152
|
+
or revision == "none"
|
|
153
|
+
or not isinstance(binding, dict)
|
|
154
|
+
or set(binding) != {"source", "content_sha256", "git_blob_digest", "snapshot"}
|
|
155
|
+
or not isinstance(observations, list)
|
|
156
|
+
or len(observations) != 2
|
|
157
|
+
or not _accepted_report_has_structural_projection(report, north_star_bytes)
|
|
158
|
+
):
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
source = binding.get("source")
|
|
162
|
+
snapshot = binding.get("snapshot")
|
|
163
|
+
source_match = (
|
|
164
|
+
re.fullmatch(
|
|
165
|
+
r"(\.ultra/decisions/[A-Za-z0-9][A-Za-z0-9._-]*\.md)#[a-z0-9][a-z0-9-]*",
|
|
166
|
+
source,
|
|
167
|
+
)
|
|
168
|
+
if isinstance(source, str)
|
|
169
|
+
else None
|
|
170
|
+
)
|
|
171
|
+
snapshot_matches = (
|
|
172
|
+
re.fullmatch(
|
|
173
|
+
r"\.ultra/research/[A-Za-z0-9][A-Za-z0-9._-]*/"
|
|
174
|
+
r"[A-Za-z0-9][A-Za-z0-9._-]*\.accepted\.md",
|
|
175
|
+
snapshot,
|
|
176
|
+
)
|
|
177
|
+
if isinstance(snapshot, str)
|
|
178
|
+
else None
|
|
179
|
+
)
|
|
180
|
+
expected_blob_digest = hashlib.sha1(
|
|
181
|
+
f"blob {len(north_star_bytes)}\0".encode("utf-8") + north_star_bytes
|
|
182
|
+
).hexdigest()
|
|
183
|
+
if (
|
|
184
|
+
source_match is None
|
|
185
|
+
or snapshot_matches is None
|
|
186
|
+
or binding.get("content_sha256") != expected_digest
|
|
187
|
+
or binding.get("git_blob_digest") != expected_blob_digest
|
|
188
|
+
):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
decision_observation, snapshot_observation = observations
|
|
192
|
+
if (
|
|
193
|
+
not isinstance(decision_observation, dict)
|
|
194
|
+
or set(decision_observation) != {"role", "path", "sha256", "byte_length"}
|
|
195
|
+
or decision_observation.get("role") != "decision"
|
|
196
|
+
or decision_observation.get("path") != source_match.group(1)
|
|
197
|
+
or not isinstance(decision_observation.get("sha256"), str)
|
|
198
|
+
or re.fullmatch(r"[0-9a-f]{64}", decision_observation["sha256"]) is None
|
|
199
|
+
or not isinstance(decision_observation.get("byte_length"), int)
|
|
200
|
+
or isinstance(decision_observation.get("byte_length"), bool)
|
|
201
|
+
or decision_observation["byte_length"] <= 0
|
|
202
|
+
or not isinstance(snapshot_observation, dict)
|
|
203
|
+
or set(snapshot_observation) != {"role", "path", "sha256", "byte_length"}
|
|
204
|
+
or snapshot_observation.get("role") != "snapshot"
|
|
205
|
+
or snapshot_observation.get("path") != snapshot
|
|
206
|
+
or snapshot_observation.get("sha256") != expected_digest
|
|
207
|
+
or snapshot_observation.get("byte_length") != len(north_star_bytes)
|
|
208
|
+
):
|
|
209
|
+
return False
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def read_north_star_snapshot(
|
|
214
|
+
root: Path,
|
|
215
|
+
north_star_file: Path,
|
|
216
|
+
*,
|
|
217
|
+
max_bytes: int = NORTH_STAR_SNAPSHOT_MAX_BYTES,
|
|
218
|
+
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
|
|
219
|
+
"""Adapt the shared file snapshot diagnostic to the canonical validator report."""
|
|
220
|
+
try:
|
|
221
|
+
relative = north_star_file.absolute().relative_to(root.absolute())
|
|
222
|
+
except ValueError:
|
|
223
|
+
return None, _validator_failure(
|
|
224
|
+
"north_star_snapshot_read_error",
|
|
225
|
+
"Canonical North Star path must remain within the repository root.",
|
|
226
|
+
north_star_file,
|
|
227
|
+
)
|
|
228
|
+
snapshot, failure = read_stable_project_file_snapshot(
|
|
229
|
+
root,
|
|
230
|
+
relative,
|
|
231
|
+
max_bytes=max_bytes,
|
|
232
|
+
code_prefix="north_star_snapshot",
|
|
233
|
+
label="Canonical North Star",
|
|
234
|
+
)
|
|
235
|
+
if failure is not None:
|
|
236
|
+
return None, _validator_failure(
|
|
237
|
+
failure["code"],
|
|
238
|
+
failure["message"],
|
|
239
|
+
north_star_file,
|
|
240
|
+
)
|
|
241
|
+
return snapshot, None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def read_project_brief_snapshot(
|
|
245
|
+
root: Path,
|
|
246
|
+
project_brief_file: Path,
|
|
247
|
+
*,
|
|
248
|
+
max_bytes: int = PROJECT_BRIEF_SNAPSHOT_MAX_BYTES,
|
|
249
|
+
) -> tuple[dict[str, Any] | None, dict[str, str] | None]:
|
|
250
|
+
try:
|
|
251
|
+
relative = project_brief_file.absolute().relative_to(root.absolute())
|
|
252
|
+
except ValueError:
|
|
253
|
+
return None, {
|
|
254
|
+
"code": "project_brief_snapshot_read_error",
|
|
255
|
+
"message": "Canonical Project Brief path must remain within the repository root.",
|
|
256
|
+
"path": str(project_brief_file.absolute()),
|
|
257
|
+
}
|
|
258
|
+
return read_stable_project_file_snapshot(
|
|
259
|
+
root,
|
|
260
|
+
relative,
|
|
261
|
+
max_bytes=max_bytes,
|
|
262
|
+
code_prefix="project_brief_snapshot",
|
|
263
|
+
label="Canonical Project Brief",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def project_brief_text(
|
|
268
|
+
snapshot: dict[str, Any] | None,
|
|
269
|
+
failure: dict[str, str] | None,
|
|
270
|
+
) -> tuple[str, dict[str, str] | None]:
|
|
271
|
+
if snapshot is None:
|
|
272
|
+
return "", failure
|
|
273
|
+
try:
|
|
274
|
+
return snapshot["bytes"].decode("utf-8"), None
|
|
275
|
+
except UnicodeDecodeError:
|
|
276
|
+
return "", {
|
|
277
|
+
"code": "project_brief_snapshot_invalid_utf8",
|
|
278
|
+
"message": "Canonical Project Brief must be valid UTF-8.",
|
|
279
|
+
"path": snapshot["path"],
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def project_brief_repair_note(failure: dict[str, str] | None) -> str:
|
|
284
|
+
code = failure.get("code") if isinstance(failure, dict) else "project_brief_snapshot_read_error"
|
|
285
|
+
return (
|
|
286
|
+
f"Ultra Project Brief fallback is unavailable (`{code}`); no Project Brief bytes "
|
|
287
|
+
"were published. Repair `.ultra/project-brief.md` as a stable UTF-8 regular file "
|
|
288
|
+
"inside the repository, then retry SessionStart."
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _terminate_and_reap(process: subprocess.Popen) -> None:
|
|
293
|
+
"""Stop one validator attempt and leave no live child behind."""
|
|
294
|
+
if process.poll() is not None:
|
|
295
|
+
return
|
|
296
|
+
try:
|
|
297
|
+
process.terminate()
|
|
298
|
+
except OSError:
|
|
299
|
+
pass
|
|
300
|
+
try:
|
|
301
|
+
process.wait(timeout=0.25)
|
|
302
|
+
return
|
|
303
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
304
|
+
pass
|
|
305
|
+
try:
|
|
306
|
+
process.kill()
|
|
307
|
+
process.wait(timeout=0.25)
|
|
308
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _read_bounded_process_output(
|
|
313
|
+
process: subprocess.Popen,
|
|
314
|
+
timeout_seconds: float,
|
|
315
|
+
max_output_bytes: int,
|
|
316
|
+
input_bytes: bytes,
|
|
317
|
+
) -> tuple[bytes, bytes, str | None]:
|
|
318
|
+
"""Drain both pipes concurrently while retaining at most one combined byte budget."""
|
|
319
|
+
buffers = {"stdout": bytearray(), "stderr": bytearray()}
|
|
320
|
+
lock = threading.Lock()
|
|
321
|
+
stop = threading.Event()
|
|
322
|
+
changed = threading.Event()
|
|
323
|
+
output_too_large = threading.Event()
|
|
324
|
+
total_bytes = 0
|
|
325
|
+
|
|
326
|
+
def drain(name: str, stream) -> None:
|
|
327
|
+
nonlocal total_bytes
|
|
328
|
+
try:
|
|
329
|
+
while not stop.is_set():
|
|
330
|
+
chunk = stream.read1(8192)
|
|
331
|
+
if not chunk:
|
|
332
|
+
break
|
|
333
|
+
with lock:
|
|
334
|
+
remaining = max(0, max_output_bytes - total_bytes)
|
|
335
|
+
retained = chunk[:remaining]
|
|
336
|
+
buffers[name].extend(retained)
|
|
337
|
+
total_bytes += len(retained)
|
|
338
|
+
if len(chunk) > remaining:
|
|
339
|
+
output_too_large.set()
|
|
340
|
+
stop.set()
|
|
341
|
+
changed.set()
|
|
342
|
+
except OSError:
|
|
343
|
+
# Closing a pipe while terminating the child is an expected cleanup path.
|
|
344
|
+
pass
|
|
345
|
+
finally:
|
|
346
|
+
changed.set()
|
|
347
|
+
|
|
348
|
+
readers = [
|
|
349
|
+
threading.Thread(target=drain, args=("stdout", process.stdout), daemon=True),
|
|
350
|
+
threading.Thread(target=drain, args=("stderr", process.stderr), daemon=True),
|
|
351
|
+
]
|
|
352
|
+
|
|
353
|
+
def feed_stdin() -> None:
|
|
354
|
+
try:
|
|
355
|
+
process.stdin.write(input_bytes)
|
|
356
|
+
process.stdin.flush()
|
|
357
|
+
except (BrokenPipeError, OSError):
|
|
358
|
+
pass
|
|
359
|
+
finally:
|
|
360
|
+
try:
|
|
361
|
+
process.stdin.close()
|
|
362
|
+
except OSError:
|
|
363
|
+
pass
|
|
364
|
+
changed.set()
|
|
365
|
+
|
|
366
|
+
writer = threading.Thread(target=feed_stdin, daemon=True)
|
|
367
|
+
for reader in readers:
|
|
368
|
+
reader.start()
|
|
369
|
+
writer.start()
|
|
370
|
+
|
|
371
|
+
deadline = time.monotonic() + timeout_seconds
|
|
372
|
+
failure = None
|
|
373
|
+
while process.poll() is None:
|
|
374
|
+
if output_too_large.is_set():
|
|
375
|
+
failure = "output_too_large"
|
|
376
|
+
break
|
|
377
|
+
remaining_time = deadline - time.monotonic()
|
|
378
|
+
if remaining_time <= 0:
|
|
379
|
+
failure = "timeout"
|
|
380
|
+
break
|
|
381
|
+
changed.wait(min(remaining_time, 0.05))
|
|
382
|
+
changed.clear()
|
|
383
|
+
|
|
384
|
+
if failure is not None:
|
|
385
|
+
stop.set()
|
|
386
|
+
_terminate_and_reap(process)
|
|
387
|
+
else:
|
|
388
|
+
process.wait()
|
|
389
|
+
|
|
390
|
+
for reader in readers:
|
|
391
|
+
reader.join(timeout=0.5)
|
|
392
|
+
writer.join(timeout=0.5)
|
|
393
|
+
if output_too_large.is_set():
|
|
394
|
+
failure = "output_too_large"
|
|
395
|
+
for stream in (process.stdin, process.stdout, process.stderr):
|
|
396
|
+
try:
|
|
397
|
+
stream.close()
|
|
398
|
+
except OSError:
|
|
399
|
+
pass
|
|
400
|
+
return bytes(buffers["stdout"]), bytes(buffers["stderr"]), failure
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def run_north_star_validator(
|
|
404
|
+
root: Path,
|
|
405
|
+
north_star_file: Path,
|
|
406
|
+
north_star_bytes: bytes,
|
|
407
|
+
*,
|
|
408
|
+
validator: Path | None = None,
|
|
409
|
+
node_binary: Path | str | None = None,
|
|
410
|
+
timeout_seconds: float = NORTH_STAR_VALIDATOR_TIMEOUT_SECONDS,
|
|
411
|
+
max_output_bytes: int = NORTH_STAR_VALIDATOR_MAX_OUTPUT_BYTES,
|
|
412
|
+
) -> dict[str, Any]:
|
|
413
|
+
"""Run the canonical JS validator; every bridge failure is typed and recoverable."""
|
|
414
|
+
if validator is None:
|
|
415
|
+
validator = find_north_star_validator(Path(__file__))
|
|
416
|
+
if validator is None:
|
|
417
|
+
return _validator_failure(
|
|
418
|
+
"north_star_validator_missing",
|
|
419
|
+
"Canonical North Star validator is unavailable in this managed layout.",
|
|
420
|
+
north_star_file,
|
|
421
|
+
)
|
|
422
|
+
try:
|
|
423
|
+
validator = Path(validator).resolve(strict=True)
|
|
424
|
+
except (OSError, RuntimeError):
|
|
425
|
+
return _validator_failure(
|
|
426
|
+
"north_star_validator_missing",
|
|
427
|
+
"Canonical North Star validator does not exist.",
|
|
428
|
+
north_star_file,
|
|
429
|
+
)
|
|
430
|
+
if not validator.is_file():
|
|
431
|
+
return _validator_failure(
|
|
432
|
+
"north_star_validator_missing",
|
|
433
|
+
"Canonical North Star validator is not a regular file.",
|
|
434
|
+
north_star_file,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
resolved_node = str(node_binary) if node_binary is not None else shutil.which("node")
|
|
438
|
+
if not resolved_node:
|
|
439
|
+
return _validator_failure(
|
|
440
|
+
"north_star_validator_node_missing",
|
|
441
|
+
"Node.js is unavailable, so accepted North Star publication cannot be verified.",
|
|
442
|
+
north_star_file,
|
|
443
|
+
)
|
|
444
|
+
try:
|
|
445
|
+
process = subprocess.Popen(
|
|
446
|
+
[
|
|
447
|
+
resolved_node,
|
|
448
|
+
str(validator),
|
|
449
|
+
"--stdin",
|
|
450
|
+
"--path",
|
|
451
|
+
str(north_star_file.absolute()),
|
|
452
|
+
],
|
|
453
|
+
cwd=root,
|
|
454
|
+
stdin=subprocess.PIPE,
|
|
455
|
+
stdout=subprocess.PIPE,
|
|
456
|
+
stderr=subprocess.PIPE,
|
|
457
|
+
shell=False,
|
|
458
|
+
)
|
|
459
|
+
except OSError as error:
|
|
460
|
+
return _validator_failure(
|
|
461
|
+
"north_star_validator_execution_error",
|
|
462
|
+
f"Canonical North Star validator could not execute: {error}",
|
|
463
|
+
north_star_file,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
stdout, _stderr, failure = _read_bounded_process_output(
|
|
467
|
+
process,
|
|
468
|
+
timeout_seconds,
|
|
469
|
+
max_output_bytes,
|
|
470
|
+
north_star_bytes,
|
|
471
|
+
)
|
|
472
|
+
if failure == "timeout":
|
|
473
|
+
return _validator_failure(
|
|
474
|
+
"north_star_validator_timeout",
|
|
475
|
+
"Canonical North Star validation timed out; retry after checking Node.js and the installation.",
|
|
476
|
+
north_star_file,
|
|
477
|
+
)
|
|
478
|
+
if failure == "output_too_large":
|
|
479
|
+
return _validator_failure(
|
|
480
|
+
"north_star_validator_output_too_large",
|
|
481
|
+
"Canonical North Star validator exceeded its bounded output contract.",
|
|
482
|
+
north_star_file,
|
|
483
|
+
)
|
|
484
|
+
try:
|
|
485
|
+
output = stdout.decode("utf-8")
|
|
486
|
+
report = json.loads(output)
|
|
487
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
488
|
+
return _validator_failure(
|
|
489
|
+
"north_star_validator_invalid_json",
|
|
490
|
+
"Canonical North Star validator returned invalid UTF-8 JSON.",
|
|
491
|
+
north_star_file,
|
|
492
|
+
)
|
|
493
|
+
if (
|
|
494
|
+
not isinstance(report, dict)
|
|
495
|
+
or report.get("$schema") != "ultra-north-star-validation-v1"
|
|
496
|
+
or not isinstance(report.get("valid"), bool)
|
|
497
|
+
or not isinstance(report.get("diagnostics"), list)
|
|
498
|
+
or not isinstance(report.get("sections"), dict)
|
|
499
|
+
or process.returncode not in {0, 1}
|
|
500
|
+
or (report["valid"] and process.returncode != 0)
|
|
501
|
+
or (not report["valid"] and process.returncode != 1)
|
|
502
|
+
):
|
|
503
|
+
return _validator_failure(
|
|
504
|
+
"north_star_validator_invalid_report",
|
|
505
|
+
"Canonical North Star validator returned an inconsistent report contract.",
|
|
506
|
+
north_star_file,
|
|
507
|
+
)
|
|
508
|
+
expected_path = str(north_star_file.absolute())
|
|
509
|
+
if report.get("path") != expected_path:
|
|
510
|
+
return _validator_failure(
|
|
511
|
+
"north_star_validator_path_mismatch",
|
|
512
|
+
"Canonical North Star validator reported a different authority path.",
|
|
513
|
+
north_star_file,
|
|
514
|
+
)
|
|
515
|
+
expected_digest = hashlib.sha256(north_star_bytes).hexdigest()
|
|
516
|
+
receipt = report.get("input")
|
|
517
|
+
if (
|
|
518
|
+
not isinstance(receipt, dict)
|
|
519
|
+
or receipt.get("path") != expected_path
|
|
520
|
+
or receipt.get("byte_length") != len(north_star_bytes)
|
|
521
|
+
or receipt.get("sha256") != expected_digest
|
|
522
|
+
):
|
|
523
|
+
return _validator_failure(
|
|
524
|
+
"north_star_validator_input_mismatch",
|
|
525
|
+
"Canonical North Star validator did not bind its verdict to the supplied snapshot bytes.",
|
|
526
|
+
north_star_file,
|
|
527
|
+
)
|
|
528
|
+
claims_accepted_publication = (
|
|
529
|
+
report.get("valid") is True
|
|
530
|
+
and (
|
|
531
|
+
report.get("status") == "accepted"
|
|
532
|
+
or report.get("classification") == "accepted"
|
|
533
|
+
)
|
|
534
|
+
)
|
|
535
|
+
if claims_accepted_publication and not _accepted_report_is_mechanically_bound(
|
|
536
|
+
report,
|
|
537
|
+
north_star_bytes,
|
|
538
|
+
expected_digest,
|
|
539
|
+
):
|
|
540
|
+
return _validator_failure(
|
|
541
|
+
"north_star_validator_invalid_report",
|
|
542
|
+
"Canonical North Star validator returned an incomplete accepted-publication report.",
|
|
543
|
+
north_star_file,
|
|
544
|
+
)
|
|
545
|
+
return report
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def snapshot_section(
|
|
549
|
+
snapshot: dict[str, Any] | None,
|
|
550
|
+
validation: dict[str, Any],
|
|
551
|
+
heading: str,
|
|
552
|
+
) -> str:
|
|
553
|
+
"""Slice one validator-identified section from the exact caller-owned bytes."""
|
|
554
|
+
if snapshot is None:
|
|
555
|
+
return ""
|
|
556
|
+
sections = validation.get("sections")
|
|
557
|
+
span = sections.get(heading) if isinstance(sections, dict) else None
|
|
558
|
+
if not isinstance(span, dict):
|
|
559
|
+
return ""
|
|
560
|
+
start = span.get("body_start")
|
|
561
|
+
end = span.get("body_end")
|
|
562
|
+
data = snapshot["bytes"]
|
|
563
|
+
if (
|
|
564
|
+
not isinstance(start, int)
|
|
565
|
+
or isinstance(start, bool)
|
|
566
|
+
or not isinstance(end, int)
|
|
567
|
+
or isinstance(end, bool)
|
|
568
|
+
or start < 0
|
|
569
|
+
or end < start
|
|
570
|
+
or end > len(data)
|
|
571
|
+
):
|
|
572
|
+
return ""
|
|
573
|
+
try:
|
|
574
|
+
return data[start:end].decode("utf-8").strip()
|
|
575
|
+
except UnicodeDecodeError:
|
|
576
|
+
return ""
|
|
5
577
|
|
|
6
578
|
|
|
7
579
|
def main() -> int:
|
|
@@ -9,19 +581,158 @@ def main() -> int:
|
|
|
9
581
|
root = project_root(payload) # Idle guard: no `.ultra/` means silent exit.
|
|
10
582
|
if root is None:
|
|
11
583
|
return 0
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
584
|
+
ultra = root / ".ultra"
|
|
585
|
+
north_star_file = ultra / "north-star.md"
|
|
586
|
+
snapshot, snapshot_failure = read_north_star_snapshot(root, north_star_file)
|
|
587
|
+
validation = (
|
|
588
|
+
run_north_star_validator(root, north_star_file, snapshot["bytes"])
|
|
589
|
+
if snapshot is not None
|
|
590
|
+
else snapshot_failure
|
|
591
|
+
)
|
|
592
|
+
if validation is None:
|
|
593
|
+
validation = _validator_failure(
|
|
594
|
+
"north_star_snapshot_read_error",
|
|
595
|
+
"Canonical North Star snapshot was unavailable.",
|
|
596
|
+
north_star_file,
|
|
597
|
+
)
|
|
16
598
|
parts = []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
599
|
+
classification = validation.get("classification", "unknown")
|
|
600
|
+
v2_sections = [
|
|
601
|
+
(heading, snapshot_section(snapshot, validation, heading))
|
|
602
|
+
for heading in (
|
|
603
|
+
"Problem Reality",
|
|
604
|
+
"First-Principle Propositions",
|
|
605
|
+
"North Star Outcomes",
|
|
606
|
+
"Hard Constraints",
|
|
607
|
+
)
|
|
608
|
+
]
|
|
609
|
+
accepted_publication = (
|
|
610
|
+
validation.get("valid") is True
|
|
611
|
+
and validation.get("kind") == "north-star-v2"
|
|
612
|
+
and validation.get("status") == "accepted"
|
|
613
|
+
and classification == "accepted"
|
|
614
|
+
and all(body for _, body in v2_sections)
|
|
615
|
+
)
|
|
616
|
+
binding = validation.get("acceptance_binding")
|
|
617
|
+
validated_input = validation.get("input")
|
|
618
|
+
expected_digest = binding.get("content_sha256") if isinstance(binding, dict) else None
|
|
619
|
+
current_digest = (
|
|
620
|
+
snapshot["sha256"]
|
|
621
|
+
if snapshot is not None
|
|
622
|
+
else None
|
|
623
|
+
)
|
|
624
|
+
if accepted_publication and current_digest != expected_digest:
|
|
625
|
+
accepted_publication = False
|
|
626
|
+
validation = _validator_failure(
|
|
627
|
+
"north_star_changed_during_validation",
|
|
628
|
+
"North Star bytes changed after canonical validation; retry SessionStart.",
|
|
629
|
+
north_star_file,
|
|
630
|
+
)
|
|
631
|
+
if accepted_publication:
|
|
632
|
+
baseline = "\n\n".join(f"{heading}:\n{body}" for heading, body in v2_sections)
|
|
633
|
+
baseline_bytes = len(baseline.encode("utf-8"))
|
|
634
|
+
if baseline_bytes <= NORTH_STAR_SESSIONSTART_BYTE_CEILING:
|
|
635
|
+
parts.append(f"Ultra accepted North Star v2:\n{baseline}")
|
|
636
|
+
else:
|
|
637
|
+
revision = validation.get("revision") or "unknown"
|
|
638
|
+
status = validation.get("status") or "accepted"
|
|
639
|
+
accepted_snapshot = binding["snapshot"]
|
|
640
|
+
validated_sha256 = validated_input["sha256"]
|
|
641
|
+
validated_byte_length = validated_input["byte_length"]
|
|
642
|
+
parts.append(
|
|
643
|
+
"Ultra accepted North Star v2 requires direct read:\n"
|
|
644
|
+
"- Path: `.ultra/north-star.md`\n"
|
|
645
|
+
f"- Immutable accepted snapshot: `{accepted_snapshot}`\n"
|
|
646
|
+
f"- Revision: `{revision}`\n"
|
|
647
|
+
f"- Status: `{status}`\n"
|
|
648
|
+
f"- Validated North Star SHA-256: `{validated_sha256}`\n"
|
|
649
|
+
f"- Validated North Star byte length: `{validated_byte_length}`\n"
|
|
650
|
+
"- Read the immutable accepted snapshot and verify both values before "
|
|
651
|
+
"using it. Do not treat different bytes as accepted authority.\n"
|
|
652
|
+
f"- Reason: the complete selected canonical sections are {baseline_bytes} "
|
|
653
|
+
f"UTF-8 bytes, exceeding the {NORTH_STAR_SESSIONSTART_BYTE_CEILING}-byte "
|
|
654
|
+
"SessionStart ceiling; semantic truncation is forbidden."
|
|
655
|
+
)
|
|
656
|
+
else:
|
|
657
|
+
diagnostics = validation.get("diagnostics")
|
|
658
|
+
first_code = (
|
|
659
|
+
diagnostics[0].get("code")
|
|
660
|
+
if isinstance(diagnostics, list)
|
|
661
|
+
and diagnostics
|
|
662
|
+
and isinstance(diagnostics[0], dict)
|
|
663
|
+
else None
|
|
664
|
+
)
|
|
665
|
+
if first_code and first_code.startswith("north_star_validator_"):
|
|
666
|
+
parts.append(
|
|
667
|
+
f"Ultra North Star validation is unavailable (`{first_code}`); accepted "
|
|
668
|
+
"session authority was not published. Repair or retry the local validator; "
|
|
669
|
+
"the project brief remains the recoverable fallback."
|
|
670
|
+
)
|
|
671
|
+
elif first_code in {"north_star_changed_during_validation", "north_star_snapshot_changed"}:
|
|
672
|
+
parts.append(
|
|
673
|
+
"Ultra North Star changed during validation; accepted session authority was "
|
|
674
|
+
"not published. Retry SessionStart after writes settle."
|
|
675
|
+
)
|
|
676
|
+
elif classification == "accepted":
|
|
677
|
+
parts.append(
|
|
678
|
+
"Ultra North Star v2 publication binding is invalid; it is not accepted "
|
|
679
|
+
"session authority. Repair `.ultra/north-star.md`, its owner decision, or "
|
|
680
|
+
"its immutable accepted snapshot, then rerun Research validation."
|
|
681
|
+
)
|
|
682
|
+
elif validation.get("valid") is False and first_code:
|
|
683
|
+
parts.append(
|
|
684
|
+
f"Ultra North Star validation rejected the current artifact (`{first_code}`); "
|
|
685
|
+
"accepted session authority was not published. Repair it through Research; "
|
|
686
|
+
"the project brief remains the recoverable fallback."
|
|
687
|
+
)
|
|
688
|
+
direction = settled_section(snapshot_section(snapshot, validation, "Project Direction"))
|
|
689
|
+
outcome = settled_section(snapshot_section(snapshot, validation, "North Star Outcome"))
|
|
690
|
+
constraints = settled_section(snapshot_section(snapshot, validation, "Hard Constraints"))
|
|
691
|
+
legacy_line = settled_section(snapshot_section(snapshot, validation, "One-line"))
|
|
692
|
+
if validation.get("valid") is True and classification == "legacy" and direction and outcome and constraints:
|
|
693
|
+
baseline = [f"Project Direction:\n{direction}"]
|
|
694
|
+
baseline.append(f"North Star Outcome:\n{outcome}")
|
|
695
|
+
baseline.append(f"Hard Constraints:\n{constraints}")
|
|
696
|
+
baseline_text = "\n\n".join(baseline)
|
|
697
|
+
parts.append(f"Ultra legacy v0.26 North Star:\n{baseline_text}")
|
|
698
|
+
elif validation.get("valid") is True and classification == "legacy" and legacy_line:
|
|
699
|
+
parts.append(f"Ultra legacy project intent:\n{legacy_line}")
|
|
700
|
+
else:
|
|
701
|
+
project_brief_file = ultra / "project-brief.md"
|
|
702
|
+
brief_snapshot, brief_failure = read_project_brief_snapshot(
|
|
703
|
+
root,
|
|
704
|
+
project_brief_file,
|
|
705
|
+
)
|
|
706
|
+
project_brief, brief_failure = project_brief_text(
|
|
707
|
+
brief_snapshot,
|
|
708
|
+
brief_failure,
|
|
709
|
+
)
|
|
710
|
+
if brief_failure is not None:
|
|
711
|
+
parts.append(project_brief_repair_note(brief_failure))
|
|
712
|
+
else:
|
|
713
|
+
brief_line = settled_section(markdown_section(project_brief, "One-line"))
|
|
714
|
+
if brief_line:
|
|
715
|
+
parts.append(f"Ultra project brief (Research not yet accepted):\n{brief_line}")
|
|
716
|
+
selection = current_task_selection(root)
|
|
717
|
+
task = selection["task"]
|
|
21
718
|
if task:
|
|
22
|
-
|
|
719
|
+
sections = task_sections(root, task)
|
|
720
|
+
acceptance = sections["acceptance"]
|
|
23
721
|
if acceptance:
|
|
24
722
|
parts.append(f"Current task {task.get('id')} acceptance:\n{acceptance}")
|
|
723
|
+
resume = sections["resume"]
|
|
724
|
+
if resume:
|
|
725
|
+
parts.append(
|
|
726
|
+
f"Current task {task.get('id')} resume note:\n{resume}\n\n"
|
|
727
|
+
+ RESUME_NAVIGATION_LIMITATION
|
|
728
|
+
)
|
|
729
|
+
diagnostics = render_task_diagnostics(sections["diagnostics"])
|
|
730
|
+
if diagnostics:
|
|
731
|
+
parts.append(f"Current task {task.get('id')} diagnostics:\n{diagnostics}")
|
|
732
|
+
else:
|
|
733
|
+
diagnostics = render_task_diagnostics(selection["diagnostics"])
|
|
734
|
+
if diagnostics:
|
|
735
|
+
parts.append(f"Task ledger diagnostics:\n{diagnostics}")
|
|
25
736
|
emit_context("SessionStart", "\n\n".join(parts))
|
|
26
737
|
return 0
|
|
27
738
|
|