@alphazede/bearing-lite 0.1.9 → 0.1.11

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/README.md CHANGED
@@ -8,9 +8,11 @@ planning, routing, bounded execution, and independent review of repository work.
8
8
  It ships portable skills, references, templates, and optional client hooks.
9
9
 
10
10
  An agent cannot certify its own work. The stateful Router owns the planning
11
- conversation and visible Journey state, then dispatches fresh bounded sessions.
12
- Independent assurance runs at the owner's selected cadence: per slice, per
13
- execution/correction round, or once at the end. Owner Authority remains human-only.
11
+ conversation, visible Journey state, and Expedition sequencing. Crewmate and
12
+ Explorer may continue in-wave when the envelope is unchanged. Independent
13
+ assurance runs once at the end on the final integrated candidate. One repair
14
+ may follow; it is verified deterministically without another review. Owner
15
+ Authority remains human-only.
14
16
 
15
17
  Bearing Lite was created by William Rumph.
16
18
 
@@ -27,9 +29,10 @@ Then give your agent a real task:
27
29
  > Use Bearing Lite to add rate limiting to this API without changing its public
28
30
  > responses. Require one independent review at the end.
29
31
 
30
- Bearing Lite asks whether the work is an Explorer Journey or an Expedition,
31
- fills only the missing planning stages, confirms the agent lineup and review
32
- cadence, and dispatches bounded sessions with visible Markdown state.
32
+ Bearing Lite fills missing planning stages, then Map the Route creates the
33
+ complete five-artifact package with proposed route, lineup, role states,
34
+ reasoning, and at-end cadence. One integrated owner review approves or changes
35
+ that package before bounded sessions dispatch with visible Markdown state.
33
36
 
34
37
  If Bearing Lite helps keep a long agent task scoped and reviewable,
