kracked-core 1.4.0 → 1.5.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 +36 -0
- package/bin/kracked-core.mjs +21 -5
- package/package.json +1 -1
- package/src/prompt.mjs +20 -0
- package/src/scaffold.mjs +21 -0
- package/src/status.mjs +125 -0
- package/src/update.mjs +14 -5
- package/src/wizard.mjs +12 -0
package/README.md
CHANGED
|
@@ -134,6 +134,42 @@ Then it tracks the work:
|
|
|
134
134
|
|
|
135
135
|
**A story cannot move to `done` without evidence.** Not "the tests pass" — what did you actually verify, and what did you *not*? Green tests are not a working feature. This one rule catches more bugs than any other part of the system.
|
|
136
136
|
|
|
137
|
+
## Terminal commands
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx kracked-core init # set up memory in this project
|
|
141
|
+
npx kracked-core status # what's installed, and is it current?
|
|
142
|
+
npx kracked-core update # refresh skills + loaders, keep your memory
|
|
143
|
+
npx kracked-core uninstall # remove it (asks before deleting anything)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Am I on the latest version?
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npx kracked-core@latest status
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Shows the version that installed your files, the latest on npm, and tells you plainly whether
|
|
153
|
+
you need to update:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
This project ~/code/my-app
|
|
157
|
+
installed version: 1.4.0
|
|
158
|
+
.agents/skills: 5/5 skills
|
|
159
|
+
|
|
160
|
+
Global memory ~/.kracked
|
|
161
|
+
4/4 files present
|
|
162
|
+
lessons learned: 12
|
|
163
|
+
|
|
164
|
+
Version
|
|
165
|
+
running now: 1.5.0
|
|
166
|
+
latest on npm: 1.5.0
|
|
167
|
+
|
|
168
|
+
Up to date.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
> The `@latest` matters — plain `npx kracked-core` can serve a cached older copy.
|
|
172
|
+
|
|
137
173
|
## Updating
|
|
138
174
|
|
|
139
175
|
When a new version ships:
|
package/bin/kracked-core.mjs
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import { runInit } from '../src/wizard.mjs';
|
|
4
4
|
import { runUninstall } from '../src/uninstall.mjs';
|
|
5
5
|
import { runUpdate } from '../src/update.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import { runStatus } from '../src/status.mjs';
|
|
7
|
+
import { beginTypeAhead, endTypeAhead } from '../src/prompt.mjs';
|
|
7
8
|
|
|
8
9
|
const USAGE = `kracked-core — memory & workflow installer for AI coding agents
|
|
9
10
|
|
|
10
11
|
Usage:
|
|
11
12
|
npx kracked-core init Set up global + project memory
|
|
13
|
+
npx kracked-core status Show what's installed and whether it's current
|
|
12
14
|
npx kracked-core update Refresh skills + loaders, keeping your memory
|
|
13
15
|
npx kracked-core uninstall Remove kracked-core files (asks before deleting)
|
|
14
16
|
|
|
@@ -29,6 +31,11 @@ async function main() {
|
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
if (subcommand === 'status') {
|
|
35
|
+
await runStatus();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
32
39
|
if (subcommand === 'update') {
|
|
33
40
|
await runUpdate();
|
|
34
41
|
return;
|
|
@@ -56,7 +63,16 @@ async function main() {
|
|
|
56
63
|
process.exit(1);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
main()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
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
|
@@ -174,6 +174,27 @@ export async function writeProjectMemory({ projectDir, tokens, ask, report }) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Record which version wrote these files. Without this there is no way to
|
|
179
|
+
* answer "am I on the latest?" — you can see what npm serves, but not what
|
|
180
|
+
* you actually installed.
|
|
181
|
+
*/
|
|
182
|
+
export function writeVersionStamp({ projectDir, version, report }) {
|
|
183
|
+
const dest = path.join(projectDir, '.kracked', '.version');
|
|
184
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
185
|
+
fs.writeFileSync(dest, `${version}\n`, 'utf8');
|
|
186
|
+
report({ path: dest, action: 'written' });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Read the version that last wrote this project's files, or null. */
|
|
190
|
+
export function readVersionStamp(projectDir) {
|
|
191
|
+
try {
|
|
192
|
+
return fs.readFileSync(path.join(projectDir, '.kracked', '.version'), 'utf8').trim();
|
|
193
|
+
} catch {
|
|
194
|
+
return null; // pre-1.5.0 install, or not installed
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
177
198
|
/** Write AGENTS.md, CLAUDE.md (shim), and .agents/rules/kracked.md. */
|
|
178
199
|
export async function writeLoaders({ projectDir, tokens, ask, report, editors }) {
|
|
179
200
|
const agentsContent = applyTokens(readTemplate('loaders/AGENTS.md'), tokens);
|
package/src/status.mjs
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// `kracked-core status` — what's installed here, and is it current?
|
|
2
|
+
//
|
|
3
|
+
// Answers the question a version number alone can't: the npm registry tells you
|
|
4
|
+
// what's available, package.json tells you what you just ran, but neither tells
|
|
5
|
+
// you which version actually wrote the files in this project.
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import { stdout } from 'node:process';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
|
|
13
|
+
import { readVersionStamp, SKILL_NAMES } from './scaffold.mjs';
|
|
14
|
+
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
17
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
18
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
19
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
20
|
+
|
|
21
|
+
function runningVersion() {
|
|
22
|
+
try {
|
|
23
|
+
return require('../package.json').version;
|
|
24
|
+
} catch {
|
|
25
|
+
return 'unknown';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Ask npm what the current published version is. Network-optional. */
|
|
30
|
+
async function latestPublished() {
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch('https://registry.npmjs.org/kracked-core', {
|
|
33
|
+
signal: AbortSignal.timeout(4000),
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) return null;
|
|
36
|
+
const body = await res.json();
|
|
37
|
+
return body['dist-tags']?.latest ?? null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null; // offline, or npm unreachable — not an error worth failing on
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Compare semver-ish strings. Returns -1, 0, or 1. */
|
|
44
|
+
function compareVersions(a, b) {
|
|
45
|
+
const pa = String(a).split('.').map(Number);
|
|
46
|
+
const pb = String(b).split('.').map(Number);
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
49
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function runStatus() {
|
|
55
|
+
const projectDir = process.cwd();
|
|
56
|
+
const globalDir = path.join(os.homedir(), '.kracked');
|
|
57
|
+
const running = runningVersion();
|
|
58
|
+
|
|
59
|
+
stdout.write(`${bold('kracked-core status')}\n\n`);
|
|
60
|
+
|
|
61
|
+
// --- This project ---
|
|
62
|
+
const installed = readVersionStamp(projectDir);
|
|
63
|
+
const hasProject = fs.existsSync(path.join(projectDir, '.kracked'));
|
|
64
|
+
|
|
65
|
+
stdout.write(`${bold('This project')} ${dim(projectDir)}\n`);
|
|
66
|
+
if (!hasProject) {
|
|
67
|
+
stdout.write(' not set up — run `npx kracked-core@latest init`\n');
|
|
68
|
+
} else {
|
|
69
|
+
stdout.write(` installed version: ${installed || dim('unknown (installed before 1.5.0)')}\n`);
|
|
70
|
+
|
|
71
|
+
const skillDirs = ['.claude/skills', '.agents/skills'].filter((d) =>
|
|
72
|
+
fs.existsSync(path.join(projectDir, d))
|
|
73
|
+
);
|
|
74
|
+
for (const dir of skillDirs) {
|
|
75
|
+
const present = SKILL_NAMES.filter((s) =>
|
|
76
|
+
fs.existsSync(path.join(projectDir, dir, s, 'SKILL.md'))
|
|
77
|
+
).length;
|
|
78
|
+
stdout.write(` ${dir}: ${present}/${SKILL_NAMES.length} skills\n`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- Global memory ---
|
|
83
|
+
stdout.write(`\n${bold('Global memory')} ${dim(globalDir)}\n`);
|
|
84
|
+
if (!fs.existsSync(globalDir)) {
|
|
85
|
+
stdout.write(' not set up\n');
|
|
86
|
+
} else {
|
|
87
|
+
const files = ['identity.md', 'preferences.md', 'lessons.md', 'projects.md'];
|
|
88
|
+
const present = files.filter((f) => fs.existsSync(path.join(globalDir, f)));
|
|
89
|
+
stdout.write(` ${present.length}/${files.length} files present\n`);
|
|
90
|
+
|
|
91
|
+
// Lesson count is the signal that memory is actually accumulating.
|
|
92
|
+
try {
|
|
93
|
+
const lessons = fs.readFileSync(path.join(globalDir, 'lessons.md'), 'utf8');
|
|
94
|
+
const count = lessons.split('\n').filter((l) => /^- /.test(l)).length;
|
|
95
|
+
stdout.write(` lessons learned: ${count}\n`);
|
|
96
|
+
} catch {
|
|
97
|
+
// No lessons file — already reflected in the count above.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- Version check ---
|
|
102
|
+
stdout.write(`\n${bold('Version')}\n`);
|
|
103
|
+
stdout.write(` running now: ${running}\n`);
|
|
104
|
+
|
|
105
|
+
const latest = await latestPublished();
|
|
106
|
+
if (!latest) {
|
|
107
|
+
stdout.write(` latest on npm: ${dim("couldn't check (offline?)")}\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
stdout.write(` latest on npm: ${latest}\n\n`);
|
|
112
|
+
|
|
113
|
+
const compareTo = installed || running;
|
|
114
|
+
const cmp = compareVersions(compareTo, latest);
|
|
115
|
+
|
|
116
|
+
if (cmp >= 0) {
|
|
117
|
+
stdout.write(` ${green('Up to date.')}\n`);
|
|
118
|
+
} else {
|
|
119
|
+
stdout.write(
|
|
120
|
+
` ${yellow(`Update available: ${compareTo} → ${latest}`)}\n` +
|
|
121
|
+
' Run `npx kracked-core@latest update` to refresh skills and loaders.\n' +
|
|
122
|
+
' Your memory is never touched by an update.\n'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/update.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { stdout } from 'node:process';
|
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
|
|
14
14
|
import { select } from './prompt.mjs';
|
|
15
|
-
import { writeLoaders, writeSkills, SKILL_NAMES } from './scaffold.mjs';
|
|
15
|
+
import { writeLoaders, writeSkills, writeVersionStamp, SKILL_NAMES } from './scaffold.mjs';
|
|
16
16
|
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
18
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
@@ -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
|
-
|
|
92
|
-
|
|
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
|
}
|
|
@@ -144,6 +152,7 @@ export async function runUpdate() {
|
|
|
144
152
|
stdout.write('\nUpdating...\n');
|
|
145
153
|
await writeLoaders({ projectDir, tokens, ask: overwrite, report, editors });
|
|
146
154
|
await writeSkills({ projectDir, tokens, editors, ask: overwrite, report });
|
|
155
|
+
writeVersionStamp({ projectDir, version, report });
|
|
147
156
|
|
|
148
157
|
stdout.write(`\n Refreshed ${created.length} file(s).\n`);
|
|
149
158
|
stdout.write('\nRestart your editor so it picks up the updated skills.\n');
|
package/src/wizard.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Follows the wizard flow in CONTRACT.md exactly — question order matters.
|
|
3
3
|
|
|
4
4
|
import { stdin, stdout } from 'node:process';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
5
6
|
import fs from 'node:fs';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import os from 'node:os';
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
writeProjectMemory,
|
|
23
24
|
writeLoaders,
|
|
24
25
|
writeSkills,
|
|
26
|
+
writeVersionStamp,
|
|
25
27
|
} from './scaffold.mjs';
|
|
26
28
|
|
|
27
29
|
/** Expand a leading ~ to the home directory. Leaves other paths untouched. */
|
|
@@ -34,6 +36,15 @@ function expandTilde(inputPath) {
|
|
|
34
36
|
return inputPath;
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
/** The version of kracked-core doing the installing. */
|
|
40
|
+
function pkgVersion() {
|
|
41
|
+
try {
|
|
42
|
+
return createRequire(import.meta.url)('../package.json').version;
|
|
43
|
+
} catch {
|
|
44
|
+
return 'unknown';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
/** Format today's date as YYYY-MM-DD. */
|
|
38
49
|
function isoDate() {
|
|
39
50
|
return new Date().toISOString().slice(0, 10);
|
|
@@ -341,6 +352,7 @@ async function wizardFlow() {
|
|
|
341
352
|
await writeProjectMemory({ projectDir, tokens, ask: conflictAsk, report: reporter });
|
|
342
353
|
await writeLoaders({ projectDir, tokens, ask: conflictAsk, report: reporter, editors });
|
|
343
354
|
await writeSkills({ projectDir, tokens, editors, ask: conflictAsk, report: reporter });
|
|
355
|
+
writeVersionStamp({ projectDir, version: pkgVersion(), report: reporter });
|
|
344
356
|
}
|
|
345
357
|
|
|
346
358
|
printSummary(created, agentName, setUpProject);
|