kracked-core 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KrackedDevs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ <div align="center">
2
+
3
+ # Kracked Core
4
+
5
+ **Your AI coding agent forgets everything between sessions — and builds whatever you vaguely described.**
6
+
7
+ Kracked Core fixes both.
8
+
9
+ [![npm](https://img.shields.io/npm/v/kracked-core?color=black)](https://www.npmjs.com/package/kracked-core)
10
+ [![license](https://img.shields.io/badge/license-MIT-black)](./LICENSE)
11
+
12
+ ```bash
13
+ npx kracked-core init
14
+ ```
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## The problem
21
+
22
+ You've had this conversation:
23
+
24
+ > **You:** Build me a login page.
25
+ > **Agent:** *builds a login page*
26
+ > **You:** No, we use Supabase auth. I told you yesterday.
27
+ > **Agent:** You're absolutely right! Let me fix that.
28
+
29
+ Tomorrow it happens again. Your agent has no memory. Every session starts from zero, and every session you re-explain your stack, your conventions, and the same three mistakes you already corrected.
30
+
31
+ The second problem is quieter: you describe a feature in one sentence, the agent builds *something*, and you spend the next hour discovering it wasn't what you meant.
32
+
33
+ ## What this gives you
34
+
35
+ | | Before | After |
36
+ |---|---|---|
37
+ | **Memory** | Re-explain everything, every session | Agent reads its memory on boot and picks up where you left off |
38
+ | **Mistakes** | Same correction, over and over | Corrections get written down once, and stay corrected |
39
+ | **Building** | "Build me X" → surprise | Idea → spec → build → review, with a tracker you can see |
40
+ | **"Done"** | Agent says done, it isn't | Nothing is `done` without stated evidence |
41
+
42
+ It's plain markdown files. You can read them, edit them, commit them to git, and delete them. No database, no cloud, no black box.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ npx kracked-core init
48
+ ```
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.
51
+
52
+ **Works with:** Antigravity · Claude Code · Cursor · Windsurf · anything that reads `AGENTS.md`
53
+
54
+ ## Use
55
+
56
+ Three commands. That's the whole thing.
57
+
58
+ ```
59
+ /kracked-boot Start of session — agent loads its memory and tells you where you left off
60
+ /kracked-sdd Build something — idea → spec → docs → build → review
61
+ /kracked-wrap End of session — agent writes down what it learned
62
+ ```
63
+
64
+ > **`/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.
65
+
66
+ ## How it works
67
+
68
+ Two layers of memory, and the split between them is the whole idea.
69
+
70
+ ```
71
+ ~/.kracked/ GLOBAL — survives every project
72
+ identity.md who your agent is
73
+ preferences.md how you like to work
74
+ lessons.md mistakes it won't repeat ← read every boot
75
+ lessons-archive.md the full detail ← read on demand only
76
+ projects.md which projects exist
77
+
78
+ your-project/.kracked/ PROJECT — this codebase only
79
+ project.md stack, conventions, how to run it
80
+ session.md where we are right now
81
+ decisions.md why things are the way they are
82
+ sdd/tracker.md story status + evidence
83
+ ```
84
+
85
+ ### The boundary rule
86
+
87
+ | Goes GLOBAL | Goes PROJECT |
88
+ |---|---|
89
+ | Who your agent is | What this codebase is |
90
+ | How you like to work | This project's conventions |
91
+ | Lessons that apply anywhere | This sprint's state |
92
+
93
+ Getting this wrong is the #1 failure mode. Put "we use Tailwind in this repo" into global memory and your agent will try to use Tailwind in your Python API. Keep project truth in the project.
94
+
95
+ ### Why lessons split into two files
96
+
97
+ `lessons.md` is read on **every single boot**. If it grew forever, boot would get slower forever — and Antigravity caps rules files at 12,000 characters, so eventually it would simply break.
98
+
99
+ So: the full lesson goes into `lessons-archive.md`, and **one line** goes into `lessons.md`. The index stays small. The archive grows without cost, and gets opened only when a specific lesson is actually relevant.
100
+
101
+ Your boot payload stays flat no matter how much your agent learns. That's the trick.
102
+
103
+ ## The build flow
104
+
105
+ `/kracked-sdd` doesn't force ceremony on everything. It sizes the job first:
106
+
107
+ | Job | What happens |
108
+ |---|---|
109
+ | Fix a typo | Skips straight to the fix. No spec. |
110
+ | Small, clear feature | Light spec → build → review |
111
+ | Big or risky | Full flow: idea → spec → stories → build → review |
112
+
113
+ Then it tracks the work:
114
+
115
+ | ID | Story | Status | Evidence |
116
+ |---|---|---|---|
117
+ | 1.1 | User can log in | done | manual login OK, 3 tests pass |
118
+ | 1.2 | Password reset | in-progress | — |
119
+ | 1.3 | Remember me | backlog | — |
120
+
121
+ **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.
122
+
123
+ ## FAQ
124
+
125
+ **Does this send my code anywhere?**
126
+ No. Everything is local markdown files. There's no service, no telemetry, no account.
127
+
128
+ **Should I commit `.kracked/` to git?**
129
+ Yes, mostly. Project memory is useful to your teammates. Add `.kracked/session.md` to `.gitignore` if you don't want your working state shared.
130
+
131
+ **My editor isn't listed. Will it work?**
132
+ If it reads `AGENTS.md`, yes. That's most of them now. Claude Code is the exception — it reads `CLAUDE.md`, so we write a one-line shim that imports `AGENTS.md`.
133
+
134
+ **Can I edit the memory files by hand?**
135
+ Please do. They're yours. Correcting your agent's memory directly is faster than arguing with it.
136
+
137
+ **How is this different from just writing a good prompt?**
138
+ A prompt lasts one session. This lasts across all of them, and it compounds — every mistake you correct is a mistake you never see again.
139
+
140
+ ## Contributing
141
+
142
+ Issues and PRs welcome. This started as teaching material for the [KrackedDevs](https://github.com/Krackeddevs-Org) weekly class, so clarity for beginners beats cleverness.
143
+
144
+ ## License
145
+
146
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runInit } from '../src/wizard.mjs';
4
+
5
+ const USAGE = `kracked-core — memory & workflow installer for AI coding agents
6
+
7
+ Usage:
8
+ npx kracked-core init Set up global + project memory
9
+
10
+ Options:
11
+ -h, --help Show this help
12
+ `;
13
+
14
+ async function main() {
15
+ const [, , subcommand] = process.argv;
16
+
17
+ if (subcommand === 'init') {
18
+ await runInit();
19
+ return;
20
+ }
21
+
22
+ if (subcommand === '--help' || subcommand === '-h' || !subcommand) {
23
+ process.stdout.write(USAGE);
24
+ process.exit(subcommand ? 0 : 1);
25
+ return;
26
+ }
27
+
28
+ process.stderr.write(`Unknown command: ${subcommand}\n\n${USAGE}`);
29
+ process.exit(1);
30
+ }
31
+
32
+ main().catch((err) => {
33
+ process.stderr.write(`\nkracked-core failed: ${err.message}\n`);
34
+ process.exit(1);
35
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "kracked-core",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency memory & workflow installer for AI coding agents — global identity, project memory, and skills that survive session restarts.",
5
+ "type": "module",
6
+ "bin": {
7
+ "kracked-core": "./bin/kracked-core.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Krackeddevs-Org/kracked-core.git"
21
+ },
22
+ "keywords": [
23
+ "ai",
24
+ "agent",
25
+ "memory",
26
+ "cli",
27
+ "claude",
28
+ "antigravity"
29
+ ]
30
+ }
package/src/detect.mjs ADDED
@@ -0,0 +1,106 @@
1
+ // Detection helpers: what's already on disk, so the wizard doesn't ask
2
+ // questions it can answer itself and scaffold.mjs doesn't clobber anything.
3
+
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+
8
+ /** Does ~/.kracked/ already exist? */
9
+ export function globalMemoryExists() {
10
+ const dir = path.join(os.homedir(), '.kracked');
11
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
12
+ }
13
+
14
+ export function globalMemoryDir() {
15
+ return path.join(os.homedir(), '.kracked');
16
+ }
17
+
18
+ /**
19
+ * Is the target dir an existing codebase, empty, or new (doesn't exist yet)?
20
+ * Returns one of: "new" (doesn't exist), "empty" (exists, no entries),
21
+ * "existing" (exists, has files/dirs signalling a real codebase).
22
+ */
23
+ export function classifyProjectDir(targetDir) {
24
+ if (!fs.existsSync(targetDir)) return 'new';
25
+
26
+ const entries = fs.readdirSync(targetDir).filter((e) => e !== '.DS_Store');
27
+ if (entries.length === 0) return 'empty';
28
+ return 'existing';
29
+ }
30
+
31
+ // Markers checked, in order, to guess the primary stack of an existing repo.
32
+ const STACK_MARKERS = [
33
+ { file: 'package.json', label: 'Node.js' },
34
+ { file: 'requirements.txt', label: 'Python' },
35
+ { file: 'pyproject.toml', label: 'Python' },
36
+ { file: 'go.mod', label: 'Go' },
37
+ { file: 'composer.json', label: 'PHP' },
38
+ { file: 'Cargo.toml', label: 'Rust' },
39
+ { file: 'Gemfile', label: 'Ruby' },
40
+ { file: 'pom.xml', label: 'Java (Maven)' },
41
+ { file: 'build.gradle', label: 'Java/Kotlin (Gradle)' },
42
+ { file: 'mix.exs', label: 'Elixir' },
43
+ ];
44
+
45
+ /**
46
+ * Extract a light summary of an existing codebase: name, stack guess,
47
+ * framework hints, package.json scripts, whether it's a git repo.
48
+ * Never throws — worst case it returns a mostly-empty summary.
49
+ */
50
+ export function scanExistingProject(targetDir) {
51
+ const summary = {
52
+ name: path.basename(path.resolve(targetDir)),
53
+ stack: [],
54
+ frameworks: [],
55
+ scripts: {},
56
+ isGitRepo: fs.existsSync(path.join(targetDir, '.git')),
57
+ };
58
+
59
+ for (const marker of STACK_MARKERS) {
60
+ if (fs.existsSync(path.join(targetDir, marker.file))) {
61
+ summary.stack.push(marker.label);
62
+ }
63
+ }
64
+
65
+ const pkgPath = path.join(targetDir, 'package.json');
66
+ if (fs.existsSync(pkgPath)) {
67
+ try {
68
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
69
+ if (pkg.name) summary.name = pkg.name;
70
+ if (pkg.scripts && typeof pkg.scripts === 'object') {
71
+ summary.scripts = pkg.scripts;
72
+ }
73
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
74
+ const frameworkHints = [
75
+ 'react', 'next', 'vue', 'nuxt', 'svelte', 'angular',
76
+ 'express', 'fastify', 'nestjs', '@nestjs/core', 'koa',
77
+ ];
78
+ for (const hint of frameworkHints) {
79
+ if (deps && deps[hint]) summary.frameworks.push(hint);
80
+ }
81
+ } catch {
82
+ // Malformed package.json — skip framework/script detection, keep the rest.
83
+ }
84
+ }
85
+
86
+ return summary;
87
+ }
88
+
89
+ /** Build the {{STACK}} token value from a scan result. */
90
+ export function stackSummaryLine(summary) {
91
+ const parts = [];
92
+ if (summary.stack.length) parts.push(summary.stack.join(', '));
93
+ if (summary.frameworks.length) parts.push(`(${summary.frameworks.join(', ')})`);
94
+ return parts.length ? parts.join(' ') : 'unknown — not auto-detected';
95
+ }
96
+
97
+ /** Which harnesses already have a footprint in this project dir? */
98
+ export function detectHarnesses(targetDir) {
99
+ return {
100
+ claudeCode: fs.existsSync(path.join(targetDir, '.claude')) ||
101
+ fs.existsSync(path.join(targetDir, 'CLAUDE.md')),
102
+ antigravity: fs.existsSync(path.join(targetDir, '.agents')) ||
103
+ fs.existsSync(path.join(targetDir, 'AGENTS.md')),
104
+ cursor: fs.existsSync(path.join(targetDir, '.cursor')),
105
+ };
106
+ }
@@ -0,0 +1,192 @@
1
+ // File writer: copies templates to their destinations, replacing {{TOKEN}}
2
+ // placeholders, and never overwrites an existing file without asking first.
3
+
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const TEMPLATES_ROOT = path.join(__dirname, '..', 'templates');
11
+
12
+ const SKILL_NAMES = ['kracked-boot', 'kracked-sdd', 'kracked-wrap', 'kracked-explain'];
13
+
14
+ /** Read a template file, throwing a clear error if Track A hasn't shipped it yet. */
15
+ function readTemplate(relPath) {
16
+ const fullPath = path.join(TEMPLATES_ROOT, relPath);
17
+ if (!fs.existsSync(fullPath)) {
18
+ throw new Error(
19
+ `Missing template: templates/${relPath}\n` +
20
+ `This file is expected by the build contract but isn't in the package yet.`
21
+ );
22
+ }
23
+ return fs.readFileSync(fullPath, 'utf8');
24
+ }
25
+
26
+ /** Replace every {{TOKEN}} in content with its value from the tokens map. */
27
+ function applyTokens(content, tokens) {
28
+ return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
29
+ return Object.prototype.hasOwnProperty.call(tokens, key) ? tokens[key] : match;
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Decide what to do about a file that may already exist.
35
+ * Returns 'skip' | 'overwrite' | 'alongside'. Delegates the actual
36
+ * question to `ask` (injected so this module has no direct readline dependency).
37
+ */
38
+ async function resolveConflict(destPath, ask) {
39
+ if (!fs.existsSync(destPath)) return 'write';
40
+ const choice = await ask(destPath);
41
+ return choice; // 'skip' | 'overwrite' | 'alongside'
42
+ }
43
+
44
+ /** Write content to destPath, honoring the conflict resolution. */
45
+ async function writeFileWithConflictCheck(destPath, content, ask, report) {
46
+ const action = await resolveConflict(destPath, ask);
47
+
48
+ if (action === 'skip') {
49
+ report({ path: destPath, action: 'skipped (already existed)' });
50
+ return;
51
+ }
52
+
53
+ let finalPath = destPath;
54
+ if (action === 'alongside') {
55
+ finalPath = `${destPath}.kracked-new`;
56
+ }
57
+
58
+ fs.mkdirSync(path.dirname(finalPath), { recursive: true });
59
+ fs.writeFileSync(finalPath, content, 'utf8');
60
+ report({
61
+ path: finalPath,
62
+ action: action === 'alongside' ? 'written alongside existing file' : 'written',
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Write global memory files to ~/.kracked/.
68
+ * `ask(destPath)` is called only when a file already exists; must resolve to
69
+ * 'skip' | 'overwrite' | 'alongside'.
70
+ * `report(entry)` is called once per file, for the wizard's final summary.
71
+ */
72
+ export async function writeGlobalMemory({ tokens, ask, report }) {
73
+ const globalDir = path.join(os.homedir(), '.kracked');
74
+ // projects.md is excluded here — it's a running registry appended to by
75
+ // every install, never treated as a skip/overwrite conflict. See registerProject().
76
+ const files = ['identity.md', 'preferences.md', 'lessons.md', 'lessons-archive.md'];
77
+
78
+ for (const file of files) {
79
+ const destPath = path.join(globalDir, file);
80
+ const content = applyTokens(readTemplate(`global/${file}`), tokens);
81
+ await writeFileWithConflictCheck(destPath, content, ask, report);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Register this project in ~/.kracked/projects.md — create the file from its
87
+ * template on first use, then append one registry line per project.
88
+ */
89
+ export function registerProject({ tokens }) {
90
+ const globalDir = path.join(os.homedir(), '.kracked');
91
+ const projectsPath = path.join(globalDir, 'projects.md');
92
+ const stack = tokens.STACK && tokens.STACK.trim() ? tokens.STACK : '—';
93
+ const row = `| ${tokens.PROJECT_NAME} | ${tokens.PROJECT_PATH} | ${stack} | active |`;
94
+
95
+ fs.mkdirSync(globalDir, { recursive: true });
96
+
97
+ let content;
98
+ if (fs.existsSync(projectsPath)) {
99
+ content = fs.readFileSync(projectsPath, 'utf8');
100
+ } else {
101
+ // First install: start from the template, minus its illustrative example rows.
102
+ content = applyTokens(readTemplate('global/projects.md'), tokens)
103
+ .split('\n')
104
+ .filter((line) => !/^\|\s*(todo-app|my-blog)\s*\|/.test(line))
105
+ .join('\n');
106
+ }
107
+
108
+ // Already registered? Leave it alone — re-running init must not duplicate rows.
109
+ if (content.includes(`| ${tokens.PROJECT_PATH} |`)) return;
110
+
111
+ const lines = content.split('\n');
112
+ const isDivider = (l) => /^\|[\s|:-]+\|?\s*$/.test(l);
113
+
114
+ // Find the table's divider row (|---|---|), then insert after the last data
115
+ // row that follows it. Anchoring on the divider matters: without it, a table
116
+ // whose only row is the header inserts ABOVE the divider and breaks the table.
117
+ const dividerIdx = lines.findIndex(isDivider);
118
+
119
+ if (dividerIdx >= 0) {
120
+ let insertAt = dividerIdx;
121
+ for (let i = dividerIdx + 1; i < lines.length; i++) {
122
+ if (/^\|/.test(lines[i])) insertAt = i;
123
+ else if (lines[i].trim() !== '') break;
124
+ }
125
+ lines.splice(insertAt + 1, 0, row);
126
+ content = lines.join('\n');
127
+ } else {
128
+ content = `${content.replace(/\s*$/, '\n')}${row}\n`;
129
+ }
130
+
131
+ fs.writeFileSync(projectsPath, content.replace(/\s*$/, '\n'), 'utf8');
132
+ }
133
+
134
+ /** Write project memory files to <project>/.kracked/. */
135
+ export async function writeProjectMemory({ projectDir, tokens, ask, report }) {
136
+ const krackedDir = path.join(projectDir, '.kracked');
137
+ const files = ['project.md', 'session.md', 'decisions.md'];
138
+
139
+ for (const file of files) {
140
+ const destPath = path.join(krackedDir, file);
141
+ const content = applyTokens(readTemplate(`project/${file}`), tokens);
142
+ await writeFileWithConflictCheck(destPath, content, ask, report);
143
+ }
144
+
145
+ // sdd/tracker.md + sdd/specs/.gitkeep
146
+ const trackerDest = path.join(krackedDir, 'sdd', 'tracker.md');
147
+ const trackerContent = applyTokens(readTemplate('project/sdd/tracker.md'), tokens);
148
+ await writeFileWithConflictCheck(trackerDest, trackerContent, ask, report);
149
+
150
+ const gitkeepDest = path.join(krackedDir, 'sdd', 'specs', '.gitkeep');
151
+ if (!fs.existsSync(gitkeepDest)) {
152
+ fs.mkdirSync(path.dirname(gitkeepDest), { recursive: true });
153
+ fs.writeFileSync(gitkeepDest, '', 'utf8');
154
+ report({ path: gitkeepDest, action: 'written' });
155
+ }
156
+ }
157
+
158
+ /** Write AGENTS.md, CLAUDE.md (shim), and .agents/rules/kracked.md. */
159
+ export async function writeLoaders({ projectDir, tokens, ask, report }) {
160
+ const agentsContent = applyTokens(readTemplate('loaders/AGENTS.md'), tokens);
161
+ await writeFileWithConflictCheck(path.join(projectDir, 'AGENTS.md'), agentsContent, ask, report);
162
+
163
+ const claudeContent = applyTokens(readTemplate('loaders/CLAUDE.md'), tokens);
164
+ await writeFileWithConflictCheck(path.join(projectDir, 'CLAUDE.md'), claudeContent, ask, report);
165
+
166
+ const krackedRulesContent = applyTokens(readTemplate('loaders/antigravity-rules.md'), tokens);
167
+ await writeFileWithConflictCheck(
168
+ path.join(projectDir, '.agents', 'rules', 'kracked.md'),
169
+ krackedRulesContent,
170
+ ask,
171
+ report
172
+ );
173
+ }
174
+
175
+ /**
176
+ * Write the 4 skills to BOTH .claude/skills/<name>/SKILL.md and
177
+ * .agents/skills/<name>/SKILL.md — but only for the harnesses selected.
178
+ * `editors` is an array that may contain 'antigravity' and/or 'claude'.
179
+ */
180
+ export async function writeSkills({ projectDir, tokens, editors, ask, report }) {
181
+ const targets = [];
182
+ if (editors.includes('claude')) targets.push('.claude/skills');
183
+ if (editors.includes('antigravity')) targets.push('.agents/skills');
184
+
185
+ for (const skillName of SKILL_NAMES) {
186
+ const content = applyTokens(readTemplate(`skills/${skillName}/SKILL.md`), tokens);
187
+ for (const targetBase of targets) {
188
+ const destPath = path.join(projectDir, targetBase, skillName, 'SKILL.md');
189
+ await writeFileWithConflictCheck(destPath, content, ask, report);
190
+ }
191
+ }
192
+ }