kracked-core 1.5.0 → 1.6.1

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 CHANGED
@@ -47,7 +47,11 @@ It's plain markdown files. You can read them, edit them, commit them to git, and
47
47
  npx kracked-core init
48
48
  ```
49
49
 
50
- The wizard asks a few questions — name your agent, is this a new or existing project, which editor you use — and writes the files. Hold Enter through it and you'll get a sane setup.
50
+ The wizard asks a few questions — name your agent, new or existing project, which editor you use —
51
+ and writes the files. Pressing Enter through it gives you a working setup.
52
+
53
+ **Existing codebase?** It scans your repo and drafts the memory from what it finds.
54
+ **Starting fresh?** It asks what you're building and what stack, so your agent knows from day one.
51
55
 
52
56
  **Full support** (memory + all five commands):
53
57
  **Antigravity** · **Claude Code** · **Kilo Code** (VS Code) · **Roo Code** (VS Code)
@@ -4,7 +4,7 @@ import { runInit } from '../src/wizard.mjs';
4
4
  import { runUninstall } from '../src/uninstall.mjs';
5
5
  import { runUpdate } from '../src/update.mjs';
6
6
  import { runStatus } from '../src/status.mjs';
7
- import { beginTypeAhead } from '../src/prompt.mjs';
7
+ import { beginTypeAhead, endTypeAhead } from '../src/prompt.mjs';
8
8
 
9
9
  const USAGE = `kracked-core — memory & workflow installer for AI coding agents
10
10
 
@@ -63,7 +63,16 @@ async function main() {
63
63
  process.exit(1);
64
64
  }
65
65
 
