@alphazede/bearing-lite 0.1.10 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CONTRIBUTING.md +25 -0
  2. package/README.md +76 -32
  3. package/hooks/assurance-budget.cjs +167 -0
  4. package/hooks/closeout.cjs +64 -15
  5. package/hooks/com.anthropic.claude-code/host.cjs +8 -11
  6. package/hooks/com.anthropic.claude-code/mapping.md +88 -2
  7. package/hooks/com.cursor/hooks.json +11 -1
  8. package/hooks/hooks.json +45 -1
  9. package/hooks/planning-review.cjs +99 -0
  10. package/hooks/te-capability.cjs +137 -0
  11. package/hooks/te-host.cjs +620 -0
  12. package/hooks/transition-order.cjs +45 -2
  13. package/lineups.json +1 -0
  14. package/package.json +9 -4
  15. package/plugin.json +1 -1
  16. package/schemas/authority.schema.json +75 -0
  17. package/schemas/implementation.schema.json +127 -0
  18. package/schemas/journey.schema.json +88 -0
  19. package/schemas/lineups.schema.json +145 -0
  20. package/schemas/seit.schema.json +108 -0
  21. package/skills/bearing-lite/SKILL.md +39 -46
  22. package/skills/bearing-lite/agents/openai.yaml +1 -1
  23. package/skills/bearing-lite/references/assurance-policy.md +66 -0
  24. package/skills/bearing-lite/references/lineups.md +81 -0
  25. package/skills/bearing-lite/references/peer-synthesis.md +8 -4
  26. package/skills/bearing-lite/references/review-policy.md +37 -0
  27. package/skills/bearing-lite/references/role-routing.mmd +10 -10
  28. package/skills/bearing-lite/references/task-state.md +6 -6
  29. package/skills/bearing-lite/references/task-state.mmd +1 -1
  30. package/skills/bearing-lite/templates/default-role-lineup.md +11 -3
  31. package/skills/bearing-lite/templates/task.md +75 -21
  32. package/skills/crewmate/SKILL.md +26 -17
  33. package/skills/explorer/SKILL.md +35 -34
  34. package/skills/gather-supplies/SKILL.md +9 -2
  35. package/skills/integration-engineer/SKILL.md +41 -0
  36. package/skills/map-the-route/SKILL.md +48 -36
  37. package/skills/map-the-route/references/artifact-grammar.md +94 -35
  38. package/skills/navigator/SKILL.md +18 -41
  39. package/skills/park-ranger/SKILL.md +24 -16
  40. package/skills/plan-integrator/SKILL.md +37 -0
  41. package/skills/scribe/SKILL.md +34 -0
  42. package/skills/surveyor/SKILL.md +22 -15
  43. package/skills/systems-modeler/SKILL.md +35 -0
  44. package/skills/test-engineer/SKILL.md +46 -0
  45. package/skills/validator/SKILL.md +20 -25
  46. package/skills/validator/references/grading-rubric.md +5 -4
@@ -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.
@@ -30,11 +31,91 @@ adapters into global hook configuration.
30
31
  |---|---|---|
31
32
  | `SessionStart`, `sessionStart`, `session_start` | activation | yes, advisory |
32
33
  | `Stop`, `stop` | closeout | yes, advisory only |
34
+ | `PreToolUse`, `preToolUse`, `beforeShellExecution`, `apply_patch` | te_test_write | only where the host has a native write-time deny |
35
+ | `Stop`, `stop`, `SubagentStop`, `subagentStop` | te_completion | only where the host has a native completion or child-stop deny |
33
36
  | any other host event | none | unmapped; fail open as `UNAVAILABLE` |
34
37
 
38
+ `hooks/com.anthropic.claude-code/host.cjs` owns activation and closeout and
39
+ maps no Test Engineering class. `hooks/te-host.cjs` owns te_test_write and
40
+ te_completion and maps neither of the original four. The two adapters are
41
+ registered separately, and a `Stop` event reaches both.
42
+
43
+ The shared planning-review evaluator is used by transition and closeout when a
44
+ client supplies a structured `planning_review` record. Current session-start /
45
+ stop mappings cannot derive that nested record safely, so Claude Code, Codex,
46
+ Grok Build, Cursor, and Kimi remain partial and the check is procedural there.
47
+ AGY, Pi, and DeepCode remain skills-only. Do not claim full planning-review
48
+ enforcement for any of these hosts until a native event supplies the complete
49
+ record.
50
+
51
+ The per-declared-phase-or-wave assurance record
52
+ (`skills/bearing-lite/references/assurance-policy.md`) is evaluated by the same
53
+ `transition` class through the `assurance_transition` action_kind. No mapped
54
+ host exposes a hook event that carries the frozen declaration and the visible
55
+ task record, so the assurance budget is procedural on every mapped host:
56
+ Claude Code, Codex, Grok Build, Cursor, and Kimi Code stay partial, and AGY,
57
+ Pi, and DeepCode stay skills-only. No host may advertise an enforcement of the
58
+ assurance budget it cannot perform.
59
+
35
60
  The adapter accepts snake_case and camelCase (`hook_event_name` /
36
61
  `hookEventName`, `cwd` / `workspaceRoot`).
