@elevasis/sdk 0.5.14 → 0.5.15

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.
package/dist/index.d.ts CHANGED
@@ -2436,7 +2436,11 @@ type Database = {
2436
2436
  };
2437
2437
  sessions: {
2438
2438
  Row: {
2439
+ context_window_size: number;
2439
2440
  created_at: string | null;
2441
+ cumulative_input_tokens: number;
2442
+ cumulative_output_tokens: number;
2443
+ deleted_at: string | null;
2440
2444
  ended_at: string | null;
2441
2445
  memory_snapshot: Json;
2442
2446
  metadata: Json | null;
@@ -2448,7 +2452,11 @@ type Database = {
2448
2452
  user_id: string | null;
2449
2453
  };
2450
2454
  Insert: {
2455
+ context_window_size?: number;
2451
2456
  created_at?: string | null;
2457
+ cumulative_input_tokens?: number;
2458
+ cumulative_output_tokens?: number;
2459
+ deleted_at?: string | null;
2452
2460
  ended_at?: string | null;
2453
2461
  memory_snapshot: Json;
2454
2462
  metadata?: Json | null;
@@ -2460,7 +2468,11 @@ type Database = {
2460
2468
  user_id?: string | null;
2461
2469
  };
2462
2470
  Update: {
2471
+ context_window_size?: number;
2463
2472
  created_at?: string | null;
2473
+ cumulative_input_tokens?: number;
2474
+ cumulative_output_tokens?: number;
2475
+ deleted_at?: string | null;
2464
2476
  ended_at?: string | null;
2465
2477
  memory_snapshot?: Json;
2466
2478
  metadata?: Json | null;
@@ -3084,6 +3096,18 @@ declare class ResourceRegistry {
3084
3096
  * @throws Error if incoming deployment contains duplicate resourceIds
3085
3097
  */
3086
3098
  registerOrganization(orgName: string, org: OrganizationResources, remote: RemoteOrgConfig): void;
3099
+ /**
3100
+ * Register built-in platform resources (static, local execution)
3101
+ *
3102
+ * Unlike registerOrganization(), these resources:
3103
+ * - Do NOT have remote config (execute in-process, not in worker threads)
3104
+ * - Are NOT removed by unregisterOrganization() (persist across redeployments)
3105
+ * - Use reserved resource IDs that external deployments cannot claim
3106
+ *
3107
+ * @param orgName - Organization name
3108
+ * @param org - Resource definitions with real handlers (not stubs)
3109
+ */
3110
+ registerStaticResources(orgName: string, org: OrganizationResources): void;
3087
3111
  /**
3088
3112
  * Unregister runtime-registered resources for an organization
3089
3113
  *
package/dist/index.js CHANGED
@@ -142,6 +142,12 @@ var DOMAIN_MAP = {
142
142
  [DOMAINS.DIAGNOSTIC]: DIAGNOSTIC_DOMAIN
143
143
  };
144
144
 
145
+ // ../core/src/platform/registry/reserved.ts
146
+ var RESERVED_RESOURCE_IDS = /* @__PURE__ */ new Set(["command-center-assistant"]);
147
+ function isReservedResourceId(resourceId) {
148
+ return RESERVED_RESOURCE_IDS.has(resourceId);
149
+ }
150
+
145
151
  // ../core/src/execution/engine/base/errors.ts
146
152
  var ExecutionError2 = class extends Error {
147
153
  /**
@@ -3295,6 +3301,13 @@ var ResourceRegistry = class {
3295
3301
  }
3296
3302
  seen.add(id);
3297
3303
  }
3304
+ for (const id of incomingIds) {
3305
+ if (isReservedResourceId(id)) {
3306
+ throw new Error(
3307
+ `Resource ID '${id}' is reserved for platform use. External deployments cannot use reserved resource IDs.`
3308
+ );
3309
+ }
3310
+ }
3298
3311
  if (this.isRemote(orgName)) {
3299
3312
  this.unregisterOrganization(orgName);
3300
3313
  }
@@ -3327,6 +3340,46 @@ var ResourceRegistry = class {
3327
3340
  }
3328
3341
  this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]));
3329
3342
  }
3343
+ /**
3344
+ * Register built-in platform resources (static, local execution)
3345
+ *
3346
+ * Unlike registerOrganization(), these resources:
3347
+ * - Do NOT have remote config (execute in-process, not in worker threads)
3348
+ * - Are NOT removed by unregisterOrganization() (persist across redeployments)
3349
+ * - Use reserved resource IDs that external deployments cannot claim
3350
+ *
3351
+ * @param orgName - Organization name
3352
+ * @param org - Resource definitions with real handlers (not stubs)
3353
+ */
3354
+ registerStaticResources(orgName, org) {
3355
+ const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId);
3356
+ const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId);
3357
+ const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds];
3358
+ const seen = /* @__PURE__ */ new Set();
3359
+ for (const id of incomingIds) {
3360
+ if (seen.has(id)) {
3361
+ throw new Error(`Duplicate resource ID '${id}' in static resources.`);
3362
+ }
3363
+ seen.add(id);
3364
+ }
3365
+ const existingOrg = this.registry[orgName];
3366
+ if (existingOrg) {
3367
+ const existingWorkflowIds = new Set((existingOrg.workflows ?? []).map((w) => w.config.resourceId));
3368
+ const existingAgentIds = new Set((existingOrg.agents ?? []).map((a) => a.config.resourceId));
3369
+ for (const id of incomingIds) {
3370
+ if (existingWorkflowIds.has(id) || existingAgentIds.has(id)) {
3371
+ throw new Error(`Static resource '${id}' conflicts with existing resource in '${orgName}'.`);
3372
+ }
3373
+ }
3374
+ }
3375
+ if (existingOrg) {
3376
+ existingOrg.workflows = [...existingOrg.workflows ?? [], ...org.workflows ?? []];
3377
+ existingOrg.agents = [...existingOrg.agents ?? [], ...org.agents ?? []];
3378
+ } else {
3379
+ this.registry[orgName] = org;
3380
+ }
3381
+ this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]));
3382
+ }
3330
3383
  /**
3331
3384
  * Unregister runtime-registered resources for an organization
3332
3385
  *
package/dist/templates.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
 
3
3
  // src/cli/commands/templates/core/workspace.ts
4
- var TEMPLATE_VERSION = 27;
4
+ var TEMPLATE_VERSION = 28;
5
5
  function configTemplate() {
6
6
  return `import type { ElevasConfig } from '@elevasis/sdk'
7
7
 
@@ -49,6 +49,28 @@ function claudeSettingsTemplate() {
49
49
  }
50
50
  ]
51
51
  }
52
+ ],
53
+ PostToolUse: [
54
+ {
55
+ matcher: "Write|Edit|MultiEdit",
56
+ hooks: [
57
+ {
58
+ type: "command",
59
+ command: "node .claude/hooks/post-edit-validate.mjs"
60
+ }
61
+ ]
62
+ }
63
+ ],
64
+ PostToolUseFailure: [
65
+ {
66
+ matcher: "Bash",
67
+ hooks: [
68
+ {
69
+ type: "command",
70
+ command: "node .claude/hooks/tool-failure-recovery.mjs"
71
+ }
72
+ ]
73
+ }
52
74
  ]
53
75
  }
54
76
  },
@@ -1725,6 +1747,199 @@ When all plan steps are marked COMPLETE, suggest \`/work complete\` to finalize
1725
1747
  - Completed tasks move OUT of \`docs/in-progress/\` to \`docs/<relevant-dir>/\`
1726
1748
  `;
1727
1749
  }
1750
+ function claudePostEditValidateHookTemplate() {
1751
+ return `#!/usr/bin/env node
1752
+ // post-edit-validate.mjs
1753
+ // PostToolUse hook \u2014 auto-formats with prettier, type-checks .ts/.tsx files.
1754
+ // Fires after Edit|Write|MultiEdit succeeds.
1755
+
1756
+ import { existsSync } from 'node:fs'
1757
+ import { resolve, normalize, extname, join, dirname } from 'node:path'
1758
+ import { execSync } from 'node:child_process'
1759
+
1760
+ const ROOT = process.env.CLAUDE_PROJECT_DIR ?? process.cwd()
1761
+
1762
+ // Extensions prettier should format
1763
+ const PRETTIER_EXTENSIONS = new Set([
1764
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json', '.css', '.md', '.mdx'
1765
+ ])
1766
+
1767
+ // Extensions that trigger type-checking
1768
+ const TS_EXTENSIONS = new Set(['.ts', '.tsx'])
1769
+
1770
+ function findNearestTsconfig(startDir) {
1771
+ let dir = startDir
1772
+ const root = normalize(ROOT)
1773
+ while (dir.length >= root.length) {
1774
+ const candidate = join(dir, 'tsconfig.json')
1775
+ if (existsSync(candidate)) return candidate
1776
+ const parent = dirname(dir)
1777
+ if (parent === dir) break
1778
+ dir = parent
1779
+ }
1780
+ return null
1781
+ }
1782
+
1783
+ try {
1784
+ const chunks = []
1785
+ for await (const chunk of process.stdin) chunks.push(chunk)
1786
+ const input = JSON.parse(Buffer.concat(chunks).toString())
1787
+
1788
+ const filePath = input.tool_input?.file_path
1789
+ if (!filePath) process.exit(0)
1790
+
1791
+ const ext = extname(filePath).toLowerCase()
1792
+ const absPath = normalize(resolve(filePath))
1793
+ if (!existsSync(absPath)) process.exit(0)
1794
+
1795
+ const results = []
1796
+
1797
+ // 1. Prettier
1798
+ if (PRETTIER_EXTENSIONS.has(ext)) {
1799
+ try {
1800
+ execSync('pnpm exec prettier --write "' + absPath + '"', {
1801
+ cwd: ROOT,
1802
+ stdio: ['pipe', 'pipe', 'pipe'],
1803
+ timeout: 10_000
1804
+ })
1805
+ } catch (err) {
1806
+ const stderr = err.stderr?.toString().trim() || ''
1807
+ if (stderr && !/ignored/i.test(stderr)) {
1808
+ results.push('Prettier error: ' + stderr.slice(0, 300))
1809
+ }
1810
+ }
1811
+ }
1812
+
1813
+ // 2. Type-check for .ts/.tsx
1814
+ if (TS_EXTENSIONS.has(ext)) {
1815
+ const tsconfig = findNearestTsconfig(dirname(absPath))
1816
+ if (tsconfig) {
1817
+ try {
1818
+ execSync('pnpm exec tsc --noEmit -p "' + tsconfig + '"', {
1819
+ cwd: ROOT,
1820
+ stdio: ['pipe', 'pipe', 'pipe'],
1821
+ timeout: 30_000,
1822
+ env: { ...process.env, NODE_OPTIONS: '--max-old-space-size=4096' }
1823
+ })
1824
+ } catch (err) {
1825
+ if (err.killed) process.exit(0) // Don't block on timeout
1826
+ const stdout = err.stdout?.toString() || ''
1827
+ if (stdout.includes('error TS')) {
1828
+ const errorLines = stdout
1829
+ .split('\\n')
1830
+ .filter(l => l.includes('error TS'))
1831
+ .slice(0, 10)
1832
+ results.push('Type errors after editing ' + filePath + ':\\n' + errorLines.join('\\n'))
1833
+ }
1834
+ }
1835
+ }
1836
+ }
1837
+
1838
+ // Output errors to Claude's context (silence = success)
1839
+ if (results.length > 0) {
1840
+ process.stderr.write(results.join('\\n\\n'))
1841
+ process.exit(2) // Exit 2 = send stderr as feedback to Claude
1842
+ }
1843
+ } catch {}
1844
+
1845
+ process.exit(0)
1846
+ `;
1847
+ }
1848
+ function claudeToolFailureRecoveryHookTemplate() {
1849
+ return `#!/usr/bin/env node
1850
+ // tool-failure-recovery.mjs
1851
+ // PostToolUseFailure hook \u2014 pattern-matches known Bash errors and returns
1852
+ // recovery advice via stderr + exit 2 (feedback to Claude).
1853
+
1854
+ const RECOVERY_TABLE = [
1855
+ {
1856
+ test: r => /JavaScript heap out of memory/i.test(r),
1857
+ advice: 'Out of memory.',
1858
+ fix: 'Run the command with NODE_OPTIONS="--max-old-space-size=4096".',
1859
+ why: 'Large TypeScript projects can exceed Node default heap limit.',
1860
+ },
1861
+ {
1862
+ test: r => /boundary hook/i.test(r) && /block|denied/i.test(r),
1863
+ advice: 'Command blocked by SDK boundary hook.',
1864
+ fix: 'Ask the user to run this command manually.',
1865
+ why: 'The boundary hook blocks gh CLI, destructive git operations, and file writes outside the project.',
1866
+ see: 'CLAUDE.md',
1867
+ },
1868
+ {
1869
+ test: r => /ENOENT/.test(r) && /node_modules/.test(r),
1870
+ advice: 'Missing node_modules dependency.',
1871
+ fix: 'Run: pnpm install',
1872
+ why: 'Dependencies are not installed or were cleared.',
1873
+ },
1874
+ {
1875
+ test: r => /ERR_MODULE_NOT_FOUND/.test(r) && /@elevasis\\/sdk/.test(r),
1876
+ advice: '@elevasis/sdk module not found.',
1877
+ fix: 'Run: pnpm install \u2014 then verify @elevasis/sdk is in package.json dependencies.',
1878
+ why: 'The SDK package is not installed or the version is mismatched.',
1879
+ },
1880
+ {
1881
+ test: r => /ERR_MODULE_NOT_FOUND/.test(r),
1882
+ advice: 'Module not found at import path.',
1883
+ fix: 'Check the import path and verify the package is installed (pnpm install).',
1884
+ why: 'The import path does not match any installed package or local file.',
1885
+ },
1886
+ {
1887
+ test: r => /TS2307/.test(r) || (/cannot find/i.test(r) && /declaration/i.test(r)),
1888
+ advice: 'TypeScript cannot resolve module or declaration file.',
1889
+ fix: 'Check that the package is installed and tsconfig paths are correct.',
1890
+ why: 'Missing dependency or incorrect TypeScript configuration.',
1891
+ },
1892
+ {
1893
+ test: r => /EPERM/.test(r) || /permission denied/i.test(r),
1894
+ advice: 'Permission denied (EPERM).',
1895
+ fix: 'Close the file in any other process (editor, terminal, or dev server) and retry.',
1896
+ why: 'On Windows, files locked by another process cannot be written to.',
1897
+ },
1898
+ {
1899
+ test: r => /lockfile/i.test(r) && /conflict|outdated|ERR_PNPM/i.test(r),
1900
+ advice: 'pnpm lockfile conflict or outdated.',
1901
+ fix: 'Run: pnpm install to regenerate the lockfile.',
1902
+ why: 'The lockfile is out of sync with package.json changes.',
1903
+ },
1904
+ {
1905
+ test: r => /elevasis-sdk check/.test(r) || /elevasis-sdk deploy/.test(r),
1906
+ advice: 'elevasis-sdk CLI command failed.',
1907
+ fix: 'Check the error output above. Common causes: missing .env ELEVASIS_API_KEY, invalid resource schemas, or network issues.',
1908
+ why: 'The SDK CLI validates resources and communicates with the platform API.',
1909
+ },
1910
+ {
1911
+ test: r => /EADDRINUSE/.test(r),
1912
+ advice: 'Port already in use.',
1913
+ fix: 'Find and kill the process using the port, or use a different port.',
1914
+ why: 'A previous dev server or process is still holding the port.',
1915
+ },
1916
+ ]
1917
+
1918
+ function formatRecovery(entry) {
1919
+ let msg = 'FAILED: ' + entry.advice + '\\nFIX: ' + entry.fix
1920
+ if (entry.why) msg += '\\nWHY: ' + entry.why
1921
+ if (entry.see) msg += '\\nSEE: ' + entry.see
1922
+ return msg
1923
+ }
1924
+
1925
+ try {
1926
+ const chunks = []
1927
+ for await (const chunk of process.stdin) chunks.push(chunk)
1928
+ const input = JSON.parse(Buffer.concat(chunks).toString())
1929
+
1930
+ const response = input.tool_response ?? ''
1931
+
1932
+ for (const entry of RECOVERY_TABLE) {
1933
+ if (entry.test(response)) {
1934
+ process.stderr.write(formatRecovery(entry))
1935
+ process.exit(2)
1936
+ }
1937
+ }
1938
+ } catch {}
1939
+
1940
+ process.exit(0)
1941
+ `;
1942
+ }
1728
1943
 
1729
1944
  // src/cli/commands/templates/core/resources.ts
1730
1945
  function starterWorkflowTemplate() {
@@ -1882,6 +2097,8 @@ function getManagedTemplates(ctx = {}) {
1882
2097
  ".claude/settings.json": claudeSettingsTemplate,
1883
2098
  ".claude/scripts/statusline-command.js": claudeStatuslineScriptTemplate,
1884
2099
  ".claude/hooks/enforce-sdk-boundary.mjs": claudeSdkBoundaryHookTemplate,
2100
+ ".claude/hooks/post-edit-validate.mjs": claudePostEditValidateHookTemplate,
2101
+ ".claude/hooks/tool-failure-recovery.mjs": claudeToolFailureRecoveryHookTemplate,
1885
2102
  ".claude/commands/tutorial.md": claudeTutorialCommandTemplate,
1886
2103
  ".claude/commands/meta.md": claudeMetaCommandTemplate,
1887
2104
  ".claude/commands/work.md": claudeWorkCommandTemplate,
@@ -3596,13 +3596,9 @@ function validateActionSequence(actions) {
3596
3596
  throw new Error("Multiple complete actions not allowed in single iteration");
3597
3597
  }
3598
3598
  if (completeActions.length === 1) {
3599
- const hasIncompatibleActions = actions.some(
3600
- (a) => a.type === "tool-call" || a.type === "navigate-knowledge"
3601
- );
3602
- if (hasIncompatibleActions) {
3603
- throw new Error(
3604
- "Complete action cannot mix with tool-call or navigate-knowledge actions"
3605
- );
3599
+ const hasNavigateKnowledge = actions.some((a) => a.type === "navigate-knowledge");
3600
+ if (hasNavigateKnowledge) {
3601
+ throw new Error("Complete action cannot mix with navigate-knowledge actions");
3606
3602
  }
3607
3603
  }
3608
3604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elevasis/sdk",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "description": "SDK for building Elevasis organization resources",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,7 +54,7 @@ Project-level configuration. The scaffolded file includes `templateVersion`, whi
54
54
  import type { ElevasConfig } from '@elevasis/sdk'
55
55
 
56
56
  export default {
57
- templateVersion: 27,
57
+ templateVersion: 28,
58
58
  // defaultStatus: 'dev', // Default status for new resources ('dev' | 'prod')
59
59
  // dev: { port: 5170 }, // Local API port (internal development only)
60
60
  } satisfies ElevasConfig
@@ -195,7 +195,7 @@ Do not remove or heavily edit `CLAUDE.md`. It is the primary context source that
195
195
 
196
196
  ### `.claude/settings.json`
197
197
 
198
- Sets `autoCompact: false`. This prevents Claude Code from automatically compacting context during long sessions, which can lose important earlier context about your project.
198
+ Configures Claude Code for the workspace: disables auto-compaction (prevents losing earlier context in long sessions) and registers three hooks -- a PreToolUse boundary hook, a PostToolUse formatting/type-check hook, and a PostToolUseFailure error recovery hook.
199
199
 
200
200
  ### `.claude/memory/`
201
201
 
@@ -235,7 +235,7 @@ For detailed command documentation, see [Agent System](agent).
235
235
 
236
236
  Not all scaffolded files participate in template updates. Files fall into two categories:
237
237
 
238
- **SCAFFOLD_FILES total: 30**
238
+ **SCAFFOLD_FILES total: 32**
239
239
 
240
240
  **INIT_ONLY** (14 files) -- Written once during `elevasis-sdk init`, never updated by `elevasis-sdk update`:
241
241
 
@@ -245,10 +245,10 @@ Not all scaffolded files participate in template updates. Files fall into two ca
245
245
  - `docs/index.mdx`, `docs/in-progress/.gitkeep`
246
246
  - `.claude/rules/workspace-patterns.md`
247
247
 
248
- **MANAGED** (16 files) -- Written during `elevasis-sdk init` and checked/updatable by `elevasis-sdk update`:
248
+ **MANAGED** (18 files) -- Written during `elevasis-sdk init` and checked/updatable by `elevasis-sdk update`:
249
249
 
250
250
  - `elevasis.config.ts`, `.gitignore`, `CLAUDE.md`, `.claude/settings.json`
251
- - One hook: `.claude/hooks/enforce-sdk-boundary.mjs`
251
+ - Three hooks: `.claude/hooks/enforce-sdk-boundary.mjs`, `.claude/hooks/post-edit-validate.mjs`, `.claude/hooks/tool-failure-recovery.mjs`
252
252
  - Four command files: `.claude/commands/meta.md`, `.claude/commands/docs.md`, `.claude/commands/tutorial.md`, `.claude/commands/work.md`
253
253
  - One skill: `.claude/skills/creds/SKILL.md`
254
254
  - Five rule files: `.claude/rules/sdk-patterns.md`, `.claude/rules/docs-authoring.md`, `.claude/rules/memory-conventions.md`, `.claude/rules/project-map.md`, `.claude/rules/task-tracking.md`