35
38
  [star the repository](https://github.com/alphazede/bearing-lite). It helps other
@@ -65,13 +68,25 @@ agy plugin install /path/to/bearing-lite/.agy
65
68
 
66
69
  # Pi — skills package, no command hooks
67
70
  pi install npm:@alphazede/bearing-lite
71
+
72
+ # DeepCode — discover the packaged skills through ~/.deepcode/skills or ~/.agents/skills;
73
+ # DeepCode has no plugin-install or command-hook surface
68
74
  ```
69
75
 
70
76
  Claude Code, Codex, Grok Build, Cursor, and Kimi Code are **partial** hook
71
77
  clients: session start runs the activation advisory and stop runs the closeout
72
- advisory. AGY and Pi are **skills-only**. Transition-order and protected-action
73
- checks stay in the skills on every host. Node.js must be on `PATH` for the
74
- hook adapters.
78
+ advisory. AGY, Pi, and DeepCode are **skills-only**. Transition-order and
79
+ protected-action checks stay in the skills on every host. Node.js must be on
80
+ `PATH` for the hook adapters.
81
+
82
+ Planning-review constraints live only in
83
+ [`skills/bearing-lite/references/review-policy.md`](skills/bearing-lite/references/review-policy.md);
84
+ the shared evaluator is `hooks/planning-review.cjs`. Journey-specific abstract
85
+ slot IDs and owner-selected primary/ordered fallback route references belong
86
+ only in the approved `lineup_snapshot`. Existing host mappings cannot safely
87
+ derive the nested record from session events, so they preserve partial or
88
+ skills-only coverage and apply this gate procedurally. Planning review is a
89
+ pre-dispatch plan gate; implementation `max_assurance_rounds` remains separate.
75
90
 
76
91
  **Skills-only copy** of `skills/` into a host skills directory does not
77
92
  register hooks. That path remains first-class. See
@@ -79,11 +94,14 @@ register hooks. That path remains first-class. See
79
94
 
80
95
  ## What it does
81
96
 
82
- 1. **Ask** whether the Journey is an Explorer Journey or an Expedition.
83
- 2. **Fill only missing planning stages:** Repository Fit → Set Bearings → Gather
84
- Supplies Map the Route.
85
- 3. **Confirm** the user-owned primary/fallback lineup and review cadence.
86
- 4. **Dispatch fresh sessions** with bounded context, authority, and return types.
97
+ 1. **Fill only missing planning stages:** Repository Fit Set Bearings → Gather
98
+ Supplies.
99
+ 2. **Invoke Map the Route** after material intent is settled. It creates
100
+ specification, design, SEIT, implementation, and `review.html` together.
101
+ 3. **Review once:** approve or change the proposed route, user-owned
102
+ primary/fallback lineup, role states, reasoning, at-end cadence, and plan.
103
+ 4. **Dispatch bounded sessions** with compact receipts. Crewmate and Explorer may
104
+ continue in-wave; assurance always starts fresh at the end.
87
105
  5. **Record state visibly** in human-readable Markdown artifacts only.
88
106
 
89
107
  Bearing Lite never selects models, providers, credentials, or launchers. The
@@ -96,11 +114,11 @@ the applicable Journey snapshot before implementation.
96
114
  | Route | When | Cost |
97
115
  |---|---|---|
98
116
  | **Explorer Journey** | One bounded packet or one wave | Direct Crewmate or one Explorer |
99
- | **Expedition** | Multi-phase or concurrent independent lanes | Navigator |
117
+ | **Expedition** | Multi-phase or concurrent independent lanes | Router sequences; Explorer owns waves |
100
118
 
101
119
  An **Explorer Journey** uses a direct Crewmate for one ready packet or one
102
120
  Explorer over a compact/sequential wave.
103
- **Expedition** adds navigation so independent lanes
121
+ **Expedition** lets the Router sequence waves so independent lanes
104
122
  stay small and sharp instead of degrading in one long context. Either shape can
105
123
  use substantial tokens; the product does not impose a default budget ceiling.
106
124
 
@@ -111,20 +129,20 @@ The text below remains authoritative for clients that do not render Mermaid.
111
129
 
112
130
  **Authoritative text (vision optional):** Owner Authority remains human-only. The
113
131
  Bearing Lite Router is the stateful planning controller, not a work role. It
114
- invokes only missing planning stages in fresh sessions, confirms the owner's
115
- lineup and cadence, then dispatches an Explorer Journey or Expedition. Explorer
132
+ invokes only missing planning stages, has Map the Route generate all five
133
+ artifacts with proposed lineup and `review_cadence: at-end`, then presents one
134
+ integrated owner review before dispatching an Explorer Journey or Expedition. Explorer
116
135
  coordinates proven-independent in-wave lanes without a nested coordinator.
117
- Validator, Park Ranger,
118
- and Surveyor appear only when declared and when the selected per-slice,
119
- per-round, or at-end boundary is reached. Diagrams explain orientation; they
120
- never authorize a transition.
136
+ Validator, Park Ranger, and Surveyor appear only when declared and only at the
137
+ end on the final integrated candidate. Diagrams
138
+ explain orientation; they never authorize a transition.
121
139
 
122
140
  ## Roles and authority
123
141
 
124
142
  | Role | What it is | Executes | Notes |
125
143
  |---|---|---|---|
126
- | **Router** | Stateful planning controller | no | User-facing; planning-state writer |
127
- | **Navigator** | Expedition orchestrator | no | Owns cross-wave sequencing and conflicts |
144
+ | **Router** | Stateful planning controller | no | User-facing; planning-state writer; Expedition sequencing |
145
+ | **Navigator** | Compatibility diagnostic | no | Not a normal role; existing plans reroute to Router |
128
146
  | **Explorer** | One-wave controller | no | Dispatches Crewmates; owns proven-independent lanes |
129
147
  | **Crewmate** | Bounded implementer | yes | Most hands-on work; exact write set |
130
148
  | **Validator** | Evidence sufficiency | no | Independent of the author |
@@ -141,7 +159,7 @@ Failure escalates to the nearest role whose scope can see it:
141
159
  |---|---|
142
160
  | Within one slice or packet | Explorer or nearest parent |
143
161
  | Across slices in a wave | Explorer |
144
- | Across waves or phases | Navigator |
162
+ | Across waves or phases | Router |
145
163
  | Contract, security, or authority change | Owner Authority |
146
164
 
147
165
  ## Task state (explanatory)
@@ -155,60 +173,30 @@ Normal progress is `PROPOSED` → `READY` → `IN_PROGRESS` → `EVIDENCE_READY`
155
173
  optional `VALIDATING` / `REVIEWING` when required, then `ACCEPTANCE` →
156
174
  `COMPLETE`. `WAITING_ON` holds for missing prerequisites, checkout-lease
157
175
  conflict, or assurance dispatch.
158
- `CORRECTION_REQUIRED` allows two in-authority repairs; a third failed correction
159
- escalates to `OWNER_DECISION_REQUIRED`. Diagrams never create state or authorize
160
- transitions.
176
+ Ordinary execution corrections remain bounded. The assurance gate allows one
177
+ review-directed repair, followed by deterministic coordinator verification and
178
+ no second review. Diagrams never create state or authorize transitions.
161
179
 
162
180
  ## Implementation process (explanatory)
163
181
 
164
- Owner-approved multi-phase work follows four phases: Inventory, Foundation,
165
- Proof and Documentation, and Final Audit. Default slice completion is author
166
- self-check plus coordinator confirmation when `required_assurance` is `none`.
167
- Fresh Validator then separate Park Ranger is mandatory only on exact integrated
168
- phase candidates, not on every packet.
169
-
170
- ![Bearing Lite implementation process: four phases with default self-check slices, optional owner slice assurance, mandatory integrated phase gates, bounded correction, and final Surveyor acceptance](docs/plans/2026-08-09-bearing-skills-first-architecture/assets/implementation-process.png)
171
-
172
- Mermaid source:
173
- [`docs/plans/2026-08-09-bearing-skills-first-architecture/assets/implementation-process.mmd`](docs/plans/2026-08-09-bearing-skills-first-architecture/assets/implementation-process.mmd).
174
-
175
- <details>
176
- <summary>Diagram source (implementation process)</summary>
182
+ Default packet completion is author self-check plus coordinator confirmation.
183
+ Declared independent assurance runs once at the end. A repairable
184
+ verdict permits one repair; deterministic coordinator verification then closes
185
+ the gate without another review. Once the Journey is `COMPLETE`, an already
186
+ authorized deployment proceeds with operational checks and rollback readiness,
187
+ not a new assurance round. Source-changing deployment work is separate work.
177
188
 
178
189
  ```mermaid
179
- flowchart TD
180
- P1[Phase 1 Inventory S1-S4 complete] --> P2[Phase 2 Foundation S4A-S7]
181
- P2 --> SLICE[Crewmate then author self-check]
182
- SLICE --> CC[Coordinator scope and dependency confirmation]
183
- CC --> OPT{Owner slice assurance?}
184
- OPT -->|Yes| V[Fresh Validator then optional Park Ranger]
185
- OPT -->|No| MORE{More Foundation slices?}
186
- V -->|Fail or repair| R[Bounded Correction Attempt]
187
- V -->|Pass| MORE
188
- R -->|Attempt 1 or 2| SLICE
189
- R -->|3rd Failure| O[OWNER_DECISION_REQUIRED]
190
- MORE -->|Yes| SLICE
191
- MORE -->|No: integrated Foundation| FG[Fresh Validator then separate Park Ranger]
192
- FG --> P3[Phase 3 Proof and Documentation S8-S10]
193
- P3 --> SLICE3[Crewmate then author self-check]
194
- SLICE3 --> CC3[Coordinator confirmation]
195
- CC3 --> OPT3{Owner slice assurance?}
196
- OPT3 -->|Yes| V3[Fresh Validator then optional Park Ranger]
197
- OPT3 -->|No| MORE3{More Proof and Documentation slices?}
198
- V3 -->|Fail or repair| R
199
- V3 -->|Pass| MORE3
200
- MORE3 -->|Yes| SLICE3
201
- MORE3 -->|No: integrated Proof and Documentation| PG[Fresh Validator then separate Park Ranger]
202
- PG --> P4[Phase 4 Final Audit S11-S12]
203
- P4 --> S11[S11 Skill and State Alignment Audit]
204
- S11 --> S12[S12 Integrated Acceptance]
205
- S12 --> S[Fresh Surveyor Final Acceptance]
206
- S -->|Pass| CMP[COMPLETE]
207
- O -->|Owner Decision| MORE
190
+ flowchart LR
191
+ W[Bounded work] --> C[Deterministic checks]
192
+ C --> R[Single independent review]
193
+ R -->|Pass| X[COMPLETE]
194
+ R -->|Repairable| F[One repair]
195
+ F --> V[Coordinator verification]
196
+ V --> X
197
+ X --> D[Authorized deploy and operational verification]
208
198
  ```
209
199
 
210
- </details>
211
-
212
200
  ## Package layout
213
201
 
214
202
  | Path | Purpose |
@@ -10,25 +10,42 @@
10
10
  const HOOK_CLASS = "closeout";
11
11
  const OUTCOMES = Object.freeze(["ADVISE", "REROUTE", "BLOCK", "UNAVAILABLE"]);
12
12
  const ENFORCEMENT = "procedural";
13
+ const { evaluatePlanningReview } = require("./planning-review.cjs");
13
14
 
14
15
  const HANDOFF_FIELDS = Object.freeze([
15
- "plan_ref",
16
- "role",
17
- "subject",
18
- "depends_on",
19
- "scope",
20
- "authority",
21
- "outcome",
22
- "evidence",
16
+ "verdict",
17
+ "candidate_ref",
18
+ "changed_paths",
19
+ "tests",
20
+ "findings",
23
21
  "blocker",
24
- "next_action",
25
- "receiving_role",
22
+ ]);
23
+
24
+ /** Closed role-return tokens. Task-block `outcome` is intent and cannot satisfy this. */
25
+ const VERDICT_VALUES = new Set([
26
+ "ACCEPT",
27
+ "ACCEPT_WITH_FINDINGS",
28
+ "BLOCK",
29
+ "CANDIDATE_READY",
30
+ "FAIL",
31
+ "GAPS",
32
+ "NEEDS_MORE_EVIDENCE",
33
+ "OWNER_DECISION_REQUIRED",
34
+ "PARTIAL",
35
+ "PASS",
36
+ "READY",
37
+ "REPAIR_REQUIRED",
38
+ "REROUTED",
39
+ "WAITING_ON",
26
40
  ]);
27
41
 
28
42
  const RECOVERY_UNAVAILABLE =
29
43
  "Report UNAVAILABLE, complete the handoff checklist manually, and do not request protected completion until required fields and assurance are present";
30
44
  const RECOVERY_HANDOFF =
31
- "Complete the role return envelope (plan_ref, role, subject, depends_on, scope, authority, outcome, evidence, blocker, next_action, receiving_role) in the project plan";
45
+ "Complete the compact receipt (verdict, candidate_ref, changed_paths, tests, findings, blocker) in the project plan";
46
+ const RECOVERY_INVALID_VERDICT =
47
+ "Replace verdict with a closed role-return token: " +
48
+ [...VERDICT_VALUES].join(", ");
32
49
  const RECOVERY_COMPLETE =
33
50
  "Handoff is complete; parent coordinator may advance using the project plan only";
34
51
  const RECOVERY_BLOCK_COMPLETION =
@@ -75,9 +92,10 @@ function normalizeAssurance(value) {
75
92
  return [String(value)].filter((s) => s && s !== "none");
76
93
  }
77
94
 
78
- function missingHandoffFields(input) {
95
+ function inspectHandoffFields(input) {
79
96
  const handoff = isPlainObject(input.handoff) ? input.handoff : input;
80
97
  const missing = [];
98
+ const invalid = [];
81
99
  for (const field of HANDOFF_FIELDS) {
82
100
  const value = handoff[field];
83
101
  if (value === undefined || value === null) {
@@ -86,10 +104,14 @@ function missingHandoffFields(input) {
86
104
  }
87
105
  if (typeof value === "string" && value.trim() === "") {
88
106
  missing.push(field);
107
+ continue;
108
+ }
109
+ if (field === "verdict" && (typeof value !== "string" || !VERDICT_VALUES.has(value.trim()))) {
110
+ invalid.push(field);
89
111
  }
90
112
  }
91
113
  // blocker may be the string "none"; that is present and valid.
92
- return missing;
114
+ return { missing, invalid };
93
115
  }
94
116
 
95
117
  /**
@@ -124,7 +146,11 @@ function evaluate(input) {
124
146
  ? "protected_completion"
125
147
  : "advisory";
126
148
 
127
- const missing = missingHandoffFields(input);
149
+ const planningVerdict = input.planning_review
150
+ ? evaluatePlanningReview(input.planning_review)
151
+ : null;
152
+
153
+ const { missing, invalid } = inspectHandoffFields(input);
128
154
  const required = normalizeAssurance(input.required_assurance);
129
155
  const accepted = Array.isArray(input.assurance_accepted)
130
156
  ? input.assurance_accepted.map(String)
@@ -141,12 +167,16 @@ function evaluate(input) {
141
167
 
142
168
  if (mode === "protected_completion") {
143
169
  const blockers = [];
144
- if (missing.length > 0) blockers.push("handoff:" + missing.join(","));
170
+ const handoffProblems = [...missing, ...invalid];
171
+ if (handoffProblems.length > 0) blockers.push("handoff:" + handoffProblems.join(","));
145
172
  if (missingAssurance.length > 0) {
146
173
  blockers.push("assurance:" + missingAssurance.join(","));
147
174
  }
148
175
  if (!candidateMatched) blockers.push("candidate_mismatch");
149
176
  if (unresolvedBlocker) blockers.push("unresolved_blocker");
177
+ if (planningVerdict && planningVerdict.outcome !== "PASS") {
178
+ blockers.push("planning_review:" + planningVerdict.outcome);
179
+ }
150
180
 
151
181
  if (blockers.length > 0) {
152
182
  return result(
@@ -165,6 +195,16 @@ function evaluate(input) {
165
195
  );
166
196
  }
167
197
 
198
+ if (planningVerdict) {
199
+ return result(
200
+ planningVerdict.outcome === "PASS" ? "ADVISE" : "REROUTE",
201
+ "planning_review:" + planningVerdict.outcome + ":" + planningVerdict.reason,
202
+ planningVerdict.outcome === "PASS"
203
+ ? "Planning-review record is complete; continue to the integrated owner gate"
204
+ : "Keep dispatch closed and complete, repair, or amend the planning-review record"
205
+ );
206
+ }
207
+
168
208
  // Advisory closeout: never BLOCK.
169
209
  if (missing.length > 0) {
170
210
  return result(
@@ -174,6 +214,14 @@ function evaluate(input) {
174
214
  );
175
215
  }
176
216
 
217
+ if (invalid.length > 0) {
218
+ return result(
219
+ "ADVISE",
220
+ "handoff_invalid:" + invalid.join(","),
221
+ RECOVERY_INVALID_VERDICT
222
+ );
223
+ }
224
+
177
225
  if (missingAssurance.length > 0) {
178
226
  return result(
179
227
  "ADVISE",
@@ -234,6 +282,7 @@ module.exports = {
234
282
  OUTCOMES,
235
283
  ENFORCEMENT,
236
284
  HANDOFF_FIELDS,
285
+ VERDICT_VALUES,
237
286
  evaluate,
238
287
  };
239
288
 
@@ -52,7 +52,9 @@ const SKIP_DIRS = new Set([
52
52
  const TASK_MARK = /(?:^|\n)###\s*task_id:\s*\S+/;
53
53
  const TASK_LINE = /^###\s*task_id:\s*(\S+)\s*$/;
54
54
  const FIELD_LINE =
55
- /^-\s*(assigned_role|next_action|status|required_assurance|blocker|evidence|candidate_ref|scope|authority|outcome|depends_on|receiving_role|plan_ref|role|subject):\s*(.*)$/;
55
+ /^-\s*(assigned_role|next_action|status|required_assurance|blocker|evidence|candidate_ref|scope|authority|outcome|verdict|depends_on|receiving_role|plan_ref|role|subject|changed_paths|tests|findings):\s*(.*)$/;
56
+ // Matches both the plan-level Journey type and the lease-nested Journey
57
+ // identity; either non-placeholder value marks the Router as invoked.
56
58
  const JOURNEY_LINE = /^\s*-\s*journey:\s*(.+)$/i;
57
59
  const LEASE_MARK = /^\s*-\s*checkout_lease:/m;
58
60
 
@@ -325,17 +327,12 @@ function evaluateForHost(eventName, derived) {
325
327
  if (classForEvent(eventName) === "closeout") {
326
328
  const task = derived.active_task || {};
327
329
  return closeout.evaluate({
328
- plan_ref: derived.plan_present ? "project-plan" : undefined,
329
- role: presentString(task.assigned_role) || presentString(task.role),
330
- subject: presentString(task.task_id) || presentString(task.subject),
331
- depends_on: task.depends_on,
332
- scope: presentString(task.scope),
333
- authority: presentString(task.authority),
334
- outcome: presentString(task.outcome),
335
- evidence: presentString(task.evidence),
330
+ verdict: presentString(task.verdict),
331
+ candidate_ref: presentString(task.candidate_ref),
332
+ changed_paths: presentString(task.changed_paths),
333
+ tests: presentString(task.tests),
334
+ findings: presentString(task.findings),
336
335
  blocker: presentString(task.blocker) || "none",
337
- next_action: presentString(task.next_action),
338
- receiving_role: presentString(task.receiving_role),
339
336
  required_assurance: task.required_assurance,
340
337
  });
341
338
  }
@@ -16,6 +16,7 @@ procedural.
16
16
  | Kimi Code | `.kimi-plugin/plugin.json` | manifest `hooks` array | partial |
17
17
  | AGY | `.agy/` (strict `plugin.json`) | none | skills-only |
18
18
  | Pi | `package.json` `"pi"` + `pi-package` | none (TypeScript extensions, not command hooks) | skills-only |
19
+ | DeepCode | `.deepcode/skills` or interoperable `.agents/skills` discovery | none | skills-only |
19
20
 
20
21
  Do **not** set `hooks` on Claude, Codex, or Grok host manifests. Those hosts
21
22
  auto-load `hooks/hooks.json`; declaring both is a duplicate-file error.
@@ -32,6 +33,14 @@ adapters into global hook configuration.
32
33
  | `Stop`, `stop` | closeout | yes, advisory only |
33
34
  | any other host event | none | unmapped; fail open as `UNAVAILABLE` |
34
35
 
36
+ The shared planning-review evaluator is used by transition and closeout when a
37
+ client supplies a structured `planning_review` record. Current session-start /
38
+ stop mappings cannot derive that nested record safely, so Claude Code, Codex,
39
+ Grok Build, Cursor, and Kimi remain partial and the check is procedural there.
40
+ AGY, Pi, and DeepCode remain skills-only. Do not claim full planning-review
41
+ enforcement for any of these hosts until a native event supplies the complete
42
+ record.
43
+
35
44
  The adapter accepts snake_case and camelCase (`hook_event_name` /
36
45
  `hookEventName`, `cwd` / `workspaceRoot`).
37
46
 
@@ -44,15 +53,20 @@ trees) and sets only:
44
53
  | Bearing field | Source |
45
54
  |---|---|
46
55
  | `plan_present` | a Markdown file contains a `task_id`, `assigned_role`, non-placeholder journey marker, or `checkout_lease` block |
47
- | `router_invoked` | a `- journey:` setting is present and not a placeholder |
56
+ | `router_invoked` | a non-placeholder `- journey:` setting is present, including the Journey identity nested inside the `checkout_lease` block |
48
57
  | `assigned_role` | active task `assigned_role` when not `unassigned` or `<…>` |
49
58
  | `next_action` / `next_action_known` | active task `next_action` when not a placeholder |
50
- | closeout handoff fields | matching task-record keys when present |
59
+ | closeout handoff fields | matching task-record keys when present. Receipt `verdict` is a closed role-return token; task `outcome` is approved intent and is not mapped into the receipt |
51
60
 
52
61
  `assigned_role` and `router_invoked` are **not** inferred from the tool-call
53
62
  payload. Missing values stay missing, so activation advises the router instead
54
63
  of inventing context.
55
64
 
65
+ The Router writes the visible `checkout_lease` before any planning write or
66
+ dispatch; its nested non-placeholder Journey identity is the Router-invoked
67
+ signal during planning. The plan-level Explorer-versus-Expedition value is
68
+ recorded later at the route review.
69
+
56
70
  ## Outcome translation
57
71
 
58
72
  | Outcome | Host JSON | Process exit |
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+
3
+ const POLICY = Object.freeze({
4
+ reviewer_slots_min: 2,
5
+ reviewer_slots_max: 2,
6
+ independence_required: true,
7
+ isolated_findings_until_aggregation: true,
8
+ candidate_fields: ["candidate_ref", "candidate_revision", "candidate_digest"],
9
+ shared_candidate_required: true,
10
+ review_rounds: 1,
11
+ aggregated_repairs_max: 1,
12
+ post_repair_gate: "deterministic_PASS",
13
+ automatic_rereview: "prohibited",
14
+ slot_exhaustion_outcome: "FAIL_ROUND",
15
+ terminal_outcomes: ["HALT", "OWNER_AMENDMENT_REQUIRED"],
16
+ });
17
+
18
+ const sameCandidate = (a, b) =>
19
+ POLICY.candidate_fields.every(
20
+ (key) => typeof a?.[key] === "string" && a[key] && a[key] === b?.[key]
21
+ );
22
+
23
+ function evaluatePlanningReview(input) {
24
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
25
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "planning_review_missing" };
26
+ }
27
+ const slots = Array.isArray(input.reviewer_slots) ? input.reviewer_slots : [];
28
+ const ids = slots.map((slot) => slot?.slot_id);
29
+ const primaryRoutes = slots.map((slot) => slot?.primary_route_ref);
30
+ if (
31
+ slots.length < POLICY.reviewer_slots_min ||
32
+ slots.length > POLICY.reviewer_slots_max ||
33
+ ids.some((id) => typeof id !== "string" || !id) ||
34
+ new Set(ids).size !== ids.length ||
35
+ new Set(primaryRoutes).size !== primaryRoutes.length ||
36
+ slots.some(
37
+ (slot) =>
38
+ typeof slot.primary_route_ref !== "string" ||
39
+ !slot.primary_route_ref ||
40
+ !Array.isArray(slot.fallback_route_refs)
41
+ )
42
+ ) {
43
+ return { outcome: "OWNER_AMENDMENT_REQUIRED", reason: "slot_binding_invalid" };
44
+ }
45
+ if (slots.some((slot) => slot.exhausted === true)) {
46
+ return { outcome: POLICY.slot_exhaustion_outcome, reason: "reviewer_slot_exhausted" };
47
+ }
48
+ if (input.round_number !== 1 || input.completed_rounds > POLICY.review_rounds) {
49
+ return { outcome: "HALT", reason: "review_round_limit" };
50
+ }
51
+ if (input.completed_rounds === 0) {
52
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "review_round_pending" };
53
+ }
54
+ if (input.completed_rounds !== POLICY.review_rounds) {
55
+ return { outcome: "OWNER_AMENDMENT_REQUIRED", reason: "review_round_invalid" };
56
+ }
57
+ if (
58
+ POLICY.automatic_rereview === "prohibited" &&
59
+ (input.automatic_rereview_requested === true || input.review_after_repair === true)
60
+ ) {
61
+ return { outcome: "OWNER_AMENDMENT_REQUIRED", reason: "automatic_rereview_prohibited" };
62
+ }
63
+ const receipts = Array.isArray(input.receipts) ? input.receipts : [];
64
+ const receiptIds = receipts.map((receipt) => receipt?.slot_id);
65
+ const selectedRoutes = receipts.map((receipt) => receipt?.selected_route_ref);
66
+ if (
67
+ receipts.length !== slots.length ||
68
+ new Set(receiptIds).size !== receiptIds.length ||
69
+ new Set(selectedRoutes).size !== selectedRoutes.length ||
70
+ !input.candidate ||
71
+ receipts.some(
72
+ (receipt) => {
73
+ const slot = slots.find((item) => item.slot_id === receipt?.slot_id);
74
+ return (
75
+ !slot ||
76
+ ![slot.primary_route_ref, ...slot.fallback_route_refs].includes(receipt.selected_route_ref) ||
77
+ receipt.independent !== POLICY.independence_required ||
78
+ receipt.findings_isolated !== POLICY.isolated_findings_until_aggregation ||
79
+ !sameCandidate(receipt, input.candidate)
80
+ );
81
+ }
82
+ )
83
+ ) {
84
+ return { outcome: "HALT", reason: "candidate_or_independence_mismatch" };
85
+ }
86
+ if (input.aggregation_complete !== true) {
87
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "aggregation_pending" };
88
+ }
89
+ const repairs = Number(input.aggregated_repairs || 0);
90
+ if (!Number.isInteger(repairs) || repairs < 0 || repairs > POLICY.aggregated_repairs_max) {
91
+ return { outcome: "HALT", reason: "aggregated_repair_limit" };
92
+ }
93
+ if (repairs === 1 && input.deterministic_gate !== POLICY.post_repair_gate.split("_")[1]) {
94
+ return { outcome: "HALT", reason: "deterministic_post_repair_gate_required" };
95
+ }
96
+ return { outcome: "PASS", reason: repairs ? "repair_gate_passed" : "review_aggregated" };
97
+ }
98
+
99
+ module.exports = { POLICY, evaluatePlanningReview };
@@ -10,6 +10,7 @@
10
10
  const HOOK_CLASS = "transition";
11
11
  const OUTCOMES = Object.freeze(["ADVISE", "REROUTE", "BLOCK", "UNAVAILABLE"]);
12
12
  const ENFORCEMENT = "procedural";
13
+ const { evaluatePlanningReview } = require("./planning-review.cjs");
13
14
 
14
15
  const RECOVERY_UNAVAILABLE =
15
16
  "Keep the current state, run the transition checklist procedurally, and record the result before retrying";
@@ -125,6 +126,18 @@ function evaluate(input) {
125
126
  return result("ADVISE", "channel_open", RECOVERY_CHANNEL);
126
127
  }
127
128
 
129
+ if (input.action_kind === "planning_review_transition") {
130
+ const verdict = evaluatePlanningReview(input.planning_review);
131
+ if (verdict.outcome === "PASS") {
132
+ return result("ADVISE", "planning_review:PASS", RECOVERY_ALLOW);
133
+ }
134
+ return result(
135
+ verdict.outcome === "NEEDS_MORE_EVIDENCE" ? "REROUTE" : "BLOCK",
136
+ "planning_review:" + verdict.outcome + ":" + verdict.reason,
137
+ "Keep dispatch closed and repair or amend the planning-review record"
138
+ );
139
+ }
140
+
128
141
  const from = typeof input.from_state === "string" ? input.from_state.trim() : "";
129
142
  const to = typeof input.to_state === "string" ? input.to_state.trim() : "";
130
143
  if (!from || !to) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphazede/bearing-lite",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Skills-first Agent Plugin for planning, routing, bounded execution, and independent review of repository work—without CLI, MCP, server, or hidden runtime state.",
5
5
  "keywords": [
6
6
  "agent-plugins",
@@ -15,7 +15,9 @@
15
15
  "pi-package"
16
16
  ],
17
17
  "pi": {
18
- "skills": ["./skills"]
18
+ "skills": [
19
+ "./skills"
20
+ ]
19
21
  },
20
22
  "author": "William Rumph / AlphaZede",
21
23
  "license": "Apache-2.0",
@@ -36,5 +38,6 @@
36
38
  "CONTRIBUTING.md",
37
39
  "SECURITY.md",
38
40
  "LICENSE-APACHE"
39
- ]
41
+ ],
42
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
40
43
  }
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "bearing-lite",
4
- "version": "0.1.9",
4
+ "version": "0.1.11",
5
5
  "description": "Skills-first portable plugin that routes repository work through the smallest valid planning stages and agent roles, using project Markdown as the only task record.",
6
6
  "author": {
7
7
  "name": "William Rumph / AlphaZede",