37
62
 
63
+ ## Test Engineering classes
64
+
65
+ `te_test_write` and `te_completion` are **additional** classes, shipped by
66
+ `hooks/te-capability.cjs` and `hooks/te-host.cjs`. They do not rename or
67
+ replace activation, closeout, transition-order, or protected-action, and they
68
+ do not reuse those classes' outcome tokens. Their verdicts are `ALLOW`,
69
+ `DENY_ROUTE_TO_TE`, `DENY_RECEIPT_REQUIRED`, and `UNAVAILABLE`.
70
+
71
+ Bearing Lite ships no Test Engineering evaluator and no Test Engineering
72
+ method content. The adapter resolves the `test-engineering` capability, loads
73
+ the evaluator at `skills/test-engineering/hooks/te-evaluator.cjs` when that
74
+ capability is present in the workspace, builds the request from the real Git
75
+ checkout and the coordinator-authored plan, and returns the evaluator verdict
76
+ unchanged.
77
+
78
+ | Capability state | Class result |
79
+ |---|---|
80
+ | neither selected nor required | `UNAVAILABLE`; the class fails open and this is not a failure |
81
+ | selected or required, evaluator absent | `UNAVAILABLE` with `code: typed_capability_gap`; a typed gap, never silent success |
82
+ | selected or required, evaluator loaded | the evaluator verdict, verbatim |
83
+
84
+ Trusted assignment comes from the visible coordinator-authored plan, never
85
+ from the tool payload or the transcript. Candidate state comes from the
86
+ installed Git executable: the committed `diff_base..HEAD` change plus the
87
+ working tree, scoped to the trusted write set. An empty trusted scope covers
88
+ nothing, so unrelated shared dirt never enters the candidate. Derived runtime
89
+ state under the Bearing runtime directory is never a candidate change.
90
+
91
+ An artifact on disk is never a local pass token; only the evaluator reads it.
92
+ An `ALLOW` completion verdict is not user acceptance and grants no planning or
93
+ publication authority.
94
+
95
+ ### Host honesty
96
+
97
+ `native` means the host exposes a verified hard deny for that channel.
98
+ Everything else is `UNAVAILABLE`: the class is procedural on that host and
99
+ this adapter must not advertise enforcement it cannot perform.
100
+
101
+ | Host | te_test_write channel | te_completion at parent stop | te_completion at child stop |
102
+ |---|---|---|---|
103
+ | Claude Code | native `PreToolUse` deny | native `Stop` deny | native `SubagentStop` deny |
104
+ | Codex | native `PreToolUse` deny | native `Stop` deny | native `SubagentStop` deny |
105
+ | Grok Build | native `PreToolUse` deny | native `Stop` deny | native `SubagentStop` deny |
106
+ | Cursor | native `preToolUse` and `beforeShellExecution` deny | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
107
+ | Kimi Code | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
108
+ | Pi | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
109
+ | AGY | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
110
+ | DeepCode | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
111
+
112
+ Cursor's `stop` guidance message is advisory text, not a hard completion
113
+ block, and Cursor exposes no distinct hard child-stop deny; neither is
114
+ registered as one. Kimi Code, Pi, AGY, and DeepCode carry no verified native
115
+ Test Engineering blocking at all, so `hooks/te-host.cjs` returns `UNAVAILABLE`
116
+ for every class on those hosts rather than claiming an enforcement it cannot
117
+ deliver.
118
+
38
119
  ## Derived fields
39
120
 
40
121
  The host event supplies session metadata. The adapter reads visible Markdown
@@ -44,15 +125,20 @@ trees) and sets only:
44
125
  | Bearing field | Source |
45
126
  |---|---|
