@aslomon/effectum 0.1.5 → 0.2.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/bin/effectum.js +50 -0
- package/bin/init.js +30 -0
- package/bin/install.js +531 -474
- package/bin/lib/config.js +55 -0
- package/bin/lib/constants.js +197 -0
- package/bin/lib/detect.js +98 -0
- package/bin/lib/stack-parser.js +56 -0
- package/bin/lib/template.js +108 -0
- package/bin/lib/ui.js +221 -0
- package/bin/lib/utils.js +56 -0
- package/bin/reconfigure.js +170 -0
- package/package.json +6 -3
- package/system/commands/setup.md +18 -6
package/bin/lib/ui.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared @clack/prompts helpers and display utilities.
|
|
3
|
+
*/
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const p = require("@clack/prompts");
|
|
7
|
+
const {
|
|
8
|
+
STACK_CHOICES,
|
|
9
|
+
LANGUAGE_CHOICES,
|
|
10
|
+
AUTONOMY_CHOICES,
|
|
11
|
+
MCP_SERVERS,
|
|
12
|
+
} = require("./constants");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Print the Effectum banner via clack intro.
|
|
16
|
+
*/
|
|
17
|
+
function printBanner() {
|
|
18
|
+
p.intro("EFFECTUM — Autonomous development system for Claude Code");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if the user cancelled a prompt (Ctrl+C).
|
|
23
|
+
* @param {*} value
|
|
24
|
+
*/
|
|
25
|
+
function handleCancel(value) {
|
|
26
|
+
if (p.isCancel(value)) {
|
|
27
|
+
p.cancel("Setup cancelled.");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ask for project name with a detected default.
|
|
34
|
+
* @param {string} detected
|
|
35
|
+
* @returns {Promise<string>}
|
|
36
|
+
*/
|
|
37
|
+
async function askProjectName(detected) {
|
|
38
|
+
const value = await p.text({
|
|
39
|
+
message: "Project name",
|
|
40
|
+
placeholder: detected,
|
|
41
|
+
initialValue: detected,
|
|
42
|
+
validate: (v) => {
|
|
43
|
+
if (!v.trim()) return "Project name is required";
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
handleCancel(value);
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Ask for tech stack selection.
|
|
52
|
+
* @param {string|null} detected - auto-detected stack key
|
|
53
|
+
* @returns {Promise<string>}
|
|
54
|
+
*/
|
|
55
|
+
async function askStack(detected) {
|
|
56
|
+
const value = await p.select({
|
|
57
|
+
message: "Tech stack",
|
|
58
|
+
options: STACK_CHOICES.map((c) => ({
|
|
59
|
+
value: c.value,
|
|
60
|
+
label: c.label,
|
|
61
|
+
hint: c.hint,
|
|
62
|
+
})),
|
|
63
|
+
initialValue: detected || "generic",
|
|
64
|
+
});
|
|
65
|
+
handleCancel(value);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Ask for communication language.
|
|
71
|
+
* @returns {Promise<{ language: string, customLanguage?: string }>}
|
|
72
|
+
*/
|
|
73
|
+
async function askLanguage() {
|
|
74
|
+
const value = await p.select({
|
|
75
|
+
message: "Communication language",
|
|
76
|
+
options: LANGUAGE_CHOICES.map((c) => ({
|
|
77
|
+
value: c.value,
|
|
78
|
+
label: c.label,
|
|
79
|
+
hint: c.hint,
|
|
80
|
+
})),
|
|
81
|
+
initialValue: "english",
|
|
82
|
+
});
|
|
83
|
+
handleCancel(value);
|
|
84
|
+
|
|
85
|
+
if (value === "custom") {
|
|
86
|
+
const custom = await p.text({
|
|
87
|
+
message: "Enter your language instruction",
|
|
88
|
+
placeholder: 'e.g. "Speak French with the user"',
|
|
89
|
+
validate: (v) => {
|
|
90
|
+
if (!v.trim()) return "Language instruction is required";
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
handleCancel(custom);
|
|
94
|
+
return { language: "custom", customLanguage: custom };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { language: value };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Ask for autonomy level.
|
|
102
|
+
* @returns {Promise<string>}
|
|
103
|
+
*/
|
|
104
|
+
async function askAutonomy() {
|
|
105
|
+
const value = await p.select({
|
|
106
|
+
message: "Autonomy level",
|
|
107
|
+
options: AUTONOMY_CHOICES.map((c) => ({
|
|
108
|
+
value: c.value,
|
|
109
|
+
label: c.label,
|
|
110
|
+
hint: c.hint,
|
|
111
|
+
})),
|
|
112
|
+
initialValue: "standard",
|
|
113
|
+
});
|
|
114
|
+
handleCancel(value);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Ask which MCP servers to install via multi-select.
|
|
120
|
+
* @returns {Promise<string[]>}
|
|
121
|
+
*/
|
|
122
|
+
async function askMcpServers() {
|
|
123
|
+
const value = await p.multiselect({
|
|
124
|
+
message: "MCP servers (space to toggle, enter to confirm)",
|
|
125
|
+
options: MCP_SERVERS.map((s) => ({
|
|
126
|
+
value: s.key,
|
|
127
|
+
label: s.label,
|
|
128
|
+
hint: s.desc,
|
|
129
|
+
})),
|
|
130
|
+
initialValues: MCP_SERVERS.map((s) => s.key),
|
|
131
|
+
required: false,
|
|
132
|
+
});
|
|
133
|
+
handleCancel(value);
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Ask whether to install Playwright browsers.
|
|
139
|
+
* @returns {Promise<boolean>}
|
|
140
|
+
*/
|
|
141
|
+
async function askPlaywright() {
|
|
142
|
+
const value = await p.confirm({
|
|
143
|
+
message: "Install Playwright browsers? (required for /e2e)",
|
|
144
|
+
initialValue: true,
|
|
145
|
+
});
|
|
146
|
+
handleCancel(value);
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Ask whether to create a new git branch.
|
|
152
|
+
* @returns {Promise<{ create: boolean, name?: string }>}
|
|
153
|
+
*/
|
|
154
|
+
async function askGitBranch() {
|
|
155
|
+
const create = await p.confirm({
|
|
156
|
+
message: "Create a new git branch for this setup?",
|
|
157
|
+
initialValue: false,
|
|
158
|
+
});
|
|
159
|
+
handleCancel(create);
|
|
160
|
+
|
|
161
|
+
if (!create) return { create: false };
|
|
162
|
+
|
|
163
|
+
const name = await p.text({
|
|
164
|
+
message: "Branch name",
|
|
165
|
+
initialValue: "effectum-setup",
|
|
166
|
+
placeholder: "effectum-setup",
|
|
167
|
+
});
|
|
168
|
+
handleCancel(name);
|
|
169
|
+
return { create: true, name };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Display a summary note.
|
|
174
|
+
* @param {object} config
|
|
175
|
+
* @param {string[]} files
|
|
176
|
+
*/
|
|
177
|
+
function showSummary(config, files) {
|
|
178
|
+
const lines = [
|
|
179
|
+
`Project: ${config.projectName}`,
|
|
180
|
+
`Stack: ${config.stack}`,
|
|
181
|
+
`Language: ${config.language}`,
|
|
182
|
+
`Autonomy: ${config.autonomyLevel}`,
|
|
183
|
+
`Pkg Mgr: ${config.packageManager}`,
|
|
184
|
+
`Formatter: ${config.formatter}`,
|
|
185
|
+
`MCP: ${(config.mcpServers || []).join(", ") || "none"}`,
|
|
186
|
+
"",
|
|
187
|
+
`Files created/updated:`,
|
|
188
|
+
...files.map((f) => ` ${f}`),
|
|
189
|
+
];
|
|
190
|
+
p.note(lines.join("\n"), "Configuration Summary");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Show the next-steps outro.
|
|
195
|
+
* @param {boolean} isGlobal
|
|
196
|
+
*/
|
|
197
|
+
function showOutro(isGlobal) {
|
|
198
|
+
if (isGlobal) {
|
|
199
|
+
p.outro(
|
|
200
|
+
"Effectum ready! In any project, run: npx @aslomon/effectum init",
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
p.outro(
|
|
204
|
+
"Effectum ready! Open Claude Code here and start building. Try /plan or /prd:new",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
printBanner,
|
|
211
|
+
handleCancel,
|
|
212
|
+
askProjectName,
|
|
213
|
+
askStack,
|
|
214
|
+
askLanguage,
|
|
215
|
+
askAutonomy,
|
|
216
|
+
askMcpServers,
|
|
217
|
+
askPlaywright,
|
|
218
|
+
askGitBranch,
|
|
219
|
+
showSummary,
|
|
220
|
+
showOutro,
|
|
221
|
+
};
|
package/bin/lib/utils.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility functions.
|
|
3
|
+
*/
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ensure a directory exists (recursive mkdir).
|
|
11
|
+
* @param {string} dir
|
|
12
|
+
*/
|
|
13
|
+
function ensureDir(dir) {
|
|
14
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Deep-merge two plain objects. Source wins on conflicts.
|
|
19
|
+
* Arrays are NOT merged — source replaces target.
|
|
20
|
+
* @param {object} target
|
|
21
|
+
* @param {object} source
|
|
22
|
+
* @returns {object}
|
|
23
|
+
*/
|
|
24
|
+
function deepMerge(target, source) {
|
|
25
|
+
const out = Object.assign({}, target);
|
|
26
|
+
for (const key of Object.keys(source)) {
|
|
27
|
+
if (
|
|
28
|
+
source[key] &&
|
|
29
|
+
typeof source[key] === "object" &&
|
|
30
|
+
!Array.isArray(source[key]) &&
|
|
31
|
+
out[key] &&
|
|
32
|
+
typeof out[key] === "object" &&
|
|
33
|
+
!Array.isArray(out[key])
|
|
34
|
+
) {
|
|
35
|
+
out[key] = deepMerge(out[key], source[key]);
|
|
36
|
+
} else {
|
|
37
|
+
out[key] = source[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find the Effectum repo root (parent of bin/).
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
function findRepoRoot() {
|
|
48
|
+
const binDir = path.dirname(require.main?.filename || __filename);
|
|
49
|
+
// If we're in bin/lib/, go up two levels; if in bin/, go up one
|
|
50
|
+
if (path.basename(binDir) === "lib") {
|
|
51
|
+
return path.resolve(binDir, "..", "..");
|
|
52
|
+
}
|
|
53
|
+
return path.resolve(binDir, "..");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { ensureDir, deepMerge, findRepoRoot };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reconfigure — re-apply settings from .effectum.json.
|
|
4
|
+
* Reads the saved config and regenerates CLAUDE.md, settings.json, guardrails.md.
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const p = require("@clack/prompts");
|
|
11
|
+
|
|
12
|
+
const { readConfig } = require("./lib/config");
|
|
13
|
+
const { loadStackPreset } = require("./lib/stack-parser");
|
|
14
|
+
const {
|
|
15
|
+
buildSubstitutionMap,
|
|
16
|
+
renderTemplate,
|
|
17
|
+
findTemplatePath,
|
|
18
|
+
findRemainingPlaceholders,
|
|
19
|
+
substituteAll,
|
|
20
|
+
} = require("./lib/template");
|
|
21
|
+
const { AUTONOMY_MAP, FORMATTER_MAP } = require("./lib/constants");
|
|
22
|
+
const { ensureDir, deepMerge, findRepoRoot } = require("./lib/utils");
|
|
23
|
+
|
|
24
|
+
// ─── Main ─────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
const dryRun = args.includes("--dry-run");
|
|
29
|
+
const targetDir = process.cwd();
|
|
30
|
+
const repoRoot = findRepoRoot();
|
|
31
|
+
|
|
32
|
+
p.intro("EFFECTUM — Reconfigure");
|
|
33
|
+
|
|
34
|
+
// Read existing config
|
|
35
|
+
const config = readConfig(targetDir);
|
|
36
|
+
if (!config) {
|
|
37
|
+
p.log.error(
|
|
38
|
+
"No .effectum.json found in this directory. Run `npx @aslomon/effectum` first.",
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
p.log.info(`Reconfiguring "${config.projectName}" (${config.stack})`);
|
|
44
|
+
|
|
45
|
+
if (dryRun) {
|
|
46
|
+
p.note(JSON.stringify(config, null, 2), "Current Configuration");
|
|
47
|
+
p.log.info("Dry run — files that would be regenerated:");
|
|
48
|
+
p.log.step(" CLAUDE.md");
|
|
49
|
+
p.log.step(" .claude/settings.json");
|
|
50
|
+
p.log.step(" .claude/guardrails.md");
|
|
51
|
+
p.outro("Dry run complete. No changes made.");
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const claudeDir = path.join(targetDir, ".claude");
|
|
56
|
+
|
|
57
|
+
// Load stack preset
|
|
58
|
+
const stackSections = loadStackPreset(config.stack, targetDir, repoRoot);
|
|
59
|
+
const vars = buildSubstitutionMap(config, stackSections);
|
|
60
|
+
|
|
61
|
+
const s = p.spinner();
|
|
62
|
+
s.start("Regenerating configuration files...");
|
|
63
|
+
|
|
64
|
+
// 1. CLAUDE.md
|
|
65
|
+
const claudeMdTmpl = findTemplatePath("CLAUDE.md.tmpl", targetDir, repoRoot);
|
|
66
|
+
const { content: claudeMdContent, remaining: claudeMdRemaining } =
|
|
67
|
+
renderTemplate(claudeMdTmpl, vars);
|
|
68
|
+
fs.writeFileSync(path.join(targetDir, "CLAUDE.md"), claudeMdContent, "utf8");
|
|
69
|
+
|
|
70
|
+
if (claudeMdRemaining.length > 0) {
|
|
71
|
+
p.log.warn(
|
|
72
|
+
`CLAUDE.md has remaining placeholders: ${claudeMdRemaining.join(", ")}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 2. settings.json
|
|
77
|
+
const settingsTmpl = findTemplatePath(
|
|
78
|
+
"settings.json.tmpl",
|
|
79
|
+
targetDir,
|
|
80
|
+
repoRoot,
|
|
81
|
+
);
|
|
82
|
+
let settingsObj;
|
|
83
|
+
try {
|
|
84
|
+
settingsObj = JSON.parse(fs.readFileSync(settingsTmpl, "utf8"));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
throw new Error(`Could not parse settings template: ${e.message}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const autonomy = AUTONOMY_MAP[config.autonomyLevel] || AUTONOMY_MAP.standard;
|
|
90
|
+
settingsObj.permissions = {
|
|
91
|
+
...settingsObj.permissions,
|
|
92
|
+
...autonomy.permissions,
|
|
93
|
+
defaultMode: autonomy.defaultMode,
|
|
94
|
+
deny: settingsObj.permissions?.deny || [],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const formatter = FORMATTER_MAP[config.stack] || FORMATTER_MAP.generic;
|
|
98
|
+
if (settingsObj.hooks?.PostToolUse) {
|
|
99
|
+
for (const group of settingsObj.hooks.PostToolUse) {
|
|
100
|
+
if (group.matcher === "Edit|Write") {
|
|
101
|
+
for (const hook of group.hooks) {
|
|
102
|
+
if (
|
|
103
|
+
hook.command &&
|
|
104
|
+
hook.command.includes("formatter-not-configured")
|
|
105
|
+
) {
|
|
106
|
+
if (formatter.command === "echo no-formatter-configured") {
|
|
107
|
+
hook.command = "echo no-formatter-configured";
|
|
108
|
+
} else {
|
|
109
|
+
hook.command = `bash -c 'INPUT=$(cat); FILE=$(echo "$INPUT" | jq -r ".tool_input.file_path // empty"); if [[ "$FILE" =~ \\.(${formatter.glob})$ ]]; then ${formatter.command} "$FILE" 2>/dev/null; fi; exit 0'`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const settingsDest = path.join(claudeDir, "settings.json");
|
|
118
|
+
let existing = {};
|
|
119
|
+
if (fs.existsSync(settingsDest)) {
|
|
120
|
+
try {
|
|
121
|
+
existing = JSON.parse(fs.readFileSync(settingsDest, "utf8"));
|
|
122
|
+
} catch (_) {}
|
|
123
|
+
}
|
|
124
|
+
const merged = deepMerge(existing, settingsObj);
|
|
125
|
+
ensureDir(path.dirname(settingsDest));
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
settingsDest,
|
|
128
|
+
JSON.stringify(merged, null, 2) + "\n",
|
|
129
|
+
"utf8",
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// 3. guardrails.md
|
|
133
|
+
const guardrailsTmpl = findTemplatePath(
|
|
134
|
+
"guardrails.md.tmpl",
|
|
135
|
+
targetDir,
|
|
136
|
+
repoRoot,
|
|
137
|
+
);
|
|
138
|
+
const guardrailsRaw = fs.readFileSync(guardrailsTmpl, "utf8");
|
|
139
|
+
let guardrailsContent = guardrailsRaw;
|
|
140
|
+
|
|
141
|
+
if (stackSections.STACK_SPECIFIC_GUARDRAILS) {
|
|
142
|
+
guardrailsContent = guardrailsContent.replace(
|
|
143
|
+
/No stack-specific guardrails configured yet\. Run \/setup to configure for your stack\./,
|
|
144
|
+
stackSections.STACK_SPECIFIC_GUARDRAILS,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (stackSections.TOOL_SPECIFIC_GUARDRAILS) {
|
|
148
|
+
guardrailsContent = guardrailsContent.replace(
|
|
149
|
+
/No tool-specific guardrails configured yet\. Run \/setup to configure\./,
|
|
150
|
+
stackSections.TOOL_SPECIFIC_GUARDRAILS,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const guardrailsDest = path.join(claudeDir, "guardrails.md");
|
|
155
|
+
ensureDir(path.dirname(guardrailsDest));
|
|
156
|
+
fs.writeFileSync(guardrailsDest, guardrailsContent, "utf8");
|
|
157
|
+
|
|
158
|
+
s.stop("Configuration files regenerated");
|
|
159
|
+
|
|
160
|
+
p.log.success("CLAUDE.md — updated");
|
|
161
|
+
p.log.success(".claude/settings.json — updated");
|
|
162
|
+
p.log.success(".claude/guardrails.md — updated");
|
|
163
|
+
|
|
164
|
+
p.outro("Reconfiguration complete!");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch((err) => {
|
|
168
|
+
p.log.error(`Fatal error: ${err.message}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aslomon/effectum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Autonomous development system for Claude Code — describe what you want, get production-ready code",
|
|
5
5
|
"bin": {
|
|
6
|
-
"effectum": "bin/
|
|
6
|
+
"effectum": "bin/effectum.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
@@ -25,5 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"engines": {
|
|
27
27
|
"node": ">=18.0.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "^1.1.0"
|
|
28
31
|
}
|
|
29
|
-
}
|
|
32
|
+
}
|
package/system/commands/setup.md
CHANGED
|
@@ -12,7 +12,7 @@ Validate:
|
|
|
12
12
|
- The path exists. If not, ask: "This directory does not exist. Should I create it?"
|
|
13
13
|
- Check for an existing `.claude/` directory at the target path.
|
|
14
14
|
- If found, ask: **"A .claude/ directory already exists. Overwrite, merge, or abort?"**
|
|
15
|
-
- **Overwrite**:
|
|
15
|
+
- **Overwrite**: Replace CONFIGURATION files only (`CLAUDE.md`, `.claude/settings.json`, `.claude/guardrails.md`) but **keep all commands** already in `.claude/commands/`.
|
|
16
16
|
- **Merge**: Keep existing files, only add missing ones. Warn about conflicts.
|
|
17
17
|
- **Abort**: Stop the installation.
|
|
18
18
|
|
|
@@ -57,12 +57,24 @@ Options: English, German (du/informal), or specify another.
|
|
|
57
57
|
|
|
58
58
|
## Step 3: Read Templates and Stack Preset
|
|
59
59
|
|
|
60
|
-
Read
|
|
60
|
+
Read the template files — check these locations **in order** and use the first one found:
|
|
61
61
|
|
|
62
|
-
1.
|
|
63
|
-
2.
|
|
64
|
-
3. `system/templates
|
|
65
|
-
|
|
62
|
+
1. `.effectum/templates/` — local install (created by `npx @aslomon/effectum --local`)
|
|
63
|
+
2. `~/.effectum/templates/` — global install (created by `npx @aslomon/effectum --global`)
|
|
64
|
+
3. `system/templates/` — development (running directly from the repo)
|
|
65
|
+
|
|
66
|
+
Templates to read:
|
|
67
|
+
- `CLAUDE.md.tmpl` — Main configuration template
|
|
68
|
+
- `settings.json.tmpl` — Settings template
|
|
69
|
+
- `guardrails.md.tmpl` — Guardrails template
|
|
70
|
+
|
|
71
|
+
Read the stack preset file — check these locations **in order** and use the first one found:
|
|
72
|
+
|
|
73
|
+
1. `.effectum/stacks/{selected-stack}.md`
|
|
74
|
+
2. `~/.effectum/stacks/{selected-stack}.md`
|
|
75
|
+
3. `system/stacks/{selected-stack}.md`
|
|
76
|
+
|
|
77
|
+
Available stacks: `nextjs-supabase.md`, `python-fastapi.md`, `swift-ios.md`, `generic.md`
|
|
66
78
|
|
|
67
79
|
The stack preset file contains values for these placeholders:
|
|
68
80
|
|