kracked-core 1.0.0 → 1.4.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/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,10 @@ import {
13
12
  classifyProjectDir,
14
13
  scanExistingProject,
15
14
  stackSummaryLine,
15
+ detectExistingAgentSetup,
16
+ sanitizeScanned,
16
17
  } from './detect.mjs';
18
+ import { select, checkbox, confirm, input } from './prompt.mjs';
17
19
  import {
18
20
  writeGlobalMemory,
19
21
  registerProject,
@@ -37,6 +39,31 @@ function isoDate() {
37
39
  return new Date().toISOString().slice(0, 10);
38
40
  }
39
41
 
42
+ // Installing into these scatters loader files across the filesystem, and
43
+ // installing into the home dir makes `<project>/.kracked` collide with global
44
+ // memory. A typo like `/` should not silently create a tree.
45
+ const FORBIDDEN_DIRS = ['/', '/etc', '/usr', '/bin', '/sbin', '/var', '/System', '/Library'];
46
+
47
+ /**
48
+ * Refuse obviously wrong install targets before anything is written.
49
+ * Returns an error string, or null when the path is fine.
50
+ */
51
+ function rejectUnsafeProjectDir(dir) {
52
+ const resolved = path.resolve(dir);
53
+
54
+ if (FORBIDDEN_DIRS.includes(resolved)) {
55
+ return `${resolved} is a system directory — pick a project folder instead.`;
56
+ }
57
+ if (resolved === path.resolve(os.homedir())) {
58
+ return (
59
+ 'That\'s your home directory. Installing here would put loader files loose in\n' +
60
+ ' your home folder and collide with global memory at ~/.kracked.\n' +
61
+ ' Make a project folder first, then run this inside it.'
62
+ );
63
+ }
64
+ return null;
65
+ }
66
+
40
67
  /**
41
68
  * Turn detected package.json scripts into a ready-to-read "how to run it" block.
42
69
  * The scan already knows these commands — writing them into project.md is the
@@ -68,87 +95,91 @@ export async function runInit() {
68
95
  return;
69
96
  }
70
97
 
71
- const rl = readline.createInterface({ input: stdin, output: stdout });
72
-
73
- let interrupted = false;
98
+ // Ctrl+C / Ctrl+D are handled inside prompt.mjs, which owns stdin in raw
99
+ // mode. Keep a SIGINT guard for interrupts arriving between prompts.
74
100
  const onSigint = () => {
75
- interrupted = true;
76
101
  stdout.write('\n\nCancelled. Nothing after this point was written.\n');
77
- rl.close();
78
102
  process.exit(130);
79
103
  };
80
104
  process.on('SIGINT', onSigint);
81
105
 
82
106
  try {
83
- await wizardFlow(rl);
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;
107
+ await wizardFlow();
93
108
  } finally {
94
109
  process.off('SIGINT', onSigint);
95
- if (!interrupted) rl.close();
110
+ stdin.pause();
96
111
  }
97
112
  }
98
113
 
99
- /** Ask a question with a default; Enter alone accepts the default. */
100
- async function ask(rl, question, defaultValue) {
101
- const suffix = defaultValue ? ` [${defaultValue}]` : '';
102
- const answer = (await rl.question(`${question}${suffix} `)).trim();
103
- return answer === '' ? defaultValue : answer;
114
+ /** Prompt for how to resolve a file-already-exists conflict. */
115
+ async function askConflict(destPath) {
116
+ stdout.write(`\n Already exists: ${destPath}\n`);
117
+ return select(
118
+ ' What should we do?',
119
+ [
120
+ { label: 'Skip', value: 'skip', hint: 'keep the existing file' },
121
+ { label: 'Overwrite', value: 'overwrite', hint: 'replace it' },
122
+ { label: 'Write alongside', value: 'alongside', hint: 'save as .kracked-new' },
123
+ ],
124
+ 0
125
+ );
104
126
  }
105
127
 
106
- /** Ask a yes/no question. Returns boolean. Default shown as Y/n or y/N. */
107
- async function askYesNo(rl, question, defaultYes) {
108
- const suffix = defaultYes ? '[Y/n]' : '[y/N]';
109
- const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
110
- if (answer === '') return defaultYes;
111
- return answer === 'y' || answer === 'yes';
128
+ /**
129
+ * Warn before writing into a directory another agent system already governs.
130
+ * A CLAUDE.md we didn't write belongs to someone else's setup — overwriting it
131
+ * silently replaces that agent's identity. Ask first, every time.
132
+ */
133
+ async function warnOnExistingSetup(projectDir) {
134
+ const found = detectExistingAgentSetup(projectDir).filter((f) => !f.ours);
135
+ if (found.length === 0) return true;
136
+
137
+ stdout.write('\n Heads up — this looks like it already has an agent setup:\n');
138
+ for (const f of found) {
139
+ const where = f.scope === 'global' ? 'global' : 'this project';
140
+ stdout.write(` • ${f.file} ${c_dim(`(${where})`)}\n`);
141
+ }
142
+ stdout.write(
143
+ '\n Installing here would overwrite those loaders and change who your\n' +
144
+ ' agent is in this directory. Existing files are never replaced without\n' +
145
+ ' asking, but it is easy to end up with two systems giving different\n' +
146
+ ' instructions.\n\n'
147
+ );
148
+
149
+ return confirm('Continue installing here?', false);
112
150
  }
113
151
 
114
- /** Prompt for how to resolve a file-already-exists conflict. */
115
- async function askConflict(rl, destPath) {
116
- stdout.write(`\n Already exists: ${destPath}\n`);
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';
152
+ /** Minimal dim helper for wizard-level notices. */
153
+ function c_dim(s) {
154
+ return `${s}`;
124
155
  }
125
156
 
126
- async function wizardFlow(rl) {
157
+ async function wizardFlow() {
127
158
  const created = [];
128
159
  const reporter = (entry) => created.push(entry);
129
- const conflictAsk = (destPath) => askConflict(rl, destPath);
160
+ const conflictAsk = (destPath) => askConflict(destPath);
130
161
 
131
162
  stdout.write('kracked-core — set up memory for your AI coding agent\n\n');
132
163
 
133
164
  // 1. Agent name
134
- const agentName = (await ask(rl, 'What should we call your agent?', 'KC')) || 'KC';
165
+ const agentName = sanitizeScanned(await input('What should we call your agent?', 'KC'), 40) || 'KC';
135
166
 
136
167
  // 2. User name
137
168
  const systemUser = os.userInfo().username || 'you';
138
- const userName = (await ask(rl, 'Your name?', systemUser)) || systemUser;
169
+ const userName = sanitizeScanned(await input('Your name?', systemUser), 40) || systemUser;
139
170
 
140
- // 3. Global memory
171
+ // 3. Global memory — always installed. It holds identity, preferences and
172
+ // cross-project lessons, and boot depends on it; making it optional only
173
+ // created a way to end up with a half-installed system that can't boot.
174
+ // Existing files are still never overwritten without asking.
141
175
  const hasGlobal = globalMemoryExists();
142
- let setUpGlobal;
143
- if (!hasGlobal) {
144
- setUpGlobal = await askYesNo(rl, 'Set up global memory now?', true);
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
176
+ const setUpGlobal = true;
177
+ if (hasGlobal) {
178
+ stdout.write(`\n Global memory found at ${globalMemoryDir()} reusing it.\n`);
148
179
  }
149
180
 
150
181
  // 4. Project setup
151
- const setUpProject = await askYesNo(rl, 'Set up this project?', true);
182
+ const setUpProject = await confirm('Set up this project?', true);
152
183
 
153
184
  let projectDir = process.cwd();
154
185
  let projectMode = null; // 'new' | 'existing'
@@ -156,24 +187,58 @@ async function wizardFlow(rl) {
156
187
  let projectSummary = null;
157
188
 
158
189
  if (setUpProject) {
159
- const pathAnswer = await ask(rl, 'Project directory?', '.');
190
+ const pathAnswer = await input('Project directory?', '.');
160
191
  const expanded = expandTilde(pathAnswer);
161
192
  projectDir = path.resolve(expanded === '' ? '.' : expanded);
162
193
 
163
- const classification = classifyProjectDir(projectDir);
194
+ const unsafe = rejectUnsafeProjectDir(projectDir);
195
+ if (unsafe) {
196
+ stdout.write(`\n ${unsafe}\n\nStopped — nothing was written.\n`);
197
+ process.exitCode = 1;
198
+ return;
199
+ }
200
+
201
+ // Already installed here? 27 individual conflict prompts is not a sane
202
+ // default — offer the three real intents up front instead.
203
+ if (fs.existsSync(path.join(projectDir, '.kracked'))) {
204
+ stdout.write('\n kracked-core is already set up in this project.\n\n');
205
+ const action = await select('What would you like to do?', [
206
+ { label: 'Refresh package files', value: 'refresh', hint: 'skills + loaders, keeps all your memory' },
207
+ { label: 'Reinstall everything', value: 'reinstall', hint: 'asks per file before replacing' },
208
+ { label: 'Cancel', value: 'cancel' },
209
+ ], 0);
210
+
211
+ if (action === 'cancel') {
212
+ stdout.write('\nCancelled — nothing changed.\n');
213
+ return;
214
+ }
215
+ if (action === 'refresh') {
216
+ const { runUpdate } = await import('./update.mjs');
217
+ process.chdir(projectDir);
218
+ await runUpdate();
219
+ return;
220
+ }
221
+ }
164
222
 
165
- if (classification === 'existing') {
166
- const modeAnswer = (
167
- await ask(rl, 'New project or existing codebase? (new/existing)', 'existing')
168
- ).toLowerCase();
169
- projectMode = modeAnswer.startsWith('n') ? 'new' : 'existing';
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';
223
+ // Another agent system already here? Ask before touching its loaders.
224
+ if (fs.existsSync(projectDir)) {
225
+ const proceed = await warnOnExistingSetup(projectDir);
226
+ if (!proceed) {
227
+ stdout.write('\nStopped nothing was written to this project.\n');
228
+ return;
229
+ }
175
230
  }
176
231
 
232
+ const classification = classifyProjectDir(projectDir);
233
+ projectMode = await select(
234
+ 'Is this a new project or an existing codebase?',
235
+ [
236
+ { label: 'Existing codebase', value: 'existing', hint: 'scan it and draft the memory' },
237
+ { label: 'New project', value: 'new', hint: 'start with a blank scaffold' },
238
+ ],
239
+ classification === 'existing' ? 0 : 1
240
+ );
241
+
177
242
  if (projectMode === 'existing') {
178
243
  if (!fs.existsSync(projectDir)) {
179
244
  stdout.write(`\n ${projectDir} doesn't exist yet — nothing to scan. Treating as new.\n`);
@@ -192,10 +257,10 @@ async function wizardFlow(rl) {
192
257
  }
193
258
  stdout.write('\n');
194
259
 
195
- const confirmed = await askYesNo(rl, 'Does this look right?', true);
260
+ const confirmed = await confirm('Does this look right?', true);
196
261
  if (!confirmed) {
197
- const manualStack = await ask(rl, 'Describe the stack yourself:', stackLine);
198
- stackLine = manualStack;
262
+ const manualStack = await input('Describe the stack yourself:', stackLine);
263
+ stackLine = sanitizeScanned(manualStack, 80) || stackLine;
199
264
  }
200
265
  }
201
266
  }
@@ -208,22 +273,42 @@ async function wizardFlow(rl) {
208
273
  }
209
274
  }
210
275
 
211
- // 5. Editors
212
- const editorAnswer = (
213
- await ask(rl, 'Which editor(s) do you use? (antigravity/claude/both/other)', 'both')
214
- ).toLowerCase();
215
-
216
- let editors;
217
- if (editorAnswer.startsWith('both')) {
218
- editors = ['antigravity', 'claude'];
219
- } else if (editorAnswer.startsWith('a')) {
220
- editors = ['antigravity'];
221
- } else if (editorAnswer.startsWith('c')) {
222
- editors = ['claude'];
223
- } else {
224
- // "other" or anything unrecognized — still write both loader formats,
225
- // since AGENTS.md/CLAUDE.md are cheap and cover most tools either way.
226
- editors = ['antigravity', 'claude'];
276
+ // 5. Editors — a checkbox, so anyone already running another agent system in
277
+ // one harness can install for the other and leave that one alone.
278
+ const existing = setUpProject ? detectExistingAgentSetup(projectDir) : [];
279
+ const claudeTaken = existing.some((f) => f.file === 'CLAUDE.md' && !f.ours);
280
+
281
+ const editors = await checkbox('Which editor(s) do you use?', [
282
+ {
283
+ label: 'Antigravity',
284
+ value: 'antigravity',
285
+ checked: true,
286
+ hint: 'writes .agents/',
287
+ },
288
+ {
289
+ label: 'Claude Code',
290
+ value: 'claude',
291
+ checked: !claudeTaken,
292
+ hint: claudeTaken ? 'CLAUDE.md already in use here' : 'writes CLAUDE.md + .claude/',
293
+ },
294
+ {
295
+ label: 'Kilo Code (VS Code)',
296
+ value: 'kilo',
297
+ checked: false,
298
+ hint: 'reads AGENTS.md + .agents/skills; adds kilo.jsonc',
299
+ },
300
+ {
301
+ label: 'Roo Code (VS Code)',
302
+ value: 'roo',
303
+ checked: false,
304
+ hint: 'reads AGENTS.md + .agents/skills; adds .roo/rules',
305
+ },
306
+ ]);
307
+
308
+ // Kilo and Roo both read AGENTS.md and .agents/skills/ natively, so picking
309
+ // either implies the shared .agents/ footprint.
310
+ if ((editors.includes('kilo') || editors.includes('roo')) && !editors.includes('antigravity')) {
311
+ editors.push('antigravity');
227
312
  }
228
313
 
229
314
  // 6. Write files
@@ -254,7 +339,7 @@ async function wizardFlow(rl) {
254
339
 
255
340
  if (setUpProject) {
256
341
  await writeProjectMemory({ projectDir, tokens, ask: conflictAsk, report: reporter });
257
- await writeLoaders({ projectDir, tokens, ask: conflictAsk, report: reporter });
342
+ await writeLoaders({ projectDir, tokens, ask: conflictAsk, report: reporter, editors });
258
343
  await writeSkills({ projectDir, tokens, editors, ask: conflictAsk, report: reporter });
259
344
  }
260
345
 
@@ -1,3 +1,4 @@
1
+ <!-- kracked-core:owned -->
1
2
  # AGENTS.md — {{PROJECT_NAME}}
2
3
 
3
4
  This is the canonical loader. Every harness (Antigravity, Claude Code, others) reads this file
@@ -50,6 +51,7 @@ the signup flow, next step is wiring the email step. Ready."
50
51
  | `/kracked-sdd` | Runs the SDD flow: idea → spec → docs → build → review |
51
52
  | `/kracked-wrap` | Writes lessons + updates session state, closes the session cleanly |
52
53
  | `/kracked-explain` | Walks {{USER_NAME}} through what kracked-core installed and why |
54
+ | `/kracked-identity` | Change who you are, your tone, or how {{USER_NAME}} likes to work |
53
55
 
54
56
  ## Writing memory (wrap)
55
57
 
@@ -1,4 +1,5 @@
1
1
  @AGENTS.md
2
+ <!-- kracked-core:owned -->
2
3
 
3
4
  This file is a shim. The canonical instructions live in `AGENTS.md` — duplicating them here would
4
5
  let the two drift apart, which is the exact bug kracked-core exists to prevent. If you need to
@@ -1,3 +1,4 @@
1
+ <!-- kracked-core:owned -->
1
2
  # Antigravity — {{PROJECT_NAME}}
2
3
 
3
4
  This file is a pointer, not the source of truth. The full instructions — who the agent is, the
@@ -0,0 +1,46 @@
1
+ # SDD — {{PROJECT_NAME}}
2
+
3
+ Spec-driven development artifacts. Run `/kracked-sdd` to work through the flow; these folders are
4
+ where it puts things.
5
+
6
+ ```
7
+ idea → spec → epic → stories → build → review
8
+ ```
9
+
10
+ | Folder | Holds | Written when |
11
+ |---|---|---|
12
+ | `specs/` | What to build and why | Start of any non-trivial job |
13
+ | `epics/` | A group of related stories | When a spec is too big for one story |
14
+ | `stories/` | One independently shippable slice | Broken out of a spec or epic |
15
+ | `architecture/` | How the system is put together | Large or risky work only |
16
+ | `tracker.md` | Status of every story + evidence | Continuously |
17
+
18
+ ## Naming
19
+
20
+ Numbers first so files sort in the order you built them.
21
+
22
+ ```
23
+ specs/login.md
24
+ epics/1-authentication.md
25
+ stories/1.1-email-login.md
26
+ stories/1.2-password-reset.md
27
+ architecture/data-model.md
28
+ architecture/decisions/0001-use-postgres.md
29
+ ```
30
+
31
+ Story IDs are `<epic>.<story>` and must match the ID in `tracker.md`. That's the link between a
32
+ story's detail and its status — if they drift, the tracker stops being trustworthy.
33
+
34
+ ## The two rules that matter
35
+
36
+ **1. Not everything needs the full flow.** A typo fix needs no spec. `/kracked-sdd` sizes the job
37
+ first and skips straight to the build when ceremony would be waste. Over-documenting small work is
38
+ the fastest way to abandon the system entirely.
39
+
40
+ **2. A story isn't `done` without evidence.** The tracker's Evidence column must say what was
41
+ actually verified — and what wasn't. "Tests pass" is not evidence of a working feature.
42
+
43
+ ## Commit these
44
+
45
+ These docs belong in git. They're how the next person — including you in three months, and your
46
+ agent on its next boot — finds out why the code looks the way it does.
File without changes
@@ -0,0 +1,51 @@
1
+ # Architecture — <area>
2
+
3
+ **Date:** {{DATE}}
4
+ **Status:** current | superseded by `<file>`
5
+
6
+ Write one of these only for work that's **large or risky** — new services, data model changes,
7
+ auth, anything touching money or user data. A small feature does not need an architecture doc,
8
+ and writing one anyway is how the system becomes ceremony people skip.
9
+
10
+ ## The shape
11
+
12
+ What the pieces are and how they talk to each other. A diagram in text is fine:
13
+
14
+ ```
15
+ browser → api route → service → database
16
+ ↘ queue → worker
17
+ ```
18
+
19
+ ## Data model
20
+
21
+ Tables/collections, key fields, and the relationships that matter. Not every column — the ones a
22
+ newcomer would get wrong.
23
+
24
+ | Entity | Key fields | Related to |
25
+ |---|---|---|
26
+ | | | |
27
+
28
+ ## Key decisions
29
+
30
+ The choices someone would otherwise re-litigate. Link to an ADR in `decisions/` for the big ones.
31
+
32
+ | Decision | Why | Alternative rejected |
33
+ |---|---|---|
34
+ | | | |
35
+
36
+ ## Failure modes
37
+
38
+ What breaks, and what happens when it does. Answer for each: does it fail loudly or silently?
39
+
40
+ | What fails | Effect | Handling |
41
+ |---|---|---|
42
+ | Database unreachable | | |
43
+ | Third-party API down | | |
44
+ | Duplicate/retried request | | |
45
+
46
+ ## What this does NOT do
47
+
48
+ Scope boundaries and deliberate limitations. Prevents someone assuming a capability that isn't
49
+ there.
50
+
51
+ - ...
@@ -0,0 +1,40 @@
1
+ # <NNNN> — <the decision, as a statement>
2
+
3
+ **Date:** {{DATE}}
4
+ **Status:** accepted | superseded by `<NNNN>` | reversed
5
+
6
+ > Example filename: `0001-use-postgres-not-mongo.md`
7
+ > Number them in order. Never renumber or delete one — a reversed decision is still history worth
8
+ > keeping, and the reasoning is often more valuable than the outcome.
9
+
10
+ ## Context
11
+
12
+ What forced a choice here? The constraints, the pressures, what was true at the time. Someone
13
+ reading this in six months has none of that context — this section is the whole point.
14
+
15
+ ## Decision
16
+
17
+ What was decided, stated plainly and in the present tense. "We use X." Not "we should" or
18
+ "we might."
19
+
20
+ ## Alternatives considered
21
+
22
+ | Option | Why not |
23
+ |---|---|
24
+ | | |
25
+
26
+ ## Consequences
27
+
28
+ Both directions — honest about the costs.
29
+
30
+ **Good:**
31
+ - ...
32
+
33
+ **Costs / things we now have to live with:**
34
+ - ...
35
+
36
+ ## Revisit if
37
+
38
+ The conditions that would make this decision wrong. If none, say "no expected trigger."
39
+
40
+ - ...
File without changes
@@ -0,0 +1,34 @@
1
+ # Epic <N> — <name>
2
+
3
+ **Spec:** `../specs/<name>.md`
4
+ **Status:** planning | in-progress | done
5
+ **Date:** {{DATE}}
6
+
7
+ ## Goal
8
+
9
+ What is true when this whole epic is finished? One or two sentences.
10
+
11
+ ## Stories
12
+
13
+ Every story must be **independently shippable** — mergeable on its own, leaving the app working.
14
+ If a story can't ship alone, split it.
15
+
16
+ | ID | Story | File |
17
+ |---|---|---|
18
+ | <N>.1 | | `../stories/<N>.1-<slug>.md` |
19
+ | <N>.2 | | `../stories/<N>.2-<slug>.md` |
20
+
21
+ Status lives in `../tracker.md`, not here. One source of truth — don't track status in two places
22
+ or they drift.
23
+
24
+ ## Order
25
+
26
+ Which stories block others, and why. If they're all independent, say so explicitly.
27
+
28
+ 1. ...
29
+
30
+ ## Out of scope
31
+
32
+ What belongs to a future epic, not this one.
33
+
34
+ - ...
@@ -0,0 +1,44 @@
1
+ # Spec — <name>
2
+
3
+ **Status:** draft | agreed | building | shipped
4
+ **Date:** {{DATE}}
5
+
6
+ ## What
7
+
8
+ One paragraph. What is being built, in plain language — no implementation detail.
9
+
10
+ ## Why
11
+
12
+ The problem this solves, and for whom. If you can't name who is worse off without it, the spec
13
+ isn't ready.
14
+
15
+ ## Acceptance criteria
16
+
17
+ Concrete and checkable. Someone else must be able to read these and tell whether the thing works.
18
+
19
+ - [ ] A user can ...
20
+ - [ ] When X happens, the system ...
21
+ - [ ] If the input is empty/invalid, it ...
22
+
23
+ ## Out of scope
24
+
25
+ **Not optional.** Scope creep starts exactly where this section is skipped. List what this spec
26
+ deliberately does NOT cover.
27
+
28
+ - ...
29
+
30
+ ## Edge cases
31
+
32
+ For every input involved, answer: what if it's empty, missing, or unexpected?
33
+
34
+ | Case | Expected behaviour |
35
+ |---|---|
36
+ | Empty input | |
37
+ | Already exists | |
38
+ | Not authorised | |
39
+
40
+ ## Open questions
41
+
42
+ Things that must be answered before or during the build. Delete this section once it's empty.
43
+
44
+ - [ ] ...
File without changes
@@ -0,0 +1,33 @@
1
+ # Story <N>.<M> — <name>
2
+
3
+ **Epic:** `../epics/<N>-<slug>.md`
4
+ **Status:** backlog | in-progress | review | done ← mirror this in `../tracker.md`
5
+ **Date:** {{DATE}}
6
+
7
+ ## What
8
+
9
+ One slice of the epic. Small enough to build and verify in one sitting.
10
+
11
+ ## Acceptance criteria
12
+
13
+ - [ ] ...
14
+ - [ ] ...
15
+
16
+ ## Notes for the build
17
+
18
+ Files likely to change, existing code to reuse, anything that would trip someone up.
19
+
20
+ > Verify these claims before building on them. A story written from memory can name a file,
21
+ > function, or column that doesn't exist — grep first, then build.
22
+
23
+ - ...
24
+
25
+ ## Evidence
26
+
27
+ **Fill this in before marking `done`.** Copy the summary line into `tracker.md`.
28
+
29
+ - **Verified:** what you actually ran or checked, and what you saw
30
+ - **Not verified:** what you did NOT check — untested paths, edge cases, anything left open
31
+
32
+ A story marked `done` with an empty Evidence section is worse than one left at `review`: it hides
33
+ risk instead of surfacing it.
@@ -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 that memory exists
8
+ ## 1. Check what memory exists
9
9
 
10
- Look for `~/.kracked/identity.md`. If it is missing, global memory has not been set up. Tell the
11
- user:
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
- > Global memory isn't set up yet. Run `npx kracked-core init` to create it, then re-run this.
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
- Stop here if memory doesn't exist. Do not fabricate identity, preferences, or project state.
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
 
@@ -36,6 +43,7 @@ If the current directory has a `.kracked/` folder, read:
36
43
  1. `.kracked/project.md` — what this project is (stack, conventions)
37
44
  2. `.kracked/session.md` — working memory: state + next steps
38
45
  3. `.kracked/decisions.md` — why things are the way they are
46
+ 4. `.kracked/sdd/tracker.md` — story status, if this project uses SDD (skip if absent)
39
47
 
40
48
  If there is no `.kracked/` folder here, this is a project that hasn't been set up yet. Say so
41
49
  plainly and suggest `npx kracked-core init` if the user wants project memory for it. Continue