minimal-vibe-coding-kit 0.5.0 → 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.
Files changed (91) hide show
  1. package/.agents/skills/tutien/SKILL.md +36 -13
  2. package/.agents/skills/tutien/references/adaptive-response.md +68 -0
  3. package/.agents/skills/tutien/references/lore-sources.md +28 -0
  4. package/.agents/skills/tutien/references/privacy.md +13 -1
  5. package/.agents/skills/tutien/references/schema.md +32 -1
  6. package/.agents/skills/tutien/references/story-system.md +176 -0
  7. package/.agents/skills/tutien/references/vi-style-guide.md +89 -0
  8. package/.agents/skills/tutien/references/voice-and-mode.md +23 -8
  9. package/.agents/skills/tutien/scripts/catalog.mjs +17 -17
  10. package/.agents/skills/tutien/scripts/classify.mjs +64 -37
  11. package/.agents/skills/tutien/scripts/command.mjs +40 -21
  12. package/.agents/skills/tutien/scripts/project-profile.mjs +202 -0
  13. package/.agents/skills/tutien/scripts/render-report.mjs +128 -72
  14. package/.agents/skills/tutien/scripts/response-brief.mjs +103 -0
  15. package/.agents/skills/tutien/scripts/run-tutien.mjs +141 -63
  16. package/.agents/skills/tutien/scripts/story-ledger.mjs +241 -0
  17. package/.agents/skills/tutien/scripts/villains.mjs +14 -14
  18. package/.claude/skills/tutien/SKILL.md +36 -13
  19. package/.claude/skills/tutien/references/adaptive-response.md +68 -0
  20. package/.claude/skills/tutien/references/lore-sources.md +28 -0
  21. package/.claude/skills/tutien/references/privacy.md +13 -1
  22. package/.claude/skills/tutien/references/schema.md +32 -1
  23. package/.claude/skills/tutien/references/story-system.md +176 -0
  24. package/.claude/skills/tutien/references/vi-style-guide.md +89 -0
  25. package/.claude/skills/tutien/references/voice-and-mode.md +23 -8
  26. package/.claude/skills/tutien/scripts/catalog.mjs +17 -17
  27. package/.claude/skills/tutien/scripts/classify.mjs +64 -37
  28. package/.claude/skills/tutien/scripts/command.mjs +40 -21
  29. package/.claude/skills/tutien/scripts/project-profile.mjs +202 -0
  30. package/.claude/skills/tutien/scripts/render-report.mjs +128 -72
  31. package/.claude/skills/tutien/scripts/response-brief.mjs +103 -0
  32. package/.claude/skills/tutien/scripts/run-tutien.mjs +141 -63
  33. package/.claude/skills/tutien/scripts/story-ledger.mjs +241 -0
  34. package/.claude/skills/tutien/scripts/villains.mjs +14 -14
  35. package/.codex-plugin/plugin.json +1 -1
  36. package/.cursor/skills/tutien/SKILL.md +36 -13
  37. package/.cursor/skills/tutien/references/adaptive-response.md +68 -0
  38. package/.cursor/skills/tutien/references/lore-sources.md +28 -0
  39. package/.cursor/skills/tutien/references/privacy.md +13 -1
  40. package/.cursor/skills/tutien/references/schema.md +32 -1
  41. package/.cursor/skills/tutien/references/story-system.md +176 -0
  42. package/.cursor/skills/tutien/references/vi-style-guide.md +89 -0
  43. package/.cursor/skills/tutien/references/voice-and-mode.md +23 -8
  44. package/.cursor/skills/tutien/scripts/catalog.mjs +17 -17
  45. package/.cursor/skills/tutien/scripts/classify.mjs +64 -37
  46. package/.cursor/skills/tutien/scripts/command.mjs +40 -21
  47. package/.cursor/skills/tutien/scripts/project-profile.mjs +202 -0
  48. package/.cursor/skills/tutien/scripts/render-report.mjs +128 -72
  49. package/.cursor/skills/tutien/scripts/response-brief.mjs +103 -0
  50. package/.cursor/skills/tutien/scripts/run-tutien.mjs +141 -63
  51. package/.cursor/skills/tutien/scripts/story-ledger.mjs +241 -0
  52. package/.cursor/skills/tutien/scripts/villains.mjs +14 -14
  53. package/.grok/skills/tutien/SKILL.md +36 -13
  54. package/.grok/skills/tutien/references/adaptive-response.md +68 -0
  55. package/.grok/skills/tutien/references/lore-sources.md +28 -0
  56. package/.grok/skills/tutien/references/privacy.md +13 -1
  57. package/.grok/skills/tutien/references/schema.md +32 -1
  58. package/.grok/skills/tutien/references/story-system.md +176 -0
  59. package/.grok/skills/tutien/references/vi-style-guide.md +89 -0
  60. package/.grok/skills/tutien/references/voice-and-mode.md +23 -8
  61. package/.grok/skills/tutien/scripts/catalog.mjs +17 -17
  62. package/.grok/skills/tutien/scripts/classify.mjs +64 -37
  63. package/.grok/skills/tutien/scripts/command.mjs +40 -21
  64. package/.grok/skills/tutien/scripts/project-profile.mjs +202 -0
  65. package/.grok/skills/tutien/scripts/render-report.mjs +128 -72
  66. package/.grok/skills/tutien/scripts/response-brief.mjs +103 -0
  67. package/.grok/skills/tutien/scripts/run-tutien.mjs +141 -63
  68. package/.grok/skills/tutien/scripts/story-ledger.mjs +241 -0
  69. package/.grok/skills/tutien/scripts/villains.mjs +14 -14
  70. package/.vibekit/skills/tutien/SKILL.md +36 -13
  71. package/.vibekit/skills/tutien/references/adaptive-response.md +68 -0
  72. package/.vibekit/skills/tutien/references/lore-sources.md +28 -0
  73. package/.vibekit/skills/tutien/references/privacy.md +13 -1
  74. package/.vibekit/skills/tutien/references/schema.md +32 -1
  75. package/.vibekit/skills/tutien/references/story-system.md +176 -0
  76. package/.vibekit/skills/tutien/references/vi-style-guide.md +89 -0
  77. package/.vibekit/skills/tutien/references/voice-and-mode.md +23 -8
  78. package/.vibekit/skills/tutien/scripts/catalog.mjs +17 -17
  79. package/.vibekit/skills/tutien/scripts/classify.mjs +64 -37
  80. package/.vibekit/skills/tutien/scripts/command.mjs +40 -21
  81. package/.vibekit/skills/tutien/scripts/project-profile.mjs +202 -0
  82. package/.vibekit/skills/tutien/scripts/render-report.mjs +128 -72
  83. package/.vibekit/skills/tutien/scripts/response-brief.mjs +103 -0
  84. package/.vibekit/skills/tutien/scripts/run-tutien.mjs +141 -63
  85. package/.vibekit/skills/tutien/scripts/story-ledger.mjs +241 -0
  86. package/.vibekit/skills/tutien/scripts/villains.mjs +14 -14
  87. package/CHANGELOG.md +22 -1
  88. package/README.md +5 -3
  89. package/docs/README.vi.md +5 -3
  90. package/docs/README.zh-CN.md +5 -3
  91. package/package.json +2 -2
