@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.
- package/CONTRIBUTING.md +25 -0
- package/README.md +76 -32
- package/hooks/assurance-budget.cjs +167 -0
- package/hooks/closeout.cjs +64 -15
- package/hooks/com.anthropic.claude-code/host.cjs +8 -11
- package/hooks/com.anthropic.claude-code/mapping.md +88 -2
- package/hooks/com.cursor/hooks.json +11 -1
- package/hooks/hooks.json +45 -1
- package/hooks/planning-review.cjs +99 -0
- package/hooks/te-capability.cjs +137 -0
- package/hooks/te-host.cjs +620 -0
- package/hooks/transition-order.cjs +45 -2
- package/lineups.json +1 -0
- package/package.json +9 -4
- package/plugin.json +1 -1
- package/schemas/authority.schema.json +75 -0
- package/schemas/implementation.schema.json +127 -0
- package/schemas/journey.schema.json +88 -0
- package/schemas/lineups.schema.json +145 -0
- package/schemas/seit.schema.json +108 -0
- package/skills/bearing-lite/SKILL.md +39 -46
- package/skills/bearing-lite/agents/openai.yaml +1 -1
- package/skills/bearing-lite/references/assurance-policy.md +66 -0
- package/skills/bearing-lite/references/lineups.md +81 -0
- package/skills/bearing-lite/references/peer-synthesis.md +8 -4
- package/skills/bearing-lite/references/review-policy.md +37 -0
- package/skills/bearing-lite/references/role-routing.mmd +10 -10
- package/skills/bearing-lite/references/task-state.md +6 -6
- package/skills/bearing-lite/references/task-state.mmd +1 -1
- package/skills/bearing-lite/templates/default-role-lineup.md +11 -3
- package/skills/bearing-lite/templates/task.md +75 -21
- package/skills/crewmate/SKILL.md +26 -17
- package/skills/explorer/SKILL.md +35 -34
- package/skills/gather-supplies/SKILL.md +9 -2
- package/skills/integration-engineer/SKILL.md +41 -0
- package/skills/map-the-route/SKILL.md +48 -36
- package/skills/map-the-route/references/artifact-grammar.md +94 -35
- package/skills/navigator/SKILL.md +18 -41
- package/skills/park-ranger/SKILL.md +24 -16
- package/skills/plan-integrator/SKILL.md +37 -0
- package/skills/scribe/SKILL.md +34 -0
- package/skills/surveyor/SKILL.md +22 -15
- package/skills/systems-modeler/SKILL.md +35 -0
- package/skills/test-engineer/SKILL.md +46 -0
- package/skills/validator/SKILL.md +20 -25
- 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
|
|
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
|
+
};
|