@pat-lewczuk/cezar 0.1.0 → 0.1.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/dist/config.d.ts +55 -0
  2. package/dist/config.js +44 -0
  3. package/dist/config.js.map +1 -0
  4. package/dist/core/agent-runner.d.ts +13 -0
  5. package/dist/core/claude-cli-runner.js +23 -2
  6. package/dist/core/claude-cli-runner.js.map +1 -1
  7. package/dist/git-worktree.d.ts +49 -0
  8. package/dist/git-worktree.js +129 -0
  9. package/dist/git-worktree.js.map +1 -0
  10. package/dist/handoff.d.ts +42 -0
  11. package/dist/handoff.js +120 -0
  12. package/dist/handoff.js.map +1 -0
  13. package/dist/index.js +27 -6
  14. package/dist/index.js.map +1 -1
  15. package/dist/planner.d.ts +17 -0
  16. package/dist/planner.js +244 -0
  17. package/dist/planner.js.map +1 -0
  18. package/dist/runs/store.d.ts +40 -13
  19. package/dist/runs/store.js +33 -3
  20. package/dist/runs/store.js.map +1 -1
  21. package/dist/server/github.d.ts +28 -0
  22. package/dist/server/github.js +133 -0
  23. package/dist/server/github.js.map +1 -0
  24. package/dist/server/launch-key.d.ts +7 -0
  25. package/dist/server/launch-key.js +33 -0
  26. package/dist/server/launch-key.js.map +1 -0
  27. package/dist/server/pr.d.ts +22 -0
  28. package/dist/server/pr.js +92 -0
  29. package/dist/server/pr.js.map +1 -0
  30. package/dist/server/server.js +370 -15
  31. package/dist/server/server.js.map +1 -1
  32. package/dist/skills-remote.d.ts +35 -0
  33. package/dist/skills-remote.js +266 -0
  34. package/dist/skills-remote.js.map +1 -0
  35. package/dist/skills.d.ts +18 -6
  36. package/dist/skills.js +8 -4
  37. package/dist/skills.js.map +1 -1
  38. package/dist/todos.d.ts +60 -0
  39. package/dist/todos.js +166 -0
  40. package/dist/todos.js.map +1 -0
  41. package/dist/workflows/load.js +5 -9
  42. package/dist/workflows/load.js.map +1 -1
  43. package/dist/workflows/run.d.ts +62 -3
  44. package/dist/workflows/run.js +332 -43
  45. package/dist/workflows/run.js.map +1 -1
  46. package/dist/workflows/types.d.ts +27 -20
  47. package/dist/workflows/types.js +21 -0
  48. package/dist/workflows/types.js.map +1 -1
  49. package/package.json +1 -1
  50. package/scripts/mock-claude.mjs +118 -0
  51. package/web/app.js +1851 -149
  52. package/web/index.html +73 -22
  53. package/web/style.css +1271 -223
