@onlooker-community/ecosystem 0.33.0 → 0.34.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.
Files changed (57) hide show
  1. package/.agents/skills/beads/SKILL.md +80 -0
  2. package/.agents/skills/beads/agents/openai.yaml +4 -0
  3. package/.claude/settings.json +13 -0
  4. package/.claude-plugin/plugin.json +1 -1
  5. package/.codex/config.toml +2 -0
  6. package/.codex/hooks.json +51 -0
  7. package/.markdownlint.json +3 -0
  8. package/.release-please-manifest.json +8 -8
  9. package/AGENTS.md +135 -0
  10. package/CHANGELOG.md +14 -0
  11. package/CLAUDE.md +56 -1
  12. package/docs/lesson-promotion-pipeline.md +210 -0
  13. package/docs/superpowers/plans/2026-08-09-lesson-transform.md +1537 -0
  14. package/docs/superpowers/specs/2026-08-09-lesson-transform-design.md +261 -0
  15. package/package.json +3 -2
  16. package/plugins/archivist/scripts/lib/archivist-config.sh +10 -34
  17. package/plugins/assayer/.claude-plugin/plugin.json +1 -1
  18. package/plugins/assayer/CHANGELOG.md +7 -0
  19. package/plugins/assayer/scripts/lib/assayer-config.sh +39 -62
  20. package/plugins/cartographer/scripts/lib/cartographer-config.sh +11 -29
  21. package/plugins/compass/scripts/lib/compass-config.sh +16 -46
  22. package/plugins/counsel/scripts/lib/counsel-config.sh +15 -46
  23. package/plugins/curator/.claude-plugin/plugin.json +1 -1
  24. package/plugins/curator/CHANGELOG.md +7 -0
  25. package/plugins/curator/scripts/lib/curator-config.sh +19 -44
  26. package/plugins/echo/scripts/lib/echo-config.sh +30 -59
  27. package/plugins/governor/scripts/lib/governor-config.sh +11 -41
  28. package/plugins/historian/scripts/lib/historian-config.sh +9 -33
  29. package/plugins/inspector/.claude-plugin/plugin.json +1 -1
  30. package/plugins/inspector/CHANGELOG.md +7 -0
  31. package/plugins/inspector/scripts/lib/inspector-config.sh +39 -63
  32. package/plugins/librarian/.claude-plugin/plugin.json +1 -1
  33. package/plugins/librarian/CHANGELOG.md +7 -0
  34. package/plugins/librarian/config.json +4 -0
  35. package/plugins/librarian/schema/PROVENANCE.json +7 -0
  36. package/plugins/librarian/schema/lesson-applies-to.subschema.json +74 -0
  37. package/plugins/librarian/schema/lesson-evidence.subschema.json +36 -0
  38. package/plugins/librarian/scripts/hooks/librarian-session-end.sh +26 -0
  39. package/plugins/librarian/scripts/lib/librarian-config.sh +10 -34
  40. package/plugins/librarian/scripts/lib/librarian-lesson-storage.sh +135 -0
  41. package/plugins/librarian/scripts/lib/librarian-lesson-transform.sh +311 -0
  42. package/plugins/librarian/scripts/lib/librarian-lesson-validate.sh +140 -0
  43. package/plugins/lineage/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lineage/CHANGELOG.md +7 -0
  45. package/plugins/lineage/scripts/lib/lineage-config.sh +17 -53
  46. package/plugins/scribe/.claude-plugin/plugin.json +1 -1
  47. package/plugins/scribe/CHANGELOG.md +7 -0
  48. package/plugins/scribe/scripts/lib/scribe-config.sh +17 -47
  49. package/plugins/tribunal/.claude-plugin/plugin.json +1 -1
  50. package/plugins/tribunal/CHANGELOG.md +7 -0
  51. package/plugins/tribunal/scripts/lib/tribunal-config.sh +25 -63
  52. package/plugins/warden/scripts/lib/warden-config.sh +17 -54
  53. package/scripts/lib/config-loader.sh +8 -1
  54. package/scripts/lint/check-lesson-schema-drift.mjs +36 -0
  55. package/test/bats/librarian-lesson-transform.bats +609 -0
  56. package/test/node/lesson-schema-drift.test.mjs +28 -0
  57. package/test/node/lesson-validate-agreement.test.mjs +154 -0
