@askexenow/exe-os 0.9.69 → 0.9.71

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 (79) hide show
  1. package/deploy/stack-manifests/v0.9.json +96 -16
  2. package/dist/bin/agentic-ontology-backfill.js +33 -0
  3. package/dist/bin/agentic-reflection-backfill.js +33 -0
  4. package/dist/bin/agentic-semantic-label.js +33 -0
  5. package/dist/bin/backfill-conversations.js +33 -0
  6. package/dist/bin/backfill-responses.js +33 -0
  7. package/dist/bin/backfill-vectors.js +33 -0
  8. package/dist/bin/bulk-sync-postgres.js +33 -0
  9. package/dist/bin/cleanup-stale-review-tasks.js +33 -0
  10. package/dist/bin/cli.js +1284 -178
  11. package/dist/bin/exe-agent.js +6 -0
  12. package/dist/bin/exe-assign.js +33 -0
  13. package/dist/bin/exe-boot.js +33 -0
  14. package/dist/bin/exe-call.js +6 -0
  15. package/dist/bin/exe-cloud.js +33 -0
  16. package/dist/bin/exe-dispatch.js +33 -0
  17. package/dist/bin/exe-doctor.js +33 -0
  18. package/dist/bin/exe-export-behaviors.js +33 -0
  19. package/dist/bin/exe-forget.js +33 -0
  20. package/dist/bin/exe-gateway.js +178 -110
  21. package/dist/bin/exe-heartbeat.js +33 -0
  22. package/dist/bin/exe-kill.js +33 -0
  23. package/dist/bin/exe-launch-agent.js +33 -0
  24. package/dist/bin/exe-new-employee.js +6 -0
  25. package/dist/bin/exe-pending-messages.js +33 -0
  26. package/dist/bin/exe-pending-notifications.js +33 -0
  27. package/dist/bin/exe-pending-reviews.js +33 -0
  28. package/dist/bin/exe-rename.js +40 -4
  29. package/dist/bin/exe-review.js +33 -0
  30. package/dist/bin/exe-search.js +33 -0
  31. package/dist/bin/exe-session-cleanup.js +33 -0
  32. package/dist/bin/exe-start-codex.js +33 -0
  33. package/dist/bin/exe-start-opencode.js +33 -0
  34. package/dist/bin/exe-status.js +33 -0
  35. package/dist/bin/exe-team.js +33 -0
  36. package/dist/bin/git-sweep.js +33 -0
  37. package/dist/bin/graph-backfill.js +177 -110
  38. package/dist/bin/graph-export.js +33 -0
  39. package/dist/bin/intercom-check.js +33 -0
  40. package/dist/bin/registry-proxy.js +207 -0
  41. package/dist/bin/scan-tasks.js +33 -0
  42. package/dist/bin/setup.js +33 -0
  43. package/dist/bin/shard-migrate.js +33 -0
  44. package/dist/bin/stack-update.js +128 -0
  45. package/dist/gateway/index.js +178 -110
  46. package/dist/hooks/bug-report-worker.js +33 -0
  47. package/dist/hooks/codex-stop-task-finalizer.js +33 -0
  48. package/dist/hooks/commit-complete.js +33 -0
  49. package/dist/hooks/error-recall.js +33 -0
  50. package/dist/hooks/ingest.js +33 -0
  51. package/dist/hooks/instructions-loaded.js +33 -0
  52. package/dist/hooks/notification.js +33 -0
  53. package/dist/hooks/post-compact.js +33 -0
  54. package/dist/hooks/post-tool-combined.js +698 -17
  55. package/dist/hooks/pre-compact.js +33 -0
  56. package/dist/hooks/pre-tool-use.js +33 -0
  57. package/dist/hooks/prompt-submit.js +314 -0
  58. package/dist/hooks/session-end.js +33 -0
  59. package/dist/hooks/session-start.js +33 -0
  60. package/dist/hooks/stop.js +279 -12
  61. package/dist/hooks/subagent-stop.js +33 -0
  62. package/dist/hooks/summary-worker.js +33 -0
  63. package/dist/index.js +178 -110
  64. package/dist/lib/cloud-sync.js +27 -0
  65. package/dist/lib/database.js +27 -0
  66. package/dist/lib/db.js +27 -0
  67. package/dist/lib/device-registry.js +27 -0
  68. package/dist/lib/employee-templates.js +6 -0
  69. package/dist/lib/exe-daemon.js +639 -259
  70. package/dist/lib/hybrid-search.js +33 -0
  71. package/dist/lib/registry-proxy.js +162 -0
  72. package/dist/lib/schedules.js +33 -0
  73. package/dist/lib/store.js +33 -0
  74. package/dist/mcp/server.js +561 -244
  75. package/dist/runtime/index.js +33 -0
  76. package/dist/tui/App.js +33 -0
  77. package/package.json +3 -2
  78. package/stack.release.json +6 -4
  79. package/stack.release.schema.json +89 -18
