@bongos/core 1.19.526 → 1.19.528

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 (50) hide show
  1. package/.bongos-core.json +81 -51
  2. package/.claude/skills/blocker-solve/SKILL.md +2 -1
  3. package/.claude/skills/builder-box/SKILL.md +2 -1
  4. package/.claude/skills/builder-connect/SKILL.md +2 -1
  5. package/.claude/skills/builder-end/SKILL.md +2 -1
  6. package/.claude/skills/builder-exit/SKILL.md +2 -1
  7. package/.claude/skills/builder-key/SKILL.md +2 -1
  8. package/.claude/skills/builder-sequence/SKILL.md +2 -1
  9. package/.claude/skills/builder-stage/SKILL.md +2 -1
  10. package/.claude/skills/design/SKILL.md +2 -1
  11. package/.claude/skills/goal-create/SKILL.md +2 -1
  12. package/.claude/skills/goal-review/SKILL.md +2 -1
  13. package/.claude/skills/grade-audit/SKILL.md +2 -1
  14. package/.claude/skills/grade-recover/SKILL.md +2 -1
  15. package/.claude/skills/ideate/SKILL.md +2 -1
  16. package/.claude/skills/new-project/SKILL.md +2 -1
  17. package/.claude/skills/otb-character-review/SKILL.md +2 -1
  18. package/.claude/skills/paint/SKILL.md +2 -1
  19. package/.claude/skills/scan-before-install/SKILL.md +2 -1
  20. package/.claude/skills/ship-check/SKILL.md +44 -0
  21. package/.claude/skills/strand-fix/SKILL.md +2 -1
  22. package/.claude/skills/worktree-clean/SKILL.md +2 -1
  23. package/.gitattributes +6 -2
  24. package/docs/file-map.md +4 -1
  25. package/docs/module-api-changelog.md +4 -0
  26. package/docs/recipes/windows-builders.md +46 -0
  27. package/modules/grading/generated-provenance.js +1 -1
  28. package/modules/grading/grader-subagent.js +20 -1
  29. package/modules/grading/grader.js +24 -1
  30. package/modules/lifecycle/cascade.js +86 -4
  31. package/modules/lifecycle/tests/cascade.mjs +63 -6
  32. package/package-lock.json +2 -2
  33. package/package.json +1 -1
  34. package/scripts/gds/claude-materialize.js +7 -2
  35. package/scripts/gds/fitness.js +40 -1
  36. package/scripts/gds/gen-repo-map.js +3 -1
  37. package/scripts/gds/session-cost.js +10 -7
  38. package/scripts/gds/session-digest.js +7 -4
  39. package/scripts/gds/ship-check.js +78 -0
  40. package/scripts/gds/ship-regen.js +2 -2
  41. package/scripts/gds/skill-lint.js +171 -0
  42. package/scripts/gds/strand-watch.js +3 -0
  43. package/src/module-api.js +1 -1
  44. package/tests/claude_materialize.mjs +10 -0
  45. package/tests/edit_hints.mjs +78 -0
  46. package/tests/git_merge_regen.mjs +1 -1
  47. package/tests/grader_root_outage.mjs +109 -0
  48. package/tests/hooks_drain.mjs +8 -2
  49. package/tests/repo_map.mjs +1 -1
  50. package/tests/skill_lint.mjs +73 -0