66
- main().catch((err) => {
67
- process.stderr.write(`\nkracked-core failed: ${err.message}\n`);
68
- process.exit(1);
69
- });
66
+ main()
67
+ .catch((err) => {
68
+ process.stderr.write(`\nkracked-core failed: ${err.message}\n`);
69
+ process.exitCode = 1;
70
+ })
71
+ // A raw-mode TTY stdin stays an ACTIVE HANDLE even after removeListener +
72
+ // pause(), so Node's event loop never drains and the terminal never returns —
73
+ // the command looks stuck even when it succeeded. Releasing stdin is not
74
+ // enough on its own; exit explicitly once the work is done.
75
+ .finally(() => {
76
+ endTypeAhead();
77
+ process.stdout.write('', () => process.exit(process.exitCode ?? 0));
78
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kracked-core",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Zero-dependency memory & workflow installer for AI coding agents — global identity, project memory, and skills that survive session restarts.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/prompt.mjs CHANGED
@@ -110,6 +110,26 @@ export function beginTypeAhead() {
110
110
  stdin.on('data', bufferEarlyKeys);
111
111
  }
112
112
 
113
+ /**
114
+ * Release stdin so the process can exit.
115
+ *
116
+ * An open stdin listener keeps Node's event loop alive indefinitely, so any
117
+ * command that finishes WITHOUT going through a prompt's cleanup (an early
118
+ * return, an error path, a completed run) leaves the terminal hanging with no
119
+ * output and no prompt back. `process.exitCode` sets the code but does not
120
+ * exit. Every command path must end here.
121
+ */
122
+ export function endTypeAhead() {
123
+ if (!stdin.isTTY) return;
124
+ stdin.removeListener('data', bufferEarlyKeys);
125
+ try {
126
+ if (stdin.isRaw) stdin.setRawMode(false);
127
+ } catch {
128
+ // Terminal already gone.
129
+ }
130
+ stdin.pause();
131
+ }
132
+
113
133
  function bufferEarlyKeys(chunk) {
114
134
  pendingKeys.push(...splitKeys(chunk));
115
135
  }
package/src/scaffold.mjs CHANGED
@@ -80,6 +80,19 @@ async function writeFileWithConflictCheck(destPath, content, ask, report) {
80
80
  * 'skip' | 'overwrite' | 'alongside'.
81
81
  * `report(entry)` is called once per file, for the wizard's final summary.
82
82
  */
83
+ /**
84
+ * Drop the illustrative example entries from a freshly-written memory file.
85
+ * They exist to show the format, but left in place they sit alongside the
86
+ * user's real lessons forever and make the index untrustworthy.
87
+ */
88
+ function stripExamples(content) {
89
+ return content
90
+ .split('\n')
91
+ .filter((line) => !/^[-|]\s*\[?(YYYY-MM-DD|\d{4}-\d{2}-\d{2})\]?\s*(todo-app|my-blog|<project>)/.test(line))
92
+ .filter((line) => !/^\|\s*(todo-app|my-blog)\s*\|/.test(line))
93
+ .join('\n');
94
+ }
95
+
83
96
  export async function writeGlobalMemory({ tokens, ask, report }) {
84
97
  const globalDir = path.join(os.homedir(), '.kracked');
85
98
  // projects.md is excluded here — it's a running registry appended to by
@@ -88,7 +101,7 @@ export async function writeGlobalMemory({ tokens, ask, report }) {
88
101
 
89
102
  for (const file of files) {
90
103
  const destPath = path.join(globalDir, file);
91
- const content = applyTokens(readTemplate(`global/${file}`), tokens);
104
+ const content = stripExamples(applyTokens(readTemplate(`global/${file}`), tokens));
92
105
  await writeFileWithConflictCheck(destPath, content, ask, report);
93
106
  }
94
107
  }
@@ -124,10 +137,13 @@ export function registerProject({ tokens }) {
124
137
  const lines = content.split('\n');
125
138
  const isDivider = (l) => /^\|[\s|:-]+\|?\s*$/.test(l);
126
139
 
127
- // Find the table's divider row (|---|---|), then insert after the last data
128
- // row that follows it. Anchoring on the divider matters: without it, a table
129
- // whose only row is the header inserts ABOVE the divider and breaks the table.
130
- const dividerIdx = lines.findIndex(isDivider);
140
+ // Anchor on the Registry heading first a table-looking line elsewhere in the
141
+ // doc (an example, a fenced snippet) would otherwise capture the insert and
142
+ // produce a second, malformed table.
143
+ const registryIdx = lines.findIndex((l) => /^##\s+Registry\s*$/i.test(l));
144
+ const searchFrom = registryIdx >= 0 ? registryIdx : 0;
145
+ const rel = lines.slice(searchFrom).findIndex(isDivider);
146
+ const dividerIdx = rel >= 0 ? searchFrom + rel : -1;
131
147
 
132
148
  if (dividerIdx >= 0) {
133
149
  let insertAt = dividerIdx;
package/src/update.mjs CHANGED
@@ -87,10 +87,18 @@ export async function runUpdate() {
87
87
  stdout.write(`${bold('kracked-core update')} ${dim(`v${version}`)}\n\n`);
88
88
 
89
89
  if (!fs.existsSync(path.join(projectDir, '.kracked'))) {
90
- stdout.write(
91
- `No kracked-core install found in ${projectDir}\n\n` +
92
- 'Run `npx kracked-core init` to set it up first.\n'
93
- );
90
+ stdout.write(`No kracked-core install found in ${projectDir}\n\n`);
91
+
92
+ // Easy mistake: running this inside the package source rather than a
93
+ // project that uses it. Say so instead of just repeating the error.
94
+ if (fs.existsSync(path.join(projectDir, 'templates', 'loaders', 'AGENTS.md'))) {
95
+ stdout.write(
96
+ 'This looks like the kracked-core package source, not a project that\n' +
97
+ 'uses it. cd into your own project first, then run this again.\n'
98
+ );
99
+ } else {
100
+ stdout.write('Run `npx kracked-core@latest init` to set it up first.\n');
101
+ }
94
102
  process.exitCode = 1;
95
103
  return;
96
104
  }
package/src/wizard.mjs CHANGED
@@ -196,6 +196,7 @@ async function wizardFlow() {
196
196
  let projectMode = null; // 'new' | 'existing'
197
197
  let stackLine = 'unknown — not auto-detected';
198
198
  let projectSummary = null;
199
+ let projectPitch = '';
199
200
 
200
201
  if (setUpProject) {
201
202
  const pathAnswer = await input('Project directory?', '.');
@@ -250,6 +251,24 @@ async function wizardFlow() {
250
251
  classification === 'existing' ? 0 : 1
251
252
  );
252
253
 
254
+ if (projectMode === 'new') {
255
+ // Nothing to scan, so ask instead — otherwise the agent boots knowing
256
+ // nothing about what's being built, which is the problem this package
257
+ // exists to solve. Both skippable with Enter.
258
+ stdout.write('\n Two quick questions so your agent knows what it\'s working on.\n');
259
+ stdout.write(` ${c_dim('Press Enter to skip either one.')}\n\n`);
260
+
261
+ projectPitch = sanitizeScanned(
262
+ await input('What are you building? (one line)', ''),
263
+ 200
264
+ );
265
+ const statedStack = sanitizeScanned(
266
+ await input('What stack? (e.g. Next.js + Supabase)', ''),
267
+ 80
268
+ );
269
+ if (statedStack) stackLine = statedStack;
270
+ }
271
+
253
272
  if (projectMode === 'existing') {
254
273
  if (!fs.existsSync(projectDir)) {
255
274
  stdout.write(`\n ${projectDir} doesn't exist yet — nothing to scan. Treating as new.\n`);
@@ -333,6 +352,9 @@ async function wizardFlow() {
333
352
  DATE: isoDate(),
334
353
  STACK: stackLine,
335
354
  RUN_COMMANDS: runCommandsBlock(projectSummary),
355
+ PITCH: projectPitch
356
+ ? projectPitch
357
+ : '_(Add a sentence or two here on what this project actually does.)_',
336
358
  };
337
359
 
338
360
  stdout.write('\nWriting files...\n');
@@ -4,13 +4,7 @@ Registry of every project {{USER_NAME}} is working on. Each row points to the pr
4
4
  `.kracked/` folder, where the real detail (stack, conventions, session state) lives. This file
5
5
  only tracks what exists and where — never project-specific truth.
6
6
 
7
- ## Format
8
-
9
- ```
10
- | Project | Path | Stack | Status |
11
- ```
12
-
13
- - **Status** is one of: `active`, `paused`, `done`.
7
+ **Status** is one of: `active`, `paused`, `done`.
14
8
 
15
9
  ## Registry
16
10
 
@@ -11,7 +11,7 @@ here.
11
11
  - **Stack:** {{STACK}}
12
12
  - **Created:** {{DATE}}
13
13
 
14
- _(Add a sentence or two here on what this project actually does, once there's something to say.)_
14
+ {{PITCH}}
15
15
 
16
16
  ## Conventions
17
17