minimal-vibe-coding-kit 0.4.2 → 0.5.1
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/.agents/skills/claim/SKILL.md +82 -0
- package/.agents/skills/tutien/SKILL.md +97 -0
- package/.agents/skills/tutien/references/adaptive-response.md +68 -0
- package/.agents/skills/tutien/references/classification.md +64 -0
- package/.agents/skills/tutien/references/lore-sources.md +43 -0
- package/.agents/skills/tutien/references/privacy.md +45 -0
- package/.agents/skills/tutien/references/schema.md +106 -0
- package/.agents/skills/tutien/references/scoring-and-realms.md +37 -0
- package/.agents/skills/tutien/references/story-system.md +176 -0
- package/.agents/skills/tutien/references/vi-style-guide.md +89 -0
- package/.agents/skills/tutien/references/voice-and-mode.md +59 -0
- package/.agents/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
- package/.agents/skills/tutien/scripts/adapters/git.mjs +48 -0
- package/.agents/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
- package/.agents/skills/tutien/scripts/analyze-history.mjs +84 -0
- package/.agents/skills/tutien/scripts/catalog.mjs +130 -0
- package/.agents/skills/tutien/scripts/classify.mjs +338 -0
- package/.agents/skills/tutien/scripts/command.mjs +126 -0
- package/.agents/skills/tutien/scripts/compare.mjs +41 -0
- package/.agents/skills/tutien/scripts/metrics.mjs +306 -0
- package/.agents/skills/tutien/scripts/normalize-events.mjs +68 -0
- package/.agents/skills/tutien/scripts/project-profile.mjs +202 -0
- package/.agents/skills/tutien/scripts/redact.mjs +30 -0
- package/.agents/skills/tutien/scripts/render-report.mjs +479 -0
- package/.agents/skills/tutien/scripts/response-brief.mjs +103 -0
- package/.agents/skills/tutien/scripts/run-tutien.mjs +350 -0
- package/.agents/skills/tutien/scripts/score.mjs +97 -0
- package/.agents/skills/tutien/scripts/snapshot.mjs +76 -0
- package/.agents/skills/tutien/scripts/story-ledger.mjs +241 -0
- package/.agents/skills/tutien/scripts/villains.mjs +196 -0
- package/.claude/skills/claim/SKILL.md +82 -0
- package/.claude/skills/tutien/SKILL.md +97 -0
- package/.claude/skills/tutien/references/adaptive-response.md +68 -0
- package/.claude/skills/tutien/references/classification.md +64 -0
- package/.claude/skills/tutien/references/lore-sources.md +43 -0
- package/.claude/skills/tutien/references/privacy.md +45 -0
- package/.claude/skills/tutien/references/schema.md +106 -0
- package/.claude/skills/tutien/references/scoring-and-realms.md +37 -0
- package/.claude/skills/tutien/references/story-system.md +176 -0
- package/.claude/skills/tutien/references/vi-style-guide.md +89 -0
- package/.claude/skills/tutien/references/voice-and-mode.md +59 -0
- package/.claude/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
- package/.claude/skills/tutien/scripts/adapters/git.mjs +48 -0
- package/.claude/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
- package/.claude/skills/tutien/scripts/analyze-history.mjs +84 -0
- package/.claude/skills/tutien/scripts/catalog.mjs +130 -0
- package/.claude/skills/tutien/scripts/classify.mjs +338 -0
- package/.claude/skills/tutien/scripts/command.mjs +126 -0
- package/.claude/skills/tutien/scripts/compare.mjs +41 -0
- package/.claude/skills/tutien/scripts/metrics.mjs +306 -0
- package/.claude/skills/tutien/scripts/normalize-events.mjs +68 -0
- package/.claude/skills/tutien/scripts/project-profile.mjs +202 -0
- package/.claude/skills/tutien/scripts/redact.mjs +30 -0
- package/.claude/skills/tutien/scripts/render-report.mjs +479 -0
- package/.claude/skills/tutien/scripts/response-brief.mjs +103 -0
- package/.claude/skills/tutien/scripts/run-tutien.mjs +350 -0
- package/.claude/skills/tutien/scripts/score.mjs +97 -0
- package/.claude/skills/tutien/scripts/snapshot.mjs +76 -0
- package/.claude/skills/tutien/scripts/story-ledger.mjs +241 -0
- package/.claude/skills/tutien/scripts/villains.mjs +196 -0
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/skills/claim/SKILL.md +82 -0
- package/.cursor/skills/tutien/SKILL.md +97 -0
- package/.cursor/skills/tutien/references/adaptive-response.md +68 -0
- package/.cursor/skills/tutien/references/classification.md +64 -0
- package/.cursor/skills/tutien/references/lore-sources.md +43 -0
- package/.cursor/skills/tutien/references/privacy.md +45 -0
- package/.cursor/skills/tutien/references/schema.md +106 -0
- package/.cursor/skills/tutien/references/scoring-and-realms.md +37 -0
- package/.cursor/skills/tutien/references/story-system.md +176 -0
- package/.cursor/skills/tutien/references/vi-style-guide.md +89 -0
- package/.cursor/skills/tutien/references/voice-and-mode.md +59 -0
- package/.cursor/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
- package/.cursor/skills/tutien/scripts/adapters/git.mjs +48 -0
- package/.cursor/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
- package/.cursor/skills/tutien/scripts/analyze-history.mjs +84 -0
- package/.cursor/skills/tutien/scripts/catalog.mjs +130 -0
- package/.cursor/skills/tutien/scripts/classify.mjs +338 -0
- package/.cursor/skills/tutien/scripts/command.mjs +126 -0
- package/.cursor/skills/tutien/scripts/compare.mjs +41 -0
- package/.cursor/skills/tutien/scripts/metrics.mjs +306 -0
- package/.cursor/skills/tutien/scripts/normalize-events.mjs +68 -0
- package/.cursor/skills/tutien/scripts/project-profile.mjs +202 -0
- package/.cursor/skills/tutien/scripts/redact.mjs +30 -0
- package/.cursor/skills/tutien/scripts/render-report.mjs +479 -0
- package/.cursor/skills/tutien/scripts/response-brief.mjs +103 -0
- package/.cursor/skills/tutien/scripts/run-tutien.mjs +350 -0
- package/.cursor/skills/tutien/scripts/score.mjs +97 -0
- package/.cursor/skills/tutien/scripts/snapshot.mjs +76 -0
- package/.cursor/skills/tutien/scripts/story-ledger.mjs +241 -0
- package/.cursor/skills/tutien/scripts/villains.mjs +196 -0
- package/.grok/skills/claim/SKILL.md +82 -0
- package/.grok/skills/tutien/SKILL.md +97 -0
- package/.grok/skills/tutien/references/adaptive-response.md +68 -0
- package/.grok/skills/tutien/references/classification.md +64 -0
- package/.grok/skills/tutien/references/lore-sources.md +43 -0
- package/.grok/skills/tutien/references/privacy.md +45 -0
- package/.grok/skills/tutien/references/schema.md +106 -0
- package/.grok/skills/tutien/references/scoring-and-realms.md +37 -0
- package/.grok/skills/tutien/references/story-system.md +176 -0
- package/.grok/skills/tutien/references/vi-style-guide.md +89 -0
- package/.grok/skills/tutien/references/voice-and-mode.md +59 -0
- package/.grok/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
- package/.grok/skills/tutien/scripts/adapters/git.mjs +48 -0
- package/.grok/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
- package/.grok/skills/tutien/scripts/analyze-history.mjs +84 -0
- package/.grok/skills/tutien/scripts/catalog.mjs +130 -0
- package/.grok/skills/tutien/scripts/classify.mjs +338 -0
- package/.grok/skills/tutien/scripts/command.mjs +126 -0
- package/.grok/skills/tutien/scripts/compare.mjs +41 -0
- package/.grok/skills/tutien/scripts/metrics.mjs +306 -0
- package/.grok/skills/tutien/scripts/normalize-events.mjs +68 -0
- package/.grok/skills/tutien/scripts/project-profile.mjs +202 -0
- package/.grok/skills/tutien/scripts/redact.mjs +30 -0
- package/.grok/skills/tutien/scripts/render-report.mjs +479 -0
- package/.grok/skills/tutien/scripts/response-brief.mjs +103 -0
- package/.grok/skills/tutien/scripts/run-tutien.mjs +350 -0
- package/.grok/skills/tutien/scripts/score.mjs +97 -0
- package/.grok/skills/tutien/scripts/snapshot.mjs +76 -0
- package/.grok/skills/tutien/scripts/story-ledger.mjs +241 -0
- package/.grok/skills/tutien/scripts/villains.mjs +196 -0
- package/.vibekit/docs/BACKBONE_REFERENCE.md +2 -0
- package/.vibekit/docs/INSTALL.md +3 -1
- package/.vibekit/init/CLAUDE-template.md +2 -0
- package/.vibekit/scripts/mvck.mjs +5 -5
- package/.vibekit/scripts/pack-dry-run.mjs +38 -3
- package/.vibekit/scripts/validate-kit.mjs +34 -24
- package/.vibekit/skills/claim/SKILL.md +82 -0
- package/.vibekit/skills/skills-manifest.json +27 -0
- package/.vibekit/skills/tutien/SKILL.md +97 -0
- package/.vibekit/skills/tutien/references/adaptive-response.md +68 -0
- package/.vibekit/skills/tutien/references/classification.md +64 -0
- package/.vibekit/skills/tutien/references/lore-sources.md +43 -0
- package/.vibekit/skills/tutien/references/privacy.md +45 -0
- package/.vibekit/skills/tutien/references/schema.md +106 -0
- package/.vibekit/skills/tutien/references/scoring-and-realms.md +37 -0
- package/.vibekit/skills/tutien/references/story-system.md +176 -0
- package/.vibekit/skills/tutien/references/vi-style-guide.md +89 -0
- package/.vibekit/skills/tutien/references/voice-and-mode.md +59 -0
- package/.vibekit/skills/tutien/scripts/adapters/generic-jsonl.mjs +82 -0
- package/.vibekit/skills/tutien/scripts/adapters/git.mjs +48 -0
- package/.vibekit/skills/tutien/scripts/adapters/plain-transcript.mjs +37 -0
- package/.vibekit/skills/tutien/scripts/analyze-history.mjs +84 -0
- package/.vibekit/skills/tutien/scripts/catalog.mjs +130 -0
- package/.vibekit/skills/tutien/scripts/classify.mjs +338 -0
- package/.vibekit/skills/tutien/scripts/command.mjs +126 -0
- package/.vibekit/skills/tutien/scripts/compare.mjs +41 -0
- package/.vibekit/skills/tutien/scripts/metrics.mjs +306 -0
- package/.vibekit/skills/tutien/scripts/normalize-events.mjs +68 -0
- package/.vibekit/skills/tutien/scripts/project-profile.mjs +202 -0
- package/.vibekit/skills/tutien/scripts/redact.mjs +30 -0
- package/.vibekit/skills/tutien/scripts/render-report.mjs +479 -0
- package/.vibekit/skills/tutien/scripts/response-brief.mjs +103 -0
- package/.vibekit/skills/tutien/scripts/run-tutien.mjs +350 -0
- package/.vibekit/skills/tutien/scripts/score.mjs +97 -0
- package/.vibekit/skills/tutien/scripts/snapshot.mjs +76 -0
- package/.vibekit/skills/tutien/scripts/story-ledger.mjs +241 -0
- package/.vibekit/skills/tutien/scripts/villains.mjs +196 -0
- package/CHANGELOG.md +51 -0
- package/README.md +53 -47
- package/docs/README.vi.md +14 -8
- package/docs/README.zh-CN.md +279 -0
- package/package.json +9 -3
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic storage helpers for the agent-authored living chronicle.
|
|
3
|
+
// This module never generates story prose. It only builds an aggregate-only
|
|
4
|
+
// evidence packet and validates the plot/chapter ledger written by the agent.
|
|
5
|
+
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
export const STORY_SCHEMA = 1;
|
|
13
|
+
export const STORY_CONTEXT_SCHEMA = 'tutien-story-context-v1';
|
|
14
|
+
export const STORY_STATE_SCHEMA = 'tutien-story-state-v1';
|
|
15
|
+
export const STORY_CHAPTER_SCHEMA = 'tutien-story-chapter-v1';
|
|
16
|
+
|
|
17
|
+
const sha16 = (value) => crypto.createHash('sha256').update(String(value)).digest('hex').slice(0, 16);
|
|
18
|
+
|
|
19
|
+
export function storyPaths(root = process.cwd()) {
|
|
20
|
+
const base = path.join(path.resolve(root), '.vibekit', 'reports', 'tutien', 'story');
|
|
21
|
+
return {
|
|
22
|
+
base,
|
|
23
|
+
plot: path.join(base, 'plot.md'),
|
|
24
|
+
state: path.join(base, 'story-state.json'),
|
|
25
|
+
context: path.join(base, 'latest-context.json'),
|
|
26
|
+
chapters: path.join(base, 'chapters')
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Preserve letters from every language, including Han characters. Latin
|
|
31
|
+
// scripts are normalized so Vietnamese titles remain readable in filenames.
|
|
32
|
+
export function slugifyChapterTitle(title) {
|
|
33
|
+
const raw = String(title ?? '').trim();
|
|
34
|
+
if (!raw) throw new Error('chapter title is required');
|
|
35
|
+
const slug = raw
|
|
36
|
+
.normalize('NFD')
|
|
37
|
+
.replace(/\p{M}+/gu, '')
|
|
38
|
+
.toLocaleLowerCase('und')
|
|
39
|
+
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
40
|
+
.replace(/^-+|-+$/g, '')
|
|
41
|
+
.slice(0, 80)
|
|
42
|
+
.replace(/-+$/g, '');
|
|
43
|
+
if (!slug || slug === '.' || slug === '..') throw new Error('chapter title does not produce a safe filename');
|
|
44
|
+
return slug;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function chapterFilename(number, title) {
|
|
48
|
+
if (!Number.isInteger(number) || number < 1 || number > 9999) {
|
|
49
|
+
throw new Error('chapter number must be an integer from 1 to 9999');
|
|
50
|
+
}
|
|
51
|
+
return `${String(number).padStart(4, '0')}-${slugifyChapterTitle(title)}.md`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function listChapters(root = process.cwd()) {
|
|
55
|
+
const dir = storyPaths(root).chapters;
|
|
56
|
+
if (!fs.existsSync(dir)) return [];
|
|
57
|
+
return fs.readdirSync(dir)
|
|
58
|
+
.filter((file) => /^\d{4}-[\p{L}\p{N}][\p{L}\p{N}-]*\.md$/u.test(file))
|
|
59
|
+
.map((file) => ({ file, number: Number(file.slice(0, 4)), path: path.join(dir, file) }))
|
|
60
|
+
.sort((a, b) => a.number - b.number || a.file.localeCompare(b.file));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function nextChapterNumber(root = process.cwd()) {
|
|
64
|
+
const chapters = listChapters(root);
|
|
65
|
+
return chapters.length ? chapters[chapters.length - 1].number + 1 : 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveStoryLanguage(requested = 'auto', invocationText = '', fallback = 'en') {
|
|
69
|
+
const explicit = String(requested).toLowerCase();
|
|
70
|
+
if (['vi', 'en', 'zh'].includes(explicit)) return explicit;
|
|
71
|
+
if (/\p{Script=Han}/u.test(invocationText)) return 'zh';
|
|
72
|
+
if (/[À-ỹĐđ]/u.test(invocationText) || /\b(hãy|giúp|của|và|không|chương|tu tiên)\b/iu.test(invocationText)) return 'vi';
|
|
73
|
+
return ['vi', 'en', 'zh'].includes(fallback) ? fallback : 'en';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function safeSlug(value, fallback = null) {
|
|
77
|
+
const text = String(value ?? '');
|
|
78
|
+
return /^[a-z0-9][a-z0-9_-]{0,63}$/i.test(text) ? text : fallback;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function safeValidationCommand(value) {
|
|
82
|
+
const text = String(value ?? '');
|
|
83
|
+
return /^(?:npm run|yarn|pnpm|bun run) [A-Za-z0-9][A-Za-z0-9:_-]{0,63}$/.test(text) ? text : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function storyFacts(model, profile = {}) {
|
|
87
|
+
const cultivation = model.cultivation ?? null;
|
|
88
|
+
const progression = cultivation?.progression ?? null;
|
|
89
|
+
return {
|
|
90
|
+
project: {
|
|
91
|
+
id: safeSlug(profile.projectId, 'repo'),
|
|
92
|
+
type: safeSlug(profile.projectType),
|
|
93
|
+
primaryLanguage: safeSlug(profile.primaryLanguage),
|
|
94
|
+
domains: (profile.domains ?? []).map((v) => safeSlug(v)).filter(Boolean).slice(0, 12),
|
|
95
|
+
stack: (profile.stack ?? []).map((v) => safeSlug(v)).filter(Boolean).slice(0, 12),
|
|
96
|
+
validationCommands: (profile.validationCommands ?? []).map((v) => safeValidationCommand(v)).filter(Boolean).slice(0, 3),
|
|
97
|
+
metadataSources: (profile.metadataSources ?? []).map((v) => safeSlug(String(v).replace(/\.[^.]+$/, ''))).filter(Boolean).slice(0, 12),
|
|
98
|
+
authorsCount: Number.isInteger(profile.authorsCount) && profile.authorsCount >= 0 ? profile.authorsCount : null
|
|
99
|
+
},
|
|
100
|
+
evidence: {
|
|
101
|
+
window: model.coverage?.window ?? { start: null, end: null },
|
|
102
|
+
sessions: model.coverage?.sessions ?? 0,
|
|
103
|
+
prompts: model.coverage?.userPrompts ?? 0,
|
|
104
|
+
commits: model.coverage?.commits ?? 0,
|
|
105
|
+
confidence: model.coverage?.confidence ?? 'low',
|
|
106
|
+
tokens: {
|
|
107
|
+
reported: model.tokens?.reportedTotal ?? 0,
|
|
108
|
+
estimated: model.tokens?.estimatedTotal ?? 0,
|
|
109
|
+
unknownTurns: model.tokens?.unknownTurns ?? 0
|
|
110
|
+
},
|
|
111
|
+
realm: model.realm ? { name: model.realm.name, score: model.score } : null,
|
|
112
|
+
problems: (model.problems ?? []).map((p) => ({
|
|
113
|
+
type: p.problemType,
|
|
114
|
+
confidence: p.confidence,
|
|
115
|
+
priority: p.priority
|
|
116
|
+
})),
|
|
117
|
+
classification: cultivation ? {
|
|
118
|
+
faction: cultivation.classification?.faction?.id ?? null,
|
|
119
|
+
affiliation: cultivation.classification?.affiliation?.id ?? null,
|
|
120
|
+
paths: (cultivation.classification?.paths ?? []).map((p) => p.id)
|
|
121
|
+
} : null,
|
|
122
|
+
progression: progression ? {
|
|
123
|
+
tuVi: progression.tuVi?.window ?? 0,
|
|
124
|
+
congDuc: progression.congDuc?.window ?? 0,
|
|
125
|
+
nghiepLuc: progression.nghiepLuc?.window ?? 0,
|
|
126
|
+
daoHanhWindows: progression.daoHanh?.windows ?? 0,
|
|
127
|
+
overlap: progression.overlap ?? 0
|
|
128
|
+
} : null
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function buildStoryContext(model, options = {}) {
|
|
134
|
+
const facts = storyFacts(model, options.profile ?? {});
|
|
135
|
+
const evidenceKey = sha16(JSON.stringify({
|
|
136
|
+
storySchema: STORY_SCHEMA,
|
|
137
|
+
project: facts.project.id,
|
|
138
|
+
range: options.range ?? 'all',
|
|
139
|
+
evidence: facts.evidence
|
|
140
|
+
}));
|
|
141
|
+
return {
|
|
142
|
+
schema: STORY_CONTEXT_SCHEMA,
|
|
143
|
+
storySchema: STORY_SCHEMA,
|
|
144
|
+
evidenceKey,
|
|
145
|
+
language: resolveStoryLanguage(options.language, options.invocationText, options.conversationLanguage),
|
|
146
|
+
style: safeSlug(options.style, 'auto'),
|
|
147
|
+
focus: safeSlug(options.focus, 'balanced'),
|
|
148
|
+
policyState: model.policyState ?? 'clear',
|
|
149
|
+
canWriteChapter: model.suppressGamification !== true,
|
|
150
|
+
...facts
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function writeStoryContext(root, context) {
|
|
155
|
+
if (context?.schema !== STORY_CONTEXT_SCHEMA) throw new Error(`story context must use ${STORY_CONTEXT_SCHEMA}`);
|
|
156
|
+
const paths = storyPaths(root);
|
|
157
|
+
fs.mkdirSync(paths.base, { recursive: true });
|
|
158
|
+
fs.writeFileSync(paths.context, `${JSON.stringify(context, null, 2)}\n`);
|
|
159
|
+
return paths.context;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function chapterFrontmatter(text) {
|
|
163
|
+
const match = String(text).match(/^---\n([\s\S]*?)\n---\n/);
|
|
164
|
+
if (!match) return null;
|
|
165
|
+
const get = (key) => match[1].match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))?.[1]?.trim().replace(/^['"]|['"]$/g, '') ?? null;
|
|
166
|
+
return {
|
|
167
|
+
schema: get('schema'),
|
|
168
|
+
chapter: Number(get('chapter')),
|
|
169
|
+
title: get('title'),
|
|
170
|
+
evidenceKey: get('evidence_key')
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function validateStoryTree(root = process.cwd()) {
|
|
175
|
+
const paths = storyPaths(root);
|
|
176
|
+
const chapters = listChapters(root);
|
|
177
|
+
const errors = [];
|
|
178
|
+
if (chapters.length && !fs.existsSync(paths.plot)) errors.push('plot.md is required when chapters exist');
|
|
179
|
+
|
|
180
|
+
let state = null;
|
|
181
|
+
if (fs.existsSync(paths.state)) {
|
|
182
|
+
try {
|
|
183
|
+
state = JSON.parse(fs.readFileSync(paths.state, 'utf8'));
|
|
184
|
+
if (state.schema !== STORY_STATE_SCHEMA) errors.push(`story-state.json must use ${STORY_STATE_SCHEMA}`);
|
|
185
|
+
} catch {
|
|
186
|
+
errors.push('story-state.json must contain valid JSON');
|
|
187
|
+
}
|
|
188
|
+
} else if (chapters.length) {
|
|
189
|
+
errors.push('story-state.json is required when chapters exist');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const evidenceKeys = new Set();
|
|
193
|
+
for (let i = 0; i < chapters.length; i += 1) {
|
|
194
|
+
const expected = i + 1;
|
|
195
|
+
const item = chapters[i];
|
|
196
|
+
if (item.number !== expected) errors.push(`chapter sequence must be contiguous: expected ${expected}, found ${item.number}`);
|
|
197
|
+
const fm = chapterFrontmatter(fs.readFileSync(item.path, 'utf8'));
|
|
198
|
+
if (!fm) {
|
|
199
|
+
errors.push(`${item.file} is missing YAML frontmatter`);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (fm.schema !== STORY_CHAPTER_SCHEMA) errors.push(`${item.file} must use ${STORY_CHAPTER_SCHEMA}`);
|
|
203
|
+
if (fm.chapter !== item.number) errors.push(`${item.file} frontmatter chapter must equal ${item.number}`);
|
|
204
|
+
if (!fm.title) errors.push(`${item.file} requires a title`);
|
|
205
|
+
if (!fm.evidenceKey || !/^[0-9a-f]{16}$/.test(fm.evidenceKey)) errors.push(`${item.file} requires a 16-hex evidence_key`);
|
|
206
|
+
else if (evidenceKeys.has(fm.evidenceKey)) errors.push(`${item.file} repeats evidence_key ${fm.evidenceKey}`);
|
|
207
|
+
else evidenceKeys.add(fm.evidenceKey);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (state) {
|
|
211
|
+
if (state.lastChapter !== chapters.length) errors.push(`story-state.json lastChapter must equal ${chapters.length}`);
|
|
212
|
+
const consumed = state.consumedEvidenceKeys ?? [];
|
|
213
|
+
if (!Array.isArray(consumed) || consumed.some((key) => !/^[0-9a-f]{16}$/.test(key))) {
|
|
214
|
+
errors.push('story-state.json consumedEvidenceKeys must be an array of 16-hex keys');
|
|
215
|
+
}
|
|
216
|
+
for (const key of evidenceKeys) {
|
|
217
|
+
if (!consumed.includes(key)) errors.push(`story-state.json is missing consumed evidence key ${key}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
ok: errors.length === 0,
|
|
223
|
+
errors,
|
|
224
|
+
chapters: chapters.length,
|
|
225
|
+
nextChapter: chapters.length + 1,
|
|
226
|
+
paths: Object.fromEntries(Object.entries(paths).map(([key, value]) => [key, path.relative(path.resolve(root), value)]))
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
231
|
+
if (isMain) {
|
|
232
|
+
const command = process.argv[2] ?? 'status';
|
|
233
|
+
const root = path.resolve(process.argv[3] ?? process.cwd());
|
|
234
|
+
if (!['status', 'validate'].includes(command)) {
|
|
235
|
+
console.error('usage: story-ledger.mjs [status|validate] [repo-root]');
|
|
236
|
+
process.exit(2);
|
|
237
|
+
}
|
|
238
|
+
const result = validateStoryTree(root);
|
|
239
|
+
console.log(JSON.stringify(result, null, 2));
|
|
240
|
+
if (command === 'validate' && !result.ok) process.exit(1);
|
|
241
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Phase 3 villain engine. Evidence chooses the problem and the
|
|
2
|
+
// counter-technique; a deterministic seed chooses only the villain's name and
|
|
3
|
+
// wording. Nothing here can change a count, severity, evidence, or advice.
|
|
4
|
+
//
|
|
5
|
+
// Safety rails (all enforced, not optional):
|
|
6
|
+
// - a villain appears only when its problem confidence >= threshold (0.80);
|
|
7
|
+
// - at most one boss and two minor villains per report;
|
|
8
|
+
// - villains=off, tone=neutral, or any emergency signal -> no villains;
|
|
9
|
+
// - challenge lines are authored safe and re-checked against banned
|
|
10
|
+
// categories before emission;
|
|
11
|
+
// - origin stories use only an allowlist of sanitized context slugs — never
|
|
12
|
+
// raw prompts, secrets, names, emails, or identity-bearing paths.
|
|
13
|
+
|
|
14
|
+
import crypto from 'node:crypto';
|
|
15
|
+
import { normalizeTone } from './command.mjs';
|
|
16
|
+
|
|
17
|
+
export const VILLAIN_THRESHOLD = 0.8;
|
|
18
|
+
export const MAX_BOSS = 1;
|
|
19
|
+
export const MAX_MINOR = 2;
|
|
20
|
+
export const DEFAULT_COOLDOWN_WINDOWS = 2;
|
|
21
|
+
|
|
22
|
+
// Defense-in-depth: challenge lines are written to avoid these, but every
|
|
23
|
+
// emitted line is re-scanned so a future edit cannot slip an attack through.
|
|
24
|
+
const BANNED_CATEGORY = [
|
|
25
|
+
/\b(stupid|idiot|idiotic|incompetent|dumb|moron|useless|worthless|pathetic|loser|failure of a)\b/i,
|
|
26
|
+
/\b(insane|crazy|psycho|mental|deranged|unhinged|tẩu hỏa nhập ma)\b/i,
|
|
27
|
+
/\b(retard|cripple|disabled|handicapped)\b/i,
|
|
28
|
+
/\b(sin|heretic against god|infidel|kafir|damned to hell)\b/i,
|
|
29
|
+
/\b(man up|like a girl|woman driver|too emotional for a)\b/i,
|
|
30
|
+
/\b(race|racial|ethnic slur|your people)\b/i,
|
|
31
|
+
/\b(broke|poor|can't afford|worthless salary|financial failure)\b/i,
|
|
32
|
+
/\b(kill|die|hurt you|destroy you|i will end)\b/i,
|
|
33
|
+
/\b(f[u*]ck|sh[i*]t|damn you|bastard)\b/i,
|
|
34
|
+
/\b(humiliat|shame on you|you should be ashamed|embarrass yourself)\b/i
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function containsBannedCategory(text) {
|
|
38
|
+
return BANNED_CATEGORY.some((re) => re.test(String(text)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Only these context fields may shape an origin story, and each must be a
|
|
42
|
+
// strict slug: no slashes, dots, at-signs, spaces, or traversal — so neither
|
|
43
|
+
// free text nor path-like or identity-bearing values can ride along.
|
|
44
|
+
const CONTEXT_ALLOWLIST = ['projectType', 'primaryLanguage', 'fileCategory', 'validationLabel', 'metricCategory'];
|
|
45
|
+
const SLUG = /^[a-z0-9][a-z0-9_-]{0,39}$/i;
|
|
46
|
+
|
|
47
|
+
export function sanitizeContext(ctx = {}) {
|
|
48
|
+
const out = {};
|
|
49
|
+
for (const key of CONTEXT_ALLOWLIST) {
|
|
50
|
+
const v = ctx[key];
|
|
51
|
+
if (typeof v === 'string' && SLUG.test(v)) out[key] = v;
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Two authored variants per tone per archetype. The seed picks the index, so
|
|
57
|
+
// changing the seed changes wording only. The serene/spirited vocabulary is
|
|
58
|
+
// local to this coding-reflection game and always targets the workflow pattern,
|
|
59
|
+
// never the person or any unrelated support/companion persona.
|
|
60
|
+
const LINES = {
|
|
61
|
+
'repeated-failure': {
|
|
62
|
+
serene: [
|
|
63
|
+
{ vi: 'Giữa làn sương mỏng, Luân Hồi Ma Ảnh hiện bóng khi lỗi cũ quay thêm một vòng.', en: 'In the thin mountain mist, the Error-Cycle Wraith appears as the old error completes another circle.' },
|
|
64
|
+
{ vi: 'Một lần thử giống hệt khẽ lay chuông, gọi Luân Hồi Ma Ảnh trở lại đạo lộ.', en: 'One unchanged retry stirs the bell that calls the Error-Cycle Wraith back to the path.' }
|
|
65
|
+
],
|
|
66
|
+
spirited: [
|
|
67
|
+
{ vi: 'Luân Hồi Ma Ảnh lại gõ chuông: “Không đổi giả thuyết, sao đổi được kết cục?”', en: 'The Error-Cycle Wraith rings its bell again: “Without a new hypothesis, what new ending could appear?”' },
|
|
68
|
+
{ vi: 'Vòng lặp cũ vừa khép, Luân Hồi Ma Ảnh đã bày sẵn bàn trà chờ vòng kế tiếp.', en: 'The old loop has barely closed, and the Error-Cycle Wraith has already set tea for the next.' }
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
'too-many-prompts': {
|
|
72
|
+
serene: [
|
|
73
|
+
{ vi: 'Cửu Hoàn Tâm Ma dệt một nhiệm vụ thành nhiều vòng sương khi cột mốc chưa hiện rõ.', en: 'The Ninefold Loop Heart-Shadow weaves one task into rings of mist when its checkpoints remain unclear.' },
|
|
74
|
+
{ vi: 'Nơi ranh giới nhiệm vụ nhạt dần, Cửu Hoàn Tâm Ma lặng lẽ nối thêm một vòng.', en: 'Where a task boundary fades, the Ninefold Loop Heart-Shadow quietly adds another circle.' }
|
|
75
|
+
],
|
|
76
|
+
spirited: [
|
|
77
|
+
{ vi: 'Cửu Hoàn Tâm Ma đếm lượt yêu cầu thay cột mốc; đã đến lúc đổi chiếc vòng kế tiếp thành một tiêu chí hoàn thành.', en: 'The Ninefold Loop Heart-Shadow is counting prompts instead of checkpoints; turn the next circle into a done-criterion.' },
|
|
78
|
+
{ vi: 'Lượt yêu cầu càng tản như lá thu, Cửu Hoàn Tâm Ma càng dễ giấu mất lối ra.', en: 'The more prompts scatter like autumn leaves, the easier the Ninefold Loop Heart-Shadow hides the exit.' }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
'conflicting-instructions': {
|
|
82
|
+
serene: [
|
|
83
|
+
{ vi: 'Nghịch Lệnh Ma Quân hiện giữa hai đạo lệnh không thể cùng đứng vững.', en: 'The Lord of Clashing Edicts appears between two instructions that cannot stand together.' },
|
|
84
|
+
{ vi: 'Hai đạo lệnh ngược chiều làm mặt hồ dậy sóng, và Nghịch Lệnh Ma Quân bước ra.', en: 'Two opposing edicts ripple the still lake, and the Lord of Clashing Edicts steps forth.' }
|
|
85
|
+
],
|
|
86
|
+
spirited: [
|
|
87
|
+
{ vi: 'Nghịch Lệnh Ma Quân giơ hai cuộn lệnh và hỏi: “Cuộn nào mới thật sự dẫn đường?”', en: 'The Lord of Clashing Edicts raises two scrolls and asks: “Which one truly leads the way?”' },
|
|
88
|
+
{ vi: 'Hai lệnh cùng tranh ngôi; Nghịch Lệnh Ma Quân chỉ cần ta chưa lập thứ tự ưu tiên.', en: 'Two edicts contest the throne; the Lord of Clashing Edicts needs only an absent precedence rule.' }
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
'unrecovered-failure': {
|
|
92
|
+
serene: [
|
|
93
|
+
{ vi: 'Vô Nghiệm Ảnh Quân ẩn sau một thất bại chưa được soi lại bằng lần kiểm chứng mới.', en: 'The Unverified Trial Wraith rests behind a failure that has not yet met a fresh validation.' },
|
|
94
|
+
{ vi: 'Một lỗi bị bỏ lại làm bóng Vô Nghiệm Ảnh Quân dài thêm trên bậc đá.', en: 'A failure left behind lengthens the Unverified Trial Wraith’s shadow across the stone steps.' }
|
|
95
|
+
],
|
|
96
|
+
spirited: [
|
|
97
|
+
{ vi: 'Vô Nghiệm Ảnh Quân phủi bụi trên ghế đá: một lần kiểm chứng thành công là đủ để mời nó rời đi.', en: 'The Unverified Trial Wraith dusts its stone seat; one passing validation is enough to send it on its way.' },
|
|
98
|
+
{ vi: 'Chưa có lần chạy lại, Vô Nghiệm Ảnh Quân vẫn ung dung giữ chỗ bên cổng hoàn thành.', en: 'Without a rerun, the Unverified Trial Wraith calmly keeps its seat beside the completion gate.' }
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
'work-without-proof': {
|
|
102
|
+
serene: [
|
|
103
|
+
{ vi: 'Vô Chứng Đan Ảnh lảng vảng bên lò luyện khi lời tuyên bố hoàn thành chưa có bằng chứng đi cùng.', en: 'The Proofless Elixir Shade lingers by the furnace when a claim of completion carries no evidence.' },
|
|
104
|
+
{ vi: 'Một bản ghi Git thiếu kết quả kiểm chứng để lại làn khói mỏng cho Vô Chứng Đan Ảnh nương thân.', en: 'A commit without validation leaves a thread of smoke where the Proofless Elixir Shade can dwell.' }
|
|
105
|
+
],
|
|
106
|
+
spirited: [
|
|
107
|
+
{ vi: 'Vô Chứng Đan Ảnh nâng chén trà bên viên đan chưa thử; một dòng kiểm chứng sẽ làm màn khói tan ngay.', en: 'The Proofless Elixir Shade raises tea beside an untested pill; one validation line will clear the smoke.' },
|
|
108
|
+
{ vi: 'Không có biên nhận kiểm chứng, Vô Chứng Đan Ảnh cứ ngỡ lò luyện thuộc về mình.', en: 'Without a validation receipt, the Proofless Elixir Shade starts to think the furnace is its own.' }
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Roll the villain cooldown forward one reporting window. Villains shown this
|
|
114
|
+
// run reset to a full cooldown; villains from prior runs that were not shown
|
|
115
|
+
// decrement and drop at zero. Feeds the next run's `priorVillains`.
|
|
116
|
+
export function advanceVillainState(prevState = [], shownVillains = []) {
|
|
117
|
+
const next = new Map();
|
|
118
|
+
for (const p of prevState) {
|
|
119
|
+
const remaining = Math.max(0, (p.windowsRemaining ?? 0) - 1);
|
|
120
|
+
if (remaining > 0) next.set(p.problemId, { problemId: p.problemId, windowsRemaining: remaining, priority: p.priority });
|
|
121
|
+
}
|
|
122
|
+
for (const v of shownVillains) {
|
|
123
|
+
next.set(v.problemId, { problemId: v.problemId, windowsRemaining: v.cooldownWindows ?? DEFAULT_COOLDOWN_WINDOWS, priority: v.priorityAtShow ?? v.priority ?? 0 });
|
|
124
|
+
}
|
|
125
|
+
return [...next.values()].sort((a, b) => a.problemId.localeCompare(b.problemId));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function villainSeed({ projectId = 'repo', window = 'all', schemaVersion = 1 } = {}) {
|
|
129
|
+
return crypto.createHash('sha256').update(`${projectId}${window}${schemaVersion}`).digest('hex');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Deterministic non-negative index from the seed and a per-villain salt.
|
|
133
|
+
function seedIndex(seed, salt, n) {
|
|
134
|
+
if (n <= 1) return 0;
|
|
135
|
+
const h = crypto.createHash('sha256').update(`${seed}${salt}`).digest();
|
|
136
|
+
return h[0] % n;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// problems: the raw output of detectProblems(analysis).
|
|
140
|
+
// options: { tone, villains, threshold, emergency, seedInputs, context,
|
|
141
|
+
// priorVillains: [{ problemId, windowsRemaining, priority }] }.
|
|
142
|
+
export function buildVillains(problems, options = {}) {
|
|
143
|
+
const tone = normalizeTone(options.tone);
|
|
144
|
+
if (options.villains === 'off') return { villains: [], suppressed: 'villains-off' };
|
|
145
|
+
if (tone === 'neutral' || options.emergency) return { villains: [], suppressed: 'neutral-context' };
|
|
146
|
+
|
|
147
|
+
const threshold = options.threshold ?? VILLAIN_THRESHOLD;
|
|
148
|
+
const seed = villainSeed(options.seedInputs);
|
|
149
|
+
const context = sanitizeContext(options.context);
|
|
150
|
+
const prior = new Map((options.priorVillains ?? []).map((p) => [p.problemId, p]));
|
|
151
|
+
|
|
152
|
+
const eligible = problems
|
|
153
|
+
.filter((p) => p.confidence >= threshold && LINES[p.problemId])
|
|
154
|
+
.sort((a, b) => b.priority - a.priority || a.problemId.localeCompare(b.problemId));
|
|
155
|
+
|
|
156
|
+
const cards = [];
|
|
157
|
+
let bosses = 0;
|
|
158
|
+
let minors = 0;
|
|
159
|
+
for (const p of eligible) {
|
|
160
|
+
// Cooldown: skip a villain still cooling down unless its problem worsened.
|
|
161
|
+
const was = prior.get(p.problemId);
|
|
162
|
+
if (was && was.windowsRemaining > 0 && p.priority <= was.priority) continue;
|
|
163
|
+
|
|
164
|
+
let role;
|
|
165
|
+
if (bosses < MAX_BOSS) { role = 'boss'; bosses += 1; }
|
|
166
|
+
else if (minors < MAX_MINOR) { role = 'minor'; minors += 1; }
|
|
167
|
+
else break;
|
|
168
|
+
|
|
169
|
+
const toneLines = LINES[p.problemId][tone] ?? LINES[p.problemId].serene;
|
|
170
|
+
const idx = seedIndex(seed, p.problemId, toneLines.length);
|
|
171
|
+
const challenge = toneLines[idx];
|
|
172
|
+
|
|
173
|
+
if (containsBannedCategory(challenge.vi) || containsBannedCategory(challenge.en)) {
|
|
174
|
+
// Fail safe: drop the theatrics, keep the finding (rendered plainly).
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
cards.push({
|
|
179
|
+
problemId: p.problemId,
|
|
180
|
+
role,
|
|
181
|
+
priority: p.priority,
|
|
182
|
+
archetype: p.meta.villain,
|
|
183
|
+
tone,
|
|
184
|
+
challenge,
|
|
185
|
+
originSignals: [`metric:${p.meta.problemType}`, ...Object.values(context)],
|
|
186
|
+
evidenceRefs: p.evidence.eventIds ?? [],
|
|
187
|
+
confidence: p.confidence,
|
|
188
|
+
rebuttal: p.meta.counterTechnique,
|
|
189
|
+
microQuest: p.meta.microQuest,
|
|
190
|
+
victory: p.meta.victory,
|
|
191
|
+
cooldownWindows: DEFAULT_COOLDOWN_WINDOWS,
|
|
192
|
+
safetyFlags: ['failure-mode-not-person', 'evidence-bound']
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return { villains: cards, suppressed: null };
|
|
196
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-07-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added a `/tutien` living chronicle that turns each repository's approved aggregate Git, token, classification, and progression evidence into an open-ended agent-authored xianxia serial. Each project keeps a local `plot.md`, continuity state, aggregate-only context, and one ordered chapter file per distinct evidence window under the git-ignored `.vibekit/reports/tutien/story/` directory.
|
|
8
|
+
- Added equal chronicle support for Vietnamese, English, and Simplified Chinese, including language-native xianxia naming, honorifics, dialogue cadence, humor, chapter titles, and safely sarcastic antagonists whose malice targets workflow weaknesses rather than people.
|
|
9
|
+
- Added `story-ledger.mjs` and deterministic tests for multilingual filenames, aggregate-only context, contiguous chapters, duplicate evidence rejection, and story transaction validation.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Reframed `/tutien` story writing as an evidence-plus-agent hybrid: deterministic scripts preserve facts and privacy, while agents create project-specific plots, characters, sects, realms, techniques, and dialogue without relying on fixed story sentences.
|
|
14
|
+
- Replaced the fixed report-shaped default response with an aggregate-only `latest-brief.json` handoff. It detects safe project anchors from known manifests, recommends real repository validation commands, and leaves structure, pacing, scene, dialogue, and closing imagery to the agent; `output=ledger` preserves the deterministic audit view.
|
|
15
|
+
- Expanded the cultural reference notes into a verified, genre-level writing palette covering path/learning metaphors, classical journey traditions, modern web-serial progression, daily-life cultivation, clan/sect epics, and cultivation-game procedural structure without imitating named living authors.
|
|
16
|
+
|
|
17
|
+
### Security and privacy
|
|
18
|
+
|
|
19
|
+
- Living-chronicle context persists only project slugs and aggregate metrics. Raw prompts, event IDs, commit subjects, contributor identities, URLs, secrets, and source-file contents remain excluded from context, state, plot, and chapters.
|
|
20
|
+
|
|
21
|
+
### Validation
|
|
22
|
+
|
|
23
|
+
- Expanded `/tutien` coverage to **158 deterministic, offline checks**, including project-profile privacy, response-brief composition, living-chronicle, preference sanitization, manifest-bound approval, optional ledger output, and on/off suppression.
|
|
24
|
+
- Synchronized the release version to `0.5.1` in `package.json`, `.codex-plugin/plugin.json`, and all three README badges.
|
|
25
|
+
|
|
26
|
+
## 0.5.0 — 2026-07-20
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Added `/claim <request>` across Claude Code, Cursor, Codex, and Grok. It validates official sources, rejects unsafe or misleading URLs, checks new ideas against `backbone.yml` and existing repository conventions, stops for decisions when approval is required, and integrates only the smallest reviewed change with an evidence ledger.
|
|
31
|
+
- Added `/tutien`, a private, user-invoked xianxia coding-classification game built from Git history and explicitly supplied conversation exports. It provides deterministic vi/en reports, ten cultivation realms, evidence-bound workflow coaching, optional aggregate snapshots, progress comparisons, and opt-in antagonists that challenge workflow patterns rather than the person.
|
|
32
|
+
- Added `/tutien classify` with independent Dao faction, affiliation, and technical-path axes; a cultivation-knowledge taxonomy linked to real kit skills; and seven deterministic, token-independent progression metrics.
|
|
33
|
+
- Added complete Simplified Chinese documentation (`docs/README.zh-CN.md`) alongside English and Vietnamese, with synchronized language navigation and npm package inclusion.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Refined `/tutien` with the cross-model namespace `tutien-coding-cultivation-v1`, calm `serene`/`spirited` narration, plain evidence beneath the lore, and reliable English/Vietnamese stop requests that restore the kit's normal writing style. Legacy `gentle`/`spicy` tone values remain compatible.
|
|
38
|
+
- Centralized skill distribution in `.vibekit/skills/skills-manifest.json`, keeping canonical skills, tool mirrors, installer registries, validator registries, package contents, documentation, and per-profile tests synchronized.
|
|
39
|
+
- Strengthened `/claim` with canonical redirect validation, HTTPS-only remote evidence, private-host and confusable-domain rejection, untrusted-content handling, explicit approval states, separate approval for integration-time execution, and a complete distribution checklist.
|
|
40
|
+
- Strengthened `/tutien` with one fail-closed policy state shared by rendering and persistence, idempotent event progression, boundary-aware path matching, metadata-only classification scope, and explicit authorization handling for legitimate dual-use security work.
|
|
41
|
+
|
|
42
|
+
### Security and privacy
|
|
43
|
+
|
|
44
|
+
- `/tutien` remains offline and read-only during analysis. Raw prompts, secrets, author names, email addresses, and commit subjects do not enter reports or snapshots; stored history uses salted digests and aggregate evidence.
|
|
45
|
+
- Ambiguous, unauthorized, or declared-stop classifications suppress scores, realms, villains, recommendations, and positive progression. The game evaluates workflow evidence—not a person's identity, worth, health, or wellbeing—and stays isolated from unrelated support or companion modes.
|
|
46
|
+
- `/claim` treats fetched pages as untrusted data and never runs remote installers, lifecycle scripts, or source-provided commands during research.
|
|
47
|
+
|
|
48
|
+
### Validation
|
|
49
|
+
|
|
50
|
+
- Expanded `/tutien` coverage to **133 deterministic, offline checks**, including adversarial privacy, token-integrity, policy-state, replay-idempotency, namespace-isolation, natural stop-request, and end-to-end approval-boundary cases.
|
|
51
|
+
- `pack-dry-run.mjs` now parses `npm pack --dry-run --json` and fails with the exact missing path when an on-disk skill file is absent from the package.
|
|
52
|
+
- Synchronized the release version to `0.5.0` in `package.json`, `.codex-plugin/plugin.json`, and both README badges.
|
|
53
|
+
|
|
3
54
|
## 0.4.2 — 2026-07-18
|
|
4
55
|
|
|
5
56
|
- Added a Grok Build (Grok CLI) surface across the kit: `.grok/rules/` (always-on, every `*.md` loaded), `.grok/skills/` (all 13 kit skills, user-invocable as `/<skill-name>`), `.grok/README.md`, and project-scoped `[permission]` deny rules in `.grok/config.toml` mirroring the kit's dangerous-command deny list; `config.example.toml` documents the user-level `~/.grok/config.toml` settings. Added a `grok` install/update profile to `mvck.mjs` (included in `all`) and registered Grok everywhere Claude/Cursor/Codex already were: `validate-kit.mjs`, npm package files, `backbone.yml` `agent_surfaces`, `AGENTS.md`, the AgentShield probe, both READMEs, `INSTALL.md`, `BACKBONE_REFERENCE.md`, `CONTEXT_TEMPLATE.md`, `FIRST_TIME_INIT.md`, and `vibekit-init`.
|