@bongos/core 1.20.42 → 1.20.44
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/.bongos-core.json +99 -74
- package/.claude/skills/backlog-review/SKILL.md +1 -0
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/bug-triage/SKILL.md +1 -0
- package/.claude/skills/builder-backup/SKILL.md +1 -0
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-cost/SKILL.md +1 -0
- package/.claude/skills/builder-exit/SKILL.md +1 -0
- package/.claude/skills/builder-key/SKILL.md +1 -1
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-setup/SKILL.md +1 -1
- package/.claude/skills/collab-review/SKILL.md +1 -1
- package/.claude/skills/design/SKILL.md +1 -1
- package/.claude/skills/design-sync/SKILL.md +1 -0
- package/.claude/skills/feedback/SKILL.md +1 -1
- package/.claude/skills/figma-design-sync/SKILL.md +1 -0
- package/.claude/skills/goal-close/SKILL.md +184 -0
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/goal-review/SKILL.md +5 -102
- package/.claude/skills/goal-uat/SKILL.md +5 -81
- package/.claude/skills/grade-audit/SKILL.md +6 -85
- package/.claude/skills/grade-recover/SKILL.md +1 -1
- package/.claude/skills/grade-sweep/SKILL.md +174 -0
- package/.claude/skills/grader-health/SKILL.md +5 -72
- package/.claude/skills/idea-triage/SKILL.md +1 -0
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +1 -0
- package/.claude/skills/owner-review/SKILL.md +40 -0
- package/.claude/skills/planning-session/SKILL.md +1 -0
- package/.claude/skills/priority-session/SKILL.md +1 -0
- package/.claude/skills/read-session-export/SKILL.md +1 -1
- package/.claude/skills/recall/SKILL.md +1 -1
- package/.claude/skills/scan-before-install/SKILL.md +1 -1
- package/.claude/skills/session-handoff/SKILL.md +1 -1
- package/.claude/skills/worktree-clean/SKILL.md +1 -1
- package/docs/api/openapi.json +1 -1
- package/docs/file-map.md +12 -10
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +3 -0
- package/docs/onboarding/slash-commands.md +17 -11
- package/docs/packs/artist.md +1 -1
- package/docs/packs/engineer.md +4 -5
- package/docs/page-readings.json +21 -21
- package/modules/copy-desk/module.json +1 -1
- package/{.claude → modules/copy-desk}/skills/tweak/SKILL.md +1 -0
- package/modules/hall-ui/public/collab.css +8 -1
- package/modules/hall-ui/public/oversight.css +0 -3
- package/modules/lifecycle/dependency-advisory.js +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/scripts/gds/fitness-ratchets.js +2 -0
- package/scripts/gds/module-artifact.js +52 -2
- package/scripts/gds/module.js +57 -4
- package/scripts/gds/skill-lint.js +32 -9
- package/src/bongos/routes/modules.js +5 -4
- package/src/module-api.js +1 -1
- package/src/module-loader/manifest-schema.js +28 -0
- package/tests/hall_lead_row_home.mjs +21 -0
- package/tests/module_manifest.mjs +22 -0
- package/tests/module_store_publish.mjs +88 -4
- package/tests/module_store_publish_route.mjs +20 -1
- package/tests/skill_grade_audit.mjs +34 -17
- package/tests/skill_grader_health.mjs +28 -10
- package/tests/skill_lint.mjs +37 -0
- package/tests/skill_menu_ruling.mjs +99 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// tests/skill_grader_health.mjs — the /grader-health
|
|
2
|
-
// the
|
|
3
|
-
// outage runs, pass-rate drift,
|
|
4
|
-
// LIVE data surfaces, emitting exact
|
|
5
|
-
// read-only (describe, never confirm/flip —
|
|
1
|
+
// tests/skill_grader_health.mjs — the grader HEALTH check (once /grader-health, now
|
|
2
|
+
// Part A of the merged /grade-sweep — task 1004471) covers the four health axes from
|
|
3
|
+
// the grader-hardening evaluation §6 (task 1002656): outage runs, pass-rate drift,
|
|
4
|
+
// cost anomalies, and stale model pins — reading LIVE data surfaces, emitting exact
|
|
5
|
+
// --regrade remediation lines, and staying read-only (describe, never confirm/flip —
|
|
6
|
+
// ADR 0158 §2; no approval step on anyone's ship — ADR 0162). /grader-health stays
|
|
7
|
+
// typeable as a hidden alias.
|
|
6
8
|
import assert from 'node:assert/strict';
|
|
7
9
|
import { test } from 'node:test';
|
|
8
10
|
import { readFileSync, existsSync } from 'node:fs';
|
|
@@ -10,16 +12,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
10
12
|
import path from 'node:path';
|
|
11
13
|
|
|
12
14
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
-
const SKILL = path.join(ROOT, '.claude', 'skills', '
|
|
15
|
+
const SKILL = path.join(ROOT, '.claude', 'skills', 'grade-sweep', 'SKILL.md');
|
|
16
|
+
const ALIAS = path.join(ROOT, '.claude', 'skills', 'grader-health', 'SKILL.md');
|
|
14
17
|
|
|
15
18
|
// .claude/skills is INSTANCE-materialized; a neutral core checkout carries none
|
|
16
19
|
// (task 1002470) — skip there, same guard shape as file_map.mjs.
|
|
17
20
|
const skillsDirExists = existsSync(path.join(ROOT, '.claude', 'skills'));
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
// Part A of the merged file: from its heading to Part B.
|
|
23
|
+
const partA = (md) => md.slice(md.indexOf('## Part A'), md.indexOf('## Part B'));
|
|
24
|
+
|
|
25
|
+
test('the grader health check (grade-sweep Part A) covers the four health axes on live data', (t) => {
|
|
20
26
|
if (!skillsDirExists) return t.skip('.claude/skills not materialized in this checkout');
|
|
21
|
-
assert.ok(existsSync(SKILL), '.claude/skills/
|
|
22
|
-
const
|
|
27
|
+
assert.ok(existsSync(SKILL), '.claude/skills/grade-sweep/SKILL.md must exist (task 1004471, was grader-health — task 1002656)');
|
|
28
|
+
const whole = readFileSync(SKILL, 'utf8');
|
|
29
|
+
const md = partA(whole);
|
|
30
|
+
assert.ok(md.length > 1000, 'Part A is present');
|
|
23
31
|
|
|
24
32
|
// Live data surfaces, named concretely.
|
|
25
33
|
assert.match(md, /\/api\/bongos\/public\/grades/, 'reads the public grade trend');
|
|
@@ -44,5 +52,15 @@ test('/grader-health exists and covers the four health axes on live data', (t) =
|
|
|
44
52
|
assert.match(md, /--probe/, 'offers the optional live calibration probe');
|
|
45
53
|
assert.match(md, /grade-smoke\.js/, 'names the probe script');
|
|
46
54
|
assert.match(md, /read-only/i, 'states the read-only contract');
|
|
47
|
-
assert.match(
|
|
55
|
+
assert.match(whole, /ADR 0162/, 'anchors the no-approval-step-on-a-ship constraint');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('/grader-health still works when typed: a hidden alias that runs grade-sweep Part A (task 1004471)', (t) => {
|
|
59
|
+
if (!skillsDirExists) return t.skip('.claude/skills not materialized in this checkout');
|
|
60
|
+
const md = readFileSync(ALIAS, 'utf8');
|
|
61
|
+
assert.match(md, /^name: grader-health$/m);
|
|
62
|
+
assert.match(md, /^disable-model-invocation: true$/m, 'hidden from the model\'s listing, typeable as /grader-health');
|
|
63
|
+
assert.match(md, /grade-sweep\/SKILL\.md/, 'points at the merged skill by path');
|
|
64
|
+
assert.match(md, /Part A/, 'runs the health half only');
|
|
65
|
+
assert.match(md, /--probe/, 'passes --probe through');
|
|
48
66
|
});
|
package/tests/skill_lint.mjs
CHANGED
|
@@ -138,6 +138,43 @@ test('the listing counts only RESIDENT skills: an off module\'s skill is linted
|
|
|
138
138
|
fs.rmSync(root, { recursive: true, force: true });
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
test('a HIDDEN skill (disable-model-invocation: true) is linted but not counted toward the listing budget, and is reported on its own (task 1004471)', () => {
|
|
142
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-lint-hidden-'));
|
|
143
|
+
const put = (rel, text) => { const p = path.join(root, rel); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, text); };
|
|
144
|
+
const skill = (n, extra = '') => `---\nname: ${n}\ndescription: >-\n ${'y'.repeat(100)}\n${extra}---\nbody\n`;
|
|
145
|
+
put('.claude/skills/shown/SKILL.md', skill('shown'));
|
|
146
|
+
put('.claude/skills/tucked/SKILL.md', skill('tucked', 'disable-model-invocation: true\n'));
|
|
147
|
+
put('.claude/skills/false-flag/SKILL.md', skill('false-flag', 'disable-model-invocation: false\n'));
|
|
148
|
+
const files = lint.listSkillFiles(root);
|
|
149
|
+
const report = lint.lintSkills(files, { resident: lint.residentSkillFiles(root, { files }) });
|
|
150
|
+
assert.equal(report.results.length, 3, 'all three are linted');
|
|
151
|
+
assert.equal(report.listedSkills, 2, 'only a literal true hides — false stays listed');
|
|
152
|
+
assert.equal(report.listingChars, ('shown'.length + 100) + ('false-flag'.length + 100));
|
|
153
|
+
assert.equal(report.hiddenSkills, 1);
|
|
154
|
+
assert.equal(report.hiddenChars, 'tucked'.length + 100);
|
|
155
|
+
assert.deepEqual(report.hiddenNames, ['tucked']);
|
|
156
|
+
const out = lint.format(report, { relTo: root });
|
|
157
|
+
assert.match(out, /listing 215 chars over 2 listed skill/);
|
|
158
|
+
assert.match(out, /1 hidden skill\(s\).*not counted\): 106 chars — tucked/);
|
|
159
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('a hidden skill is exempt from the long-description warning (it costs no listing) but not from the hard cap', () => {
|
|
163
|
+
const long = 'z'.repeat(lint.DESC_WARN_CHARS + 10);
|
|
164
|
+
assert.equal(run(fm(long, 'disable-model-invocation: true\n')).warnings.length, 0);
|
|
165
|
+
assert.equal(run(fm(long)).warnings.length, 1, 'the same text listed still warns');
|
|
166
|
+
assert.equal(run(fm('z'.repeat(lint.DESC_HARD_CHARS + 1), 'disable-model-invocation: true\n')).errors.length, 1);
|
|
167
|
+
assert.equal(lint.isHidden({ 'disable-model-invocation': 'TRUE ' }), true);
|
|
168
|
+
assert.equal(lint.isHidden({ 'disable-model-invocation': 'yes' }), false, 'anything but true leaves it listed');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('the real default listing is under the ~8,000-char budget once hidden skills drop out (task 1004471)', () => {
|
|
172
|
+
const files = lint.listSkillFiles(ROOT);
|
|
173
|
+
const report = lint.lintSkills(files, { resident: lint.residentSkillFiles(ROOT, { files }) });
|
|
174
|
+
assert.equal(report.overBudget, false, `listing is ${report.listingChars} chars over ${report.listedSkills} listed skills`);
|
|
175
|
+
assert.ok(report.hiddenSkills >= 12, `the owner's hide list is in effect (${report.hiddenSkills} hidden)`);
|
|
176
|
+
});
|
|
177
|
+
|
|
141
178
|
test('every committed skill parses (the ten that did not on 2026-09-03 must stay fixed)', () => {
|
|
142
179
|
const report = lint.lintSkills(lint.listSkillFiles(ROOT));
|
|
143
180
|
const broken = report.results.filter((r) => r.errors.length).map((r) => path.relative(ROOT, r.file) + ': ' + r.errors.join('; '));
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// tests/skill_menu_ruling.mjs — the owner's skill-menu ruling (task 1004471, blocker
|
|
2
|
+
// 1000139) holds: which skills the model is shown, which are hidden but still typeable
|
|
3
|
+
// as /name, and the three merges (/owner-review, /goal-close, /grade-sweep) with every
|
|
4
|
+
// old command still answering. Pinned skill by skill, so a later edit that re-lists a
|
|
5
|
+
// hidden skill, hides a safety net, or breaks an old /name fails here by name.
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
+
const lint = require('../scripts/gds/skill-lint.js');
|
|
16
|
+
|
|
17
|
+
const byName = new Map(lint.listSkillFiles(ROOT).map((f) => [path.basename(path.dirname(f)), f]));
|
|
18
|
+
const read = (n) => fs.readFileSync(byName.get(n), 'utf8');
|
|
19
|
+
const info = (n) => lint.lintSkillFile(read(n), { file: byName.get(n) });
|
|
20
|
+
|
|
21
|
+
// The ruling, as written into the task.
|
|
22
|
+
const LISTED_TRIMMED = ['grade-recover', 'collab-review', 'session-handoff', 'builder-claim', 'builder-sequence', 'blocker-solve', 'design', 'goal-create', 'blocker-review', 'builder-reauth', 'read-session-export', 'recall', 'worktree-clean', 'merge-mode', 'builder-setup', 'builder-key', 'bongos-feedback', 'feedback', 'builder-redteam', 'scan-before-install', 'owner-review'];
|
|
23
|
+
const LISTED_AS_IS = ['strand-fix', 'builder-ship', 'builder-start', 'fix-task', 'ask-for-help', 'ship-check', 'status', 'builder-end', 'builder-release', 'builder-stage'];
|
|
24
|
+
const HIDDEN = ['planning-session', 'goal-close', 'grade-sweep', 'builder-exit', 'builder-backup', 'builder-cost', 'new-project', 'design-sync', 'figma-design-sync', 'tweak'];
|
|
25
|
+
const ALIASES = ['backlog-review', 'bug-triage', 'idea-triage', 'priority-session', 'goal-uat', 'goal-review', 'grade-audit', 'grader-health'];
|
|
26
|
+
|
|
27
|
+
test('every skill the ruling keeps listed is present, listed, and (where trimmed) at most 250 chars', () => {
|
|
28
|
+
for (const n of [...LISTED_TRIMMED, ...LISTED_AS_IS]) {
|
|
29
|
+
assert.ok(byName.has(n), `${n} exists`);
|
|
30
|
+
const r = info(n);
|
|
31
|
+
assert.equal(r.hidden, false, `${n} must stay in the model's listing`);
|
|
32
|
+
assert.deepEqual(r.errors, [], `${n} parses`);
|
|
33
|
+
}
|
|
34
|
+
const over = LISTED_TRIMMED.filter((n) => info(n).description.length > 250).map((n) => `${n} ${info(n).description.length}`);
|
|
35
|
+
assert.deepEqual(over, [], 'trimmed descriptions are <= 250 chars');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('every skill the ruling hides — and every old name a merge replaced — is hidden but still a typeable skill', () => {
|
|
39
|
+
for (const n of [...HIDDEN, ...ALIASES]) {
|
|
40
|
+
assert.ok(byName.has(n), `/${n} still exists, so typing it still works`);
|
|
41
|
+
assert.equal(info(n).hidden, true, `${n} carries disable-model-invocation: true`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('the safety nets and owner-pinned triggers survive the trim', () => {
|
|
46
|
+
const d = (n) => info(n).description;
|
|
47
|
+
assert.match(d('builder-redteam'), /"I found a security issue"/);
|
|
48
|
+
assert.match(d('scan-before-install'), /"is this plugin safe to install"/);
|
|
49
|
+
assert.match(d('builder-reauth'), /"Bongos CLI session is no longer valid"/, 'fires on the CLI\'s own error string');
|
|
50
|
+
assert.match(d('builder-setup'), /"no Bongos session"/, 'fires on the CLI\'s own error string');
|
|
51
|
+
assert.match(d('builder-key'), /never accept a key pasted in chat/, 'keeps the hard rule in the listing');
|
|
52
|
+
assert.match(d('bongos-feedback'), /unasked/, 'keeps its unprompted trigger');
|
|
53
|
+
for (const n of [...LISTED_TRIMMED, ...LISTED_AS_IS, ...HIDDEN, ...ALIASES]) {
|
|
54
|
+
const fm = read(n).split('\n---')[0];
|
|
55
|
+
for (const k of ['plain', 'reach-for', 'cost']) assert.match(fm, new RegExp(`^${k}:`, 'm'), `${n} keeps its ${k}: line (the hall Commands page reads it)`);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('/owner-review is the listed front door to backlog -> bugs -> ideas -> priorities, and plain words open each step', () => {
|
|
60
|
+
const md = read('owner-review');
|
|
61
|
+
const desc = info('owner-review').description;
|
|
62
|
+
for (const phrase of ['"review the backlog"', '"triage bugs"', '"triage ideas"', '"review the inbox"']) assert.ok(desc.includes(phrase), `listing routes ${phrase}`);
|
|
63
|
+
const order = ['backlog-review', 'bug-triage', 'idea-triage', 'priority-session'].map((s) => md.indexOf(`.claude/skills/${s}/SKILL.md`));
|
|
64
|
+
assert.ok(order.every((i) => i > 0), 'each step names its playbook by path');
|
|
65
|
+
assert.deepEqual([...order].sort((a, b) => a - b), order, 'in the ruling\'s order');
|
|
66
|
+
for (const step of ['backlog', 'bugs', 'ideas', 'priorities']) assert.match(md, new RegExp('`' + step + '`'), `/owner-review ${step} opens one step`);
|
|
67
|
+
assert.match(md, /Read that step's `SKILL\.md` with the Read tool/, 'the model reads a hidden step by path — the Skill tool cannot load it');
|
|
68
|
+
for (const s of ['backlog-review', 'bug-triage', 'idea-triage', 'priority-session']) assert.match(md, new RegExp('`/' + s + '`'), `/${s} is named as still typeable`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('the nightly idea-triage routine still finds the playbook it reads by path', () => {
|
|
72
|
+
const nightly = fs.readFileSync(path.join(ROOT, '.claude/scheduled-tasks/idea-triage-nightly/SKILL.md'), 'utf8');
|
|
73
|
+
assert.match(nightly, /\.claude\/skills\/idea-triage\/SKILL\.md/);
|
|
74
|
+
assert.ok(fs.existsSync(path.join(ROOT, '.claude/skills/idea-triage/SKILL.md')));
|
|
75
|
+
assert.ok(read('idea-triage').length > 5000, 'the full playbook, not an alias stub');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('/goal-close carries both halves; /goal-uat and /goal-review run one each', () => {
|
|
79
|
+
const md = read('goal-close');
|
|
80
|
+
const p1 = md.indexOf('## Part 1'), p2 = md.indexOf('## Part 2');
|
|
81
|
+
assert.ok(p1 > 0 && p2 > p1, 'UAT part, then residue part');
|
|
82
|
+
assert.match(md.slice(p1, p2), /uat\.js --queue/, 'Part 1 reads the UAT queue');
|
|
83
|
+
assert.match(md.slice(p1, p2), /--recording/, 'Part 1 signs off with a recording');
|
|
84
|
+
assert.match(md.slice(p2), /done-when\/pending-review/, 'Part 2 reads the review queue');
|
|
85
|
+
assert.match(md.slice(p2), /override_reason/, 'Part 2 overrides only with a written reason');
|
|
86
|
+
assert.match(md, /xenos/, 'one Metic+ gate at the top');
|
|
87
|
+
assert.match(read('goal-uat'), /goal-close\/SKILL\.md[\s\S]*Part 1/);
|
|
88
|
+
assert.match(read('goal-review'), /goal-close\/SKILL\.md[\s\S]*Part 2/);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('/tweak lives in the copy-desk module, which contributes it (default-on, so it stays installed)', () => {
|
|
92
|
+
assert.ok(fs.existsSync(path.join(ROOT, 'modules/copy-desk/skills/tweak/SKILL.md')));
|
|
93
|
+
assert.ok(!fs.existsSync(path.join(ROOT, '.claude/skills/tweak/SKILL.md')) || !require('node:child_process').execFileSync('git', ['ls-files', '.claude/skills/tweak'], { cwd: ROOT, encoding: 'utf8' }).trim(), 'no tracked core copy (an untracked materialised one is fine)');
|
|
94
|
+
const mj = JSON.parse(fs.readFileSync(path.join(ROOT, 'modules/copy-desk/module.json'), 'utf8'));
|
|
95
|
+
assert.deepEqual(mj.contributes.skills, ['tweak']);
|
|
96
|
+
assert.equal(mj.default, true);
|
|
97
|
+
const files = lint.listSkillFiles(ROOT);
|
|
98
|
+
assert.ok(lint.residentSkillFiles(ROOT, { files }).some((f) => path.basename(path.dirname(f)) === 'tweak'), 'resident on a default instance');
|
|
99
|
+
});
|