@olegkoval/agent-skills 1.40.1 → 1.41.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/prompts/open-source-publisher.prompt.md +3 -4
- package/.kiro/steering/open-source-publisher.md +3 -4
- package/.windsurf/rules/open-source-publisher.md +3 -4
- package/adapters/claude/olko-github-pr/skills/lekker-review/SKILL.md +15 -17
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/implementation.md +2 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/prover.md +13 -10
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/quality.md +1 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/test-quality.md +7 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/triage-quality.md +1 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/agents/verifier.md +12 -9
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/artifact-page.md +6 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/output-format.md +5 -8
- package/adapters/claude/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
- package/adapters/claude/olko-release/skills/open-source-publisher/SKILL.md +4 -5
- package/adapters/cursor/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
- package/adapters/cursor/olko-release/skills/open-source-publisher/SKILL.md +4 -5
- package/adapters/grok/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
- package/adapters/grok/olko-release/skills/open-source-publisher/SKILL.md +4 -5
- 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-github-pr/skills/lekker-review/README.md +9 -9
- package/plugins/olko-github-pr/skills/lekker-review/SKILL.md +15 -17
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/implementation.md +2 -1
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/prover.md +13 -10
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/quality.md +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/test-quality.md +7 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/triage-quality.md +1 -1
- package/plugins/olko-github-pr/skills/lekker-review/references/agents/verifier.md +12 -9
- package/plugins/olko-github-pr/skills/lekker-review/references/artifact-page.md +6 -1
- package/plugins/olko-github-pr/skills/lekker-review/references/output-format.md +5 -8
- package/plugins/olko-github-pr/skills/lekker-review/workflow.js +176 -117
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/skills/self-critique/scripts/critique-nudge.mjs +3 -0
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/skills/open-source-publisher/SKILL.md +4 -5
- 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 +3 -0
|
@@ -8,43 +8,68 @@ export const meta = {
|
|
|
8
8
|
// Schemas
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
|
|
11
|
+
const FINDINGS_ARRAY_SCHEMA = {
|
|
12
|
+
type: 'array',
|
|
13
|
+
items: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
required: ['file', 'line', 'severity', 'title', 'description', 'badCode', 'fix'],
|
|
16
|
+
properties: {
|
|
17
|
+
file: { type: 'string' },
|
|
18
|
+
line: { type: 'integer' },
|
|
19
|
+
severity: { enum: ['critical', 'important', 'observation', 'idiomatic'] },
|
|
20
|
+
title: { type: 'string' },
|
|
21
|
+
description: { type: 'string' },
|
|
22
|
+
badCode: { type: 'string' },
|
|
23
|
+
fix: { type: 'string' },
|
|
24
|
+
precedent: { type: 'string' },
|
|
25
|
+
rule: { type: 'string', minLength: 1, pattern: '\\S' },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const MOCK_SMELLS_SCHEMA = {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
required: ['file', 'line', 'description', 'fix'],
|
|
35
|
+
properties: {
|
|
36
|
+
file: { type: 'string' },
|
|
37
|
+
line: { type: 'integer' },
|
|
38
|
+
description: { type: 'string' },
|
|
39
|
+
fix: { type: 'string' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
11
44
|
const FINDINGS_SCHEMA = {
|
|
12
45
|
type: 'object',
|
|
13
46
|
required: ['findings'],
|
|
14
47
|
properties: {
|
|
15
|
-
findings:
|
|
16
|
-
type: 'array',
|
|
17
|
-
items: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
required: ['file', 'line', 'severity', 'title', 'description', 'badCode', 'fix'],
|
|
20
|
-
properties: {
|
|
21
|
-
file: { type: 'string' },
|
|
22
|
-
line: { type: 'integer' },
|
|
23
|
-
severity: { enum: ['critical', 'important', 'observation', 'idiomatic'] },
|
|
24
|
-
title: { type: 'string' },
|
|
25
|
-
description: { type: 'string' },
|
|
26
|
-
badCode: { type: 'string' },
|
|
27
|
-
fix: { type: 'string' },
|
|
28
|
-
precedent: { type: 'string' },
|
|
29
|
-
rule: { enum: ['TS-1', 'TS-2', 'GQL-1', 'PR-1'] },
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
48
|
+
findings: FINDINGS_ARRAY_SCHEMA,
|
|
33
49
|
acCoverage: { type: 'string' },
|
|
34
50
|
mutationSlip: { type: 'string' },
|
|
35
51
|
coverageVerdict: { type: 'string' },
|
|
36
|
-
mockSmells:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
mockSmells: MOCK_SMELLS_SCHEMA,
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const IMPLEMENTATION_SCHEMA = {
|
|
57
|
+
type: 'object',
|
|
58
|
+
required: ['findings', 'acCoverage'],
|
|
59
|
+
properties: {
|
|
60
|
+
findings: FINDINGS_ARRAY_SCHEMA,
|
|
61
|
+
acCoverage: { type: 'string' },
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const TEST_QUALITY_SCHEMA = {
|
|
66
|
+
type: 'object',
|
|
67
|
+
required: ['findings', 'coverageVerdict', 'mutationSlip', 'mockSmells'],
|
|
68
|
+
properties: {
|
|
69
|
+
findings: FINDINGS_ARRAY_SCHEMA,
|
|
70
|
+
coverageVerdict: { type: 'string' },
|
|
71
|
+
mutationSlip: { type: 'string' },
|
|
72
|
+
mockSmells: MOCK_SMELLS_SCHEMA,
|
|
48
73
|
},
|
|
49
74
|
}
|
|
50
75
|
|
|
@@ -80,10 +105,11 @@ const CRITIC_SCHEMA = {
|
|
|
80
105
|
|
|
81
106
|
const PROOF_SCHEMA = {
|
|
82
107
|
type: 'object',
|
|
83
|
-
required: ['attempted', 'proven', 'reason'],
|
|
108
|
+
required: ['attempted', 'proven', 'outcome', 'reason'],
|
|
84
109
|
properties: {
|
|
85
110
|
attempted: { type: 'boolean' },
|
|
86
111
|
proven: { type: 'boolean' },
|
|
112
|
+
outcome: { enum: ['proven', 'passed', 'inconclusive', 'not_attempted'] },
|
|
87
113
|
reason: { type: 'string' },
|
|
88
114
|
testCode: { type: 'string' },
|
|
89
115
|
testCommand: { type: 'string' },
|
|
@@ -95,54 +121,8 @@ const PROOF_SCHEMA = {
|
|
|
95
121
|
// Helpers
|
|
96
122
|
// ---------------------------------------------------------------------------
|
|
97
123
|
|
|
98
|
-
const HARD_RULES = ['TS-1', 'TS-2', 'GQL-1', 'PR-1']
|
|
99
|
-
|
|
100
|
-
// Does the finding's OWN evidence corroborate the hard rule it claims?
|
|
101
|
-
//
|
|
102
|
-
// A `rule` tag is worth a lot: it preserves Critical severity AND skips
|
|
103
|
-
// adversarial verification. Nothing used to check that the tag matched the
|
|
104
|
-
// finding, so any reviewer agent could bypass the verifier by writing
|
|
105
|
-
// `rule: "TS-1"`. Seen in practice: a missing-test-coverage finding and a
|
|
106
|
-
// comment-policy finding were both tagged TS-1, which is about `as X` casts
|
|
107
|
-
// and `any`, and both shipped Critical and unverified.
|
|
108
|
-
//
|
|
109
|
-
// An uncorroborated tag does NOT lose its severity here; it simply stops being
|
|
110
|
-
// exempt, so the verifier weighs it like any other finding. That is the
|
|
111
|
-
// conservative direction: unproven claims get scrutiny, not a free pass.
|
|
112
|
-
function hardRuleCorroborated(finding) {
|
|
113
|
-
const evidence = [finding.badCode, finding.description, finding.title]
|
|
114
|
-
.map(function(x) { return String(x || '') }).join('\n')
|
|
115
|
-
const file = String(finding.file || '')
|
|
116
|
-
// TS-1 is judged on the QUOTED CODE only. Prose is full of the words `as` and
|
|
117
|
-
// `any` ("any cart with items"), and matching those re-opened the very bypass
|
|
118
|
-
// this function exists to close.
|
|
119
|
-
const code = String(finding.badCode || '')
|
|
120
|
-
|
|
121
|
-
switch (finding.rule) {
|
|
122
|
-
case 'TS-1':
|
|
123
|
-
// A cast to a capitalised type OR to a lowercase built-in: `as string`
|
|
124
|
-
// is every bit as much a TS-1 violation as `as Foo`, and missing it sent
|
|
125
|
-
// a genuine hard-rule finding to a verifier whose five challenges cannot
|
|
126
|
-
// answer a standards claim, where it could be dropped outright.
|
|
127
|
-
return /\bas\s+(?:[A-Z_$][\w$]*|string|number|boolean|bigint|symbol|object|unknown|never|any|const)\b/.test(code)
|
|
128
|
-
|| /:\s*any\b|<\s*any[\s,>]|\bany\[\]/.test(code)
|
|
129
|
-
case 'TS-2':
|
|
130
|
-
return /\.js$/.test(file)
|
|
131
|
-
case 'GQL-1':
|
|
132
|
-
return /\bnodes\b|\bpageInfo\b|\bedges\b/.test(evidence)
|
|
133
|
-
case 'PR-1':
|
|
134
|
-
// PR-1 is about the PR title, so it has no source file to anchor to.
|
|
135
|
-
return /pr\s*(title|description)/i.test(file + '\n' + evidence)
|
|
136
|
-
default:
|
|
137
|
-
return false
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
124
|
function isHardRule(finding) {
|
|
142
|
-
|
|
143
|
-
return false
|
|
144
|
-
}
|
|
145
|
-
return hardRuleCorroborated(finding)
|
|
125
|
+
return typeof finding.rule === 'string' && finding.rule.trim().length > 0
|
|
146
126
|
}
|
|
147
127
|
|
|
148
128
|
const SEVERITY_RANK = { critical: 3, important: 2, observation: 1, idiomatic: 0 }
|
|
@@ -180,6 +160,13 @@ function mergeFindings(a, b) {
|
|
|
180
160
|
|
|
181
161
|
if (SEVERITY_RANK[b.severity] > SEVERITY_RANK[merged.severity]) {
|
|
182
162
|
merged.severity = b.severity
|
|
163
|
+
for (const field of ['verificationStatus', 'verifierReasoning', 'proof']) {
|
|
164
|
+
if (Object.prototype.hasOwnProperty.call(b, field)) {
|
|
165
|
+
merged[field] = b[field]
|
|
166
|
+
} else {
|
|
167
|
+
delete merged[field]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
183
170
|
}
|
|
184
171
|
|
|
185
172
|
merged.description = longest(a.description, b.description)
|
|
@@ -273,21 +260,51 @@ function dedup(allFindings) {
|
|
|
273
260
|
return result
|
|
274
261
|
}
|
|
275
262
|
|
|
276
|
-
function shouldVerify(finding
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
263
|
+
function shouldVerify(finding) {
|
|
264
|
+
// Review depth controls breadth, not the trust bar. Every finding that can
|
|
265
|
+
// affect the verdict must be checked. Hard rules take the verifier's
|
|
266
|
+
// rule-specific anchor/applicability path instead of its runtime challenges.
|
|
267
|
+
return finding.severity === 'critical' || finding.severity === 'important'
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function normalizeProof(result) {
|
|
271
|
+
const hasText = function(value) {
|
|
272
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
282
273
|
}
|
|
283
|
-
|
|
284
|
-
|
|
274
|
+
const coherent = (
|
|
275
|
+
result.attempted === true
|
|
276
|
+
&& result.proven === true
|
|
277
|
+
&& result.outcome === 'proven'
|
|
278
|
+
&& hasText(result.testCode)
|
|
279
|
+
&& hasText(result.testCommand)
|
|
280
|
+
&& hasText(result.redOutput)
|
|
281
|
+
) || (
|
|
282
|
+
result.attempted === true
|
|
283
|
+
&& result.proven === false
|
|
284
|
+
&& result.outcome === 'passed'
|
|
285
|
+
&& hasText(result.testCode)
|
|
286
|
+
&& hasText(result.testCommand)
|
|
287
|
+
) || (
|
|
288
|
+
result.attempted === true
|
|
289
|
+
&& result.proven === false
|
|
290
|
+
&& result.outcome === 'inconclusive'
|
|
291
|
+
) || (
|
|
292
|
+
result.attempted === false
|
|
293
|
+
&& result.proven === false
|
|
294
|
+
&& result.outcome === 'not_attempted'
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if (coherent) {
|
|
298
|
+
return result
|
|
285
299
|
}
|
|
286
|
-
|
|
287
|
-
|
|
300
|
+
|
|
301
|
+
const attempted = result.attempted === true
|
|
302
|
+
return {
|
|
303
|
+
attempted,
|
|
304
|
+
proven: false,
|
|
305
|
+
outcome: attempted ? 'inconclusive' : 'not_attempted',
|
|
306
|
+
reason: `Prover returned an inconsistent proof state; ignored. ${result.reason || ''}`.trim(),
|
|
288
307
|
}
|
|
289
|
-
// deep: critical + important
|
|
290
|
-
return finding.severity === 'critical' || finding.severity === 'important'
|
|
291
308
|
}
|
|
292
309
|
|
|
293
310
|
// Run thunks in sequential batches, giving the per-finding stages a ceiling.
|
|
@@ -428,6 +445,7 @@ const budgetAtStart = budget.spent()
|
|
|
428
445
|
`Read and follow ${promptDir}/verifier.md.`,
|
|
429
446
|
`FINDING (JSON): ${JSON.stringify(finding)}.`,
|
|
430
447
|
`DIFF_FILE=${diffFile}, CONTEXT_FILE=${contextFile}, WORKTREE_PATH=${wtDisplay}.`,
|
|
448
|
+
`HOUSE_RULES_FILE: read key "houseRulesFile" from CONTEXT_FILE, then read that exact path before validating a hard rule.`,
|
|
431
449
|
`Default to dropping when uncertain.`,
|
|
432
450
|
].join(' ')
|
|
433
451
|
}
|
|
@@ -438,16 +456,22 @@ const budgetAtStart = budget.spent()
|
|
|
438
456
|
|
|
439
457
|
async function reviewStage(dim) {
|
|
440
458
|
agentCount++
|
|
459
|
+
let schema = FINDINGS_SCHEMA
|
|
460
|
+
if (dim.key === 'implementation') {
|
|
461
|
+
schema = IMPLEMENTATION_SCHEMA
|
|
462
|
+
} else if (dim.key === 'test-quality') {
|
|
463
|
+
schema = TEST_QUALITY_SCHEMA
|
|
464
|
+
}
|
|
441
465
|
const result = await agent(reviewPrompt(dim), {
|
|
442
466
|
label: `review:${dim.key}`,
|
|
443
467
|
phase: 'Review',
|
|
444
|
-
schema
|
|
468
|
+
schema,
|
|
445
469
|
model: dim.model,
|
|
446
470
|
})
|
|
447
471
|
|
|
448
472
|
if (!result) {
|
|
449
473
|
log(`${dim.key}: agent returned null, skipping`)
|
|
450
|
-
return []
|
|
474
|
+
return { key: dim.key, findings: [] }
|
|
451
475
|
}
|
|
452
476
|
|
|
453
477
|
const findings = (result.findings || []).map(function(f) {
|
|
@@ -457,44 +481,45 @@ const budgetAtStart = budget.spent()
|
|
|
457
481
|
})
|
|
458
482
|
|
|
459
483
|
log(`${dim.key}: ${findings.length} findings, ${
|
|
460
|
-
findings.filter(function(f) { return shouldVerify(f
|
|
484
|
+
findings.filter(function(f) { return shouldVerify(f) }).length
|
|
461
485
|
} to verify`)
|
|
462
486
|
|
|
463
|
-
return
|
|
487
|
+
return {
|
|
488
|
+
key: dim.key,
|
|
489
|
+
findings,
|
|
490
|
+
acCoverage: dim.key === 'implementation' ? result.acCoverage : undefined,
|
|
491
|
+
coverageVerdict: dim.key === 'test-quality' ? result.coverageVerdict : undefined,
|
|
492
|
+
mutationSlip: dim.key === 'test-quality' ? result.mutationSlip : undefined,
|
|
493
|
+
mockSmells: dim.key === 'test-quality' ? result.mockSmells : undefined,
|
|
494
|
+
}
|
|
464
495
|
}
|
|
465
496
|
|
|
466
497
|
// -------------------------------------------------------------------------
|
|
467
498
|
// Verify stage: takes the deduped cross-dimension finding set, splits into
|
|
468
|
-
// in-scope (
|
|
469
|
-
//
|
|
470
|
-
//
|
|
499
|
+
// in-scope (verdict-affecting) and out-of-scope (non-blocking), and runs
|
|
500
|
+
// verifiers in parallel over the in-scope set. Hard rules use the verifier's
|
|
501
|
+
// rule-specific checks and skip only its runtime-oriented challenges.
|
|
471
502
|
// -------------------------------------------------------------------------
|
|
472
503
|
|
|
473
504
|
async function verifyAll(findings) {
|
|
474
505
|
const inScope = findings.filter(function(f) {
|
|
475
|
-
return shouldVerify(f
|
|
506
|
+
return shouldVerify(f)
|
|
476
507
|
})
|
|
477
508
|
const outOfScope = findings.filter(function(f) {
|
|
478
|
-
return !shouldVerify(f
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
const exempted = outOfScope.map(function(f) {
|
|
482
|
-
if (!isHardRule(f)) {
|
|
483
|
-
return f
|
|
484
|
-
}
|
|
485
|
-
hardRuleCount++
|
|
486
|
-
const exempt = Object.assign({}, f)
|
|
487
|
-
exempt.verifierReasoning = `hard rule ${f.rule}: exempt from adversarial verification (standards violation, not a runtime-failure claim)`
|
|
488
|
-
return exempt
|
|
509
|
+
return !shouldVerify(f)
|
|
489
510
|
})
|
|
490
511
|
|
|
491
512
|
if (inScope.length === 0) {
|
|
492
|
-
return
|
|
513
|
+
return outOfScope
|
|
493
514
|
}
|
|
494
515
|
|
|
495
516
|
const verified = await batched(inScope.map(function(finding) {
|
|
496
517
|
return async function() {
|
|
497
518
|
agentCount++
|
|
519
|
+
if (isHardRule(finding)) {
|
|
520
|
+
hardRuleCount++
|
|
521
|
+
}
|
|
522
|
+
|
|
498
523
|
const verdict = await agent(verifierPrompt(finding), {
|
|
499
524
|
label: `verify:${finding.file}:${finding.line}`,
|
|
500
525
|
model: 'sonnet',
|
|
@@ -504,9 +529,12 @@ const budgetAtStart = budget.spent()
|
|
|
504
529
|
})
|
|
505
530
|
|
|
506
531
|
if (!verdict) {
|
|
507
|
-
//
|
|
532
|
+
// Missing verification cannot support a blocking finding.
|
|
533
|
+
downgradedCount++
|
|
508
534
|
const kept = Object.assign({}, finding)
|
|
509
|
-
kept.
|
|
535
|
+
kept.severity = 'observation'
|
|
536
|
+
kept.verificationStatus = 'unavailable'
|
|
537
|
+
kept.verifierReasoning = 'verifier agent returned no usable result; downgraded to non-blocking'
|
|
510
538
|
return kept
|
|
511
539
|
}
|
|
512
540
|
|
|
@@ -519,18 +547,20 @@ const budgetAtStart = budget.spent()
|
|
|
519
547
|
downgradedCount++
|
|
520
548
|
const downgraded = Object.assign({}, finding)
|
|
521
549
|
downgraded.severity = verdict.newSeverity || 'observation'
|
|
550
|
+
downgraded.verificationStatus = 'downgraded'
|
|
522
551
|
downgraded.verifierReasoning = verdict.reasoning
|
|
523
552
|
return downgraded
|
|
524
553
|
}
|
|
525
554
|
|
|
526
555
|
// confirmed
|
|
527
556
|
const confirmed = Object.assign({}, finding)
|
|
557
|
+
confirmed.verificationStatus = isHardRule(finding) ? 'hard-rule-confirmed' : 'confirmed'
|
|
528
558
|
confirmed.verifierReasoning = verdict.reasoning
|
|
529
559
|
return confirmed
|
|
530
560
|
}
|
|
531
561
|
}), FANOUT_PLAN)
|
|
532
562
|
|
|
533
|
-
return
|
|
563
|
+
return outOfScope.concat(verified.filter(Boolean))
|
|
534
564
|
}
|
|
535
565
|
|
|
536
566
|
// -------------------------------------------------------------------------
|
|
@@ -548,10 +578,19 @@ const budgetAtStart = budget.spent()
|
|
|
548
578
|
return function() { return reviewStage(dim) }
|
|
549
579
|
}), REVIEW_PLAN)
|
|
550
580
|
|
|
551
|
-
const
|
|
581
|
+
const reviewResults = reviewed.filter(Boolean)
|
|
582
|
+
const implementationResult = reviewResults.find(function(result) {
|
|
583
|
+
return result.key === 'implementation'
|
|
584
|
+
}) || {}
|
|
585
|
+
const testQualityResult = reviewResults.find(function(result) {
|
|
586
|
+
return result.key === 'test-quality'
|
|
587
|
+
}) || {}
|
|
588
|
+
const deduped = dedup(reviewResults.flatMap(function(result) {
|
|
589
|
+
return result.findings
|
|
590
|
+
}))
|
|
552
591
|
|
|
553
592
|
log(`${deduped.length} unique finding(s) after dedup; verifying ${
|
|
554
|
-
deduped.filter(function(f) { return shouldVerify(f
|
|
593
|
+
deduped.filter(function(f) { return shouldVerify(f) }).length
|
|
555
594
|
}`)
|
|
556
595
|
|
|
557
596
|
phase('Verify')
|
|
@@ -635,11 +674,17 @@ const budgetAtStart = budget.spent()
|
|
|
635
674
|
return []
|
|
636
675
|
}
|
|
637
676
|
|
|
677
|
+
if (isHardRule(newFinding)) {
|
|
678
|
+
hardRuleCount++
|
|
679
|
+
}
|
|
680
|
+
|
|
638
681
|
if (verdict.verdict === 'downgraded') {
|
|
639
682
|
downgradedCount++
|
|
640
683
|
newFinding.severity = verdict.newSeverity || 'observation'
|
|
684
|
+
newFinding.verificationStatus = 'downgraded'
|
|
641
685
|
newFinding.verifierReasoning = verdict.reasoning
|
|
642
686
|
} else {
|
|
687
|
+
newFinding.verificationStatus = isHardRule(newFinding) ? 'hard-rule-confirmed' : 'confirmed'
|
|
643
688
|
newFinding.verifierReasoning = verdict.reasoning
|
|
644
689
|
}
|
|
645
690
|
|
|
@@ -705,9 +750,19 @@ const budgetAtStart = budget.spent()
|
|
|
705
750
|
return
|
|
706
751
|
}
|
|
707
752
|
|
|
708
|
-
|
|
709
|
-
|
|
753
|
+
const proof = normalizeProof(result)
|
|
754
|
+
finding.proof = proof
|
|
755
|
+
if (proof.proven === true) {
|
|
710
756
|
provenCount++
|
|
757
|
+
finding.verificationStatus = 'proven'
|
|
758
|
+
} else if (proof.outcome === 'passed') {
|
|
759
|
+
downgradedCount++
|
|
760
|
+
finding.severity = 'important'
|
|
761
|
+
finding.verificationStatus = 'counter-evidence'
|
|
762
|
+
finding.verifierReasoning = [
|
|
763
|
+
finding.verifierReasoning,
|
|
764
|
+
`Proof test passed: ${proof.reason}`,
|
|
765
|
+
].filter(Boolean).join(' ')
|
|
711
766
|
}
|
|
712
767
|
}
|
|
713
768
|
}), FANOUT_PLAN)
|
|
@@ -731,6 +786,10 @@ const budgetAtStart = budget.spent()
|
|
|
731
786
|
agentCount,
|
|
732
787
|
proveAttemptCount,
|
|
733
788
|
provenCount,
|
|
789
|
+
acCoverage: implementationResult.acCoverage || null,
|
|
790
|
+
coverageVerdict: testQualityResult.coverageVerdict || null,
|
|
791
|
+
mutationSlip: testQualityResult.mutationSlip || null,
|
|
792
|
+
mockSmells: Array.isArray(testQualityResult.mockSmells) ? testQualityResult.mockSmells : [],
|
|
734
793
|
outputTokens: budget.spent() - budgetAtStart,
|
|
735
794
|
turnTokensTotal: budget.spent(),
|
|
736
795
|
}
|
|
@@ -11,6 +11,9 @@ import { readFileSync } from 'node:fs';
|
|
|
11
11
|
const THRESHOLD = 1500;
|
|
12
12
|
const OFFER_MARKER = /critical-thinking review/i; // the offer line must contain this; doubles as a loop guard
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Exits the hook cleanly when conditions for offering a critique are not met.
|
|
16
|
+
*/
|
|
14
17
|
function bail() { process.exit(0); } // any uncertainty => do nothing, never block the user
|
|
15
18
|
|
|
16
19
|
let raw = '';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: open-source-publisher
|
|
3
|
-
description: Prepare an open-source repository for polished public publishing. Use when a user asks to publish, open-source, launch, polish, package, brand, or make a GitHub project presentable with a minimal project icon, social preview image, GitHub Pages landing page, standardized README, essential shields, CI/CD quality gates, release automation checks, and optional donation setup. Prefer the external `logo-generator` skill for icon generation when available, and `nolangz/pixel2motion` for optional motion previews of
|
|
3
|
+
description: Prepare an open-source repository for polished public publishing. Use when a user asks to publish, open-source, launch, polish, package, brand, or make a GitHub project presentable with a minimal project icon, social preview image, GitHub Pages landing page, standardized README, essential shields, CI/CD quality gates, release automation checks, and optional donation setup. Prefer the external `logo-generator` skill for icon generation when available, and `nolangz/pixel2motion` for optional motion previews of supported logos or simple artwork.
|
|
4
4
|
license: MIT
|
|
5
5
|
allowed-tools: Bash, Read, Write, Edit, WebSearch, WebFetch
|
|
6
6
|
compatibility: Codex, Claude Code, Cursor, GitHub Copilot, Windsurf, Kiro, and other Agent Skills compatible tools. Requires a writable git repository; browser or image rendering tools are useful for visual validation.
|
|
@@ -43,12 +43,11 @@ Use this skill to audit whether an OSS repository is ready to publish, then help
|
|
|
43
43
|
5. For existing usable pieces, do not propose replacement by default. Ask a change-oriented question only when useful, for example:
|
|
44
44
|
- "You already have a terminal-style GitHub Pages site. Do you want to keep it or restyle it?"
|
|
45
45
|
- "You already have an icon and social card. Do you want a refresh, or should I leave them as-is?"
|
|
46
|
-
- "You already have a static social card. Do you want me to animate it with pixel2motion, or leave it static?"
|
|
47
46
|
- "You already have release automation. Do you want me to audit only, or also tighten it?"
|
|
48
47
|
6. Ask only for choices needed to fix missing or weak pieces:
|
|
49
48
|
- If GitHub Pages is missing or weak, ask for style: `oldschool linux`, `terminal`, `modern`, `brutalist`, `glassmorphism`, `y2k`, `hacker`, or custom.
|
|
50
49
|
- If donation wiring is missing, ask whether to enable it: `none`, `GitHub Sponsors`, `Ko-fi`, `Buy Me a Coffee`, `Open Collective`, `Thanks.dev`, or custom URL.
|
|
51
|
-
- If the repo has a generated
|
|
50
|
+
- If the repo has a generated logo or simple hero artwork supported by `nolangz/pixel2motion`, ask whether to animate it or leave it static.
|
|
52
51
|
- If the repo has no clear product essence, ask for a one-sentence positioning statement.
|
|
53
52
|
7. Implement only approved, missing, or weak work in this order:
|
|
54
53
|
- OSS governance and support files
|
|
@@ -212,8 +211,8 @@ Use `magick` or another renderer when `rsvg-convert` is unavailable.
|
|
|
212
211
|
|
|
213
212
|
Some repos benefit from a short animated preview. Keep this opt-in.
|
|
214
213
|
|
|
215
|
-
-
|
|
216
|
-
-
|
|
214
|
+
- After the user opts in during workflow step 6, animate only the approved generated logo or simple artwork.
|
|
215
|
+
- Use `nolangz/pixel2motion` as the optional dependency for that animation, animated HTML demos, and GIF/video previews.
|
|
217
216
|
- Do not auto-animate by default.
|
|
218
217
|
|
|
219
218
|
## README Standard
|
package/scripts/lib/catalog.mjs
CHANGED
|
@@ -54,6 +54,9 @@ export const PLUGIN_ASSIGNMENT = {
|
|
|
54
54
|
},
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Loads and parses the skills catalog from the specified JSON file.
|
|
59
|
+
*/
|
|
57
60
|
export function loadCatalog(catalogPath = 'catalog/skills.json') {
|
|
58
61
|
const raw = JSON.parse(readFileSync(catalogPath, 'utf8'))
|
|
59
62
|
const plugins = raw.plugins || []
|