minimal-vibe-coding-kit 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/.agents/skills/agentshield-security-review/SKILL.md +75 -0
  2. package/.agents/skills/agentshield-security-review/agents/openai.yaml +5 -0
  3. package/.agents/skills/agentshield-security-review/references/native-install.md +97 -0
  4. package/.agents/skills/agentshield-security-review/references/report-template.md +54 -0
  5. package/.agents/skills/agentshield-security-review/references/review-checklist.md +45 -0
  6. package/.agents/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +110 -0
  7. package/.agents/skills/autoresearch-coding/README.md +15 -0
  8. package/.agents/skills/autoresearch-coding/SKILL.md +101 -0
  9. package/.agents/skills/autoresearch-coding/references/experiment-contract.md +13 -0
  10. package/.agents/skills/autoresearch-coding/references/metric-extraction.md +8 -0
  11. package/.agents/skills/autoresearch-coding/references/result-ledger.md +15 -0
  12. package/.agents/skills/autoresearch-coding/scripts/log_result.py +24 -0
  13. package/.agents/skills/autoresearch-coding/scripts/run_logged.py +30 -0
  14. package/.agents/skills/clearthought/SKILL.md +100 -0
  15. package/.agents/skills/clearthought/examples/decision-framework.md +441 -0
  16. package/.agents/skills/clearthought/examples/metagame-examples.md +536 -0
  17. package/.agents/skills/clearthought/examples/sequential-thinking.md +380 -0
  18. package/.agents/skills/clearthought/references/output-schemas.md +494 -0
  19. package/.agents/skills/clearthought/references/parameter-reference.md +482 -0
  20. package/.agents/skills/coding-level/SKILL.md +27 -0
  21. package/.agents/skills/coding-level/references/level-0.md +131 -0
  22. package/.agents/skills/coding-level/references/level-1.md +118 -0
  23. package/.agents/skills/coding-level/references/level-2.md +140 -0
  24. package/.agents/skills/coding-level/references/level-3.md +142 -0
  25. package/.agents/skills/coding-level/references/level-4.md +152 -0
  26. package/.agents/skills/coding-level/references/level-5.md +84 -0
  27. package/.agents/skills/daily-workflow-curator/SKILL.md +52 -0
  28. package/.agents/skills/memento/SKILL.md +36 -0
  29. package/.agents/skills/reviewing-4p-priorities/SKILL.md +72 -0
  30. package/.agents/skills/reviewing-4p-priorities/examples.md +298 -0
  31. package/.agents/skills/sequential-thinking/SKILL.md +106 -0
  32. package/.agents/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
  33. package/.agents/skills/sequential-thinking/examples/branching-exploration.md +474 -0
  34. package/.agents/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
  35. package/.agents/skills/sequential-thinking/examples/revision-pattern.md +304 -0
  36. package/.agents/skills/sequential-thinking/references/output-schema.md +483 -0
  37. package/.agents/skills/sequential-thinking/references/parameters.md +463 -0
  38. package/.agents/skills/sequential-thinking/references/patterns.md +666 -0
  39. package/.agents/skills/vibekit-init/SKILL.md +52 -0
  40. package/.agents/skills/visual-design-loop/SKILL.md +103 -0
  41. package/.agents/skills/visual-design-loop/agents/openai.yaml +4 -0
  42. package/.claude/agents/code-reviewer.md +11 -0
  43. package/.claude/agents/context-architect.md +11 -0
  44. package/.claude/agents/debug-fixer.md +11 -0
  45. package/.claude/agents/hypothesis-planner.md +11 -0
  46. package/.claude/agents/implementation-hacker.md +11 -0
  47. package/.claude/agents/research-coordinator.md +11 -0
  48. package/.claude/agents/results-analyst.md +11 -0
  49. package/.claude/agents/security-reviewer.md +11 -0
  50. package/.claude/agents/test-runner.md +11 -0
  51. package/.claude/agents/workflow-curator.md +11 -0
  52. package/.claude/commands/autoresearch-coding.md +15 -0
  53. package/.claude/commands/council.md +7 -0
  54. package/.claude/commands/daily-enhance.md +7 -0
  55. package/.claude/commands/init-vibe.md +7 -0
  56. package/.claude/commands/security-scan.md +7 -0
  57. package/.claude/commands/vibe-finalize.md +7 -0
  58. package/.claude/rules/autoresearch.md +3 -0
  59. package/.claude/rules/security.md +3 -0
  60. package/.claude/rules/tooling.md +6 -0
  61. package/.claude/rules/vibe-core.md +8 -0
  62. package/.claude/settings.json +22 -0
  63. package/.claude/skills/agentshield-security-review/SKILL.md +75 -0
  64. package/.claude/skills/agentshield-security-review/agents/openai.yaml +5 -0
  65. package/.claude/skills/agentshield-security-review/references/native-install.md +97 -0
  66. package/.claude/skills/agentshield-security-review/references/report-template.md +54 -0
  67. package/.claude/skills/agentshield-security-review/references/review-checklist.md +45 -0
  68. package/.claude/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +110 -0
  69. package/.claude/skills/autoresearch-coding/README.md +15 -0
  70. package/.claude/skills/autoresearch-coding/SKILL.md +101 -0
  71. package/.claude/skills/autoresearch-coding/references/experiment-contract.md +13 -0
  72. package/.claude/skills/autoresearch-coding/references/metric-extraction.md +8 -0
  73. package/.claude/skills/autoresearch-coding/references/result-ledger.md +15 -0
  74. package/.claude/skills/autoresearch-coding/scripts/log_result.py +24 -0
  75. package/.claude/skills/autoresearch-coding/scripts/run_logged.py +30 -0
  76. package/.claude/skills/clearthought/SKILL.md +100 -0
  77. package/.claude/skills/clearthought/examples/decision-framework.md +441 -0
  78. package/.claude/skills/clearthought/examples/metagame-examples.md +536 -0
  79. package/.claude/skills/clearthought/examples/sequential-thinking.md +380 -0
  80. package/.claude/skills/clearthought/references/output-schemas.md +494 -0
  81. package/.claude/skills/clearthought/references/parameter-reference.md +482 -0
  82. package/.claude/skills/coding-level/SKILL.md +27 -0
  83. package/.claude/skills/coding-level/references/level-0.md +131 -0
  84. package/.claude/skills/coding-level/references/level-1.md +118 -0
  85. package/.claude/skills/coding-level/references/level-2.md +140 -0
  86. package/.claude/skills/coding-level/references/level-3.md +142 -0
  87. package/.claude/skills/coding-level/references/level-4.md +152 -0
  88. package/.claude/skills/coding-level/references/level-5.md +84 -0
  89. package/.claude/skills/daily-workflow-curator/SKILL.md +52 -0
  90. package/.claude/skills/memento/SKILL.md +36 -0
  91. package/.claude/skills/reviewing-4p-priorities/SKILL.md +72 -0
  92. package/.claude/skills/reviewing-4p-priorities/examples.md +298 -0
  93. package/.claude/skills/sequential-thinking/SKILL.md +106 -0
  94. package/.claude/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
  95. package/.claude/skills/sequential-thinking/examples/branching-exploration.md +474 -0
  96. package/.claude/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
  97. package/.claude/skills/sequential-thinking/examples/revision-pattern.md +304 -0
  98. package/.claude/skills/sequential-thinking/references/output-schema.md +483 -0
  99. package/.claude/skills/sequential-thinking/references/parameters.md +463 -0
  100. package/.claude/skills/sequential-thinking/references/patterns.md +666 -0
  101. package/.claude/skills/vibekit-init/SKILL.md +52 -0
  102. package/.claude/skills/visual-design-loop/SKILL.md +103 -0
  103. package/.claude/skills/visual-design-loop/agents/openai.yaml +4 -0
  104. package/.codex/README.md +11 -0
  105. package/.codex/config.example.toml +8 -0
  106. package/.codex-plugin/plugin.json +10 -0
  107. package/.cursor/commands/autoresearch-coding.md +11 -0
  108. package/.cursor/commands/council.md +3 -0
  109. package/.cursor/commands/daily-enhance.md +3 -0
  110. package/.cursor/commands/init-vibe.md +3 -0
  111. package/.cursor/commands/security-scan.md +3 -0
  112. package/.cursor/commands/vibe-finalize.md +3 -0
  113. package/.cursor/rules/001-vibe-core.mdc +6 -0
  114. package/.cursor/rules/010-init.mdc +7 -0
  115. package/.cursor/rules/020-security-agentshield.mdc +7 -0
  116. package/.cursor/rules/030-autoresearch-loop.mdc +7 -0
  117. package/.cursor/settings.json +22 -0
  118. package/.cursor/skills/clearthought/SKILL.md +100 -0
  119. package/.cursor/skills/clearthought/examples/decision-framework.md +441 -0
  120. package/.cursor/skills/clearthought/examples/metagame-examples.md +536 -0
  121. package/.cursor/skills/clearthought/examples/sequential-thinking.md +380 -0
  122. package/.cursor/skills/clearthought/references/output-schemas.md +494 -0
  123. package/.cursor/skills/clearthought/references/parameter-reference.md +482 -0
  124. package/.cursor/skills/coding-level/SKILL.md +27 -0
  125. package/.cursor/skills/coding-level/references/level-0.md +131 -0
  126. package/.cursor/skills/coding-level/references/level-1.md +118 -0
  127. package/.cursor/skills/coding-level/references/level-2.md +140 -0
  128. package/.cursor/skills/coding-level/references/level-3.md +142 -0
  129. package/.cursor/skills/coding-level/references/level-4.md +152 -0
  130. package/.cursor/skills/coding-level/references/level-5.md +84 -0
  131. package/.cursor/skills/memento/SKILL.md +36 -0
  132. package/.cursor/skills/reviewing-4p-priorities/SKILL.md +72 -0
  133. package/.cursor/skills/reviewing-4p-priorities/examples.md +298 -0
  134. package/.cursor/skills/sequential-thinking/SKILL.md +106 -0
  135. package/.cursor/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
  136. package/.cursor/skills/sequential-thinking/examples/branching-exploration.md +474 -0
  137. package/.cursor/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
  138. package/.cursor/skills/sequential-thinking/examples/revision-pattern.md +304 -0
  139. package/.cursor/skills/sequential-thinking/references/output-schema.md +483 -0
  140. package/.cursor/skills/sequential-thinking/references/parameters.md +463 -0
  141. package/.cursor/skills/sequential-thinking/references/patterns.md +666 -0
  142. package/.vbkit-commands/autoresearch-coding.md +11 -0
  143. package/.vbkit-commands/council.md +3 -0
  144. package/.vbkit-commands/daily-enhance.md +3 -0
  145. package/.vbkit-commands/init-vibe.md +3 -0
  146. package/.vbkit-commands/security-scan.md +3 -0
  147. package/.vbkit-commands/vibe-finalize.md +3 -0
  148. package/.vbkit-docs/AUTORESEARCH_LEDGER.md +11 -0
  149. package/.vbkit-docs/BACKBONE_REFERENCE.md +59 -0
  150. package/.vbkit-docs/INSTALL.md +120 -0
  151. package/.vbkit-docs/RESEARCH_NOTES.md +22 -0
  152. package/.vbkit-docs/SECURITY_MODEL.md +40 -0
  153. package/.vbkit-docs/TOOLING_GUIDE.md +22 -0
  154. package/.vbkit-docs/backbone.schema.json +110 -0
  155. package/.vbkit-docs/templates/CONTEXT_TEMPLATE.md +47 -0
  156. package/.vbkit-docs/templates/PRD_TEMPLATE.md +48 -0
  157. package/.vbkit-scripts/agentshield-probe.mjs +22 -0
  158. package/.vbkit-scripts/daily-enhance.mjs +62 -0
  159. package/.vbkit-scripts/doctor.mjs +323 -0
  160. package/.vbkit-scripts/init-backbone.mjs +628 -0
  161. package/.vbkit-scripts/mvck.mjs +444 -0
  162. package/.vbkit-scripts/pack-dry-run.mjs +16 -0
  163. package/.vbkit-scripts/test-install.mjs +118 -0
  164. package/.vbkit-scripts/validate-kit.mjs +419 -0
  165. package/.vbkit-scripts/vibekit-finalize.mjs +185 -0
  166. package/AGENTS.md +60 -0
  167. package/CHANGELOG.md +34 -0
  168. package/CLAUDE-template.md +52 -0
  169. package/CODE_OF_CONDUCT.md +18 -0
  170. package/CONTRIBUTING.md +28 -0
  171. package/FIRST_PROMPT.md +54 -0
  172. package/FIRST_TIME_INIT.md +165 -0
  173. package/LICENSE +21 -0
  174. package/README.md +493 -0
  175. package/README.vi.md +431 -0
  176. package/SECURITY.md +28 -0
  177. package/backbone.yml +103 -0
  178. package/bin/mvck.js +2 -0
  179. package/bin/vibe-kit.js +2 -0
  180. package/install.ps1 +14 -0
  181. package/install.sh +8 -0
  182. package/package.json +92 -0
  183. package/skills/agentshield-security-review/SKILL.md +75 -0
  184. package/skills/agentshield-security-review/agents/openai.yaml +5 -0
  185. package/skills/agentshield-security-review/references/native-install.md +97 -0
  186. package/skills/agentshield-security-review/references/report-template.md +54 -0
  187. package/skills/agentshield-security-review/references/review-checklist.md +45 -0
  188. package/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +110 -0
  189. package/skills/autoresearch-coding/README.md +15 -0
  190. package/skills/autoresearch-coding/SKILL.md +101 -0
  191. package/skills/autoresearch-coding/references/experiment-contract.md +13 -0
  192. package/skills/autoresearch-coding/references/metric-extraction.md +8 -0
  193. package/skills/autoresearch-coding/references/result-ledger.md +15 -0
  194. package/skills/autoresearch-coding/scripts/log_result.py +24 -0
  195. package/skills/autoresearch-coding/scripts/run_logged.py +30 -0
  196. package/skills/clearthought/SKILL.md +100 -0
  197. package/skills/clearthought/examples/decision-framework.md +441 -0
  198. package/skills/clearthought/examples/metagame-examples.md +536 -0
  199. package/skills/clearthought/examples/sequential-thinking.md +380 -0
  200. package/skills/clearthought/references/output-schemas.md +494 -0
  201. package/skills/clearthought/references/parameter-reference.md +482 -0
  202. package/skills/coding-level/SKILL.md +27 -0
  203. package/skills/coding-level/references/level-0.md +131 -0
  204. package/skills/coding-level/references/level-1.md +118 -0
  205. package/skills/coding-level/references/level-2.md +140 -0
  206. package/skills/coding-level/references/level-3.md +142 -0
  207. package/skills/coding-level/references/level-4.md +152 -0
  208. package/skills/coding-level/references/level-5.md +84 -0
  209. package/skills/daily-workflow-curator/SKILL.md +52 -0
  210. package/skills/memento/SKILL.md +36 -0
  211. package/skills/reviewing-4p-priorities/SKILL.md +72 -0
  212. package/skills/reviewing-4p-priorities/examples.md +298 -0
  213. package/skills/sequential-thinking/SKILL.md +106 -0
  214. package/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
  215. package/skills/sequential-thinking/examples/branching-exploration.md +474 -0
  216. package/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
  217. package/skills/sequential-thinking/examples/revision-pattern.md +304 -0
  218. package/skills/sequential-thinking/references/output-schema.md +483 -0
  219. package/skills/sequential-thinking/references/parameters.md +463 -0
  220. package/skills/sequential-thinking/references/patterns.md +666 -0
  221. package/skills/vibekit-init/SKILL.md +52 -0
  222. package/skills/visual-design-loop/SKILL.md +103 -0
  223. package/skills/visual-design-loop/agents/openai.yaml +4 -0
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const kitRoot = path.resolve(__dirname, '..');
10
+ const args = process.argv.slice(2);
11
+
12
+ function usage() {
13
+ console.log(`Minimal Vibe Coding Kit
14
+
15
+ Usage:
16
+ mvck install [target] [--profile all|claude,cursor,codex] [--force] [--dry-run] [--json]
17
+ mvck update [target] [--profile all|claude,cursor,codex] [--dry-run] [--json] [--no-backup]
18
+ mvck init [target] [--propose|--write --yes] [--preset nextjs|wordpress|python|laravel|docker]
19
+ mvck validate [target]
20
+ mvck doctor [target] [--write-report] [--json]
21
+ mvck daily [target] [--write-report]
22
+ mvck finalize [target] [--write --yes] [--restore] [--json]
23
+
24
+ Examples:
25
+ node .vbkit-scripts/mvck.mjs install ~/work/my-repo --profile all
26
+ npx --yes minimal-vibe-coding-kit@latest update .
27
+ node .vbkit-scripts/mvck.mjs init . --propose
28
+ node .vbkit-scripts/mvck.mjs doctor .
29
+ node .vbkit-scripts/mvck.mjs validate .
30
+ node .vbkit-scripts/mvck.mjs finalize .
31
+
32
+ update refreshes kit-owned files (skills, commands, rules, scripts, docs, agent
33
+ mirrors) to the running kit version. It never overwrites user-owned files
34
+ (backbone.yml, CLAUDE.md, AGENTS.md content, settings.json) and backs up any
35
+ changed kit file under .vibekit/update-backup/ before replacing it.
36
+ `);
37
+ }
38
+
39
+ function hasFlag(flag) {
40
+ return args.includes(flag);
41
+ }
42
+
43
+ function optionValue(name, fallback = null) {
44
+ const idx = args.indexOf(name);
45
+ if (idx >= 0 && args[idx + 1] && !args[idx + 1].startsWith('--')) return args[idx + 1];
46
+ const prefix = `${name}=`;
47
+ const match = args.find((item) => item.startsWith(prefix));
48
+ return match ? match.slice(prefix.length) : fallback;
49
+ }
50
+
51
+ function parseTargetAndFlags(command, { valueFlags = [] } = {}) {
52
+ const values = new Set(valueFlags);
53
+ const flags = [];
54
+ const positionals = [];
55
+ const rest = args[0] === command ? args.slice(1) : args.slice();
56
+
57
+ for (let i = 0; i < rest.length; i += 1) {
58
+ const item = rest[i];
59
+ if (item === '--') {
60
+ positionals.push(...rest.slice(i + 1));
61
+ break;
62
+ }
63
+ if (!item.startsWith('--')) {
64
+ positionals.push(item);
65
+ continue;
66
+ }
67
+
68
+ flags.push(item);
69
+ if (!item.includes('=') && values.has(item) && rest[i + 1] && !rest[i + 1].startsWith('--')) {
70
+ flags.push(rest[i + 1]);
71
+ i += 1;
72
+ }
73
+ }
74
+
75
+ return {
76
+ target: path.resolve(positionals[0] || process.cwd()),
77
+ flags,
78
+ positionals
79
+ };
80
+ }
81
+
82
+ function ensureDir(dir, dryRun) {
83
+ if (dryRun) return;
84
+ fs.mkdirSync(dir, { recursive: true });
85
+ }
86
+
87
+ function copyFileSafe(srcRel, destRel, target, { force = false, dryRun = false } = {}) {
88
+ const src = path.join(kitRoot, srcRel);
89
+ const dest = path.join(target, destRel ?? srcRel);
90
+ if (!fs.existsSync(src)) throw new Error(`Missing source file: ${srcRel}`);
91
+ if (fs.existsSync(dest) && !force) return { action: 'skip', path: destRel ?? srcRel };
92
+ if (!dryRun) {
93
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
94
+ fs.copyFileSync(src, dest);
95
+ const mode = fs.statSync(src).mode;
96
+ fs.chmodSync(dest, mode);
97
+ }
98
+ return { action: fs.existsSync(dest) && force ? 'replace' : 'copy', path: destRel ?? srcRel };
99
+ }
100
+
101
+ function copyDirSafe(srcRel, destRel, target, { force = false, dryRun = false } = {}) {
102
+ const src = path.join(kitRoot, srcRel);
103
+ const destRoot = path.join(target, destRel ?? srcRel);
104
+ if (!fs.existsSync(src)) throw new Error(`Missing source dir: ${srcRel}`);
105
+ let copied = 0;
106
+ let skipped = 0;
107
+ function copyRecursive(from, to) {
108
+ const stat = fs.statSync(from);
109
+ if (stat.isDirectory()) {
110
+ if (!dryRun) fs.mkdirSync(to, { recursive: true });
111
+ for (const entry of fs.readdirSync(from)) copyRecursive(path.join(from, entry), path.join(to, entry));
112
+ return;
113
+ }
114
+ if (fs.existsSync(to) && !force) { skipped += 1; return; }
115
+ copied += 1;
116
+ if (!dryRun) {
117
+ fs.mkdirSync(path.dirname(to), { recursive: true });
118
+ fs.copyFileSync(from, to);
119
+ fs.chmodSync(to, fs.statSync(from).mode);
120
+ }
121
+ }
122
+ copyRecursive(src, destRoot);
123
+ const action = force ? 'merge-replace' : copied > 0 && skipped > 0 ? 'merge-copy-missing' : copied > 0 ? 'copy-dir' : 'skip-dir';
124
+ return { action: `${action} (${copied} copied, ${skipped} skipped)`, path: destRel ?? srcRel };
125
+ }
126
+
127
+ function appendManagedBlock(file, block, begin, end, { dryRun = false } = {}) {
128
+ let current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
129
+ const start = current.indexOf(begin);
130
+ const finish = current.indexOf(end);
131
+ let next;
132
+ if (start >= 0 && finish > start) {
133
+ next = current.slice(0, start).trimEnd() + '\n\n' + block.trim() + '\n' + current.slice(finish + end.length);
134
+ } else if (current.trim()) {
135
+ next = current.trimEnd() + '\n\n' + block.trim() + '\n';
136
+ } else {
137
+ next = block.trim() + '\n';
138
+ }
139
+ if (!dryRun) {
140
+ fs.mkdirSync(path.dirname(file), { recursive: true });
141
+ fs.writeFileSync(file, next);
142
+ }
143
+ }
144
+
145
+ function managedBlockFromTemplate(block, begin, end) {
146
+ const start = block.indexOf(begin);
147
+ const finish = block.indexOf(end);
148
+ if (start >= 0 && finish > start) return block.slice(start, finish + end.length);
149
+ return `${begin}\n${block.trim()}\n${end}`;
150
+ }
151
+
152
+ const KIT_SEED_FILES = ['FIRST_TIME_INIT.md', 'FIRST_PROMPT.md', 'CLAUDE-template.md', 'backbone.yml'];
153
+ const KIT_SHARED_DIRS = ['skills', '.vbkit-commands', '.vbkit-docs'];
154
+ const KIT_SCRIPTS = [
155
+ '.vbkit-scripts/mvck.mjs', '.vbkit-scripts/init-backbone.mjs', '.vbkit-scripts/daily-enhance.mjs', '.vbkit-scripts/validate-kit.mjs',
156
+ '.vbkit-scripts/doctor.mjs', '.vbkit-scripts/test-install.mjs', '.vbkit-scripts/agentshield-probe.mjs', '.vbkit-scripts/pack-dry-run.mjs', '.vbkit-scripts/vibekit-finalize.mjs'
157
+ ];
158
+ const CLAUDE_DIRS = ['.claude/agents', '.claude/commands', '.claude/rules'];
159
+ const CLAUDE_SKILLS = [
160
+ 'autoresearch-coding', 'agentshield-security-review', 'daily-workflow-curator', 'vibekit-init', 'visual-design-loop',
161
+ 'clearthought', 'sequential-thinking', 'reviewing-4p-priorities', 'memento', 'coding-level'
162
+ ];
163
+ const CURSOR_DIRS = ['.cursor/rules', '.cursor/commands'];
164
+ const CURSOR_SKILLS = ['clearthought', 'sequential-thinking', 'reviewing-4p-priorities', 'memento', 'coding-level'];
165
+ const CODEX_DIRS = ['.agents', '.codex', '.codex-plugin'];
166
+ const GITIGNORE_BLOCK = `# BEGIN: minimal-vibe-coding-kit\n.autoresearch/\nresults.tsv\n.vibekit/INIT_DONE\n.vibekit/FINALIZE_DONE\n.vibekit/reports/\n.vibekit/update-backup/\n_vibekit-cleanup/\nCLAUDE.local.md\n# END: minimal-vibe-coding-kit`;
167
+
168
+ function kitVersion() {
169
+ try {
170
+ return JSON.parse(fs.readFileSync(path.join(kitRoot, 'package.json'), 'utf8')).version || 'unknown';
171
+ } catch {
172
+ return 'unknown';
173
+ }
174
+ }
175
+
176
+ function writeKitVersion(target, dryRun) {
177
+ if (dryRun) return;
178
+ const markerDir = path.join(target, '.vibekit');
179
+ fs.mkdirSync(markerDir, { recursive: true });
180
+ fs.writeFileSync(path.join(markerDir, 'KIT_VERSION'), `${kitVersion()}\n`);
181
+ }
182
+
183
+ function applyManagedBlocks(target, profiles, actions, { dryRun = false } = {}) {
184
+ appendManagedBlock(path.join(target, '.gitignore'), GITIGNORE_BLOCK, '# BEGIN: minimal-vibe-coding-kit', '# END: minimal-vibe-coding-kit', { dryRun });
185
+ actions.push({ action: 'managed-block', path: '.gitignore' });
186
+
187
+ if (profiles.has('claude')) {
188
+ const claudeTarget = path.join(target, 'CLAUDE.md');
189
+ if (!fs.existsSync(claudeTarget)) {
190
+ actions.push(copyFileSafe('CLAUDE-template.md', 'CLAUDE.md', target, { force: false, dryRun }));
191
+ } else {
192
+ const block = `<!-- BEGIN: minimal-vibe-coding-kit -->\n@AGENTS.md\n\n## Minimal Vibe Coding Kit\n\n- Read \`backbone.yml\` before changing code.\n- If \`meta.template_status\` is \`uninitialized\`, follow \`FIRST_TIME_INIT.md\` and wait for approval before writing.\n- After init, follow \`backbone.yml\` \`conventions\` before adding new project patterns.\n- Prefer project skills for multi-step workflows: \`/autoresearch-coding\`, \`/security-scan\`, \`/daily-enhance\`.\n<!-- END: minimal-vibe-coding-kit -->`;
193
+ appendManagedBlock(claudeTarget, block, '<!-- BEGIN: minimal-vibe-coding-kit -->', '<!-- END: minimal-vibe-coding-kit -->', { dryRun });
194
+ actions.push({ action: 'managed-block', path: 'CLAUDE.md' });
195
+ }
196
+ }
197
+
198
+ const agentsTarget = path.join(target, 'AGENTS.md');
199
+ if (!fs.existsSync(agentsTarget)) {
200
+ actions.push(copyFileSafe('AGENTS.md', 'AGENTS.md', target, { force: false, dryRun }));
201
+ } else {
202
+ const block = fs.readFileSync(path.join(kitRoot, 'AGENTS.md'), 'utf8');
203
+ const begin = '<!-- BEGIN: minimal-vibe-coding-kit -->';
204
+ const end = '<!-- END: minimal-vibe-coding-kit -->';
205
+ const managed = managedBlockFromTemplate(block, begin, end);
206
+ appendManagedBlock(agentsTarget, managed, begin, end, { dryRun });
207
+ actions.push({ action: 'managed-block', path: 'AGENTS.md' });
208
+ }
209
+ }
210
+
211
+ function install() {
212
+ const force = hasFlag('--force');
213
+ const dryRun = hasFlag('--dry-run');
214
+ const json = hasFlag('--json');
215
+ const target = parseTargetAndFlags('install', { valueFlags: ['--profile'] }).target;
216
+ const profileRaw = optionValue('--profile', 'all');
217
+ const profiles = new Set(profileRaw === 'all' ? ['claude', 'cursor', 'codex'] : profileRaw.split(',').map((x) => x.trim()).filter(Boolean));
218
+
219
+ if (!fs.existsSync(target)) throw new Error(`Target does not exist: ${target}`);
220
+ const actions = [];
221
+ const opts = { force, dryRun };
222
+
223
+ for (const file of KIT_SEED_FILES) {
224
+ actions.push(copyFileSafe(file, file, target, opts));
225
+ }
226
+ for (const dir of KIT_SHARED_DIRS) {
227
+ actions.push(copyDirSafe(dir, dir, target, opts));
228
+ }
229
+ for (const file of KIT_SCRIPTS) {
230
+ actions.push(copyFileSafe(file, file, target, opts));
231
+ }
232
+
233
+ if (profiles.has('claude')) {
234
+ for (const dir of CLAUDE_DIRS) {
235
+ actions.push(copyDirSafe(dir, dir, target, opts));
236
+ }
237
+ for (const skill of CLAUDE_SKILLS) {
238
+ actions.push(copyDirSafe(`.claude/skills/${skill}`, `.claude/skills/${skill}`, target, opts));
239
+ }
240
+ actions.push(copyFileSafe('.claude/settings.json', '.claude/settings.json', target, opts));
241
+ }
242
+ if (profiles.has('cursor')) {
243
+ for (const dir of CURSOR_DIRS) {
244
+ actions.push(copyDirSafe(dir, dir, target, opts));
245
+ }
246
+ for (const skill of CURSOR_SKILLS) {
247
+ actions.push(copyDirSafe(`.cursor/skills/${skill}`, `.cursor/skills/${skill}`, target, opts));
248
+ }
249
+ actions.push(copyFileSafe('.cursor/settings.json', '.cursor/settings.json', target, opts));
250
+ }
251
+ if (profiles.has('codex')) {
252
+ for (const dir of CODEX_DIRS) {
253
+ actions.push(copyDirSafe(dir, dir, target, opts));
254
+ }
255
+ }
256
+
257
+ applyManagedBlocks(target, profiles, actions, { dryRun });
258
+ writeKitVersion(target, dryRun);
259
+ actions.push({ action: 'version-marker', path: '.vibekit/KIT_VERSION' });
260
+
261
+ const nextPrompt = 'Read FIRST_TIME_INIT.md and initialize this repo with Minimal Vibe Coding Kit. Print requirements first, infer project conventions, propose a diff, and wait for my yes before writing.';
262
+ if (json) {
263
+ console.log(JSON.stringify({
264
+ status: dryRun ? 'dry-run' : 'installed',
265
+ target,
266
+ profiles: [...profiles].sort(),
267
+ force,
268
+ dryRun,
269
+ actions,
270
+ nextPrompt
271
+ }, null, 2));
272
+ return;
273
+ }
274
+
275
+ console.log(dryRun ? 'Dry-run install plan:' : 'Install complete:');
276
+ for (const a of actions) console.log(`- ${a.action}: ${a.path}`);
277
+ console.log('\nNext prompt:');
278
+ console.log(nextPrompt);
279
+ }
280
+
281
+ function updateFileSafe(srcRel, destRel, target, { dryRun = false, backup = null } = {}) {
282
+ const src = path.join(kitRoot, srcRel);
283
+ const dest = path.join(target, destRel ?? srcRel);
284
+ if (!fs.existsSync(src)) throw new Error(`Missing source file: ${srcRel}`);
285
+
286
+ if (!fs.existsSync(dest)) {
287
+ if (!dryRun) {
288
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
289
+ fs.copyFileSync(src, dest);
290
+ fs.chmodSync(dest, fs.statSync(src).mode);
291
+ }
292
+ return { action: 'add', path: destRel ?? srcRel };
293
+ }
294
+
295
+ if (fs.readFileSync(src).equals(fs.readFileSync(dest))) {
296
+ return { action: 'unchanged', path: destRel ?? srcRel };
297
+ }
298
+
299
+ if (!dryRun) {
300
+ if (backup) {
301
+ const backupPath = path.join(backup.dir, destRel ?? srcRel);
302
+ fs.mkdirSync(path.dirname(backupPath), { recursive: true });
303
+ fs.copyFileSync(dest, backupPath);
304
+ backup.count += 1;
305
+ }
306
+ fs.copyFileSync(src, dest);
307
+ fs.chmodSync(dest, fs.statSync(src).mode);
308
+ }
309
+ return { action: 'update', path: destRel ?? srcRel };
310
+ }
311
+
312
+ function updateDirSafe(srcRel, target, opts) {
313
+ const src = path.join(kitRoot, srcRel);
314
+ if (!fs.existsSync(src)) throw new Error(`Missing source dir: ${srcRel}`);
315
+ const results = [];
316
+ function walk(current) {
317
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
318
+ const child = path.join(current, entry.name);
319
+ if (entry.isDirectory()) walk(child);
320
+ else {
321
+ const rel = path.join(srcRel, path.relative(src, child)).replaceAll(path.sep, '/');
322
+ results.push(updateFileSafe(rel, rel, target, opts));
323
+ }
324
+ }
325
+ }
326
+ walk(src);
327
+ return results;
328
+ }
329
+
330
+ function update() {
331
+ const dryRun = hasFlag('--dry-run');
332
+ const json = hasFlag('--json');
333
+ const noBackup = hasFlag('--no-backup');
334
+ const target = parseTargetAndFlags('update', { valueFlags: ['--profile'] }).target;
335
+ const profileRaw = optionValue('--profile', 'all');
336
+ const profiles = new Set(profileRaw === 'all' ? ['claude', 'cursor', 'codex'] : profileRaw.split(',').map((x) => x.trim()).filter(Boolean));
337
+
338
+ if (!fs.existsSync(target)) throw new Error(`Target does not exist: ${target}`);
339
+ if (fs.realpathSync(kitRoot) === fs.realpathSync(target)) {
340
+ throw new Error('Update source and target are the same directory. Run the update from a newer kit, e.g.: npx --yes minimal-vibe-coding-kit@latest update .');
341
+ }
342
+ if (!fs.existsSync(path.join(target, 'backbone.yml')) && !fs.existsSync(path.join(target, '.vbkit-scripts'))) {
343
+ throw new Error('Kit not detected in target (no backbone.yml or .vbkit-scripts). Run mvck install first.');
344
+ }
345
+
346
+ const previousVersionFile = path.join(target, '.vibekit', 'KIT_VERSION');
347
+ const previousVersion = fs.existsSync(previousVersionFile) ? fs.readFileSync(previousVersionFile, 'utf8').trim() : 'unknown';
348
+ const finalized = fs.existsSync(path.join(target, '.vibekit', 'FINALIZE_DONE'));
349
+ const stamp = new Date().toISOString().replaceAll(':', '-').slice(0, 19);
350
+ const backup = noBackup || dryRun ? null : { dir: path.join(target, '.vibekit', 'update-backup', stamp), count: 0 };
351
+ const opts = { dryRun, backup };
352
+ const actions = [];
353
+
354
+ // Kit-owned surfaces: refresh to the running kit version, never delete extras.
355
+ for (const dir of KIT_SHARED_DIRS) actions.push(...updateDirSafe(dir, target, opts));
356
+ for (const file of KIT_SCRIPTS) actions.push(updateFileSafe(file, file, target, opts));
357
+ if (profiles.has('claude')) {
358
+ for (const dir of CLAUDE_DIRS) actions.push(...updateDirSafe(dir, target, opts));
359
+ for (const skill of CLAUDE_SKILLS) actions.push(...updateDirSafe(`.claude/skills/${skill}`, target, opts));
360
+ }
361
+ if (profiles.has('cursor')) {
362
+ for (const dir of CURSOR_DIRS) actions.push(...updateDirSafe(dir, target, opts));
363
+ for (const skill of CURSOR_SKILLS) actions.push(...updateDirSafe(`.cursor/skills/${skill}`, target, opts));
364
+ }
365
+ if (profiles.has('codex')) {
366
+ for (const dir of CODEX_DIRS) actions.push(...updateDirSafe(dir, target, opts));
367
+ }
368
+
369
+ // User-owned files: seed only when missing, never overwrite. Finalized
370
+ // projects removed the one-time files on purpose, so skip re-seeding them.
371
+ const seedFiles = finalized ? ['backbone.yml'] : KIT_SEED_FILES;
372
+ for (const file of seedFiles) actions.push(copyFileSafe(file, file, target, { force: false, dryRun }));
373
+ if (profiles.has('claude')) actions.push(copyFileSafe('.claude/settings.json', '.claude/settings.json', target, { force: false, dryRun }));
374
+ if (profiles.has('cursor')) actions.push(copyFileSafe('.cursor/settings.json', '.cursor/settings.json', target, { force: false, dryRun }));
375
+
376
+ applyManagedBlocks(target, profiles, actions, { dryRun });
377
+ writeKitVersion(target, dryRun);
378
+
379
+ const summary = { add: 0, update: 0, unchanged: 0, skip: 0, 'managed-block': 0 };
380
+ for (const a of actions) summary[a.action] = (summary[a.action] || 0) + 1;
381
+ const backupInfo = backup && backup.count > 0 ? path.relative(target, backup.dir) : null;
382
+ const preserved = ['backbone.yml', 'CLAUDE.md', 'AGENTS.md content outside the managed block', '.claude/settings.json', '.cursor/settings.json'];
383
+
384
+ if (json) {
385
+ console.log(JSON.stringify({
386
+ status: dryRun ? 'dry-run' : 'updated',
387
+ target,
388
+ profiles: [...profiles].sort(),
389
+ fromVersion: previousVersion,
390
+ toVersion: kitVersion(),
391
+ summary,
392
+ backupDir: backupInfo,
393
+ preserved,
394
+ actions: actions.filter((a) => a.action !== 'unchanged')
395
+ }, null, 2));
396
+ return;
397
+ }
398
+
399
+ console.log(dryRun ? 'Dry-run update plan:' : 'Update complete:');
400
+ console.log(`- kit version: ${previousVersion} -> ${kitVersion()}`);
401
+ for (const a of actions) {
402
+ if (a.action === 'unchanged') continue;
403
+ console.log(`- ${a.action}: ${a.path}`);
404
+ }
405
+ console.log(`\nSummary: ${summary.add} added, ${summary.update} updated, ${summary.unchanged} unchanged.`);
406
+ if (backupInfo) console.log(`Backups of replaced files: ${backupInfo}/`);
407
+ console.log(`Preserved (never overwritten by update): ${preserved.join(', ')}.`);
408
+ }
409
+
410
+ function delegate(scriptName, { valueFlags = [] } = {}) {
411
+ const { target, flags } = parseTargetAndFlags(args[0], { valueFlags });
412
+ const script = path.join(kitRoot, '.vbkit-scripts', scriptName);
413
+ const childArgs = [script, target, ...flags];
414
+ const result = spawnSync(process.execPath, childArgs, { stdio: 'inherit' });
415
+ process.exit(result.status ?? 1);
416
+ }
417
+
418
+ try {
419
+ const command = args[0];
420
+ if (!command || command === '--help' || command === '-h') {
421
+ usage();
422
+ } else if (command === 'install') {
423
+ install();
424
+ } else if (command === 'update') {
425
+ update();
426
+ } else if (command === 'init') {
427
+ delegate('init-backbone.mjs', { valueFlags: ['--preset'] });
428
+ } else if (command === 'validate') {
429
+ delegate('validate-kit.mjs');
430
+ } else if (command === 'doctor') {
431
+ delegate('doctor.mjs');
432
+ } else if (command === 'daily') {
433
+ delegate('daily-enhance.mjs');
434
+ } else if (command === 'finalize') {
435
+ delegate('vibekit-finalize.mjs');
436
+ } else {
437
+ console.error(`Unknown command: ${command}`);
438
+ usage();
439
+ process.exit(1);
440
+ }
441
+ } catch (error) {
442
+ console.error(`Error: ${error.message}`);
443
+ process.exit(1);
444
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
7
+ const cache = process.env.MVCK_NPM_CACHE || path.join(os.tmpdir(), 'mvck-npm-cache');
8
+ const result = spawnSync(npm, ['pack', '--dry-run'], {
9
+ stdio: 'inherit',
10
+ env: {
11
+ ...process.env,
12
+ npm_config_cache: cache
13
+ }
14
+ });
15
+
16
+ process.exit(result.status ?? 1);
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const kitRoot = path.resolve(path.dirname(__filename), '..');
10
+ const node = process.execPath;
11
+ const keep = process.argv.includes('--keep');
12
+ const temps = [];
13
+
14
+ function tempDir(label) {
15
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), `mvck-${label}-`));
16
+ temps.push(dir);
17
+ return dir;
18
+ }
19
+
20
+ function run(args, { cwd = kitRoot, expect = 0 } = {}) {
21
+ const result = spawnSync(node, args, { cwd, encoding: 'utf8' });
22
+ if (result.status !== expect) {
23
+ console.error(`Command failed: ${node} ${args.join(' ')}`);
24
+ console.error(`cwd: ${cwd}`);
25
+ console.error(`exit: ${result.status}`);
26
+ if (result.stdout) console.error(result.stdout);
27
+ if (result.stderr) console.error(result.stderr);
28
+ process.exit(1);
29
+ }
30
+ return result;
31
+ }
32
+
33
+ function assert(condition, message) {
34
+ if (!condition) {
35
+ console.error(`FAIL ${message}`);
36
+ process.exit(1);
37
+ }
38
+ console.log(`PASS ${message}`);
39
+ }
40
+
41
+ function count(text, marker) {
42
+ return (text.match(new RegExp(marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
43
+ }
44
+
45
+ try {
46
+ const clean = tempDir('clean');
47
+ run(['.vbkit-scripts/mvck.mjs', 'install', clean, '--profile', 'all']);
48
+ run(['.vbkit-scripts/validate-kit.mjs', clean]);
49
+ assert(fs.existsSync(path.join(clean, 'AGENTS.md')), 'clean install creates AGENTS.md');
50
+ assert(fs.existsSync(path.join(clean, '.vbkit-commands')), 'clean install creates .vbkit-commands');
51
+ assert(fs.existsSync(path.join(clean, '.vbkit-scripts')), 'clean install creates .vbkit-scripts');
52
+ assert(fs.existsSync(path.join(clean, '.vbkit-docs')), 'clean install creates .vbkit-docs');
53
+ assert(!fs.existsSync(path.join(clean, 'commands')), 'clean install does not create root commands');
54
+ assert(!fs.existsSync(path.join(clean, 'scripts')), 'clean install does not create root scripts');
55
+ assert(!fs.existsSync(path.join(clean, 'docs')), 'clean install does not create root docs');
56
+
57
+ const existing = tempDir('existing');
58
+ fs.writeFileSync(path.join(existing, 'AGENTS.md'), '# Existing\n');
59
+ fs.writeFileSync(path.join(existing, 'CLAUDE.md'), '# Existing Claude\n');
60
+ run(['.vbkit-scripts/mvck.mjs', 'install', existing, '--profile', 'all']);
61
+ run(['.vbkit-scripts/mvck.mjs', 'install', existing, '--profile', 'all']);
62
+
63
+ const agents = fs.readFileSync(path.join(existing, 'AGENTS.md'), 'utf8');
64
+ const claude = fs.readFileSync(path.join(existing, 'CLAUDE.md'), 'utf8');
65
+ assert(agents.includes('# Existing'), 'AGENTS.md preserves existing content');
66
+ assert(claude.includes('# Existing Claude'), 'CLAUDE.md preserves existing content');
67
+ assert(count(agents, 'BEGIN: minimal-vibe-coding-kit') === 1, 'AGENTS.md has one managed begin marker');
68
+ assert(count(agents, 'END: minimal-vibe-coding-kit') === 1, 'AGENTS.md has one managed end marker');
69
+ assert(count(claude, 'BEGIN: minimal-vibe-coding-kit') === 1, 'CLAUDE.md has one managed begin marker');
70
+ assert(count(claude, 'END: minimal-vibe-coding-kit') === 1, 'CLAUDE.md has one managed end marker');
71
+
72
+ const cwdTarget = tempDir('cwd-target');
73
+ run([path.join(kitRoot, '.vbkit-scripts/mvck.mjs'), 'install', '--profile', 'all'], { cwd: cwdTarget });
74
+ assert(fs.existsSync(path.join(cwdTarget, 'backbone.yml')), 'install without target uses current working directory');
75
+
76
+ const proposed = run([path.join(kitRoot, '.vbkit-scripts/mvck.mjs'), 'init', '--propose'], { cwd: cwdTarget });
77
+ assert(proposed.stdout.includes('Proposed backbone.yml'), 'init --propose without target preserves flag');
78
+
79
+ const jsonPlan = run(['.vbkit-scripts/mvck.mjs', 'install', clean, '--dry-run', '--json']);
80
+ const parsed = JSON.parse(jsonPlan.stdout);
81
+ assert(parsed.status === 'dry-run' && parsed.dryRun === true, 'install --dry-run --json returns machine-readable plan');
82
+
83
+ const upd = tempDir('update');
84
+ run(['.vbkit-scripts/mvck.mjs', 'install', upd, '--profile', 'all']);
85
+ assert(fs.existsSync(path.join(upd, '.vibekit/KIT_VERSION')), 'install stamps .vibekit/KIT_VERSION');
86
+ fs.appendFileSync(path.join(upd, 'backbone.yml'), '# user-custom-line\n');
87
+ fs.writeFileSync(path.join(upd, 'skills/memento/SKILL.md'), '# stale kit file\n');
88
+ fs.rmSync(path.join(upd, '.claude/skills/coding-level'), { recursive: true, force: true });
89
+
90
+ run(['.vbkit-scripts/mvck.mjs', 'update', upd]);
91
+ assert(fs.readFileSync(path.join(upd, 'skills/memento/SKILL.md'), 'utf8').includes('name: memento'), 'update refreshes stale kit files');
92
+ assert(fs.existsSync(path.join(upd, '.claude/skills/coding-level/SKILL.md')), 'update re-adds missing kit skill mirrors');
93
+ assert(fs.readFileSync(path.join(upd, 'backbone.yml'), 'utf8').includes('# user-custom-line'), 'update preserves user-modified backbone.yml');
94
+ const backupRoot = path.join(upd, '.vibekit', 'update-backup');
95
+ assert(fs.existsSync(backupRoot) && fs.readdirSync(backupRoot).length >= 1, 'update backs up replaced kit files');
96
+ run(['.vbkit-scripts/validate-kit.mjs', upd]);
97
+
98
+ const updAgents = fs.readFileSync(path.join(upd, 'AGENTS.md'), 'utf8');
99
+ assert(count(updAgents, 'BEGIN: minimal-vibe-coding-kit') === 1, 'update keeps one managed begin marker in AGENTS.md');
100
+
101
+ const emptyTarget = tempDir('update-empty');
102
+ run(['.vbkit-scripts/mvck.mjs', 'update', emptyTarget], { expect: 1 });
103
+ assert(true, 'update refuses a target without the kit installed');
104
+
105
+ const updPlan = run(['.vbkit-scripts/mvck.mjs', 'update', upd, '--dry-run', '--json']);
106
+ const updParsed = JSON.parse(updPlan.stdout);
107
+ assert(updParsed.status === 'dry-run' && typeof updParsed.toVersion === 'string', 'update --dry-run --json returns machine-readable plan');
108
+
109
+ console.log('\nInstall behavior tests passed.');
110
+ } finally {
111
+ if (!keep) {
112
+ for (const dir of temps) {
113
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
114
+ }
115
+ } else {
116
+ console.log(`Kept temp dirs:\n${temps.join('\n')}`);
117
+ }
118
+ }