@@ -13,6 +13,14 @@ const TONE_ALIASES = new Map([
13
13
  ['gentle', 'serene'],
14
14
  ['spicy', 'spirited']
15
15
  ]);
16
+ const STORY_LANGUAGES = new Set(['auto', 'vi', 'en', 'zh']);
17
+ const STORY_STYLES = new Set(['auto', 'classic-quest', 'web-serial', 'daily-life', 'clan-epic', 'comic-adventure']);
18
+ const STORY_FOCUSES = new Set(['balanced', 'project', 'characters', 'world', 'sect-politics']);
19
+
20
+ const allowedOr = (value, allowed, fallback) => allowed.has(String(value).toLowerCase()) ? String(value).toLowerCase() : fallback;
21
+ export const normalizeStoryLanguage = (value) => allowedOr(value, STORY_LANGUAGES, 'auto');
22
+ export const normalizeStoryStyle = (value) => allowedOr(value, STORY_STYLES, 'auto');
23
+ export const normalizeStoryFocus = (value) => allowedOr(value, STORY_FOCUSES, 'balanced');
16
24
 
17
25
  export const TUTIEN_EXPERIENCE = Object.freeze({
18
26
  kind: 'wholesome-coding-classification-game',
@@ -48,15 +56,21 @@ export function parseInvocation(argsString = '') {
48
56
  score: 'hidden',
49
57
  includeExcerpts: false,
50
58
  range: 'all',
51
- sources: []
59
+ sources: [],
60
+ story: 'on',
61
+ storyLanguage: 'auto',
62
+ storyStyle: 'auto',
63
+ storyFocus: 'balanced',
64
+ output: 'brief'
52
65
  };
66
+ const providedOptions = new Set();
53
67
  let action = null;
54
68
 
55
69
  // A direct request to end the experience always wins over report parsing.
56
70
  // This keeps prose mode and approval state from lingering after the user
57
71
  // asks to return to the kit's normal voice.
58
72
  if (isStopRequest(argsString)) {
59
- return { action: 'off', isModeToggle: true, explicitAction: true, options };
73
+ return { action: 'off', isModeToggle: true, explicitAction: true, options, providedOptions: [] };
60
74
  }
61
75
 
62
76
  for (const tok of tokens) {
@@ -68,24 +82,29 @@ export function parseInvocation(argsString = '') {
68
82
  const key = tok.slice(0, eq);
69
83
  const value = tok.slice(eq + 1);
70
84
  switch (key) {
71
- case 'language': options.language = value; break;
72
- case 'tone': options.tone = normalizeTone(value); break;
73
- case 'villains': options.villains = value === 'off' ? 'off' : 'on'; break;
74
- case 'score': options.score = value === 'show' ? 'show' : 'hidden'; break;
75
- case 'scope': options.scope = value; break;
76
- case 'privacy': options.privacy = value; break;
77
- case 'range': options.range = value; break;
78
- case 'include-excerpts': options.includeExcerpts = value === 'true'; break;
79
- case 'sources': options.sources = value.split(',').filter(Boolean); break;
80
- case 'previous': options.previous = value; break;
81
- case 'metric': options.metric = value; break;
82
- case 'approve': options.approve = value; break;
83
- case 'snapshot': options.snapshot = value === 'true'; break;
84
- case 'faction': options.faction = value; break;
85
- case 'affiliation': options.affiliation = value; break;
86
- case 'paths': options.paths = value.split(',').filter(Boolean); break;
87
- case 'domains': options.domains = value.split(',').filter(Boolean); break;
88
- case 'authorization': options.authorization = value; break;
85
+ case 'language': providedOptions.add(key); options.language = value; break;
86
+ case 'tone': providedOptions.add(key); options.tone = normalizeTone(value); break;
87
+ case 'villains': providedOptions.add(key); options.villains = value === 'off' ? 'off' : 'on'; break;
88
+ case 'score': providedOptions.add(key); options.score = value === 'show' ? 'show' : 'hidden'; break;
89
+ case 'scope': providedOptions.add(key); options.scope = value; break;
90
+ case 'privacy': providedOptions.add(key); options.privacy = value; break;
91
+ case 'range': providedOptions.add(key); options.range = value; break;
92
+ case 'include-excerpts': providedOptions.add(key); options.includeExcerpts = value === 'true'; break;
93
+ case 'sources': providedOptions.add(key); options.sources = value.split(',').filter(Boolean); break;
94
+ case 'previous': providedOptions.add(key); options.previous = value; break;
95
+ case 'metric': providedOptions.add(key); options.metric = value; break;
96
+ case 'approve': providedOptions.add(key); options.approve = value; break;
97
+ case 'snapshot': providedOptions.add(key); options.snapshot = value === 'true'; break;
98
+ case 'faction': providedOptions.add(key); options.faction = value; break;
99
+ case 'affiliation': providedOptions.add(key); options.affiliation = value; break;
100
+ case 'paths': providedOptions.add(key); options.paths = value.split(',').filter(Boolean); break;
101
+ case 'domains': providedOptions.add(key); options.domains = value.split(',').filter(Boolean); break;
102
+ case 'authorization': providedOptions.add(key); options.authorization = value; break;
103
+ case 'story': providedOptions.add(key); options.story = value === 'off' ? 'off' : 'on'; break;
104
+ case 'story-language': providedOptions.add(key); options.storyLanguage = normalizeStoryLanguage(value); break;
105
+ case 'story-style': providedOptions.add(key); options.storyStyle = normalizeStoryStyle(value); break;
106
+ case 'story-focus': providedOptions.add(key); options.storyFocus = normalizeStoryFocus(value); break;
107
+ case 'output': providedOptions.add(key); options.output = value === 'ledger' ? 'ledger' : 'brief'; break;
89
108
  default: break;
90
109
  }
91
110
  }
@@ -96,7 +115,7 @@ export function parseInvocation(argsString = '') {
96
115
  const explicitAction = action !== null;
97
116
  if (!action) action = 'preview';
98
117
  const isModeToggle = action === 'on' || action === 'off' || action === 'status';
99
- return { action, isModeToggle, explicitAction, options };
118
+ return { action, isModeToggle, explicitAction, options, providedOptions: [...providedOptions] };
100
119
  }
101
120
 
102
121
  // The livelier `spirited` voice is explicit opt-in. Safety-sensitive contexts
@@ -0,0 +1,202 @@
1
+ // Read a deliberately small, known set of project manifests and reduce them
2
+ // to safe metadata. Repo scripts are never executed and their bodies are never
3
+ // returned or persisted.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+
8
+ const MAX_METADATA_BYTES = 1024 * 1024;
9
+ const MANIFESTS = Object.freeze([
10
+ 'backbone.yml',
11
+ 'package.json',
12
+ 'yarn.lock',
13
+ 'package-lock.json',
14
+ 'pnpm-lock.yaml',
15
+ 'bun.lock',
16
+ 'bun.lockb',
17
+ 'tsconfig.json',
18
+ 'pyproject.toml',
19
+ 'go.mod',
20
+ 'Cargo.toml',
21
+ 'pubspec.yaml',
22
+ 'composer.json',
23
+ 'pom.xml',
24
+ 'build.gradle',
25
+ 'build.gradle.kts'
26
+ ]);
27
+
28
+ const STACK_RULES = Object.freeze([
29
+ ['react', ['react']],
30
+ ['typescript', ['typescript']],
31
+ ['vite', ['vite']],
32
+ ['material-ui', ['@mui/material', '@material-ui/core']],
33
+ ['nextjs', ['next']],
34
+ ['vue', ['vue']],
35
+ ['nuxt', ['nuxt']],
36
+ ['angular', ['@angular/core']],
37
+ ['svelte', ['svelte']],
38
+ ['tailwind-css', ['tailwindcss']],
39
+ ['express', ['express']],
40
+ ['nestjs', ['@nestjs/core']]
41
+ ]);
42
+
43
+ const SAFE_SCRIPT = /^[A-Za-z0-9][A-Za-z0-9:_-]{0,63}$/;
44
+ const VALIDATION_PRIORITY = ['validate', 'check', 'test', 'test:unit', 'lint', 'typecheck', 'build'];
45
+
46
+ function regularFile(root, relative) {
47
+ const absolute = path.join(path.resolve(root), relative);
48
+ try {
49
+ const stat = fs.lstatSync(absolute);
50
+ if (!stat.isFile() || stat.isSymbolicLink()) return null;
51
+ return { path: relative, absolute, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) };
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ export function inventoryProjectMetadata(root) {
58
+ return MANIFESTS.map((name) => regularFile(root, name))
59
+ .filter(Boolean)
60
+ .map(({ path: relative, size, mtimeMs }) => ({ path: relative, size, mtimeMs }));
61
+ }
62
+
63
+ function readKnownFile(root, name, warnings) {
64
+ const item = regularFile(root, name);
65
+ if (!item) return null;
66
+ if (item.size > MAX_METADATA_BYTES) {
67
+ warnings.push({ code: 'metadata-too-large', path: name });
68
+ return null;
69
+ }
70
+ try {
71
+ return fs.readFileSync(item.absolute, 'utf8');
72
+ } catch {
73
+ warnings.push({ code: 'metadata-unreadable', path: name });
74
+ return null;
75
+ }
76
+ }
77
+
78
+ function safeSlug(value, fallback = null) {
79
+ const text = String(value ?? '').trim().toLowerCase();
80
+ return /^[a-z0-9][a-z0-9._-]{0,79}$/.test(text) ? text : fallback;
81
+ }
82
+
83
+ function yamlScalar(text, key) {
84
+ const match = text?.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
85
+ if (!match) return null;
86
+ const value = match[1].trim().replace(/^['"]|['"]$/g, '');
87
+ return value.length <= 160 && !/[\r\n]/.test(value) ? value : null;
88
+ }
89
+
90
+ function packageManager(pkg, files) {
91
+ const declared = String(pkg?.packageManager ?? '').match(/^(npm|yarn|pnpm|bun)@/i)?.[1]?.toLowerCase();
92
+ if (declared) return declared;
93
+ if (files.has('pnpm-lock.yaml')) return 'pnpm';
94
+ if (files.has('yarn.lock')) return 'yarn';
95
+ if (files.has('bun.lock') || files.has('bun.lockb')) return 'bun';
96
+ return 'npm';
97
+ }
98
+
99
+ function scriptCommand(manager, key) {
100
+ if (manager === 'yarn') return `yarn ${key}`;
101
+ if (manager === 'pnpm') return `pnpm ${key}`;
102
+ if (manager === 'bun') return `bun run ${key}`;
103
+ return `npm run ${key}`;
104
+ }
105
+
106
+ function packageFacts(pkg, files) {
107
+ const deps = new Set([
108
+ ...Object.keys(pkg?.dependencies ?? {}),
109
+ ...Object.keys(pkg?.devDependencies ?? {}),
110
+ ...Object.keys(pkg?.peerDependencies ?? {})
111
+ ]);
112
+ const stack = STACK_RULES.filter(([, names]) => names.some((name) => deps.has(name))).map(([name]) => name);
113
+ if (files.has('tsconfig.json') && !stack.includes('typescript')) stack.push('typescript');
114
+
115
+ const manager = packageManager(pkg, files);
116
+ const scripts = Object.keys(pkg?.scripts ?? {}).filter((key) => SAFE_SCRIPT.test(key));
117
+ const validationCommands = VALIDATION_PRIORITY
118
+ .filter((key) => scripts.includes(key))
119
+ .slice(0, 3)
120
+ .map((key) => scriptCommand(manager, key));
121
+
122
+ const hasWebFramework = stack.some((name) => ['react', 'nextjs', 'vue', 'nuxt', 'angular', 'svelte'].includes(name));
123
+ const hasLibraryEntry = Boolean(pkg?.main || pkg?.module || pkg?.exports);
124
+ return {
125
+ projectId: safeSlug(pkg?.name),
126
+ packageManager: manager,
127
+ stack,
128
+ primaryLanguage: stack.includes('typescript') ? 'typescript' : 'javascript',
129
+ projectType: hasLibraryEntry ? 'library' : (hasWebFramework ? 'web-app' : 'node-project'),
130
+ validationCommands
131
+ };
132
+ }
133
+
134
+ function uniqueSlugs(values, limit = 16) {
135
+ return [...new Set(values.map((value) => safeSlug(value)).filter(Boolean))].slice(0, limit);
136
+ }
137
+
138
+ export function buildProjectProfile(root, options = {}) {
139
+ const resolvedRoot = path.resolve(root);
140
+ const inventory = inventoryProjectMetadata(resolvedRoot);
141
+ const files = new Set(inventory.map((item) => item.path));
142
+ const warnings = [];
143
+ const profile = {
144
+ projectId: safeSlug(path.basename(resolvedRoot), 'repo'),
145
+ description: null,
146
+ primaryLanguage: null,
147
+ projectType: null,
148
+ domains: [],
149
+ stack: [],
150
+ packageManager: null,
151
+ validationCommands: [],
152
+ recommendedValidation: null,
153
+ metadataSources: inventory.map((item) => item.path),
154
+ kitInstalled: files.has('backbone.yml') && fs.existsSync(path.join(resolvedRoot, '.vibekit', 'skills')),
155
+ authorization: options.authorization ?? null,
156
+ declared: { faction: options.faction, affiliation: options.affiliation, paths: options.paths },
157
+ warnings
158
+ };
159
+
160
+ const backbone = readKnownFile(resolvedRoot, 'backbone.yml', warnings);
161
+ if (backbone) {
162
+ profile.description = yamlScalar(backbone, 'description');
163
+ profile.primaryLanguage = safeSlug(yamlScalar(backbone, 'primary_language'));
164
+ profile.projectType = safeSlug(yamlScalar(backbone, 'type'));
165
+ }
166
+
167
+ const packageText = readKnownFile(resolvedRoot, 'package.json', warnings);
168
+ if (packageText) {
169
+ try {
170
+ const pkg = JSON.parse(packageText);
171
+ const facts = packageFacts(pkg, files);
172
+ Object.assign(profile, {
173
+ projectId: facts.projectId ?? profile.projectId,
174
+ packageManager: facts.packageManager,
175
+ stack: facts.stack,
176
+ primaryLanguage: profile.primaryLanguage ?? facts.primaryLanguage,
177
+ projectType: profile.projectType ?? facts.projectType,
178
+ validationCommands: facts.validationCommands
179
+ });
180
+ } catch {
181
+ warnings.push({ code: 'metadata-invalid-json', path: 'package.json' });
182
+ }
183
+ }
184
+
185
+ if (!profile.primaryLanguage) {
186
+ if (files.has('pyproject.toml')) profile.primaryLanguage = 'python';
187
+ else if (files.has('go.mod')) profile.primaryLanguage = 'go';
188
+ else if (files.has('Cargo.toml')) profile.primaryLanguage = 'rust';
189
+ else if (files.has('pubspec.yaml')) profile.primaryLanguage = 'dart';
190
+ }
191
+ if (!profile.projectType) profile.projectType = files.has('package.json') ? 'node-project' : 'software-project';
192
+
193
+ const domains = [...(options.domains ?? [])];
194
+ if (profile.stack.some((name) => ['react', 'nextjs', 'vue', 'nuxt', 'angular', 'svelte'].includes(name))) domains.push('frontend', 'application', 'interface');
195
+ if (profile.stack.includes('material-ui')) domains.push('ui', 'design', 'component');
196
+ if (profile.projectType === 'library') domains.push('library', 'framework');
197
+ profile.domains = uniqueSlugs(domains, 12);
198
+ profile.stack = uniqueSlugs(profile.stack, 12);
199
+ profile.recommendedValidation = profile.validationCommands[0] ?? null;
200
+ return profile;
201
+ }
202
+