@olegkoval/agent-skills 1.59.2 → 1.60.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 (40) hide show
  1. package/.github/copilot-instructions.md +1 -0
  2. package/.github/prompts/ingest-publication-slice.prompt.md +43 -0
  3. package/.github/prompts/self-critique.prompt.md +167 -43
  4. package/.kiro/steering/ingest-publication-slice.md +42 -0
  5. package/.windsurf/rules/ingest-publication-slice.md +41 -0
  6. package/README.md +5 -3
  7. package/adapters/claude/olko-product/skills/ingest-publication-slice/SKILL.md +44 -0
  8. package/adapters/claude/olko-reflection/skills/self-critique/SKILL.md +167 -43
  9. package/adapters/claude/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  10. package/adapters/claude/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  11. package/adapters/codex/olko-product/README.md +1 -0
  12. package/adapters/cursor/olko-product/skills/ingest-publication-slice/SKILL.md +44 -0
  13. package/adapters/cursor/olko-reflection/skills/self-critique/SKILL.md +167 -43
  14. package/adapters/cursor/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  15. package/adapters/cursor/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  16. package/adapters/grok/olko-product/skills/ingest-publication-slice/SKILL.md +44 -0
  17. package/adapters/grok/olko-reflection/skills/self-critique/SKILL.md +167 -43
  18. package/adapters/grok/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  19. package/adapters/grok/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  20. package/adapters/hermes/olko-product/README.md +1 -0
  21. package/catalog/skills.json +23 -1
  22. package/docs/superpowers/plans/2026-10-27-self-critique-v2.md +306 -0
  23. package/docs/superpowers/specs/2026-10-27-self-critique-v2-spec.md +104 -0
  24. package/package.json +1 -1
  25. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  26. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  27. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  28. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  29. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  30. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  31. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  32. package/plugins/olko-product/skills/ingest-publication-slice/SKILL.md +43 -0
  33. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  34. package/plugins/olko-reflection/skills/self-critique/SKILL.md +167 -43
  35. package/plugins/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
  36. package/plugins/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
  37. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  38. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  39. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
  40. package/scripts/lib/catalog.mjs +1 -1
