loadout-ai 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/README.md +160 -315
  3. package/SECURITY.md +21 -1
  4. package/catalog/discovered.json +31269 -26003
  5. package/catalog/packages.json +4 -4
  6. package/dist/src/cli.js +13 -0
  7. package/dist/src/commands/agents.js +3 -1
  8. package/dist/src/commands/catalog-candidate.js +150 -0
  9. package/dist/src/commands/catalog-workflows.js +147 -0
  10. package/dist/src/commands/catalog.js +9 -367
  11. package/dist/src/commands/coordinate.js +586 -0
  12. package/dist/src/commands/coordination-discussions.js +194 -0
  13. package/dist/src/commands/coordination-sessions.js +197 -0
  14. package/dist/src/commands/inventory.js +4 -2
  15. package/dist/src/core/agents/agent-inspection.js +26 -4
  16. package/dist/src/core/{routing → agents}/model-config.js +7 -2
  17. package/dist/src/core/catalog/catalog.js +1 -0
  18. package/dist/src/core/catalog/registry.js +44 -10
  19. package/dist/src/core/catalog/safety.js +36 -7
  20. package/dist/src/core/coordination/adapters/claude-code.js +154 -0
  21. package/dist/src/core/coordination/adapters/codex.js +137 -0
  22. package/dist/src/core/coordination/adapters/types.js +64 -0
  23. package/dist/src/core/coordination/auth.js +59 -0
  24. package/dist/src/core/coordination/bridge-lease.js +79 -0
  25. package/dist/src/core/coordination/conflict-preview.js +167 -0
  26. package/dist/src/core/coordination/contract-diff.js +106 -0
  27. package/dist/src/core/coordination/coordinator.js +552 -0
  28. package/dist/src/core/coordination/crash-recovery.js +169 -0
  29. package/dist/src/core/coordination/daemon.js +667 -0
  30. package/dist/src/core/coordination/discussion.js +340 -0
  31. package/dist/src/core/coordination/events.js +161 -0
  32. package/dist/src/core/coordination/http-api.js +118 -0
  33. package/dist/src/core/coordination/interrupt-policy.js +89 -0
  34. package/dist/src/core/coordination/lock.js +110 -0
  35. package/dist/src/core/coordination/mcp-server.js +424 -0
  36. package/dist/src/core/coordination/redaction.js +85 -0
  37. package/dist/src/core/coordination/replay.js +188 -0
  38. package/dist/src/core/coordination/retention.js +128 -0
  39. package/dist/src/core/coordination/runtime.js +21 -0
  40. package/dist/src/core/coordination/session-manager.js +366 -0
  41. package/dist/src/core/coordination/watcher.js +128 -0
  42. package/dist/src/core/{routing → delegation}/first-party-skills.js +25 -13
  43. package/dist/src/core/{routing → delegation}/handoff.js +130 -63
  44. package/dist/src/core/discovery/candidate-intelligence-evidence.js +108 -0
  45. package/dist/src/core/discovery/candidate-intelligence-types.js +1 -0
  46. package/dist/src/core/discovery/candidate-intelligence-validation.js +108 -0
  47. package/dist/src/core/discovery/candidate-intelligence.js +3 -214
  48. package/dist/src/core/discovery/community.js +12 -4
  49. package/dist/src/core/discovery/github-discovery.js +6 -2
  50. package/dist/src/core/discovery/private-discovery.js +6 -2
  51. package/dist/src/core/install/reconcile.js +1 -1
  52. package/dist/src/core/install/source.js +21 -7
  53. package/dist/src/core/install/uninstall.js +39 -3
  54. package/dist/src/core/reporting/cli-guide.js +10 -6
  55. package/dist/src/core/reporting/completion.js +61 -109
  56. package/dist/src/core/reporting/doctor.js +4 -6
  57. package/dist/src/core/runtime/bounded-json.js +65 -0
  58. package/dist/src/core/runtime/github.js +30 -16
  59. package/dist/src/core/runtime/mcp-recipes.js +1 -1
  60. package/dist/src/core/workspace/active-policy.js +1 -1
  61. package/docs/CANDIDATE_INTELLIGENCE.md +9 -2
  62. package/docs/CATALOG.md +1 -1
  63. package/docs/CREDENTIAL_AND_UPDATE_POLICY.md +1 -1
  64. package/docs/DEMO_SCRIPT.md +19 -23
  65. package/docs/DISCOVERED.md +251 -250
  66. package/docs/FEATURE_TEST_MATRIX.md +10 -263
  67. package/docs/GITHUB_AUTHORIZATION.md +5 -0
  68. package/docs/LIVE_COLLABORATION.md +234 -0
  69. package/docs/PROVENANCE_AND_COMPARISON.md +1 -1
  70. package/docs/REFERENCE.md +163 -0
  71. package/docs/RELEASE_REVIEW.md +1 -2
  72. package/docs/TESTING.md +17 -1
  73. package/docs/USER_TEST_GUIDE.md +138 -3
  74. package/docs/assets/loadout-discover-activate.webp +0 -0
  75. package/docs/assets/loadout-handoff-coordinate.webp +0 -0
  76. package/docs/assets/loadout-social-preview.png +0 -0
  77. package/docs/decisions/001-coordination-jsonl-locking.md +35 -0
  78. package/docs/decisions/002-local-daemon-authentication.md +32 -0
  79. package/docs/decisions/003-bounded-agent-discussions.md +91 -0
  80. package/docs/specs/BOUNDED_AGENT_DISCUSSIONS.md +169 -0
  81. package/docs/superpowers/plans/2026-09-03-coordination-hardening.md +231 -0
  82. package/docs/superpowers/plans/2026-09-03-release-readiness.md +232 -0
  83. package/docs/superpowers/plans/2026-09-04-bounded-agent-discussions.md +121 -0
  84. package/package.json +18 -7
  85. package/skills/loadout-handoff/SKILL.md +238 -0
  86. package/MASTER_PLAN.md +0 -2207
  87. package/dist/src/core/routing/route.js +0 -539
  88. package/docs/ACTIVE_SET.md +0 -53
  89. package/docs/COMPATIBILITY_POLICY.md +0 -22
  90. package/docs/CONVERSION_AND_SANDBOX.md +0 -27
  91. package/docs/EVALUATION_PROTOCOL_V1.md +0 -300
  92. package/docs/HEAD_TO_HEAD_EVALUATION.md +0 -79
  93. package/docs/PROVIDER_CONFIGURATION.md +0 -45
  94. package/docs/README_RESEARCH.md +0 -36
  95. package/docs/REPOSITORY_STABILIZATION.md +0 -190
  96. package/docs/SAFE_UPDATE_DEMO.md +0 -25
  97. package/docs/SCHEMA_DECISIONS.md +0 -25
  98. package/docs/SUBMISSION_COPY.md +0 -90
  99. package/docs/TEAM_POLICY.md +0 -18
  100. package/docs/assets/loadout-workflow.png +0 -0
  101. package/docs/superpowers/plans/2026-07-19-relatable-readme-hero.md +0 -283
  102. package/docs/superpowers/plans/2026-07-20-loadout-readme-explainer.md +0 -116
  103. package/docs/superpowers/plans/2026-07-20-project-activation-safety.md +0 -469
  104. package/docs/superpowers/specs/2026-07-19-relatable-readme-hero-design.md +0 -80
  105. package/docs/superpowers/specs/2026-07-20-loadout-readme-explainer-design.md +0 -55
  106. package/docs/superpowers/specs/2026-07-20-project-activation-safety-design.md +0 -228
  107. package/skills/loadout-router/SKILL.md +0 -120
  108. /package/dist/src/core/{routing → agents}/credentials.js +0 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Redact sensitive data from coordination event payloads.
