@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.
- package/.github/copilot-instructions.md +1 -0
- package/.github/prompts/ingest-publication-slice.prompt.md +43 -0
- package/.github/prompts/self-critique.prompt.md +167 -43
- package/.kiro/steering/ingest-publication-slice.md +42 -0
- package/.windsurf/rules/ingest-publication-slice.md +41 -0
- package/README.md +5 -3
- package/adapters/claude/olko-product/skills/ingest-publication-slice/SKILL.md +44 -0
- package/adapters/claude/olko-reflection/skills/self-critique/SKILL.md +167 -43
- package/adapters/claude/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/adapters/claude/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
- package/adapters/codex/olko-product/README.md +1 -0
- package/adapters/cursor/olko-product/skills/ingest-publication-slice/SKILL.md +44 -0
- package/adapters/cursor/olko-reflection/skills/self-critique/SKILL.md +167 -43
- package/adapters/cursor/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/adapters/cursor/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
- package/adapters/grok/olko-product/skills/ingest-publication-slice/SKILL.md +44 -0
- package/adapters/grok/olko-reflection/skills/self-critique/SKILL.md +167 -43
- package/adapters/grok/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/adapters/grok/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
- package/adapters/hermes/olko-product/README.md +1 -0
- package/catalog/skills.json +23 -1
- package/docs/superpowers/plans/2026-10-27-self-critique-v2.md +306 -0
- package/docs/superpowers/specs/2026-10-27-self-critique-v2-spec.md +104 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/skills/ingest-publication-slice/SKILL.md +43 -0
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/skills/self-critique/SKILL.md +167 -43
- package/plugins/olko-reflection/skills/self-critique/scripts/claim-ledger.mjs +159 -0
- package/plugins/olko-reflection/skills/self-critique/scripts/rule-store.mjs +193 -0
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
- 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`
|
package/catalog/skills.json
CHANGED
|
@@ -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:
|
|
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",
|