@@ -0,0 +1,266 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { basename, dirname, join } from 'node:path';
6
+ import { loadConfig } from './config.js';
7
+ import { parseFrontmatter } from './skills.js';
8
+ /**
9
+ * Team skills from remote git repos (spec 005), janitor-style: a bare clone
10
+ * without a checkout, listed with `git ls-tree` and read with `git show`.
11
+ * The cache lives in `~/.cache/cez/skills/<owner>__<name>/` — global, so one
12
+ * fetch serves every project. Everything degrades: no network / no access to
13
+ * the skills repo means the team skills quietly disappear from the list while
14
+ * local skills keep working. Nothing here ever blocks startup.
15
+ */
16
+ const LIST_TIMEOUT_MS = 10_000; // ls-tree / show / rev-parse
17
+ const CLONE_TIMEOUT_MS = 60_000; // clone / fetch
18
+ function git(args, timeoutMs, cwd) {
19
+ return new Promise((resolve) => {
20
+ execFile('git', args, { cwd, timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 16 * 1024 * 1024, encoding: 'utf8' }, (err, stdout, stderr) => resolve({ ok: !err, stdout: stdout ?? '', stderr: stderr ?? '' }));
21
+ });
22
+ }
23
+ /** `owner/name` shorthand → GitHub HTTPS; anything else (URL, file://, local path) as-is. */
24
+ function remoteFor(repo) {
25
+ return /^[\w.-]+\/[\w.-]+$/.test(repo) ? `https://github.com/${repo}.git` : repo;
26
+ }
27
+ /** Stable cache directory name: the last two path segments, `owner__name`. */
28
+ export function bareDirFor(repo) {
29
+ const trimmed = repo
30
+ .replace(/\/+$/, '')
31
+ .replace(/\.git$/, '')
32
+ .replace(/^[a-z+]+:\/\//i, '')
33
+ .replace(/^~\//, '');
34
+ const segments = trimmed.split(/[/:]/).filter(Boolean).map(sanitizeSegment);
35
+ const key = segments.slice(-2).join('__') || 'skills';
36
+ return join(homedir(), '.cache', 'cez', 'skills', key);
37
+ }
38
+ function sanitizeSegment(s) {
39
+ return s.replace(/[^\w.-]/g, '-');
40
+ }
41
+ /** Clone the skills repo bare (no checkout) into the global cache, once. */
42
+ export async function ensureBareClone(repo) {
43
+ const bareDir = bareDirFor(repo);
44
+ if (existsSync(join(bareDir, 'HEAD')))
45
+ return { bareDir, created: false };
46
+ await mkdir(dirname(bareDir), { recursive: true });
47
+ const remote = remoteFor(repo);
48
+ const res = await git(['clone', '--bare', remote, bareDir], CLONE_TIMEOUT_MS);
49
+ if (!res.ok)
50
+ throw new Error(`git clone --bare ${remote} failed: ${res.stderr.trim()}`);
51
+ return { bareDir, created: true };
52
+ }
53
+ /** "Refresh" — update every branch head in the bare clone from origin. */
54
+ export async function fetchAll(bareDir) {
55
+ const res = await git(['fetch', 'origin', '--prune', '+refs/heads/*:refs/heads/*'], CLONE_TIMEOUT_MS, bareDir);
56
+ if (!res.ok)
57
+ throw new Error(`git fetch failed: ${res.stderr.trim() || res.stdout.trim()}`);
58
+ }
59
+ async function resolveRef(bareDir, ref) {
60
+ for (const candidate of [ref, `refs/heads/${ref}`, 'HEAD']) {
61
+ const probe = await git(['rev-parse', '--verify', '--quiet', candidate], LIST_TIMEOUT_MS, bareDir);
62
+ if (probe.ok)
63
+ return candidate;
64
+ }
65
+ return ref;
66
+ }
67
+ /**
68
+ * Match the janitor conventions plus our own:
69
+ * - `**\/SKILL.md` → skill named after the parent directory (with references/)
70
+ * - `**\/commands/<n>.md` → skill `<n>`
71
+ * - `.ai/skills/**\/*.md` or `.ai/cezar/skills/**\/*.md` → frontmatter/basename name
72
+ */
73
+ function matchSkillPath(line) {
74
+ if (line === 'SKILL.md' || line.endsWith('/SKILL.md')) {
75
+ const parts = line.split('/');
76
+ if (parts.length < 2)
77
+ return null;
78
+ const parent = parts[parts.length - 2];
79
+ return parent && parent !== '.' ? { name: parent, kind: 'skill' } : null;
80
+ }
81
+ const cmd = /(?:^|\/)commands\/([^/]+)\.md$/.exec(line);
82
+ if (cmd)
83
+ return { name: cmd[1], kind: 'command' };
84
+ if (/(?:^|\/)\.ai\/(?:cezar\/)?skills\/.+\.md$/.test(line))
85
+ return { name: null, kind: 'markdown' };
86
+ return null;
87
+ }
88
+ /** Read one file from the bare clone at the source's ref. Null on any failure. */
89
+ export async function readRemoteSkill(src, path) {
90
+ const bareDir = bareDirFor(src.repo);
91
+ if (!existsSync(join(bareDir, 'HEAD')))
92
+ return null;
93
+ const ref = await resolveRef(bareDir, src.ref);
94
+ const res = await git(['show', `${ref}:${path}`], LIST_TIMEOUT_MS, bareDir);
95
+ return res.ok ? res.stdout : null;
96
+ }
97
+ /**
98
+ * List every skill the repo defines at `src.ref`. Reads from the local bare
99
+ * clone only — no network. Empty list when the clone doesn't exist yet or
100
+ * the ref can't be resolved.
101
+ */
102
+ export async function listRemoteSkills(src) {
103
+ const bareDir = bareDirFor(src.repo);
104
+ if (!existsSync(join(bareDir, 'HEAD')))
105
+ return [];
106
+ const ref = await resolveRef(bareDir, src.ref);
107
+ const ls = await git(['ls-tree', '-r', '--name-only', ref], LIST_TIMEOUT_MS, bareDir);
108
+ if (!ls.ok)
109
+ return [];
110
+ const skills = [];
111
+ const seen = new Set();
112
+ for (const line of ls.stdout.split('\n')) {
113
+ if (!line)
114
+ continue;
115
+ const hit = matchSkillPath(line);
116
+ if (!hit)
117
+ continue;
118
+ const raw = await readRemoteSkill(src, line);
119
+ if (raw === null)
120
+ continue;
121
+ const { frontmatter, body } = parseFrontmatter(raw);
122
+ const name = hit.name ??
123
+ (typeof frontmatter.name === 'string' && frontmatter.name.trim()
124
+ ? frontmatter.name.trim()
125
+ : basename(line, '.md'));
126
+ if (seen.has(name))
127
+ continue;
128
+ seen.add(name);
129
+ const description = typeof frontmatter.description === 'string' && frontmatter.description.trim()
130
+ ? frontmatter.description.trim()
131
+ : undefined;
132
+ skills.push({
133
+ name,
134
+ description,
135
+ body,
136
+ path: `${src.repo}@${src.ref}:${line}`,
137
+ source: 'team',
138
+ team: { repo: src.repo, ref: src.ref, path: line, dir: hit.kind === 'skill' },
139
+ });
140
+ }
141
+ return skills;
142
+ }
143
+ // ---- materialization (directory skills) ---------------------------------------
144
+ /**
145
+ * Copy a directory skill (SKILL.md + references/…) out of the bare clone into
146
+ * `<repoRoot>/.claude/skills/<name>/` so claude sees the references on disk,
147
+ * and keep it out of the user's git via `.git/info/exclude`. Returns false
148
+ * when there is nothing to materialize (not a directory skill, no clone…).
149
+ */
150
+ export async function materializeSkillDir(repoRoot, skill) {
151
+ if (!skill.team?.dir || !skill.team.path.endsWith('SKILL.md'))
152
+ return false;
153
+ const bareDir = bareDirFor(skill.team.repo);
154
+ if (!existsSync(join(bareDir, 'HEAD')))
155
+ return false;
156
+ const ref = await resolveRef(bareDir, skill.team.ref);
157
+ const srcDir = skill.team.path.slice(0, -'/SKILL.md'.length);
158
+ const ls = await git(['ls-tree', '-r', '--name-only', ref, '--', srcDir], LIST_TIMEOUT_MS, bareDir);
159
+ if (!ls.ok)
160
+ return false;
161
+ const destDir = join(repoRoot, '.claude', 'skills', skill.name);
162
+ let wrote = 0;
163
+ for (const file of ls.stdout.split('\n').filter(Boolean)) {
164
+ const rel = file.slice(srcDir.length + 1);
165
+ // git paths are repo-relative and normalized, but never trust them blindly.
166
+ if (!rel || rel.split('/').includes('..'))
167
+ continue;
168
+ const show = await git(['show', `${ref}:${file}`], LIST_TIMEOUT_MS, bareDir);
169
+ if (!show.ok)
170
+ continue;
171
+ const target = join(destDir, rel);
172
+ await mkdir(dirname(target), { recursive: true });
173
+ await writeFile(target, show.stdout, 'utf8');
174
+ wrote++;
175
+ }
176
+ if (wrote === 0)
177
+ return false;
178
+ await excludeFromGit(repoRoot, `.claude/skills/${skill.name}/`);
179
+ return true;
180
+ }
181
+ /** Append a pattern to git's `info/exclude` (idempotent, non-fatal). */
182
+ async function excludeFromGit(repoRoot, pattern) {
183
+ try {
184
+ // Resolve the real exclude file: in a linked worktree (spec 006) `.git`
185
+ // is a file and `info/exclude` lives in the shared common dir — which
186
+ // also means one exclude entry covers every task worktree.
187
+ const probe = await git(['rev-parse', '--path-format=absolute', '--git-common-dir'], LIST_TIMEOUT_MS, repoRoot);
188
+ const gitDir = probe.ok && probe.stdout.trim() ? probe.stdout.trim() : join(repoRoot, '.git');
189
+ const excludePath = join(gitDir, 'info', 'exclude');
190
+ let prev = '';
191
+ try {
192
+ prev = await readFile(excludePath, 'utf8');
193
+ }
194
+ catch {
195
+ // no exclude file yet
196
+ }
197
+ if (prev.split('\n').includes(pattern))
198
+ return;
199
+ await mkdir(dirname(excludePath), { recursive: true });
200
+ await writeFile(excludePath, prev + (prev && !prev.endsWith('\n') ? '\n' : '') + pattern + '\n', 'utf8');
201
+ }
202
+ catch {
203
+ // non-fatal — `.git` might be a linked file (worktree) or absent entirely
204
+ }
205
+ }
206
+ // ---- in-process cache ----------------------------------------------------------
207
+ // Clone attempts are expensive when the network is down (git can hang on
208
+ // DNS/TCP), so each source gets one implicit attempt per process. "Refresh"
209
+ // always retries.
210
+ const cloneAttempted = new Set();
211
+ let teamSkills = [];
212
+ let firstLoadStarted = false;
213
+ /**
214
+ * The current team-skill list, straight from memory. The first call kicks off
215
+ * an async background load (clone + list) and returns immediately — the GUI
216
+ * refetches, so remote skills appear moments later instead of blocking the
217
+ * first `GET /api/skills`.
218
+ */
219
+ export function getTeamSkillsCached(repoRoot) {
220
+ if (!firstLoadStarted) {
221
+ firstLoadStarted = true;
222
+ void loadTeamSkills(repoRoot, false).catch(() => undefined);
223
+ }
224
+ return teamSkills;
225
+ }
226
+ /** Refresh: clone missing sources, `git fetch` existing ones, reload the list. */
227
+ export async function refreshTeamSkills(repoRoot) {
228
+ firstLoadStarted = true;
229
+ return loadTeamSkills(repoRoot, true);
230
+ }
231
+ async function loadTeamSkills(repoRoot, refresh) {
232
+ const config = await loadConfig(repoRoot);
233
+ const out = [];
234
+ const seen = new Set();
235
+ for (const src of config.skillsRepos) {
236
+ try {
237
+ if (refresh) {
238
+ const { bareDir, created } = await ensureBareClone(src.repo);
239
+ if (!created)
240
+ await fetchAll(bareDir);
241
+ cloneAttempted.add(src.repo);
242
+ }
243
+ else if (!cloneAttempted.has(src.repo)) {
244
+ cloneAttempted.add(src.repo);
245
+ await ensureBareClone(src.repo);
246
+ }
247
+ }
248
+ catch {
249
+ // offline / no access — list whatever an older clone has (or nothing)
250
+ }
251
+ try {
252
+ for (const skill of await listRemoteSkills(src)) {
253
+ if (seen.has(skill.name))
254
+ continue;
255
+ seen.add(skill.name);
256
+ out.push(skill);
257
+ }
258
+ }
259
+ catch {
260
+ // degrade: this source contributes nothing
261
+ }
262
+ }
263
+ teamSkills = out;
264
+ return out;
265
+ }
266
+ //# sourceMappingURL=skills-remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-remote.js","sourceRoot":"","sources":["../src/skills-remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,UAAU,EAAyB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAc,MAAM,aAAa,CAAC;AAE3D;;;;;;;GAOG;AAEH,MAAM,eAAe,GAAG,MAAM,CAAC,CAAC,6BAA6B;AAC7D,MAAM,gBAAgB,GAAG,MAAM,CAAC,CAAC,gBAAgB;AAUjD,SAAS,GAAG,CAAC,IAAc,EAAE,SAAiB,EAAE,GAAY;IAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CACN,KAAK,EACL,IAAI,EACJ,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EACjG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC,CAC3F,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,6FAA6F;AAC7F,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,sBAAsB,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACnF,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,IAAI;SACjB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC5E,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;IACtD,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,eAAe,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC1E,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC9E,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,YAAY,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe;IAC5C,MAAM,GAAG,GAAG,MAAM,GAAG,CACnB,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,4BAA4B,CAAC,EAC5D,gBAAgB,EAChB,OAAO,CACR,CAAC;IACF,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAC9F,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,OAAe,EAAE,GAAW;IACpD,KAAK,MAAM,SAAS,IAAI,CAAC,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;QACnG,IAAI,KAAK,CAAC,EAAE;YAAE,OAAO,SAAS,CAAC;IACjC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAUD;;;;;GAKG;AACH,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAClC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvC,OAAO,MAAM,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,CAAC;IACD,MAAM,GAAG,GAAG,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,IAAI,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAW,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC5D,IAAI,2CAA2C,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IACpG,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAqB,EAAE,IAAY;IACvE,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAqB;IAC1D,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;IACtF,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IAEtB,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC7C,IAAI,GAAG,KAAK,IAAI;YAAE,SAAS;QAC3B,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,IAAI,GACR,GAAG,CAAC,IAAI;YACR,CAAC,OAAO,WAAW,CAAC,IAAI,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE;gBAC9D,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE;gBACzB,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,MAAM,WAAW,GACf,OAAO,WAAW,CAAC,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE;YAC3E,CAAC,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE;YAChC,CAAC,CAAC,SAAS,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,WAAW;YACX,IAAI;YACJ,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,IAAI,IAAI,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,KAAK,OAAO,EAAE;SAC9E,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,kFAAkF;AAElF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB,EAAE,KAAY;IACtE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5E,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;IACpG,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,OAAO,KAAK,CAAC;IAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC1C,4EAA4E;QAC5E,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS;QACpD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;QAC7E,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAClC,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7C,KAAK,EAAE,CAAC;IACV,CAAC;IACD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,cAAc,CAAC,QAAQ,EAAE,kBAAkB,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wEAAwE;AACxE,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAe;IAC7D,IAAI,CAAC;QACH,wEAAwE;QACxE,sEAAsE;QACtE,2DAA2D;QAC3D,MAAM,KAAK,GAAG,MAAM,GAAG,CACrB,CAAC,WAAW,EAAE,wBAAwB,EAAE,kBAAkB,CAAC,EAC3D,eAAe,EACf,QAAQ,CACT,CAAC;QACF,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9F,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QACpD,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO;QAC/C,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3G,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;IAC5E,CAAC;AACH,CAAC;AAED,mFAAmF;AAEnF,yEAAyE;AACzE,4EAA4E;AAC5E,kBAAkB;AAClB,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;AACzC,IAAI,UAAU,GAAY,EAAE,CAAC;AAC7B,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,gBAAgB,GAAG,IAAI,CAAC;QACxB,KAAK,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IACtD,gBAAgB,GAAG,IAAI,CAAC;IACxB,OAAO,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,OAAgB;IAC9D,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAY,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC7D,IAAI,CAAC,OAAO;oBAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;gBACtC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;iBAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC7B,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;QACxE,CAAC;QACD,IAAI,CAAC;YACH,KAAK,MAAM,KAAK,IAAI,MAAM,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChD,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBACnC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,2CAA2C;QAC7C,CAAC;IACH,CAAC;IACD,UAAU,GAAG,GAAG,CAAC;IACjB,OAAO,GAAG,CAAC;AACb,CAAC"}
package/dist/skills.d.ts CHANGED
@@ -1,20 +1,32 @@
1
1
  /**
2
2
  * A skill is a Markdown file with optional YAML-ish frontmatter (`name`,
3
3
  * `description`). Discovered from the repo's `.ai/skills/` (shared with other
4
- * agent tooling) and `.ai/cezar/skills/` (cez-local). Adapted from
5
- * @cezar/core's skill-catalog, trimmed to two on-disk sources.
4
+ * agent tooling), `.ai/cezar/skills/` (cez-local), and the configured team
5
+ * skills repos (spec 005 bare clones, no checkout). Adapted from
6
+ * @cezar/core's skill-catalog.
6
7
  */
7
8
  export interface Skill {
8
9
  name: string;
9
10
  description?: string;
10
11
  body: string;
11
12
  path: string;
12
- source: 'ai' | 'cezar';
13
+ source: 'ai' | 'cezar' | 'team';
14
+ /** Team skills only: where the definition lives in its skills repo. */
15
+ team?: {
16
+ repo: string;
17
+ ref: string;
18
+ path: string;
19
+ /** True for the `SKILL.md` convention — a whole directory (references/…). */
20
+ dir: boolean;
21
+ };
13
22
  }
14
23
  /**
15
- * Discover the merged skill catalog for a repo. `.ai/cezar/skills` wins name
16
- * collisions over `.ai/skills`. Missing directories are fine an empty
17
- * catalog is fully supported (steps fall back to their plain prompt).
24
+ * Discover the merged skill catalog for a repo. Name collisions resolve
25
+ * local-first: `.ai/cezar/skills` `.ai/skills` team repo ("the user's
26
+ * repo is the source of truth"). Missing directories are fine — an empty
27
+ * catalog is fully supported (steps fall back to their plain prompt). Team
28
+ * skills come from the in-process cache; the first call starts a background
29
+ * load so nothing here ever waits on the network.
18
30
  */
19
31
  export declare function discoverSkills(repoRoot: string): Promise<Skill[]>;
20
32
  type FrontmatterValue = string | string[];
package/dist/skills.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import { readdir, readFile } from 'node:fs/promises';
2
2
  import { join, resolve, basename, extname } from 'node:path';
3
+ import { getTeamSkillsCached } from './skills-remote.js';
3
4
  const SKILL_DIRS = [
4
5
  { dir: '.ai/cezar/skills', source: 'cezar' },
5
6
  { dir: '.ai/skills', source: 'ai' },
6
7
  ];
7
8
  /**
8
- * Discover the merged skill catalog for a repo. `.ai/cezar/skills` wins name
9
- * collisions over `.ai/skills`. Missing directories are fine an empty
10
- * catalog is fully supported (steps fall back to their plain prompt).
9
+ * Discover the merged skill catalog for a repo. Name collisions resolve
10
+ * local-first: `.ai/cezar/skills` `.ai/skills` team repo ("the user's
11
+ * repo is the source of truth"). Missing directories are fine — an empty
12
+ * catalog is fully supported (steps fall back to their plain prompt). Team
13
+ * skills come from the in-process cache; the first call starts a background
14
+ * load so nothing here ever waits on the network.
11
15
  */
12
16
  export async function discoverSkills(repoRoot) {
13
17
  const lists = await Promise.all(SKILL_DIRS.map(({ dir, source }) => readMarkdownSkills(resolve(repoRoot, dir), source)));
14
18
  const merged = [];
15
19
  const seen = new Set();
16
- for (const skills of lists) {
20
+ for (const skills of [...lists, getTeamSkillsCached(repoRoot)]) {
17
21
  for (const skill of skills) {
18
22
  if (seen.has(skill.name))
19
23
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The global follow-up inbox (spec 007): `.ai/cezar/todos.json`, a flat JSON
4
+ * array agents append to (via CEZ_TODOS_FILE). Agent entries are external
5
+ * data — each one is zod-validated on read and malformed ones are skipped
6
+ * with a warning, never fatal. Server writes are serialized with an
7
+ * in-process lock (the janitor `withLock` pattern) and land atomically
8
+ * (tmp + rename, the runs.json pattern).
9
+ */
10
+ declare const todoSchema: z.ZodObject<{
11
+ id: z.ZodOptional<z.ZodString>;
12
+ ts: z.ZodOptional<z.ZodString>;
13
+ taskId: z.ZodOptional<z.ZodString>;
14
+ summary: z.ZodString;
15
+ action: z.ZodOptional<z.ZodString>;
16
+ prUrl: z.ZodOptional<z.ZodString>;
17
+ suggestedSkill: z.ZodOptional<z.ZodString>;
18
+ suggestedArgs: z.ZodOptional<z.ZodString>;
19
+ suggestedPrompt: z.ZodOptional<z.ZodString>;
20
+ /** Set by the server when "▶ Run" turned this entry into a task. */
21
+ startedTaskId: z.ZodOptional<z.ZodString>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ summary: string;
24
+ id?: string | undefined;
25
+ ts?: string | undefined;
26
+ taskId?: string | undefined;
27
+ action?: string | undefined;
28
+ prUrl?: string | undefined;
29
+ suggestedSkill?: string | undefined;
30
+ suggestedArgs?: string | undefined;
31
+ suggestedPrompt?: string | undefined;
32
+ startedTaskId?: string | undefined;
33
+ }, {
34
+ summary: string;
35
+ id?: string | undefined;
36
+ ts?: string | undefined;
37
+ taskId?: string | undefined;
38
+ action?: string | undefined;
39
+ prUrl?: string | undefined;
40
+ suggestedSkill?: string | undefined;
41
+ suggestedArgs?: string | undefined;
42
+ suggestedPrompt?: string | undefined;
43
+ startedTaskId?: string | undefined;
44
+ }>;
45
+ export type TodoItem = z.infer<typeof todoSchema> & {
46
+ id: string;
47
+ };
48
+ export declare function todosPath(dataDir: string): string;
49
+ export declare function readTodos(dataDir: string): Promise<TodoItem[]>;
50
+ /** Check off (delete) an entry. False when the id isn't there. */
51
+ export declare function removeTodo(dataDir: string, id: string): Promise<boolean>;
52
+ /** Record that "▶ Run" turned the entry into task `taskId`. The entry stays
53
+ * in the file as an audit trail; the GUI hides started entries. */
54
+ export declare function markStarted(dataDir: string, id: string, taskId: string): Promise<boolean>;
55
+ /** Watch `.ai/cezar/` for todos.json changes (agents write it from another
56
+ * process) and emit debounced change events. Degrades to no watch on error. */
57
+ export declare function startTodosWatch(dataDir: string): void;
58
+ /** Subscribe to inbox changes; returns the unsubscribe function. */
59
+ export declare function onTodosChanged(cb: () => void): () => void;
60
+ export {};
package/dist/todos.js ADDED
@@ -0,0 +1,166 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { promises as fs } from 'node:fs';
4
+ import { mkdirSync, watch } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { z } from 'zod';
7
+ /**
8
+ * The global follow-up inbox (spec 007): `.ai/cezar/todos.json`, a flat JSON
9
+ * array agents append to (via CEZ_TODOS_FILE). Agent entries are external
10
+ * data — each one is zod-validated on read and malformed ones are skipped
11
+ * with a warning, never fatal. Server writes are serialized with an
12
+ * in-process lock (the janitor `withLock` pattern) and land atomically
13
+ * (tmp + rename, the runs.json pattern).
14
+ */
15
+ const todoSchema = z.object({
16
+ id: z.string().min(1).optional(),
17
+ ts: z.string().optional(),
18
+ taskId: z.string().optional(),
19
+ summary: z.string().min(1),
20
+ action: z.string().optional(),
21
+ prUrl: z.string().optional(),
22
+ suggestedSkill: z.string().optional(),
23
+ suggestedArgs: z.string().optional(),
24
+ suggestedPrompt: z.string().optional(),
25
+ /** Set by the server when "▶ Run" turned this entry into a task. */
26
+ startedTaskId: z.string().optional(),
27
+ });
28
+ export function todosPath(dataDir) {
29
+ return join(dataDir, 'todos.json');
30
+ }
31
+ // ---- in-process lock (15-line port of janitor's storage.withLock) ----------
32
+ const locks = new Map();
33
+ async function withLock(key, fn) {
34
+ const prev = locks.get(key) ?? Promise.resolve();
35
+ let release = () => undefined;
36
+ const next = new Promise((r) => {
37
+ release = r;
38
+ });
39
+ locks.set(key, prev.then(() => next));
40
+ try {
41
+ await prev;
42
+ return await fn();
43
+ }
44
+ finally {
45
+ release();
46
+ }
47
+ }
48
+ // ---- read / write -----------------------------------------------------------
49
+ /** Parse + validate the file. Broken JSON / non-array → []; bad entries are
50
+ * skipped with a warning; entries without an id get one assigned. */
51
+ async function readRaw(dataDir) {
52
+ let raw;
53
+ try {
54
+ raw = await fs.readFile(todosPath(dataDir), 'utf8');
55
+ }
56
+ catch {
57
+ return { items: [], needsRewrite: false }; // no file yet — empty inbox
58
+ }
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(raw);
62
+ }
63
+ catch (err) {
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ console.warn(`[cez] todos.json is not valid JSON — showing an empty inbox (${message})`);
66
+ return { items: [], needsRewrite: false };
67
+ }
68
+ if (!Array.isArray(parsed)) {
69
+ console.warn('[cez] todos.json is not a JSON array — showing an empty inbox');
70
+ return { items: [], needsRewrite: false };
71
+ }
72
+ const items = [];
73
+ let needsRewrite = false;
74
+ for (const entry of parsed) {
75
+ const result = todoSchema.safeParse(entry);
76
+ if (!result.success) {
77
+ console.warn(`[cez] skipped a malformed todos.json entry: ${result.error.issues.map((i) => i.message).join('; ')}`);
78
+ continue;
79
+ }
80
+ if (!result.data.id) {
81
+ // Agent entries arrive without ids — assign one so the GUI can address
82
+ // the entry; the file is rewritten (under the lock) on this read.
83
+ needsRewrite = true;
84
+ items.push({ ...result.data, id: randomUUID() });
85
+ }
86
+ else {
87
+ items.push({ ...result.data, id: result.data.id });
88
+ }
89
+ }
90
+ return { items, needsRewrite };
91
+ }
92
+ async function writeAtomic(dataDir, items) {
93
+ const file = todosPath(dataDir);
94
+ const tmp = `${file}.tmp`;
95
+ await fs.mkdir(dataDir, { recursive: true });
96
+ await fs.writeFile(tmp, JSON.stringify(items, null, 2), 'utf8');
97
+ await fs.rename(tmp, file);
98
+ }
99
+ export async function readTodos(dataDir) {
100
+ return withLock(dataDir, async () => {
101
+ const { items, needsRewrite } = await readRaw(dataDir);
102
+ if (needsRewrite) {
103
+ await writeAtomic(dataDir, items).catch(() => undefined); // best effort
104
+ }
105
+ return items;
106
+ });
107
+ }
108
+ /** Check off (delete) an entry. False when the id isn't there. */
109
+ export async function removeTodo(dataDir, id) {
110
+ return withLock(dataDir, async () => {
111
+ const { items } = await readRaw(dataDir);
112
+ const next = items.filter((t) => t.id !== id);
113
+ if (next.length === items.length)
114
+ return false;
115
+ await writeAtomic(dataDir, next);
116
+ return true;
117
+ });
118
+ }
119
+ /** Record that "▶ Run" turned the entry into task `taskId`. The entry stays
120
+ * in the file as an audit trail; the GUI hides started entries. */
121
+ export async function markStarted(dataDir, id, taskId) {
122
+ return withLock(dataDir, async () => {
123
+ const { items } = await readRaw(dataDir);
124
+ const item = items.find((t) => t.id === id);
125
+ if (!item)
126
+ return false;
127
+ item.startedTaskId = taskId;
128
+ await writeAtomic(dataDir, items);
129
+ return true;
130
+ });
131
+ }
132
+ // ---- change notifications ----------------------------------------------------
133
+ const emitter = new EventEmitter();
134
+ emitter.setMaxListeners(100);
135
+ let watching = false;
136
+ /** Watch `.ai/cezar/` for todos.json changes (agents write it from another
137
+ * process) and emit debounced change events. Degrades to no watch on error. */
138
+ export function startTodosWatch(dataDir) {
139
+ if (watching)
140
+ return;
141
+ watching = true;
142
+ try {
143
+ mkdirSync(dataDir, { recursive: true });
144
+ let timer;
145
+ const watcher = watch(dataDir, (_event, filename) => {
146
+ if (filename && filename !== 'todos.json')
147
+ return;
148
+ if (timer)
149
+ clearTimeout(timer);
150
+ timer = setTimeout(() => emitter.emit('changed'), 300);
151
+ timer.unref?.();
152
+ });
153
+ watcher.on('error', () => undefined); // a dying watcher must not kill the server
154
+ watcher.unref?.();
155
+ }
156
+ catch (err) {
157
+ const message = err instanceof Error ? err.message : String(err);
158
+ console.warn(`[cez] todos watch unavailable — the Inbox updates on refresh only (${message})`);
159
+ }
160
+ }
161
+ /** Subscribe to inbox changes; returns the unsubscribe function. */
162
+ export function onTodosChanged(cb) {
163
+ emitter.on('changed', cb);
164
+ return () => emitter.off('changed', cb);
165
+ }
166
+ //# sourceMappingURL=todos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todos.js","sourceRoot":"","sources":["../src/todos.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { readdir, readFile } from 'node:fs/promises';
2
2
  import { extname, join, resolve } from 'node:path';
3
3
  import { parse as parseYaml } from 'yaml';
4
- import { QUICK_TASK_WORKFLOW, workflowFileSchema } from './types.js';
4
+ import { QUICK_TASK_WORKFLOW, stepsIssue, workflowFileSchema } from './types.js';
5
5
  export const WORKFLOWS_DIR = '.ai/cezar/workflows';
6
6
  /**
7
7
  * Load the workflow catalog: the built-in `quick-task` plus every
@@ -31,14 +31,10 @@ export async function loadWorkflows(repoRoot) {
31
31
  issues.push({ path, message: parsed.error.issues.map((i) => i.message).join('; ') });
32
32
  continue;
33
33
  }
34
- // Steps referenced by onFail.retry must exist and come earlier.
35
- const ids = parsed.data.steps.map((s) => s.id);
36
- const badRetry = parsed.data.steps.find((s) => s.onFail && ids.indexOf(s.onFail.retry) >= ids.indexOf(s.id));
37
- if (badRetry?.onFail) {
38
- issues.push({
39
- path,
40
- message: `step "${badRetry.id}": onFail.retry must reference an earlier step (got "${badRetry.onFail.retry}")`,
41
- });
34
+ // Steps referenced by onFail.retry must exist and come earlier; ids unique.
35
+ const issue = stepsIssue(parsed.data.steps);
36
+ if (issue) {
37
+ issues.push({ path, message: issue });
42
38
  continue;
43
39
  }
44
40
  fromFiles.push({ ...parsed.data, source: 'file', path });