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.
- package/.agents/skills/agentshield-security-review/SKILL.md +75 -0
- package/.agents/skills/agentshield-security-review/agents/openai.yaml +5 -0
- package/.agents/skills/agentshield-security-review/references/native-install.md +97 -0
- package/.agents/skills/agentshield-security-review/references/report-template.md +54 -0
- package/.agents/skills/agentshield-security-review/references/review-checklist.md +45 -0
- package/.agents/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +110 -0
- package/.agents/skills/autoresearch-coding/README.md +15 -0
- package/.agents/skills/autoresearch-coding/SKILL.md +101 -0
- package/.agents/skills/autoresearch-coding/references/experiment-contract.md +13 -0
- package/.agents/skills/autoresearch-coding/references/metric-extraction.md +8 -0
- package/.agents/skills/autoresearch-coding/references/result-ledger.md +15 -0
- package/.agents/skills/autoresearch-coding/scripts/log_result.py +24 -0
- package/.agents/skills/autoresearch-coding/scripts/run_logged.py +30 -0
- package/.agents/skills/clearthought/SKILL.md +100 -0
- package/.agents/skills/clearthought/examples/decision-framework.md +441 -0
- package/.agents/skills/clearthought/examples/metagame-examples.md +536 -0
- package/.agents/skills/clearthought/examples/sequential-thinking.md +380 -0
- package/.agents/skills/clearthought/references/output-schemas.md +494 -0
- package/.agents/skills/clearthought/references/parameter-reference.md +482 -0
- package/.agents/skills/coding-level/SKILL.md +27 -0
- package/.agents/skills/coding-level/references/level-0.md +131 -0
- package/.agents/skills/coding-level/references/level-1.md +118 -0
- package/.agents/skills/coding-level/references/level-2.md +140 -0
- package/.agents/skills/coding-level/references/level-3.md +142 -0
- package/.agents/skills/coding-level/references/level-4.md +152 -0
- package/.agents/skills/coding-level/references/level-5.md +84 -0
- package/.agents/skills/daily-workflow-curator/SKILL.md +52 -0
- package/.agents/skills/memento/SKILL.md +36 -0
- package/.agents/skills/reviewing-4p-priorities/SKILL.md +72 -0
- package/.agents/skills/reviewing-4p-priorities/examples.md +298 -0
- package/.agents/skills/sequential-thinking/SKILL.md +106 -0
- package/.agents/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
- package/.agents/skills/sequential-thinking/examples/branching-exploration.md +474 -0
- package/.agents/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
- package/.agents/skills/sequential-thinking/examples/revision-pattern.md +304 -0
- package/.agents/skills/sequential-thinking/references/output-schema.md +483 -0
- package/.agents/skills/sequential-thinking/references/parameters.md +463 -0
- package/.agents/skills/sequential-thinking/references/patterns.md +666 -0
- package/.agents/skills/vibekit-init/SKILL.md +52 -0
- package/.agents/skills/visual-design-loop/SKILL.md +103 -0
- package/.agents/skills/visual-design-loop/agents/openai.yaml +4 -0
- package/.claude/agents/code-reviewer.md +11 -0
- package/.claude/agents/context-architect.md +11 -0
- package/.claude/agents/debug-fixer.md +11 -0
- package/.claude/agents/hypothesis-planner.md +11 -0
- package/.claude/agents/implementation-hacker.md +11 -0
- package/.claude/agents/research-coordinator.md +11 -0
- package/.claude/agents/results-analyst.md +11 -0
- package/.claude/agents/security-reviewer.md +11 -0
- package/.claude/agents/test-runner.md +11 -0
- package/.claude/agents/workflow-curator.md +11 -0
- package/.claude/commands/autoresearch-coding.md +15 -0
- package/.claude/commands/council.md +7 -0
- package/.claude/commands/daily-enhance.md +7 -0
- package/.claude/commands/init-vibe.md +7 -0
- package/.claude/commands/security-scan.md +7 -0
- package/.claude/commands/vibe-finalize.md +7 -0
- package/.claude/rules/autoresearch.md +3 -0
- package/.claude/rules/security.md +3 -0
- package/.claude/rules/tooling.md +6 -0
- package/.claude/rules/vibe-core.md +8 -0
- package/.claude/settings.json +22 -0
- package/.claude/skills/agentshield-security-review/SKILL.md +75 -0
- package/.claude/skills/agentshield-security-review/agents/openai.yaml +5 -0
- package/.claude/skills/agentshield-security-review/references/native-install.md +97 -0
- package/.claude/skills/agentshield-security-review/references/report-template.md +54 -0
- package/.claude/skills/agentshield-security-review/references/review-checklist.md +45 -0
- package/.claude/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +110 -0
- package/.claude/skills/autoresearch-coding/README.md +15 -0
- package/.claude/skills/autoresearch-coding/SKILL.md +101 -0
- package/.claude/skills/autoresearch-coding/references/experiment-contract.md +13 -0
- package/.claude/skills/autoresearch-coding/references/metric-extraction.md +8 -0
- package/.claude/skills/autoresearch-coding/references/result-ledger.md +15 -0
- package/.claude/skills/autoresearch-coding/scripts/log_result.py +24 -0
- package/.claude/skills/autoresearch-coding/scripts/run_logged.py +30 -0
- package/.claude/skills/clearthought/SKILL.md +100 -0
- package/.claude/skills/clearthought/examples/decision-framework.md +441 -0
- package/.claude/skills/clearthought/examples/metagame-examples.md +536 -0
- package/.claude/skills/clearthought/examples/sequential-thinking.md +380 -0
- package/.claude/skills/clearthought/references/output-schemas.md +494 -0
- package/.claude/skills/clearthought/references/parameter-reference.md +482 -0
- package/.claude/skills/coding-level/SKILL.md +27 -0
- package/.claude/skills/coding-level/references/level-0.md +131 -0
- package/.claude/skills/coding-level/references/level-1.md +118 -0
- package/.claude/skills/coding-level/references/level-2.md +140 -0
- package/.claude/skills/coding-level/references/level-3.md +142 -0
- package/.claude/skills/coding-level/references/level-4.md +152 -0
- package/.claude/skills/coding-level/references/level-5.md +84 -0
- package/.claude/skills/daily-workflow-curator/SKILL.md +52 -0
- package/.claude/skills/memento/SKILL.md +36 -0
- package/.claude/skills/reviewing-4p-priorities/SKILL.md +72 -0
- package/.claude/skills/reviewing-4p-priorities/examples.md +298 -0
- package/.claude/skills/sequential-thinking/SKILL.md +106 -0
- package/.claude/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
- package/.claude/skills/sequential-thinking/examples/branching-exploration.md +474 -0
- package/.claude/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
- package/.claude/skills/sequential-thinking/examples/revision-pattern.md +304 -0
- package/.claude/skills/sequential-thinking/references/output-schema.md +483 -0
- package/.claude/skills/sequential-thinking/references/parameters.md +463 -0
- package/.claude/skills/sequential-thinking/references/patterns.md +666 -0
- package/.claude/skills/vibekit-init/SKILL.md +52 -0
- package/.claude/skills/visual-design-loop/SKILL.md +103 -0
- package/.claude/skills/visual-design-loop/agents/openai.yaml +4 -0
- package/.codex/README.md +11 -0
- package/.codex/config.example.toml +8 -0
- package/.codex-plugin/plugin.json +10 -0
- package/.cursor/commands/autoresearch-coding.md +11 -0
- package/.cursor/commands/council.md +3 -0
- package/.cursor/commands/daily-enhance.md +3 -0
- package/.cursor/commands/init-vibe.md +3 -0
- package/.cursor/commands/security-scan.md +3 -0
- package/.cursor/commands/vibe-finalize.md +3 -0
- package/.cursor/rules/001-vibe-core.mdc +6 -0
- package/.cursor/rules/010-init.mdc +7 -0
- package/.cursor/rules/020-security-agentshield.mdc +7 -0
- package/.cursor/rules/030-autoresearch-loop.mdc +7 -0
- package/.cursor/settings.json +22 -0
- package/.cursor/skills/clearthought/SKILL.md +100 -0
- package/.cursor/skills/clearthought/examples/decision-framework.md +441 -0
- package/.cursor/skills/clearthought/examples/metagame-examples.md +536 -0
- package/.cursor/skills/clearthought/examples/sequential-thinking.md +380 -0
- package/.cursor/skills/clearthought/references/output-schemas.md +494 -0
- package/.cursor/skills/clearthought/references/parameter-reference.md +482 -0
- package/.cursor/skills/coding-level/SKILL.md +27 -0
- package/.cursor/skills/coding-level/references/level-0.md +131 -0
- package/.cursor/skills/coding-level/references/level-1.md +118 -0
- package/.cursor/skills/coding-level/references/level-2.md +140 -0
- package/.cursor/skills/coding-level/references/level-3.md +142 -0
- package/.cursor/skills/coding-level/references/level-4.md +152 -0
- package/.cursor/skills/coding-level/references/level-5.md +84 -0
- package/.cursor/skills/memento/SKILL.md +36 -0
- package/.cursor/skills/reviewing-4p-priorities/SKILL.md +72 -0
- package/.cursor/skills/reviewing-4p-priorities/examples.md +298 -0
- package/.cursor/skills/sequential-thinking/SKILL.md +106 -0
- package/.cursor/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
- package/.cursor/skills/sequential-thinking/examples/branching-exploration.md +474 -0
- package/.cursor/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
- package/.cursor/skills/sequential-thinking/examples/revision-pattern.md +304 -0
- package/.cursor/skills/sequential-thinking/references/output-schema.md +483 -0
- package/.cursor/skills/sequential-thinking/references/parameters.md +463 -0
- package/.cursor/skills/sequential-thinking/references/patterns.md +666 -0
- package/.vbkit-commands/autoresearch-coding.md +11 -0
- package/.vbkit-commands/council.md +3 -0
- package/.vbkit-commands/daily-enhance.md +3 -0
- package/.vbkit-commands/init-vibe.md +3 -0
- package/.vbkit-commands/security-scan.md +3 -0
- package/.vbkit-commands/vibe-finalize.md +3 -0
- package/.vbkit-docs/AUTORESEARCH_LEDGER.md +11 -0
- package/.vbkit-docs/BACKBONE_REFERENCE.md +59 -0
- package/.vbkit-docs/INSTALL.md +120 -0
- package/.vbkit-docs/RESEARCH_NOTES.md +22 -0
- package/.vbkit-docs/SECURITY_MODEL.md +40 -0
- package/.vbkit-docs/TOOLING_GUIDE.md +22 -0
- package/.vbkit-docs/backbone.schema.json +110 -0
- package/.vbkit-docs/templates/CONTEXT_TEMPLATE.md +47 -0
- package/.vbkit-docs/templates/PRD_TEMPLATE.md +48 -0
- package/.vbkit-scripts/agentshield-probe.mjs +22 -0
- package/.vbkit-scripts/daily-enhance.mjs +62 -0
- package/.vbkit-scripts/doctor.mjs +323 -0
- package/.vbkit-scripts/init-backbone.mjs +628 -0
- package/.vbkit-scripts/mvck.mjs +444 -0
- package/.vbkit-scripts/pack-dry-run.mjs +16 -0
- package/.vbkit-scripts/test-install.mjs +118 -0
- package/.vbkit-scripts/validate-kit.mjs +419 -0
- package/.vbkit-scripts/vibekit-finalize.mjs +185 -0
- package/AGENTS.md +60 -0
- package/CHANGELOG.md +34 -0
- package/CLAUDE-template.md +52 -0
- package/CODE_OF_CONDUCT.md +18 -0
- package/CONTRIBUTING.md +28 -0
- package/FIRST_PROMPT.md +54 -0
- package/FIRST_TIME_INIT.md +165 -0
- package/LICENSE +21 -0
- package/README.md +493 -0
- package/README.vi.md +431 -0
- package/SECURITY.md +28 -0
- package/backbone.yml +103 -0
- package/bin/mvck.js +2 -0
- package/bin/vibe-kit.js +2 -0
- package/install.ps1 +14 -0
- package/install.sh +8 -0
- package/package.json +92 -0
- package/skills/agentshield-security-review/SKILL.md +75 -0
- package/skills/agentshield-security-review/agents/openai.yaml +5 -0
- package/skills/agentshield-security-review/references/native-install.md +97 -0
- package/skills/agentshield-security-review/references/report-template.md +54 -0
- package/skills/agentshield-security-review/references/review-checklist.md +45 -0
- package/skills/agentshield-security-review/scripts/agentshield_repo_probe.py +110 -0
- package/skills/autoresearch-coding/README.md +15 -0
- package/skills/autoresearch-coding/SKILL.md +101 -0
- package/skills/autoresearch-coding/references/experiment-contract.md +13 -0
- package/skills/autoresearch-coding/references/metric-extraction.md +8 -0
- package/skills/autoresearch-coding/references/result-ledger.md +15 -0
- package/skills/autoresearch-coding/scripts/log_result.py +24 -0
- package/skills/autoresearch-coding/scripts/run_logged.py +30 -0
- package/skills/clearthought/SKILL.md +100 -0
- package/skills/clearthought/examples/decision-framework.md +441 -0
- package/skills/clearthought/examples/metagame-examples.md +536 -0
- package/skills/clearthought/examples/sequential-thinking.md +380 -0
- package/skills/clearthought/references/output-schemas.md +494 -0
- package/skills/clearthought/references/parameter-reference.md +482 -0
- package/skills/coding-level/SKILL.md +27 -0
- package/skills/coding-level/references/level-0.md +131 -0
- package/skills/coding-level/references/level-1.md +118 -0
- package/skills/coding-level/references/level-2.md +140 -0
- package/skills/coding-level/references/level-3.md +142 -0
- package/skills/coding-level/references/level-4.md +152 -0
- package/skills/coding-level/references/level-5.md +84 -0
- package/skills/daily-workflow-curator/SKILL.md +52 -0
- package/skills/memento/SKILL.md +36 -0
- package/skills/reviewing-4p-priorities/SKILL.md +72 -0
- package/skills/reviewing-4p-priorities/examples.md +298 -0
- package/skills/sequential-thinking/SKILL.md +106 -0
- package/skills/sequential-thinking/examples/adaptive-depth.md +542 -0
- package/skills/sequential-thinking/examples/branching-exploration.md +474 -0
- package/skills/sequential-thinking/examples/linear-reasoning.md +210 -0
- package/skills/sequential-thinking/examples/revision-pattern.md +304 -0
- package/skills/sequential-thinking/references/output-schema.md +483 -0
- package/skills/sequential-thinking/references/parameters.md +463 -0
- package/skills/sequential-thinking/references/patterns.md +666 -0
- package/skills/vibekit-init/SKILL.md +52 -0
- package/skills/visual-design-loop/SKILL.md +103 -0
- package/skills/visual-design-loop/agents/openai.yaml +4 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const shouldWrite = args.includes('--write');
|
|
7
|
+
const yes = args.includes('--yes');
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv, { valueFlags = [] } = {}) {
|
|
10
|
+
const values = new Set(valueFlags);
|
|
11
|
+
const flags = new Set();
|
|
12
|
+
const options = new Map();
|
|
13
|
+
const positionals = [];
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
16
|
+
const item = argv[i];
|
|
17
|
+
if (item === '--') {
|
|
18
|
+
positionals.push(...argv.slice(i + 1));
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
if (!item.startsWith('--')) {
|
|
22
|
+
positionals.push(item);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const eq = item.indexOf('=');
|
|
27
|
+
if (eq >= 0) {
|
|
28
|
+
const key = item.slice(0, eq);
|
|
29
|
+
flags.add(key);
|
|
30
|
+
options.set(key, item.slice(eq + 1));
|
|
31
|
+
} else {
|
|
32
|
+
flags.add(item);
|
|
33
|
+
if (values.has(item) && argv[i + 1] && !argv[i + 1].startsWith('--')) {
|
|
34
|
+
options.set(item, argv[i + 1]);
|
|
35
|
+
i += 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { flags, options, positionals };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parsed = parseArgs(args, { valueFlags: ['--preset'] });
|
|
44
|
+
const target = path.resolve(parsed.positionals[0] || process.cwd());
|
|
45
|
+
const preset = parsed.options.get('--preset') || 'auto';
|
|
46
|
+
|
|
47
|
+
function readJson(file) {
|
|
48
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fileExists(rel) { return fs.existsSync(path.join(target, rel)); }
|
|
52
|
+
|
|
53
|
+
function walk(dir, limit = 5000) {
|
|
54
|
+
const out = [];
|
|
55
|
+
const skip = new Set(['.git', 'node_modules', 'dist', 'build', 'coverage', '.next', '.venv', 'vendor']);
|
|
56
|
+
function inner(current) {
|
|
57
|
+
if (out.length >= limit) return;
|
|
58
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
59
|
+
if (skip.has(entry.name)) continue;
|
|
60
|
+
const p = path.join(current, entry.name);
|
|
61
|
+
if (entry.isDirectory()) inner(p);
|
|
62
|
+
else out.push(path.relative(target, p).replaceAll(path.sep, '/'));
|
|
63
|
+
if (out.length >= limit) return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
try { inner(dir); } catch {}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function countExt(files) {
|
|
71
|
+
const counts = new Map();
|
|
72
|
+
for (const f of files) {
|
|
73
|
+
const ext = path.extname(f).toLowerCase();
|
|
74
|
+
if (!ext) continue;
|
|
75
|
+
counts.set(ext, (counts.get(ext) || 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
return counts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function uniqueLimit(values, limit = 12) {
|
|
81
|
+
return [...new Set(values.filter(Boolean))].slice(0, limit);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function yamlList(values, indent = 4) {
|
|
85
|
+
if (!values?.length) return ' []';
|
|
86
|
+
const pad = ' '.repeat(indent);
|
|
87
|
+
return `\n${values.map((value) => `${pad}- ${yamlString(value)}`).join('\n')}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cleanName(name) {
|
|
91
|
+
return name.replace(/^\.+/, '').replace(/\.[^.]+$/, '');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isKitManagedPath(file) {
|
|
95
|
+
const managedRoots = new Set(['.claude', '.cursor', '.agents', '.codex', '.codex-plugin', 'skills', '.vbkit-commands', '.vbkit-docs', '.vbkit-scripts']);
|
|
96
|
+
const managedFiles = new Set([
|
|
97
|
+
'AGENTS.md',
|
|
98
|
+
'CLAUDE-template.md',
|
|
99
|
+
'FIRST_PROMPT.md',
|
|
100
|
+
'FIRST_TIME_INIT.md',
|
|
101
|
+
'backbone.yml',
|
|
102
|
+
'.vbkit-scripts/mvck.mjs',
|
|
103
|
+
'.vbkit-scripts/init-backbone.mjs',
|
|
104
|
+
'.vbkit-scripts/daily-enhance.mjs',
|
|
105
|
+
'.vbkit-scripts/validate-kit.mjs',
|
|
106
|
+
'.vbkit-scripts/doctor.mjs',
|
|
107
|
+
'.vbkit-scripts/test-install.mjs',
|
|
108
|
+
'.vbkit-scripts/agentshield-probe.mjs',
|
|
109
|
+
'.vbkit-scripts/pack-dry-run.mjs',
|
|
110
|
+
'.vbkit-scripts/vibekit-finalize.mjs'
|
|
111
|
+
]);
|
|
112
|
+
const root = file.split('/')[0];
|
|
113
|
+
return managedRoots.has(root) || managedFiles.has(file);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function classifyCase(name) {
|
|
117
|
+
const value = cleanName(name);
|
|
118
|
+
if (!value || /^\d+$/.test(value)) return null;
|
|
119
|
+
if (/^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(value)) return 'kebab-case';
|
|
120
|
+
if (/^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/.test(value)) return 'snake_case';
|
|
121
|
+
if (/^[A-Z][A-Za-z0-9]*$/.test(value)) return 'PascalCase';
|
|
122
|
+
if (/^[a-z][A-Za-z0-9]*$/.test(value) && /[A-Z]/.test(value)) return 'camelCase';
|
|
123
|
+
if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(value)) return 'UPPER_SNAKE_CASE';
|
|
124
|
+
if (/^[a-z0-9]+$/.test(value)) return 'lowercase';
|
|
125
|
+
return 'mixed';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function dominantCase(names) {
|
|
129
|
+
const counts = new Map();
|
|
130
|
+
for (const name of names) {
|
|
131
|
+
const style = classifyCase(name);
|
|
132
|
+
if (!style) continue;
|
|
133
|
+
counts.set(style, (counts.get(style) || 0) + 1);
|
|
134
|
+
}
|
|
135
|
+
const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
136
|
+
if (!ranked.length) return { style: 'not-detected', evidence: [] };
|
|
137
|
+
const total = ranked.reduce((sum, item) => sum + item[1], 0);
|
|
138
|
+
const [style, count] = ranked[0];
|
|
139
|
+
return {
|
|
140
|
+
style: count / total >= 0.45 ? style : 'mixed',
|
|
141
|
+
evidence: ranked.slice(0, 4).map(([itemStyle, itemCount]) => `${itemStyle}: ${itemCount}`)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function detectAppPaths(files) {
|
|
146
|
+
const candidates = [];
|
|
147
|
+
const workspaceRoots = new Set(['apps', 'packages', 'services', 'workspaces']);
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
const parts = file.split('/');
|
|
150
|
+
if (parts.length >= 3 && workspaceRoots.has(parts[0])) candidates.push(`${parts[0]}/${parts[1]}`);
|
|
151
|
+
}
|
|
152
|
+
for (const direct of ['client', 'server', 'frontend', 'backend', 'mobile', 'api', 'web']) {
|
|
153
|
+
if (files.some((file) => file.startsWith(`${direct}/`))) candidates.push(direct);
|
|
154
|
+
}
|
|
155
|
+
return uniqueLimit(candidates, 16).length ? uniqueLimit(candidates, 16) : ['.'];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function detectLanguage(files) {
|
|
159
|
+
const counts = countExt(files);
|
|
160
|
+
const scores = {
|
|
161
|
+
ts: (counts.get('.ts') || 0) + (counts.get('.tsx') || 0),
|
|
162
|
+
js: (counts.get('.js') || 0) + (counts.get('.jsx') || 0) + (counts.get('.mjs') || 0),
|
|
163
|
+
py: counts.get('.py') || 0,
|
|
164
|
+
go: counts.get('.go') || 0,
|
|
165
|
+
rs: counts.get('.rs') || 0,
|
|
166
|
+
java: counts.get('.java') || 0,
|
|
167
|
+
dotnet: (counts.get('.cs') || 0) + (counts.get('.fs') || 0),
|
|
168
|
+
php: counts.get('.php') || 0,
|
|
169
|
+
ruby: counts.get('.rb') || 0
|
|
170
|
+
};
|
|
171
|
+
for (const marker of ['go.mod', 'Cargo.toml', 'pyproject.toml', 'package.json', 'pom.xml', 'composer.json', 'Gemfile']) {
|
|
172
|
+
if (fileExists(marker)) {
|
|
173
|
+
if (marker === 'go.mod') scores.go += 20;
|
|
174
|
+
if (marker === 'Cargo.toml') scores.rs += 20;
|
|
175
|
+
if (marker === 'pyproject.toml') scores.py += 20;
|
|
176
|
+
if (marker === 'package.json') {
|
|
177
|
+
if (fileExists('tsconfig.json') || scores.ts > 0) scores.ts += 10;
|
|
178
|
+
else scores.js += 10;
|
|
179
|
+
}
|
|
180
|
+
if (marker === 'pom.xml') scores.java += 20;
|
|
181
|
+
if (marker === 'composer.json') scores.php += 20;
|
|
182
|
+
if (marker === 'Gemfile') scores.ruby += 20;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
186
|
+
return sorted[0][1] > 0 ? sorted[0][0] : 'unknown';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function packageManager() {
|
|
190
|
+
if (fileExists('pnpm-lock.yaml')) return 'pnpm';
|
|
191
|
+
if (fileExists('yarn.lock')) return 'yarn';
|
|
192
|
+
if (fileExists('bun.lockb') || fileExists('bun.lock')) return 'bun';
|
|
193
|
+
if (fileExists('package-lock.json') || fileExists('package.json')) return 'npm';
|
|
194
|
+
if (fileExists('uv.lock')) return 'uv';
|
|
195
|
+
if (fileExists('poetry.lock')) return 'poetry';
|
|
196
|
+
if (fileExists('requirements.txt') || fileExists('pyproject.toml')) return 'pip';
|
|
197
|
+
if (fileExists('go.mod')) return 'go';
|
|
198
|
+
if (fileExists('Cargo.toml')) return 'cargo';
|
|
199
|
+
if (fileExists('pom.xml')) return 'maven';
|
|
200
|
+
if (fileExists('build.gradle') || fileExists('build.gradle.kts')) return 'gradle';
|
|
201
|
+
if (fileExists('composer.json')) return 'composer';
|
|
202
|
+
if (fileExists('Gemfile')) return 'bundler';
|
|
203
|
+
return 'unknown';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function projectName() {
|
|
207
|
+
const pkg = readJson(path.join(target, 'package.json'));
|
|
208
|
+
if (pkg?.name) return String(pkg.name).split('/').pop();
|
|
209
|
+
if (fileExists('go.mod')) {
|
|
210
|
+
const text = fs.readFileSync(path.join(target, 'go.mod'), 'utf8');
|
|
211
|
+
const match = text.match(/^module\s+(.+)$/m);
|
|
212
|
+
if (match) return match[1].trim().split('/').pop();
|
|
213
|
+
}
|
|
214
|
+
if (fileExists('pyproject.toml')) {
|
|
215
|
+
const text = fs.readFileSync(path.join(target, 'pyproject.toml'), 'utf8');
|
|
216
|
+
const match = text.match(/^name\s*=\s*["']([^"']+)["']/m);
|
|
217
|
+
if (match) return match[1];
|
|
218
|
+
}
|
|
219
|
+
return path.basename(target);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function description() {
|
|
223
|
+
const pkg = readJson(path.join(target, 'package.json'));
|
|
224
|
+
if (pkg?.description) return String(pkg.description);
|
|
225
|
+
const readme = ['README.md', 'readme.md'].find(fileExists);
|
|
226
|
+
if (readme) {
|
|
227
|
+
const lines = fs.readFileSync(path.join(target, readme), 'utf8').split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
|
|
228
|
+
const line = lines.find((x) => !x.startsWith('#') && !x.startsWith('!['));
|
|
229
|
+
if (line) return line.replace(/^>\s*/, '').slice(0, 160);
|
|
230
|
+
}
|
|
231
|
+
return 'TBD';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function projectType(files = []) {
|
|
235
|
+
const markers = ['pnpm-workspace.yaml', 'turbo.json', 'nx.json', 'lerna.json', 'go.work'];
|
|
236
|
+
if (markers.some(fileExists)) return 'monorepo';
|
|
237
|
+
if (fileExists('.gitmodules')) return 'multi-repo';
|
|
238
|
+
const appPaths = detectAppPaths(files);
|
|
239
|
+
if (appPaths.length > 1 && appPaths[0] !== '.') return 'monorepo';
|
|
240
|
+
return 'single-repo';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function commands(pm, lang) {
|
|
244
|
+
const pkg = readJson(path.join(target, 'package.json'));
|
|
245
|
+
const prefix = pm === 'pnpm' ? 'pnpm' : pm === 'yarn' ? 'yarn' : pm === 'bun' ? 'bun' : 'npm run';
|
|
246
|
+
const c = { install: null, test: null, lint: null, typecheck: null, build: null, validate: null };
|
|
247
|
+
if (pkg?.scripts) {
|
|
248
|
+
c.install = pm === 'pnpm' ? 'pnpm install' : pm === 'yarn' ? 'yarn install' : pm === 'bun' ? 'bun install' : 'npm install';
|
|
249
|
+
if (pkg.scripts.test) c.test = pm === 'npm' ? 'npm test' : `${prefix} test`;
|
|
250
|
+
if (pkg.scripts.lint) c.lint = `${prefix} lint`;
|
|
251
|
+
if (pkg.scripts.typecheck) c.typecheck = `${prefix} typecheck`;
|
|
252
|
+
if (pkg.scripts.build) c.build = `${prefix} build`;
|
|
253
|
+
} else if (lang === 'go') {
|
|
254
|
+
c.test = 'go test ./...'; c.build = 'go build ./...';
|
|
255
|
+
} else if (lang === 'py') {
|
|
256
|
+
c.test = 'pytest -q';
|
|
257
|
+
} else if (lang === 'rs') {
|
|
258
|
+
c.test = 'cargo test'; c.build = 'cargo build';
|
|
259
|
+
} else if (lang === 'java') {
|
|
260
|
+
c.test = fileExists('pom.xml') ? 'mvn test' : 'gradle test';
|
|
261
|
+
c.build = fileExists('pom.xml') ? 'mvn package' : 'gradle build';
|
|
262
|
+
}
|
|
263
|
+
c.validate = c.test || c.build || c.lint || 'echo "No validation command detected"';
|
|
264
|
+
return c;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function applyPreset(data, presetName) {
|
|
268
|
+
if (presetName === 'auto') return data;
|
|
269
|
+
const pkg = readJson(path.join(target, 'package.json'));
|
|
270
|
+
const hasScript = (name) => Boolean(pkg?.scripts?.[name]);
|
|
271
|
+
const detectedValidate = data.commands.validate && !data.commands.validate.startsWith('echo ');
|
|
272
|
+
const presets = {
|
|
273
|
+
nextjs: {
|
|
274
|
+
language: data.language === 'unknown' ? 'ts' : data.language,
|
|
275
|
+
packageManager: data.packageManager === 'unknown' ? 'npm' : data.packageManager,
|
|
276
|
+
sourcePaths: ['app', 'pages', 'src', 'components'].filter(fileExists),
|
|
277
|
+
testPaths: ['tests', '__tests__', 'e2e'].filter(fileExists),
|
|
278
|
+
commands: {
|
|
279
|
+
install: data.commands.install || 'npm install',
|
|
280
|
+
test: data.commands.test,
|
|
281
|
+
lint: data.commands.lint || (hasScript('lint') ? 'npm run lint' : null),
|
|
282
|
+
typecheck: data.commands.typecheck || (hasScript('typecheck') ? 'npm run typecheck' : null),
|
|
283
|
+
build: data.commands.build || (hasScript('build') ? 'npm run build' : null),
|
|
284
|
+
validate: data.commands.test || data.commands.lint || data.commands.typecheck || data.commands.build || 'echo "Set a Next.js validation command"'
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
wordpress: {
|
|
288
|
+
language: 'php',
|
|
289
|
+
packageManager: data.packageManager === 'unknown' ? 'composer' : data.packageManager,
|
|
290
|
+
sourcePaths: ['wp-content', 'app', 'src'].filter(fileExists),
|
|
291
|
+
testPaths: ['tests'].filter(fileExists),
|
|
292
|
+
commands: {
|
|
293
|
+
install: data.commands.install || (fileExists('composer.json') ? 'composer install' : null),
|
|
294
|
+
test: data.commands.test,
|
|
295
|
+
lint: data.commands.lint,
|
|
296
|
+
typecheck: data.commands.typecheck,
|
|
297
|
+
build: data.commands.build,
|
|
298
|
+
validate: data.commands.test || data.commands.lint || 'echo "Set a WordPress validation command"'
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
python: {
|
|
302
|
+
language: 'py',
|
|
303
|
+
packageManager: data.packageManager === 'unknown' ? 'pip' : data.packageManager,
|
|
304
|
+
sourcePaths: data.sourcePaths.length ? data.sourcePaths : ['src'].filter(fileExists),
|
|
305
|
+
testPaths: data.testPaths.length ? data.testPaths : ['tests'].filter(fileExists),
|
|
306
|
+
commands: {
|
|
307
|
+
install: data.commands.install || (fileExists('pyproject.toml') ? 'pip install -e .' : fileExists('requirements.txt') ? 'pip install -r requirements.txt' : null),
|
|
308
|
+
test: data.commands.test || 'pytest -q',
|
|
309
|
+
lint: data.commands.lint,
|
|
310
|
+
typecheck: data.commands.typecheck,
|
|
311
|
+
build: data.commands.build,
|
|
312
|
+
validate: data.commands.test || 'pytest -q'
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
laravel: {
|
|
316
|
+
language: 'php',
|
|
317
|
+
packageManager: data.packageManager === 'unknown' ? 'composer' : data.packageManager,
|
|
318
|
+
sourcePaths: ['app', 'routes', 'database', 'resources'].filter(fileExists),
|
|
319
|
+
testPaths: ['tests'].filter(fileExists),
|
|
320
|
+
commands: {
|
|
321
|
+
install: data.commands.install || 'composer install',
|
|
322
|
+
test: data.commands.test || 'php artisan test',
|
|
323
|
+
lint: data.commands.lint,
|
|
324
|
+
typecheck: data.commands.typecheck,
|
|
325
|
+
build: data.commands.build || (fileExists('package.json') ? 'npm run build' : null),
|
|
326
|
+
validate: data.commands.test || 'php artisan test'
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
docker: {
|
|
330
|
+
language: data.language === 'unknown' ? 'mixed' : data.language,
|
|
331
|
+
packageManager: data.packageManager,
|
|
332
|
+
sourcePaths: data.sourcePaths,
|
|
333
|
+
testPaths: data.testPaths,
|
|
334
|
+
commands: {
|
|
335
|
+
...data.commands,
|
|
336
|
+
validate: detectedValidate ? data.commands.validate : (fileExists('docker-compose.yml') || fileExists('compose.yml') ? 'docker compose config' : 'echo "Set a Docker validation command"')
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const selected = presets[presetName];
|
|
342
|
+
if (!selected) {
|
|
343
|
+
console.error(`Unknown preset: ${presetName}`);
|
|
344
|
+
console.error('Supported presets: nextjs, wordpress, python, laravel, docker');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
...data,
|
|
350
|
+
...selected,
|
|
351
|
+
commands: {
|
|
352
|
+
...data.commands,
|
|
353
|
+
...selected.commands
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function yamlString(value) {
|
|
359
|
+
if (value === null || value === undefined) return 'null';
|
|
360
|
+
const s = String(value);
|
|
361
|
+
if (/^[a-zA-Z0-9_./:-]+( [a-zA-Z0-9_./:-]+)*$/.test(s)) return s;
|
|
362
|
+
return JSON.stringify(s);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function detectNaming(files) {
|
|
366
|
+
const ignoredDirs = new Set(['node_modules', 'dist', 'build', 'coverage', 'vendor']);
|
|
367
|
+
const codeExt = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.py', '.go', '.rs', '.java', '.cs', '.php', '.rb', '.dart', '.kt', '.swift']);
|
|
368
|
+
const fileNames = [];
|
|
369
|
+
const dirNames = [];
|
|
370
|
+
const symbolNames = [];
|
|
371
|
+
let scannedCodeFiles = 0;
|
|
372
|
+
for (const file of files) {
|
|
373
|
+
const parts = file.split('/');
|
|
374
|
+
if (parts.some((part) => part.startsWith('.') || ignoredDirs.has(part))) continue;
|
|
375
|
+
if (codeExt.has(path.extname(file).toLowerCase())) {
|
|
376
|
+
fileNames.push(path.basename(file, path.extname(file)));
|
|
377
|
+
if (scannedCodeFiles < 300) {
|
|
378
|
+
const text = readSmallFile(file);
|
|
379
|
+
const pattern = /\b(?:class|interface|enum|type|function|const|let|var|def|func|struct|trait|object)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
380
|
+
for (const match of text.matchAll(pattern)) symbolNames.push(match[1]);
|
|
381
|
+
scannedCodeFiles += 1;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
for (const part of parts.slice(0, -1)) {
|
|
385
|
+
if (!part.startsWith('.') && !ignoredDirs.has(part)) dirNames.push(part);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const filesCase = dominantCase(fileNames);
|
|
389
|
+
const dirsCase = dominantCase(dirNames);
|
|
390
|
+
const symbolsCase = dominantCase(symbolNames);
|
|
391
|
+
return {
|
|
392
|
+
files: filesCase.style,
|
|
393
|
+
directories: dirsCase.style,
|
|
394
|
+
symbols: symbolsCase.style,
|
|
395
|
+
evidence: uniqueLimit([
|
|
396
|
+
...filesCase.evidence.map((x) => `files ${x}`),
|
|
397
|
+
...dirsCase.evidence.map((x) => `dirs ${x}`),
|
|
398
|
+
...symbolsCase.evidence.map((x) => `symbols ${x}`)
|
|
399
|
+
], 10)
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function detectArchitecture(files) {
|
|
404
|
+
const dirs = new Set();
|
|
405
|
+
for (const file of files) {
|
|
406
|
+
for (const part of file.split('/').slice(0, -1)) {
|
|
407
|
+
if (!part.startsWith('.')) dirs.add(part.toLowerCase());
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const patterns = [];
|
|
412
|
+
if (['models', 'views', 'controllers'].every((dir) => dirs.has(dir))) patterns.push('mvc');
|
|
413
|
+
if (dirs.has('viewmodels') || dirs.has('view_models')) patterns.push('mvvm');
|
|
414
|
+
if (['domain', 'application', 'infrastructure'].every((dir) => dirs.has(dir))) patterns.push('clean-architecture');
|
|
415
|
+
if (dirs.has('features') || dirs.has('modules')) patterns.push('feature-modules');
|
|
416
|
+
if (['controllers', 'services', 'repositories'].filter((dir) => dirs.has(dir)).length >= 2) patterns.push('layered-services');
|
|
417
|
+
if (['screens', 'pages', 'routes'].filter((dir) => dirs.has(dir)).length >= 1) patterns.push('route-or-screen-structured');
|
|
418
|
+
if (['cmd', 'internal', 'pkg'].filter((dir) => dirs.has(dir)).length >= 2) patterns.push('go-standard-layout');
|
|
419
|
+
if (['apps', 'packages', 'services'].filter((dir) => dirs.has(dir)).length >= 1) patterns.push('workspace-packages');
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
detected: uniqueLimit(patterns, 8),
|
|
423
|
+
rule: patterns.length
|
|
424
|
+
? 'Place new code in the nearest existing layer, feature, app, or package that matches the detected architecture.'
|
|
425
|
+
: 'No dominant architecture was detected; ask before adding new top-level layers or broad structural patterns.'
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function readSmallFile(rel) {
|
|
430
|
+
const file = path.join(target, rel);
|
|
431
|
+
try {
|
|
432
|
+
const stat = fs.statSync(file);
|
|
433
|
+
if (stat.size > 200_000) return '';
|
|
434
|
+
return fs.readFileSync(file, 'utf8');
|
|
435
|
+
} catch {
|
|
436
|
+
return '';
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function detectResourceConventions(files) {
|
|
441
|
+
const assetExt = new Set(['.png', '.jpg', '.jpeg', '.svg', '.webp', '.gif', '.avif', '.ico', '.ttf', '.otf']);
|
|
442
|
+
const codeExt = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.dart', '.kt', '.swift', '.java', '.cs', '.php', '.py', '.rb']);
|
|
443
|
+
const assetRoots = uniqueLimit(files
|
|
444
|
+
.filter((file) => assetExt.has(path.extname(file).toLowerCase()))
|
|
445
|
+
.map((file) => path.dirname(file).replaceAll(path.sep, '/'))
|
|
446
|
+
.filter((dir) => /(^|\/)(assets?|images?|icons?|public|static|res|drawable|mipmap)(\/|$)/i.test(dir)), 12);
|
|
447
|
+
|
|
448
|
+
const registryFiles = [];
|
|
449
|
+
let scannedCodeFiles = 0;
|
|
450
|
+
for (const file of files.filter((item) => codeExt.has(path.extname(item).toLowerCase()))) {
|
|
451
|
+
const base = path.basename(file);
|
|
452
|
+
const likelyName = /(app[-_]?images|asset[-_]?paths|assets?|images?|resources?|generated[-_]?assets?)/i.test(base);
|
|
453
|
+
const text = likelyName || scannedCodeFiles < 400 ? readSmallFile(file) : '';
|
|
454
|
+
scannedCodeFiles += 1;
|
|
455
|
+
if (likelyName || /\b(AppImages|AssetPaths|AppAssets|AssetRegistry|ImageAssets)\b|(?:\bAssets|\bImages|\bResources)\s*\./.test(text)) registryFiles.push(file);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
assetRoots,
|
|
460
|
+
registries: uniqueLimit(registryFiles, 12),
|
|
461
|
+
rule: registryFiles.length
|
|
462
|
+
? 'Use the existing shared resource accessors or generated resource API; do not add raw asset paths in feature code.'
|
|
463
|
+
: assetRoots.length
|
|
464
|
+
? 'Avoid scattering repeated literal resource paths; ask before introducing a shared registry or generator.'
|
|
465
|
+
: 'No asset/resource convention was detected; ask before adding a project-wide resource abstraction.'
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function detectLocalizationConventions(files) {
|
|
470
|
+
const codeExt = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.dart', '.kt', '.swift', '.java', '.cs', '.php', '.py', '.rb']);
|
|
471
|
+
const catalogs = uniqueLimit(files.filter((file) => {
|
|
472
|
+
const ext = path.extname(file).toLowerCase();
|
|
473
|
+
return ext === '.arb' || /(^|\/)(i18n|l10n|locales?|translations?|lang|messages)(\/|$)/i.test(file);
|
|
474
|
+
}), 12);
|
|
475
|
+
|
|
476
|
+
const accessors = [];
|
|
477
|
+
let scannedCodeFiles = 0;
|
|
478
|
+
for (const file of files.filter((item) => codeExt.has(path.extname(item).toLowerCase()))) {
|
|
479
|
+
const base = path.basename(file);
|
|
480
|
+
const likelyName = /(localizations?|i18n|l10n|intl|messages?|strings?)/i.test(base);
|
|
481
|
+
const text = likelyName || scannedCodeFiles < 400 ? readSmallFile(file) : '';
|
|
482
|
+
scannedCodeFiles += 1;
|
|
483
|
+
if (likelyName || /\b(AppLocalizations|Intl|I18n)\b|\b(t|translate|tr)\s*\(|\bS\.of\b/.test(text)) accessors.push(file);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
catalogs,
|
|
488
|
+
accessors: uniqueLimit(accessors, 12),
|
|
489
|
+
rule: catalogs.length || accessors.length
|
|
490
|
+
? 'Use the existing localization catalogs and message accessors for user-facing text; do not hardcode copy in feature code.'
|
|
491
|
+
: 'No localization convention was detected; follow nearby copy patterns and ask before introducing a project-wide i18n layer.'
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function detectConventions(files, type) {
|
|
496
|
+
const projectFiles = files.filter((file) => !isKitManagedPath(file));
|
|
497
|
+
const naming = detectNaming(projectFiles);
|
|
498
|
+
const architecture = detectArchitecture(projectFiles);
|
|
499
|
+
const resources = detectResourceConventions(projectFiles);
|
|
500
|
+
const localization = detectLocalizationConventions(projectFiles);
|
|
501
|
+
return {
|
|
502
|
+
scope: type === 'monorepo' ? 'per app/package when evidence differs' : 'repo',
|
|
503
|
+
naming,
|
|
504
|
+
architecture,
|
|
505
|
+
resources,
|
|
506
|
+
localization,
|
|
507
|
+
reviewQuestions: [
|
|
508
|
+
`Confirm naming rules: files=${naming.files}, directories=${naming.directories}, symbols=${naming.symbols}.`,
|
|
509
|
+
`Confirm architecture scope: ${architecture.detected.length ? architecture.detected.join(', ') : 'not detected'}.`,
|
|
510
|
+
`Confirm resource rule: ${resources.registries.length ? 'reuse detected accessors' : 'ask before adding a shared registry'}.`,
|
|
511
|
+
`Confirm localization rule: ${localization.catalogs.length || localization.accessors.length ? 'use detected catalogs/accessors' : 'ask before adding i18n conventions'}.`
|
|
512
|
+
]
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const PRD_CANDIDATES = [
|
|
517
|
+
'PRD.md', '.vbkit-docs/PRD.md', 'prd.md', '.vbkit-docs/prd.md',
|
|
518
|
+
'REQUIREMENTS.md', '.vbkit-docs/REQUIREMENTS.md',
|
|
519
|
+
'SPEC.md', '.vbkit-docs/SPEC.md', '.vbkit-docs/spec.md',
|
|
520
|
+
'PRODUCT.md', '.vbkit-docs/PRODUCT.md'
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
function countCodeFiles(files) {
|
|
524
|
+
const codeExt = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.cs', '.fs', '.php', '.rb', '.dart', '.kt', '.swift', '.c', '.cc', '.cpp', '.h', '.hpp', '.scala', '.ex', '.exs', '.clj', '.vue', '.svelte']);
|
|
525
|
+
return files.filter((file) => codeExt.has(path.extname(file).toLowerCase())).length;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function detectProjectMode(files) {
|
|
529
|
+
return countCodeFiles(files) === 0 ? 'greenfield' : 'brownfield';
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function detectPrd(files) {
|
|
533
|
+
for (const candidate of PRD_CANDIDATES) {
|
|
534
|
+
if (fileExists(candidate)) return candidate;
|
|
535
|
+
}
|
|
536
|
+
const dirMatch = files.find((file) => /(^|\/)(prd|product|requirements|specs?)\//i.test(file) && /\.(md|mdx|txt)$/i.test(file));
|
|
537
|
+
return dirMatch || 'none';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function detectContext() {
|
|
541
|
+
for (const candidate of ['.vbkit-docs/CONTEXT.md', 'CONTEXT.md', '.vbkit-docs/context.md']) {
|
|
542
|
+
if (fileExists(candidate)) return candidate;
|
|
543
|
+
}
|
|
544
|
+
return 'none';
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function render(data) {
|
|
548
|
+
const cmd = data.commands;
|
|
549
|
+
const rules = data.conventions;
|
|
550
|
+
return `# Minimal Vibe Coding Kit backbone. See .vbkit-docs/BACKBONE_REFERENCE.md for details.\nversion: 2\nmeta:\n template_status: initialized\n initialized_at: ${yamlString(data.initializedAt)}\n template_source: minimal-vibe-coding-kit\n schema_version: 3\n init_runbook: FIRST_TIME_INIT.md\n init_done_marker: .vibekit/INIT_DONE\n\nproject:\n name: ${yamlString(data.name)}\n description: ${yamlString(data.description)}\n type: ${data.type}\n primary_language: ${data.language}\n package_manager: ${data.packageManager}\n mode: ${data.mode}\n prd: ${yamlString(data.prd)}\n context: ${yamlString(data.context)}\n\npaths:\n root: .\n apps:${yamlList(data.appPaths, 4)}\n source: ${JSON.stringify(data.sourcePaths)}\n tests: ${JSON.stringify(data.testPaths)}\n docs: [README.md, .vbkit-docs/]\n generated: [node_modules/, dist/, build/, coverage/]\n\nconventions:\n review_required_before_write: true\n scope: ${yamlString(rules.scope)}\n naming:\n files: ${yamlString(rules.naming.files)}\n directories: ${yamlString(rules.naming.directories)}\n symbols: follow existing language and framework conventions in nearby code\n evidence:${yamlList(rules.naming.evidence, 6)}\n architecture:\n detected:${yamlList(rules.architecture.detected, 6)}\n rule: ${yamlString(rules.architecture.rule)}\n resources:\n detected_asset_roots:${yamlList(rules.resources.assetRoots, 6)}\n detected_registries:${yamlList(rules.resources.registries, 6)}\n rule: ${yamlString(rules.resources.rule)}\n localization:\n detected_catalogs:${yamlList(rules.localization.catalogs, 6)}\n detected_accessors:${yamlList(rules.localization.accessors, 6)}\n rule: ${yamlString(rules.localization.rule)}\n custom_rules:\n - Add team-specific rules here during init review; future agents must follow them.\n\ncommands:\n install: ${yamlString(cmd.install)}\n test: ${yamlString(cmd.test)}\n lint: ${yamlString(cmd.lint)}\n typecheck: ${yamlString(cmd.typecheck)}\n build: ${yamlString(cmd.build)}\n validate: ${yamlString(cmd.validate)}\n\npolicy:\n default_branch: main\n branch_naming: "feat/<short-topic>"\n commit_style: conventional\n editable_paths: [.]\n protected_paths:\n - .git/\n - .env*\n - "**/*secret*"\n - "**/*token*"\n - "**/migrations/**"\n - "**/node_modules/**"\n - "**/dist/**"\n - "**/build/**"\n - "**/coverage/**"\n - "**/*lock*"\n\nagent_surfaces:\n claude: .claude/\n cursor: .cursor/\n codex: .agents/\n codex_plugin: .codex-plugin/plugin.json\n shared_skills: skills/\n shared_commands: .vbkit-commands/\n\nautomation:\n autoresearch:\n results_file: results.tsv\n logs_dir: .autoresearch/logs\n default_budget: 3\n default_timeout_seconds: 600\n daily_enhance:\n report_dir: .vibekit/reports\n write_mode: propose_only\n finalize:\n cleanup_dir: _vibekit-cleanup\n marker: .vibekit/FINALIZE_DONE\n one_time_files: [FIRST_TIME_INIT.md, FIRST_PROMPT.md, PUSH_TO_GITHUB.md, CLAUDE-template.md]\n security:\n probe: node .vbkit-scripts/agentshield-probe.mjs .\n scan: npx ecc-agentshield scan --path . --format text --min-severity medium\n`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function inferPaths(files) {
|
|
554
|
+
const dirs = new Set(files.map((f) => f.split('/')[0]));
|
|
555
|
+
const sourceCandidates = ['src', 'app', 'lib', 'cmd', 'pkg', 'internal', 'server', 'client', 'packages'];
|
|
556
|
+
const testCandidates = ['test', 'tests', '__tests__', 'spec'];
|
|
557
|
+
return {
|
|
558
|
+
appPaths: detectAppPaths(files),
|
|
559
|
+
sourcePaths: sourceCandidates.filter((d) => dirs.has(d)),
|
|
560
|
+
testPaths: testCandidates.filter((d) => dirs.has(d))
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const files = walk(target);
|
|
565
|
+
const projectFiles = files.filter((file) => !isKitManagedPath(file));
|
|
566
|
+
const lang = detectLanguage(projectFiles);
|
|
567
|
+
const pm = packageManager();
|
|
568
|
+
const inferred = applyPreset({
|
|
569
|
+
initializedAt: new Date().toISOString(),
|
|
570
|
+
name: projectName(),
|
|
571
|
+
description: description(),
|
|
572
|
+
type: projectType(projectFiles),
|
|
573
|
+
language: lang,
|
|
574
|
+
packageManager: pm,
|
|
575
|
+
mode: detectProjectMode(projectFiles),
|
|
576
|
+
prd: detectPrd(projectFiles),
|
|
577
|
+
context: detectContext(),
|
|
578
|
+
commands: commands(pm, lang),
|
|
579
|
+
...inferPaths(projectFiles)
|
|
580
|
+
}, preset);
|
|
581
|
+
inferred.conventions = detectConventions(projectFiles, inferred.type);
|
|
582
|
+
|
|
583
|
+
const out = render(inferred).replace(
|
|
584
|
+
'symbols: follow existing language and framework conventions in nearby code',
|
|
585
|
+
`symbols: ${yamlString(inferred.conventions.naming.symbols)}`
|
|
586
|
+
);
|
|
587
|
+
const backbonePath = path.join(target, 'backbone.yml');
|
|
588
|
+
|
|
589
|
+
if (shouldWrite) {
|
|
590
|
+
if (!yes) {
|
|
591
|
+
console.error('Refusing to write without --yes. Run with --propose first, review, then --write --yes.');
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
fs.writeFileSync(backbonePath, out);
|
|
595
|
+
const marker = path.join(target, '.vibekit', 'INIT_DONE');
|
|
596
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
597
|
+
fs.writeFileSync(marker, `initialized_at: ${inferred.initializedAt}\nschema_version: 3\ntool: minimal-vibe-coding-kit\n`);
|
|
598
|
+
console.log(`Initialized backbone.yml at ${inferred.initializedAt}`);
|
|
599
|
+
} else {
|
|
600
|
+
console.log('Requirements detected:');
|
|
601
|
+
console.log(`- target: ${target}`);
|
|
602
|
+
console.log(`- backbone.yml: ${fileExists('backbone.yml') ? 'found' : 'missing'}`);
|
|
603
|
+
console.log(`- project: ${inferred.name}`);
|
|
604
|
+
console.log(`- language: ${inferred.language}`);
|
|
605
|
+
console.log(`- package manager: ${inferred.packageManager}`);
|
|
606
|
+
console.log(`- preset: ${preset}`);
|
|
607
|
+
console.log(`- validate command: ${inferred.commands.validate}`);
|
|
608
|
+
console.log(`- project mode: ${inferred.mode} (${inferred.mode === 'greenfield' ? 'no source code yet' : 'existing source code detected'})`);
|
|
609
|
+
console.log(`- PRD: ${inferred.prd === 'none' ? 'not found' : `found at ${inferred.prd}`}`);
|
|
610
|
+
console.log('\nProject understanding (always offer a short interview, then propose a PRD):');
|
|
611
|
+
console.log('- What is this project and who is it for?');
|
|
612
|
+
console.log('- What core problem does it solve, and what is the primary goal/focus right now?');
|
|
613
|
+
console.log('- What does success look like (key outcomes or metrics)?');
|
|
614
|
+
console.log('- What are the key constraints or non-goals?');
|
|
615
|
+
if (inferred.mode === 'brownfield') console.log('- Which existing area should we focus on improving first?');
|
|
616
|
+
console.log(inferred.prd === 'none'
|
|
617
|
+
? '- Offer to create .vbkit-docs/PRD.md from .vbkit-docs/templates/PRD_TEMPLATE.md using the answers.'
|
|
618
|
+
: `- Offer to refresh ${inferred.prd} with the answers; do not overwrite without approval.`);
|
|
619
|
+
console.log(inferred.context === 'none'
|
|
620
|
+
? '- Offer to create .vbkit-docs/CONTEXT.md (glossary) from .vbkit-docs/templates/CONTEXT_TEMPLATE.md.'
|
|
621
|
+
: `- CONTEXT glossary found at ${inferred.context}; keep it current.`);
|
|
622
|
+
console.log('\nConvention review questions:');
|
|
623
|
+
for (const question of inferred.conventions.reviewQuestions) console.log(`- ${question}`);
|
|
624
|
+
console.log('\nProposed backbone.yml:\n');
|
|
625
|
+
console.log(out);
|
|
626
|
+
console.log('Apply this proposed backbone and convention rules? Reply yes, edit, or abort.');
|
|
627
|
+
console.log('To write it after approval, run: node .vbkit-scripts/init-backbone.mjs . --write --yes');
|
|
628
|
+
}
|