@yeison.restrepo.r/code-conductor 1.23.1 → 1.23.3

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.
@@ -0,0 +1,160 @@
1
+ // scripts/resume-read.mjs
2
+ // ARCH-008-B: phase-entry resume reader. Zero-dep. Node-14 syntax only.
3
+ // Exit 0 = hit (RESUME_HIT block on stdout), 3 = clean miss (zero bytes), 4 = corrupt handoff halt.
4
+ import { existsSync, readFileSync, writeFileSync, appendFileSync, unlinkSync, mkdirSync } from 'node:fs';
5
+ import { execFileSync, spawnSync } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { dirname, basename, join } from 'node:path';
8
+
9
+ const SENTINEL = '0000000';
10
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
11
+
12
+ let rootTier = 1; // 1 = git toplevel, 2 = .git upward walk, 3 = script-parent
13
+ function resolveRoot() {
14
+ try {
15
+ const top = execFileSync('git', ['rev-parse', '--show-toplevel'],
16
+ { encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'], env: process.env }).trim();
17
+ if (top) { rootTier = 1; return top; }
18
+ } catch {}
19
+ let dir = process.cwd();
20
+ for (let i = 0; i < 40; i++) {
21
+ if (existsSync(join(dir, '.git'))) { rootTier = 2; return dir; }
22
+ const parent = dirname(dir);
23
+ if (parent === dir) break;
24
+ dir = parent;
25
+ }
26
+ rootTier = 3;
27
+ // this script lives at <root>/scripts/resume-read.mjs (dev checkout) or
28
+ // <root>/.claude/scripts/resume-read.mjs (deployed project).
29
+ const parent = join(SCRIPT_DIR, '..');
30
+ return basename(parent) === '.claude' ? join(parent, '..') : parent;
31
+ }
32
+
33
+ const root = resolveRoot();
34
+ const COND = join(root, '.conductor');
35
+ const LOG = join(COND, 'last-write.log');
36
+ const HANDOFF = join(root, '.claude/memory/session-snapshot.json');
37
+ const LEGACY_MD = join(root, '.claude/memory/session-snapshot.md');
38
+ const VALIDATE = join(SCRIPT_DIR, 'snap-validate.mjs');
39
+ const CONDUCTOR_DB = join(SCRIPT_DIR, 'conductor-db.mjs');
40
+
41
+ function trace(token) {
42
+ try {
43
+ mkdirSync(COND, { recursive: true });
44
+ appendFileSync(LOG, new Date().toISOString() + ' resume: ' + token + '\n', { encoding: 'utf8', flag: 'a' });
45
+ } catch {}
46
+ }
47
+
48
+ function tryUnlink(p) { try { unlinkSync(p); } catch {} }
49
+ function parseJson(s) { try { return JSON.parse(s); } catch { return undefined; } }
50
+
51
+ function resolveHash() {
52
+ try {
53
+ const out = execFileSync('git', ['rev-parse', 'HEAD'],
54
+ { encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'], env: process.env }).trim().toLowerCase();
55
+ if (/^[0-9a-f]{7,64}$/.test(out)) return out;
56
+ } catch {}
57
+ return SENTINEL;
58
+ }
59
+
60
+ // Validate an existing file path via snap-validate (exit-code verdict).
61
+ function validateFile(p) {
62
+ const r = spawnSync(process.execPath, [VALIDATE, p], { encoding: 'utf8', env: process.env });
63
+ return r.status === 0;
64
+ }
65
+
66
+ // Probe how to launch node:sqlite. Returns [] (no flag), ['--experimental-sqlite','--no-warnings'], or null (unavailable).
67
+ function probeSqliteFlags() {
68
+ const opts = { encoding: 'utf8', timeout: 2000, env: process.env };
69
+ if (spawnSync(process.execPath, ['--no-warnings', '-e', "require('node:sqlite')"], opts).status === 0) return [];
70
+ if (spawnSync(process.execPath, ['--experimental-sqlite', '--no-warnings', '-e', "require('node:sqlite')"], opts).status === 0) {
71
+ return ['--experimental-sqlite', '--no-warnings'];
72
+ }
73
+ return null;
74
+ }
75
+
76
+ // Validate an in-memory blob by bridging it through the path-only snap-validate via a pid-unique temp.
77
+ function validateBlob(blob) {
78
+ const tmp = join(COND, 'resume-validate.' + process.pid + '.tmp.json');
79
+ try { mkdirSync(COND, { recursive: true }); writeFileSync(tmp, blob, 'utf8'); }
80
+ catch { return false; } // FS write error → blob unusable, degrade
81
+ try {
82
+ const r = spawnSync(process.execPath, [VALIDATE, tmp], { encoding: 'utf8', env: process.env });
83
+ return r.status === 0;
84
+ } finally { tryUnlink(tmp); }
85
+ }
86
+
87
+ // DB branch - returns a validated snap object on a hit, or null to fall through to the file branch.
88
+ function queryDb(hash) {
89
+ if (!/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(hash)) {
90
+ trace(hash === SENTINEL ? 'sentinel-bypass' : 'nonfull-hash-bypass');
91
+ return null;
92
+ }
93
+ const flags = probeSqliteFlags();
94
+ if (flags === null) return null; // Node <22.5 or node:sqlite absent → degrade
95
+ const args = flags.concat([CONDUCTOR_DB, 'get-snapshot', hash]);
96
+ const r = spawnSync(process.execPath, args, { encoding: 'utf8', timeout: 5000, env: process.env });
97
+ if (r.status !== 0 || !r.stdout) return null; // timeout/kill/non-zero/empty → miss
98
+ const blob = r.stdout.trim();
99
+ if (blob === '') return null;
100
+ if (!validateBlob(blob)) { trace('db-invalid degrade'); return null; }
101
+ const snap = parseJson(blob);
102
+ if (snap === undefined) { trace('db-invalid degrade'); return null; }
103
+ return snap;
104
+ }
105
+
106
+ function buildHit(source, snap, hash) {
107
+ const proseAvail = typeof snap.pr === 'string' && snap.pr.length > 0;
108
+ const lines = [
109
+ 'RESUME_HIT',
110
+ 'source: ' + source,
111
+ 'commit: ' + hash,
112
+ 'phase: ' + snap.sys.ph,
113
+ 'spec: ' + snap.sys.s,
114
+ 'version: ' + snap.v,
115
+ 'prose: ' + (proseAvail ? 'available' : 'none'),
116
+ 'pending:'
117
+ ];
118
+ const pend = (snap.ops && Array.isArray(snap.ops.n)) ? snap.ops.n : [];
119
+ for (const item of pend) lines.push('- ' + item);
120
+ return lines.join('\n') + '\n';
121
+ }
122
+
123
+ function main() {
124
+ // Tier-2/tier-3 root fallbacks are diagnostically traced (git was absent or not a repo).
125
+ if (rootTier === 2) trace('root-tier2-gitwalk');
126
+ else if (rootTier === 3) trace('root-tier3-scriptparent');
127
+ // Legacy .md sweep - unread, best-effort.
128
+ if (existsSync(LEGACY_MD)) { tryUnlink(LEGACY_MD); trace('legacy-md-swept'); }
129
+
130
+ const hash = resolveHash();
131
+
132
+ // DB branch (authoritative when present + valid).
133
+ const dbSnap = queryDb(hash);
134
+ if (dbSnap) {
135
+ if (existsSync(HANDOFF)) tryUnlink(HANDOFF); // superseded
136
+ trace('db-hit @' + hash);
137
+ return { code: 0, out: buildHit('db', dbSnap, hash) };
138
+ }
139
+
140
+ // Handoff-file branch.
141
+ if (!existsSync(HANDOFF)) { trace('miss'); return { code: 3, out: '' }; }
142
+ let content;
143
+ try { content = readFileSync(HANDOFF, 'utf8'); }
144
+ catch { trace('file-unreadable degrade'); return { code: 3, out: '' }; } // leave file on disk
145
+ if (content.trim() === '') { trace('file-empty degrade'); tryUnlink(HANDOFF); return { code: 3, out: '' }; }
146
+ if (!validateFile(HANDOFF)) { trace('file-invalid halt'); return { code: 4, out: '' }; } // leave on disk
147
+ const snap = parseJson(content);
148
+ if (snap === undefined) { trace('file-invalid halt'); return { code: 4, out: '' }; }
149
+ if (snap.sys.c !== hash) { trace('file-stale-hash degrade'); tryUnlink(HANDOFF); return { code: 3, out: '' }; }
150
+ // valid + hash matches → bind (content already captured), then unlink.
151
+ const out = buildHit('file', snap, hash);
152
+ trace('file-bind+unlink');
153
+ tryUnlink(HANDOFF);
154
+ return { code: 0, out: out };
155
+ }
156
+
157
+ let res;
158
+ try { res = main(); } catch { res = { code: 3, out: '' }; }
159
+ if (res.out) { process.stdout.write(res.out, () => process.exit(res.code)); }
160
+ else { process.exit(res.code); }
@@ -0,0 +1,67 @@
1
+ import { readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, existsSync } from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, basename, join } from 'node:path';
6
+
7
+ // Repo root: git toplevel, else bounded .git upward walk, else this script's parent.
8
+ function resolveRoot() {
9
+ try {
10
+ const top = execFileSync('git', ['rev-parse', '--show-toplevel'],
11
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
12
+ if (top) return top;
13
+ } catch { /* fall through */ }
14
+ let dir = process.cwd();
15
+ for (let i = 0; i < 40; i++) {
16
+ if (existsSync(join(dir, '.git'))) return dir;
17
+ const parent = dirname(dir);
18
+ if (parent === dir) break;
19
+ dir = parent;
20
+ }
21
+ // this script lives at <root>/scripts/session-id.mjs (dev checkout) or
22
+ // <root>/.claude/scripts/session-id.mjs (deployed project).
23
+ const parent = join(dirname(fileURLToPath(import.meta.url)), '..');
24
+ return basename(parent) === '.claude' ? join(parent, '..') : parent;
25
+ }
26
+
27
+ function emit(id) { process.stdout.write(id + '\n'); }
28
+
29
+ // A cache value is adoptable only if it is a single non-empty token with no interior
30
+ // whitespace/control chars and a sane length. Atomic temp+rename already prevents torn
31
+ // writes, so this is defense-in-depth against ever emitting a truncated/garbage id.
32
+ const looksValid = (v) => !!v && v.length <= 200 && !/\s/.test(v);
33
+
34
+ function main() {
35
+ // Empty or whitespace-only env var is treated as unset: trim → '' → falsy → fall through.
36
+ const env = (process.env.CLAUDE_CODE_SESSION_ID || '').trim();
37
+ if (env) return emit(env); // primary path: cacheless, unique per session
38
+
39
+ const dir = join(resolveRoot(), '.conductor');
40
+ const cache = join(dir, 'session-id');
41
+
42
+ try {
43
+ const cached = readFileSync(cache, 'utf8').trim();
44
+ if (looksValid(cached)) return emit(cached);
45
+ } catch { /* absent or unreadable -> generate */ }
46
+
47
+ const id = randomUUID();
48
+ const tmp = join(dir, `session-id.${process.pid}.tmp`);
49
+ try {
50
+ mkdirSync(dir, { recursive: true });
51
+ writeFileSync(tmp, id + '\n');
52
+ renameSync(tmp, cache); // atomic publish
53
+ } catch {
54
+ // Any write-side failure is caught here: a read-only/permission-denied shared
55
+ // dir (EACCES/EPERM on mkdir or write), or a rename race (EEXIST on POSIX,
56
+ // EPERM/EACCES from a Windows AV/reader lock on the target). Re-read and adopt
57
+ // the winner if one landed; otherwise fall through to the in-memory UUID.
58
+ try {
59
+ const won = readFileSync(cache, 'utf8').trim();
60
+ if (looksValid(won)) { try { unlinkSync(tmp); } catch {} return emit(won); }
61
+ } catch { /* still nothing on disk / dir unreadable */ }
62
+ try { unlinkSync(tmp); } catch {} // best-effort temp cleanup; a locked temp is left, swept by post-compact
63
+ }
64
+ emit(id);
65
+ }
66
+
67
+ main();
@@ -0,0 +1,90 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ const MAX_SNAP_BYTES = 10485760; // 10 MiB (v2)
4
+ const V1_MAX_CHARS = 4096; // handoff-file contract (v1)
5
+ const CAPS = { n: [3, 200], f: [20, 300], d: [10, 300], x: [5, 200] };
6
+
7
+ const die = (msg) => { process.stderr.write(`SNAP_BUILD_ERROR: ${msg}\n`); process.exit(1); };
8
+ const byteLen = (s) => Buffer.byteLength(s, 'utf8');
9
+
10
+ // Async write, then exit 0 once the OS has drained it. A synchronous write to a
11
+ // non-blocking stdout pipe throws EAGAIN on large payloads, and process.exit right
12
+ // after an async write truncates the un-flushed tail; the drain callback avoids both.
13
+ function writeOut(s) { process.stdout.write(s + '\n', () => process.exit(0)); }
14
+
15
+ // filter empties, dedup (first wins), Unicode-safe per-element truncation, head-drop to count cap
16
+ function normArray(raw, [cap, elemCap]) {
17
+ const arr = Array.isArray(raw) ? raw : [];
18
+ const seen = new Set(); const out = [];
19
+ for (const item of arr) {
20
+ if (typeof item !== 'string') continue;
21
+ const t = item.trim();
22
+ if (!t || seen.has(t)) continue;
23
+ seen.add(t);
24
+ out.push(Array.from(t).slice(0, elemCap).join(''));
25
+ }
26
+ while (out.length > cap) out.shift();
27
+ return out;
28
+ }
29
+
30
+ // back off one unit if the boundary would keep a lone high surrogate
31
+ function surrogateSafe(str, n) {
32
+ if (n > 0 && n <= str.length) {
33
+ const code = str.charCodeAt(n - 1);
34
+ if (code >= 0xD800 && code <= 0xDBFF) return n - 1;
35
+ }
36
+ return n;
37
+ }
38
+
39
+ let input;
40
+ try { input = readFileSync(0, 'utf8'); } catch (e) { die(`cannot read stdin: ${e.code || e.message}`); }
41
+
42
+ let obj;
43
+ try { obj = JSON.parse(input); } catch { die('malformed JSON on stdin'); }
44
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) die('stdin must be a JSON object');
45
+
46
+ for (const k of ['ph', 'c', 's']) {
47
+ if (typeof obj[k] !== 'string' || obj[k] === '') die(`missing or empty scalar: ${k}`);
48
+ }
49
+
50
+ const sys = { ph: obj.ph, c: obj.c, s: obj.s };
51
+ const ops = { n: normArray(obj.n, CAPS.n), f: normArray(obj.f, CAPS.f) };
52
+ const mem = { d: normArray(obj.d, CAPS.d), x: normArray(obj.x, CAPS.x) };
53
+ const pr = typeof obj.pr === 'string' ? obj.pr : '';
54
+
55
+ if (pr === '') {
56
+ // ---- v1: 4096-char cap, drop oldest of mem.d / ops.f ----
57
+ const snap = { v: 1, sys, ops, mem };
58
+ let line = JSON.stringify(snap);
59
+ while (line.length > V1_MAX_CHARS && (mem.d.length || ops.f.length)) {
60
+ if (mem.d.length >= ops.f.length && mem.d.length) mem.d.shift();
61
+ else ops.f.shift();
62
+ line = JSON.stringify(snap);
63
+ }
64
+ writeOut(line);
65
+ } else {
66
+ // ---- v2: 10 MiB cap, truncate raw pr before serialize ----
67
+ const skeletonBytes = byteLen(JSON.stringify({ v: 2, sys, ops, mem, pr: '' }));
68
+ if (skeletonBytes > MAX_SNAP_BYTES) die('skeleton exceeds cap even without prose');
69
+
70
+ // total serialized bytes for a candidate pr value (skeleton already counts the two empty-value quotes)
71
+ const totalBytes = (val) => skeletonBytes + byteLen(JSON.stringify(val)) - 2;
72
+
73
+ let keep = pr.length;
74
+ if (totalBytes(pr) > MAX_SNAP_BYTES) {
75
+ let lo = 0, hi = pr.length, best = 0;
76
+ for (let i = 0; i < 64 && lo <= hi; i++) {
77
+ const mid = (lo + hi) >> 1;
78
+ if (totalBytes(pr.slice(0, mid)) <= MAX_SNAP_BYTES) { best = mid; lo = mid + 1; }
79
+ else hi = mid - 1;
80
+ }
81
+ keep = surrogateSafe(pr, best);
82
+ // defensive fallback if the search somehow left us over cap
83
+ if (totalBytes(pr.slice(0, keep)) > MAX_SNAP_BYTES) {
84
+ keep = surrogateSafe(pr, Math.max(0, Math.floor((MAX_SNAP_BYTES - skeletonBytes - 2) / 6)));
85
+ }
86
+ }
87
+
88
+ const snap = { v: 2, sys, ops, mem, pr: pr.slice(0, keep) };
89
+ writeOut(JSON.stringify(snap));
90
+ }
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from 'node:fs';
2
+ const err = (m) => { process.stderr.write(`SNAP_ERROR: ${m}\n`); process.exit(1); };
3
+ const path = process.argv[2]; if (path === undefined) err('no path provided');
4
+ let raw; try { raw = readFileSync(path, 'utf8'); } catch (e) { err(e.code === 'ENOENT' ? 'file not found' : e.code); }
5
+ if (raw.includes('�')) err('encoding error');
6
+ const trimmed = raw.trim(); if (trimmed.includes('\n')) err('internal newline in payload');
7
+ if (trimmed === '') err('empty file'); if (raw.length > 4096) err('payload too large');
8
+ let snap; try { snap = JSON.parse(trimmed); } catch { err('malformed JSON'); }
9
+ if (typeof snap !== 'object' || snap === null || Array.isArray(snap)) err('root must be a plain object');
10
+ for (const b of ['sys', 'ops', 'mem']) if (typeof snap[b] !== 'object' || snap[b] === null || Array.isArray(snap[b])) err(`missing block: ${b}`);
11
+ const req = { v: snap.v, 'sys.ph': snap.sys.ph, 'sys.c': snap.sys.c, 'sys.s': snap.sys.s, 'ops.n': snap.ops.n, 'ops.f': snap.ops.f, 'mem.d': snap.mem.d, 'mem.x': snap.mem.x };
12
+ const missing = Object.entries(req).filter(([, v]) => v === undefined).map(([k]) => k);
13
+ if (missing.length) { for (const k of missing) process.stderr.write(`SNAP_ERROR: missing: ${k}\n`); process.exit(1); }
14
+ const topAllowed = snap.v === 2 ? ['v', 'sys', 'ops', 'mem', 'pr'] : ['v', 'sys', 'ops', 'mem'];
15
+ const topExtra = Object.keys(snap).find(k => !topAllowed.includes(k)); if (topExtra) err(`unexpected key: ${topExtra}`);
16
+ if (snap.pr !== undefined && typeof snap.pr !== 'string') err('pr must be a string');
17
+ const allow = { sys: ['ph', 'c', 's'], ops: ['n', 'f'], mem: ['d', 'x'] };
18
+ for (const b of ['sys', 'ops', 'mem']) { const extra = Object.keys(snap[b]).find(k => !allow[b].includes(k)); if (extra) err(`unexpected key: ${b}.${extra}`); }
19
+ if (typeof snap.v !== 'number' || !Number.isInteger(snap.v) || snap.v < 1) err('v must be a positive integer'); if (snap.v > 2) err('SNAP_UNKNOWN_VERSION');
20
+ if (!['spec', 'plan', 'impl', 'rev'].includes(snap.sys.ph)) err('ph must be spec|plan|impl|rev');
21
+ const caps = { 'ops.n': [3, 200], 'ops.f': [20, 300], 'mem.d': [10, 300], 'mem.x': [5, 200] };
22
+ for (const [key, [cap, elemCap]] of Object.entries(caps)) {
23
+ const [blk, sub] = key.split('.'); const arr = snap[blk][sub]; if (!Array.isArray(arr)) err(`${key} must be an array`); if (arr.length > cap) err(`${key} exceeds cap`);
24
+ arr.forEach((el, i) => { if (typeof el !== 'string' || el.trim() === '') err(`empty element in ${key}[${i}]`); if (JSON.stringify(el).slice(1, -1).length > elemCap) err(`element too long in ${key}[${i}]`); });
25
+ }
26
+ snap.ops.f.forEach((el, i) => {
27
+ if (el.includes('\\')) err(`backslash in ops.f[${i}]`);
28
+ const idx = el.lastIndexOf(':'); if (idx <= 0) err(`empty path in ops.f[${i}]`);
29
+ if (!['C', 'M', 'D'].includes(el.slice(idx + 1))) err(`invalid action code in ops.f[${i}]`);
30
+ });
31
+ if (!/^[0-9a-f]{7,64}$/.test(snap.sys.c)) err('invalid sys.c format'); if (!/^[a-zA-Z0-9._-]+$/.test(snap.sys.s)) err('invalid chars in sys.s');
32
+ process.exit(0);