@alphazede/bearing-lite 0.2.2 → 1.0.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 +11 -6
- package/README.md +224 -131
- package/com.github.copilot/hooks/hooks.json +33 -0
- package/hooks/assurance-budget.cjs +33 -11
- package/hooks/com.anthropic.claude-code/mapping.md +35 -16
- package/hooks/plan-package.cjs +5 -4
- package/hooks/policy.cjs +10 -4
- package/hooks/profiles.cjs +119 -0
- package/hooks/te-host.cjs +40 -5
- package/hooks/transition-order.cjs +12 -4
- package/hooks/verification.cjs +358 -0
- package/package.json +6 -3
- package/plugin.json +2 -34
- package/profiles.json +1 -0
- package/schemas/implementation.schema.json +204 -5
- package/schemas/journey.schema.json +3 -3
- package/schemas/profiles.schema.json +359 -0
- package/schemas/verification.schema.json +126 -0
- package/skills/{set-bearings → architectural-alignment}/SKILL.md +17 -13
- package/skills/{set-bearings → architectural-alignment}/templates/workspace.md +1 -1
- package/skills/bearing-lite/SKILL.md +22 -22
- package/skills/bearing-lite/references/assurance-policy.md +32 -16
- package/skills/bearing-lite/references/lineups.md +7 -110
- package/skills/bearing-lite/references/owner-stops.md +13 -13
- package/skills/bearing-lite/references/peer-synthesis.md +11 -12
- package/skills/bearing-lite/references/profiles.md +149 -0
- package/skills/bearing-lite/references/resume.md +30 -0
- package/skills/bearing-lite/references/review-policy.md +3 -3
- package/skills/bearing-lite/references/role-routing.mmd +14 -14
- package/skills/bearing-lite/references/task-state.md +10 -10
- package/skills/bearing-lite/references/task-state.mmd +2 -2
- package/skills/bearing-lite/references/verification.md +52 -0
- package/skills/bearing-lite/templates/task.md +29 -28
- package/skills/{explorer → coordinator}/SKILL.md +16 -16
- package/skills/{crewmate → implementer}/SKILL.md +17 -15
- package/skills/{repository-fit → intake}/SKILL.md +10 -9
- package/skills/integration-engineer/SKILL.md +17 -12
- package/skills/light-implementer/SKILL.md +6 -5
- package/skills/onboard-bearing/SKILL.md +46 -0
- package/skills/plan-integrator/SKILL.md +14 -14
- package/skills/{map-the-route → planning-and-design}/SKILL.md +27 -27
- package/skills/{map-the-route → planning-and-design}/references/artifact-grammar.md +29 -27
- package/skills/prompt/SKILL.md +245 -0
- package/skills/requirements-engineer/SKILL.md +11 -11
- package/skills/{park-ranger → reviewer}/SKILL.md +16 -13
- package/skills/{gather-supplies → scope-definition}/SKILL.md +13 -13
- package/skills/scribe/SKILL.md +8 -8
- package/skills/systems-modeler/SKILL.md +5 -3
- package/skills/test-engineer/SKILL.md +16 -13
- package/templates/dod-manifest-v1.html +648 -0
- package/tools/render-dod-manifest.mjs +1743 -0
- package/lineups.json +0 -1
- package/schemas/lineups.schema.json +0 -145
- package/skills/navigator/SKILL.md +0 -36
- package/skills/surveyor/SKILL.md +0 -48
- package/skills/validator/SKILL.md +0 -37
- package/skills/validator/references/grading-rubric.md +0 -40
|
@@ -17,9 +17,13 @@ procedural.
|
|
|
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
19
|
| DeepCode | `.deepcode/skills` or interoperable `.agents/skills` discovery | none | skills-only |
|
|
20
|
+
| GitHub Copilot | Agent Plugins 1.0 `plugin.json` + `com.github.copilot/` | `com.github.copilot/hooks/hooks.json` (`SessionStart`, `PreToolUse`, `Stop`, `SubagentStop`) | partial |
|
|
20
21
|
|
|
21
|
-
Do **not** set `hooks` on
|
|
22
|
-
|
|
22
|
+
Do **not** set `hooks` on the root Agent Plugins 1.0 `plugin.json`, or on Claude,
|
|
23
|
+
Codex, or Grok host manifests. Root `hooks` is outside the closed Agent Plugins
|
|
24
|
+
1.0 field set. Claude, Codex, and Grok auto-load `hooks/hooks.json`; declaring
|
|
25
|
+
both is a duplicate-file error. GitHub Copilot in VS Code discovers plugin hooks
|
|
26
|
+
from `com.github.copilot/hooks/hooks.json`.
|
|
23
27
|
|
|
24
28
|
Skill-copy into a host skills directory does **not** register hooks. Plugin
|
|
25
29
|
install or disable uses the host's native controls. Bearing Lite never copies
|
|
@@ -43,19 +47,19 @@ registered separately, and a `Stop` event reaches both.
|
|
|
43
47
|
The shared planning-review evaluator is used by transition and closeout when a
|
|
44
48
|
client supplies a structured `planning_review` record. Current session-start /
|
|
45
49
|
stop mappings cannot derive that nested record safely, so Claude Code, Codex,
|
|
46
|
-
Grok Build, Cursor, and
|
|
47
|
-
AGY, Pi, and DeepCode remain skills-only. Do not claim full
|
|
48
|
-
enforcement for any of these hosts until a native event supplies
|
|
49
|
-
record.
|
|
50
|
+
Grok Build, Cursor, Kimi, and GitHub Copilot remain partial and the check is
|
|
51
|
+
procedural there. AGY, Pi, and DeepCode remain skills-only. Do not claim full
|
|
52
|
+
planning-review enforcement for any of these hosts until a native event supplies
|
|
53
|
+
the complete record.
|
|
50
54
|
|
|
51
55
|
The per-declared-phase-or-wave assurance record
|
|
52
56
|
(`skills/bearing-lite/references/assurance-policy.md`) is evaluated by the same
|
|
53
57
|
`transition` class through the `assurance_transition` action_kind. No mapped
|
|
54
58
|
host exposes a hook event that carries the frozen declaration and the visible
|
|
55
59
|
task record, so the assurance budget is procedural on every mapped host:
|
|
56
|
-
Claude Code, Codex, Grok Build, Cursor,
|
|
57
|
-
Pi, and DeepCode stay skills-only. No host may advertise an
|
|
58
|
-
assurance budget it cannot perform.
|
|
60
|
+
Claude Code, Codex, Grok Build, Cursor, Kimi Code, and GitHub Copilot stay
|
|
61
|
+
partial, and AGY, Pi, and DeepCode stay skills-only. No host may advertise an
|
|
62
|
+
enforcement of the assurance budget it cannot perform.
|
|
59
63
|
|
|
60
64
|
The adapter accepts snake_case and camelCase (`hook_event_name` /
|
|
61
65
|
`hookEventName`, `cwd` / `workspaceRoot`).
|
|
@@ -108,13 +112,24 @@ this adapter must not advertise enforcement it cannot perform.
|
|
|
108
112
|
| Pi | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
|
|
109
113
|
| AGY | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
|
|
110
114
|
| DeepCode | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
|
|
115
|
+
| GitHub Copilot | native `PreToolUse` deny | native `Stop` deny | native `SubagentStop` deny |
|
|
111
116
|
|
|
112
117
|
Cursor's `stop` guidance message is advisory text, not a hard completion
|
|
113
118
|
block, and Cursor exposes no distinct hard child-stop deny; neither is
|
|
114
119
|
registered as one. Kimi Code, Pi, AGY, and DeepCode carry no verified native
|
|
115
|
-
Test Engineering blocking
|
|
116
|
-
for every class on those hosts rather than claiming an
|
|
117
|
-
deliver.
|
|
120
|
+
Test Engineering blocking in this adapter, so `hooks/te-host.cjs` returns
|
|
121
|
+
`UNAVAILABLE` for every class on those hosts rather than claiming an
|
|
122
|
+
enforcement it cannot deliver.
|
|
123
|
+
|
|
124
|
+
GitHub Copilot in VS Code is a native TE host. Agent Plugins 1.0 discovers
|
|
125
|
+
`com.github.copilot/hooks/hooks.json` and expands `${PLUGIN_ROOT}`; the TE
|
|
126
|
+
commands quote that path and pass `--host=copilot`. Copilot `PreToolUse` denies
|
|
127
|
+
through `hookSpecificOutput.permissionDecision`. Copilot `Stop` denies through
|
|
128
|
+
`hookSpecificOutput` with `hookEventName` `Stop`, `decision` `block`, and
|
|
129
|
+
`reason`. Copilot `SubagentStop` denies through a top-level `decision` block
|
|
130
|
+
and `reason`. `Stop` and `SubagentStop` carry `stop_hook_active`; a true
|
|
131
|
+
re-entry terminates as quiet success so a deny cannot loop. Transition-order,
|
|
132
|
+
protected-action, planning-review, and assurance-budget stay procedural.
|
|
118
133
|
|
|
119
134
|
## Derived fields
|
|
120
135
|
|
|
@@ -134,10 +149,10 @@ trees) and sets only:
|
|
|
134
149
|
payload. Missing values stay missing, so activation advises the router instead
|
|
135
150
|
of inventing context.
|
|
136
151
|
|
|
137
|
-
The
|
|
138
|
-
dispatch; its nested non-placeholder
|
|
139
|
-
signal during planning. The plan-level
|
|
140
|
-
recorded later at the
|
|
152
|
+
The Orchestrator writes the visible `checkout_lease` before any planning write
|
|
153
|
+
or dispatch; its nested non-placeholder Lifecycle identity is the
|
|
154
|
+
Router-invoked signal during planning. The plan-level lifecycle setting is
|
|
155
|
+
recorded later at the integrated owner review.
|
|
141
156
|
|
|
142
157
|
## Outcome translation
|
|
143
158
|
|
|
@@ -148,6 +163,10 @@ recorded later at the route review.
|
|
|
148
163
|
| Stop/SubagentStop with no discoverable Journey | quiet success (empty JSON; no `additionalContext`) | always `0` |
|
|
149
164
|
| first-pass Stop/SubagentStop with a discoverable Journey | `hookSpecificOutput.additionalContext` | always `0` |
|
|
150
165
|
| `BLOCK` | JSON `decision: "block"` only; this mapping never requests protected completion | always `0` |
|
|
166
|
+
| Copilot `PreToolUse` deny | `hookSpecificOutput.permissionDecision` `"deny"` | always `0` |
|
|
167
|
+
| Copilot `Stop` deny | `hookSpecificOutput` `{ hookEventName: "Stop", decision: "block", reason }` | always `0` |
|
|
168
|
+
| Copilot `SubagentStop` deny | top-level `{ decision: "block", reason }` | always `0` |
|
|
169
|
+
| Copilot `Stop`/`SubagentStop` re-entry (`stop_hook_active`) | quiet success (empty JSON) | always `0` |
|
|
151
170
|
|
|
152
171
|
A discoverable Journey is a visible plan with a `task_id`, `assigned_role`,
|
|
153
172
|
non-placeholder journey marker, or `checkout_lease` block. Empty cwd and
|
package/hooks/plan-package.cjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Map the Route freeze (#70, #73, #77, #80): pure checks over a planning package.
|
|
5
5
|
* checkRoles: every command a slice runs whose seit procedure names an actor
|
|
6
6
|
* must name the slice's role. checkWorkClass validates light slices;
|
|
7
|
-
* checkPlanningRoles excludes planning-only roles from
|
|
7
|
+
* checkPlanningRoles excludes planning-only roles from Lifecycle implementation slices.
|
|
8
8
|
* verifyDigests: every planning input digest
|
|
9
9
|
* embedded in seit.json matches the file on disk; the manifest digest over
|
|
10
10
|
* those inputs plus seit.json matches implementation.json's
|
|
@@ -70,13 +70,14 @@ function checkWorkClass(node, findings = []) {
|
|
|
70
70
|
return findings;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
/** #80: Requirements Engineer is planning-only and never
|
|
73
|
+
/** #80: Requirements Engineer is planning-only and never a Lifecycle implementation slice. */
|
|
74
74
|
function checkPlanningRoles(node, findings = []) {
|
|
75
75
|
if (Array.isArray(node)) node.forEach((item) => checkPlanningRoles(item, findings));
|
|
76
76
|
else if (node && typeof node === "object") {
|
|
77
|
-
|
|
77
|
+
const isSlice = typeof node.id === "string" || Array.isArray(node.command_ids);
|
|
78
|
+
if (isSlice && typeof node.role === "string" && node.role.startsWith("Requirements Engineer")) {
|
|
78
79
|
findings.push({
|
|
79
|
-
code: "
|
|
80
|
+
code: "planning_role_in_lifecycle",
|
|
80
81
|
step: typeof node.id === "string" ? node.id : null,
|
|
81
82
|
role: node.role,
|
|
82
83
|
});
|
package/hooks/policy.cjs
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const PLANNING_REVIEW_POLICY = Object.freeze({
|
|
11
|
-
reviewer_slots_min:
|
|
12
|
-
reviewer_slots_max:
|
|
11
|
+
reviewer_slots_min: 1,
|
|
12
|
+
reviewer_slots_max: 1,
|
|
13
13
|
independence_required: true,
|
|
14
14
|
isolated_findings_until_aggregation: true,
|
|
15
15
|
candidate_fields: ["candidate_ref", "candidate_revision", "candidate_digest"],
|
|
@@ -23,12 +23,18 @@ const PLANNING_REVIEW_POLICY = Object.freeze({
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
const ASSURANCE_BUDGET_POLICY = Object.freeze({
|
|
26
|
-
budget_scope: "
|
|
26
|
+
budget_scope: "per_declared_cadence_unit",
|
|
27
|
+
cadence_values: ["slice", "phase", "lifecycle"],
|
|
28
|
+
default_cadence: {
|
|
29
|
+
"test_engineer.assurance": "phase",
|
|
30
|
+
reviewer: "phase",
|
|
31
|
+
"integration_engineer.execution": "lifecycle",
|
|
32
|
+
},
|
|
27
33
|
review_rounds: 1,
|
|
28
34
|
aggregated_repairs_max: 1,
|
|
29
35
|
post_repair_gate: "deterministic_PASS",
|
|
30
36
|
automatic_phase_or_wave_end_review: "required",
|
|
31
|
-
automatic_per_slice_review: "
|
|
37
|
+
automatic_per_slice_review: "cadence_gated",
|
|
32
38
|
post_repair_rereview: "prohibited",
|
|
33
39
|
automatic_rereview_of_same_unit: "prohibited",
|
|
34
40
|
budget_reset_on_candidate_change: false,
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Profile catalog presence and migration classifier (DES-BDL-004/005).
|
|
5
|
+
* Pure evaluator: never writes, never treats lineups.json as live configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require("node:fs");
|
|
9
|
+
const path = require("node:path");
|
|
10
|
+
|
|
11
|
+
const PACKAGED_BYTES = '{"schema_version":1,"profiles":{}}';
|
|
12
|
+
const NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
13
|
+
const RETIRED_ROLE_KEYS = new Set([
|
|
14
|
+
"surveyor",
|
|
15
|
+
"explorer",
|
|
16
|
+
"crewmate",
|
|
17
|
+
"navigator",
|
|
18
|
+
"park_ranger",
|
|
19
|
+
"park-ranger",
|
|
20
|
+
"validator",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function isPlainObject(value) {
|
|
24
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveHome(env) {
|
|
28
|
+
const home = typeof env.HOME === "string" ? env.HOME.trim() : "";
|
|
29
|
+
if (home && path.isAbsolute(home)) return home;
|
|
30
|
+
const profile = typeof env.USERPROFILE === "string" ? env.USERPROFILE.trim() : "";
|
|
31
|
+
if (profile && path.isAbsolute(profile)) return profile;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function exists(file) {
|
|
36
|
+
try {
|
|
37
|
+
return fs.existsSync(file);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJson(file) {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function retiredRoleKeys(profile) {
|
|
52
|
+
if (!isPlainObject(profile) || !isPlainObject(profile.roles)) return [];
|
|
53
|
+
return Object.keys(profile.roles).filter((key) => RETIRED_ROLE_KEYS.has(key));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {object} [input]
|
|
58
|
+
* @returns {{ outcome: string, reason: string, path?: string }}
|
|
59
|
+
*/
|
|
60
|
+
function evaluateCatalogState(input) {
|
|
61
|
+
const env = (input && input.env) || process.env;
|
|
62
|
+
const home = resolveHome(env);
|
|
63
|
+
if (!home) {
|
|
64
|
+
return { outcome: "NEEDS_MORE_EVIDENCE", reason: "home_unresolved" };
|
|
65
|
+
}
|
|
66
|
+
const dir = path.join(home, ".agents", "bearing-lite");
|
|
67
|
+
const profilesPath = path.join(dir, "profiles.json");
|
|
68
|
+
const lineupsPath = path.join(dir, "lineups.json");
|
|
69
|
+
|
|
70
|
+
const hasLineups = exists(lineupsPath);
|
|
71
|
+
const hasProfiles = exists(profilesPath);
|
|
72
|
+
|
|
73
|
+
if (hasLineups) {
|
|
74
|
+
return {
|
|
75
|
+
outcome: "MIGRATION_REQUIRED",
|
|
76
|
+
reason: "legacy_lineups_present",
|
|
77
|
+
path: lineupsPath,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (!hasProfiles) {
|
|
81
|
+
return { outcome: "no_named_profiles", reason: "missing_user_catalog", path: profilesPath };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const parsed = readJson(profilesPath);
|
|
85
|
+
if (!isPlainObject(parsed)) {
|
|
86
|
+
return { outcome: "NEEDS_MORE_EVIDENCE", reason: "catalog_not_json", path: profilesPath };
|
|
87
|
+
}
|
|
88
|
+
if ("lineups" in parsed) {
|
|
89
|
+
return { outcome: "MIGRATION_REQUIRED", reason: "legacy_lineups_field", path: profilesPath };
|
|
90
|
+
}
|
|
91
|
+
const profiles = parsed.profiles;
|
|
92
|
+
if (!isPlainObject(profiles)) {
|
|
93
|
+
return { outcome: "NEEDS_MORE_EVIDENCE", reason: "profiles_not_object", path: profilesPath };
|
|
94
|
+
}
|
|
95
|
+
for (const name of Object.keys(profiles)) {
|
|
96
|
+
if (!NAME_PATTERN.test(name)) {
|
|
97
|
+
return { outcome: "NEEDS_MORE_EVIDENCE", reason: "invalid_profile_name", path: profilesPath };
|
|
98
|
+
}
|
|
99
|
+
const retired = retiredRoleKeys(profiles[name]);
|
|
100
|
+
if (retired.length) {
|
|
101
|
+
return {
|
|
102
|
+
outcome: "NEEDS_MORE_EVIDENCE",
|
|
103
|
+
reason: "retired_role:" + retired[0],
|
|
104
|
+
path: profilesPath,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { outcome: "READY", reason: "profiles_live", path: profilesPath };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function packagedCatalogBytes() {
|
|
112
|
+
return PACKAGED_BYTES;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
evaluateCatalogState,
|
|
117
|
+
packagedCatalogBytes,
|
|
118
|
+
PACKAGED_BYTES,
|
|
119
|
+
};
|
package/hooks/te-host.cjs
CHANGED
|
@@ -69,6 +69,7 @@ const HOST_SUPPORT = Object.freeze({
|
|
|
69
69
|
grok: FULL_NATIVE,
|
|
70
70
|
codex: FULL_NATIVE,
|
|
71
71
|
"claude-code": FULL_NATIVE,
|
|
72
|
+
copilot: FULL_NATIVE,
|
|
72
73
|
cursor: Object.freeze({
|
|
73
74
|
write_time_deny: NATIVE,
|
|
74
75
|
completion_deny: UNAVAILABLE,
|
|
@@ -118,6 +119,10 @@ function presentString(value) {
|
|
|
118
119
|
return trimmed;
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
function isTruthyFlag(value) {
|
|
123
|
+
return value === true || value === "true";
|
|
124
|
+
}
|
|
125
|
+
|
|
121
126
|
/** Normalize Claude, Codex, Grok, Cursor, and Kimi event spellings. */
|
|
122
127
|
function normalizeEvent(name) {
|
|
123
128
|
if (typeof name !== "string") return "";
|
|
@@ -508,6 +513,19 @@ function handle(envelope, options) {
|
|
|
508
513
|
);
|
|
509
514
|
}
|
|
510
515
|
|
|
516
|
+
if (
|
|
517
|
+
host === "copilot" &&
|
|
518
|
+
hookClass === TE_COMPLETION &&
|
|
519
|
+
(isTruthyFlag(input.stop_hook_active) || isTruthyFlag(input.stopHookActive))
|
|
520
|
+
) {
|
|
521
|
+
return hostOutput(
|
|
522
|
+
hookClass,
|
|
523
|
+
ALLOW,
|
|
524
|
+
"stop_hook_active: re-entry terminates quietly",
|
|
525
|
+
{ hookEventName: eventName, host, code: "stop_hook_active" }
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
511
529
|
const root = path.resolve(
|
|
512
530
|
presentString(input.cwd) || presentString(input.workspaceRoot) || process.cwd()
|
|
513
531
|
);
|
|
@@ -522,12 +540,13 @@ function handle(envelope, options) {
|
|
|
522
540
|
hookClass,
|
|
523
541
|
"capability_inactive: test-engineering is unselected and not required; " +
|
|
524
542
|
"the class fails open and this is not a failure",
|
|
525
|
-
{ hookEventName: eventName, code: resolved.code }
|
|
543
|
+
{ hookEventName: eventName, host, code: resolved.code }
|
|
526
544
|
);
|
|
527
545
|
}
|
|
528
546
|
if (resolved.status !== "ACTIVE" || !resolved.evaluator) {
|
|
529
547
|
return unavailable(hookClass, resolved.message || "typed_capability_gap", {
|
|
530
548
|
hookEventName: eventName,
|
|
549
|
+
host,
|
|
531
550
|
code: resolved.code || "typed_capability_gap",
|
|
532
551
|
recovery: resolved.recovery,
|
|
533
552
|
});
|
|
@@ -545,7 +564,7 @@ function handle(envelope, options) {
|
|
|
545
564
|
hookClass,
|
|
546
565
|
"evaluator_failure: the test-engineering evaluator did not return a " +
|
|
547
566
|
"verdict; enforcement is unavailable for this event",
|
|
548
|
-
{ hookEventName: eventName, code: "evaluator_failure" }
|
|
567
|
+
{ hookEventName: eventName, host, code: "evaluator_failure" }
|
|
549
568
|
);
|
|
550
569
|
}
|
|
551
570
|
|
|
@@ -554,14 +573,14 @@ function handle(envelope, options) {
|
|
|
554
573
|
return unavailable(
|
|
555
574
|
hookClass,
|
|
556
575
|
"evaluator_verdict_unrecognized: enforcement is unavailable for this event",
|
|
557
|
-
{ hookEventName: eventName, code: "evaluator_verdict_unrecognized" }
|
|
576
|
+
{ hookEventName: eventName, host, code: "evaluator_verdict_unrecognized" }
|
|
558
577
|
);
|
|
559
578
|
}
|
|
560
579
|
|
|
561
580
|
const reason =
|
|
562
581
|
(isPlainObject(result) ? presentString(result.reason) : undefined) ||
|
|
563
582
|
"test_engineering_verdict";
|
|
564
|
-
return toHostResponse(hookClass, verdict, reason, { hookEventName: eventName });
|
|
583
|
+
return toHostResponse(hookClass, verdict, reason, { hookEventName: eventName, host });
|
|
565
584
|
}
|
|
566
585
|
|
|
567
586
|
function flag(value) {
|
|
@@ -602,7 +621,23 @@ function toWire(response) {
|
|
|
602
621
|
},
|
|
603
622
|
};
|
|
604
623
|
}
|
|
605
|
-
if (response.decision === "block")
|
|
624
|
+
if (response.decision === "block") {
|
|
625
|
+
// VS Code Stop deny is nested; Claude/Codex/Grok and VS Code SubagentStop
|
|
626
|
+
// keep the top-level decision block.
|
|
627
|
+
if (
|
|
628
|
+
presentString(inner.host) === "copilot" &&
|
|
629
|
+
normalizeEvent(inner.hookEventName) === "stop"
|
|
630
|
+
) {
|
|
631
|
+
return {
|
|
632
|
+
hookSpecificOutput: {
|
|
633
|
+
hookEventName: "Stop",
|
|
634
|
+
decision: "block",
|
|
635
|
+
reason: response.reason,
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
return { decision: "block", reason: response.reason };
|
|
640
|
+
}
|
|
606
641
|
return {};
|
|
607
642
|
}
|
|
608
643
|
|
|
@@ -83,7 +83,7 @@ function normalizeAssurance(value) {
|
|
|
83
83
|
return [String(value)].filter((s) => s && s !== "none");
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/** Normalize role labels for order comparison (Validator vs validator vs
|
|
86
|
+
/** Normalize role labels for order comparison (Validator vs validator vs reviewer). */
|
|
87
87
|
function normalizeRoleKey(role) {
|
|
88
88
|
return String(role)
|
|
89
89
|
.trim()
|
|
@@ -94,15 +94,23 @@ function normalizeRoleKey(role) {
|
|
|
94
94
|
/**
|
|
95
95
|
* Map the next missing assurance role to the state that should receive it.
|
|
96
96
|
* Assurance Test Engineer (or the retired Validator label) -> VALIDATING,
|
|
97
|
-
* Park Ranger -> REVIEWING,
|
|
97
|
+
* Reviewer (or retired Park Ranger) -> REVIEWING,
|
|
98
|
+
* Integration Engineer execution (or retired Surveyor) -> ACCEPTANCE.
|
|
98
99
|
*/
|
|
99
100
|
function assuranceTargetState(role) {
|
|
100
101
|
const key = normalizeRoleKey(role);
|
|
101
102
|
if (key === "validator" || key === "assurance-test-engineer" || key === "test-engineer") {
|
|
102
103
|
return "VALIDATING";
|
|
103
104
|
}
|
|
104
|
-
if (key === "park-ranger" || key === "parkranger") return "REVIEWING";
|
|
105
|
-
if (
|
|
105
|
+
if (key === "reviewer" || key === "park-ranger" || key === "parkranger") return "REVIEWING";
|
|
106
|
+
if (
|
|
107
|
+
key === "surveyor" ||
|
|
108
|
+
key === "integration-engineer" ||
|
|
109
|
+
key === "integration-engineer-execution" ||
|
|
110
|
+
key === "execution-integration-engineer"
|
|
111
|
+
) {
|
|
112
|
+
return "ACCEPTANCE";
|
|
113
|
+
}
|
|
106
114
|
return null;
|
|
107
115
|
}
|
|
108
116
|
|