bearings 0.3.4 → 0.5.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.
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from 'node:fs/promises';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ const TOP_LEVEL_FIELDS = new Set(['title', 'summary', 'intro', 'callout', 'conventions', 'sections']);
7
+ const SECTION_FIELDS = new Set(['number', 'title', 'kind', 'intro', 'items']);
8
+ const ITEM_FIELDS = new Set([
9
+ 'id',
10
+ 'title',
11
+ 'instruction',
12
+ 'expectedResult',
13
+ 'codeBlocks',
14
+ 'links',
15
+ 'optional',
16
+ 'warning',
17
+ 'failureGuidance',
18
+ 'subItems',
19
+ ]);
20
+ const SUB_ITEM_FIELDS = new Set([...ITEM_FIELDS].filter((field) => field !== 'subItems'));
21
+
22
+ function isObject(value) {
23
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
24
+ }
25
+
26
+ function nonEmptyString(value) {
27
+ return typeof value === 'string' && value.trim().length > 0;
28
+ }
29
+
30
+ function rejectUnknownFields(value, allowed, path, errors) {
31
+ for (const field of Object.keys(value)) {
32
+ if (!allowed.has(field)) errors.push(`${path}.${field} is not allowed`);
33
+ }
34
+ }
35
+
36
+ function validateOptionalString(value, path, errors) {
37
+ if (value !== undefined && typeof value !== 'string') errors.push(`${path} must be a string`);
38
+ }
39
+
40
+ function validateCodeBlocks(value, path, errors) {
41
+ if (value === undefined) return;
42
+ if (!Array.isArray(value)) {
43
+ errors.push(`${path} must be an array`);
44
+ return;
45
+ }
46
+ value.forEach((block, index) => {
47
+ const blockPath = `${path}[${index}]`;
48
+ if (!isObject(block)) {
49
+ errors.push(`${blockPath} must be an object`);
50
+ return;
51
+ }
52
+ rejectUnknownFields(block, new Set(['language', 'content']), blockPath, errors);
53
+ if (!nonEmptyString(block.language)) errors.push(`${blockPath}.language must be a non-empty string`);
54
+ if (typeof block.content !== 'string') errors.push(`${blockPath}.content must be a string`);
55
+ });
56
+ }
57
+
58
+ function validateLinks(value, path, errors) {
59
+ if (value === undefined) return;
60
+ if (!Array.isArray(value)) {
61
+ errors.push(`${path} must be an array`);
62
+ return;
63
+ }
64
+ value.forEach((link, index) => {
65
+ const linkPath = `${path}[${index}]`;
66
+ if (!isObject(link)) {
67
+ errors.push(`${linkPath} must be an object`);
68
+ return;
69
+ }
70
+ rejectUnknownFields(link, new Set(['label', 'url']), linkPath, errors);
71
+ for (const field of ['label', 'url']) {
72
+ if (!nonEmptyString(link[field])) errors.push(`${linkPath}.${field} must be a non-empty string`);
73
+ }
74
+ });
75
+ }
76
+
77
+ function validateItem(item, path, ids, errors, subItem = false) {
78
+ if (!isObject(item)) {
79
+ errors.push(`${path} must be an object`);
80
+ return;
81
+ }
82
+ rejectUnknownFields(item, subItem ? SUB_ITEM_FIELDS : ITEM_FIELDS, path, errors);
83
+ for (const field of ['id', 'title']) {
84
+ if (!nonEmptyString(item[field])) errors.push(`${path}.${field} must be a non-empty string`);
85
+ }
86
+ if (nonEmptyString(item.id)) {
87
+ if (ids.has(item.id)) errors.push(`${path}.id duplicates "${item.id}"`);
88
+ ids.add(item.id);
89
+ }
90
+ for (const field of ['instruction', 'expectedResult', 'warning', 'failureGuidance']) {
91
+ validateOptionalString(item[field], `${path}.${field}`, errors);
92
+ }
93
+ if (item.optional !== undefined && typeof item.optional !== 'boolean') {
94
+ errors.push(`${path}.optional must be a boolean`);
95
+ }
96
+ validateCodeBlocks(item.codeBlocks, `${path}.codeBlocks`, errors);
97
+ validateLinks(item.links, `${path}.links`, errors);
98
+
99
+ if (subItem || item.subItems === undefined) return;
100
+ if (!Array.isArray(item.subItems)) {
101
+ errors.push(`${path}.subItems must be an array`);
102
+ return;
103
+ }
104
+ item.subItems.forEach((child, index) => validateItem(child, `${path}.subItems[${index}]`, ids, errors, true));
105
+ }
106
+
107
+ export function validateChecklist(checklist) {
108
+ const errors = [];
109
+ const ids = new Set();
110
+ if (!isObject(checklist)) return ['$ must be an object'];
111
+
112
+ rejectUnknownFields(checklist, TOP_LEVEL_FIELDS, '$', errors);
113
+ for (const field of ['title', 'summary']) {
114
+ if (!nonEmptyString(checklist[field])) errors.push(`$.${field} must be a non-empty string`);
115
+ }
116
+ for (const field of ['intro', 'callout', 'conventions']) {
117
+ validateOptionalString(checklist[field], `$.${field}`, errors);
118
+ }
119
+ if (!Array.isArray(checklist.sections) || checklist.sections.length === 0) {
120
+ errors.push('$.sections must be a non-empty array');
121
+ return errors;
122
+ }
123
+
124
+ checklist.sections.forEach((section, sectionIndex) => {
125
+ const sectionPath = `$.sections[${sectionIndex}]`;
126
+ if (!isObject(section)) {
127
+ errors.push(`${sectionPath} must be an object`);
128
+ return;
129
+ }
130
+ rejectUnknownFields(section, SECTION_FIELDS, sectionPath, errors);
131
+ for (const field of ['number', 'title']) {
132
+ if (!nonEmptyString(section[field])) errors.push(`${sectionPath}.${field} must be a non-empty string`);
133
+ }
134
+ if (!['checklist', 'info'].includes(section.kind)) {
135
+ errors.push(`${sectionPath}.kind must be "checklist" or "info"`);
136
+ }
137
+ validateOptionalString(section.intro, `${sectionPath}.intro`, errors);
138
+ if (!Array.isArray(section.items) || section.items.length === 0) {
139
+ errors.push(`${sectionPath}.items must be a non-empty array`);
140
+ return;
141
+ }
142
+ section.items.forEach((item, itemIndex) => validateItem(item, `${sectionPath}.items[${itemIndex}]`, ids, errors));
143
+ });
144
+
145
+ return errors;
146
+ }
147
+
148
+ export async function loadAndValidateChecklist(path) {
149
+ let checklist;
150
+ try {
151
+ checklist = JSON.parse(await readFile(path, 'utf8'));
152
+ } catch (error) {
153
+ return { errors: [`Unable to read valid JSON: ${error.message}`] };
154
+ }
155
+ const errors = validateChecklist(checklist);
156
+ return errors.length === 0 ? { checklist, errors } : { errors };
157
+ }
158
+
159
+ async function main() {
160
+ const path = process.argv[2];
161
+ if (!path || process.argv.length !== 3) {
162
+ console.error('Usage: node validate.mjs <checklist.json>');
163
+ process.exitCode = 1;
164
+ return;
165
+ }
166
+ const { errors } = await loadAndValidateChecklist(path);
167
+ if (errors.length > 0) {
168
+ for (const error of errors) console.error(`ERROR ${error}`);
169
+ process.exitCode = 1;
170
+ return;
171
+ }
172
+ console.log('OK');
173
+ }
174
+
175
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) await main();
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: forge-a-skill
3
+ description: Forge one codebase rule into a guardrail skill. Use only when the developer invokes /forge-a-skill by name.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Forge a Skill
8
+
9
+ Forge exactly one agent-owned guardrail skill. Do not continue unless the
10
+ developer invoked `/forge-a-skill` by name.
11
+
12
+ ## Workflow
13
+
14
+ ### Step 1: Guard and input
15
+
16
+ Confirm that the developer invoked `/forge-a-skill` by name. Take the rule from
17
+ the invocation arguments. If there is no rule argument, ask one question for
18
+ the rule. Stop if the skill was selected by the model instead of the developer.
19
+
20
+ ### Step 2: Apply the one-job test
21
+
22
+ Ask yourself exactly one question: "Does this rule answer exactly one question
23
+ an agent asks itself?" Split a rule joined by "and", or a rule whose correct
24
+ tool depends on the request, into separate jobs. Forge only the first job and
25
+ save each remaining job for the report as follow-up `/forge-a-skill` runs.
26
+
27
+ ### Step 3: Run the six-field interview
28
+
29
+ Read the repository guidance and its routed knowledge maps first. Then inspect
30
+ the relevant source, tests, configuration, and commands. Derive a candidate
31
+ name from the rule using the naming contract in Step 4, then derive:
32
+
33
+ - proposed anchors;
34
+ - one to three exemplars cited as `path#symbol`;
35
+ - a positive decision procedure;
36
+ - a repository violation check command, or one concrete manual check;
37
+ - current violations, including every finding's path and evidence; and
38
+ - whether a guardrail of that name already exists.
39
+
40
+ Do not treat a map statement as source evidence when a more direct file or
41
+ symbol is available. Complete the scan before the interview only when it has
42
+ one to three direct `path#symbol` exemplars. If none exists, stop and explain
43
+ that the rule cannot be forged from current repository evidence. If an
44
+ existing skill is a guardrail, this run is a **Re-forge**: use its current
45
+ content as the interview defaults and update it in place after approval. If the
46
+ existing canonical skill is not a guardrail, stop rather than replace it.
47
+
48
+ Present all six fields in one message. This is one fixed round; do not spread
49
+ the fields across multiple rounds. Present the one to three exemplars with the
50
+ six fields. Propose Anchors, Decision procedure, and Violation check command
51
+ from the scan. Ask the developer only for Question answered, Why, and
52
+ Exceptions; let them correct any proposal.
53
+
54
+ 1. **Question answered**: the one question this rule answers.
55
+ 2. **Why**: the system scale and use case that make the rule suitable.
56
+ 3. **Anchors**: directories, libraries, layers, or verbs where the rule applies.
57
+ 4. **Decision procedure**: positive steps an agent follows.
58
+ 5. **Exceptions**: known cases where the rule does not apply.
59
+ 6. **Violation check command**: a repository command that detects violations,
60
+ or a concrete manual check when no command exists.
61
+
62
+ ### Step 4: Derive the skill contract
63
+
64
+ - Choose a lowercase, hyphenated, plain trigger-word-led name. It must match the
65
+ directory `.agents/skills/<name>/`.
66
+ - Write a description of at most 200 characters in exactly this form:
67
+ `<rule in one clause>. Use when planning, reviewing, or changing <anchors>.`
68
+ - Use the interview answers to fill
69
+ `.agents/skills/forge-a-skill/templates/guardrail.md`. Keep its eight sections
70
+ in order and keep the body at most 60 lines. Include one to three direct
71
+ `path#symbol` exemplars. Use `None confirmed` only for an exception or
72
+ reference that the available evidence does not show.
73
+ - Put the command and its success criterion in Verification. If there is no
74
+ command, put one concrete `Manual check:` line there.
75
+
76
+ ### Step 5: Show the plan and get approval
77
+
78
+ Show the skill name, the description verbatim, anchors, exemplars, and
79
+ verification command or manual check. List every current violation and require
80
+ one choice for each finding before approval:
81
+
82
+ - **Grandfather**: add it to Known exceptions with its path.
83
+ - **Park**: delegate it to `defer-work`, which requires developer approval. This
84
+ skill does not park the work itself.
85
+
86
+ Count unique project skills and personal skills on the developer's machine;
87
+ do not count harness exposures or count an existing Re-forge twice. Sum their
88
+ description character lengths, including the new or updated guardrail. Use the
89
+ 8,000-character budget and calculate
90
+ `headroom = 8,000 - description characters`. Show the calculated plan line with
91
+ all placeholders replaced:
92
+
93
+ `Skill listing: <project-count> project skills + <personal-count> personal skills; <total> description characters; 8,000-character budget; <headroom> headroom.`
94
+
95
+ If this rule is a consequential decision, offer to invoke
96
+ `recording-decisions`; that skill owns any ADR. If the developer accepts,
97
+ invoke `recording-decisions`, wait for its ADR path, and add that ADR path to
98
+ the planned References before asking for approval. End with one explicit
99
+ approval question. No guardrail file is written before the developer approves
100
+ the complete plan and every violation has a disposition.
101
+
102
+ ### Step 6: Write the guardrail
103
+
104
+ After approval, invoke `defer-work` for each parked finding. Then confirm that
105
+ the name matches its directory, the description has the required form and is
106
+ at most 200 characters, the fixed sections remain in order, Exemplars contains
107
+ one to three `path#symbol` entries, and the body is at most 60 lines. Then
108
+ create a new guardrail, or update it in place for a Re-forge:
109
+
110
+ - Write `.agents/skills/<name>/SKILL.md` from the fixed guardrail template. Add
111
+ each grandfathered finding to Known exceptions with its path.
112
+ - Write `.agents/skills/<name>/evals/evals.json` from
113
+ `.agents/skills/forge-a-skill/templates/probe-set.json`. Replace every
114
+ placeholder with concrete repository language. Keep five should-trigger
115
+ prompts across planning, design, review, editing, and refactoring phrasings,
116
+ and three should-not-trigger prompts outside the anchors. Each
117
+ `expected_output` must state whether the guardrail loads.
118
+
119
+ Do not add the guardrail to `.agents/bearings.json`; forged skills are
120
+ agent-owned project files. Stop rather than replace an existing canonical
121
+ skill that is not a guardrail. Never edit source files or fix violations during
122
+ a forge.
123
+
124
+ ### Step 7: Expose the guardrail
125
+
126
+ If `.agents/bearings.json` does not exist, write only the canonical skill
127
+ directory and state that no harness exposure was created. Otherwise, read
128
+ `harnesses` and `exposure` from the Manifest:
129
+
130
+ - For `symlink`, create `.<harness>/skills/<name>` as the relative symlink
131
+ `../../.agents/skills/<name>`.
132
+ - For `copy`, recursively copy the canonical skill directory to
133
+ `.<harness>/skills/<name>`.
134
+
135
+ Expose it to every configured harness. Do not change the Manifest or harness
136
+ settings, and do not replace an unrelated exposure collision.
137
+
138
+ ### Step 8: Verify and report
139
+
140
+ When a Manifest exists, run `npx bearings verify` and fix only guardrail or
141
+ exposure errors from this run. Finish only when it reports zero failures.
142
+ Report the canonical path, exposure paths, a `git diff` pointer for review, the
143
+ fix list with every violation and its disposition, and all follow-up forges
144
+ from Step 2. When there is no Manifest, clearly state that verification and
145
+ exposure were not available.
146
+
147
+ Recalculate the Step 5 skill-listing math from the files now on disk and repeat
148
+ its exact `Skill listing:` line in the report.
149
+
150
+ If headroom is 1,600 characters or less, advise the developer that they can
151
+ raise `skillListingBudgetFraction`. Report only. Do not edit harness settings.
152
+ Tell the developer to use `skill-creator` description tuning when it is
153
+ installed. Otherwise, tell them to check all probes in a fresh session and
154
+ confirm that should-trigger prompts load the guardrail and should-not-trigger
155
+ prompts do not.
156
+
157
+ ## Wall
158
+
159
+ This skill does not fix violations, record an ADR, park work, edit the Agent
160
+ Seed, edit knowledge maps, edit harness settings, or approve anything on the
161
+ developer's behalf. Delegate an approved ADR to `recording-decisions` and an
162
+ approved deferral to `defer-work`.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: <skill-name>
3
+ description: <description>
4
+ metadata:
5
+ kind: guardrail
6
+ anchors:
7
+ - <anchor>
8
+ ---
9
+
10
+ # <Guardrail title>
11
+
12
+ ## Why
13
+
14
+ <System scale and use case that make this rule suitable.>
15
+
16
+ ## Applies when
17
+
18
+ <Directories, libraries, layers, or verbs governed by this rule.>
19
+
20
+ ## Decision procedure
21
+
22
+ 1. <Positive step.>
23
+
24
+ ## Exemplars
25
+
26
+ - `<path>#<symbol>` - <Established shape to follow. Include one to three exemplar lines.>
27
+
28
+ ## Known exceptions
29
+
30
+ - <Known exception, or None confirmed.>
31
+ - An agent may deviate only after asking the developer, then must add the approved exception here.
32
+
33
+ ## Does not govern
34
+
35
+ <Nearest concern to which this rule must not be applied.>
36
+
37
+ ## Verification
38
+
39
+ `<violation-check-command>`
40
+
41
+ Completion criterion: <Observable successful result.>
42
+
43
+ ## References
44
+
45
+ - <ADR or documentation path, or None confirmed.>
@@ -0,0 +1,45 @@
1
+ {
2
+ "skill_name": "<skill-name>",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "Plan a change that touches <anchor> and explain the approach before editing.",
7
+ "expected_output": "The <skill-name> guardrail loads before the plan is written."
8
+ },
9
+ {
10
+ "id": 2,
11
+ "prompt": "Design a new <rule-subject> for <anchor>.",
12
+ "expected_output": "The <skill-name> guardrail loads while the design is prepared."
13
+ },
14
+ {
15
+ "id": 3,
16
+ "prompt": "Review the proposed work in <anchor> for compliance with repository patterns.",
17
+ "expected_output": "The <skill-name> guardrail loads for the review."
18
+ },
19
+ {
20
+ "id": 4,
21
+ "prompt": "Update <anchor> to support <rule-related-change>.",
22
+ "expected_output": "The <skill-name> guardrail loads before files are changed."
23
+ },
24
+ {
25
+ "id": 5,
26
+ "prompt": "Refactor the <rule-subject> implementation in <anchor>.",
27
+ "expected_output": "The <skill-name> guardrail loads for the edit."
28
+ },
29
+ {
30
+ "id": 6,
31
+ "prompt": "Plan an unrelated change in <outside-anchor>.",
32
+ "expected_output": "The <skill-name> guardrail does not load."
33
+ },
34
+ {
35
+ "id": 7,
36
+ "prompt": "Edit <outside-anchor> to update <unrelated-concern>.",
37
+ "expected_output": "The <skill-name> guardrail does not load."
38
+ },
39
+ {
40
+ "id": 8,
41
+ "prompt": "Explain <neighboring-concern> without changing repository files.",
42
+ "expected_output": "The <skill-name> guardrail does not load."
43
+ }
44
+ ]
45
+ }
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: manual-testplan
3
+ description: Design a small human-run browser or curl test plan from product scope. Use when the developer asks for a manual test plan, acceptance test plan, smoke test, or feature test checklist.
4
+ ---
5
+
6
+ # Manual Test Plan
7
+
8
+ Select the smallest useful set of human-executed cases, then delegate its
9
+ checklist artifact to the `checklist` skill.
10
+
11
+ ## Workflow
12
+
13
+ 1. Establish the test basis. Read the requested issue, specification,
14
+ acceptance criteria, changed behavior, and relevant known defects. Identify
15
+ the primary actor, intended outcome, supported configuration, and explicit
16
+ product risks. Ask a small number of blocking questions when these facts do
17
+ not establish what success means.
18
+ 2. Map the basis to reachable public interfaces. Inspect repository maps,
19
+ runtime wiring, routes, UI flows, API contracts, authentication, feature
20
+ flags, test data, and supported setup, reset, and cleanup commands. Record
21
+ only browser and HTTP interfaces that a human can reach in the stated
22
+ environment.
23
+ 3. Select cases in execution order:
24
+ - Start with the shortest principal happy path that proves the actor reaches
25
+ the final intended outcome with ordinary valid data.
26
+ - Add uncovered acceptance criteria.
27
+ - Add only the highest-value alternatives and failures, selected from
28
+ likelihood and impact, materially different input partitions, boundaries,
29
+ business rules, state transitions, and relevant defect history.
30
+ - Remove duplicate coverage and cosmetic data variations. Keep each case an
31
+ independent, repeatable workflow with one main reason to fail.
32
+ 4. Define each case before delegation. Give it an outcome-focused title, the
33
+ criterion or risk it covers, its selection reason and priority,
34
+ configuration, known initial state, concrete non-secret data, ordered
35
+ actions and observable expectations, cleanup, and useful failure evidence.
36
+ 5. Invoke the `checklist` skill with the selected cases and require the output
37
+ at `checklists/tests/<slug>.json`. The `checklist` skill owns the JSON
38
+ contract, identifiers, checklist structure, validation, library build,
39
+ preview opening, and execution state.
40
+ 6. Review the resulting test content against the test basis. Every acceptance
41
+ criterion must be covered or named as an explicit gap, and every selected
42
+ risk must have a case whose oracle can detect that failure.
43
+
44
+ ## Browser Cases
45
+
46
+ - State the tested build, browser, relevant viewport or device class, user
47
+ role, starting URL, and starting session state.
48
+ - Write actions in user and domain language while naming controls clearly
49
+ enough to find. Put an observable expectation after each important
50
+ validation point and verify the final visible or persisted outcome.
51
+ - Use supported fixtures, APIs, or seed commands for prerequisite data when
52
+ that keeps the browser workflow focused. Capture the current screen and exact
53
+ observed text first on failure.
54
+
55
+ ## Curl Cases
56
+
57
+ - State the shell, base URL, authentication prerequisite, environment
58
+ variables, and data setup. Use secret placeholders such as `$TOKEN` through
59
+ the repository's established secret mechanism.
60
+ - Give a directly runnable `curl` command with the evidenced method, quoted
61
+ URL, headers, and body. State the exact expected HTTP status, relevant
62
+ headers, body fields, and externally observable side effects.
63
+ - Distinguish the HTTP result from curl's transfer exit status. State required
64
+ redirect and cookie behavior, preserve TLS verification unless the test
65
+ environment explicitly requires otherwise, and treat unexpected transport
66
+ failures as blocked environment failures.
67
+
68
+ ## Evidence Guardrail
69
+
70
+ Requirements state intent; running code, configuration, contracts, tests, and
71
+ developer confirmation establish reachable behavior. Ask for missing critical
72
+ facts. Never invent commands, routes, URLs, credentials, selectors, status
73
+ codes, response fields, messages, seed data, or cleanup behavior.
74
+
75
+ ## Completion
76
+
77
+ Finish only when the test basis and reachable interfaces are explicit, the
78
+ principal happy path proves the intended outcome, each additional case has a
79
+ traceable risk or coverage reason, every oracle is concrete and observable,
80
+ and the `checklist` skill completes one checklist under `checklists/tests/`.