@gobing-ai/knowledge-kit 0.0.10 → 0.0.12
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/dist/index.js +363 -138
- package/package.json +1 -1
- package/plugins/generations/content-gen/src/storm.ts +99 -50
- package/plugins/generations/core-facts-gen/package.json +17 -0
- package/plugins/generations/core-facts-gen/plugin.json +7 -0
- package/plugins/generations/core-facts-gen/src/index.ts +116 -0
- package/plugins/generations/core-facts-gen/tsconfig.json +4 -0
- package/plugins/generations/daily-article-gen/package.json +17 -0
- package/plugins/generations/daily-article-gen/plugin.json +7 -0
- package/plugins/generations/daily-article-gen/src/index.ts +91 -0
- package/plugins/generations/daily-article-gen/tsconfig.json +4 -0
- package/plugins/generations/dailynews-gen/src/index.ts +11 -0
- package/plugins/generations/dailynews-gen/src/script-builder.ts +1 -1
- package/plugins/generations/episode-plan-gen/package.json +17 -0
- package/plugins/generations/episode-plan-gen/plugin.json +7 -0
- package/plugins/generations/episode-plan-gen/src/index.ts +726 -0
- package/plugins/generations/episode-plan-gen/tsconfig.json +4 -0
- package/plugins/generations/voice-gen/src/index.ts +102 -11
- package/plugins/generations/voice-gen/src/qc.ts +154 -9
- package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
- package/plugins/ingestions/aihot-ingest/src/index.ts +72 -13
- package/plugins/ingestions/aihot-ingest/src/mapper.ts +1 -0
- package/plugins/ingestions/aihot-ingest/src/rss.ts +151 -0
- package/plugins/ingestions/horizon-ingest/package.json +17 -0
- package/plugins/ingestions/horizon-ingest/plugin.json +7 -0
- package/plugins/ingestions/horizon-ingest/src/index.ts +205 -0
- package/plugins/ingestions/horizon-ingest/tsconfig.json +4 -0
- package/plugins/ingestions/last30days-ingest/package.json +17 -0
- package/plugins/ingestions/last30days-ingest/plugin.json +7 -0
- package/plugins/ingestions/last30days-ingest/src/index.ts +148 -0
- package/plugins/ingestions/last30days-ingest/tsconfig.json +4 -0
- package/plugins/kk/README.md +2 -2
- package/plugins/kk/commands/tell-me.md +16 -0
- package/plugins/kk/skills/explain-things/SKILL.md +58 -0
- package/plugins/kk/skills/explain-things/references/views.md +95 -0
- package/plugins/kk/skills/taste-unslop/SKILL.md +98 -0
- package/plugins/kk/skills/taste-unslop/references/pattern-guide.md +148 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +432 -19
- package/plugins/publishings/podcast-pub/package.json +17 -0
- package/plugins/publishings/podcast-pub/plugin.json +7 -0
- package/plugins/publishings/podcast-pub/src/index.ts +538 -0
- package/plugins/publishings/podcast-pub/src/map.ts +165 -0
- package/plugins/publishings/podcast-pub/src/microfeed-client.ts +196 -0
- package/plugins/publishings/podcast-pub/src/show-notes.ts +132 -0
- package/plugins/publishings/podcast-pub/tsconfig.json +4 -0
- package/plugins/publishings/surfdash-pub/src/index.ts +328 -62
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { type Doc, DocListSchema } from '@gobing-ai/kk-core';
|
|
5
|
+
import { atomicWriteJson, createNodeFileSystem, readJsonFile } from '@gobing-ai/ts-runtime';
|
|
6
|
+
import { echoError } from '@gobing-ai/ts-utils';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ingestion adapter over the Horizon CLI (task 0089 contract).
|
|
11
|
+
* Runs `uv run horizon` (cwd = the Horizon repo), then parses the digest
|
|
12
|
+
* `<data-dir>/summaries/horizon-<date>-<lang>.md` written during this run —
|
|
13
|
+
* one Doc per `### [title](url)` item block.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const InputConfigSchema = z.object({
|
|
17
|
+
data_dir: z.string().trim().min(1).optional(),
|
|
18
|
+
config: z.string().trim().min(1).optional(),
|
|
19
|
+
hours: z.number().int().min(1).optional(),
|
|
20
|
+
languages: z.array(z.string().trim().min(1)).optional(),
|
|
21
|
+
horizon_dir: z.string().trim().min(1).optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export interface HorizonRunResult {
|
|
25
|
+
exitCode: number;
|
|
26
|
+
stderr: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Subprocess seam — tests inject a fake that pre-seeds a summaries file. */
|
|
30
|
+
export type RunHorizon = (config: z.infer<typeof InputConfigSchema>, horizonDir: string) => Promise<HorizonRunResult>;
|
|
31
|
+
|
|
32
|
+
export const defaultRunner: RunHorizon = async (config, horizonDir) => {
|
|
33
|
+
const argv = [process.env.HORIZON_UV ?? 'uv', 'run', 'horizon'];
|
|
34
|
+
if (config.hours !== undefined) argv.push('--hours', String(config.hours));
|
|
35
|
+
if (config.data_dir !== undefined) argv.push('-d', config.data_dir);
|
|
36
|
+
if (config.config !== undefined) argv.push('-c', config.config);
|
|
37
|
+
const proc = Bun.spawnSync(argv, { cwd: horizonDir, stdout: 'pipe', stderr: 'pipe' });
|
|
38
|
+
return { exitCode: proc.exitCode ?? 1, stderr: proc.stderr.toString() };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const HEADING_RE = /^### \[(.+)\]\((.+)\) ⭐️ ([\d.]+)\/10\s*$/m;
|
|
42
|
+
const ANCHOR_RE = /<a id="item-([a-z0-9-]+)-(\d+)"><\/a>/;
|
|
43
|
+
|
|
44
|
+
interface ParsedItem {
|
|
45
|
+
anchorProfile: string;
|
|
46
|
+
anchorN: string;
|
|
47
|
+
title: string;
|
|
48
|
+
url: string;
|
|
49
|
+
score: number;
|
|
50
|
+
block: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Split a digest markdown file into per-item blocks keyed off the item anchors. */
|
|
54
|
+
export function splitItemBlocks(markdown: string): ParsedItem[] {
|
|
55
|
+
const items: ParsedItem[] = [];
|
|
56
|
+
const segments = markdown.split(/(?=<a id="item-)/);
|
|
57
|
+
for (const segment of segments) {
|
|
58
|
+
const anchor = segment.match(ANCHOR_RE);
|
|
59
|
+
const heading = segment.match(HEADING_RE);
|
|
60
|
+
if (!anchor || !heading) continue;
|
|
61
|
+
items.push({
|
|
62
|
+
anchorProfile: anchor[1] ?? '',
|
|
63
|
+
anchorN: anchor[2] ?? '',
|
|
64
|
+
title: heading[1] ?? '',
|
|
65
|
+
url: heading[2] ?? '',
|
|
66
|
+
score: Number(heading[3]),
|
|
67
|
+
block: segment,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return items;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Map one parsed item block to a Doc. */
|
|
74
|
+
export function itemToDoc(item: ParsedItem, date: string, lang: string): Doc {
|
|
75
|
+
const lines = item.block.split('\n');
|
|
76
|
+
const bodyParts: string[] = [];
|
|
77
|
+
const references: string[] = [];
|
|
78
|
+
let sourceLine = '';
|
|
79
|
+
let tags: string[] = [];
|
|
80
|
+
let inDetails = false;
|
|
81
|
+
for (const line of lines.slice(1)) {
|
|
82
|
+
if (line.startsWith('<details')) inDetails = true;
|
|
83
|
+
if (line.startsWith('</details>')) {
|
|
84
|
+
inDetails = false;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (inDetails) {
|
|
88
|
+
const href = line.match(/<a href="([^"]+)"/);
|
|
89
|
+
if (href?.[1]) references.push(href[1]);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const tagMatch = line.match(/^\*\*Tags\*\*:\s*(.+)$/);
|
|
93
|
+
if (tagMatch?.[1]) {
|
|
94
|
+
tags = [...tagMatch[1].matchAll(/#([^`,\s]+)/g)].map((m) => m[1] ?? '');
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (/^---\s*$/.test(line)) continue;
|
|
98
|
+
if (/^[a-z0-9_-]+ · /i.test(line) && !sourceLine) {
|
|
99
|
+
sourceLine = line.trim();
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (line.trim()) bodyParts.push(line);
|
|
103
|
+
}
|
|
104
|
+
const doc: Doc = {
|
|
105
|
+
id: `horizon:${date}-${item.anchorProfile}-${item.anchorN}`,
|
|
106
|
+
title: item.title,
|
|
107
|
+
body: bodyParts.join('\n').trim(),
|
|
108
|
+
sourceUri: item.url,
|
|
109
|
+
mediaType: 'text/markdown',
|
|
110
|
+
metadata: {
|
|
111
|
+
lang,
|
|
112
|
+
date,
|
|
113
|
+
profile: item.anchorProfile,
|
|
114
|
+
score: item.score,
|
|
115
|
+
source: sourceLine.split(' · ')[0] ?? '',
|
|
116
|
+
source_line: sourceLine,
|
|
117
|
+
tags,
|
|
118
|
+
references,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
return doc;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Parse a full digest file into Doc[]. */
|
|
125
|
+
export function digestToDocs(markdown: string, date: string, lang: string): Doc[] {
|
|
126
|
+
return DocListSchema.parse(splitItemBlocks(markdown).map((item) => itemToDoc(item, date, lang)));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface IngestionOptions {
|
|
130
|
+
out: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function processIngestionIO(
|
|
134
|
+
options: IngestionOptions,
|
|
135
|
+
config: z.infer<typeof InputConfigSchema>,
|
|
136
|
+
runner: RunHorizon = defaultRunner,
|
|
137
|
+
now: Date = new Date(),
|
|
138
|
+
): Promise<{ docs: Doc[] }> {
|
|
139
|
+
const fs = createNodeFileSystem();
|
|
140
|
+
const horizonDir = config.horizon_dir ?? process.env.HORIZON_DIR ?? join(homedir(), 'tools', 'Horizon');
|
|
141
|
+
const dataDir = config.data_dir ?? join(horizonDir, 'data');
|
|
142
|
+
|
|
143
|
+
const runStartedAt = now.getTime();
|
|
144
|
+
const run = await runner(config, horizonDir);
|
|
145
|
+
if (run.exitCode !== 0) {
|
|
146
|
+
throw new Error(`horizon exited ${run.exitCode}: ${run.stderr.trim() || 'no stderr'}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Collect digests written (or refreshed) during this run. The date comes from the
|
|
150
|
+
// FILENAME, not the run clock — Horizon names the digest for the date it finishes,
|
|
151
|
+
// which crosses UTC midnight when a long crawl runs late in the day (dogfood
|
|
152
|
+
// daily-ai-news-20260903: run started 23:35 UTC, digest written 00:03 UTC next day).
|
|
153
|
+
const langs = new Set(config.languages ?? ['zh', 'en']);
|
|
154
|
+
const summariesDir = join(dataDir, 'summaries');
|
|
155
|
+
const docs: Doc[] = [];
|
|
156
|
+
if (!(await fs.exists(summariesDir))) {
|
|
157
|
+
echoError(`horizon-ingest: no summaries dir at ${summariesDir} — emitting []`);
|
|
158
|
+
}
|
|
159
|
+
const digestRe = /^horizon-(\d{4}-\d{2}-\d{2})-([a-z-]+)\.md$/;
|
|
160
|
+
const entries = (await fs.exists(summariesDir)) ? await fs.readDir(summariesDir) : [];
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
const match = entry.match(digestRe);
|
|
163
|
+
if (!match?.[1] || !match?.[2] || !langs.has(match[2])) continue;
|
|
164
|
+
const file = join(summariesDir, entry);
|
|
165
|
+
const stat = await fs.stat(file);
|
|
166
|
+
if (!stat || stat.mtimeMs < runStartedAt - 1000) continue; // stale file from an earlier run
|
|
167
|
+
docs.push(...digestToDocs(await fs.readFile(file), match[1], match[2]));
|
|
168
|
+
}
|
|
169
|
+
if (docs.length === 0) {
|
|
170
|
+
echoError(
|
|
171
|
+
`horizon-ingest: no fresh digest in ${summariesDir} (languages: ${[...langs].join(', ')}) — emitting []`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const outDir = dirname(options.out);
|
|
176
|
+
if (outDir && outDir !== '.') await fs.ensureDir(outDir);
|
|
177
|
+
await atomicWriteJson(options.out, DocListSchema.parse(docs), fs);
|
|
178
|
+
return { docs };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function main(runner?: RunHorizon): Promise<number> {
|
|
182
|
+
let values: { in?: string; out?: string };
|
|
183
|
+
try {
|
|
184
|
+
({ values } = parseArgs({ options: { in: { type: 'string' }, out: { type: 'string' } } }));
|
|
185
|
+
} catch (err: unknown) {
|
|
186
|
+
echoError(`horizon-ingest failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
if (!values.in || !values.out) {
|
|
190
|
+
echoError('horizon-ingest failed: Missing required arguments: --in <config.json> --out <docs.json>');
|
|
191
|
+
return 1;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const config = InputConfigSchema.parse(await readJsonFile(values.in));
|
|
195
|
+
await processIngestionIO({ out: values.out }, config, runner);
|
|
196
|
+
return 0;
|
|
197
|
+
} catch (err: unknown) {
|
|
198
|
+
echoError(`horizon-ingest failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (import.meta.main) {
|
|
204
|
+
process.exit(await main());
|
|
205
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gobing-ai/last30days-ingest",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"typecheck": "tsc --noEmit"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@gobing-ai/kk-core": "workspace:*",
|
|
10
|
+
"@gobing-ai/ts-runtime": "catalog:",
|
|
11
|
+
"@gobing-ai/ts-utils": "catalog:",
|
|
12
|
+
"zod": "4.4.3"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "1.3.14"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { parseArgs } from 'node:util';
|
|
5
|
+
import { type Doc, DocListSchema } from '@gobing-ai/kk-core';
|
|
6
|
+
import { atomicWriteJson, createNodeFileSystem, readJsonFile } from '@gobing-ai/ts-runtime';
|
|
7
|
+
import { echoError } from '@gobing-ai/ts-utils';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ingestion adapter over the last30days skill CLI (task 0089 contract).
|
|
12
|
+
* Shells out to `last30days.py --emit=json --json-profile=agent`, captures the
|
|
13
|
+
* agent JSON from `--output`, and maps `results[]` → `Doc[]` (one Doc per item).
|
|
14
|
+
* Without `SETUP_COMPLETE` the tool self-degrades to keyless web search — that
|
|
15
|
+
* degraded `Doc[]` is still valid output (exit 0).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const InputConfigSchema = z.object({
|
|
19
|
+
topic: z.string().trim().min(1),
|
|
20
|
+
days: z.number().int().min(1).max(90).optional(),
|
|
21
|
+
web_backend: z.string().trim().min(1).optional(),
|
|
22
|
+
save_dir: z.string().trim().min(1).optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export interface Last30DaysRunResult {
|
|
26
|
+
exitCode: number;
|
|
27
|
+
outputPath: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Subprocess seam — tests inject a fake that writes a canned agent JSON. */
|
|
32
|
+
export type RunLast30Days = (
|
|
33
|
+
config: z.infer<typeof InputConfigSchema>,
|
|
34
|
+
outputPath: string,
|
|
35
|
+
) => Promise<Last30DaysRunResult>;
|
|
36
|
+
|
|
37
|
+
export const defaultRunner: RunLast30Days = async (config, outputPath) => {
|
|
38
|
+
const skillDir = process.env.LAST30DAYS_SKILL_DIR ?? join(homedir(), '.agents', 'skills', 'last30days');
|
|
39
|
+
const argv = [
|
|
40
|
+
process.env.LAST30DAYS_PYTHON ?? 'python3',
|
|
41
|
+
join(skillDir, 'scripts', 'last30days.py'),
|
|
42
|
+
config.topic,
|
|
43
|
+
'--emit=json',
|
|
44
|
+
'--json-profile=agent',
|
|
45
|
+
'--output',
|
|
46
|
+
outputPath,
|
|
47
|
+
];
|
|
48
|
+
if (config.days !== undefined) argv.push('--days', String(config.days));
|
|
49
|
+
if (config.web_backend !== undefined) argv.push('--web-backend', config.web_backend);
|
|
50
|
+
if (config.save_dir !== undefined) argv.push('--save-dir', config.save_dir);
|
|
51
|
+
const proc = Bun.spawnSync(argv, { cwd: skillDir, stdout: 'pipe', stderr: 'pipe' });
|
|
52
|
+
return { exitCode: proc.exitCode ?? 1, outputPath, stderr: proc.stderr.toString() };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const AgentJsonSchema = z.object({
|
|
56
|
+
schema_version: z.string().optional(),
|
|
57
|
+
window_days: z.number().optional(),
|
|
58
|
+
query: z.string().optional(),
|
|
59
|
+
results: z.array(z.record(z.string(), z.unknown())),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export function mapResultsToDocs(agentJson: unknown, topic: string): Doc[] {
|
|
63
|
+
const parsed = AgentJsonSchema.parse(agentJson);
|
|
64
|
+
const docs: Doc[] = [];
|
|
65
|
+
for (const item of parsed.results) {
|
|
66
|
+
try {
|
|
67
|
+
// Degraded/keyless mode uses the full URL as candidate_id — a 90-char URL id is
|
|
68
|
+
// brittle for downstream STORM citation (outline agents miscite them; dogfood
|
|
69
|
+
// run ff10095b failed assertKnownEvidence with 53 unknown IDs). Hash those.
|
|
70
|
+
const rawId =
|
|
71
|
+
typeof item.candidate_id === 'string' && item.candidate_id ? item.candidate_id : String(item.url ?? '');
|
|
72
|
+
const idSeed = rawId.startsWith('http')
|
|
73
|
+
? createHash('sha256').update(rawId).digest('hex').slice(0, 16)
|
|
74
|
+
: rawId;
|
|
75
|
+
const doc: Doc = {
|
|
76
|
+
id: `last30days:${idSeed}`,
|
|
77
|
+
body: String(item.summary ?? ''),
|
|
78
|
+
};
|
|
79
|
+
if (typeof item.title === 'string' && item.title) doc.title = item.title;
|
|
80
|
+
if (typeof item.url === 'string' && item.url) doc.sourceUri = item.url;
|
|
81
|
+
doc.mediaType = 'text/markdown';
|
|
82
|
+
doc.metadata = {
|
|
83
|
+
topic,
|
|
84
|
+
window_days: parsed.window_days,
|
|
85
|
+
cluster: item.cluster,
|
|
86
|
+
source: item.source,
|
|
87
|
+
engagement: item.engagement,
|
|
88
|
+
relevance_score: item.relevance_score,
|
|
89
|
+
published_at: item.published_at,
|
|
90
|
+
schema_version: parsed.schema_version,
|
|
91
|
+
};
|
|
92
|
+
docs.push(doc);
|
|
93
|
+
} catch (err: unknown) {
|
|
94
|
+
echoError(
|
|
95
|
+
`last30days-ingest: skipping unparseable item (${err instanceof Error ? err.message : String(err)})`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return DocListSchema.parse(docs);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface IngestionOptions {
|
|
103
|
+
out: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function processIngestionIO(
|
|
107
|
+
options: IngestionOptions,
|
|
108
|
+
config: z.infer<typeof InputConfigSchema>,
|
|
109
|
+
runner: RunLast30Days = defaultRunner,
|
|
110
|
+
): Promise<{ docs: Doc[] }> {
|
|
111
|
+
const fs = createNodeFileSystem();
|
|
112
|
+
const outDir = dirname(options.out);
|
|
113
|
+
if (outDir && outDir !== '.') await fs.ensureDir(outDir);
|
|
114
|
+
const capturePath = join(outDir || '.', '.last30days-agent.json');
|
|
115
|
+
const run = await runner(config, capturePath);
|
|
116
|
+
if (run.exitCode !== 0) {
|
|
117
|
+
throw new Error(`last30days exited ${run.exitCode}: ${run.stderr.trim() || 'no stderr'}`);
|
|
118
|
+
}
|
|
119
|
+
const docs = mapResultsToDocs(await readJsonFile(capturePath), config.topic);
|
|
120
|
+
await atomicWriteJson(options.out, docs, fs);
|
|
121
|
+
return { docs };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function main(runner?: RunLast30Days): Promise<number> {
|
|
125
|
+
let values: { in?: string; out?: string };
|
|
126
|
+
try {
|
|
127
|
+
({ values } = parseArgs({ options: { in: { type: 'string' }, out: { type: 'string' } } }));
|
|
128
|
+
} catch (err: unknown) {
|
|
129
|
+
echoError(`last30days-ingest failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
if (!values.in || !values.out) {
|
|
133
|
+
echoError('last30days-ingest failed: Missing required arguments: --in <config.json> --out <docs.json>');
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const config = InputConfigSchema.parse(await readJsonFile(values.in));
|
|
138
|
+
await processIngestionIO({ out: values.out }, config, runner);
|
|
139
|
+
return 0;
|
|
140
|
+
} catch (err: unknown) {
|
|
141
|
+
echoError(`last30days-ingest failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (import.meta.main) {
|
|
147
|
+
process.exit(await main());
|
|
148
|
+
}
|
package/plugins/kk/README.md
CHANGED
|
@@ -4,7 +4,7 @@ This directory is the **only** home for knowledge-kit agent capabilities (ADR-00
|
|
|
4
4
|
It is a Claude Code / Superskill plugin, **not** a kk-core product plugin.
|
|
5
5
|
|
|
6
6
|
| Path | Holds |
|
|
7
|
-
|
|
7
|
+
| ------ | -------- |
|
|
8
8
|
| `skills/` | Fat skills (`SKILL.md`) |
|
|
9
9
|
| `commands/` | Thin slash-command wrappers |
|
|
10
10
|
| `agents/` | Thin subagent wrappers (currently empty — see below) |
|
|
@@ -24,5 +24,5 @@ Install: `superskill install kk`.
|
|
|
24
24
|
|
|
25
25
|
Capability files **omit a leading `kk-`**. `superskill install` prefixes the plugin name, so
|
|
26
26
|
`skills/content-judge` installs as `kk:content-judge` (not `kk:kk-content-judge`). Same for `topic`,
|
|
27
|
-
`storm-research`, `itc-generating`, and `/workflow-run`. Product workflow YAML (`kk-storm-research.yaml`)
|
|
27
|
+
`storm-research`, `itc-generating`, `explain-things` (via `/tell-me`), and `/workflow-run`. Product workflow YAML (`kk-storm-research.yaml`)
|
|
28
28
|
keeps its existing name — that is a workflow stem, not an installable agent capability.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tell-me
|
|
3
|
+
description: Explain the supplied or current topic with the smallest useful view; use ELI5 only when requested.
|
|
4
|
+
argument-hint: "[topic] [--eli5]"
|
|
5
|
+
allowed-tools: ["Skill"]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# tell-me
|
|
9
|
+
|
|
10
|
+
Thin wrapper for the `explain-things` skill — all explanation and view-selection logic lives in
|
|
11
|
+
the skill. Forward `$ARGUMENTS` unchanged, including empty arguments (the skill owns
|
|
12
|
+
conversation-topic fallback).
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
Skill(skill="kk:explain-things", args="$ARGUMENTS")
|
|
16
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explain-things
|
|
3
|
+
description: >-
|
|
4
|
+
Explain a topic, mechanism, or change with the smallest useful view — pseudocode,
|
|
5
|
+
call tree, component tree, file tree, Mermaid, diff, copyable block, or a focused
|
|
6
|
+
HTML artifact as a last resort. Use when the user says "explain", "tell me",
|
|
7
|
+
"how does X work", "ELI5", "like I'm 5", or "show me". Normal explanations are
|
|
8
|
+
concise inline views; the ELI5 register is opt-in. This is not for content authoring,
|
|
9
|
+
research, judging, publishing, or package/code review workflows.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# explain-things
|
|
13
|
+
|
|
14
|
+
Explain the topic with the smallest useful view and only the prose needed to read it.
|
|
15
|
+
Skip the preamble.
|
|
16
|
+
|
|
17
|
+
## 1. Resolve the topic
|
|
18
|
+
|
|
19
|
+
`$ARGUMENTS` carries an optional standalone `--eli5` modifier; strip it if present, and the
|
|
20
|
+
remaining text is the explicit topic.
|
|
21
|
+
|
|
22
|
+
- Explicit topic present → use it.
|
|
23
|
+
- No explicit topic → use the current conversation topic.
|
|
24
|
+
- Neither → stop before creating any artifact and say exactly:
|
|
25
|
+
|
|
26
|
+
> Provide a topic, for example `/tell-me <topic>`, or ask again while a topic is active.
|
|
27
|
+
|
|
28
|
+
## 2. Choose the register
|
|
29
|
+
|
|
30
|
+
- **Normal** (default): concise supporting prose beside one smallest useful inline view.
|
|
31
|
+
- **ELI5**: picture-first, few words — use only when `--eli5` was passed or the user explicitly
|
|
32
|
+
asks for ELI5 / "like I'm 5". Lead with a familiar analogy; define unavoidable jargon.
|
|
33
|
+
ELI5 changes vocabulary and presentation, not the view catalog, and never forces HTML.
|
|
34
|
+
|
|
35
|
+
## 3. Pick the smallest view
|
|
36
|
+
|
|
37
|
+
Read `references/views.md` for the full decision catalog plus syntax examples. The default
|
|
38
|
+
mapping:
|
|
39
|
+
|
|
40
|
+
| Question shape | Smallest default view |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| Decision logic or algorithm | Pseudocode |
|
|
43
|
+
| In-process call order | Call tree |
|
|
44
|
+
| Multiple actors, timing, or data flow | Mermaid sequence/flow diagram |
|
|
45
|
+
| UI composition, state, or ownership | Component tree |
|
|
46
|
+
| File/module responsibility | Shallow file tree |
|
|
47
|
+
| Change to an existing known shape | Focused `diff` block |
|
|
48
|
+
| Mostly new, copyable target | Complete code/config block |
|
|
49
|
+
| Dense spatial layout/comparison inline forms cannot express | One focused HTML artifact |
|
|
50
|
+
|
|
51
|
+
Honor an explicitly requested format when feasible. Add a second view only when it answers a
|
|
52
|
+
distinct missing question.
|
|
53
|
+
|
|
54
|
+
## 4. Render
|
|
55
|
+
|
|
56
|
+
Place the view directly beside only the prose it needs. If inline text, trees, code, diffs, and
|
|
57
|
+
Mermaid cannot make the point, write one focused HTML file (match the product's look, real labels,
|
|
58
|
+
desktop + mobile), then open it and report its path.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# View catalog
|
|
2
|
+
|
|
3
|
+
Decision table first; syntax sketches below. Pick **one** view that makes the point (a second
|
|
4
|
+
only for a distinct missing question). Provenance: distilled from `vendors/misc/eli5/SKILL.md`
|
|
5
|
+
(picture-first few-words register) and `vendors/misc/show-me/SKILL.md` (view forms) — read them
|
|
6
|
+
only to trace history; this file is the operative catalog.
|
|
7
|
+
|
|
8
|
+
| Question shape | View |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| Decision logic or algorithm | Pseudocode |
|
|
11
|
+
| In-process call order | Call tree |
|
|
12
|
+
| Multiple actors, timing, or data flow | Mermaid sequence/flow diagram |
|
|
13
|
+
| UI composition, state, or ownership | Component tree |
|
|
14
|
+
| File/module responsibility, broad refactor | Shallow file tree |
|
|
15
|
+
| Change to an existing known shape | Focused `diff` block |
|
|
16
|
+
| Mostly new, copyable target | Complete code/config block |
|
|
17
|
+
| Dense spatial layout/comparison the inline forms cannot express | One focused HTML artifact |
|
|
18
|
+
|
|
19
|
+
## Syntax sketches
|
|
20
|
+
|
|
21
|
+
Pseudocode — logic, not language:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
on(save)
|
|
25
|
+
if content is unchanged
|
|
26
|
+
return cached result
|
|
27
|
+
write new content
|
|
28
|
+
return fresh result
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Call tree — who calls whom:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
submitForm
|
|
35
|
+
createSession
|
|
36
|
+
persistPrompt
|
|
37
|
+
launchAgent
|
|
38
|
+
navigateToSession
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Component tree — composition, with state/module boundaries that matter:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<SessionPage> (apps/example/src/routes/session.tsx)
|
|
45
|
+
useSessionEvents()
|
|
46
|
+
<SessionToolbar>
|
|
47
|
+
<RunSkillButton> (packages/ui)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
File tree — responsibilities at one level:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
src/
|
|
54
|
+
├── commands/ # parses user actions
|
|
55
|
+
├── sessions/ # owns session state
|
|
56
|
+
└── transport/ # sends API requests
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Mermaid — interaction/timing across actors:
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
sequenceDiagram
|
|
63
|
+
participant User
|
|
64
|
+
participant UI
|
|
65
|
+
participant Daemon
|
|
66
|
+
User->>UI: choose command
|
|
67
|
+
UI->>Daemon: send expanded prompt
|
|
68
|
+
Daemon-->>UI: stream result
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Diff — match its shape to the topic: component change, file-layout change, call-tree change, or
|
|
72
|
+
state/control-flow change:
|
|
73
|
+
|
|
74
|
+
```diff
|
|
75
|
+
on(save)
|
|
76
|
+
- write content
|
|
77
|
+
+ if content is unchanged
|
|
78
|
+
+ return cached result
|
|
79
|
+
+ write new content
|
|
80
|
+
+ invalidate cache
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Copyable block — most of it is new, omitted context would hide ownership/order, or the user needs
|
|
84
|
+
a target shape to paste:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
function expandSkill(command: string): string {
|
|
88
|
+
const skillName = command.slice(1);
|
|
89
|
+
return `use the ${skillName} skill`;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
HTML artifact — last resort only. For a visual too dense for Mermaid (layout walk-through,
|
|
94
|
+
before/after comparison, short slide deck): one focused HTML file matching the product's colors,
|
|
95
|
+
type, spacing, and components; real labels and data; desktop + mobile. Open it and report the path.
|