@joenandez/academy 0.4.0-rc.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.
Files changed (53) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +6 -0
  3. package/CHANGELOG.md +46 -0
  4. package/LICENSE +21 -0
  5. package/README.md +209 -0
  6. package/bin/academy +2 -0
  7. package/conformance/README.md +60 -0
  8. package/conformance/discovery.test.mjs +140 -0
  9. package/conformance/envelope.test.mjs +185 -0
  10. package/conformance/error-codes.test.mjs +125 -0
  11. package/conformance/harness.mjs +180 -0
  12. package/conformance/identity.test.mjs +125 -0
  13. package/docs/integration-guide.md +1026 -0
  14. package/hooks/hook_runtime.mjs +100 -0
  15. package/hooks/hooks.json +26 -0
  16. package/hooks/inject_surface.py +122 -0
  17. package/hooks/memory_bridge.mjs +120 -0
  18. package/hooks/memory_store.mjs +66 -0
  19. package/hooks/register_session.mjs +51 -0
  20. package/hooks/sync_memory.mjs +27 -0
  21. package/package.json +41 -0
  22. package/scripts/agent.mjs +3 -0
  23. package/scripts/cli/archive.mjs +161 -0
  24. package/scripts/cli/archived.mjs +82 -0
  25. package/scripts/cli/args.mjs +282 -0
  26. package/scripts/cli/codex.mjs +216 -0
  27. package/scripts/cli/core.mjs +389 -0
  28. package/scripts/cli/create.mjs +242 -0
  29. package/scripts/cli/doctor.mjs +203 -0
  30. package/scripts/cli/eventlog.mjs +129 -0
  31. package/scripts/cli/events.mjs +80 -0
  32. package/scripts/cli/hire-headless.mjs +229 -0
  33. package/scripts/cli/hire-spec.mjs +164 -0
  34. package/scripts/cli/hire.mjs +92 -0
  35. package/scripts/cli/inspect.mjs +286 -0
  36. package/scripts/cli/lifecycle.mjs +296 -0
  37. package/scripts/cli/main.mjs +102 -0
  38. package/scripts/cli/migrate.mjs +183 -0
  39. package/scripts/cli/notes.mjs +104 -0
  40. package/scripts/cli/rename.mjs +172 -0
  41. package/scripts/cli/run.mjs +227 -0
  42. package/scripts/cli/runtime.mjs +47 -0
  43. package/scripts/cli/scaffold.mjs +332 -0
  44. package/scripts/cli/sessions.mjs +98 -0
  45. package/scripts/cli/templates.mjs +104 -0
  46. package/scripts/cli/yaml.mjs +124 -0
  47. package/skills/hire/SKILL.md +669 -0
  48. package/templates/agents/claude-code/knowledge-curator.md +14 -0
  49. package/templates/agents/codex/knowledge-curator.toml +9 -0
  50. package/templates/skills/check-in/SKILL.md +122 -0
  51. package/templates/skills/knowledge-curation/SKILL.md +132 -0
  52. package/templates/skills/nightly-consolidation/SKILL.md +240 -0
  53. package/templates/skills/self-update/SKILL.md +121 -0
