kracked-core 1.0.0 → 1.1.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/README.md +4 -1
- package/package.json +1 -1
- package/src/detect.mjs +33 -0
- package/src/prompt.mjs +235 -0
- package/src/scaffold.mjs +8 -2
- package/src/wizard.mjs +94 -82
- package/templates/loaders/AGENTS.md +1 -0
- package/templates/skills/kracked-boot/SKILL.md +12 -5
- package/templates/skills/kracked-identity/SKILL.md +70 -0
package/README.md
CHANGED
|
@@ -53,12 +53,15 @@ The wizard asks a few questions — name your agent, is this a new or existing p
|
|
|
53
53
|
|
|
54
54
|
## Use
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Five commands. Three of them are the whole daily loop.
|
|
57
57
|
|
|
58
58
|
```
|
|
59
59
|
/kracked-boot Start of session — agent loads its memory and tells you where you left off
|
|
60
60
|
/kracked-sdd Build something — idea → spec → docs → build → review
|
|
61
61
|
/kracked-wrap End of session — agent writes down what it learned
|
|
62
|
+
|
|
63
|
+
/kracked-identity Change your agent's name, tone, or how you like to work
|
|
64
|
+
/kracked-explain "What is all this?" — walks you through what got installed
|
|
62
65
|
```
|
|
63
66
|
|
|
64
67
|
> **`/kracked-wrap` is not optional.** Boot without wrap is a memory system that just forgets more slowly. The wrap is where memory actually gets written.
|
package/package.json
CHANGED
package/src/detect.mjs
CHANGED
|
@@ -15,6 +15,39 @@ export function globalMemoryDir() {
|
|
|
15
15
|
return path.join(os.homedir(), '.kracked');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Find agent setups already governing this directory, so we never silently
|
|
20
|
+
* overwrite someone's existing system. A CLAUDE.md/AGENTS.md that kracked-core
|
|
21
|
+
* didn't write is another system's loader — installing over it would replace
|
|
22
|
+
* that agent's identity, which is exactly the failure this check prevents.
|
|
23
|
+
*
|
|
24
|
+
* Returns [{ file, path, scope, ours }] — `ours` marks files kracked-core
|
|
25
|
+
* itself wrote (safe to refresh), everything else is someone else's.
|
|
26
|
+
*/
|
|
27
|
+
export function detectExistingAgentSetup(projectDir) {
|
|
28
|
+
const found = [];
|
|
29
|
+
|
|
30
|
+
const inspect = (filePath, label, scope) => {
|
|
31
|
+
if (!fs.existsSync(filePath)) return;
|
|
32
|
+
let body = '';
|
|
33
|
+
try {
|
|
34
|
+
body = fs.readFileSync(filePath, 'utf8');
|
|
35
|
+
} catch {
|
|
36
|
+
return; // unreadable is not our problem to solve here
|
|
37
|
+
}
|
|
38
|
+
// Our own loaders always reference kracked-core by name.
|
|
39
|
+
const ours = /kracked-core|kracked\.md|\.kracked\//.test(body);
|
|
40
|
+
found.push({ file: label, path: filePath, scope, ours });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
inspect(path.join(projectDir, 'CLAUDE.md'), 'CLAUDE.md', 'project');
|
|
44
|
+
inspect(path.join(projectDir, 'AGENTS.md'), 'AGENTS.md', 'project');
|
|
45
|
+
inspect(path.join(projectDir, '.agents', 'rules', 'kracked.md'), '.agents/rules/kracked.md', 'project');
|
|
46
|
+
inspect(path.join(os.homedir(), '.claude', 'CLAUDE.md'), '~/.claude/CLAUDE.md', 'global');
|
|
47
|
+
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* Is the target dir an existing codebase, empty, or new (doesn't exist yet)?
|
|
20
53
|
* Returns one of: "new" (doesn't exist), "empty" (exists, no entries),
|
package/src/prompt.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Keyboard-driven prompts: arrow-key single select and spacebar checkboxes.
|
|
2
|
+
// Zero dependencies — raw TTY keypress handling, so the installer never fails
|
|
3
|
+
// in a classroom because of a missing package.
|
|
4
|
+
|
|
5
|
+
import { stdin, stdout } from 'node:process';
|
|
6
|
+
|
|
7
|
+
const ESC = '';
|
|
8
|
+
const KEY = {
|
|
9
|
+
up: `${ESC}[A`,
|
|
10
|
+
down: `${ESC}[B`,
|
|
11
|
+
ctrlC: '',
|
|
12
|
+
ctrlD: '',
|
|
13
|
+
enter: '\r',
|
|
14
|
+
newline: '\n',
|
|
15
|
+
space: ' ',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const c = {
|
|
19
|
+
dim: (s) => `[2m${s}[0m`,
|
|
20
|
+
cyan: (s) => `[36m${s}[0m`,
|
|
21
|
+
green: (s) => `[32m${s}[0m`,
|
|
22
|
+
bold: (s) => `[1m${s}[0m`,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Hide/show the cursor so the redrawing menu doesn't flicker a caret around. */
|
|
26
|
+
function setCursor(visible) {
|
|
27
|
+
stdout.write(visible ? `${ESC}[?25h` : `${ESC}[?25l`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Move up n lines and clear from there down — used to redraw the menu in place. */
|
|
31
|
+
function clearLines(n) {
|
|
32
|
+
if (n > 0) stdout.write(`${ESC}[${n}A`);
|
|
33
|
+
stdout.write(`${ESC}[0J`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read single keypresses until `onKey` signals completion.
|
|
38
|
+
* onKey(key) returns { done: true, value } to finish, or falsy to keep listening.
|
|
39
|
+
*/
|
|
40
|
+
function readKeys(onKey) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const wasRaw = stdin.isRaw;
|
|
43
|
+
stdin.setRawMode(true);
|
|
44
|
+
stdin.resume();
|
|
45
|
+
stdin.setEncoding('utf8');
|
|
46
|
+
|
|
47
|
+
const cleanup = () => {
|
|
48
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
49
|
+
stdin.pause();
|
|
50
|
+
stdin.removeListener('data', handler);
|
|
51
|
+
setCursor(true);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function handler(key) {
|
|
55
|
+
// Ctrl+C / Ctrl+D must always escape, whatever the prompt is doing.
|
|
56
|
+
if (key === KEY.ctrlC || key === KEY.ctrlD) {
|
|
57
|
+
cleanup();
|
|
58
|
+
stdout.write('\n\nCancelled — nothing was written.\n');
|
|
59
|
+
process.exit(130);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let result;
|
|
63
|
+
try {
|
|
64
|
+
result = onKey(key);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
cleanup();
|
|
67
|
+
reject(err);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (result && result.done) {
|
|
72
|
+
cleanup();
|
|
73
|
+
resolve(result.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
stdin.on('data', handler);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Arrow-key single select. `choices` is [{ label, value, hint }].
|
|
83
|
+
* Returns the chosen `value`.
|
|
84
|
+
*/
|
|
85
|
+
export async function select(question, choices, defaultIndex = 0) {
|
|
86
|
+
let index = defaultIndex;
|
|
87
|
+
let drawn = 0;
|
|
88
|
+
|
|
89
|
+
const render = () => {
|
|
90
|
+
clearLines(drawn);
|
|
91
|
+
let out = `${c.bold('?')} ${question} ${c.dim('↑↓ to move, Enter to pick')}\n`;
|
|
92
|
+
for (let i = 0; i < choices.length; i++) {
|
|
93
|
+
const active = i === index;
|
|
94
|
+
const pointer = active ? c.cyan('❯') : ' ';
|
|
95
|
+
const label = active ? c.cyan(choices[i].label) : choices[i].label;
|
|
96
|
+
const hint = choices[i].hint ? ` ${c.dim(choices[i].hint)}` : '';
|
|
97
|
+
out += `${pointer} ${label}${hint}\n`;
|
|
98
|
+
}
|
|
99
|
+
stdout.write(out);
|
|
100
|
+
drawn = choices.length + 1;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
setCursor(false);
|
|
104
|
+
render();
|
|
105
|
+
|
|
106
|
+
const value = await readKeys((key) => {
|
|
107
|
+
if (key === KEY.up) {
|
|
108
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
109
|
+
render();
|
|
110
|
+
} else if (key === KEY.down) {
|
|
111
|
+
index = (index + 1) % choices.length;
|
|
112
|
+
render();
|
|
113
|
+
} else if (key === KEY.enter || key === KEY.newline) {
|
|
114
|
+
return { done: true, value: choices[index].value };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Collapse the menu to a single answered line.
|
|
120
|
+
clearLines(drawn);
|
|
121
|
+
const chosen = choices.find((ch) => ch.value === value);
|
|
122
|
+
stdout.write(`${c.green('✔')} ${question} ${c.cyan(chosen.label)}\n`);
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Spacebar checkbox multi-select. `choices` is [{ label, value, hint, checked }].
|
|
128
|
+
* Returns an array of checked `value`s. Enter with nothing checked is refused,
|
|
129
|
+
* since every multi-select in this wizard needs at least one answer.
|
|
130
|
+
*/
|
|
131
|
+
export async function checkbox(question, choices) {
|
|
132
|
+
let index = 0;
|
|
133
|
+
let drawn = 0;
|
|
134
|
+
const state = choices.map((ch) => Boolean(ch.checked));
|
|
135
|
+
let warning = '';
|
|
136
|
+
|
|
137
|
+
const render = () => {
|
|
138
|
+
clearLines(drawn);
|
|
139
|
+
let out = `${c.bold('?')} ${question} ${c.dim('↑↓ move, space to toggle, Enter to confirm')}\n`;
|
|
140
|
+
for (let i = 0; i < choices.length; i++) {
|
|
141
|
+
const active = i === index;
|
|
142
|
+
const pointer = active ? c.cyan('❯') : ' ';
|
|
143
|
+
const box = state[i] ? c.green('◉') : '◯';
|
|
144
|
+
const label = active ? c.cyan(choices[i].label) : choices[i].label;
|
|
145
|
+
const hint = choices[i].hint ? ` ${c.dim(choices[i].hint)}` : '';
|
|
146
|
+
out += `${pointer} ${box} ${label}${hint}\n`;
|
|
147
|
+
}
|
|
148
|
+
if (warning) out += `${c.dim(warning)}\n`;
|
|
149
|
+
stdout.write(out);
|
|
150
|
+
drawn = choices.length + 1 + (warning ? 1 : 0);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
setCursor(false);
|
|
154
|
+
render();
|
|
155
|
+
|
|
156
|
+
const values = await readKeys((key) => {
|
|
157
|
+
if (key === KEY.up) {
|
|
158
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
159
|
+
warning = '';
|
|
160
|
+
render();
|
|
161
|
+
} else if (key === KEY.down) {
|
|
162
|
+
index = (index + 1) % choices.length;
|
|
163
|
+
warning = '';
|
|
164
|
+
render();
|
|
165
|
+
} else if (key === KEY.space) {
|
|
166
|
+
state[index] = !state[index];
|
|
167
|
+
warning = '';
|
|
168
|
+
render();
|
|
169
|
+
} else if (key === KEY.enter || key === KEY.newline) {
|
|
170
|
+
const picked = choices.filter((_, i) => state[i]).map((ch) => ch.value);
|
|
171
|
+
if (picked.length === 0) {
|
|
172
|
+
warning = ' Pick at least one (space to toggle).';
|
|
173
|
+
render();
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return { done: true, value: picked };
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
clearLines(drawn);
|
|
182
|
+
const labels = choices.filter((_, i) => state[i]).map((ch) => ch.label).join(', ');
|
|
183
|
+
stdout.write(`${c.green('✔')} ${question} ${c.cyan(labels)}\n`);
|
|
184
|
+
return values;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Free-text input with a default. Implemented on the same raw-mode reader as
|
|
189
|
+
* the menus: readline and raw mode cannot share stdin — if both are listening,
|
|
190
|
+
* keystrokes go to the wrong reader and the wizard hangs. One owner, always.
|
|
191
|
+
*/
|
|
192
|
+
export async function input(question, defaultValue = '') {
|
|
193
|
+
let buffer = '';
|
|
194
|
+
const hint = defaultValue ? c.dim(` [${defaultValue}]`) : '';
|
|
195
|
+
|
|
196
|
+
const render = () => {
|
|
197
|
+
stdout.write(`\r${ESC}[0K${c.bold('?')} ${question}${hint} ${buffer}`);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
setCursor(true);
|
|
201
|
+
render();
|
|
202
|
+
|
|
203
|
+
const value = await readKeys((key) => {
|
|
204
|
+
if (key === KEY.enter || key === KEY.newline) {
|
|
205
|
+
return { done: true, value: buffer.trim() || defaultValue };
|
|
206
|
+
}
|
|
207
|
+
// Backspace (DEL or BS)
|
|
208
|
+
if (key === '' || key === '\b') {
|
|
209
|
+
buffer = buffer.slice(0, -1);
|
|
210
|
+
render();
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
// Ignore escape sequences (arrows etc.) and other control chars.
|
|
214
|
+
if (key.startsWith(ESC) || key < ' ') return null;
|
|
215
|
+
|
|
216
|
+
buffer += key;
|
|
217
|
+
render();
|
|
218
|
+
return null;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
stdout.write(`\r${ESC}[0K${c.green('✔')} ${question} ${c.cyan(value)}\n`);
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Yes/no as an arrow menu, so every prompt in the wizard behaves the same way. */
|
|
226
|
+
export async function confirm(question, defaultYes = true) {
|
|
227
|
+
return select(
|
|
228
|
+
question,
|
|
229
|
+
[
|
|
230
|
+
{ label: 'Yes', value: true },
|
|
231
|
+
{ label: 'No', value: false },
|
|
232
|
+
],
|
|
233
|
+
defaultYes ? 0 : 1
|
|
234
|
+
);
|
|
235
|
+
}
|
package/src/scaffold.mjs
CHANGED
|
@@ -9,7 +9,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const TEMPLATES_ROOT = path.join(__dirname, '..', 'templates');
|
|
11
11
|
|
|
12
|
-
const SKILL_NAMES = [
|
|
12
|
+
const SKILL_NAMES = [
|
|
13
|
+
'kracked-boot',
|
|
14
|
+
'kracked-sdd',
|
|
15
|
+
'kracked-wrap',
|
|
16
|
+
'kracked-explain',
|
|
17
|
+
'kracked-identity',
|
|
18
|
+
];
|
|
13
19
|
|
|
14
20
|
/** Read a template file, throwing a clear error if Track A hasn't shipped it yet. */
|
|
15
21
|
function readTemplate(relPath) {
|
|
@@ -173,7 +179,7 @@ export async function writeLoaders({ projectDir, tokens, ask, report }) {
|
|
|
173
179
|
}
|
|
174
180
|
|
|
175
181
|
/**
|
|
176
|
-
* Write
|
|
182
|
+
* Write every skill to BOTH .claude/skills/<name>/SKILL.md and
|
|
177
183
|
* .agents/skills/<name>/SKILL.md — but only for the harnesses selected.
|
|
178
184
|
* `editors` is an array that may contain 'antigravity' and/or 'claude'.
|
|
179
185
|
*/
|
package/src/wizard.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Interactive prompt flow for `npx kracked-core init`.
|
|
2
2
|
// Follows the wizard flow in CONTRACT.md exactly — question order matters.
|
|
3
3
|
|
|
4
|
-
import readline from 'node:readline/promises';
|
|
5
4
|
import { stdin, stdout } from 'node:process';
|
|
6
5
|
import fs from 'node:fs';
|
|
7
6
|
import path from 'node:path';
|
|
@@ -13,7 +12,9 @@ import {
|
|
|
13
12
|
classifyProjectDir,
|
|
14
13
|
scanExistingProject,
|
|
15
14
|
stackSummaryLine,
|
|
15
|
+
detectExistingAgentSetup,
|
|
16
16
|
} from './detect.mjs';
|
|
17
|
+
import { select, checkbox, confirm, input } from './prompt.mjs';
|
|
17
18
|
import {
|
|
18
19
|
writeGlobalMemory,
|
|
19
20
|
registerProject,
|
|
@@ -68,87 +69,91 @@ export async function runInit() {
|
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let interrupted = false;
|
|
72
|
+
// Ctrl+C / Ctrl+D are handled inside prompt.mjs, which owns stdin in raw
|
|
73
|
+
// mode. Keep a SIGINT guard for interrupts arriving between prompts.
|
|
74
74
|
const onSigint = () => {
|
|
75
|
-
interrupted = true;
|
|
76
75
|
stdout.write('\n\nCancelled. Nothing after this point was written.\n');
|
|
77
|
-
rl.close();
|
|
78
76
|
process.exit(130);
|
|
79
77
|
};
|
|
80
78
|
process.on('SIGINT', onSigint);
|
|
81
79
|
|
|
82
80
|
try {
|
|
83
|
-
await wizardFlow(
|
|
84
|
-
} catch (err) {
|
|
85
|
-
// Ctrl+D (EOF) mid-question surfaces as readline's AbortError. Treat it
|
|
86
|
-
// the same as Ctrl+C — a clean cancel, not a crash with a scary stack.
|
|
87
|
-
if (err.code === 'ABORT_ERR' || /ctrl\+d/i.test(err.message || '')) {
|
|
88
|
-
stdout.write('\n\nCancelled. Nothing after this point was written.\n');
|
|
89
|
-
process.exitCode = 130;
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
throw err;
|
|
81
|
+
await wizardFlow();
|
|
93
82
|
} finally {
|
|
94
83
|
process.off('SIGINT', onSigint);
|
|
95
|
-
|
|
84
|
+
stdin.pause();
|
|
96
85
|
}
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
/**
|
|
100
|
-
async function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
/** Prompt for how to resolve a file-already-exists conflict. */
|
|
89
|
+
async function askConflict(destPath) {
|
|
90
|
+
stdout.write(`\n Already exists: ${destPath}\n`);
|
|
91
|
+
return select(
|
|
92
|
+
' What should we do?',
|
|
93
|
+
[
|
|
94
|
+
{ label: 'Skip', value: 'skip', hint: 'keep the existing file' },
|
|
95
|
+
{ label: 'Overwrite', value: 'overwrite', hint: 'replace it' },
|
|
96
|
+
{ label: 'Write alongside', value: 'alongside', hint: 'save as .kracked-new' },
|
|
97
|
+
],
|
|
98
|
+
0
|
|
99
|
+
);
|
|
104
100
|
}
|
|
105
101
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Warn before writing into a directory another agent system already governs.
|
|
104
|
+
* A CLAUDE.md we didn't write belongs to someone else's setup — overwriting it
|
|
105
|
+
* silently replaces that agent's identity. Ask first, every time.
|
|
106
|
+
*/
|
|
107
|
+
async function warnOnExistingSetup(projectDir) {
|
|
108
|
+
const found = detectExistingAgentSetup(projectDir).filter((f) => !f.ours);
|
|
109
|
+
if (found.length === 0) return true;
|
|
110
|
+
|
|
111
|
+
stdout.write('\n Heads up — this looks like it already has an agent setup:\n');
|
|
112
|
+
for (const f of found) {
|
|
113
|
+
const where = f.scope === 'global' ? 'global' : 'this project';
|
|
114
|
+
stdout.write(` • ${f.file} ${c_dim(`(${where})`)}\n`);
|
|
115
|
+
}
|
|
116
|
+
stdout.write(
|
|
117
|
+
'\n Installing here would overwrite those loaders and change who your\n' +
|
|
118
|
+
' agent is in this directory. Existing files are never replaced without\n' +
|
|
119
|
+
' asking, but it is easy to end up with two systems giving different\n' +
|
|
120
|
+
' instructions.\n\n'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return confirm('Continue installing here?', false);
|
|
112
124
|
}
|
|
113
125
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const answer = (
|
|
118
|
-
await rl.question(' skip / overwrite / write alongside as .kracked-new? [skip] ')
|
|
119
|
-
).trim().toLowerCase();
|
|
120
|
-
|
|
121
|
-
if (answer === 'o' || answer === 'overwrite') return 'overwrite';
|
|
122
|
-
if (answer === 'a' || answer === 'alongside' || answer === 'write alongside') return 'alongside';
|
|
123
|
-
return 'skip';
|
|
126
|
+
/** Minimal dim helper for wizard-level notices. */
|
|
127
|
+
function c_dim(s) {
|
|
128
|
+
return `[2m${s}[0m`;
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
async function wizardFlow(
|
|
131
|
+
async function wizardFlow() {
|
|
127
132
|
const created = [];
|
|
128
133
|
const reporter = (entry) => created.push(entry);
|
|
129
|
-
const conflictAsk = (destPath) => askConflict(
|
|
134
|
+
const conflictAsk = (destPath) => askConflict(destPath);
|
|
130
135
|
|
|
131
136
|
stdout.write('kracked-core — set up memory for your AI coding agent\n\n');
|
|
132
137
|
|
|
133
138
|
// 1. Agent name
|
|
134
|
-
const agentName = (await
|
|
139
|
+
const agentName = (await input('What should we call your agent?', 'KC')) || 'KC';
|
|
135
140
|
|
|
136
141
|
// 2. User name
|
|
137
142
|
const systemUser = os.userInfo().username || 'you';
|
|
138
|
-
const userName = (await
|
|
143
|
+
const userName = (await input('Your name?', systemUser)) || systemUser;
|
|
139
144
|
|
|
140
|
-
// 3. Global memory
|
|
145
|
+
// 3. Global memory — always installed. It holds identity, preferences and
|
|
146
|
+
// cross-project lessons, and boot depends on it; making it optional only
|
|
147
|
+
// created a way to end up with a half-installed system that can't boot.
|
|
148
|
+
// Existing files are still never overwritten without asking.
|
|
141
149
|
const hasGlobal = globalMemoryExists();
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
} else {
|
|
146
|
-
const reuse = await askYesNo(rl, 'Global memory found. Reuse it?', true);
|
|
147
|
-
setUpGlobal = !reuse; // if not reusing, we still (re)write it below
|
|
150
|
+
const setUpGlobal = true;
|
|
151
|
+
if (hasGlobal) {
|
|
152
|
+
stdout.write(`\n Global memory found at ${globalMemoryDir()} — reusing it.\n`);
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
// 4. Project setup
|
|
151
|
-
const setUpProject = await
|
|
156
|
+
const setUpProject = await confirm('Set up this project?', true);
|
|
152
157
|
|
|
153
158
|
let projectDir = process.cwd();
|
|
154
159
|
let projectMode = null; // 'new' | 'existing'
|
|
@@ -156,24 +161,29 @@ async function wizardFlow(rl) {
|
|
|
156
161
|
let projectSummary = null;
|
|
157
162
|
|
|
158
163
|
if (setUpProject) {
|
|
159
|
-
const pathAnswer = await
|
|
164
|
+
const pathAnswer = await input('Project directory?', '.');
|
|
160
165
|
const expanded = expandTilde(pathAnswer);
|
|
161
166
|
projectDir = path.resolve(expanded === '' ? '.' : expanded);
|
|
162
167
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
} else {
|
|
171
|
-
const modeAnswer = (
|
|
172
|
-
await ask(rl, 'New project or existing codebase? (new/existing)', 'new')
|
|
173
|
-
).toLowerCase();
|
|
174
|
-
projectMode = modeAnswer.startsWith('e') ? 'existing' : 'new';
|
|
168
|
+
// Another agent system already here? Ask before touching its loaders.
|
|
169
|
+
if (fs.existsSync(projectDir)) {
|
|
170
|
+
const proceed = await warnOnExistingSetup(projectDir);
|
|
171
|
+
if (!proceed) {
|
|
172
|
+
stdout.write('\nStopped — nothing was written to this project.\n');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
const classification = classifyProjectDir(projectDir);
|
|
178
|
+
projectMode = await select(
|
|
179
|
+
'Is this a new project or an existing codebase?',
|
|
180
|
+
[
|
|
181
|
+
{ label: 'Existing codebase', value: 'existing', hint: 'scan it and draft the memory' },
|
|
182
|
+
{ label: 'New project', value: 'new', hint: 'start with a blank scaffold' },
|
|
183
|
+
],
|
|
184
|
+
classification === 'existing' ? 0 : 1
|
|
185
|
+
);
|
|
186
|
+
|
|
177
187
|
if (projectMode === 'existing') {
|
|
178
188
|
if (!fs.existsSync(projectDir)) {
|
|
179
189
|
stdout.write(`\n ${projectDir} doesn't exist yet — nothing to scan. Treating as new.\n`);
|
|
@@ -192,9 +202,9 @@ async function wizardFlow(rl) {
|
|
|
192
202
|
}
|
|
193
203
|
stdout.write('\n');
|
|
194
204
|
|
|
195
|
-
const confirmed = await
|
|
205
|
+
const confirmed = await confirm('Does this look right?', true);
|
|
196
206
|
if (!confirmed) {
|
|
197
|
-
const manualStack = await
|
|
207
|
+
const manualStack = await input('Describe the stack yourself:', stackLine);
|
|
198
208
|
stackLine = manualStack;
|
|
199
209
|
}
|
|
200
210
|
}
|
|
@@ -208,23 +218,25 @@ async function wizardFlow(rl) {
|
|
|
208
218
|
}
|
|
209
219
|
}
|
|
210
220
|
|
|
211
|
-
// 5. Editors
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
// 5. Editors — a checkbox, so anyone already running another agent system in
|
|
222
|
+
// one harness can install for the other and leave that one alone.
|
|
223
|
+
const existing = setUpProject ? detectExistingAgentSetup(projectDir) : [];
|
|
224
|
+
const claudeTaken = existing.some((f) => f.file === 'CLAUDE.md' && !f.ours);
|
|
225
|
+
|
|
226
|
+
const editors = await checkbox('Which editor(s) do you use?', [
|
|
227
|
+
{
|
|
228
|
+
label: 'Antigravity',
|
|
229
|
+
value: 'antigravity',
|
|
230
|
+
checked: true,
|
|
231
|
+
hint: 'writes .agents/',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
label: 'Claude Code',
|
|
235
|
+
value: 'claude',
|
|
236
|
+
checked: !claudeTaken,
|
|
237
|
+
hint: claudeTaken ? 'CLAUDE.md already in use here' : 'writes CLAUDE.md + .claude/',
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
228
240
|
|
|
229
241
|
// 6. Write files
|
|
230
242
|
const projectName = projectSummary?.name || (setUpProject ? path.basename(projectDir) : 'unnamed-project');
|
|
@@ -50,6 +50,7 @@ the signup flow, next step is wiring the email step. Ready."
|
|
|
50
50
|
| `/kracked-sdd` | Runs the SDD flow: idea → spec → docs → build → review |
|
|
51
51
|
| `/kracked-wrap` | Writes lessons + updates session state, closes the session cleanly |
|
|
52
52
|
| `/kracked-explain` | Walks {{USER_NAME}} through what kracked-core installed and why |
|
|
53
|
+
| `/kracked-identity` | Change who you are, your tone, or how {{USER_NAME}} likes to work |
|
|
53
54
|
|
|
54
55
|
## Writing memory (wrap)
|
|
55
56
|
|
|
@@ -5,14 +5,21 @@ description: Use at the start of every session, before any other work, to load g
|
|
|
5
5
|
|
|
6
6
|
Run this before doing anything else in the session. Do not skip steps or merge reads together.
|
|
7
7
|
|
|
8
|
-
## 1. Check
|
|
8
|
+
## 1. Check what memory exists
|
|
9
9
|
|
|
10
|
-
Look for `~/.kracked/identity.md
|
|
11
|
-
|
|
10
|
+
Look for `~/.kracked/identity.md` (global) and `.kracked/` in the current directory (project).
|
|
11
|
+
At least one must exist. Boot works with either layer — never halt just because one is missing.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- **Both present** — normal boot. Continue to step 2.
|
|
14
|
+
- **Project only** (no `~/.kracked/`) — boot from project memory alone. Behave as a careful,
|
|
15
|
+
direct coding agent, and say in your orientation line that global memory isn't set up, so
|
|
16
|
+
cross-project lessons and preferences aren't loaded. Mention `npx kracked-core init` once.
|
|
17
|
+
Do NOT invent preferences or lessons you haven't read.
|
|
18
|
+
- **Global only** (no `.kracked/` here) — see step 3.
|
|
19
|
+
- **Neither** — nothing is installed. Say so and point at `npx kracked-core init`. This is the
|
|
20
|
+
only case where there's nothing to load.
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
Never fabricate identity, preferences, project state, or lessons. Missing is missing — say it.
|
|
16
23
|
|
|
17
24
|
## 2. Load global memory, in this exact order
|
|
18
25
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kracked-identity
|
|
3
|
+
description: Use when the user wants to change their agent's name, personality, or communication style, or to record how they like to work — anything about who the agent is or what it should remember about them.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Help the user shape who you are and how you work with them. Two files, two purposes — keep them
|
|
7
|
+
straight, because mixing them up is the most common mistake.
|
|
8
|
+
|
|
9
|
+
| File | Holds | Example |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `~/.kracked/identity.md` | Who the AGENT is | name, tone, how direct to be |
|
|
12
|
+
| `~/.kracked/preferences.md` | How the USER works | their stack, their conventions, what to never do |
|
|
13
|
+
|
|
14
|
+
If they say "be less formal" → identity. If they say "I use Tailwind, stop suggesting CSS
|
|
15
|
+
modules" → preferences. When unsure, ask which one they mean rather than guessing.
|
|
16
|
+
|
|
17
|
+
## 1. Work out what they actually want to change
|
|
18
|
+
|
|
19
|
+
Read both files first, so you're editing from what's there rather than overwriting blind.
|
|
20
|
+
|
|
21
|
+
Then figure out which of these they're asking for:
|
|
22
|
+
|
|
23
|
+
- **Rename** — just the agent's name
|
|
24
|
+
- **Personality / tone** — how you talk to them
|
|
25
|
+
- **Preferences** — their stack, style, or a standing rule for you to follow
|
|
26
|
+
- **"I don't know, it just feels off"** — go to step 2
|
|
27
|
+
|
|
28
|
+
## 2. If they can't name it, ask about behaviour, not settings
|
|
29
|
+
|
|
30
|
+
Don't hand them a config form. Ask what's been annoying:
|
|
31
|
+
|
|
32
|
+
- "Am I too wordy, or not detailed enough?"
|
|
33
|
+
- "Do I explain too much before doing the work?"
|
|
34
|
+
- "Anything I keep suggesting that you never want?"
|
|
35
|
+
- "Anything I keep getting wrong about your setup?"
|
|
36
|
+
|
|
37
|
+
Turn each answer into a concrete line. "Be less annoying" is not actionable; "answer first, then
|
|
38
|
+
explain, and don't restate my question back to me" is.
|
|
39
|
+
|
|
40
|
+
## 3. Make the edit
|
|
41
|
+
|
|
42
|
+
Edit the file directly — surgical changes, not a rewrite. Keep the existing structure and only
|
|
43
|
+
touch the lines that need to change.
|
|
44
|
+
|
|
45
|
+
- Renaming: update the name in `identity.md`. Mention that `AGENTS.md` in their projects may
|
|
46
|
+
also name the agent, and offer to update those too.
|
|
47
|
+
- Tone/personality: edit the "How I work" or "Communication style" section.
|
|
48
|
+
- Preferences: add to the right section of `preferences.md`. One line per rule, specific enough
|
|
49
|
+
to act on.
|
|
50
|
+
|
|
51
|
+
**Never invent preferences they didn't state.** If you observed something during the session
|
|
52
|
+
("you corrected me twice about using Postgres, not MySQL"), say so and ask whether to write it
|
|
53
|
+
down — don't just write it.
|
|
54
|
+
|
|
55
|
+
## 4. Read the change back
|
|
56
|
+
|
|
57
|
+
Show them the lines you changed, exactly as written. Then say plainly:
|
|
58
|
+
|
|
59
|
+
> This takes effect at your next boot. Run `/kracked-boot` in a fresh session to load it.
|
|
60
|
+
|
|
61
|
+
Do not claim the change is already active in the current session — memory is read at boot.
|
|
62
|
+
|
|
63
|
+
## Two things worth telling them once
|
|
64
|
+
|
|
65
|
+
- **These are plain markdown files they own.** They can open `~/.kracked/identity.md` in any
|
|
66
|
+
editor and change it directly. Doing that is often faster than asking you.
|
|
67
|
+
- **Global vs project.** Identity and preferences are global — they apply everywhere. Anything
|
|
68
|
+
true only for one codebase (its stack, conventions, structure) belongs in that project's
|
|
69
|
+
`.kracked/project.md` instead. Putting project detail in global memory makes you apply it to
|
|
70
|
+
every other project, which is the most common way this system goes wrong.
|