Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
155 commits
Select commit Hold shift + click to select a range
80c8e50
feat(rules): add Cursor team-wiki skill export format (#222)
advancedresearcharray Jun 4, 2026
329c6de
chore(main): release 0.19.15 (#223)
github-actions[bot] Jun 4, 2026
55f27ce
feat(workspace): ship default tasks workflow and task template (#224)
advancedresearcharray Jun 4, 2026
7c6f896
feat(mcp): add kiwi_task_create and kiwi_task_progress tools (#225)
advancedresearcharray Jun 4, 2026
bc03e2f
test(mcp): add integration harness for MCP tool round-trips (#226)
advancedresearcharray Jun 4, 2026
cca26bf
fix(mcp): correct appendTaskProgress slice indexing that duplicated c…
amelia751 Jun 4, 2026
ebda43e
feat(kanban): show blocked-by dependencies on workflow board (#230)
advancedresearcharray Jun 4, 2026
f2f3b26
test(webdav): add integration tests for PROPFIND, PUT, GET, MKCOL, DE…
ibobgunardi Jun 4, 2026
9a55434
feat(dql,import): add days_ago() and --infer-schema for csv/json (#231)
advancedresearcharray Jun 4, 2026
e961e47
feat(dev): Docker Compose dev setup with sample KB and MCP (#232)
advancedresearcharray Jun 4, 2026
fb5ceb8
chore(main): release 0.19.16 (#227)
github-actions[bot] Jun 4, 2026
8a2aaec
fix(import): use native JSON types for schema inference (#233)
amelia751 Jun 4, 2026
55df760
chore(main): release 0.19.17 (#234)
github-actions[bot] Jun 4, 2026
abbedf9
fix(update): match actual asset names and extract binary from archive…
amelia751 Jun 5, 2026
f5a5c23
chore(main): release 0.19.18 (#243)
github-actions[bot] Jun 5, 2026
cbd7203
fix(update): handle platform-suffixed binary names + add test coverag…
amelia751 Jun 5, 2026
72815e4
chore(main): release 0.19.19 (#245)
github-actions[bot] Jun 5, 2026
c62b221
feat(import): save inferred schema to .kiwi/schemas (#236)
advancedresearcharray Jun 5, 2026
4d5535a
test(import): add unit test for Google Sheets importer (#237)
advancedresearcharray Jun 5, 2026
9eef0b8
feat(import): add field mapping step to import wizard (#235)
advancedresearcharray Jun 5, 2026
1843c6c
feat(import): Confluence hierarchy by stable page ID (#238)
advancedresearcharray Jun 5, 2026
2682ba4
feat(import): rewrite Confluence attachment links to _assets (#239)
advancedresearcharray Jun 5, 2026
4165143
chore(main): release 0.19.20 (#246)
github-actions[bot] Jun 5, 2026
b88f123
fix(import): schema path, wizard routing, binary attachments, img tag…
amelia751 Jun 5, 2026
75dc137
chore(main): release 0.19.21 (#248)
github-actions[bot] Jun 5, 2026
b7459b1
feat(import): rewrite Confluence export page links to wiki paths (#249)
advancedresearcharray Jun 6, 2026
1f346f1
feat(memory): add memory_status frontmatter indexing and search filte…
advancedresearcharray Jun 6, 2026
fcc3e4d
feat(janitor): add expires_at and ttl janitor rule for memory expirat…
advancedresearcharray Jun 6, 2026
eb0c8f4
feat(cli): add kiwifs check command for CI-friendly hygiene scans (#263)
advancedresearcharray Jun 6, 2026
92cf427
feat(mcp): add kiwi_remember and kiwi_forget memory tools (#265)
advancedresearcharray Jun 6, 2026
46098ad
test(import): add MongoDB, Redis, Elasticsearch, and DynamoDB integra…
advancedresearcharray Jun 6, 2026
3cdde90
chore(main): release 0.19.22 (#266)
github-actions[bot] Jun 6, 2026
b5fb62a
fix(janitor): TTL overflow, malformed date warnings, error count, roo…
amelia751 Jun 6, 2026
87ba2f3
chore(main): release 0.19.23 (#269)
github-actions[bot] Jun 6, 2026
2f649fa
fix(importer): make ExtractKeywords deterministic for single-doc corp…
advancedresearcharray Jun 6, 2026
9c40685
chore(main): release 0.19.24 (#270)
github-actions[bot] Jun 6, 2026
b92f982
feat(search): add scope filter to search APIs (#271)
ibobgunardi Jun 6, 2026
be63bd0
feat(search): add recency weighting to search (#272)
ibobgunardi Jun 6, 2026
f3dedac
chore(main): release 0.19.25 (#273)
github-actions[bot] Jun 6, 2026
d6d0d9b
fix(api): handle copied public page title suffixes (#276)
shoveller Jun 9, 2026
ae83920
feat(exporter): add MkDocs static site project export (#275)
advancedresearcharray Jun 9, 2026
6ca2c32
chore(main): release 0.19.26 (#277)
github-actions[bot] Jun 9, 2026
d2162f8
fix(exporter): handle code blocks, deep nav hierarchy, and anchors in…
amelia751 Jun 9, 2026
7f09de4
chore(main): release 0.19.27 (#279)
github-actions[bot] Jun 9, 2026
ccb7bb4
fix(ui): allow folder collapse toggle in KiwiTree (#280)
amelia751 Jun 9, 2026
576681d
chore(main): release 0.19.28 (#282)
github-actions[bot] Jun 9, 2026
195d481
feat(ui): add widget system for embedding React components in markdow…
amelia751 Jun 9, 2026
539f25a
feat(ui): add widget:live (react-live) and playback engine (#284)
amelia751 Jun 10, 2026
330f966
chore(main): release 0.19.29 (#283)
github-actions[bot] Jun 10, 2026
1750db6
fix(ui): remove gap between code block header and content (#285)
amelia751 Jun 10, 2026
01425e3
feat(ui): widget:live playback engine, reusable components, and cache…
amelia751 Jun 10, 2026
1e0805c
chore(main): release 0.19.30
github-actions[bot] Jun 10, 2026
a60601d
Merge pull request #286 from kiwifs/release-please--branches--main
amelia751 Jun 10, 2026
1810ebf
feat(ui): add Shiki syntax highlighting to CodeHighlight widget
Jun 10, 2026
a7ceadf
fix(ui): strip wiki link syntax from ToC heading text
Jun 10, 2026
acc4460
chore(main): release 0.19.31
github-actions[bot] Jun 10, 2026
a10a4bf
Merge pull request #288 from kiwifs/release-please--branches--main
amelia751 Jun 10, 2026
f87a286
fix(ci): auto-merge Cursor agent fix (#289)
cursor[bot] Jun 10, 2026
009990c
refactor(ui): rename StateTable to PropertyBar
Jun 10, 2026
7c0e695
chore(main): release 0.19.32
github-actions[bot] Jun 10, 2026
3d3ad6b
Merge pull request #291 from kiwifs/release-please--branches--main
amelia751 Jun 10, 2026
0ccaf1e
feat(search): complete ONNX embedder acceptance for issue #102 (#290)
advancedresearcharray Jun 10, 2026
42e6334
chore(main): release 0.19.33 (#293)
github-actions[bot] Jun 10, 2026
a7eb050
fix(embed): recover from panic in tokenizer library on malformed JSON…
amelia751 Jun 10, 2026
23a9f60
docs(examples,faq): align ONNX embedder docs with issue #102 (#295)
advancedresearcharray Jun 10, 2026
6e9b199
chore(main): release 0.19.34 (#297)
github-actions[bot] Jun 10, 2026
ca87f7a
test(embed): cover loadTokenizerSafe malformed JSON recovery (#299)
advancedresearcharray Jun 11, 2026
562047d
fix(lint): skip wikilinks inside code blocks and inline code (#305)
amelia751 Jun 13, 2026
34b0fe9
chore(main): release 0.19.35 (#306)
github-actions[bot] Jun 13, 2026
178bae0
docs(template): add memory schema fields and lifecycle examples (#303)
advancedresearcharray Jun 14, 2026
e8237d4
feat(memory): add coverage, freshness, and scope metrics to report (#…
advancedresearcharray Jun 14, 2026
d3686da
feat(api): add content negotiation to public reader endpoint (#307)
advancedresearcharray Jun 14, 2026
7d06644
chore(main): release 0.19.36
github-actions[bot] Jun 14, 2026
e41a762
fix(links): skip wikilinks inside indented code blocks (#309)
amelia751 Jun 14, 2026
0d34d77
chore(main): release 0.19.37 (#311)
github-actions[bot] Jun 14, 2026
acc49b0
feat(memory): index contradicts frontmatter as backlinks (#310)
advancedresearcharray Jun 15, 2026
be6d7c2
test(importer): add MySQL integration test with testcontainers (#312)
advancedresearcharray Jun 15, 2026
bc45d23
test(importer): expand PostgreSQL integration test (#313)
advancedresearcharray Jun 15, 2026
95c287f
fix(serve): mount MCP Streamable HTTP on main server (#315)
advancedresearcharray Jun 15, 2026
6a05708
chore(main): release 0.19.38 (#317)
github-actions[bot] Jun 15, 2026
a2405de
fix(links): CommonMark-compliant extraction and contradicts normaliza…
amelia751 Jun 15, 2026
25c232f
chore(main): release 0.19.39 (#319)
github-actions[bot] Jun 15, 2026
f48381a
feat(ui): add data structure visualizers and utility widget component…
amelia751 Jun 15, 2026
a654e8c
docs(wiki): add UC-6 through UC-10 and update existing wiki (#341)
amelia751 Jun 15, 2026
99ca02a
feat: add CodeRunner widget for browser-side code execution (#356)
amelia751 Jun 15, 2026
162703e
fix: redesign CodeRunner to match ShikiCode visual style (#359)
amelia751 Jun 15, 2026
ea6f081
fix: CodeRunner header — Run button always visible, remove clutter
Jun 15, 2026
ffae2e1
fix(ci): auto-merge Cursor agent fix (#360)
cursor[bot] Jun 15, 2026
33d4aa6
fix: move Copy to header, remove pyodide progress text
Jun 16, 2026
ec8e506
fix: move ShikiCode copy button into header
Jun 16, 2026
fe6ef5b
feat(dql): add FLATTEN clause for querying nested arrays (#342)
advancedresearcharray Jun 16, 2026
e3399bd
feat(pipeline): add configurable ValidateWrite hooks via config.toml …
advancedresearcharray Jun 16, 2026
6a7c2b7
feat(ui): add custom CSS injection via .kiwi/custom.css (#357)
Jun 16, 2026
13f8131
feat(ui): add keyboard shortcuts config for custom keybindings (#358)
Jun 16, 2026
4c112e5
test(api): add path traversal regression for custom CSS (closes #347)
advancedresearcharray Jun 17, 2026
58f567a
fix(ui): priority-dismiss overlays on Escape for keybindings
advancedresearcharray Jun 17, 2026
7749570
feat(ui): add startup splash / dashboard page config (closes #354)
amelia751 Jun 17, 2026
28d9ae6
feat(ui): add sidebar structure config for pinned pages and sections …
amelia751 Jun 17, 2026
ec76b62
feat(ui): add per-user preferences API + wire themeLocked (closes #34…
amelia751 Jun 17, 2026
d5b9b3f
feat(ui): add branding config and feature flags for header views (clo…
amelia751 Jun 17, 2026
aee1097
fix(ci): auto-merge Cursor agent fix (#377)
cursor[bot] Jun 17, 2026
02d767f
fix(webui): wire injectBranding and remove unused imports
amelia751 Jun 17, 2026
703b621
feat(api): add frontmatter-only PATCH mode for file updates (#364)
advancedresearcharray Jun 17, 2026
96ce165
feat(links): add configurable typed-link indexing for frontmatter fie…
Jun 17, 2026
91ff702
feat(dql): add DATE(), NOW(), and BETWEEN temporal evaluation (#370)
advancedresearcharray Jun 17, 2026
e230a21
feat(ui): add configurable slash commands for editor extensions (#378)
Jun 17, 2026
c84099a
fix(dql): prevent string fallback when comparing temporal values with…
amelia751 Jun 17, 2026
5d2db8d
ci: add CJK spam filter action
Jun 18, 2026
516c3f7
fix(spaces): wire MCP handler into dynamically created spaces
Jun 18, 2026
2c59003
fix(ci): auto-merge Cursor agent fix (#393)
cursor[bot] Jun 18, 2026
11b1d51
fix(ci): auto-merge Cursor agent fix (#395)
cursor[bot] Jun 18, 2026
16b0fb3
fix(test): skip Elasticsearch integration test when image is not cached
cursor[bot] Jun 19, 2026
b656867
fix(links): clear stale typed links and validate typed field names (c…
advancedresearcharray Jun 19, 2026
bff7827
feat(links): index supersedes and superseded_by as backlinks (closes …
amelia751 Jun 19, 2026
b65b058
feat(workspace): ship prompt library init template and schema (closes…
advancedresearcharray Jun 19, 2026
a2141fd
feat(ui): add toolbar composition config to show/hide/reorder buttons…
advancedresearcharray Jun 19, 2026
2506a6c
feat(ui): add configurable slash commands for editor extensions (clos…
advancedresearcharray Jun 19, 2026
ddd91bc
feat(pipeline): add auto-sequence FormatWrite hook for directories (c…
advancedresearcharray Jun 19, 2026
243f24c
feat(mcp): add kiwi_cite tool for DOI/arXiv metadata fetch (closes #336)
advancedresearcharray Jun 19, 2026
f5e7f34
feat(importer): add BibTeX import source (closes #335)
advancedresearcharray Jun 19, 2026
3ca5c61
fix(links): flatten nested arrays in typed link frontmatter extraction
amelia751 Jun 19, 2026
769dc1d
fix(ci): unlock spam moderation log before posting tracking comments …
advancedresearcharray Jun 20, 2026
6f1c8b3
feat(search): extract template variables at index time (#403)
advancedresearcharray Jun 20, 2026
a729921
feat(api): add word-level diff granularity (#401)
advancedresearcharray Jun 20, 2026
37be175
feat(exporter): MkDocs static site export (Closes #103) (#399)
advancedresearcharray Jun 20, 2026
d47b82d
feat(ui): complete branding config with document.title and regression…
advancedresearcharray Jun 20, 2026
5d6bcee
feat(pipeline): enforce append_only frontmatter on PUT overwrites (#400)
advancedresearcharray Jun 20, 2026
a8fe010
feat(pipeline): monotonic sequence numbering on append (Closes #338) …
advancedresearcharray Jun 20, 2026
2904465
feat(workspace): ship research library init template with reading wor…
advancedresearcharray Jun 20, 2026
db2e629
feat(workspace): ship ADR init template with workflow and schema (#406)
Jun 20, 2026
29c241a
feat: align templates with use cases — add kb, cms, data, log; rename…
amelia751 Jun 20, 2026
e921478
feat(demo): interactive template gallery on demo.kiwifs.com (#412)
amelia751 Jun 20, 2026
98c98c0
fix(demo): tweak gallery subtitle wording (#413)
amelia751 Jun 20, 2026
1645e15
feat(demo): redesign gallery with prominent storybook link and card g…
amelia751 Jun 20, 2026
517b3bc
fix(ui): tone down inline #tag badge — use muted colors instead of pr…
amelia751 Jun 20, 2026
ba0de5c
fix(ci): auto-merge Cursor agent fix (#417)
cursor[bot] Jun 21, 2026
c4deecd
fix(workspace): preserve ADR frontmatter on workflow advance (#410)
advancedresearcharray Jun 22, 2026
75fee2c
feat(ui): add link-type filter controls to graph view (#409)
advancedresearcharray Jun 22, 2026
f86e293
feat(ui): apply workspace theme to published reader pages (#407)
advancedresearcharray Jun 22, 2026
0b1fabd
feat(janitor): add execution staleness rule for runbooks (#411)
advancedresearcharray Jun 22, 2026
58a51d3
feat(workspace): ship runbook init template and frontmatter schema (#…
Jun 22, 2026
a973d78
docs(wiki): update Good-First-Issues — mark shipped items, add new is…
Jun 23, 2026
42deab3
fix(toc): make ON THIS PAGE nav scrollable when content overflows (#439)
amelia751 Jun 25, 2026
7b6d855
docs: sync internal docs with shipped features (v0.19.x)
Jun 25, 2026
a63eba0
feat: local notes overlay — show .local/ annotations on page (#440)
amelia751 Jun 25, 2026
254385e
fix(toc): reduce max-height from calc(100vh-6rem) to 60vh
Jun 25, 2026
2b7ef1c
feat: local-state API + PageTracker widget (#441)
amelia751 Jun 25, 2026
d235488
fix: render URL frontmatter values as clickable links
Jun 25, 2026
1e69597
chore(main): release 0.19.40 (#321)
github-actions[bot] Jun 25, 2026
68a9d15
docs: add live demo links to README and all wiki UC pages (#442)
amelia751 Jun 26, 2026
724b122
rename: .local → .me, /local-state → /me/state, /local-note → /me/not…
amelia751 Jun 26, 2026
7706269
feat: bookmarks/highlights — colored text highlighting with toolbar p…
amelia751 Jun 26, 2026
a2e6cab
revert: undo post-0.19.40 changes (bookmarks, .me rename, docs)
Jun 26, 2026
4f50a8e
fix: always-mounted comments + kiwi-colored text selection
Jun 26, 2026
dd92a81
feat(ui): keyboard shortcut cheat sheet overlay
Jun 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
261 changes: 261 additions & 0 deletions .github/scripts/spam-filter.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
'use strict';

const SPAM_LOG_ISSUE = 392;
const MAINTAINER = 'amelia751';

const TRUSTED_BOTS = [
'github-actions[bot]',
'dependabot[bot]',
'release-please[bot]',
'cursor[bot]',
'renovate[bot]',
];

const CJK_REGEX =
/[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef\u2e80-\u2eff\u3200-\u32ff\ufe30-\ufe4f]/g;

function cjkRatio(body) {
const cjkMatches = body.match(CJK_REGEX) || [];
const totalChars = body.replace(/\s/g, '').length;
if (totalChars === 0) {
return 0;
}
return cjkMatches.length / totalChars;
}

function isCjkDominant(body, threshold = 0.5) {
return cjkRatio(body) >= threshold;
}

function buildLogCommentBody({
maintainer,
action,
author,
issueNumber,
cjkRatioValue,
isComment,
snippet,
bodyLength,
}) {
return [
`@${maintainer} 🚨 **Spam ${action} hidden**`,
'',
'| Field | Value |',
'|-------|-------|',
`| Author | \`${author}\` |`,
`| Issue/PR | #${issueNumber} |`,
`| CJK ratio | ${(cjkRatioValue * 100).toFixed(0)}% |`,
`| Action taken | ${isComment ? 'Comment minimized' : 'Issue closed + locked'} + user blocked |`,
'',
'**Content preview:**',
`> ${snippet}${bodyLength > 200 ? '...' : ''}`,
].join('\n');
}

async function ensureIssueUnlocked(github, { owner, repo, issueNumber }) {
const { data: issue } = await github.rest.issues.get({
owner,
repo,
issue_number: issueNumber,
});

if (!issue.locked) {
return false;
}

await github.rest.issues.unlock({
owner,
repo,
issue_number: issueNumber,
});

return true;
}

async function logSpamModeration(github, context, details) {
const owner = context.repo.owner;
const repo = context.repo.repo;
const {
author,
issueNumber,
cjkRatioValue,
isComment,
body,
} = details;

const snippet = body.substring(0, 200).replace(/\n/g, ' ');
const action = isComment ? 'comment' : 'issue';
const commentBody = buildLogCommentBody({
maintainer: MAINTAINER,
action,
author,
issueNumber,
cjkRatioValue,
isComment,
snippet,
bodyLength: body.length,
});

try {
const unlocked = await ensureIssueUnlocked(github, {
owner,
repo,
issueNumber: SPAM_LOG_ISSUE,
});
if (unlocked) {
console.log(`Unlocked #${SPAM_LOG_ISSUE} for spam moderation logging`);
}

await github.rest.issues.createComment({
owner,
repo,
issue_number: SPAM_LOG_ISSUE,
body: commentBody,
});
} catch (error) {
console.log(`Failed to log spam to #${SPAM_LOG_ISSUE}: ${error.message}`);
}
}

async function runSpamFilter({ github, context }) {
const isComment = !!context.payload.comment;
const body = isComment
? context.payload.comment.body
: context.payload.issue.body || '';
const author = isComment
? context.payload.comment.user.login
: context.payload.issue.user.login;
const issueNumber = context.payload.issue.number;

if (issueNumber === SPAM_LOG_ISSUE) {
console.log(`Skipping spam filter on moderation log issue #${SPAM_LOG_ISSUE}`);
return;
}

if (TRUSTED_BOTS.includes(author) || author === MAINTAINER) {
return;
}

try {
const { data: permLevel } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: author,
});
if (['admin', 'write', 'maintain'].includes(permLevel.permission)) {
return;
}
} catch (error) {
// Not a collaborator — continue with validation.
}

try {
await github.rest.orgs.checkMembershipForUser({
org: context.repo.owner,
username: author,
});
return;
} catch (error) {
// Not an org member — continue.
}

const ratio = cjkRatio(body);
if (ratio < 0.5) {
return;
}

let hasPriorActivity = false;
try {
const { data: comments } = await github.rest.issues.listCommentsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 5,
sort: 'created',
direction: 'desc',
});
hasPriorActivity = comments.some(
(comment) =>
comment.user.login === author &&
comment.id !== (context.payload.comment?.id),
);
} catch (error) {
// Best-effort prior activity check.
}

let isContributor = false;
try {
const { data: contributors } = await github.rest.repos.listContributors({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
});
isContributor = contributors.some((contributor) => contributor.login === author);
} catch (error) {
// Best-effort contributor check.
}

if (hasPriorActivity || isContributor) {
return;
}

console.log(
`🚨 Spam detected from ${author} on #${issueNumber} (CJK ratio: ${(ratio * 100).toFixed(0)}%)`,
);

if (isComment) {
const commentNodeId = context.payload.comment.node_id;
await github.graphql(
`
mutation($id: ID!) {
minimizeComment(input: { subjectId: $id, classifier: SPAM }) {
minimizedComment { isMinimized }
}
}
`,
{ id: commentNodeId },
);
} else {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned',
});
await github.rest.issues.lock({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
lock_reason: 'spam',
});
}

try {
await github.rest.orgs.blockUser({
org: context.repo.owner,
username: author,
});
} catch (error) {
console.log(`Failed to block ${author}: ${error.message}`);
}

await logSpamModeration(github, context, {
author,
issueNumber,
cjkRatioValue: ratio,
isComment,
body,
});
}

module.exports = {
SPAM_LOG_ISSUE,
MAINTAINER,
TRUSTED_BOTS,
cjkRatio,
isCjkDominant,
buildLogCommentBody,
ensureIssueUnlocked,
logSpamModeration,
runSpamFilter,
};
Loading
Loading