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 +21 -0
- package/README.md +146 -0
- package/bin/kracked-core.mjs +35 -0
- package/package.json +30 -0
- package/src/detect.mjs +106 -0
- package/src/scaffold.mjs +192 -0
- package/src/wizard.mjs +276 -0
- package/templates/global/identity.md +37 -0
- package/templates/global/lessons-archive.md +29 -0
- package/templates/global/lessons.md +23 -0
- package/templates/global/preferences.md +53 -0
- package/templates/global/projects.md +26 -0
- package/templates/loaders/AGENTS.md +67 -0
- package/templates/loaders/CLAUDE.md +5 -0
- package/templates/loaders/antigravity-rules.md +8 -0
- package/templates/project/decisions.md +34 -0
- package/templates/project/project.md +49 -0
- package/templates/project/sdd/specs/.gitkeep +0 -0
- package/templates/project/sdd/tracker.md +36 -0
- package/templates/project/session.md +23 -0
- package/templates/skills/kracked-boot/SKILL.md +53 -0
- package/templates/skills/kracked-explain/SKILL.md +69 -0
- package/templates/skills/kracked-sdd/SKILL.md +92 -0
- package/templates/skills/kracked-wrap/SKILL.md +60 -0
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
|
+
[](https://www.npmjs.com/package/kracked-core)
|
|
10
|
+
[](./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
|
+
}
|
package/src/scaffold.mjs
ADDED
|
@@ -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
|
+
}
|