@@ -0,0 +1,159 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { pathToFileURL } from 'node:url'
3
+
4
+ export const PENALTIES = {
5
+ refuted_load_bearing: 25,
6
+ refuted: 10,
7
+ unverifiable_load_bearing: 10,
8
+ unverifiable: 4,
9
+ inferred_accepted: 5,
10
+ missed_load_bearing: 15,
11
+ missed: 5,
12
+ only_critic_found: 5,
13
+ }
14
+
15
+ const ORIGINS = ['author', 'critic', 'both']
16
+ const STATUSES = ['verified', 'refuted', 'unverifiable', 'inferred']
17
+ const CHECKED_BY = ['command', 'read', 'inferred']
18
+
19
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
20
+ const isText = (v) => typeof v === 'string' && v.length > 0
21
+
22
+ const enumError = (path, field, value, allowed) => {
23
+ if (value === undefined) return [`${path}.${field} missing`]
24
+ return allowed.includes(value) ? [] : [`${path}.${field} invalid: ${value}`]
25
+ }
26
+
27
+ const claimErrors = (c, i) => {
28
+ const path = `claims[${i}]`
29
+ if (!isRecord(c)) return [`${path} invalid: not an object`]
30
+ return [
31
+ ...(isText(c.id) ? [] : [`${path}.id missing`]),
32
+ ...(isText(c.text) ? [] : [`${path}.text missing`]),
33
+ ...(typeof c.load_bearing === 'boolean' ? [] : [`${path}.load_bearing missing`]),
34
+ ...enumError(path, 'origin', c.origin, ORIGINS),
35
+ ...enumError(path, 'status', c.status, STATUSES),
36
+ ...enumError(path, 'checked_by', c.checked_by, CHECKED_BY),
37
+ ...(['verified', 'refuted'].includes(c.status) && c.checked_by === 'inferred'
38
+ ? [`${path}.checked_by: verified and refuted claims must be checked by command or read`]
39
+ : []),
40
+ ...(['verified', 'refuted'].includes(c.status) && !(typeof c.receipt === 'string' && c.receipt.trim().length > 0)
41
+ ? [`${path}.receipt: verified and refuted claims require a non-empty receipt`]
42
+ : []),
43
+ ]
44
+ }
45
+
46
+ const missedErrors = (m, i) => {
47
+ const path = `missed[${i}]`
48
+ if (!isRecord(m)) return [`${path} invalid: not an object`]
49
+ return [
50
+ ...(isText(m.id) ? [] : [`${path}.id missing`]),
51
+ ...(isText(m.text) ? [] : [`${path}.text missing`]),
52
+ ...(typeof m.load_bearing === 'boolean' ? [] : [`${path}.load_bearing missing`]),
53
+ ...enumError(path, 'checked_by', m.checked_by, CHECKED_BY),
54
+ ]
55
+ }
56
+
57
+ export const validateLedger = (ledger) => {
58
+ if (!isRecord(ledger)) return ['ledger invalid: not an object']
59
+ if (!Array.isArray(ledger.claims)) return ['claims missing']
60
+ if (!Array.isArray(ledger.missed)) return ['missed missing']
61
+ return [...ledger.claims.flatMap(claimErrors), ...ledger.missed.flatMap(missedErrors)]
62
+ }
63
+
64
+ const claimPenalties = (c) => {
65
+ const lb = c.load_bearing
66
+ const byStatus = {
67
+ refuted: [lb ? 'refuted_load_bearing' : 'refuted'],
68
+ unverifiable: [lb ? 'unverifiable_load_bearing' : 'unverifiable'],
69
+ inferred: ['inferred_accepted'],
70
+ verified: c.origin === 'critic' ? ['only_critic_found'] : [],
71
+ }
72
+ return byStatus[c.status]
73
+ }
74
+
75
+ const missedPenalty = (m) => (m.load_bearing ? 'missed_load_bearing' : 'missed')
76
+
77
+ const tally = (keys) =>
78
+ keys.reduce((acc, k) => ({ ...acc, [k]: (acc[k] ?? 0) + PENALTIES[k] }), {})
79
+
80
+ export const scoreLedger = (ledger) => {
81
+ const breakdown = tally([...ledger.claims.flatMap(claimPenalties), ...ledger.missed.filter((m) => m.checked_by !== 'inferred').map(missedPenalty)])
82
+ const total = Object.values(breakdown).reduce((a, b) => a + b, 0)
83
+ return { score: Math.max(0, 100 - total), breakdown }
84
+ }
85
+
86
+ export const ratchet = (prev, next) => {
87
+ const p = scoreLedger(prev).score
88
+ const n = scoreLedger(next).score
89
+ return p > n ? { winner: 'prev', score: p } : { winner: 'next', score: n }
90
+ }
91
+
92
+ const mk = (id, lb, status) => ({
93
+ id,
94
+ text: `claim ${id}`,
95
+ load_bearing: lb,
96
+ origin: 'both',
97
+ status,
98
+ checked_by: status === 'inferred' ? 'inferred' : 'read',
99
+ receipt: 'anchor',
100
+ })
101
+
102
+ // 95 = 5 (one inferred); 72 = 10+10+4+4; 40 = 25+25+10
103
+ const ANCHOR_LEDGERS = [
104
+ { label: 'strong', claims: [mk('a1', false, 'inferred')] },
105
+ { label: 'middling', claims: [mk('b1', false, 'refuted'), mk('b2', false, 'refuted'), mk('b3', false, 'unverifiable'), mk('b4', false, 'unverifiable')] },
106
+ { label: 'weak', claims: [mk('c1', true, 'refuted'), mk('c2', true, 'refuted'), mk('c3', true, 'unverifiable')] },
107
+ ]
108
+
109
+ export const anchors = () =>
110
+ ANCHOR_LEDGERS.map(({ label, claims }) => {
111
+ const ledger = { version: 1, claims, missed: [] }
112
+ return { label, ledger, score: scoreLedger(ledger).score }
113
+ })
114
+
115
+ const cell = (s) => String(s ?? '').replaceAll('|', '\\|').replaceAll('\n', ' ')
116
+
117
+ const claimRow = (c) =>
118
+ `| ${[c.id, c.text, c.origin, c.status, c.checked_by, c.receipt].map(cell).join(' | ')} |`
119
+
120
+ export const summaryMarkdown = (ledger) =>
121
+ [
122
+ '| id | claim | origin | status | checked_by | receipt |',
123
+ '| --- | --- | --- | --- | --- | --- |',
124
+ ...ledger.claims.map(claimRow),
125
+ ].join('\n')
126
+
127
+ const fail = (message) => {
128
+ process.stderr.write(`${JSON.stringify({ error: message })}\n`)
129
+ process.exit(2)
130
+ }
131
+
132
+ const loadLedger = (path) => {
133
+ let parsed
134
+ try {
135
+ parsed = JSON.parse(readFileSync(path, 'utf8'))
136
+ } catch (e) {
137
+ return fail(`cannot read ${path}: ${e.message}`)
138
+ }
139
+ const errors = validateLedger(parsed)
140
+ if (errors.length > 0) return fail(errors.join('; '))
141
+ return parsed
142
+ }
143
+
144
+ const COMMANDS = {
145
+ score: ([a]) => scoreLedger(loadLedger(a)),
146
+ ratchet: ([a, b]) => ratchet(loadLedger(a), loadLedger(b)),
147
+ anchors: () => anchors(),
148
+ summary: ([a]) => summaryMarkdown(loadLedger(a)),
149
+ }
150
+
151
+ const main = (argv) => {
152
+ const [cmd, ...args] = argv
153
+ const run = COMMANDS[cmd]
154
+ if (!run) return fail(`unknown command: ${cmd}`)
155
+ const out = run(args)
156
+ process.stdout.write(typeof out === 'string' ? `${out}\n` : `${JSON.stringify(out, null, 2)}\n`)
157
+ }
158
+
159
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main(process.argv.slice(2))
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ // Rule store over a directory of Claude Code memory files (Markdown + frontmatter).
3
+ // Frontmatter is parsed line by line so unrelated lines survive a rewrite byte for byte.
4
+ import fs from 'node:fs';
5
+ import { randomUUID } from 'node:crypto';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const FENCE = '---';
10
+ const EVIDENCE_LINE = /^\s*-\s.*\b20\d\d-\d\d-\d\d\b/;
11
+ const DAY_MS = 86_400_000;
12
+
13
+ const topLevelKey = (line) => /^([A-Za-z_][\w-]*):(?: (.*))?$/.exec(line);
14
+ const metaEntry = (line) => /^ {2}([A-Za-z_][\w-]*):(?: (.*))?$/.exec(line);
15
+
16
+ function splitFrontmatter(text) {
17
+ if (!text.startsWith(`${FENCE}\n`)) return null;
18
+ const rest = text.slice(FENCE.length + 1);
19
+ const closeAt = rest.startsWith(`${FENCE}\n`) || rest === FENCE
20
+ ? 0
21
+ : rest.indexOf(`\n${FENCE}\n`) === -1
22
+ ? (rest.endsWith(`\n${FENCE}`) ? rest.length - FENCE.length - 1 : -1)
23
+ : rest.indexOf(`\n${FENCE}\n`) + 1;
24
+ if (closeAt === -1) return null;
25
+ const front = rest.slice(0, closeAt);
26
+ const body = rest.slice(closeAt + FENCE.length + 1);
27
+ return { frontLines: front === '' ? [] : front.replace(/\n$/, '').split('\n'), body };
28
+ }
29
+
30
+ // top keeps every frontmatter line in order; metadata children are lifted into `metadata`.
31
+ function readTop(frontLines) {
32
+ const top = [];
33
+ const metadata = {};
34
+ let inMeta = false;
35
+ for (const line of frontLines) {
36
+ const meta = inMeta ? metaEntry(line) : null;
37
+ if (meta) {
38
+ metadata[meta[1]] = meta[2] ?? '';
39
+ continue;
40
+ }
41
+ const key = topLevelKey(line);
42
+ inMeta = key?.[1] === 'metadata';
43
+ top.push({ key: key?.[1] ?? null, line });
44
+ }
45
+ return { top, metadata };
46
+ }
47
+
48
+ export function parseRule(text) {
49
+ const parts = splitFrontmatter(text);
50
+ if (!parts) return null;
51
+ const { top, metadata } = readTop(parts.frontLines);
52
+ const valueOf = (key) => {
53
+ const entry = top.find((t) => t.key === key);
54
+ return entry ? (topLevelKey(entry.line)?.[2] ?? '') : '';
55
+ };
56
+ return { name: valueOf('name'), description: valueOf('description'), metadata, body: parts.body, top };
57
+ }
58
+
59
+ function topLines(rule) {
60
+ const hasMeta = rule.top.some((t) => t.key === 'metadata');
61
+ const top = hasMeta || Object.keys(rule.metadata).length === 0
62
+ ? rule.top
63
+ : [...rule.top, { key: 'metadata', line: 'metadata:' }];
64
+ return top.flatMap((t) =>
65
+ t.key === 'metadata'
66
+ ? [t.line, ...Object.entries(rule.metadata).map(([k, v]) => ` ${k}: ${v}`)]
67
+ : [t.line],
68
+ );
69
+ }
70
+
71
+ export function serializeRule(rule) {
72
+ const lines = topLines(rule);
73
+ return `${FENCE}\n${lines.length ? `${lines.join('\n')}\n` : ''}${FENCE}\n${rule.body}`;
74
+ }
75
+
76
+ function readRuleFiles(dir) {
77
+ return fs
78
+ .readdirSync(dir)
79
+ .filter((f) => f.endsWith('.md'))
80
+ .sort()
81
+ .flatMap((file) => {
82
+ const rule = parseRule(fs.readFileSync(path.join(dir, file), 'utf8'));
83
+ return rule && rule.name ? [{ file, rule }] : [];
84
+ });
85
+ }
86
+
87
+ export const listRules = (dir) => readRuleFiles(dir).map((e) => e.rule);
88
+
89
+ export const checklistMarkdown = (dir) =>
90
+ listRules(dir).map((r) => `- [${r.name}]: ${r.description}\n`).join('');
91
+
92
+ export function bumpFire(rule, isoDate) {
93
+ const fires = Number.parseInt(rule.metadata.fires ?? '0', 10) || 0;
94
+ return { ...rule, metadata: { ...rule.metadata, fires: String(fires + 1), last_fired: isoDate } };
95
+ }
96
+
97
+ export function fire(dir, name, isoDate) {
98
+ const entry = readRuleFiles(dir).find((e) => e.rule.name === name);
99
+ if (!entry) throw new Error(`rule not found: ${name}`);
100
+ const updated = bumpFire(entry.rule, isoDate);
101
+ const target = path.join(dir, entry.file);
102
+ const temporary = path.join(dir, `.${entry.file}.${randomUUID()}.tmp`);
103
+ try {
104
+ fs.writeFileSync(temporary, serializeRule(updated), { flag: 'wx', mode: fs.statSync(target).mode });
105
+ fs.renameSync(temporary, target);
106
+ } finally {
107
+ fs.rmSync(temporary, { force: true });
108
+ }
109
+ return updated;
110
+ }
111
+
112
+ const daysBetween = (fromIso, toIso) => (Date.parse(toIso) - Date.parse(fromIso)) / DAY_MS;
113
+
114
+ const ISO_DAY = /^\d{4}-\d{2}-\d{2}/;
115
+
116
+ // age signal: frontmatter modified, else created, else file mtime
117
+ function ageDate(rule, mtimeIso) {
118
+ const declared = [rule.metadata.modified, rule.metadata.created].find((v) => ISO_DAY.test(v ?? ''));
119
+ return declared ?? mtimeIso;
120
+ }
121
+
122
+ export function pruneReason(rule, { idleDays, today, mtimeIso }) {
123
+ if (rule.metadata.fires === undefined) {
124
+ return daysBetween(ageDate(rule, mtimeIso), today) > idleDays ? 'never-fired' : null;
125
+ }
126
+ const last = rule.metadata.last_fired;
127
+ return last && daysBetween(last, today) > idleDays ? 'idle' : null;
128
+ }
129
+
130
+ export function prune(dir, { idleDays, today }) {
131
+ return readRuleFiles(dir).flatMap(({ file, rule }) => {
132
+ if (rule.metadata.rule_store !== 'self-critique') return [];
133
+ const mtimeIso = fs.statSync(path.join(dir, file)).mtime.toISOString().slice(0, 10);
134
+ const reason = pruneReason(rule, { idleDays, today, mtimeIso });
135
+ return reason ? [{ name: rule.name, reason }] : [];
136
+ });
137
+ }
138
+
139
+ export const countEvidence = (body) => body.split('\n').filter((l) => EVIDENCE_LINE.test(l)).length;
140
+
141
+ export function evidenceCheck(dir, { max }) {
142
+ return listRules(dir).flatMap((rule) => {
143
+ const count = countEvidence(rule.body);
144
+ return count > max ? [{ name: rule.name, count }] : [];
145
+ });
146
+ }
147
+
148
+ function parseArgs(argv) {
149
+ const [command, ...rest] = argv;
150
+ const flags = {};
151
+ const positional = [];
152
+ for (let i = 0; i < rest.length; i += 1) {
153
+ const arg = rest[i];
154
+ if (arg.startsWith('--')) {
155
+ flags[arg.slice(2)] = rest[i + 1];
156
+ i += 1;
157
+ } else {
158
+ positional.push(arg);
159
+ }
160
+ }
161
+ return { command, flags, positional };
162
+ }
163
+
164
+ const todayIso = () => new Date().toISOString().slice(0, 10);
165
+
166
+ function runCli(argv) {
167
+ const { command, flags, positional } = parseArgs(argv);
168
+ const dir = flags.dir;
169
+ if (!dir) throw new Error('--dir is required');
170
+ const date = flags.date ?? todayIso();
171
+ const commands = {
172
+ list: () => JSON.stringify(listRules(dir).map(({ top, ...rule }) => rule), null, 2),
173
+ checklist: () => checklistMarkdown(dir).trimEnd(),
174
+ fire: () => {
175
+ const { top, ...rule } = fire(dir, positional[0], date);
176
+ return JSON.stringify(rule, null, 2);
177
+ },
178
+ prune: () => JSON.stringify(prune(dir, { idleDays: Number(flags['idle-days'] ?? 60), today: date }), null, 2),
179
+ 'evidence-check': () => JSON.stringify(evidenceCheck(dir, { max: 3 }), null, 2),
180
+ };
181
+ const run = commands[command];
182
+ if (!run) throw new Error(`usage: rule-store.mjs ${Object.keys(commands).join('|')} --dir <dir>`);
183
+ return run();
184
+ }
185
+
186
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
187
+ try {
188
+ console.log(runCli(process.argv.slice(2)));
189
+ } catch (err) {
190
+ console.error(err.message);
191
+ process.exit(1);
192
+ }
193
+ }
@@ -5,6 +5,7 @@
5
5
  Run `./scripts/install-hermes-symlinks.sh` from the repository root to link these skills