@@ -0,0 +1,47 @@
1
+ import { resolve } from 'node:path';
2
+ import { exitJsonError, jsonMode } from './core.mjs';
3
+ import { readAgentYaml } from './yaml.mjs';
4
+
5
+ // Two vocabularies meet here and nowhere else. The `runtime:` yaml scalar and
6
+ // the published `runtimeProvider` field say claude_code; the `--agent` token
7
+ // and the internal RUNTIMES set say claude-code. Map at this boundary so
8
+ // neither form leaks into the other.
9
+ const PROVIDER_BY_TOKEN = { 'claude-code': 'claude_code', codex: 'codex' };
10
+ const TOKEN_BY_PROVIDER = { claude_code: 'claude-code', codex: 'codex' };
11
+
12
+ /** The provider of an agent that never declared one is claude_code. */
13
+ const DEFAULT_RUNTIME_PROVIDER = 'claude_code';
14
+
15
+ export function toRuntimeProvider(token) {
16
+ return PROVIDER_BY_TOKEN[token];
17
+ }
18
+
19
+ export function toRuntimeToken(provider) {
20
+ return TOKEN_BY_PROVIDER[provider];
21
+ }
22
+
23
+ // Reporting a runtime and resolving one are different questions. This answers
24
+ // the reporting one and never exits: an unsupported scalar reads as null, which
25
+ // is neither claude_code nor codex and so cannot be mistaken for either. A
26
+ // roster built on this stays whole when one hand-edited agent.yaml carries a
27
+ // typo, and still refuses to name a provider Academy did not resolve.
28
+ export function runtimeProviderOrNull(dir) {
29
+ const value = readAgentYaml(dir).runtime;
30
+ if (!value) return DEFAULT_RUNTIME_PROVIDER;
31
+ return TOKEN_BY_PROVIDER[value] ? value : null;
32
+ }
33
+
34
+ // The resolving question, layered on the reporting one. `inspect <name>` and
35
+ // `run` both need a runtime they can act on, so an unreadable one is raised
36
+ // rather than defaulted: reporting the wrong provider is the defect this
37
+ // scalar exists to end.
38
+ export function readRuntimeProvider(dir) {
39
+ const provider = runtimeProviderOrNull(dir);
40
+ if (provider) return provider;
41
+
42
+ const value = readAgentYaml(dir).runtime;
43
+ const message = `Agent runtime "${value}" is not a runtime Academy supports. Use ${Object.keys(TOKEN_BY_PROVIDER).join(' or ')}.`;
44
+ if (jsonMode()) exitJsonError('invalid_runtime', message, { runtime: value, dir: resolve(dir) });
45
+ console.error(`Error: ${message}`);
46
+ process.exit(1);
47
+ }
@@ -0,0 +1,332 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import {
4
+ ACADEMY_ROOT,
5
+ ACADEMY_SYSTEM_PROMPT,
6
+ SURFACES,
7
+ UNIVERSAL_SKILLS,
8
+ ensureAcademyGitignore,
9
+ ensureSymlink,
10
+ memoryBridgeEnabled,
11
+ projectPluginDir,
12
+ } from './core.mjs';
13
+ import { TEMPLATES } from './templates.mjs';
14
+ import {
15
+ MEMORY_BRIDGE_PERMISSIONS,
16
+ MEMORY_BRIDGE_SKILL_GUIDANCE,
17
+ } from '../../hooks/memory_bridge.mjs';
18
+
19
+ export function scaffoldBootFiles(dir, name) {
20
+ const today = new Date().toISOString().slice(0, 10);
21
+ for (const surface of SURFACES) {
22
+ const filename = `${surface}.md`;
23
+ const path = join(dir, filename);
24
+ if (existsSync(path)) continue;
25
+ writeFileSync(path, TEMPLATES[filename](name, today));
26
+ }
27
+ return today;
28
+ }
29
+
30
+ export function writeAgentYaml(dir, name, today) {
31
+ const yaml = `# Agent metadata. Hand-edit role and objective once the hire flow runs.
32
+ name: ${name}
33
+ created: ${today}
34
+ # Launch runtime — claude_code or codex. \`academy run --agent\` rewrites it.
35
+ runtime: claude_code
36
+ role: ""
37
+ objective: ""
38
+
39
+ # Boot context — 8 surfaces, ~5–6k tokens combined (see scope §3).
40
+ # Files live alongside this yaml; academy run compiles them into the generated
41
+ # .academy/generated/${ACADEMY_SYSTEM_PROMPT} before launching an agent runtime.
42
+ surfaces:
43
+ - identity.md
44
+ - role.md
45
+ - knowledge.md
46
+ - goals.md
47
+ - priorities.md
48
+ - threads.md
49
+ - notes.md
50
+ - dailys.md
51
+ `;
52
+ writeFileSync(join(dir, 'agent.yaml'), yaml);
53
+ }
54
+
55
+ export function writeAgentClaudeMd(dir, name) {
56
+ // Tiny CLAUDE.md — intentionally minimal. Identity lives in identity.md, the
57
+ // user instructions channel can grow over time but starts effectively empty.
58
+ const md = `# ${name}
59
+
60
+ User instructions for this agent. The 8 boot surfaces (\`identity.md\`,
61
+ \`role.md\`, \`knowledge.md\`, \`goals.md\`, \`priorities.md\`, \`threads.md\`,
62
+ \`notes.md\`, \`dailys.md\`) are compiled into \`.academy/generated/${ACADEMY_SYSTEM_PROMPT}\`
63
+ when \`academy run ${name}\` launches an agent runtime.
64
+
65
+ Add user-driven instructions below as they come up.
66
+ `;
67
+ writeFileSync(join(dir, 'CLAUDE.md'), md);
68
+ }
69
+
70
+ export function writeOwnershipMarker(dir, name) {
71
+ writeFileSync(
72
+ join(dir, '.academy-agent.json'),
73
+ JSON.stringify(
74
+ {
75
+ capability: 'academy-agent',
76
+ name,
77
+ packageRoot: ACADEMY_ROOT,
78
+ createdAt: new Date().toISOString(),
79
+ },
80
+ null,
81
+ 2,
82
+ ) + '\n',
83
+ );
84
+ }
85
+
86
+ export function writePluginSymlink(dir) {
87
+ // The portable plugin layout (§2): each agent has its own .claude-plugin/
88
+ // pointing at the Academy package's plugin manifest, so lifecycle hooks fire
89
+ // when Claude Code runs in the agent's cwd.
90
+ ensureSymlink(join(ACADEMY_ROOT, '.claude-plugin'), join(dir, '.claude-plugin'));
91
+
92
+ // Also symlink hooks/ so plugin.json's relative ./hooks/hooks.json resolves.
93
+ ensureSymlink(join(ACADEMY_ROOT, 'hooks'), join(dir, 'hooks'));
94
+ }
95
+
96
+ export function writeProjectPluginInstance(projectDir, name, dir) {
97
+ const pluginDir = projectPluginDir(projectDir, name);
98
+ mkdirSync(pluginDir, { recursive: true });
99
+
100
+ ensureSymlink(join(ACADEMY_ROOT, '.claude-plugin'), join(pluginDir, '.claude-plugin'));
101
+ ensureSymlink(join(ACADEMY_ROOT, 'hooks'), join(pluginDir, 'hooks'));
102
+
103
+ const agentSkillsDir = join(dir, '.claude', 'skills');
104
+ if (existsSync(agentSkillsDir)) ensureSymlink(agentSkillsDir, join(pluginDir, 'skills'));
105
+ const agentDefinitionsDir = join(dir, '.claude', 'agents');
106
+ if (existsSync(agentDefinitionsDir))
107
+ ensureSymlink(agentDefinitionsDir, join(pluginDir, 'agents'));
108
+
109
+ writeFileSync(
110
+ join(pluginDir, 'instance.json'),
111
+ JSON.stringify(
112
+ {
113
+ agent: name,
114
+ agentDir: dir,
115
+ projectPath: resolve(projectDir),
116
+ lastActive: new Date().toISOString(),
117
+ },
118
+ null,
119
+ 2,
120
+ ) + '\n',
121
+ );
122
+
123
+ ensureAcademyGitignore(projectDir);
124
+ return pluginDir;
125
+ }
126
+
127
+ export function writeProjectCodexSkillBridge(projectDir, dir) {
128
+ const agentSkillsDir = join(dir, '.agents', 'skills');
129
+ const projectSkillsDir = join(projectDir, '.agents', 'skills');
130
+ mkdirSync(projectSkillsDir, { recursive: true });
131
+
132
+ for (const skillName of UNIVERSAL_SKILLS) {
133
+ const source = join(agentSkillsDir, skillName);
134
+ const target = join(projectSkillsDir, skillName);
135
+ if (!existsSync(source) || existsSync(target)) continue;
136
+ ensureSymlink(source, target);
137
+ }
138
+ }
139
+
140
+ function renderTemplate(template, values) {
141
+ const rendered = template.replace(/\{\{([a-z_]+)\}\}/g, (match, key) => {
142
+ if (!(key in values)) throw new Error(`Unknown template variable: ${match}`);
143
+ return values[key];
144
+ });
145
+ // An optional section that renders empty must not leave a hole in a surface
146
+ // Academy charges a token budget for.
147
+ return rendered.replace(/\n{3,}/g, '\n\n');
148
+ }
149
+
150
+ function universalSkillValues(dir, name, skillsDir) {
151
+ const dreamsDir = join(dir, 'dreams');
152
+ return {
153
+ agent_name: name,
154
+ agent_dir: dir,
155
+ identity_path: join(dir, 'identity.md'),
156
+ role_path: join(dir, 'role.md'),
157
+ knowledge_path: join(dir, 'knowledge.md'),
158
+ goals_path: join(dir, 'goals.md'),
159
+ priorities_path: join(dir, 'priorities.md'),
160
+ threads_path: join(dir, 'threads.md'),
161
+ notes_path: join(dir, 'notes.md'),
162
+ dailys_path: join(dir, 'dailys.md'),
163
+ memory_observations_path: join(dir, 'memory', 'observations'),
164
+ memory_bridge_guidance: memoryBridgeEnabled() ? MEMORY_BRIDGE_SKILL_GUIDANCE : '',
165
+ dreams_dir: dreamsDir,
166
+ skills_surface: skillsDir.endsWith(join('.agents', 'skills'))
167
+ ? '.agents/skills'
168
+ : '.claude/skills',
169
+ skills_dir: skillsDir,
170
+ check_in_path: join(skillsDir, 'check-in', 'SKILL.md'),
171
+ self_update_path: join(skillsDir, 'self-update', 'SKILL.md'),
172
+ nightly_consolidation_path: join(skillsDir, 'nightly-consolidation', 'SKILL.md'),
173
+ knowledge_curation_path: join(skillsDir, 'knowledge-curation', 'SKILL.md'),
174
+ };
175
+ }
176
+
177
+ function writeUniversalSkill(dir, name, skillName, skillsDir) {
178
+ const skillDir = join(skillsDir, skillName);
179
+ const templatePath = join(ACADEMY_ROOT, 'templates', 'skills', skillName, 'SKILL.md');
180
+ const template = readFileSync(templatePath, 'utf8');
181
+
182
+ mkdirSync(skillDir, { recursive: true });
183
+ writeFileSync(
184
+ join(skillDir, 'SKILL.md'),
185
+ renderTemplate(template, universalSkillValues(dir, name, skillsDir)),
186
+ );
187
+ }
188
+
189
+ export function writeSkillsScaffold(dir, name) {
190
+ // Universal Academy skills are copied into each agent with agent-specific
191
+ // paths. Agent/domain skills can be added later by hire or self-update.
192
+ writeDreamsDir(dir);
193
+ writeMemoryScaffold(dir);
194
+ for (const skillsDir of [join(dir, '.claude', 'skills'), join(dir, '.agents', 'skills')]) {
195
+ mkdirSync(skillsDir, { recursive: true });
196
+ for (const skillName of UNIVERSAL_SKILLS) writeUniversalSkill(dir, name, skillName, skillsDir);
197
+ }
198
+ writeKnowledgeCuratorAgents(dir, name);
199
+ }
200
+
201
+ function writeKnowledgeCuratorAgents(dir, name) {
202
+ const definitions = [
203
+ {
204
+ source: join(ACADEMY_ROOT, 'templates', 'agents', 'claude-code', 'knowledge-curator.md'),
205
+ target: join(dir, '.claude', 'agents', 'knowledge-curator.md'),
206
+ skillsDir: join(dir, '.claude', 'skills'),
207
+ },
208
+ {
209
+ source: join(ACADEMY_ROOT, 'templates', 'agents', 'codex', 'knowledge-curator.toml'),
210
+ target: join(dir, '.codex', 'agents', 'knowledge-curator.toml'),
211
+ skillsDir: join(dir, '.agents', 'skills'),
212
+ },
213
+ ];
214
+
215
+ for (const { source, target, skillsDir } of definitions) {
216
+ mkdirSync(dirname(target), { recursive: true });
217
+ writeFileSync(
218
+ target,
219
+ renderTemplate(readFileSync(source, 'utf8'), universalSkillValues(dir, name, skillsDir)),
220
+ );
221
+ }
222
+ }
223
+
224
+ export function academySystemPromptPath(dir) {
225
+ return join(dir, '.academy', 'generated', ACADEMY_SYSTEM_PROMPT);
226
+ }
227
+
228
+ function surfaceTitle(surface) {
229
+ return `${surface[0].toUpperCase()}${surface.slice(1)}`;
230
+ }
231
+
232
+ function readSurface(dir, surface) {
233
+ const surfaceFile = join(dir, `${surface}.md`);
234
+ if (!existsSync(surfaceFile))
235
+ return `# ${surfaceTitle(surface)}\n\n_(No ${surface}.md present yet.)_`;
236
+ const body = readFileSync(surfaceFile, 'utf8').trimEnd();
237
+ return body || `# ${surfaceTitle(surface)}\n\n_(${surface}.md is empty.)_`;
238
+ }
239
+
240
+ export function buildAcademySystemPrompt(dir, name) {
241
+ const intro = [
242
+ '<!-- Generated by `academy`. Do not edit this file directly. Edit the source surfaces instead. -->',
243
+ '',
244
+ `# Academy Agent: ${name}`,
245
+ '',
246
+ 'You are an Academy v3 agent. Treat the following surfaces as your durable identity, role, knowledge, goals, priorities, active threads, notes, and recent daily context.',
247
+ '',
248
+ 'Capture transient steering cheaply with `academy notes add "..."` — it appends a timestamped bullet to your notes.md without reading the whole file. Use it for corrections, stakeholder facts, caveats, and raw learnings before they are durable enough for another surface; review with `academy notes list`. The self-update skill explains when a note should graduate or expire.',
249
+ '',
250
+ ];
251
+ const surfaces = SURFACES.map((surface) => {
252
+ const path = join(dir, `${surface}.md`);
253
+ const marker = `<!-- academy:surface:${surface} -->`;
254
+ const content = readSurface(dir, surface);
255
+ return {
256
+ name: surface,
257
+ file: `${surface}.md`,
258
+ path,
259
+ exists: existsSync(path),
260
+ marker,
261
+ content,
262
+ };
263
+ });
264
+ const prompt = [
265
+ ...intro,
266
+ ...surfaces.flatMap((surface) => [surface.marker, surface.content, '']),
267
+ ].join('\n');
268
+ return {
269
+ prompt: prompt.endsWith('\n') ? prompt : `${prompt}\n`,
270
+ intro,
271
+ surfaces,
272
+ };
273
+ }
274
+
275
+ export function renderAcademySystemPrompt(dir, name) {
276
+ mkdirSync(dirname(academySystemPromptPath(dir)), { recursive: true });
277
+ const { prompt } = buildAcademySystemPrompt(dir, name);
278
+ const promptPath = academySystemPromptPath(dir);
279
+ writeFileSync(promptPath, prompt);
280
+ return promptPath;
281
+ }
282
+
283
+ function estimateTokens(text) {
284
+ const normalized = text.trim();
285
+ if (!normalized) return 0;
286
+ const chunks = normalized.match(/[\p{L}\p{N}]+|[^\s\p{L}\p{N}]/gu) ?? [];
287
+ return Math.max(chunks.length, Math.ceil(text.length / 4));
288
+ }
289
+
290
+ export function tokenRecord(text) {
291
+ return {
292
+ estimatedTokens: estimateTokens(text),
293
+ chars: text.length,
294
+ };
295
+ }
296
+
297
+ function writeDreamsDir(dir) {
298
+ mkdirSync(join(dir, 'dreams'), { recursive: true });
299
+ }
300
+
301
+ function writeMemoryScaffold(dir) {
302
+ mkdirSync(join(dir, 'memory', 'observations'), { recursive: true });
303
+ const sessionsPath = join(dir, 'memory', 'sessions.jsonl');
304
+ if (!existsSync(sessionsPath)) writeFileSync(sessionsPath, '');
305
+ }
306
+
307
+ export function writeSettingsLocal(dir) {
308
+ // Permissions template — Phase 0 is permissive; tighten later if needed.
309
+ // Plugin discovery happens via the symlinked .claude-plugin/ in agent dir.
310
+ const settings = {
311
+ permissions: {
312
+ allow: [
313
+ 'Bash(academy:*)',
314
+ 'Bash(helm:*)',
315
+ 'Bash(helm-tasks:*)',
316
+ ...(memoryBridgeEnabled() ? MEMORY_BRIDGE_PERMISSIONS : []),
317
+ 'Bash(date:*)',
318
+ 'Read',
319
+ 'Edit',
320
+ 'Write',
321
+ 'Glob',
322
+ 'Grep',
323
+ 'TodoWrite',
324
+ ],
325
+ },
326
+ };
327
+ mkdirSync(join(dir, '.claude'), { recursive: true });
328
+ writeFileSync(
329
+ join(dir, '.claude', 'settings.local.json'),
330
+ JSON.stringify(settings, null, 2) + '\n',
331
+ );
332
+ }
@@ -0,0 +1,98 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join, resolve } from 'node:path';
4
+ import {
5
+ AGENTS_ROOT,
6
+ checkAgentsRoot,
7
+ contractOk,
8
+ exitJsonError,
9
+ isInside,
10
+ validateName,
11
+ } from './core.mjs';
12
+
13
+ // The session index is global across installs — the specialists work needs one
14
+ // file a client can read whichever root it drives. Attribution is therefore a
15
+ // read-time question, answered by `agentDir` containment inside the resolved
16
+ // AGENTS_ROOT, and never by the agent name: two roots can hold a "kai".
17
+ //
18
+ // `sessions --json [--agent <name>]` payload, for the conformance suite:
19
+ // agentsRoot absolute, resolved AGENTS_ROOT the rows were filtered against
20
+ // sessionIndex absolute path of the file the rows were read from
21
+ // sessions[] index order, oldest first, each row exactly:
22
+ // { sessionId, agentName, agentDir, cwd, startedAt }
23
+
24
+ function sessionIndexPath() {
25
+ return join(homedir(), '.academy', 'sessions.jsonl');
26
+ }
27
+
28
+ // Guarded on every read. `doctor` counts unattributable rows through this same
29
+ // reader and must never exit from inside a probe, and a torn or hand-edited
30
+ // line is not a reason to withhold every other session.
31
+ function readIndex() {
32
+ const path = sessionIndexPath();
33
+ if (!existsSync(path)) return [];
34
+ try {
35
+ return readFileSync(path, 'utf8').split('\n').map(parseRow).filter(Boolean);
36
+ } catch {
37
+ return [];
38
+ }
39
+ }
40
+
41
+ function parseRow(line) {
42
+ if (line.trim() === '') return null;
43
+ try {
44
+ const row = JSON.parse(line);
45
+ return row && typeof row === 'object' && row.sessionId ? row : null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ // Records written before the index carried a directory. They are historical,
52
+ // not corrupt: nothing rewrites or removes them, they are left out of every
53
+ // root's answer, and `doctor` reports how many there are.
54
+ export function unattributableSessionCount() {
55
+ return readIndex().filter((row) => !row.agentDir).length;
56
+ }
57
+
58
+ function attributed(row, rootReal, agent) {
59
+ if (!row.agentDir) return false;
60
+ if (agent && row.agentName !== agent) return false;
61
+ return isInside(row.agentDir, rootReal);
62
+ }
63
+
64
+ function publishedRow(row) {
65
+ return {
66
+ sessionId: row.sessionId,
67
+ agentName: row.agentName ?? null,
68
+ agentDir: row.agentDir,
69
+ cwd: row.cwd ?? null,
70
+ startedAt: row.startedAt ?? null,
71
+ };
72
+ }
73
+
74
+ export function readSessions({ json, agent, invalidOption }) {
75
+ if (invalidOption !== undefined) return unreadableInvocation(invalidOption, json);
76
+ if (agent !== undefined) validateName(agent, json);
77
+ checkAgentsRoot(json);
78
+ const agentsRoot = resolve(AGENTS_ROOT);
79
+ const sessions = readIndex()
80
+ .filter((row) => attributed(row, agentsRoot, agent))
81
+ .map(publishedRow);
82
+
83
+ if (json) {
84
+ contractOk('sessions', { agentsRoot, sessionIndex: sessionIndexPath(), sessions });
85
+ return;
86
+ }
87
+ console.log(`sessions ${agentsRoot} index ${sessionIndexPath()}`);
88
+ for (const row of sessions) {
89
+ console.log(` ${row.startedAt} ${row.agentName} ${row.sessionId}`);
90
+ }
91
+ }
92
+
93
+ function unreadableInvocation(option, json) {
94
+ const message = `Unknown sessions option: ${option}. Use [--agent <name>] [--json].`;
95
+ if (json) exitJsonError('invalid_spec', message, { option });
96
+ console.error(`Error: ${message}`);
97
+ process.exit(1);
98
+ }
@@ -0,0 +1,104 @@
1
+ export const TEMPLATES = {
2
+ 'identity.md': (name, today) =>
3
+ `# Identity
4
+
5
+ _(Who you are — values, character, voice, persona/backstory.)_
6
+
7
+ You are ${name}.
8
+
9
+ _(Hire flow will populate this. See \`academy hire\`.)_
10
+
11
+ ---
12
+ _Created: ${today}._
13
+ `,
14
+ 'role.md': (name, today) =>
15
+ `# Role
16
+
17
+ _(What you do — job, responsibilities, scope, deliverable shape, cadence.)_
18
+
19
+ _(Hire flow will populate this. See \`academy hire\`.)_
20
+
21
+ ---
22
+ _Created: ${today}._
23
+ `,
24
+ 'knowledge.md': (name, today) =>
25
+ `# Knowledge
26
+
27
+ _(What you know — domain expertise, mental models, frameworks, learned patterns.)_
28
+
29
+ Lightweight sections by domain (max 5–8). Dated entries within sections.
30
+ Curation is by reference + uniqueness + user signal — see scope §7.
31
+
32
+ _(Hire flow will populate this from research. See \`academy hire\`.)_
33
+
34
+ ---
35
+ _Created: ${today}._
36
+ `,
37
+ 'goals.md': (name, today) =>
38
+ `# Goals
39
+
40
+ _(Strategic direction — quarterly horizon, hard cap of 3.)_
41
+
42
+ 1. _(goal one)_
43
+ 2. _(goal two)_
44
+ 3. _(goal three)_
45
+
46
+ ---
47
+ _Created: ${today}. Re-affirm every 14 days._
48
+ `,
49
+ 'priorities.md': (name, today) =>
50
+ `# Priorities
51
+
52
+ _(Weekly direction — WIP-limited, 3–5 visible.)_
53
+
54
+ - _(priority one)_
55
+ - _(priority two)_
56
+
57
+ ---
58
+ _Created: ${today}._
59
+ `,
60
+ 'threads.md': (name, today) =>
61
+ `# Threads
62
+
63
+ _(Active work pursuits — 5 active visible / 8 idle. Auto-demote by \`last_touched\`.)_
64
+
65
+ ## Active
66
+
67
+ _(none yet)_
68
+
69
+ ## Idle
70
+
71
+ _(none yet)_
72
+
73
+ ## Parked
74
+
75
+ _(none yet)_
76
+
77
+ ---
78
+ _Created: ${today}._
79
+ `,
80
+ 'notes.md': (name, today) =>
81
+ `# Notes
82
+
83
+ _(Micro-steering staging area — 8–12 visible cap. Graduate or expire.)_
84
+
85
+ _Capture temporary steering, corrections, stakeholder facts, caveats, and raw
86
+ learnings here with_ \`academy notes add "..."\` _— it appends a timestamped
87
+ bullet without rewriting this file. Review with_ \`academy notes list\`_._
88
+
89
+ _(none yet)_
90
+
91
+ ---
92
+ _Created: ${today}. Long-staying notes are a smell — they should graduate to knowledge / role / identity / skill, or expire._
93
+ `,
94
+ 'dailys.md': (name, today) =>
95
+ `# Recent Days
96
+
97
+ _(Tight summaries, last 7 working days, FIFO.)_
98
+
99
+ _(none yet — populated by daily primitive in Phase 3.)_
100
+
101
+ ---
102
+ _Created: ${today}._
103
+ `,
104
+ };