@@ -0,0 +1,73 @@
1
+ // tests/skill_lint.mjs — the SKILL.md frontmatter linter reproduces the YAML failure modes that
2
+ // silently broke ten skills (task 1003548), and the committed skills all pass it.
3
+ import { strict as assert } from 'node:assert';
4
+ import { test } from 'node:test';
5
+ import { createRequire } from 'node:module';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
+ const lint = require('../scripts/gds/skill-lint.js');
12
+
13
+ const fm = (desc, extra = '') => `---\nname: demo\ndescription: ${desc}\n${extra}---\n\nBody text.\n`;
14
+ const run = (text) => lint.lintSkillFile(text, { file: path.join('x', '.claude', 'skills', 'demo', 'SKILL.md') });
15
+
16
+ test('a plain description containing ": " is an error (the whole frontmatter would fail to parse)', () => {
17
+ const r = run(fm('Show the box and help them get on it: reconnect or wake.'));
18
+ assert.equal(r.errors.length, 1, r.errors.join('; '));
19
+ assert.match(r.errors[0], /colon-space/);
20
+ assert.match(r.errors[0], /description: >-/, 'the message carries the fix');
21
+ });
22
+
23
+ test('a plain description containing " #" is an error (YAML would truncate it as a comment)', () => {
24
+ const r = run(fm('Connect the box (ADR 0031, #701) and open the terminal.'));
25
+ assert.equal(r.errors.length, 1);
26
+ assert.match(r.errors[0], / #/);
27
+ });
28
+
29
+ test('the same text as a folded block scalar is clean', () => {
30
+ const r = run('---\nname: demo\ndescription: >-\n Show the box and help them get on it: reconnect (ADR 0031, #701) or wake.\n---\n\nBody.\n');
31
+ assert.deepEqual(r.errors, []);
32
+ assert.match(r.description, /reconnect \(ADR 0031, #701\)/);
33
+ });
34
+
35
+ test('a double-quoted description is clean; an unclosed quote is an error', () => {
36
+ assert.deepEqual(run(fm('"Do it: now"')).errors, []);
37
+ assert.equal(run(fm('"Do it: now')).errors.length, 1);
38
+ });
39
+
40
+ test('a flow list in requires is valid YAML and not flagged', () => {
41
+ const r = run(fm('Fine text.', 'requires: [gh, main-checkout]\n'));
42
+ assert.deepEqual(r.errors, []);
43
+ });
44
+
45
+ test('missing name or description is an error; a folder/name mismatch is a warning', () => {
46
+ assert.match(run('---\nname: demo\n---\nBody\n').errors.join(' '), /missing or empty "description"/);
47
+ assert.match(run('---\ndescription: x\n---\nBody\n').errors.join(' '), /missing "name"/);
48
+ const r = lint.lintSkillFile('---\nname: other\ndescription: x\n---\nBody\n', { file: path.join('x', '.claude', 'skills', 'demo', 'SKILL.md') });
49
+ assert.match(r.warnings.join(' '), /differs from its folder/);
50
+ });
51
+
52
+ test('a long description warns; an over-cap one errors', () => {
53
+ assert.equal(run(fm('a'.repeat(450))).warnings.filter((w) => /chars/.test(w)).length, 1);
54
+ assert.equal(run(fm('a'.repeat(1100))).errors.filter((w) => /cap/.test(w)).length, 1);
55
+ });
56
+
57
+ test('no frontmatter at all is an error', () => {
58
+ assert.equal(run('# Title\n\nBody.\n').errors.length, 1);
59
+ });
60
+
61
+ test('the hook path filter matches only .claude/skills/<name>/SKILL.md', () => {
62
+ assert.ok(lint.isSkillMd('C:\\repo\\.claude\\skills\\dev\\SKILL.md'));
63
+ assert.ok(lint.isSkillMd('/repo/.claude/skills/dev/SKILL.md'));
64
+ assert.ok(!lint.isSkillMd('/repo/.claude/skills/dev/notes.md'));
65
+ assert.ok(!lint.isSkillMd('/repo/modules/ui-design/skills/dev/SKILL.md'));
66
+ });
67
+
68
+ test('every committed skill parses (the ten that did not on 2026-09-03 must stay fixed)', () => {
69
+ const report = lint.lintSkills(lint.listSkillFiles(ROOT));
70
+ const broken = report.results.filter((r) => r.errors.length).map((r) => path.relative(ROOT, r.file) + ': ' + r.errors.join('; '));
71
+ assert.deepEqual(broken, [], broken.join('\n'));
72
+ assert.ok(report.results.length >= 40, 'the repo ships dozens of skills; the lister found ' + report.results.length);
73
+ });