6
6
  into the active Hermes profile. The canonical packages are:
7
7
 
8
+ - `plugins/olko-product/skills/ingest-publication-slice/SKILL.md`
8
9
  - `plugins/olko-product/skills/product-builder/SKILL.md`
9
10
  - `plugins/olko-product/skills/mvp-oneshot/SKILL.md`
10
11
  - `plugins/olko-product/skills/ux-ui-audit-loop/SKILL.md`
@@ -514,6 +514,28 @@
514
514
  "name": "olko-product",
515
515
  "description": "Take a product idea to a shippable build: MVP passes, full-stack scaffolds, UX/UI quality loops, launch plans.",
516
516
  "skills": [
517
+ {
518
+ "name": "ingest-publication-slice",
519
+ "lookupName": "olko:ingest-publication-slice",
520
+ "path": "plugins/olko-product/skills/ingest-publication-slice",
521
+ "description": "Build and verify a small end-to-end publication path for validated ingested data, including atomic persistence, read fallback, and static artifact precedence.",
522
+ "tags": [
523
+ "ingest",
524
+ "publication",
525
+ "testing",
526
+ "workflow"
527
+ ],
528
+ "adapters": [
529
+ "claude",
530
+ "codex",
531
+ "cursor",
532
+ "copilot",
533
+ "grok",
534
+ "windsurf",
535
+ "kiro",
536
+ "hermes"
537
+ ]
538
+ },
517
539
  {
518
540
  "name": "product-builder",
519
541
  "lookupName": "olko:product-builder",
@@ -846,7 +868,7 @@
846
868
  "name": "self-critique",
847
869
  "lookupName": "olko:self-critique",
848
870
  "path": "plugins/olko-reflection/skills/self-critique",
849
- "description": "Adversarially critique your own last answer: spawn a critic agent that verifies claims against live sources, finds the related issue or pattern you missed, then loop until satisfied and report where you were wrong, iterations-to-satisfy, the numeric improvement, and a final score.",
871
+ "description": "Adversarially critique your own last answer: a blind critic agent re-derives and verifies every claim against live sources, the score is computed from a labelled claim ledger, you revise under a ratchet, restate the corrected answer, and turn each caught error into a durable rule with a fire counter.",
850
872
  "tags": [
851
873
  "critical-thinking",
852
874
  "self-review",