ultra-builder-pro-cli 0.26.0 → 0.26.1

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.
File without changes
@@ -32,6 +32,8 @@
32
32
  <!-- The verifiable definition of done. Read by mid_workflow_recall and session_context, and
33
33
  injected into agent context — keep every line testable and keep noise out. -->
34
34
 
35
+ **Change Acceptance IDs**: [`AC-1`, `AC-3`, or `none` for task-local-only work]
36
+
35
37
  - [ ] `exact test command`
36
38
  - [ ] Pass: [scenario 1]
37
39
  - [ ] Pass: [scenario 2]
@@ -51,6 +53,11 @@
51
53
 
52
54
  **Source**: `.ultra/specs/product.md#section-id`
53
55
 
56
+ **Hard Constraints**: [`HC-1`, `HC-3`, or `none` — the north-star constraints this
57
+ task could plausibly violate. Read them before the first edit and again before
58
+ marking complete. Crossing one stops the work and goes to the owner, however good
59
+ the argument.]
60
+
54
61
  **Decisions**: [relevant `.ultra/decisions/<id>.md` entries, or `none`]
55
62
 
56
63
  ## Change Log
package/CHANGELOG.md CHANGED
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.26.1] — 2026-08-03
11
+
12
+ ### Added
13
+
14
+ - Added reframing to `ultra-grilling`, five bounded framing questions, and checkable
15
+ resolved, stalled, and unavailable exits so a literal request can be translated
16
+ into the role and outcome the owner actually means.
17
+ - Added `HC-<n>` hard-constraint identifiers, task-level constraint mapping,
18
+ executable Change verification, and a supported `changes/abandoned/` exit.
19
+ - Added owner-authorized cross-model-family review for major or security-relevant
20
+ Changes, plus optional native fan-out inside independent research and test regions.
21
+
22
+ ### Changed
23
+
24
+ - Made `ultra-dev` converge on each task's mapped Change acceptance IDs, track the
25
+ best-ever passing set, and stop after two no-progress rounds or three repair rounds.
26
+ - Expanded delivery reconciliation to every repository document and kept its wiring
27
+ gate local to an explicitly invoked `ultra-deliver`; ordinary npm lifecycle commands
28
+ never depend on project `.ultra/` evidence.
29
+ - Made `ultra-change` read relevant archive history before opening intent, preferring
30
+ `delivery.md` while supporting legacy archive summaries and verification records.
31
+ - Made all six review lenses the default and required every skipped lens to preserve
32
+ its reason in the aggregate result.
33
+
34
+ ### Fixed
35
+
36
+ - Prevented delegated workers from modifying `.ultra/`, including when `.` grants the
37
+ rest of an isolated checkout.
38
+ - Made dangerous-command authorization reachable by hashing the protected effect
39
+ independently of its retry spelling.
40
+ - Kept additive protected-branch publication on the host's native authority path instead
41
+ of hard-blocking an already owner-authorized release, retained exact-digest guards for
42
+ history rewrites and branch deletion, and stopped treating inert searches as effects.
43
+ - Kept expandable interpreter heredocs inside dangerous-effect inspection while
44
+ continuing to treat quoted commit-message heredocs as data.
45
+ - Required explicit owner authority before `ultra-review` can launch a cross-host
46
+ model-family recheck.
47
+ - Bound `ultra-dev` convergence to the active Change acceptance IDs, with explicit
48
+ stalled and unreachable exits instead of an open-ended repair loop.
49
+ - Made `ultra-change` recover archived intent from legacy archive evidence when the
50
+ preferred delivery record is absent.
51
+ - Kept the `ultra-deliver` wiring gate local to an owner-invoked delivery workflow;
52
+ ordinary package commands remain independent of project `.ultra/` state.
53
+ - Added the missing `changes/abandoned/` template directory and migrated completed WIP
54
+ evidence links to their canonical runtime document.
55
+
56
+ ### Verified
57
+
58
+ - Completed an authenticated Claude-to-Codex continuation on one isolated
59
+ file-first task: Claude banked the failing test and Resume Note without changing
60
+ implementation, and Codex resumed from Git plus `.ultra/`, reached green, and
61
+ closed the ledger and canonical evidence without conversation transfer.
62
+ - Verified the release candidate with 111 Node tests, 10 Hook tests, 14 Skill Creator
63
+ validations, five healthy host Doctor reports, and zero audit vulnerabilities.
64
+
10
65
  ## [0.26.0] — 2026-08-01
11
66
 
12
67
  ### Added
@@ -1167,7 +1222,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1167
1222
  skill manifest, CLI protocol + mapping table; 5 spec validators.
1168
1223
  - **Phase 0 — skeleton**: multi-runtime installer scaffolding.
1169
1224
 