3
+ *
4
+ * Applied on emit, before storage. Patterns match common secret formats
5
+ * (API keys, tokens, passwords, GitHub PATs, Stripe keys, JWTs, etc.).
6
+ */
7
+ const REDACTION_MARKER = "[REDACTED]";
8
+ const SENSITIVE_KEY = /^(?:api[_-]?key|access[_-]?token|auth(?:orization)?|bearer|client[_-]?secret|secret|token|password|passwd|pwd|credentials?)$/i;
9
+ const SECRET_PATTERNS = [
10
+ // Key-value patterns: api_key=..., secret: "...", password='...'
11
+ /(?:api[_-]?key|apikey|secret|token|password|passwd|pwd|credentials?)\s*[:=]\s*['"]?([^\s'"]{12,})['"]?/gi,
12
+ // Stripe keys: sk_live_..., pk_test_..., rk_live_...
13
+ /(?:sk|pk|rk)[-_](?:live|test)[-_][a-zA-Z0-9]{10,}/g,
14
+ // GitHub PATs
15
+ /ghp_[a-zA-Z0-9]{36}/g,
16
+ /github_pat_[a-zA-Z0-9_]{22,}/g,
17
+ // OpenAI keys
18
+ /sk-[a-zA-Z0-9]{20,}/g,
19
+ // Anthropic keys
20
+ /sk-ant-[a-zA-Z0-9_-]{20,}/g,
21
+ // AWS keys
22
+ /AKIA[0-9A-Z]{16}/g,
23
+ // Bearer tokens
24
+ /Bearer\s+[a-zA-Z0-9._\-/+=]{20,}/gi,
25
+ // JWTs (three base64 segments separated by dots)
26
+ /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g,
27
+ // Generic long hex/base64 strings that look like secrets (40+ chars)
28
+ /(?:^|["'\s=:])([a-f0-9]{40,})(?:["'\s]|$)/gm,
29
+ // .env style: ENV_VAR=value on its own line
30
+ /^[A-Z][A-Z0-9_]{2,}_(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)\s*=\s*(.+)$/gm,
31
+ ];
32
+ export function redactString(input) {
33
+ let result = input;
34
+ for (const pattern of SECRET_PATTERNS) {
35
+ // Reset lastIndex for stateful regexes
36
+ pattern.lastIndex = 0;
37
+ result = result.replace(pattern, (match) => {
38
+ // Keep the key part, redact the value part
39
+ const eqIndex = match.search(/[:=]\s*/);
40
+ if (eqIndex !== -1) {
41
+ return match.slice(0, eqIndex + 1) + " " + REDACTION_MARKER;
42
+ }
43
+ return REDACTION_MARKER;
44
+ });
45
+ }
46
+ return result;
47
+ }
48
+ export function redactPayload(payload) {
49
+ const result = {};
50
+ for (const [key, value] of Object.entries(payload)) {
51
+ if (SENSITIVE_KEY.test(key) && typeof value === "string") {
52
+ result[key] = REDACTION_MARKER;
53
+ }
54
+ else if (typeof value === "string") {
55
+ result[key] = redactString(value);
56
+ }
57
+ else if (value !== null &&
58
+ typeof value === "object" &&
59
+ !Array.isArray(value)) {
60
+ result[key] = redactPayload(value);
61
+ }
62
+ else if (Array.isArray(value)) {
63
+ result[key] = value.map((item) => typeof item === "string"
64
+ ? redactString(item)
65
+ : item !== null && typeof item === "object"
66
+ ? redactPayload(item)
67
+ : item);
68
+ }
69
+ else {
70
+ result[key] = value;
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ export function redactDescription(description) {
76
+ return redactString(description);
77
+ }
78
+ export function redactCoordinationInput(input) {
79
+ return {
80
+ ...input,
81
+ description: redactString(input.description),
82
+ ...(input.context ? { context: redactString(input.context) } : {}),
83
+ ...(input.payload ? { payload: redactPayload(input.payload) } : {}),
84
+ };
85
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Coordination replay — play back the full coordination timeline as a story.
3
+ *
4
+ * `loadout coord replay` reads the JSONL log and presents it as a formatted
5
+ * narrative timeline with timestamps, agent actions, and state transitions.
6
+ */
7
+ import { readCoordLog, getContracts, getOwnership } from "./coordinator.js";
8
+ const TYPE_EMOJI = {
9
+ task: "📋",
10
+ handoff: "🤝",
11
+ question: "❓",
12
+ done: "✅",
13
+ status: "📊",
14
+ error: "❌",
15
+ cancel: "🚫",
16
+ contract: "📜",
17
+ ownership: "🔒",
18
+ decision: "⚖️",
19
+ update: "📝",
20
+ ack: "👍",
21
+ discussion: "💬",
22
+ };
23
+ function eventHeadline(event) {
24
+ const payload = event.payload;
25
+ switch (event.type) {
26
+ case "contract": {
27
+ const name = payload?.name ?? "unknown";
28
+ const rev = payload?.revision ?? "?";
29
+ return `Published contract "${name}" rev${rev}`;
30
+ }
31
+ case "ownership": {
32
+ const paths = payload?.paths ?? [];
33
+ const mode = payload?.mode ?? "exclusive";
34
+ return `Claimed ${mode} ownership of ${paths.length} path(s): ${paths.slice(0, 3).join(", ")}${paths.length > 3 ? "..." : ""}`;
35
+ }
36
+ case "decision": {
37
+ const title = payload?.title ?? event.description;
38
+ return `Decision: ${title}`;
39
+ }
40
+ case "update": {
41
+ const note = payload?.note ?? event.description;
42
+ const files = payload?.files;
43
+ const filePart = files ? ` (${files.length} files)` : "";
44
+ return `${note}${filePart}`;
45
+ }
46
+ case "ack": {
47
+ const ackSeq = payload?.eventSeq ?? "?";
48
+ return `Acknowledged through seq ${ackSeq}`;
49
+ }
50
+ case "discussion": {
51
+ const threadId = payload?.threadId ?? "unknown";
52
+ const round = payload?.round ?? "?";
53
+ const kind = payload?.kind ?? "message";
54
+ return `Discussion ${threadId} · round ${round} · ${kind}`;
55
+ }
56
+ case "task":
57
+ return `Task: ${event.description}`;
58
+ case "handoff":
59
+ return `Handoff to ${event.to}: ${event.description}`;
60
+ case "done":
61
+ return `Completed: ${event.description}`;
62
+ case "error":
63
+ return `Error: ${event.description}`;
64
+ default:
65
+ return event.description;
66
+ }
67
+ }
68
+ function eventDetail(event) {
69
+ if (event.type !== "discussion" || !event.payload)
70
+ return event.context;
71
+ const payload = event.payload;
72
+ if (!payload.content)
73
+ return event.context;
74
+ return `${payload.content}${payload.replyTo ? ` (reply to ${payload.replyTo})` : ""}`;
75
+ }
76
+ function formatDuration(ms) {
77
+ if (ms < 1000)
78
+ return `${ms}ms`;
79
+ const seconds = Math.floor(ms / 1000);
80
+ if (seconds < 60)
81
+ return `${seconds}s`;
82
+ const minutes = Math.floor(seconds / 60);
83
+ if (minutes < 60)
84
+ return `${minutes}m ${seconds % 60}s`;
85
+ const hours = Math.floor(minutes / 60);
86
+ return `${hours}h ${minutes % 60}m`;
87
+ }
88
+ function relativeTimestamp(eventTime, startTime) {
89
+ const ms = eventTime.getTime() - startTime.getTime();
90
+ return `+${formatDuration(ms)}`;
91
+ }
92
+ /** Max events to include in a replay to prevent memory issues. */
93
+ const MAX_REPLAY_EVENTS = 2000;
94
+ /**
95
+ * Build the replay timeline from the coordination log.
96
+ * Bounded to the most recent MAX_REPLAY_EVENTS entries.
97
+ */
98
+ export async function buildReplay(projectRoot) {
99
+ const log = await readCoordLog(projectRoot);
100
+ const contracts = await getContracts(projectRoot);
101
+ const ownership = await getOwnership(projectRoot);
102
+ if (log.events.length === 0) {
103
+ return {
104
+ entries: [],
105
+ agents: [],
106
+ startTime: "",
107
+ endTime: "",
108
+ duration: "0s",
109
+ contractCount: 0,
110
+ ownershipCount: 0,
111
+ totalEvents: 0,
112
+ };
113
+ }
114
+ // Bound to recent events
115
+ const bounded = log.events.length > MAX_REPLAY_EVENTS
116
+ ? log.events.slice(-MAX_REPLAY_EVENTS)
117
+ : log.events;
118
+ const startTime = new Date(bounded[0].timestamp);
119
+ const endTime = new Date(bounded[bounded.length - 1].timestamp);
120
+ const agents = [...new Set(bounded.map((e) => e.from))];
121
+ const entries = bounded.map((event) => ({
122
+ seq: event.seq,
123
+ timestamp: event.timestamp,
124
+ agent: event.from,
125
+ type: event.type,
126
+ emoji: TYPE_EMOJI[event.type] ?? "•",
127
+ headline: eventHeadline(event),
128
+ detail: eventDetail(event),
129
+ relativeTime: relativeTimestamp(new Date(event.timestamp), startTime),
130
+ }));
131
+ return {
132
+ entries,
133
+ agents,
134
+ startTime: bounded[0].timestamp,
135
+ endTime: bounded[bounded.length - 1].timestamp,
136
+ duration: formatDuration(endTime.getTime() - startTime.getTime()),
137
+ contractCount: contracts.size,
138
+ ownershipCount: ownership.size,
139
+ totalEvents: log.events.length,
140
+ };
141
+ }
142
+ /**
143
+ * Format replay timeline for terminal display.
144
+ */
145
+ export function formatReplay(timeline) {
146
+ if (timeline.entries.length === 0) {
147
+ return "No coordination events to replay.";
148
+ }
149
+ const lines = [];
150
+ // Header
151
+ lines.push("\x1b[1m╔══════════════════════════════════════════╗\x1b[0m");
152
+ lines.push("\x1b[1m║ COORDINATION REPLAY ║\x1b[0m");
153
+ lines.push("\x1b[1m╚══════════════════════════════════════════╝\x1b[0m");
154
+ lines.push("");
155
+ lines.push(` Agents: ${timeline.agents.map((a) => `\x1b[36m${a}\x1b[0m`).join(", ")}`);
156
+ lines.push(` Duration: ${timeline.duration}`);
157
+ lines.push(` Events: ${timeline.totalEvents} · Contracts: ${timeline.contractCount} · Ownership claims: ${timeline.ownershipCount}`);
158
+ lines.push("");
159
+ lines.push("\x1b[90m─────────────────────────────────────────────\x1b[0m");
160
+ lines.push("");
161
+ // Assign colors to agents
162
+ const agentColors = ["\x1b[36m", "\x1b[33m", "\x1b[35m", "\x1b[34m"];
163
+ const colorMap = new Map();
164
+ timeline.agents.forEach((a, i) => {
165
+ colorMap.set(a, agentColors[i % agentColors.length]);
166
+ });
167
+ // Timeline entries
168
+ let lastAgent = "";
169
+ for (const entry of timeline.entries) {
170
+ const color = colorMap.get(entry.agent) ?? "\x1b[37m";
171
+ // Agent separator
172
+ if (entry.agent !== lastAgent) {
173
+ if (lastAgent)
174
+ lines.push("");
175
+ lastAgent = entry.agent;
176
+ }
177
+ const timeStr = `\x1b[90m${entry.relativeTime.padStart(8)}\x1b[0m`;
178
+ const agentStr = `${color}${entry.agent.padEnd(12)}\x1b[0m`;
179
+ lines.push(` ${timeStr} ${entry.emoji} ${agentStr} ${entry.headline}`);
180
+ if (entry.detail) {
181
+ lines.push(` \x1b[90m└─ ${entry.detail}\x1b[0m`);
182
+ }
183
+ }
184
+ lines.push("");
185
+ lines.push("\x1b[90m─────────────────────────────────────────────\x1b[0m");
186
+ lines.push(` \x1b[32m✓\x1b[0m Replay complete · ${timeline.totalEvents} events`);
187
+ return lines.join("\n");
188
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Retention and compaction for the coordination log.
3
+ *
4
+ * The JSONL audit log is never deleted. Compaction rewrites the working log
5
+ * with a summary event followed by recent events, keeping the active set
6
+ * bounded.
7
+ */
8
+ import { readFile, writeFile, rename, stat, mkdir } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { assertCoordinationEnabled, readCoordLog } from "./coordinator.js";
11
+ import { withCoordinationLock } from "./lock.js";
12
+ const COORD_DIR = ".handoff";
13
+ const COORD_LOG = "coordination.jsonl";
14
+ export const DEFAULT_RETENTION = {
15
+ maxEvents: 10000,
16
+ maxAgeDays: 30,
17
+ };
18
+ export async function compact(projectRoot, config = DEFAULT_RETENTION) {
19
+ const dir = join(projectRoot, COORD_DIR);
20
+ await mkdir(dir, { recursive: true });
21
+ return withCoordinationLock(dir, async () => {
22
+ await assertCoordinationEnabled(projectRoot);
23
+ const log = await readCoordLog(projectRoot);
24
+ if (!Number.isSafeInteger(config.maxEvents) || config.maxEvents < 1) {
25
+ throw new Error("maxEvents must be a positive integer");
26
+ }
27
+ if (!Number.isFinite(config.maxAgeDays) || config.maxAgeDays < 0) {
28
+ throw new Error("maxAgeDays must be a non-negative number");
29
+ }
30
+ const cutoffDate = new Date();
31
+ cutoffDate.setDate(cutoffDate.getDate() - config.maxAgeDays);
32
+ const cutoffIso = cutoffDate.toISOString();
33
+ const firstFresh = log.events.findIndex((event) => event.timestamp >= cutoffIso);
34
+ const ageStart = firstFresh === -1 ? log.events.length : firstFresh;
35
+ const countStart = Math.max(0, log.events.length - config.maxEvents);
36
+ const keepStart = Math.max(ageStart, countStart);
37
+ const remove = log.events.slice(0, keepStart);
38
+ const finalRetained = log.events.slice(keepStart);
39
+ if (remove.length === 0) {
40
+ return {
41
+ before: log.events.length,
42
+ after: log.events.length,
43
+ removed: 0,
44
+ compacted: false,
45
+ };
46
+ }
47
+ // Build summary of what was compacted
48
+ const agents = new Set();
49
+ const types = new Map();
50
+ for (const e of remove) {
51
+ agents.add(e.from);
52
+ types.set(e.type, (types.get(e.type) ?? 0) + 1);
53
+ }
54
+ const summaryPayload = {
55
+ compactedCount: remove.length,
56
+ compactedRange: {
57
+ from: remove[0].seq,
58
+ to: remove[remove.length - 1].seq,
59
+ },
60
+ agents: [...agents],
61
+ typeCounts: Object.fromEntries(types),
62
+ compactedAt: new Date().toISOString(),
63
+ };
64
+ const logPath = join(projectRoot, COORD_DIR, COORD_LOG);
65
+ // Archival is the durability boundary: never rewrite if it fails.
66
+ const archivePath = `${logPath}.${Date.now()}.archive`;
67
+ const raw = await readFile(logPath, "utf-8");
68
+ await writeFile(archivePath, raw, {
69
+ encoding: "utf-8",
70
+ flag: "wx",
71
+ mode: 0o600,
72
+ });
73
+ // Write compacted log: summary event + retained events
74
+ const summaryLine = JSON.stringify({
75
+ id: `compaction-${Date.now()}`,
76
+ seq: remove[remove.length - 1].seq,
77
+ type: "status",
78
+ from: "loadout",
79
+ to: "*",
80
+ description: `Compacted ${remove.length} events (seq ${summaryPayload.compactedRange.from}–${summaryPayload.compactedRange.to})`,
81
+ timestamp: new Date().toISOString(),
82
+ payload: summaryPayload,
83
+ });
84
+ const lines = [
85
+ summaryLine,
86
+ ...finalRetained.map((e) => JSON.stringify(e)),
87
+ ].join("\n");
88
+ // Atomic write: write to temp, rename over
89
+ const tmpPath = `${logPath}.tmp`;
90
+ await writeFile(tmpPath, lines + "\n", {
91
+ encoding: "utf-8",
92
+ mode: 0o600,
93
+ });
94
+ await rename(tmpPath, logPath);
95
+ // Log corrupt lines if any were found
96
+ if (log.corrupt.length > 0) {
97
+ const corruptPath = `${logPath}.${Date.now()}.corrupt`;
98
+ const corruptData = log.corrupt.map((c) => JSON.stringify(c)).join("\n");
99
+ await writeFile(corruptPath, corruptData + "\n", {
100
+ encoding: "utf-8",
101
+ mode: 0o600,
102
+ }).catch(() => { });
103
+ }
104
+ return {
105
+ before: log.events.length,
106
+ after: finalRetained.length + 1, // +1 for summary
107
+ removed: remove.length,
108
+ compacted: true,
109
+ };
110
+ });
111
+ }
112
+ export async function logSize(projectRoot) {
113
+ const logPath = join(projectRoot, COORD_DIR, COORD_LOG);
114
+ try {
115
+ const s = await stat(logPath);
116
+ const log = await readCoordLog(projectRoot);
117
+ return { events: log.events.length, bytes: s.size };
118
+ }
119
+ catch (error) {
120
+ if (typeof error === "object" &&
121
+ error !== null &&
122
+ "code" in error &&
123
+ error.code === "ENOENT") {
124
+ return { events: 0, bytes: 0 };
125
+ }
126
+ throw error;
127
+ }
128
+ }
@@ -0,0 +1,21 @@
1
+ /** Composition boundary for optional provider-driven coordination. */
2
+ import { Codex } from "@openai/codex-sdk";
3
+ import { ClaudeCodeAdapter } from "./adapters/claude-code.js";
4
+ import { CodexAdapter } from "./adapters/codex.js";
5
+ /**
6
+ * Build adapters backed by the provider-supported interfaces shipped with
7
+ * Loadout: Claude Code print-mode CLI and the OpenAI Codex SDK.
8
+ */
9
+ export function createProviderAdapters() {
10
+ const codex = new Codex();
11
+ const codexDriver = {
12
+ runtimeVersion: "Codex SDK (bundled)",
13
+ startThread(options) {
14
+ return codex.startThread(options);
15
+ },
16
+ resumeThread(sessionId, options) {
17
+ return codex.resumeThread(sessionId, options);
18
+ },
19
+ };
20
+ return [new ClaudeCodeAdapter(), new CodexAdapter(codexDriver)];
21
+ }