@@ -0,0 +1,154 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { describe, it } from 'node:test';
6
+ import { fileURLToPath } from 'node:url';
7
+ import Ajv from 'ajv';
8
+
9
+ // Drives the same candidate corpus through two independent validation
10
+ // mechanisms and asserts they agree:
11
+ //
12
+ // 1. librarian_lesson_validate_candidate (bash + jq), the runtime gate —
13
+ // ajv is unavailable at runtime because installed marketplace plugins
14
+ // ship no node_modules (ADR-005), so this is a hand-written mirror.
15
+ // 2. ajv against the vendored lesson-evidence and lesson-applies-to
16
+ // sub-schemas, the contract the jq rules are meant to mirror.
17
+ //
18
+ // The jq rules and the vendored schemas have been proven able to disagree
19
+ // in both directions (a jq-too-loose direction and a jq-too-strict
20
+ // direction), so this is not a redundant check against the drift-guard in
21
+ // lesson-schema-drift.test.mjs, which only confirms the vendored files
22
+ // themselves are present, valid JSON, and pinned to the right provenance —
23
+ // it never exercises the jq rules against them.
24
+ //
25
+ // The vendored evidence schema declares `format: "date-time"` on
26
+ // observed_at. ajv ignores unknown formats without the ajv-formats package,
27
+ // which is deliberately not a dependency here (see ADR-005 and the F4
28
+ // review note) — validity for that field comes entirely from the sibling
29
+ // `pattern` keyword, which ajv always enforces regardless of format
30
+ // support. `strict: false` only silences the resulting "unknown format
31
+ // ignored" warning; it does not weaken the comparison.
32
+
33
+ const HERE = dirname(fileURLToPath(import.meta.url));
34
+ const REPO_ROOT = resolve(HERE, '..', '..');
35
+ const SCHEMA_DIR = join(REPO_ROOT, 'plugins', 'librarian', 'schema');
36
+ const VALIDATE_LIB = join(REPO_ROOT, 'plugins', 'librarian', 'scripts', 'lib', 'librarian-lesson-validate.sh');
37
+
38
+ const evidenceSchema = JSON.parse(readFileSync(join(SCHEMA_DIR, 'lesson-evidence.subschema.json'), 'utf8'));
39
+ const appliesToSchema = JSON.parse(readFileSync(join(SCHEMA_DIR, 'lesson-applies-to.subschema.json'), 'utf8'));
40
+
41
+ const ajv = new Ajv({ strict: false, allErrors: true, logger: false });
42
+ const validateEvidence = ajv.compile(evidenceSchema);
43
+ const validateAppliesTo = ajv.compile(appliesToSchema);
44
+
45
+ // True when both vendored sub-schemas accept their half of the candidate.
46
+ // Only these two fields are covered by a vendored schema — claim, rationale,
47
+ // and the versions-keys-subset-of-stack cross-field rule are jq-only checks
48
+ // with no schema counterpart (JSON Schema cannot express the latter), so the
49
+ // corpus below always holds those fixed at a schema-satisfying value and
50
+ // varies only the fields the two vendored sub-schemas actually constrain.
51
+ function schemaAccepts(candidate) {
52
+ return validateEvidence(candidate.evidence) && validateAppliesTo(candidate.applies_to);
53
+ }
54
+
55
+ // Shells out to the real runtime gate rather than reimplementing its logic,
56
+ // so this test tracks the actual function, not a description of it.
57
+ function jqAccepts(candidate) {
58
+ const result = spawnSync(
59
+ 'bash',
60
+ ['-c', `source '${VALIDATE_LIB}' && librarian_lesson_validate_candidate "$CANDIDATE_JSON"`],
61
+ { env: { ...process.env, CANDIDATE_JSON: JSON.stringify(candidate) }, encoding: 'utf8' },
62
+ );
63
+ return result.status === 0;
64
+ }
65
+
66
+ function baseCandidate() {
67
+ return {
68
+ claim: 'Vitest 4 cannot import vite/module-runner on Vite 5',
69
+ rationale: 'vite/module-runner ships in Vite 6; Vitest 4 assumes it exists.',
70
+ evidence: {
71
+ artifact_ids: ['01KZ45MKAM734ZS7JK24D2DK0R'],
72
+ session_ids: ['sess-1'],
73
+ project_key: '6a7678979e31',
74
+ observed_at: '2026-08-03T15:59:48Z',
75
+ resolution: 'Pin vitest to 3.x until Vite 6 lands.',
76
+ },
77
+ applies_to: {
78
+ stack: ['vite'],
79
+ scope: { kind: 'versioned', versions: { vite: '<6' } },
80
+ file_patterns: [],
81
+ task_kinds: [],
82
+ },
83
+ };
84
+ }
85
+
86
+ function withEvidence(overrides) {
87
+ const candidate = baseCandidate();
88
+ candidate.evidence = { ...candidate.evidence, ...overrides };
89
+ return candidate;
90
+ }
91
+
92
+ function withAppliesTo(overrides) {
93
+ const candidate = baseCandidate();
94
+ candidate.applies_to = { ...candidate.applies_to, ...overrides };
95
+ return candidate;
96
+ }
97
+
98
+ function withRange(range) {
99
+ return withAppliesTo({ scope: { kind: 'versioned', versions: { vite: range } } });
100
+ }
101
+
102
+ function assertAgree(name, candidate, expected) {
103
+ const jq = jqAccepts(candidate);
104
+ const schema = schemaAccepts(candidate);
105
+ assert.equal(jq, schema, `${name}: jq=${jq} schema=${schema} disagree`);
106
+ assert.equal(jq, expected, `${name}: expected ${expected}, jq validator said ${jq}`);
107
+ }
108
+
109
+ describe('lesson validator / vendored schema agreement', () => {
110
+ it('agree on a well-formed baseline candidate', () => {
111
+ assertAgree('baseline', baseCandidate(), true);
112
+ });
113
+
114
+ it('agree that an extra evidence property is rejected (additionalProperties: false)', () => {
115
+ assertAgree('extra evidence key', withEvidence({ confidence: 0.9 }), false);
116
+ });
117
+
118
+ it('agree that an empty string in stack is rejected (minLength: 1 on items)', () => {
119
+ assertAgree('empty stack entry', withAppliesTo({ stack: ['vite', ''] }), false);
120
+ });
121
+
122
+ it('agree that a non-string in file_patterns is rejected (items type: string)', () => {
123
+ assertAgree('non-string file_patterns entry', withAppliesTo({ file_patterns: [123] }), false);
124
+ });
125
+
126
+ it('agree that a leading-zero lower bound is accepted (>=05)', () => {
127
+ assertAgree('leading-zero range', withRange('>=05'), true);
128
+ });
129
+
130
+ it('agree that an extra applies_to property is rejected (additionalProperties: false)', () => {
131
+ assertAgree('extra applies_to key', withAppliesTo({ extra: true }), false);
132
+ });
133
+
134
+ it('agree that a non-string in task_kinds is rejected (items type: string)', () => {
135
+ assertAgree('non-string task_kinds entry', withAppliesTo({ task_kinds: [true] }), false);
136
+ });
137
+
138
+ const rangeCorpus = [
139
+ ['<6', true],
140
+ ['>=4', true],
141
+ ['>=4 <6', true],
142
+ ['^5.4.21', false],
143
+ ['~5', false],
144
+ ['5.x', false],
145
+ ['>=0', false],
146
+ ['>=05', true],
147
+ ];
148
+
149
+ for (const [range, expected] of rangeCorpus) {
150
+ it(`agree on range "${range}" (expect ${expected ? 'accept' : 'reject'})`, () => {
151
+ assertAgree(`range ${range}`, withRange(range), expected);
152
+ });
153
+ }
154
+ });