knight-os 0.1.0 β†’ 0.1.2

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/bin/knight.js CHANGED
@@ -8,6 +8,13 @@ const readline = require('readline');
8
8
  const { loadConfig, resolveWorkspace } = require('../src/config');
9
9
  const { chat } = require('../src/chat');
10
10
  const { setup } = require('../src/setup');
11
+ const {
12
+ runMigrations,
13
+ checkVersion,
14
+ refreshTemplates,
15
+ backupWorkspace,
16
+ CURRENT_DATA_VERSION,
17
+ } = require('../src/migrate');
11
18
 
12
19
  const VERSION = '0.1.0';
13
20
  const DEFAULT_WORKSPACE = path.join(process.env.HOME || '~', '.openclaw', 'workspace');
@@ -213,6 +220,52 @@ function commandVersion() {
213
220
  console.log(`knight-os v${VERSION}`);
214
221
  }
215
222
 
223
+ async function commandUpgrade() {
224
+ const workspace = DEFAULT_WORKSPACE;
225
+
226
+ console.log(`\nπŸ”„ Knight OS β€” Upgrade Check`);
227
+ console.log(` Workspace: ${workspace}\n`);
228
+
229
+ if (!fs.existsSync(workspace)) {
230
+ console.log(' ❌ Workspace not found. Run "knight setup" first.\n');
231
+ process.exit(1);
232
+ }
233
+
234
+ // 1. Run data migrations
235
+ const { migrated, backupPath, error } = runMigrations(workspace);
236
+ if (error) {
237
+ console.error(`\n❌ Upgrade failed: ${error.message}\n`);
238
+ process.exit(1);
239
+ }
240
+
241
+ if (!migrated) {
242
+ const { currentVersion } = checkVersion(workspace);
243
+ console.log(` βœ… Already up to date (data v${currentVersion}).\n`);
244
+ }
245
+
246
+ // 2. Refresh non-protected template files (add new ones, skip existing)
247
+ console.log(' Checking for new template files…');
248
+ const { added, skipped } = refreshTemplates(workspace, TEMPLATES_DIR);
249
+ if (added.length > 0) {
250
+ console.log(` βœ… Added ${added.length} new file(s):`);
251
+ added.forEach((f) => console.log(` + ${f}`));
252
+ } else {
253
+ console.log(' βœ… No new template files.');
254
+ }
255
+
256
+ const protectedSkipped = skipped.filter((s) => s.includes('(protected)'));
257
+ if (protectedSkipped.length > 0) {
258
+ console.log(`\n πŸ”’ Protected files untouched (your personal data is safe):`);
259
+ protectedSkipped.forEach((f) => console.log(` ${f}`));
260
+ }
261
+
262
+ if (backupPath) {
263
+ console.log(`\n πŸ“¦ Backup kept at:\n ${backupPath}`);
264
+ }
265
+
266
+ console.log(`\nβœ… Upgrade complete. Workspace is at data v${CURRENT_DATA_VERSION}.\n`);
267
+ }
268
+
216
269
  async function commandChat() {
217
270
  const config = loadConfig();
218
271
  const workspace = resolveWorkspace(config);
@@ -234,6 +287,9 @@ switch (command) {
234
287
  case 'status':
235
288
  commandStatus();
236
289
  break;
290
+ case 'upgrade':
291
+ commandUpgrade();
292
+ break;
237
293
  case 'version':
238
294
  case '--version':
239
295
  case '-v':
@@ -247,6 +303,7 @@ switch (command) {
247
303
  console.log(' init Initialize a new workspace (standalone, no OpenClaw required)');
248
304
  console.log(' chat Start interactive AI chat session');
249
305
  console.log(' status Check workspace file status');
306
+ console.log(' upgrade Migrate workspace data + refresh template files safely');
250
307
  console.log(' version Show version number');
251
308
  console.log('');
252
309
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knight-os",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "AI companion OS for OpenClaw β€” memory, reflection, and identity framework",
5
5
  "main": "bin/knight.js",
6
6
  "bin": {
package/src/migrate.js ADDED
@@ -0,0 +1,306 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * migrate.js β€” Safe upgrade framework for knight-os
5
+ *
6
+ * Design principles:
7
+ * 1. Data and code live in different places β€” npm upgrades never touch user data
8
+ * 2. Version file (.knight-version) tracks the data format version
9
+ * 3. Before any migration: full backup to .knight-backups/<timestamp>/
10
+ * 4. Migrations only ADD or TRANSFORM β€” never delete user content
11
+ * 5. Protected files (SOUL/MEMORY/USER/REDLINES) are never touched
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ // ─────────────────────────────────────────────────────────────
18
+ // Constants
19
+ // ─────────────────────────────────────────────────────────────
20
+
21
+ /** Current data format version expected by this version of knight-os */
22
+ const CURRENT_DATA_VERSION = 1;
23
+
24
+ /** Version file stored in the workspace root */
25
+ const VERSION_FILE = '.knight-version';
26
+
27
+ /** Backup directory inside the workspace */
28
+ const BACKUP_DIR = '.knight-backups';
29
+
30
+ /** Files that must never be overwritten during migration */
31
+ const PROTECTED_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md', 'REDLINES.md'];
32
+
33
+ // ─────────────────────────────────────────────────────────────
34
+ // Version helpers
35
+ // ─────────────────────────────────────────────────────────────
36
+
37
+ /**
38
+ * Read the data version from the workspace.
39
+ * Returns 0 if the file doesn't exist (pre-versioning install).
40
+ */
41
+ function readDataVersion(workspace) {
42
+ const versionPath = path.join(workspace, VERSION_FILE);
43
+ if (!fs.existsSync(versionPath)) return 0;
44
+ const raw = fs.readFileSync(versionPath, 'utf8').trim();
45
+ const parsed = parseInt(raw, 10);
46
+ return isNaN(parsed) ? 0 : parsed;
47
+ }
48
+
49
+ /**
50
+ * Write the data version to the workspace.
51
+ */
52
+ function writeDataVersion(workspace, version) {
53
+ const versionPath = path.join(workspace, VERSION_FILE);
54
+ fs.writeFileSync(versionPath, String(version) + '\n', 'utf8');
55
+ }
56
+
57
+ // ─────────────────────────────────────────────────────────────
58
+ // Backup
59
+ // ─────────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Recursively copy files from src to dst, skipping .knight-backups itself.
63
+ */
64
+ function copyDirRecursive(src, dst) {
65
+ fs.mkdirSync(dst, { recursive: true });
66
+ const entries = fs.readdirSync(src, { withFileTypes: true });
67
+ for (const entry of entries) {
68
+ if (entry.name === BACKUP_DIR) continue; // don't backup backups
69
+ const srcPath = path.join(src, entry.name);
70
+ const dstPath = path.join(dst, entry.name);
71
+ if (entry.isDirectory()) {
72
+ copyDirRecursive(srcPath, dstPath);
73
+ } else {
74
+ fs.copyFileSync(srcPath, dstPath);
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Create a timestamped backup of the entire workspace.
81
+ * Returns the backup path so callers can report it to the user.
82
+ */
83
+ function backupWorkspace(workspace) {
84
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
85
+ const backupPath = path.join(workspace, BACKUP_DIR, timestamp);
86
+ console.log(` πŸ“¦ Backing up workspace to ${backupPath} …`);
87
+ copyDirRecursive(workspace, backupPath);
88
+ console.log(` βœ… Backup complete.`);
89
+ return backupPath;
90
+ }
91
+
92
+ // ─────────────────────────────────────────────────────────────
93
+ // Migration registry
94
+ // ─────────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Each migration:
98
+ * from β€” data version before this migration
99
+ * to β€” data version after this migration
100
+ * desc β€” human-readable description
101
+ * run(workspace) β€” the actual migration function; must not throw on clean workspaces
102
+ */
103
+ const MIGRATIONS = [
104
+ {
105
+ from: 0,
106
+ to: 1,
107
+ desc: 'Bootstrap versioning β€” record baseline data version for existing installs',
108
+ run(workspace) {
109
+ // Ensure memory subdirectories exist (previously optional)
110
+ const memoryDirs = [
111
+ 'memory/logs',
112
+ 'memory/projects',
113
+ 'memory/templates',
114
+ 'memory/references',
115
+ ];
116
+ for (const dir of memoryDirs) {
117
+ const fullPath = path.join(workspace, dir);
118
+ if (!fs.existsSync(fullPath)) {
119
+ fs.mkdirSync(fullPath, { recursive: true });
120
+ console.log(` πŸ“ Created missing directory: ${dir}/`);
121
+ }
122
+ }
123
+
124
+ // Add UPGRADE.md so users know migration ran
125
+ const upgradePath = path.join(workspace, 'UPGRADE.md');
126
+ if (!fs.existsSync(upgradePath)) {
127
+ fs.writeFileSync(
128
+ upgradePath,
129
+ [
130
+ '# Upgrade Log',
131
+ '',
132
+ 'knight-os upgrade history for this workspace.',
133
+ 'This file is auto-maintained β€” do not edit.',
134
+ '',
135
+ ].join('\n'),
136
+ 'utf8'
137
+ );
138
+ }
139
+ // Append an entry
140
+ const entry = `\n## v1 β€” ${new Date().toISOString().slice(0, 10)}\n- Baseline version established\n- memory/ subdirectories ensured\n`;
141
+ fs.appendFileSync(upgradePath, entry, 'utf8');
142
+ },
143
+ },
144
+
145
+ // ── Future migrations go here ──────────────────────────────
146
+ //
147
+ // Example v1 β†’ v2:
148
+ // {
149
+ // from: 1,
150
+ // to: 2,
151
+ // desc: 'Rename ai-patterns.md β†’ noa-patterns.md',
152
+ // run(workspace) {
153
+ // const oldPath = path.join(workspace, 'memory', 'ai-patterns.md');
154
+ // const newPath = path.join(workspace, 'memory', 'noa-patterns.md');
155
+ // if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
156
+ // fs.renameSync(oldPath, newPath);
157
+ // }
158
+ // },
159
+ // },
160
+ ];
161
+
162
+ // ─────────────────────────────────────────────────────────────
163
+ // Migration runner
164
+ // ─────────────────────────────────────────────────────────────
165
+
166
+ /**
167
+ * Check if the workspace data is up to date.
168
+ * Returns { needsMigration: bool, currentVersion: number, targetVersion: number }
169
+ */
170
+ function checkVersion(workspace) {
171
+ const currentVersion = readDataVersion(workspace);
172
+ return {
173
+ needsMigration: currentVersion < CURRENT_DATA_VERSION,
174
+ currentVersion,
175
+ targetVersion: CURRENT_DATA_VERSION,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Run all pending migrations for the workspace.
181
+ *
182
+ * - Skips silently if already up to date.
183
+ * - Creates a backup before running any migrations.
184
+ * - Runs migrations in order, updating the version file after each one.
185
+ * - If a migration throws, stops immediately (version file reflects last successful step).
186
+ *
187
+ * Returns { migrated: bool, backupPath: string|null, error: Error|null }
188
+ */
189
+ function runMigrations(workspace) {
190
+ if (!fs.existsSync(workspace)) {
191
+ return { migrated: false, backupPath: null, error: null };
192
+ }
193
+
194
+ const { needsMigration, currentVersion, targetVersion } = checkVersion(workspace);
195
+
196
+ if (!needsMigration) {
197
+ return { migrated: false, backupPath: null, error: null };
198
+ }
199
+
200
+ const pending = MIGRATIONS.filter(
201
+ (m) => m.from >= currentVersion && m.to <= targetVersion
202
+ ).sort((a, b) => a.from - b.from);
203
+
204
+ if (pending.length === 0) {
205
+ // No migration steps defined yet β€” just bump the version
206
+ writeDataVersion(workspace, targetVersion);
207
+ return { migrated: true, backupPath: null, error: null };
208
+ }
209
+
210
+ console.log(`\nπŸ”„ knight-os: workspace needs upgrade (v${currentVersion} β†’ v${targetVersion})`);
211
+
212
+ // Backup before touching anything
213
+ let backupPath = null;
214
+ try {
215
+ backupPath = backupWorkspace(workspace);
216
+ } catch (err) {
217
+ return {
218
+ migrated: false,
219
+ backupPath: null,
220
+ error: new Error(`Backup failed, aborting migration: ${err.message}`),
221
+ };
222
+ }
223
+
224
+ // Run each pending migration
225
+ for (const migration of pending) {
226
+ console.log(` βš™οΈ Migration ${migration.from}β†’${migration.to}: ${migration.desc}`);
227
+ try {
228
+ migration.run(workspace);
229
+ writeDataVersion(workspace, migration.to);
230
+ console.log(` βœ… Done.`);
231
+ } catch (err) {
232
+ return {
233
+ migrated: false,
234
+ backupPath,
235
+ error: new Error(
236
+ `Migration ${migration.from}β†’${migration.to} failed: ${err.message}\n` +
237
+ `Your data is backed up at: ${backupPath}`
238
+ ),
239
+ };
240
+ }
241
+ }
242
+
243
+ console.log(`\nβœ… Workspace upgraded to v${targetVersion}. Backup kept at:\n ${backupPath}\n`);
244
+ return { migrated: true, backupPath, error: null };
245
+ }
246
+
247
+ // ─────────────────────────────────────────────────────────────
248
+ // Template refresh (for `knight upgrade` command)
249
+ // ─────────────────────────────────────────────────────────────
250
+
251
+ /**
252
+ * Refresh non-protected template files in the workspace.
253
+ * Protected files (SOUL/MEMORY/USER/REDLINES) are always skipped.
254
+ * For all other files: only write if the file doesn't exist yet (safe default).
255
+ * Pass { force: true } to overwrite non-protected existing files.
256
+ *
257
+ * Returns { added: string[], skipped: string[] }
258
+ */
259
+ function refreshTemplates(workspace, templatesDir, opts) {
260
+ opts = opts || {};
261
+ const added = [];
262
+ const skipped = [];
263
+
264
+ function walk(dir, base) {
265
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
266
+ for (const entry of entries) {
267
+ const relPath = path.relative(base, path.join(dir, entry.name));
268
+ if (entry.isDirectory()) {
269
+ walk(path.join(dir, entry.name), base);
270
+ continue;
271
+ }
272
+ const isRoot = !relPath.includes(path.sep);
273
+ const isProtected = isRoot && PROTECTED_FILES.includes(entry.name);
274
+ if (isProtected) {
275
+ skipped.push(relPath + ' (protected)');
276
+ continue;
277
+ }
278
+ const dest = path.join(workspace, relPath);
279
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
280
+ if (!fs.existsSync(dest) || opts.force) {
281
+ fs.copyFileSync(path.join(dir, entry.name), dest);
282
+ added.push(relPath);
283
+ } else {
284
+ skipped.push(relPath);
285
+ }
286
+ }
287
+ }
288
+
289
+ walk(templatesDir, templatesDir);
290
+ return { added, skipped };
291
+ }
292
+
293
+ // ─────────────────────────────────────────────────────────────
294
+ // Exports
295
+ // ─────────────────────────────────────────────────────────────
296
+
297
+ module.exports = {
298
+ CURRENT_DATA_VERSION,
299
+ PROTECTED_FILES,
300
+ readDataVersion,
301
+ writeDataVersion,
302
+ backupWorkspace,
303
+ checkVersion,
304
+ runMigrations,
305
+ refreshTemplates,
306
+ };
package/src/setup.js CHANGED
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const os = require('os');
6
6
  const readline = require('readline');
7
7
  const { execSync, spawnSync } = require('child_process');
8
+ const { runMigrations, writeDataVersion, CURRENT_DATA_VERSION } = require('./migrate');
8
9
 
9
10
  const DEFAULT_WORKSPACE = path.join(os.homedir(), '.openclaw', 'workspace');
10
11
 
@@ -214,14 +215,42 @@ async function setup() {
214
215
  const workspaceExists = fs.existsSync(workspace);
215
216
  const hasCoreFiles = workspaceExists && fs.existsSync(path.join(workspace, 'AGENTS.md'));
216
217
 
217
- let overwrite = true;
218
+ // Files that contain user's personal memory/identity β€” never overwrite by default
219
+ const PROTECTED_FILES = ['SOUL.md', 'MEMORY.md', 'USER.md', 'REDLINES.md'];
220
+ const hasPersonalMemory = hasCoreFiles && PROTECTED_FILES.some(
221
+ f => fs.existsSync(path.join(workspace, f))
222
+ );
223
+
224
+ let overwrite = false;
225
+ let overwriteProtected = false;
226
+
218
227
  if (hasCoreFiles) {
219
- console.log(`\n⚠️ Workspace already exists at: ${workspace}`);
220
- const answer = await ask(rl, 'Overwrite existing files? (y/N)', 'N');
221
- overwrite = answer.toLowerCase().startsWith('y');
222
- if (!overwrite) {
223
- console.log('\nSkipping template write. Continuing with other setup steps...');
228
+ if (hasPersonalMemory) {
229
+ console.log(`\n⚠️ Existing OpenClaw workspace detected at: ${workspace}`);
230
+ console.log(' Protected files found: SOUL.md, MEMORY.md, USER.md, REDLINES.md');
231
+ console.log(' These contain your personal memory and identity.\n');
232
+ console.log(' Knight OS will add missing files and update scripts/templates.');
233
+ console.log(' Your existing memory files will NOT be touched.\n');
234
+ const answer = await ask(rl, 'Also overwrite protected files? (y/N)', 'N');
235
+ overwriteProtected = answer.toLowerCase().startsWith('y');
236
+ if (overwriteProtected) {
237
+ console.log('\n ⚠️ Protected files WILL be overwritten. Existing content will be lost.');
238
+ } else {
239
+ console.log('\n βœ… Protected files preserved. Only missing/new files will be added.');
240
+ }
241
+ overwrite = true; // always write non-protected files (scripts, AGENTS.md, HEARTBEAT.md, PROJECTS.md)
242
+ } else {
243
+ console.log(`\n⚠️ Workspace already exists at: ${workspace}`);
244
+ const answer = await ask(rl, 'Overwrite existing files? (y/N)', 'N');
245
+ overwrite = answer.toLowerCase().startsWith('y');
246
+ overwriteProtected = overwrite;
247
+ if (!overwrite) {
248
+ console.log('\nSkipping template write. Continuing with other setup steps...');
249
+ }
224
250
  }
251
+ } else {
252
+ overwrite = true;
253
+ overwriteProtected = true;
225
254
  }
226
255
 
227
256
  // Create required dirs
@@ -297,15 +326,32 @@ async function setup() {
297
326
  .replace(/\{\{CHANNEL\}\}/g, 'direct');
298
327
  }
299
328
 
300
- function copyTemplates(srcDir, destDir) {
329
+ function copyTemplates(srcDir, destDir, isRoot) {
301
330
  const entries = fs.readdirSync(srcDir, { withFileTypes: true });
302
331
  for (const entry of entries) {
303
332
  const src = path.join(srcDir, entry.name);
304
333
  const dest = path.join(destDir, entry.name);
305
334
  if (entry.isDirectory()) {
306
335
  fs.mkdirSync(dest, { recursive: true });
307
- copyTemplates(src, dest);
336
+ copyTemplates(src, dest, false);
308
337
  } else {
338
+ // Check if this is a protected file (root level only)
339
+ const isProtected = isRoot && PROTECTED_FILES.includes(entry.name);
340
+ if (isProtected && !overwriteProtected) {
341
+ if (!fs.existsSync(dest)) {
342
+ // File doesn't exist yet β€” safe to create
343
+ try {
344
+ const content = fs.readFileSync(src, 'utf-8');
345
+ fs.writeFileSync(dest, fillTemplate(content, vars), 'utf-8');
346
+ console.log(` βœ… ${path.relative(workspace, dest)}`);
347
+ } catch (e) {
348
+ console.log(` ⚠️ ${path.relative(workspace, dest)}: ${e.message}`);
349
+ }
350
+ } else {
351
+ console.log(` πŸ”’ ${path.relative(workspace, dest)} (protected, skipped)`);
352
+ }
353
+ continue;
354
+ }
309
355
  try {
310
356
  const content = fs.readFileSync(src, 'utf-8');
311
357
  fs.writeFileSync(dest, fillTemplate(content, vars), 'utf-8');
@@ -317,7 +363,7 @@ async function setup() {
317
363
  }
318
364
  }
319
365
 
320
- copyTemplates(TEMPLATES_DIR, workspace);
366
+ copyTemplates(TEMPLATES_DIR, workspace, true);
321
367
  } else {
322
368
  console.log(' ⏭️ Templates skipped (existing files preserved)');
323
369
  }
@@ -402,6 +448,9 @@ async function setup() {
402
448
  }
403
449
  }
404
450
 
451
+ // Record the data version so future upgrades know where to start
452
+ writeDataVersion(workspace, CURRENT_DATA_VERSION);
453
+
405
454
  console.log(`\n${separator}`);
406
455
  console.log('βœ… Knight OS setup complete!\n');
407
456
  console.log(`Workspace: ${workspace}`);
@@ -35,7 +35,30 @@ On session start, read files in this order:
35
35
  6. `memory/ai-patterns.md` (load own behavior rules)
36
36
  7. `USER.md` (load user profile)
37
37
  8. `TOOLS.md` (load available tools)
38
- 9. `PROJECTS.md` (load project index β€” on-demand per project)
38
+ 9. `memory/YYYY-MM-DD.md` for today + yesterday (load recent context; skip if file doesn't exist)
39
+ 10. `PROJECTS.md` (load project index β€” on-demand per project)
40
+
41
+ > **Why daily logs?** Without reading recent logs, the AI starts each session with no memory of what happened yesterday. Always load today + yesterday at boot.
42
+
43
+ ## On-Demand Loading Trigger Table
44
+
45
+ Do NOT load everything at boot. Load these files only when the matching situation arises:
46
+
47
+ | Trigger | Load |
48
+ |---------|------|
49
+ | Replying to a message / adjusting tone | `memory/ai-patterns.md` chat section |
50
+ | Before executing a task | `memory/ai-patterns.md` exec section |
51
+ | User mentions a project by name | `memory/projects/<name>/main.md` |
52
+ | Executing a task tied to a project | `memory/projects/<name>/main.md` + latest log |
53
+ | Heartbeat / daily review | `PROJECTS.md` index only (no main.md) |
54
+ | Writing daily report | Update main.md β†’ Current Sprint with today's progress |
55
+ | Writing to memory / log / daily report | Check `memory/ai-patterns.md` memory section |
56
+ | Received group message / someone @-mentioned | group handling rules |
57
+ | Involves code / development / PR | `memory/ai-patterns.md` code section |
58
+ | Using scripts / external tools | `memory/ai-patterns.md` tool section |
59
+ | Writing copy / articles / presentations | `memory/user-patterns.md` writing style section |}
60
+
61
+ > **Principle:** Static identity + rules β†’ system prompt (always present). Long-term memory β†’ load at session start. Project details + situational rules β†’ lazy-load on demand. Per-turn context β†’ conversation history only.
39
62
 
40
63
  ## Memory Structure Quick Reference
41
64
 
@@ -1,23 +1,31 @@
1
1
  # PROJECTS.md β€” {{AI_NAME}} Project Overview
2
2
 
3
- > Active projects index. Update when starting or closing a project.
4
- > Detailed notes β†’ `memory/projects/<name>/main.md`
3
+ > Active projects index. Load this file at every session start β€” keep it short (target: under 40 lines).
4
+ > Full context lives in `memory/projects/<name>/main.md` β€” load on demand when the project is discussed.
5
5
 
6
6
  ---
7
7
 
8
8
  ## Active Projects
9
9
 
10
- | ID | Name | Status | Priority | Started | Note |
11
- |----|------|--------|----------|---------|------|
12
- | β€” | _(add your first project)_ | β€” | β€” | β€” | β€” |
10
+ | Name | Status | Priority | One-liner |
11
+ |------|--------|----------|-----------|
12
+ | _(add your first project)_ | 🟒 | β€” | _(what is this?)_ |
13
+
14
+ Status: 🟒 Active / 🟑 On Hold / πŸ”΄ Blocked / βœ… Done
13
15
 
14
16
  ---
15
17
 
16
- ## How to Use This File
18
+ ## Loading Rules
19
+
20
+ {{AI_NAME}} follows these rules for project context:
17
21
 
18
- - One row per project. Keep it scannable.
19
- - Detail goes in `memory/projects/<name>/main.md`
20
- - Status: 🟒 Active / 🟑 On Hold / πŸ”΄ Blocked / βœ… Done
22
+ | When | Do |
23
+ |------|----|
24
+ | User mentions a project name | Load `memory/projects/<name>/main.md` |
25
+ | Executing a task related to a project | Load main.md + most recent project log |
26
+ | Heartbeat / daily review | Scan PROJECTS.md index only (no main.md) |
27
+ | Writing daily report | Update main.md β†’ Current Sprint section with today's progress |
28
+ | Project not mentioned in session | Do NOT load main.md (save tokens) |
21
29
 
22
30
  ---
23
31
 
@@ -32,8 +40,8 @@ _(Move completed or abandoned projects here)_
32
40
  ```
33
41
  memory/projects/
34
42
  β”œβ”€β”€ <project-name>/
35
- β”‚ β”œβ”€β”€ main.md # Full project context (goals, decisions, roadmap)
36
- β”‚ └── logs/ # Session logs specific to this project
43
+ β”‚ β”œβ”€β”€ main.md # Project "workbench" β€” goals, current sprint, blockers, decisions
44
+ β”‚ └── logs/ # Deep history β€” load only when reviewing past decisions
37
45
  ```
38
46
 
39
47
  ### main.md template
@@ -43,18 +51,29 @@ memory/projects/
43
51
 
44
52
  **Status:** 🟒 Active
45
53
  **Started:** YYYY-MM-DD
46
- **Goal:** One sentence.
54
+ **Goal:** One sentence. What does success look like?
47
55
 
48
- ## Context
49
- [What is this? Why does it matter?]
56
+ ## Current Sprint / This Week
57
+ - [ ] Task 1
58
+ - [ ] Task 2
59
+ _(Update this section at the end of each working session)_
60
+
61
+ ## Open Questions / Blockers
62
+ - [YYYY-MM-DD] Question or blocker β€” owner or resolution
63
+
64
+ ## Next Actions (Top 3)
65
+ 1.
66
+ 2.
67
+ 3.
50
68
 
51
69
  ## Key Decisions
52
70
  - YYYY-MM-DD: [Decision and rationale]
53
71
 
54
- ## Milestones
55
- - [ ] M1: [Description]
56
- - [ ] M2: [Description]
72
+ ## Context
73
+ [What is this project? Why does it matter? Who is it for?]
57
74
 
58
- ## Notes
59
- [Anything {{AI_NAME}} should remember between sessions]
75
+ ## Notes for {{AI_NAME}}
76
+ [Anything the AI must remember between sessions β€” constraints, preferences, gotchas]
60
77
  ```
78
+
79
+ > Keep main.md under 100 lines. If it grows longer, move older decisions/context to `logs/archive.md`.