1170
- [Unreleased]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.20.0...HEAD
1225
+ [Unreleased]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.26.1...HEAD
1226
+ [0.26.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.26.0...v0.26.1
1227
+ [0.26.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.25.1...v0.26.0
1228
+ [0.25.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.25.0...v0.25.1
1229
+ [0.25.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.24.2...v0.25.0
1230
+ [0.24.2]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.24.1...v0.24.2
1231
+ [0.24.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.24.0...v0.24.1
1232
+ [0.24.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.23.0...v0.24.0
1233
+ [0.23.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.22.0...v0.23.0
1234
+ [0.22.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.21.1...v0.22.0
1235
+ [0.21.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.21.0...v0.21.1
1236
+ [0.21.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.20.0...v0.21.0
1171
1237
  [0.20.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.1...v0.20.0
1172
1238
  [0.19.1]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.19.0...v0.19.1
1173
1239
  [0.19.0]: https://github.com/rocky2431/ultra-builder-pro-cli/compare/v0.18.0...v0.19.0
package/README.md CHANGED
@@ -219,7 +219,7 @@ Five optional hooks accelerate file reading and protect a narrow effect boundary
219
219
  | `mid_workflow_recall.py` | Restate acceptance before relevant source operations |
220
220
  | `compact_context.py` | Save and restore a disposable Git/file snapshot |
221
221
  | `post_edit_guard.py` | Record mechanical evidence observations after edits |
222
- | `block_dangerous_commands.py` | Deny named destructive shell effects until the exact command is authorized |
222
+ | `block_dangerous_commands.py` | Advise on additive protected-branch publication; deny history rewrites and named destructive shell effects until the exact command is authorized |
223
223
 
224
224
  All five exit silently when `.ultra/` is absent. Details are in
225
225
  [Plugin Isolation](docs/PLUGIN-ISOLATION-CONTRACT.md).
@@ -7,6 +7,7 @@ const path = require('node:path');
7
7
  const { execFileSync, spawn } = require('node:child_process');
8
8
 
9
9
  const RESULT_SCHEMA = 'ultra-delegation-result-v1';
10
+ const PROTECTED_ROOT = '.ultra';
10
11
  const MODEL_FIELDS = new Set([
11
12
  '$schema', 'status', 'summary', 'changed_files', 'checks', 'evidence',
12
13
  'questions', 'residual_risks',
@@ -148,7 +149,11 @@ function validateModelResult(payload) {
148
149
  return null;
149
150
  }
150
151
 
152
+ // The project's memory has exactly one writer: the primary host. A worker that edits
153
+ // `.ultra` in its own worktree collides in the files every later session reads to
154
+ // resume, so no permission grants it — not even `writable_roots: ["."]`.
151
155
  function authorized(spec, file) {
156
+ if (file === PROTECTED_ROOT || file.startsWith(`${PROTECTED_ROOT}/`)) return false;
152
157
  return spec.writable_roots.some((root) => (
153
158
  root === '.' || file === root || file.startsWith(`${root}/`)
154
159
  ));
package/bin/delegate.cjs CHANGED
@@ -12,6 +12,7 @@ const PERMISSION_SCHEMA = 'ultra-delegation-permission-v1';
12
12
  const RECEIPT_SCHEMA = 'ultra-delegation-receipt-v1';
13
13
  const RESULT_SCHEMA = 'ultra-delegation-result-v1';
14
14
  const PERMISSION_FIELDS = new Set(['$schema', 'writable_roots', 'external_effects']);
15
+ const PROTECTED_ROOT = '.ultra';
15
16
  const RESULT_JSON_SCHEMA = Object.freeze({
16
17
  type: 'object',
17
18
  additionalProperties: false,
@@ -149,6 +150,12 @@ function readPermission(file, worktree) {
149
150
  throw new Error('permission writable_roots must not escape through symlinks');
150
151
  }
151
152
  const normalized = path.relative(worktree, real).split(path.sep).join('/') || '.';
153
+ // `.ultra` is the project's memory and has exactly one writer: the primary host.
154
+ // Rejected here so the delegation never starts, and again against the actual diff
155
+ // in the worker, which also covers the `.` grant.
156
+ if (normalized === PROTECTED_ROOT || normalized.startsWith(`${PROTECTED_ROOT}/`)) {
157
+ throw new Error(`permission writable_roots must not include ${PROTECTED_ROOT}; the primary host is its only writer`);
158
+ }
152
159
  if (!writableRoots.includes(normalized)) {
153
160
  writableRoots.push(normalized);
154
161
  writableRootPaths.push(real);
@@ -87,7 +87,10 @@ the parent model runs the same references sequentially.
87
87
  Five hooks read canonical files and emit bounded context or observations. Their output
88
88
  under `.ultra/.runtime/`, `.ultra/progress/`, or `.ultra/reviews/` is derived and
89
89
  disposable. The dangerous-command hook is the only hard effect guard; it classifies a
90
- small named set of shell effects and provides an exact-command authorization repair.
90
+ small named destructive set and provides an exact-command authorization repair.
91
+ Additive protected-branch publication remains advisory when portable host wiring cannot
92
+ project a trusted owner-approval receipt; history rewrites and branch deletion remain
93
+ guarded.
91
94
 
92
95
  ## Completion and recovery
93
96
 
package/docs/DECISIONS.md CHANGED
@@ -52,8 +52,10 @@ No single sensor is promoted into a semantic completion gate.
52
52
 
53
53
  The hook surface is session context, mid-workflow acceptance recall, compact snapshot,
54
54
  post-edit evidence observation, and dangerous-command protection. Every hook is silent
55
- without `.ultra/`. Only a narrow named destructive effect can be denied, and its repair
56
- is authorization scoped to the exact command digest.
55
+ without `.ultra/`. Additive protected-branch publication is advisory because the
56
+ portable hook cannot receive every host's trusted owner-approval receipt. History
57
+ rewrites, branch deletion, and the narrower named destructive effects can be denied;
58
+ their repair is authorization scoped to the exact command digest.
57
59
 
58
60
  ## Delegation is a process boundary
59
61
 
@@ -61,16 +61,20 @@ agents to find loopholes (rename `mock` to `stub`, etc.). The cheaper path must
61
61
 
62
62
  ### C3 — Sensors not Blockers
63
63
 
64
- Hooks emit signal; agents and users decide. Block only the **truly irreversible**:
64
+ Hooks emit signal; agents and users decide. Block only the **truly irreversible or
65
+ privileged destructive** effects for which the hook has an exact repair:
65
66
 
66
- - `git push` to protected branches
67
67
  - Funds transfer / on-chain transactions
68
- - DB migrations / `DROP` / `TRUNCATE`
68
+ - Protected-branch history rewrite or deletion
69
+ - `DROP` / `TRUNCATE`
69
70
  - Hardcoded secret commit
70
71
  - Arbitrary code execution driven by user input
71
72
 
72
- Everything else is advisory on stderr — the work stands, the signal is delivered, the agent
73
- proceeds.
73
+ Additive protected-branch publication and database migrations are advisory: the former
74
+ remains recoverable through Git and the portable hook cannot receive every host's
75
+ trusted approval receipt; the latter is not mechanically classifiable as reversible or
76
+ irreversible. The work stands, the signal is delivered, and the agent proceeds inside
77
+ the owner's authorized frame.
74
78
 
75
79
  > **Why this is absolute**: hard final gates and hard pre-commit gates each produced the same
76
80
  > over-correction loop in v7 — the agent weakened tests to escape the gate and drifted from the
@@ -102,6 +106,27 @@ boundary surfaces to the user.
102
106
  feature flags, in-memory replacing real persistence, scope reduction, external or irreversible
103
107
  effects
104
108
 
109
+ **Which decisions reach the owner.** Inside/Crossing above sorts actions. Read from the owner's
110
+ side, the same boundary sorts decisions into three:
111
+
112
+ | Type | Definition | Handling |
113
+ |---|---|---|
114
+ | taste | no objectively correct answer; only the owner's preference settles it | always ask |
115
+ | crossing | objectively decidable, but the consequence is irreversible | always ask |
116
+ | everything else | has an objective answer, or is cheap to reverse | decide and proceed; asking is the error |
117
+
118
+ How much lands in the third row is a function of how sharp the north star is. With constraints
119
+ that can veto a concrete decision, most execution-time questions are answered by reading them.
120
+ With vague ones, "does this cross a boundary?" cannot be decided at all, so every question
121
+ degrades into taste and reaches the owner. **Interrupt frequency measures the north star, not
122
+ the work.** Frequent interruptions mean going back to sharpen constraints, not asking faster.
123
+
124
+ **Boundaries grow; they do not change.** Adding a hard constraint or a non-goal mid-flight is
125
+ routine and needs no permission — the frame got sharper and no prior promise broke. Editing or
126
+ deleting one is a REDUCTION under the test below. This is what makes iteration five cheaper than
127
+ iteration one: the frame accumulates, and the share of decisions the model settles alone rises
128
+ with it.
129
+
105
130
  **Operational test for spec changes.** When implementation and spec disagree, classify the change
106
131
  before touching the spec. Do not classify by intent — classify by asking one question that can be
107
132
  checked against the file:
@@ -119,6 +144,12 @@ not convert a REDUCTION into a CORRECTION; the rationale goes into the question
119
144
  This is the mechanism by which specs shrink silently, so it is the one place the model must not
120
145
  be allowed to self-certify.
121
146
 
147
+ **Not to be confused with Planning Posture.** A Change's `## Planning Posture` uses
148
+ EXPAND / SELECTIVE / HOLD / REDUCE to say how much work to plan right now. That is a scope
149
+ decision about the future and is freely reversible. The classification above is about the past:
150
+ whether a promise already made still holds. `REDUCE` the plan is routine; a `REDUCTION` of
151
+ commitments stops the work and goes to the owner.
152
+
122
153
  > **Test**: any boundary-crossing action must trigger an owner question, an active task
123
154
  > `## Change Log` entry, or an accepted `.ultra/decisions/<id>.md`.
124
155
 
@@ -70,9 +70,12 @@ Every hook resolves the current project and checks for `.ultra/` before reading,
70
70
  writing, injecting context, or denying an effect. Without `.ultra/`, it emits no stdout,
71
71
  exits zero, and leaves the project untouched.
72
72
 
73
- The only hook denial is a named dangerous shell effect in an active Ultra project. The
74
- denial identifies the protected effect and supplies an exact-command SHA-256
75
- authorization path. All other hook failures are fail-open diagnostics.
73
+ The only hook denial is a named destructive shell effect in an active Ultra project.
74
+ The denial identifies the protected effect and supplies an exact-command SHA-256
75
+ authorization path. Additive protected-branch publication is an advisory observation
76
+ because a portable hook cannot consume every host's trusted owner-approval receipt;
77
+ history rewrites and branch deletion remain guarded. All other hook failures are
78
+ fail-open diagnostics.
76
79
 
77
80
  ## Project data boundary
78
81
 
@@ -60,7 +60,7 @@ host feature OpenCode does not expose.
60
60
  | mid-workflow recall | yes | handles `apply_patch` | native bridge | normalized | executed |
61
61
  | compact context | pre/start | pre/post | native compact event | pre/post | pre-compact |
62
62
  | post-edit observation | Write/Edit | Write/Edit/`apply_patch` | write/edit/patch | Write/Edit | Write/Edit/patch |
63
- | dangerous command guard | Bash | Bash | bash | Bash | Bash deny mapping |
63
+ | dangerous command guard | Bash advisory/deny | Bash advisory/deny | bash advisory/deny | Bash advisory/deny | Bash allow/deny mapping |
64
64
 
65
65
  All hooks share the same `.ultra/` idle guard. Wire adapters normalize payload and
66
66
  output fields only; they do not change semantic policy. Codex installation health and
@@ -99,3 +99,45 @@ inherits the same isolated `HOME` and `CODEX_HOME`. Grok native registration is
99
99
  only in explicit isolated test mode. Lifecycle tests also require the isolated config
100
100
  and fake HOME to contain zero children after uninstall, while separate ownership tests
101
101
  prove that pre-existing empty Codex and Kimi registries survive.
102
+
103
+ ## Authenticated cross-host continuation evidence
104
+
105
+ On 2026-08-03, one deliberately narrow task was run in an isolated Git repository to
106
+ test the product's central recovery claim with real authenticated models. This was a
107
+ continuation drill, not a full project workflow or a delegation receipt test.
108
+
109
+ Both hosts used globally installed artifacts from the same clean source candidate:
110
+
111
+ - source commit: `c473a248bde2afce77591f2ba382246d420b5c72`;
112
+ - worktree digest: `e83bdcbe2905cf02cb06cb0219127f6ce0400484331a6e9fc95622bbe24b44bd`;
113
+ - Doctor: Claude healthy with 83 assets and zero issues; Codex healthy with 99 assets
114
+ and zero issues, with optional hooks still `user_review_required`.
115
+
116
+ The seed repository had one passing smoke test, one pending task, one mapped Change
117
+ acceptance ID, and seed HEAD `cf27d62aeeae353e0650413de6d9a118b89a0406`.
118
+
119
+ | Phase | Execution boundary | Observed result |
120
+ |---|---|---|
121
+ | Claude | One print-mode call, `acceptEdits`, no persistent session, empty MCP config, explicit file and local-test tools, USD 1.00 cap | Exit 0 at USD 0.741367 with zero web requests. It changed only the task ledger/context, added `test/slug.test.cjs`, and allowed the hook to write disposable progress. `src/slug.cjs` and completion evidence remained untouched. The task test exited 1 with actual `hello,-world!` versus expected `hello-world`; both task surfaces read `in_progress`, and the Resume Note named the exact next action. |
122
+ | Codex | One ephemeral `workspace-write` call with sandbox network disabled and no conversation from Claude | Exit 0. It found and read the installed `ultra-dev` Skill, recovered the single task from Git and `.ultra/`, reproduced the same red, changed only the intended implementation, then passed the task test 1/1 and `npm test` 2/2. It wrote the three-command canonical evidence record and logs, completed both task status surfaces, and filled Completion and the final Resume Note. |
123
+ | Independent check | Local commands after both model calls | `node --test test/slug.test.cjs` passed 1/1, `npm test` passed 2/2, `git diff --check` passed, and a structural check confirmed one completed task, six evidence dimensions, three existing command-log references, and matching ledger/context state. |
124
+
125
+ The Codex process emitted non-blocking diagnostics for an older local model-cache
126
+ schema and an unauthenticated user-config MCP startup. No MCP result was consumed and
127
+ no external effect occurred; all task actions used local file and shell tools. This
128
+ means the drill proves file-backed Claude-to-Codex continuation even while Codex hooks
129
+ remain untrusted, but it does not claim that arbitrary user configuration starts
130
+ without unrelated host diagnostics.
131
+
132
+ The result establishes one real end-to-end example of cross-host resume without a
133
+ shared conversation, database, MCP authority, daemon, or generated prompt projection.
134
+ It does not establish output quality for every model, host, repository, or task shape.
135
+ Task-level `ultra-review` and commits were intentionally excluded from the drill so the
136
+ two host calls tested only the handoff boundary.
137
+
138
+ Two older `.ultra` evidence snapshots originally named the completed WIP by its former
139
+ path. The current-path artifact contract requires every reference to resolve in the
140
+ checkout, so those two references now point here while their 2026-08-01 status and
141
+ limitations remain explicitly historical. The original WIP is still recoverable from
142
+ their pinned source commit `3f99189bc68697262cd90444685ac2d4857139c4` with
143
+ `git show`; the live `docs/wip/` directory contains no unfinished document.
@@ -132,3 +132,7 @@ the common entry contract and compares the working tree with those claims.
132
132
 
133
133
  This path remains valid when hooks and `ubp` are disabled. A compact snapshot may be
134
134
  used as a hint, then checked against canonical files and Git.
135
+
136
+ An authenticated Claude-to-Codex continuation using this path passed on 2026-08-03;
137
+ the exact scope, provenance, results, and limitations are recorded in the
138
+ [runtime compatibility matrix](RUNTIME-COMPAT-MATRIX.md#authenticated-cross-host-continuation-evidence).
package/hooks/README.md CHANGED
@@ -9,7 +9,7 @@ directory and exits silently when the workflow is not active.
9
9
  | `mid_workflow_recall.py` | Before source reads or edits | Restate current acceptance only |
10
10
  | `compact_context.py` | Before/after compaction | Save and restore a disposable snapshot derived from files and Git |
11
11
  | `post_edit_guard.py` | After edits | Record mechanical evidence observations without deciding completion |
12
- | `block_dangerous_commands.py` | Before shell execution | Block a narrow named external effect until the exact command is owner-authorized |
12
+ | `block_dangerous_commands.py` | Before shell execution | Advise on additive protected-branch publication; block history rewrites and a narrow destructive set until the exact command is owner-authorized |
13
13
 
14
14
  `_common.py` is a shared library, not a lifecycle registration. Compact snapshots and
15
15
  progress observations live under ignored `.ultra/.runtime/` or `.ultra/progress/`; the
@@ -18,5 +18,7 @@ canonical semantic facts remain the owner-readable files and Git.
18
18
  Claude Code runs the hooks directly. Codex, Kimi Code, and Grok Build use the small
19
19
  wire adapters under `hooks/adapters/`. OpenCode calls the same scripts from its native
20
20
  JavaScript plugin. A hook failure is advisory except for the explicitly classified
21
- external effects in `block_dangerous_commands.py`, whose denial includes a reachable
22
- SHA-256-scoped authorization path.
21
+ destructive effects in `block_dangerous_commands.py`, whose denial includes a reachable
22
+ SHA-256-scoped authorization path. Additive protected-branch publication stays advisory
23
+ because portable hooks cannot consume every host's trusted owner-approval receipt;
24
+ history rewrites and branch deletion remain guarded.
@@ -7,11 +7,88 @@ import hashlib
7
7
  import json
8
8
  import os
9
9
  import re
10
+ import shlex
10
11
  import subprocess
11
12
 
12
13
  from _common import emit_context, project_root, read_payload
13
14
 
14
15
 
16
+ APPROVAL_VAR = "UBP_DANGEROUS_COMMAND_APPROVED"
17
+ APPROVAL_PREFIX = re.compile(rf"^\s*{APPROVAL_VAR}=\S*\s+")
18
+
19
+ HEREDOC = re.compile(
20
+ r"<<-?\s*(['\"]?)(?P<tag>[A-Za-z_]\w*)\1(?P<rest>[^\n]*)\n(?P<body>.*?)^\s*(?P=tag)\s*$",
21
+ re.DOTALL | re.MULTILINE,
22
+ )
23
+ # Commands whose heredoc is a payload rather than a script. Deliberately short: an
24
+ # unlisted sink keeps its body in scope, so the cost of an omission is a false positive,
25
+ # never a missed effect.
26
+ DATA_SINK = re.compile(r"\bgit\s+(?:commit|tag|notes)\b", re.IGNORECASE)
27
+ SEARCH_COMMANDS = {"egrep", "fgrep", "grep", "rg"}
28
+ SHELL_CONTROL = {";", ";;", "&", "&&", "|", "||", "(", ")", "\n"}
29
+
30
+
31
+ def strip_data_heredocs(command: str) -> str:
32
+ """Drop heredoc bodies that are data for a known sink.
33
+
34
+ Describing an effect inside a commit message is not performing it, and a message
35
+ that mentions one used to be blocked as though it did.
36
+
37
+ Bodies are kept whenever they might be executed: an interpreter reading a heredoc
38
+ runs it, so stripping there would make quoting style a way around this gate. The
39
+ body survives unless the delimiter is quoted, the segment introducing it is a
40
+ listed data sink, and nothing on the heredoc's own line pipes it somewhere else.
41
+ An unquoted delimiter performs parameter, command and arithmetic expansion before
42
+ the sink receives the data, so its body remains executable shell input.
43
+ """
44
+ def resolve(match: re.Match) -> str:
45
+ segment = re.split(r"\|\||&&|[;&|\n]", command[:match.start()])[-1]
46
+ if (
47
+ not match.group(1)
48
+ or "|" in match.group("rest")
49
+ or not DATA_SINK.search(segment)
50
+ ):
51
+ return match.group(0)
52
+ tag = match.group("tag")
53
+ return f"<<{tag}\n{tag}"
54
+
55
+ return HEREDOC.sub(resolve, command)
56
+
57
+
58
+ def effect_of(command: str) -> str:
59
+ """The command reduced to what it actually does.
60
+
61
+ Strips an inline approval assignment, then any heredoc body that is data for a
62
+ known sink. What remains is the effect: the part worth classifying and hashing.
63
+
64
+ The digest must identify the *effect*, not the spelling of the retry. Without this,
65
+ prepending the variable changes the command text, which changes the digest, which
66
+ means the quoted digest is never the one that matches -- the repair path is a loop.
67
+
68
+ The assigned value is discarded on purpose. Only the process environment authorizes,
69
+ because the command string is composed by the model and honouring an inline value
70
+ would let it approve itself.
71
+ """
72
+ return strip_data_heredocs(APPROVAL_PREFIX.sub("", command, count=1))
73
+
74
+
75
+ def is_inert_search(command: str) -> bool:
76
+ """Return true when a search query is data, not executable shell input."""
77
+ if any(marker in command for marker in ("$(", "<(", ">(", "`")):
78
+ return False
79
+ try:
80
+ lexer = shlex.shlex(command, posix=True, punctuation_chars=";&|()\n")
81
+ lexer.whitespace = " \t\r"
82
+ lexer.whitespace_split = True
83
+ lexer.commenters = ""
84
+ tokens = list(lexer)
85
+ except ValueError:
86
+ return False
87
+ if not tokens or any(token in SHELL_CONTROL for token in tokens):
88
+ return False
89
+ return os.path.basename(tokens[0]) in SEARCH_COMMANDS
90
+
91
+
15
92
  def protected_push(command: str, root) -> bool:
16
93
  if not re.search(r"\bgit\s+push\b", command, re.IGNORECASE):
17
94
  return False
@@ -24,9 +101,26 @@ def protected_push(command: str, root) -> bool:
24
101
  return result.stdout.strip() in {"main", "master", "production", "prod"}
25
102
 
26
103
 
104
+ def destructive_protected_push(command: str, root) -> bool:
105
+ """Detect history rewrites or deletion, not ordinary additive publication."""
106
+ if not protected_push(command, root):
107
+ return False
108
+ match = re.search(r"\bgit\s+push\b", command, re.IGNORECASE)
109
+ if match is None:
110
+ return False
111
+ segment = re.split(r"\|\||&&|[;&|\n]", command[match.end():], maxsplit=1)[0]
112
+ force_or_delete = re.search(
113
+ r"(?:^|\s)(?:-f|--force(?:-with-lease(?:=\S+)?|-if-includes)?|--delete)(?=\s|$)",
114
+ segment,
115
+ re.IGNORECASE,
116
+ )
117
+ forced_refspec = re.search(r"(?:^|\s)(?:\+\S+|:\S+)(?=\s|$)", segment)
118
+ return force_or_delete is not None or forced_refspec is not None
119
+
120
+
27
121
  def classify(command: str, root) -> str | None:
28
- if protected_push(command, root):
29
- return "protected branch push"
122
+ if destructive_protected_push(command, root):
123
+ return "protected branch history rewrite or deletion"
30
124
  if re.search(r"\b(?:drop\s+(?:table|database|schema)|truncate\s+(?:table\s+)?)\b", command, re.IGNORECASE):
31
125
  return "destructive database operation"
32
126
  if re.search(r"\b(?:cast\s+send|solana\s+transfer|bitcoin-cli\s+send|near\s+send|aptos\s+move\s+run)\b", command, re.IGNORECASE):
@@ -42,7 +136,14 @@ def classify(command: str, root) -> str | None:
42
136
  return None
43
137
 
44
138
 
45
- def advisory(command: str) -> str | None:
139
+ def advisory(command: str, root) -> str | None:
140
+ if protected_push(command, root):
141
+ return (
142
+ "Ultra observed a protected branch push. This portable hook cannot receive "
143
+ "the host's trusted owner-approval receipt, so it does not replace the host's "
144
+ "native authority flow. Proceed only after explicit owner authorization and "
145
+ "verify the remote result."
146
+ )
46
147
  if re.search(
47
148
  r"\b(?:alembic\s+upgrade|prisma\s+migrate|knex\s+migrate|sequelize\s+db:migrate|rails\s+db:migrate|manage\.py\s+migrate)\b",
48
149
  command,
@@ -65,19 +166,24 @@ def main() -> int:
65
166
  command = tool_input.get("command")
66
167
  if not isinstance(command, str) or not command.strip():
67
168
  return 0
68
- threat = classify(command, root)
169
+ effect = effect_of(command)
170
+ if is_inert_search(effect):
171
+ return 0
172
+ threat = classify(effect, root)
69
173
  if threat is None:
70
- note = advisory(command)
174
+ note = advisory(effect, root)
71
175
  if note:
72
176
  emit_context("PreToolUse", note)
73
177
  return 0
74
- digest = hashlib.sha256(command.encode()).hexdigest()
75
- if os.environ.get("UBP_DANGEROUS_COMMAND_APPROVED") == digest:
178
+ digest = hashlib.sha256(effect.encode()).hexdigest()
179
+ if os.environ.get(APPROVAL_VAR) == digest:
76
180
  return 0
77
181
  reason = (
78
- f"Ultra blocked {threat}. Protected effect: the exact shell command. "
79
- f"After explicit owner authorization, rerun with "
80
- f"UBP_DANGEROUS_COMMAND_APPROVED={digest}."
182
+ f"Ultra blocked {threat}. Protected effect: the exact shell command, "
183
+ f"digest {digest}. Authorization comes from the owner's environment, never from "
184
+ f"the command text. Either run it yourself, or export "
185
+ f"{APPROVAL_VAR}={digest} in the environment that launches the agent and ask "
186
+ f"again. Prefixing the assignment onto this command does not authorize it."
81
187
  )
82
188
  print(json.dumps({
83
189
  "decision": "block",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra-builder-pro-cli",
3
- "version": "0.26.0",
3
+ "version": "0.26.1",
4
4
  "description": "File-first Ultra Builder Pro workflows for Claude Code, OpenCode, Codex CLI, Kimi Code, and Grok Build.",
5
5
  "bin": {
6
6
  "ultra-builder-pro-cli": "bin/install.js",
@@ -16,6 +16,10 @@ disagreement instead of resolving it.
16
16
  2. Read `CONTEXT.md` for vocabulary and `.ultra/decisions/` for entries in scope.
17
17
  3. Read `.ultra/north-star.md` — a Change that does not serve it is worth
18
18
  questioning out loud before it is written down.
19
+ 4. Grep `.ultra/changes/archive/` for Changes that already touched this area. Read
20
+ `delivery.md` when present; otherwise read `archive-summary.md`, `verification.md` and
21
+ remaining owner-readable Markdown. A missing `delivery.md` does not mean no history;
22
+ a module that keeps reappearing is an architecture signal.
19
23
 
20
24
  ## Whether this skill applies at all
21
25
 
@@ -59,8 +63,14 @@ Sort what you find into three buckets, each with your recommended disposition:
59
63
  | Code implements it, no specification says so | Built and unpromised |
60
64
  | Both speak and they conflict | Contradictory |
61
65
 
62
- Take the buckets to the owner. This is confirmed rather than assumed, because a
63
- disposition can quietly be a REDUCTION.
66
+ Take the buckets to the owner — confirmed rather than assumed, because a
67
+ disposition can quietly withdraw a commitment. Sort by risk first and match the
68
+ asking to it: anything that touches a `HC-<n>` hard constraint or removes
69
+ something already promised goes one at a time with your recommendation; the
70
+ remaining low-risk rows go as a single list with a recommended disposition each,
71
+ for one confirmation. Twenty separate questions about rows that change nothing
72
+ spends the owner's attention where it was not needed, and trains them to approve
73
+ without reading.
64
74
 
65
75
  ## Write the intent
66
76
 
@@ -86,16 +96,24 @@ reconciliation justified, possibly a `.ultra/decisions/<id>.md`, and any
86
96
  vocabulary update. Recommend `ultra-research` for a real evidence gap or
87
97
  `ultra-plan` once the contract is evidenced enough; do not invoke either.
88
98
 
99
+ ## Abandoning a Change
100
+
101
+ A Change that turns out to be the wrong thing to build exits by
102
+ `git mv .ultra/changes/active/<id> .ultra/changes/abandoned/<id>`, plus one line in
103
+ its `intent.md` saying why. Completed tasks keep their evidence; the work is
104
+ unlinked from the frontier, not deleted. This is the owner's call and a cheap one —
105
+ a Change kept alive to avoid admitting it was wrong poisons every later
106
+ reconciliation, which then measures the code against promises nobody intends to keep.
107
+
89
108
  ## When the owner decides
90
109
 
91
- Every non-empty bucket, the Change boundary, and anything reconciliation turns up
92
- that shrinks a prior commitment. A change that makes no specification sentence
93
- false needs no permission at all.
110
+ Every non-empty bucket, the Change boundary, abandonment, and anything
111
+ reconciliation turns up that shrinks a prior commitment. A change that makes no
112
+ specification sentence false needs no permission at all.
94
113
 
95
114
  ## References
96
115
 
97
116
  - `../ultra-grilling/SKILL.md` — the loop for settling the boundary.
98
117
  - `references/change-contract.md` — exact `intent.md` structure and profile fields.
99
- - `../ultra-think/references/autonomy-boundary.md` — read before dispositioning a
100
- bucket in a way that removes something already promised.
118
+ - `../ultra-think/references/autonomy-boundary.md` — read before removing a promise.
101
119
  - `../ultra-tdd/SKILL.md` — read for an evidenced one-slice correction.
@@ -31,6 +31,10 @@ stable enough to survive a fresh session.
31
31
 
32
32
  ## Planning Posture
33
33
  <EXPAND | SELECTIVE | HOLD | REDUCE, rationale, and owner confirmation>
34
+ <!-- Scope for this Change only. Not the commitment classification: REDUCE here
35
+ means "plan less work now", while a REDUCTION in a Change Log means "a
36
+ promise the specification already made no longer holds". -->
37
+
34
38
 
35
39
  ## Recovery
36
40
  <reversal or recovery path and its verification>
@@ -45,8 +49,10 @@ heading, and do not add another file that restates the same contract.
45
49
  ## Contract fields
46
50
 
47
51
  - `outcome`: one externally observable result.
48
- - `acceptance`: stable ids with a criterion and exact verification for each accepted
49
- behavior.
52
+ - `acceptance`: stable ids, one criterion each, and a `Verification` that is an
53
+ **executable command** — something with an exit code, not a description of an
54
+ intention. `curl -sf localhost:3000/health` qualifies; "check that the endpoint
55
+ responds" does not.
50
56
  - `non_goals`: explicit boundaries that prevent accidental scope growth.
51
57
  - `public_seams`: entry points or outputs through which acceptance is observed.
52
58
  - `recovery`: a bounded reversal or recovery strategy and its verification.
@@ -55,6 +61,26 @@ heading, and do not add another file that restates the same contract.
55
61
  Resolve every blocking decision before planning. Keep non-blocking uncertainty visible
56
62
  in the task and verification contracts.
57
63
 
64
+ ## Why verification has to be executable
65
+
66
+ A criterion whose verification cannot be run is not acceptance, it is a wish. Three
67
+ consumers downstream depend on it being runnable:
68
+
69
+ - task development can treat passing acceptance as an exit condition only when
70
+ something can actually decide "passing";
71
+ - the review tests lens maps each acceptance claim to executable evidence, and can
72
+ only report an honest gap when there is something to map onto;
73
+ - delivery cannot otherwise separate "shipped" from "believed shipped".
74
+
75
+ Writing the command is also the cheapest way to find out that a criterion is vague.
76
+ "The feature is wired up" resists a command until it becomes "this export has a
77
+ non-test importer", which is a `grep`.
78
+
79
+ Where a criterion is genuinely a judgement call — visual polish, wording, feel — it
80
+ does not belong in Acceptance at all. Record it as a taste decision for the owner and
81
+ keep Acceptance to what a machine can settle. Mixing the two is what makes an
82
+ acceptance set undecidable.
83
+
58
84
  ## Profile
59
85
 
60
86
  - `quick`: one low-risk task, no material risk flags, and no research dependency.
@@ -21,6 +21,8 @@ semantic synthesis and remains the only writer of `.ultra/tasks.json`.
21
21
  spec digests remain unchanged.
22
22
  - The worker terminates as `finished`, `blocked` or `failed` in a schema-valid
23
23
  `result.json`; declared paths equal the actual Git diff and stay in writable roots.
24
+ - No `writable_roots` entry is `.ultra/` or sits under it, and the actual diff touches
25
+ nothing there.
24
26
  - Timeout and cancellation reach a terminal failed result and release `run.lock`.
25
27
  - The primary host verifies the result against the delegated workflow's acceptance.
26
28
 
@@ -34,6 +36,11 @@ Write under `.ultra/.runtime/delegations/<id>/`:
34
36
 
35
37
  `writable_roots` is an array of existing worktree-relative directories. An empty array
36
38
  selects native read-only mode; one or more roots selects bounded write mode.
39
+ **Neither `.ultra/` nor any path under it may appear in it.** The worktree carries a
40
+ checkout of `.ultra` because Git tracks it, but the project's memory has exactly one
41
+ writer. Two workers appending to `tasks.json`, `progress/` or `evidence/` in separate
42
+ worktrees collide in the very files every later session reads to resume. Worker findings
43
+ and state changes travel back in `result.json` and are applied by the primary host.
37
44
  `external_effects` must be empty in this portable launcher: commit, push, publish,
38
45
  deployment and other effects stay with the primary host. There is no `readable_roots`
39
46
  claim because the five host sandboxes do not share one mechanically enforceable read
@@ -12,8 +12,16 @@
12
12
 
13
13
  Fields are exact. Roots are normalized worktree-relative directories that already
14
14
  exist. An empty array selects native read-only mode; `.` deliberately grants the whole
15
- isolated checkout. Absolute paths, symlink escapes, unknown fields, `readable_roots`,
16
- and non-empty external effects are rejected.
15
+ isolated checkout except `.ultra/`. Absolute paths, symlink escapes, unknown fields,
16
+ `readable_roots`, `.ultra` or any path beneath it, and non-empty external effects are
17
+ rejected.
18
+
19
+ `.ultra/` is never writable by a worker under any root, `.` included. The project's
20
+ memory has exactly one writer — the primary host — because two worktrees appending to
21
+ `tasks.json`, `progress/` or `evidence/` collide in precisely the files a later session
22
+ reads to resume. Enforced twice: the launcher rejects a `.ultra` root before the
23
+ delegation starts, and the worker fails an actual diff that touches it with
24
+ `unauthorized_write`.
17
25
 
18
26
  ## Model final response
19
27
 
@@ -18,7 +18,9 @@ separate effects; local readiness grants none of them automatically.
18
18
 
19
19
  ## Definition of done
20
20
 
21
- - Actual outcome, specifications, docs, tests and aggregate review have been reconciled.
21
+ - Actual outcome, specifications, docs, tests and aggregate review have been reconciled,
22
+ with every documentation file checked against the actual diff rather than from memory.
23
+ - Every changed export has a non-test consumer or a recorded owner disposition.
22
24
  - Every omitted stage is explicitly recorded with its evidence-based rationale.
23
25
  - Build and release checks have exact commands and real results.
24
26
  - The Change is archived with `git mv`, and rollback remains a normal Git operation.
@@ -34,13 +36,48 @@ against the resulting HEAD.
34
36
 
35
37
  1. Compare accepted intent and task traces with delivered behavior.
36
38
  2. Apply justified specification corrections; route any REDUCTION to the owner.
37
- 3. Update CHANGELOG and README when public behavior changed. Record bounded debt only
38
- in the active Change `delivery.md` under `## Technical Debt`.
39
+ 3. Reconcile every document against the actual diff, not only the ones you remember
40
+ touching. List the repository's documentation files, read the Change's full diff, and
41
+ repair each place a document now describes behavior that no longer exists. CHANGELOG
42
+ and README are the usual ones, but a stale example in a guide, a renamed flag in a
43
+ runbook and an obsolete field in an API note drift the same way and are found the
44
+ same way. Record bounded debt only in the active Change `delivery.md` under
45
+ `## Technical Debt`.
39
46
  4. Run the repository's build and packaging checks.
40
47
  5. Determine the version impact from the public contract and project convention.
41
48
  6. Follow `references/baseline-reconciliation.md` and write the one canonical active
42
49
  Change `delivery.md` with exact checks, residual risks, omissions and rollback.
43
50
 
51
+ Step 3 is the **exit** reconciliation and does not duplicate `ultra-change`'s entry
52
+ reconciliation. Entry asks whether the specifications already disagreed with the code
53
+ before this Change was written; exit asks which documents this Change just made wrong.
54
+ One keeps the intent from inheriting drift, the other keeps the Change from leaving new
55
+ drift behind.
56
+
57
+ ## The one delivery gate
58
+
59
+ This gate applies only after the owner explicitly invokes `ultra-deliver`; it does not
60
+ attach itself to ordinary repository packaging outside this workflow.
61
+
62
+ Everything else in this workflow reports and hands over. This is the exception, and it
63
+ is deliberately narrow:
64
+
65
+ **Do not package while an export this Change added or changed has no non-test consumer
66
+ and no recorded owner disposition.**
67
+
68
+ | | |
69
+ |---|---|
70
+ | Invariant | Every changed export is either reachable from a non-test caller or explicitly declared internal |
71
+ | Fact source | The Wiring Verification results in `.ultra/test-report.json`, bound to the current HEAD — the audit already found these; this is not a fresh opinion |
72
+ | Blocked effect | Packaging and the release effects after it. Local commit and archival are untouched |
73
+ | Repair | Wire the export to its caller, or record the owner's disposition in `delivery.md` naming it internal-only and why. Either one clears the gate |
74
+
75
+ "Written but never connected" is the single defect that passes every other check: tests
76
+ green, review clean, evidence complete, feature absent. An advisory here would be
77
+ ignored exactly when it matters, because everything else already reads as done. The gate
78
+ sits at the last step rather than at edit time on purpose — a gate early in the loop
79
+ gets escaped by damaging the work.
80
+
44
81
  ## Archive locally
45
82
 
46
83
  Move `.ultra/changes/active/<id>` to `.ultra/changes/archive/<id>` with `git mv` only
@@ -42,16 +42,37 @@ Follow `../ultra-tdd/SKILL.md`, writing tests only on the seams the plan already
42
42
  confirmed, one slice at a time. Do no refactoring here — `ultra-review` owns it,
43
43
  because what is worth restructuring only becomes visible after several slices.
44
44
 
45
+ ## Converge on the task-scoped acceptance set
46
+
47
+ Development ends when the task's acceptance commands pass, not when the code looks
48
+ finished. Resolve the context's **Change Acceptance IDs** against the active `intent.md`
49
+ and use each named row's executable `Verification`. Commands under the context's
50
+ Acceptance Criteria may add task-local checks; they do not replace the Change mapping.
51
+
52
+ Run one baseline before repairing anything. Track the best-ever passing set of mapped
53
+ IDs and task-local commands, the total repair rounds, and consecutive rounds that add
54
+ nothing to that set. The loop has three explicit exits:
55
+
56
+ | Exit | Condition | Action |
57
+ |---|---|---|
58
+ | Converged | Every mapped Change verification and task-local command exits zero | Close out below |
59
+ | Stalled | Two consecutive repair rounds add no new item to the best-ever passing set, or three repair rounds finish without convergence | Stop; report the remaining criteria and what each repair tried |
60
+ | Unreachable | A command did not start because its executable, fixture, service or declared environment prerequisite is unavailable | Stop that criterion; report the missing prerequisite and its repair path |
61
+
62
+ A nonzero exit normally means a failing criterion, not an unreachable one; inspect its
63
+ output. A regression never shrinks the best-ever set, and the total round limit stops
64
+ oscillation where different criteria alternate between pass and fail.
65
+
45
66
  ## Answer the six evidence dimensions
46
67
 
47
68
  Each has a rule you can check against the work rather than assert:
48
69
 
49
70
  | Dimension | Answered when |
50
71
  |---|---|
51
- | `tests_written` | This diff changes a test file |
72
+ | `tests_written` | This diff adds or changes at least one assertion — touching a test file is not enough |
52
73
  | `tests_passed` | The last test run exited zero and covered the files this diff touched |
53
74
  | `persistence_real` | On any path that stores data, the test uses real storage or a container |
54
- | `feature_flags_audit` | No flag this task added defaults to off |
75
+ | `feature_flags_audit` | No flag on this change's execution path defaults to off, including flags this task did not add but now depends on |
55
76
  | `vertical_slice` | One test's execution path runs from the entry point through to persistence |
56
77
  | `spec_trace` | The anchor the task's `trace_to` names exists in the specification |
57
78
 
@@ -92,9 +113,7 @@ as it stands and says so in the Resume Note instead of tidying it away.
92
113
 
93
114
  ## References
94
115
 
95
- - `../ultra-tdd/SKILL.md` — read before the first test, for the seam and the mock
96
- boundary.
97
- - `../ultra-think/references/autonomy-boundary.md` — read the moment the
98
- specification and the implementation disagree.
116
+ - `../ultra-tdd/SKILL.md` — read before the first test, for the seam and mock boundary.
117
+ - `../ultra-think/references/autonomy-boundary.md` — read when spec and code disagree.
99
118
  - `../ultra-review/SKILL.md` — read when implementation evidence is ready for review.
100
119
  - `references/debugging.md` — read for a reproduced failure before selecting a repair.
@@ -22,18 +22,37 @@ have answered by reading the repository spends the one input only they can give.
22
22
 
23
23
  ## Definition of done
24
24
 
25
- - Every branch reachable from the original ask is resolved, or deferred with the
26
- reason recorded.
27
- - Each resolved branch carries the owner's confirmation in their own words,
28
- rather than something that sounded like agreement.
25
+ - Every field the caller named as required is either answered in the owner's own
26
+ words or explicitly deferred with the reason recorded. "Every branch of the
27
+ tree" is not something you can check against a file; the caller's field list is.
28
+ - The reframe below was put to the owner and either confirmed or corrected. It is
29
+ never skipped because the original wording sounded clear.
30
+ - Each answer carries the owner's confirmation, rather than something that
31
+ sounded like agreement.
29
32
  - The calling skill holds a decision list it can write to a file: one line per
30
33
  decision, each with the options rejected and why.
31
34
 
32
- ## Extract first, then ask
35
+ ## Extract, reframe, then ask
33
36
 
34
- Restate what the owner already told you and ask them to confirm the restatement.
35
- Ask only about what is genuinely missing. Re-asking something they answered
36
- teaches them that answering does not stick.
37
+ **Extract.** Restate what the owner already told you and ask them to confirm the
38
+ restatement. Ask only about what is genuinely missing. Re-asking something they
39
+ answered teaches them that answering does not stick.
40
+
41
+ **Reframe.** The owner describes a *form* when they want a *role*. Say what you
42
+ believe they actually want, name the gap against their wording, and end on a
43
+ question:
44
+
45
+ > You said a daily briefing app. What you described is a personal chief of staff
46
+ > — the briefing is one of its outputs, not the thing itself. Have I got that
47
+ > right?
48
+
49
+ This is what separates the loop from a questionnaire. A form accepted at face
50
+ value yields a specification that is internally consistent and builds the wrong
51
+ product. Offer the reframe, never assert it, and treat a correction as the better
52
+ outcome — "no, it really is just the briefing" is a constraint you did not have a
53
+ minute ago. Read `references/reframing.md` for when it applies and how it fails.
54
+
55
+ **Then ask** only what the confirmed reframe leaves open.
37
56
 
38
57
  ## One question per turn
39
58
 
@@ -44,14 +63,27 @@ the load-bearing one skipped.
44
63
  Every question carries your recommended answer and what would change it. A
45
64
  question without a recommendation hands your own work back to the owner.
46
65
 
47
- Order questions so that answering one unlocks the next, and prefer the ones whose
48
- answer changes what gets built:
66
+ Order questions so that answering one unlocks the next. Ask the framing questions
67
+ before the detail questions — a detail settled inside the wrong frame has to be
68
+ asked again.
69
+
70
+ **Framing questions** come first — at most five, from the table in
71
+ `references/reframing.md`. They refuse the owner's framing on purpose, which is
72
+ how they surface what the request left out. Stop early once the frame stops
73
+ moving.
74
+
75
+ **Detail questions** fill in the confirmed frame:
49
76
 
50
77
  - What happens when this goes wrong, and how would we find out?
51
78
  - Which of these options is cheaper to reverse?
52
79
  - What are we deliberately not doing?
53
80
  - What does done look like, concretely enough to check?
54
81
 
82
+ An answer counts once it is specific enough to **rule something out**. "It should
83
+ be fast" rules nothing out; "the list renders before they finish typing" does.
84
+ When an answer rules nothing out, ask what would have to be true for the owner to
85
+ *reject* a candidate solution.
86
+
55
87
  Skip what you can derive, what does not change the work, and what they already
56
88
  settled.
57
89
 
@@ -61,19 +93,26 @@ This whole skill is owner-facing, so the boundary is narrow: hold the loop open
61
93
  until each decision is confirmed explicitly, and start no implementation on an
62
94
  inferred consensus.
63
95
 
64
- Where the host cannot present a structured question, ask one plain question per
65
- turn instead. Where it cannot ask at all, stop at the last confirmed decision,
66
- record what remains open, and hand that back to the caller — neither guessing an
67
- answer nor blocking the owner's next move.
96
+ ## How the loop ends
97
+
98
+ Every ending hands back the same artifact: the decisions written one per line,
99
+ each with the options rejected and the reason they lost. The caller chooses where
100
+ it lands — `north-star.md`, `discovery.md`, a Change intent, or a decision file.
68
101
 
69
- ## Hand back a list
102
+ - **Resolved** — every required field answered or explicitly deferred.
103
+ - **Stalled** — two consecutive turns add no newly confirmed decision. Hand back
104
+ the open fields and what each one blocks.
105
+ - **Unavailable** — the host cannot ask, or the owner stopped answering. Hand back
106
+ the last confirmed state, every unanswered field named.
70
107
 
71
- Once the tree is walked, write the decisions out one per line, each with the
72
- rejected options and the reason they lost. That list is the record. The caller
73
- chooses where it lands: `north-star.md`, `discovery.md`, a Change intent, or a
74
- decision file.
108
+ A loop with no exit traps the owner as much as you: re-asking what they cannot
109
+ currently answer costs more than deferring it. Deferral is a recorded outcome,
110
+ not a failure. Where the host has no structured question surface, ask one plain
111
+ question per turn instead.
75
112
 
76
113
  ## References
77
114
 
115
+ - `references/reframing.md` — read before the first question: when to reframe,
116
+ how it fails, the five framing questions, and what counts as an answer.
78
117
  - `../ultra-think/references/autonomy-boundary.md` — read when an answer removes
79
118
  something the specification already promised, or otherwise crosses C5.
@@ -0,0 +1,72 @@
1
+ # Reframing: recovering the role from the form
2
+
3
+ An owner fluent in their own domain still describes a **form** when what they
4
+ want is a **role**. They are not being vague; they are naming the nearest thing
5
+ they can picture. Taken at face value, that name produces a specification that is
6
+ internally consistent and builds the wrong product.
7
+
8
+ ## When to reframe
9
+
10
+ Most first asks qualify. Any one of these is enough:
11
+
12
+ - the request names an artifact — an app, a dashboard, a bot, a script — rather
13
+ than an outcome;
14
+ - the request names a solution whose problem has not been stated;
15
+ - the request would be satisfied by something far smaller, or is impossible as
16
+ worded;
17
+ - two readings of the request would lead to materially different builds;
18
+ - the request describes what a competitor has, rather than what the owner needs.
19
+
20
+ ## How to put it
21
+
22
+ State what you believe they want, name the gap against the words they used, and
23
+ end on a question:
24
+
25
+ > You said a daily briefing app. What you described is a personal chief of staff
26
+ > — the briefing is one of its outputs, not the thing itself. Have I got that
27
+ > right?
28
+
29
+ Rules that keep this honest:
30
+
31
+ - **One reframe at a time.** Two competing reinterpretations in one turn is a
32
+ quiz, and the owner will pick the one that sounds more flattering.
33
+ - **Offer it, never assert it.** A reframe is a hypothesis about their intent,
34
+ and they are the only authority on it.
35
+ - **A correction is the better outcome.** "No, it really is just the briefing" is
36
+ a constraint you did not have a minute ago. Record it as a decision.
37
+ - **Never reframe toward what is easier to build.** If your reinterpretation
38
+ happens to shrink the work, say that out loud and let the owner weigh it.
39
+ - **Stop after two rejected reframes.** A third is no longer listening. Take the
40
+ owner's framing as given and move to the framing questions below.
41
+
42
+ ## Framing questions
43
+
44
+ These refuse the owner's framing on purpose, which is how they surface what the
45
+ request left out. Ask at most five, and stop early once the frame stops moving.
46
+
47
+ | Question | What it recovers |
48
+ |---|---|
49
+ | What do you do today, without this? | The real job, with the proposed form stripped off |
50
+ | What would make you say this was a waste of effort? | Success via its negative — people picture failure far more concretely than they picture success |
51
+ | What must never happen, even if it would work better? | The hard constraints they were never going to volunteer |
52
+ | If it could only do one thing and you would still use it, which? | Core against periphery, before scope has a chance to spread |
53
+ | Whose situation gets worse when this exists? | Stakeholders, and the constraints attached to them |
54
+
55
+ Two of these are worth their place for a specific reason. Asking for success
56
+ directly gets an aspiration; asking what would make it a waste gets a threshold.
57
+ Asking who is made worse off surfaces the constraints an owner never thinks to
58
+ state, because to them they are simply how the world is.
59
+
60
+ ## What counts as an answer
61
+
62
+ An answer counts once it is specific enough to **rule something out**.
63
+
64
+ | Not yet an answer | An answer |
65
+ |---|---|
66
+ | "It should be fast" | "The list renders before they finish typing" |
67
+ | "It should be secure" | "A support agent must never see a full card number" |
68
+ | "Keep costs reasonable" | "Under fifty dollars a month in API spend" |
69
+
70
+ When an answer rules nothing out, do not re-ask the same question louder. Ask
71
+ what would have to be true for the owner to **reject** a candidate solution — the
72
+ rejection criterion is the constraint, stated from the other side.
@@ -26,6 +26,11 @@ or refute. A technology choice is a claim, so it is not initialization's busines
26
26
  ## Definition of done
27
27
 
28
28
  - `.ultra/north-star.md` carries the owner's one-line goal in their own words.
29
+ - Every `## Hard Constraints` entry has a stable `HC-<n>` id and can veto a
30
+ concrete technical decision. "Should be usable" vetoes nothing and is not yet a
31
+ constraint; "first paint under two seconds" is. Same bar for the success
32
+ metric: a threshold, not an aspiration. See
33
+ `../ultra-grilling/references/reframing.md` for what counts as an answer.
29
34
  - Every file listed under **What gets written** exists; you read each one back
30
35
  after writing and stopped at the first one missing.
31
36
  - `.git` exists. Any initial commit is a separately authorized delivery effect.
@@ -56,7 +61,7 @@ genuinely missing. Five things come out of it:
56
61
  |---|---|
57
62
  | What you want to build, in one sentence, their words | `north-star.md`, `## One-line` |
58
63
  | What counts as success | `specs/product.md`, behavioral requirements and acceptance |
59
- | What must never happen | `north-star.md`, `## Hard Constraints` |
64
+ | What must never happen | `north-star.md`, `## Hard Constraints`, one `HC-<n>` per line |
60
65
  | Who uses it, and how they cope today | `discovery.md`, marked as the owner's statement |
61
66
  | **What you already know you are unsure about** | `discovery.md` — research's priority queue |
62
67
 
@@ -76,7 +81,7 @@ placeholders from verified facts and owner decisions.
76
81
  .ultra/specs/{product,architecture,discovery}.md ## Observed, ## Decisions, ## Unknowns
77
82
  .ultra/tasks.json {"tasks": []}
78
83
  .ultra/test-report.json not-yet-run report with git_commit
79
- .ultra/contexts/ .ultra/decisions/ .ultra/evidence/
84
+ .ultra/{contexts,decisions,evidence,research}/ and changes/{active,archive,abandoned}/
80
85
  CONTEXT.md repository root, once a term is settled
81
86
  ```
82
87
 
@@ -99,8 +104,11 @@ authority that already exists and looks healthy is the owner's call, never a
99
104
  convenience on the way to a clean start — preserve it and report what you found.
100
105
 
101
106
  Where the host cannot ask at all, leave the one-line goal empty and say plainly
102
- that it is empty, that goal injection will therefore be empty, and that the next
103
- skill asks again. Do not infer it from the directory name.
107
+ that it is empty. Do not infer it from the directory name. An empty one-line goal
108
+ is a **blocking** state, not a note: goal injection, boundary read-back and a
109
+ Change's touched-constraints field all read from it, so planning and development
110
+ must not start until it is filled. Say that too, and recommend rerunning this
111
+ skill on a host that can ask.
104
112
 
105
113
  ## References
106
114
 
@@ -23,6 +23,10 @@ sessions and hosts; no runtime state decides whether the plan is meaningful.
23
23
  - The owner confirmed the scope posture and the seam list.
24
24
  - Every feature task is a tracer bullet touching at least two layers, unless the
25
25
  Change itself demonstrably touches only one layer.
26
+ - Every context names the north-star hard constraints its work could violate, and
27
+ each `HC-<n>` it names resolves to a real entry in `.ultra/north-star.md`.
28
+ - Every Change acceptance id maps to at least one task context, and every context id
29
+ resolves back to the active Change.
26
30
  - Coverage, dependency, trace, scope and context-budget checks have concrete results.
27
31
 
28
32
  ## Fix the planning posture
@@ -64,18 +68,27 @@ Each task records `id`, `title`, `type`, `priority`, `complexity`, `status`,
64
68
  splitting and context signal: 1–2 is local, 3–5 is bounded, 6–7 crosses a boundary,
65
69
  and complexity > 7 must split.
66
70
 
67
- Each context records Acceptance, confirmed seams, layers touched, target files,
68
- Implementation, verification commands, Definition of Drift, Change Log, Completion
69
- and Resume Note. Read every file back; task count and context count must match.
71
+ Each context records Change Acceptance IDs, task-local Acceptance, confirmed seams,
72
+ layers touched, target files, Implementation, verification commands, the hard
73
+ constraints the work could violate, Definition of Drift, Change Log, Completion and
74
+ Resume Note. Read every file back; task count and context count must match.
75
+
76
+ Name a hard constraint whenever the task could plausibly breach it, not only when you
77
+ expect it to. The entry costs one line and is what makes a later breach a checkable
78
+ question instead of a judgement call; `none` is a legitimate answer for a task that
79
+ touches nothing constrained.
70
80
 
71
81
  ## Verify the plan
72
82
 
73
- 1. Map every in-scope user story or acceptance anchor to at least one task.
83
+ 1. Map every Change acceptance ID to at least one task context and reject dangling
84
+ context IDs. Map every in-scope user story or specification anchor to at least one task.
74
85
  2. Walk dependencies and repair every cycle with the exact cycle chain visible.
75
86
  3. Resolve every `trace_to` path and heading against a real file.
76
- 4. Surface complexity, more than eight target files, or more than twenty tasks as
87
+ 4. Resolve every `HC-<n>` against `.ultra/north-star.md`. A dangling constraint id is
88
+ the same defect as a dangling `trace_to`: it reads as checked and never was.
89
+ 5. Surface complexity, more than eight target files, or more than twenty tasks as
77
90
  scope warnings; they are observations, not semantic verdicts.
78
- 5. Estimate context at roughly `complexity × 5%`; surface anything above 40%.
91
+ 6. Estimate context at roughly `complexity × 5%`; surface anything above 40%.
79
92
 
80
93
  Report the graph, frontier, seams, warnings and first task. Recommend the next explicit
81
94
  capability from the files; do not invoke it.
@@ -39,6 +39,12 @@ The three checkpoints are `04-product-strategy`, `21-features-scope`, and
39
39
  recommendation, wait for the owner's decision, then continue. Other areas write their
40
40
  report and mapped section immediately without another ceremony.
41
41
 
42
+ Areas sitting between two checkpoints are independent and may run in parallel through
43
+ the host's native bounded subagents, each loading exactly one reference. That
44
+ strengthens progressive disclosure rather than breaking it — the parent holds no step
45
+ prompt at all. The checkpoints stay barriers: nothing after one starts until the owner
46
+ has answered it, because its conclusion changes what the later areas are even asking.
47
+
42
48
  ## Investigate and write
43
49
 
44
50
  For each selected area:
@@ -28,8 +28,10 @@ happens here, after several slices make a useful structure visible, not inside T
28
28
  ## Run the lenses
29
29
 
30
30
  Use the host's native bounded subagents, in the background where available; otherwise
31
- run them sequentially. `review-spec` is always selected; select other lenses only when
32
- their evidence can change a verdict. Give each the packet path and digest,
31
+ run them sequentially. **All six lenses are selected by default.** Skipping one requires
32
+ a stated reason recorded in `SUMMARY.json` — "its evidence cannot change the verdict"
33
+ is a judgement made before seeing the evidence, and left as a default it becomes a way
34
+ to run less work rather than a considered choice. Give each the packet path and digest,
33
35
  `references/unified-schema.md`, and exactly one lens:
34
36
 
35
37
  - `references/code.md`
@@ -60,6 +62,28 @@ three or more files do so, write `UNRESOLVED.md` with `ARCHITECTURAL_CONCERN` an
60
62
 
61
63
  Report both axes, exact scope, checks, residual findings and any refactor performed.
62
64
 
65
+ ## Recheck across model families
66
+
67
+ Six lenses are six angles from one model family, so they share blind spots: what all
68
+ six miss, a seventh angle from the same family misses too. Independence comes from a
69
+ different model, not from another lens.
70
+
71
+ For a `major` Change, or one touching authorization, payments, personal data or
72
+ migrations, recommend a cross-family recheck and wait for the owner's decision. Do not
73
+ invoke `ultra-delegate` unless the owner has explicitly asked for another model or host
74
+ for this review. Once that authority exists, follow `../ultra-delegate/SKILL.md`. For a
75
+ `quick` profile, skip the recommendation and say so in the report.
76
+
77
+ - Send the aggregated `SUMMARY.json` and the diff, **not** the raw lens artifacts. The
78
+ worker should form its own reading rather than grade this one.
79
+ - Ask two things: which findings the evidence does not actually support, and what this
80
+ diff gets wrong that no finding mentions. The second question is why the recheck is
81
+ worth its cost.
82
+ - A worker finding no local lens raised is a candidate, not a verdict. Verify it
83
+ against the source exactly as a lens finding is verified, and record its origin.
84
+ - Disagreement is information, not a tie to break. `ultra-delegate` already forbids
85
+ turning a vote or a score into truth; record both readings with their evidence.
86
+
63
87
  ## When the owner decides
64
88
 
65
89
  The owner chooses risk acceptance, a scope reduction, or which stuck path to take.
@@ -70,4 +94,5 @@ Workers never edit source, task state or another lens artifact.
70
94
  - `references/unified-schema.md` — JSON contract shared by all lenses.
71
95
  - `references/worker-packet.md` — exact immutable input packet and digest procedure.
72
96
  - `references/review-modes.md` — choose task, Change, full, or delta scope.
97
+ - `../ultra-delegate/SKILL.md` — read after the owner authorizes a cross-family recheck.
73
98
  - `../ultra-think/references/autonomy-boundary.md` — read before a fix reduces intent.
@@ -45,6 +45,11 @@ the owner decides what to fix or accept.
45
45
  6. **Security**: run the repository's dependency and security checks, then inspect the
46
46
  trust boundaries changed by this Change.
47
47
 
48
+ The six are independent read-only audits and may run in parallel through the host's
49
+ native bounded subagents. Each returns its findings; the parent writes the one report.
50
+ A worker never writes `.ultra/test-report.json` itself — six writers on one canonical
51
+ file is how a report loses findings.
52
+
48
53
  Read `references/export-syntax.md` before collecting exports. The table finds
49
54
  candidates; repository conventions and real consumers decide what is public.
50
55