@@ -3038,6 +3038,33 @@ async function ensureSchema() {
3038
3038
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
3039
3039
  ON chat_history(session_id, id);
3040
3040
  `);
3041
+ await client.executeMultiple(`
3042
+ CREATE TABLE IF NOT EXISTS session_events (
3043
+ id TEXT PRIMARY KEY,
3044
+ agent_id TEXT NOT NULL,
3045
+ agent_role TEXT NOT NULL,
3046
+ session_id TEXT NOT NULL,
3047
+ session_scope TEXT,
3048
+ project_name TEXT NOT NULL,
3049
+ event_index INTEGER NOT NULL,
3050
+ event_type TEXT NOT NULL,
3051
+ tool_name TEXT,
3052
+ tool_use_id TEXT,
3053
+ content TEXT NOT NULL,
3054
+ payload_json TEXT,
3055
+ has_error INTEGER NOT NULL DEFAULT 0,
3056
+ created_at TEXT NOT NULL
3057
+ );
3058
+
3059
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
3060
+ ON session_events(agent_id, created_at DESC);
3061
+
3062
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
3063
+ ON session_events(session_id, event_index);
3064
+
3065
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
3066
+ ON session_events(session_scope, agent_id, created_at DESC);
3067
+ `);
3041
3068
  await client.executeMultiple(`
3042
3069
  CREATE TABLE IF NOT EXISTS workspaces (
3043
3070
  id TEXT PRIMARY KEY,
@@ -7912,6 +7939,12 @@ var init_platform_procedures = __esm({
7912
7939
  priority: "p0",
7913
7940
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
7914
7941
  },
7942
+ {
7943
+ title: "Code context first for repository orientation",
7944
+ domain: "workflow",
7945
+ priority: "p1",
7946
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
7947
+ },
7915
7948
  {
7916
7949
  title: "Commit discipline \u2014 never leave verified work floating",
7917
7950
  domain: "workflow",
package/dist/bin/setup.js CHANGED
@@ -3841,6 +3841,33 @@ async function ensureSchema() {
3841
3841
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
3842
3842
  ON chat_history(session_id, id);
3843
3843
  `);
3844
+ await client.executeMultiple(`
3845
+ CREATE TABLE IF NOT EXISTS session_events (
3846
+ id TEXT PRIMARY KEY,
3847
+ agent_id TEXT NOT NULL,
3848
+ agent_role TEXT NOT NULL,
3849
+ session_id TEXT NOT NULL,
3850
+ session_scope TEXT,
3851
+ project_name TEXT NOT NULL,
3852
+ event_index INTEGER NOT NULL,
3853
+ event_type TEXT NOT NULL,
3854
+ tool_name TEXT,
3855
+ tool_use_id TEXT,
3856
+ content TEXT NOT NULL,
3857
+ payload_json TEXT,
3858
+ has_error INTEGER NOT NULL DEFAULT 0,
3859
+ created_at TEXT NOT NULL
3860
+ );
3861
+
3862
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
3863
+ ON session_events(agent_id, created_at DESC);
3864
+
3865
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
3866
+ ON session_events(session_id, event_index);
3867
+
3868
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
3869
+ ON session_events(session_scope, agent_id, created_at DESC);
3870
+ `);
3844
3871
  await client.executeMultiple(`
3845
3872
  CREATE TABLE IF NOT EXISTS workspaces (
3846
3873
  id TEXT PRIMARY KEY,
@@ -6411,6 +6438,12 @@ var init_platform_procedures = __esm({
6411
6438
  priority: "p0",
6412
6439
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
6413
6440
  },
6441
+ {
6442
+ title: "Code context first for repository orientation",
6443
+ domain: "workflow",
6444
+ priority: "p1",
6445
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
6446
+ },
6414
6447
  {
6415
6448
  title: "Commit discipline \u2014 never leave verified work floating",
6416
6449
  domain: "workflow",
@@ -2222,6 +2222,33 @@ async function ensureSchema() {
2222
2222
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
2223
2223
  ON chat_history(session_id, id);
2224
2224
  `);
2225
+ await client.executeMultiple(`
2226
+ CREATE TABLE IF NOT EXISTS session_events (
2227
+ id TEXT PRIMARY KEY,
2228
+ agent_id TEXT NOT NULL,
2229
+ agent_role TEXT NOT NULL,
2230
+ session_id TEXT NOT NULL,
2231
+ session_scope TEXT,
2232
+ project_name TEXT NOT NULL,
2233
+ event_index INTEGER NOT NULL,
2234
+ event_type TEXT NOT NULL,
2235
+ tool_name TEXT,
2236
+ tool_use_id TEXT,
2237
+ content TEXT NOT NULL,
2238
+ payload_json TEXT,
2239
+ has_error INTEGER NOT NULL DEFAULT 0,
2240
+ created_at TEXT NOT NULL
2241
+ );
2242
+
2243
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
2244
+ ON session_events(agent_id, created_at DESC);
2245
+
2246
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
2247
+ ON session_events(session_id, event_index);
2248
+
2249
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
2250
+ ON session_events(session_scope, agent_id, created_at DESC);
2251
+ `);
2225
2252
  await client.executeMultiple(`
2226
2253
  CREATE TABLE IF NOT EXISTS workspaces (
2227
2254
  id TEXT PRIMARY KEY,
@@ -3353,6 +3380,12 @@ var init_platform_procedures = __esm({
3353
3380
  priority: "p0",
3354
3381
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
3355
3382
  },
3383
+ {
3384
+ title: "Code context first for repository orientation",
3385
+ domain: "workflow",
3386
+ priority: "p1",
3387
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
3388
+ },
3356
3389
  {
3357
3390
  title: "Commit discipline \u2014 never leave verified work floating",
3358
3391
  domain: "workflow",
@@ -150,6 +150,102 @@ function createStackUpdatePlan(manifest, envRaw, targetVersion) {
150
150
  breakingChanges: release.breakingChanges ?? []
151
151
  };
152
152
  }
153
+ var ASKEXE_GHCR_IMAGE = /^(?:ghcr\.io\/askexe|registry\.askexe\.com\/askexe)\/[a-z0-9._/-]+(?::[^:@$/{]+|@sha256:[a-f0-9]{64})$/i;
154
+ function validatePinnedGhcrImage(image, label) {
155
+ const trimmed = image.trim().replace(/^['"]|['"]$/g, "");
156
+ if (!trimmed) return `${label} is empty`;
157
+ if (trimmed.includes("${")) return null;
158
+ if (!trimmed.startsWith("ghcr.io/askexe/") && !trimmed.startsWith("registry.askexe.com/askexe/")) return `${label} must use ghcr.io/askexe/* or registry.askexe.com/askexe/*, got ${trimmed}`;
159
+ if (/:latest(?:$|[\s#])/.test(trimmed)) return `${label} must not use :latest (${trimmed})`;
160
+ if (!ASKEXE_GHCR_IMAGE.test(trimmed)) return `${label} must be pinned with an explicit tag or sha256 digest from ghcr.io/askexe or registry.askexe.com/askexe, got ${trimmed}`;
161
+ return null;
162
+ }
163
+ function validateComposeImageLiteral(image, label) {
164
+ const trimmed = image.trim().replace(/^['"]|['"]$/g, "");
165
+ if (!trimmed) return `${label} is empty`;
166
+ if (trimmed.startsWith("ghcr.io/askexe/") || trimmed.startsWith("registry.askexe.com/askexe/")) return validatePinnedGhcrImage(trimmed, label);
167
+ if (/^(postgres|pgvector\/pgvector|clickhouse\/clickhouse-server|redis|nginx|postgrest\/postgrest|supabase\/gotrue):[^:]+$/i.test(trimmed)) return null;
168
+ return `${label} uses unsupported non-AskExe image ${trimmed}; customer app images must come from pinned ghcr.io/askexe images`;
169
+ }
170
+ function collectProductionDeployGateIssues(plan, envRaw, composeRaw) {
171
+ const issues = [];
172
+ const env = parseEnv(envRaw);
173
+ for (const [serviceName, service] of Object.entries(plan.release.services)) {
174
+ const manifestIssue = validatePinnedGhcrImage(service.image, `manifest ${plan.targetVersion}.${serviceName}.image`);
175
+ if (manifestIssue) issues.push({ kind: "manifest-image", message: manifestIssue });
176
+ const envImage = env.get(service.env);
177
+ if (envImage) {
178
+ const envIssue = validatePinnedGhcrImage(envImage, `env ${service.env}`);
179
+ if (envIssue) issues.push({ kind: "env-image", message: envIssue });
180
+ }
181
+ }
182
+ const lines = composeRaw.split(/\r?\n/);
183
+ lines.forEach((line, index) => {
184
+ if (/^\s*build\s*:/.test(line)) {
185
+ issues.push({ kind: "compose-build", message: `compose line ${index + 1} contains build:, production deploys must pull images` });
186
+ }
187
+ const imageMatch = line.match(/^\s*image\s*:\s*(.+?)\s*(?:#.*)?$/);
188
+ if (imageMatch) {
189
+ const image = imageMatch[1].trim();
190
+ if (image.includes("${")) {
191
+ const fallback = image.match(/:-([^}]+)}/)?.[1];
192
+ if (fallback) {
193
+ const composeIssue = validateComposeImageLiteral(fallback, `compose image fallback on line ${index + 1}`);
194
+ if (composeIssue) issues.push({ kind: "compose-image", message: composeIssue });
195
+ }
196
+ } else {
197
+ const composeIssue = validateComposeImageLiteral(image, `compose image on line ${index + 1}`);
198
+ if (composeIssue) issues.push({ kind: "compose-image", message: composeIssue });
199
+ }
200
+ }
201
+ });
202
+ return issues;
203
+ }
204
+ function assertDeploymentScopeAllowed(plan, persona = "customer") {
205
+ if (persona === "askexe-control-plane") return;
206
+ const blocked = Object.entries(plan.release.services).filter(([, service]) => service.deploymentScope === "askexe-control-plane").map(([name]) => name);
207
+ if (blocked.length > 0) {
208
+ throw new Error(
209
+ `Customer deployment manifest includes AskExe control-plane service(s): ${blocked.join(", ")}. Customer VPSs may deploy customer services and optional agents only.`
210
+ );
211
+ }
212
+ }
213
+ function assertProductionDeployGate(plan, envRaw, composeRaw, options = {}) {
214
+ const issues = collectProductionDeployGateIssues(plan, envRaw, composeRaw);
215
+ if (issues.length === 0) return;
216
+ if (options.breakGlassReason?.trim()) {
217
+ writeBreakGlassAudit(plan, issues, options);
218
+ return;
219
+ }
220
+ const details = issues.map((issue) => `- [${issue.kind}] ${issue.message}`).join("\n");
221
+ throw new Error(
222
+ `Production deploy gate failed. Exe OS deploys must use pinned ghcr.io/askexe or registry.askexe.com/askexe images and must not build from source on the VPS.
223
+ ${details}
224
+ Emergency override requires --break-glass <reason> and writes an audit file.`
225
+ );
226
+ }
227
+ function writeBreakGlassAudit(plan, issues, options) {
228
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
229
+ const stamp = now().toISOString().replace(/[:.]/g, "-");
230
+ const defaultDir = existsSync("exe/output") ? "exe/output" : path.dirname(options.envFile ?? ".");
231
+ const auditFile = options.breakGlassAuditFile ?? path.join(defaultDir, `stack-update-break-glass-${stamp}.md`);
232
+ mkdirSync(path.dirname(auditFile), { recursive: true });
233
+ const body = [
234
+ `# Stack Update Break-Glass Audit \u2014 ${now().toISOString()}`,
235
+ "",
236
+ `Target version: ${plan.targetVersion}`,
237
+ `Reason: ${options.breakGlassReason?.trim()}`,
238
+ "",
239
+ "## Gate failures overridden",
240
+ ...issues.map((issue) => `- [${issue.kind}] ${issue.message}`),
241
+ "",
242
+ "## Required follow-up",
243
+ "Return this deployment to the standard pinned GHCR image path immediately after the emergency is resolved.",
244
+ ""
245
+ ].join("\n");
246
+ writeFileSync(auditFile, body, { mode: 384 });
247
+ console.warn(`[stack-update] BREAK-GLASS deploy override recorded: ${auditFile}`);
248
+ }
153
249
  function assertBreakingChangesAllowed(plan, allowedIds) {
154
250
  const required = plan.breakingChanges.filter((c) => c.requiresConfirmation !== false);
155
251
  const missing = required.filter((c) => !allowedIds.includes(c.id));
@@ -172,6 +268,15 @@ async function runStackUpdate(options) {
172
268
  const envRaw = readFileSync(options.envFile, "utf8");
173
269
  const plan = createStackUpdatePlan(manifest, envRaw, options.targetVersion);
174
270
  assertBreakingChangesAllowed(plan, options.allowedBreakingChangeIds ?? []);
271
+ assertDeploymentScopeAllowed(plan, options.deploymentPersona ?? "customer");
272
+ const plannedEnvRaw = patchEnv(envRaw, Object.fromEntries(plan.changes.map((c) => [c.key, c.after])));
273
+ const composeRaw = readFileSync(options.composeFile, "utf8");
274
+ assertProductionDeployGate(plan, plannedEnvRaw, composeRaw, {
275
+ breakGlassReason: options.breakGlassReason,
276
+ breakGlassAuditFile: options.breakGlassAuditFile,
277
+ now,
278
+ envFile: options.envFile
279
+ });
175
280
  const lockFile = options.lockFile ?? path.join(path.dirname(options.envFile), ".exe-stack-lock.json");
176
281
  const previousVersion = readCurrentStackVersion(lockFile);
177
282
  if (options.dryRun || plan.changes.length === 0) {
@@ -360,6 +465,7 @@ function parseArgs(args) {
360
465
  dryRun: false,
361
466
  check: false,
362
467
  rollback: false,
468
+ deploymentPersona: process.env.EXE_STACK_DEPLOYMENT_PERSONA === "askexe-control-plane" ? "askexe-control-plane" : "customer",
363
469
  yes: false,
364
470
  allowedBreakingChangeIds: []
365
471
  };
@@ -390,6 +496,18 @@ function parseArgs(args) {
390
496
  else if (arg === "--license-key") opts.licenseKey = next();
391
497
  else if (arg.startsWith("--license-key=")) opts.licenseKey = arg.split("=").slice(1).join("=");
392
498
  else if (arg === "--rollback") opts.rollback = true;
499
+ else if (arg === "--deployment-persona") {
500
+ const value = next();
501
+ if (value !== "customer" && value !== "askexe-control-plane") throw new Error(`Invalid --deployment-persona: ${value}`);
502
+ opts.deploymentPersona = value;
503
+ } else if (arg.startsWith("--deployment-persona=")) {
504
+ const value = arg.split("=").slice(1).join("=");
505
+ if (value !== "customer" && value !== "askexe-control-plane") throw new Error(`Invalid --deployment-persona: ${value}`);
506
+ opts.deploymentPersona = value;
507
+ } else if (arg === "--break-glass") opts.breakGlassReason = next();
508
+ else if (arg.startsWith("--break-glass=")) opts.breakGlassReason = arg.split("=").slice(1).join("=");
509
+ else if (arg === "--break-glass-audit-file") opts.breakGlassAuditFile = next();
510
+ else if (arg.startsWith("--break-glass-audit-file=")) opts.breakGlassAuditFile = arg.split("=").slice(1).join("=");
393
511
  else if (arg === "--dry-run") opts.dryRun = true;
394
512
  else if (arg === "--check") opts.check = true;
395
513
  else if (arg === "--yes" || arg === "-y") opts.yes = true;
@@ -427,6 +545,9 @@ Options:
427
545
  --device-id <id> Device ID to include in deploy audit
428
546
  --license-key <key> License key to include in deploy audit
429
547
  --rollback Restore latest backed-up .env and restart stack
548
+ --deployment-persona <customer|askexe-control-plane> Scope gate (default: customer)
549
+ --break-glass <reason> Emergency override for production deploy gate; writes audit file
550
+ --break-glass-audit-file <path> Audit file path for --break-glass
430
551
  --allow-breaking <ids> Confirm breaking changes, comma-separated
431
552
  -y, --yes Non-interactive confirmation
432
553
  `);
@@ -466,6 +587,13 @@ async function main() {
466
587
  const manifest = await loadStackManifest(opts.manifestRef, void 0, opts.manifestPublicKey, opts.manifestAuthToken);
467
588
  const envRaw = readFileSync2(opts.envFile, "utf8");
468
589
  const plan = createStackUpdatePlan(manifest, envRaw, opts.targetVersion);
590
+ assertDeploymentScopeAllowed(plan, opts.deploymentPersona);
591
+ const plannedEnvRaw = patchEnv(envRaw, Object.fromEntries(plan.changes.map((c) => [c.key, c.after])));
592
+ assertProductionDeployGate(plan, plannedEnvRaw, readFileSync2(opts.composeFile, "utf8"), {
593
+ breakGlassReason: opts.breakGlassReason,
594
+ breakGlassAuditFile: opts.breakGlassAuditFile,
595
+ envFile: opts.envFile
596
+ });
469
597
  console.log(`Exe OS stack target: ${plan.targetVersion}`);
470
598
  console.log(`Manifest: ${opts.manifestRef}`);
471
599
  console.log(`Compose: ${opts.composeFile}`);
@@ -3092,6 +3092,33 @@ async function ensureSchema() {
3092
3092
  CREATE INDEX IF NOT EXISTS idx_chat_history_session
3093
3093
  ON chat_history(session_id, id);
3094
3094
  `);
3095
+ await client.executeMultiple(`
3096
+ CREATE TABLE IF NOT EXISTS session_events (
3097
+ id TEXT PRIMARY KEY,
3098
+ agent_id TEXT NOT NULL,
3099
+ agent_role TEXT NOT NULL,
3100
+ session_id TEXT NOT NULL,
3101
+ session_scope TEXT,
3102
+ project_name TEXT NOT NULL,
3103
+ event_index INTEGER NOT NULL,
3104
+ event_type TEXT NOT NULL,
3105
+ tool_name TEXT,
3106
+ tool_use_id TEXT,
3107
+ content TEXT NOT NULL,
3108
+ payload_json TEXT,
3109
+ has_error INTEGER NOT NULL DEFAULT 0,
3110
+ created_at TEXT NOT NULL
3111
+ );
3112
+
3113
+ CREATE INDEX IF NOT EXISTS idx_session_events_agent_time
3114
+ ON session_events(agent_id, created_at DESC);
3115
+
3116
+ CREATE INDEX IF NOT EXISTS idx_session_events_session_index
3117
+ ON session_events(session_id, event_index);
3118
+
3119
+ CREATE INDEX IF NOT EXISTS idx_session_events_scope_agent_time
3120
+ ON session_events(session_scope, agent_id, created_at DESC);
3121
+ `);
3095
3122
  await client.executeMultiple(`
3096
3123
  CREATE TABLE IF NOT EXISTS workspaces (
3097
3124
  id TEXT PRIMARY KEY,
@@ -4793,6 +4820,12 @@ var init_platform_procedures = __esm({
4793
4820
  priority: "p0",
4794
4821
  content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
4795
4822
  },
4823
+ {
4824
+ title: "Code context first for repository orientation",
4825
+ domain: "workflow",
4826
+ priority: "p1",
4827
+ content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
4828
+ },
4796
4829
  {
4797
4830
  title: "Commit discipline \u2014 never leave verified work floating",
4798
4831
  domain: "workflow",
@@ -6151,13 +6184,25 @@ var init_wiki_client = __esm({
6151
6184
 
6152
6185
  // src/lib/code-chunker.ts
6153
6186
  import ts from "typescript";
6187
+ function languageForFile(filePath) {
6188
+ const base = filePath.split(/[\\/]/).pop()?.toLowerCase() ?? "";
6189
+ if (["dockerfile", "makefile", "rakefile", "gemfile"].includes(base)) return base;
6190
+ const ext = base.includes(".") ? base.split(".").pop() : void 0;
6191
+ return ext ? LANGUAGE_BY_EXTENSION[ext] : void 0;
6192
+ }
6154
6193
  function chunkSourceFile(source, fileName = "file.ts") {
6194
+ const language = languageForFile(fileName);
6195
+ if (language === "typescript" || language === "javascript") {
6196
+ return chunkTypeScriptLike(source, fileName);
6197
+ }
6198
+ return chunkGenericSource(source, fileName, language);
6199
+ }
6200
+ function chunkTypeScriptLike(source, fileName) {
6155
6201
  const sourceFile = ts.createSourceFile(
6156
6202
  fileName,
6157
6203
  source,
6158
6204
  ts.ScriptTarget.Latest,
6159
6205
  true,
6160
- // setParentNodes
6161
6206
  fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
6162
6207
  );
6163
6208
  const chunks = [];
@@ -6179,153 +6224,176 @@ function chunkSourceFile(source, fileName = "file.ts") {
6179
6224
  if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
6180
6225
  return node.name?.getText(sourceFile) ?? "(anonymous)";
6181
6226
  }
6182
- if (ts.isClassDeclaration(node)) {
6183
- return node.name?.getText(sourceFile) ?? "(anonymous class)";
6184
- }
6185
- if (ts.isInterfaceDeclaration(node)) {
6186
- return node.name.getText(sourceFile);
6187
- }
6188
- if (ts.isTypeAliasDeclaration(node)) {
6189
- return node.name.getText(sourceFile);
6190
- }
6191
- if (ts.isEnumDeclaration(node)) {
6192
- return node.name.getText(sourceFile);
6193
- }
6194
- if (ts.isVariableStatement(node)) {
6195
- const decls = node.declarationList.declarations;
6196
- return decls.map((d) => d.name.getText(sourceFile)).join(", ");
6197
- }
6198
- if (ts.isExportAssignment(node)) {
6199
- return "default export";
6200
- }
6227
+ if (ts.isClassDeclaration(node)) return node.name?.getText(sourceFile) ?? "(anonymous class)";
6228
+ if (ts.isInterfaceDeclaration(node)) return node.name.getText(sourceFile);
6229
+ if (ts.isTypeAliasDeclaration(node)) return node.name.getText(sourceFile);
6230
+ if (ts.isEnumDeclaration(node)) return node.name.getText(sourceFile);
6231
+ if (ts.isVariableStatement(node)) return node.declarationList.declarations.map((d) => d.name.getText(sourceFile)).join(", ");
6232
+ if (ts.isExportAssignment(node)) return "default export";
6201
6233
  return "(unknown)";
6202
6234
  }
6203
6235
  function visitTopLevel(node) {
6204
6236
  if (ts.isImportDeclaration(node)) {
6205
- importLines.push({
6206
- start: getLineNumber(node.getStart(sourceFile)),
6207
- end: getLineNumber(node.getEnd())
6208
- });
6237
+ importLines.push({ start: getLineNumber(node.getStart(sourceFile)), end: getLineNumber(node.getEnd()) });
6209
6238
  return;
6210
6239
  }
6211
6240
  if (ts.isFunctionDeclaration(node)) {
6212
- chunks.push({
6213
- kind: "function",
6214
- name: getName(node),
6215
- text: getNodeText(node),
6216
- startLine: getLineNumber(node.getStart(sourceFile)),
6217
- endLine: getLineNumber(node.getEnd()),
6218
- comment: getLeadingComment(node)
6219
- });
6241
+ chunks.push({ kind: "function", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
6220
6242
  return;
6221
6243
  }
6222
6244
  if (ts.isClassDeclaration(node)) {
6223
- chunks.push({
6224
- kind: "class",
6225
- name: getName(node),
6226
- text: getNodeText(node),
6227
- startLine: getLineNumber(node.getStart(sourceFile)),
6228
- endLine: getLineNumber(node.getEnd()),
6229
- comment: getLeadingComment(node)
6230
- });
6245
+ chunks.push({ kind: "class", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
6231
6246
  return;
6232
6247
  }
6233
- if (ts.isInterfaceDeclaration(node)) {
6234
- chunks.push({
6235
- kind: "type",
6236
- name: getName(node),
6237
- text: getNodeText(node),
6238
- startLine: getLineNumber(node.getStart(sourceFile)),
6239
- endLine: getLineNumber(node.getEnd()),
6240
- comment: getLeadingComment(node)
6241
- });
6242
- return;
6243
- }
6244
- if (ts.isTypeAliasDeclaration(node)) {
6245
- chunks.push({
6246
- kind: "type",
6247
- name: getName(node),
6248
- text: getNodeText(node),
6249
- startLine: getLineNumber(node.getStart(sourceFile)),
6250
- endLine: getLineNumber(node.getEnd()),
6251
- comment: getLeadingComment(node)
6252
- });
6253
- return;
6254
- }
6255
- if (ts.isEnumDeclaration(node)) {
6256
- chunks.push({
6257
- kind: "type",
6258
- name: getName(node),
6259
- text: getNodeText(node),
6260
- startLine: getLineNumber(node.getStart(sourceFile)),
6261
- endLine: getLineNumber(node.getEnd()),
6262
- comment: getLeadingComment(node)
6263
- });
6248
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
6249
+ chunks.push({ kind: "type", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
6264
6250
  return;
6265
6251
  }
6266
6252
  if (ts.isVariableStatement(node)) {
6267
- const decls = node.declarationList.declarations;
6268
- const isFnLike = decls.some(
6269
- (d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))
6270
- );
6271
- chunks.push({
6272
- kind: isFnLike ? "function" : "variable",
6273
- name: getName(node),
6274
- text: getNodeText(node),
6275
- startLine: getLineNumber(node.getStart(sourceFile)),
6276
- endLine: getLineNumber(node.getEnd()),
6277
- comment: getLeadingComment(node)
6278
- });
6253
+ const isFnLike = node.declarationList.declarations.some((d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)));
6254
+ chunks.push({ kind: isFnLike ? "function" : "variable", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
6279
6255
  return;
6280
6256
  }
6281
6257
  if (ts.isExportAssignment(node)) {
6282
- chunks.push({
6283
- kind: "export",
6284
- name: "default export",
6285
- text: getNodeText(node),
6286
- startLine: getLineNumber(node.getStart(sourceFile)),
6287
- endLine: getLineNumber(node.getEnd()),
6288
- comment: getLeadingComment(node)
6289
- });
6258
+ chunks.push({ kind: "export", name: "default export", text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
6290
6259
  return;
6291
6260
  }
6292
6261
  if (ts.isExpressionStatement(node)) {
6293
6262
  const text = getNodeText(node);
6294
- if (text.length > 10) {
6295
- chunks.push({
6296
- kind: "other",
6297
- name: text.slice(0, 40).replace(/\n/g, " "),
6298
- text,
6299
- startLine: getLineNumber(node.getStart(sourceFile)),
6300
- endLine: getLineNumber(node.getEnd())
6301
- });
6302
- }
6303
- return;
6263
+ if (text.length > 10) chunks.push({ kind: "other", name: text.slice(0, 40).replace(/\n/g, " "), text, startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()) });
6304
6264
  }
6305
6265
  }
6306
6266
  sourceFile.statements.forEach(visitTopLevel);
6307
6267
  if (importLines.length > 0) {
6308
6268
  const startLine = importLines[0].start;
6309
6269
  const endLine = importLines[importLines.length - 1].end;
6310
- const importText = lines.slice(startLine - 1, endLine).join("\n");
6311
- chunks.unshift({
6312
- kind: "import",
6313
- name: `${importLines.length} imports`,
6314
- text: importText,
6315
- startLine,
6270
+ chunks.unshift({ kind: "import", name: `${importLines.length} imports`, text: lines.slice(startLine - 1, endLine).join("\n"), startLine, endLine });
6271
+ }
6272
+ chunks.sort((a, b) => a.startLine - b.startLine);
6273
+ return chunks.length > 0 ? chunks : chunkByWindows(source, 80);
6274
+ }
6275
+ function chunkGenericSource(source, _fileName, language) {
6276
+ const lines = source.split("\n");
6277
+ if (source.trim().length === 0) return [];
6278
+ if (language && TEXT_LIKE_LANGUAGES.has(language)) return chunkTextLike(lines, language);
6279
+ const chunks = [];
6280
+ const patterns = [
6281
+ { kind: "function", regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
6282
+ { kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
6283
+ { kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*[<(]/, name: (m) => m[1] },
6284
+ { kind: "class", regex: /^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
6285
+ { kind: "function", regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
6286
+ { kind: "function", regex: /^\s*(?:public|private|protected|static|final|suspend|fun|override|open|internal|export|async|func|function|subroutine)\s+.*?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
6287
+ { kind: "function", regex: /^\s*function\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
6288
+ { kind: "type", regex: /^\s*(?:interface|type|enum|record|data\s+class|case\s+class|contract|library)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
6289
+ { kind: "section", regex: /^\s{0,3}#{1,6}\s+(.+)$/, name: (m) => m[1].trim() }
6290
+ ];
6291
+ const starts = [];
6292
+ for (let i = 0; i < lines.length; i++) {
6293
+ const line = lines[i];
6294
+ for (const pattern of patterns) {
6295
+ const match = line.match(pattern.regex);
6296
+ if (match) {
6297
+ starts.push({ line: i + 1, kind: pattern.kind, name: pattern.name(match) });
6298
+ break;
6299
+ }
6300
+ }
6301
+ }
6302
+ if (starts.length === 0) return chunkByWindows(source, 80);
6303
+ for (let i = 0; i < starts.length; i++) {
6304
+ const start = starts[i];
6305
+ const next = starts[i + 1]?.line ?? lines.length + 1;
6306
+ const endLine = Math.max(start.line, next - 1);
6307
+ chunks.push({
6308
+ kind: start.kind,
6309
+ name: start.name,
6310
+ text: lines.slice(start.line - 1, endLine).join("\n"),
6311
+ startLine: start.line,
6316
6312
  endLine
6317
6313
  });
6318
6314
  }
6319
- chunks.sort((a, b) => a.startLine - b.startLine);
6315
+ return chunks;
6316
+ }
6317
+ function chunkTextLike(lines, language) {
6318
+ if (language === "markdown" || language === "mdx") {
6319
+ const starts = lines.map((line, i) => ({ line, i })).filter(({ line }) => /^\s{0,3}#{1,6}\s+/.test(line));
6320
+ if (starts.length > 0) {
6321
+ return starts.map((start, idx) => {
6322
+ const end = starts[idx + 1]?.i ?? lines.length;
6323
+ const title = start.line.replace(/^\s{0,3}#{1,6}\s+/, "").trim();
6324
+ return { kind: "section", name: title, text: lines.slice(start.i, end).join("\n"), startLine: start.i + 1, endLine: end };
6325
+ });
6326
+ }
6327
+ }
6328
+ return chunkByWindows(lines.join("\n"), 80);
6329
+ }
6330
+ function chunkByWindows(source, windowLines) {
6331
+ const lines = source.split("\n");
6332
+ const chunks = [];
6333
+ for (let i = 0; i < lines.length; i += windowLines) {
6334
+ const end = Math.min(lines.length, i + windowLines);
6335
+ const text = lines.slice(i, end).join("\n");
6336
+ if (text.trim()) chunks.push({ kind: "other", name: `lines ${i + 1}-${end}`, text, startLine: i + 1, endLine: end });
6337
+ }
6320
6338
  return chunks;
6321
6339
  }
6322
6340
  function isChunkable(filePath) {
6323
- const ext = filePath.split(".").pop()?.toLowerCase();
6324
- return ext === "ts" || ext === "tsx" || ext === "js" || ext === "jsx";
6341
+ return Boolean(languageForFile(filePath));
6325
6342
  }
6343
+ var LANGUAGE_BY_EXTENSION, TEXT_LIKE_LANGUAGES;
6326
6344
  var init_code_chunker = __esm({
6327
6345
  "src/lib/code-chunker.ts"() {
6328
6346
  "use strict";
6347
+ LANGUAGE_BY_EXTENSION = {
6348
+ c: "c",
6349
+ cc: "cpp",
6350
+ cpp: "cpp",
6351
+ cxx: "cpp",
6352
+ h: "c",
6353
+ hh: "cpp",
6354
+ hpp: "cpp",
6355
+ cs: "csharp",
6356
+ css: "css",
6357
+ scss: "scss",
6358
+ f: "fortran",
6359
+ f90: "fortran",
6360
+ f95: "fortran",
6361
+ go: "go",
6362
+ html: "html",
6363
+ htm: "html",
6364
+ java: "java",
6365
+ js: "javascript",
6366
+ jsx: "javascript",
6367
+ json: "json",
6368
+ kt: "kotlin",
6369
+ kts: "kotlin",
6370
+ lua: "lua",
6371
+ md: "markdown",
6372
+ mdx: "mdx",
6373
+ pas: "pascal",
6374
+ php: "php",
6375
+ py: "python",
6376
+ r: "r",
6377
+ rb: "ruby",
6378
+ rs: "rust",
6379
+ scala: "scala",
6380
+ sc: "scala",
6381
+ sol: "solidity",
6382
+ sql: "sql",
6383
+ svelte: "svelte",
6384
+ swift: "swift",
6385
+ toml: "toml",
6386
+ ts: "typescript",
6387
+ tsx: "typescript",
6388
+ vue: "vue",
6389
+ xml: "xml",
6390
+ yaml: "yaml",
6391
+ yml: "yaml",
6392
+ sh: "shell",
6393
+ bash: "shell",
6394
+ zsh: "shell"
6395
+ };
6396
+ TEXT_LIKE_LANGUAGES = /* @__PURE__ */ new Set(["json", "markdown", "mdx", "toml", "yaml", "xml", "html", "css", "scss", "sql"]);
6329
6397
  }
6330
6398
  });
6331
6399