46
127
  | `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 |
128
+ | `router_invoked` | a non-placeholder `- journey:` setting is present, including the Journey identity nested inside the `checkout_lease` block |
48
129
  | `assigned_role` | active task `assigned_role` when not `unassigned` or `<…>` |
49
130
  | `next_action` / `next_action_known` | active task `next_action` when not a placeholder |
50
- | closeout handoff fields | matching task-record keys when present |
131
+ | 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
132
 
52
133
  `assigned_role` and `router_invoked` are **not** inferred from the tool-call
53
134
  payload. Missing values stay missing, so activation advises the router instead
54
135
  of inventing context.
55
136
 
137
+ The Router writes the visible `checkout_lease` before any planning write or
138
+ dispatch; its nested non-placeholder Journey identity is the Router-invoked
139
+ signal during planning. The plan-level Explorer-versus-Expedition value is
140
+ recorded later at the route review.
141
+
56
142
  ## Outcome translation
57
143
 
58
144
  | Outcome | Host JSON | Process exit |
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Bearing Lite Cursor mapping. Partial coverage: sessionStart activation, stop closeout. Transition-order and protected-action remain procedural.",
2
+ "description": "Bearing Lite Cursor mapping. Partial coverage: sessionStart activation, stop closeout, and write-time te_test_write on preToolUse and beforeShellExecution. Cursor exposes no hard completion or child-stop deny, so te_completion stays procedural. Transition-order and protected-action remain procedural.",
3
3
  "hooks": {
4
4
  "sessionStart": [
5
5
  {
@@ -10,6 +10,16 @@
10
10
  {
11
11
  "command": "node ./hooks/com.anthropic.claude-code/host.cjs"
12
12
  }
13
+ ],
14
+ "preToolUse": [
15
+ {
16
+ "command": "node ./hooks/te-host.cjs --host=cursor"
17
+ }
18
+ ],
19
+ "beforeShellExecution": [
20
+ {
21
+ "command": "node ./hooks/te-host.cjs --host=cursor"
22
+ }
13
23
  ]
14
24
  }
15
25
  }
package/hooks/hooks.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Bearing Lite verified Claude Code and Codex mapping. Partial coverage: activation on SessionStart, closeout on Stop. Transition-order and protected-action remain procedural.",
2
+ "description": "Bearing Lite verified Claude Code and Codex mapping. Partial coverage: activation on SessionStart, closeout on Stop, and the Test Engineering classes te_test_write on PreToolUse and te_completion on Stop and SubagentStop. Transition-order and protected-action remain procedural.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -16,6 +16,22 @@
16
16
  ]
17
17
  }
18
18
  ],
19
+ "PreToolUse": [
20
+ {
21
+ "matcher": "Write|Edit|MultiEdit|NotebookEdit|apply_patch|Bash",
22
+ "hooks": [
23
+ {
24
+ "type": "command",
25
+ "command": "node",
26
+ "args": [
27
+ "${CLAUDE_PLUGIN_ROOT}/hooks/te-host.cjs"
28
+ ],
29
+ "timeout": 15,
30
+ "statusMessage": "Bearing Lite test-engineering write check"
31
+ }
32
+ ]
33
+ }
34
+ ],
19
35
  "Stop": [
20
36
  {
21
37
  "hooks": [
@@ -29,6 +45,34 @@
29
45
  "statusMessage": "Bearing Lite closeout"
30
46
  }
31
47
  ]
48
+ },
49
+ {
50
+ "hooks": [
51
+ {
52
+ "type": "command",
53
+ "command": "node",
54
+ "args": [
55
+ "${CLAUDE_PLUGIN_ROOT}/hooks/te-host.cjs"
56
+ ],
57
+ "timeout": 15,
58
+ "statusMessage": "Bearing Lite test-engineering completion check"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "SubagentStop": [
64
+ {
65
+ "hooks": [
66
+ {
67
+ "type": "command",
68
+ "command": "node",
69
+ "args": [
70
+ "${CLAUDE_PLUGIN_ROOT}/hooks/te-host.cjs"
71
+ ],
72
+ "timeout": 15,
73
+ "statusMessage": "Bearing Lite test-engineering child completion check"
74
+ }
75
+ ]
32
76
  }
33
77
  ]
34
78
  }
@@ -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 };
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Test Engineering capability resolver (SEIT-EMV-017, SEIT-EMV-036).
5
+ *
6
+ * Bearing Lite ships no Test Engineering evaluator and no Test Engineering
7
+ * method content. This module only answers one question: is the
8
+ * `test-engineering` capability active for this workspace, and if so, is the
9
+ * real evaluator loadable at its capability path?
10
+ *
11
+ * Activation is `selected OR required`.
12
+ * - neither -> INACTIVE. Not a failure; the class simply does not run.
13
+ * - active, gone -> TYPED_GAP. Never silent success, never invented policy.
14
+ * - active, here -> ACTIVE, with the loaded evaluator module.
15
+ *
16
+ * `invented` is always `false`: this resolver never substitutes a local
17
+ * decision for the evaluator's.
18
+ */
19
+
20
+ const fs = require("node:fs");
21
+ const path = require("node:path");
22
+
23
+ const CAPABILITY = "test-engineering";
24
+ const STATUSES = Object.freeze(["INACTIVE", "ACTIVE", "TYPED_GAP"]);
25
+ /** Capability path, relative to the workspace root. */
26
+ const EVALUATOR_RELATIVE_PATH = path.join(
27
+ "skills",
28
+ CAPABILITY,
29
+ "hooks",
30
+ "te-evaluator.cjs"
31
+ );
32
+
33
+ /**
34
+ * @param {unknown} workspaceRoot
35
+ * @returns {string}
36
+ */
37
+ function evaluatorPath(workspaceRoot) {
38
+ const root =
39
+ typeof workspaceRoot === "string" && workspaceRoot.trim()
40
+ ? workspaceRoot.trim()
41
+ : process.cwd();
42
+ return path.resolve(root, EVALUATOR_RELATIVE_PATH);
43
+ }
44
+
45
+ /**
46
+ * Load the evaluator at the capability path. Absence, a load failure, or a
47
+ * module that does not implement the evaluator contract are all the same
48
+ * observable: the capability is unavailable.
49
+ * @param {unknown} workspaceRoot
50
+ * @returns {{ evaluateTestWrite: Function, evaluateCompletion: Function } | null}
51
+ */
52
+ function loadEvaluator(workspaceRoot) {
53
+ const target = evaluatorPath(workspaceRoot);
54
+ let stat;
55
+ try {
56
+ stat = fs.statSync(target);
57
+ } catch {
58
+ return null;
59
+ }
60
+ if (!stat.isFile()) return null;
61
+ let mod;
62
+ try {
63
+ mod = require(target);
64
+ } catch {
65
+ return null;
66
+ }
67
+ if (
68
+ !mod ||
69
+ typeof mod.evaluateTestWrite !== "function" ||
70
+ typeof mod.evaluateCompletion !== "function"
71
+ ) {
72
+ return null;
73
+ }
74
+ return mod;
75
+ }
76
+
77
+ /**
78
+ * Resolve the `test-engineering` capability for one workspace.
79
+ * @param {{ workspaceRoot?: string, selected?: boolean, required?: boolean }} [input]
80
+ */
81
+ function resolve(input) {
82
+ const opts = input !== null && typeof input === "object" ? input : {};
83
+ const selected = opts.selected === true;
84
+ const required = opts.required === true;
85
+ const base = {
86
+ capability: CAPABILITY,
87
+ selected,
88
+ required,
89
+ invented: false,
90
+ };
91
+
92
+ if (!selected && !required) {
93
+ return {
94
+ ...base,
95
+ active: false,
96
+ status: "INACTIVE",
97
+ evaluator: null,
98
+ code: "capability_inactive",
99
+ message:
100
+ `capability "${CAPABILITY}" is neither selected nor required; ` +
101
+ "the class does not run and this is not a failure",
102
+ };
103
+ }
104
+
105
+ const evaluator = loadEvaluator(opts.workspaceRoot);
106
+ if (!evaluator) {
107
+ return {
108
+ ...base,
109
+ active: true,
110
+ status: "TYPED_GAP",
111
+ evaluator: null,
112
+ code: "typed_capability_gap",
113
+ message:
114
+ `selected or required capability "${CAPABILITY}" is unavailable: ` +
115
+ `no loadable evaluator at ${EVALUATOR_RELATIVE_PATH}`,
116
+ recovery:
117
+ "install the test-engineering method capability in this workspace, " +
118
+ "or clear the selected/required activation for this task",
119
+ };
120
+ }
121
+
122
+ return {
123
+ ...base,
124
+ active: true,
125
+ status: "ACTIVE",
126
+ evaluator,
127
+ };
128
+ }
129
+
130
+ module.exports = {
131
+ CAPABILITY,
132
+ STATUSES,
133
+ EVALUATOR_RELATIVE_PATH,
134
+ evaluatorPath,
135
+ loadEvaluator,
136
+ resolve,
137
+ };