@astrale-os/cli 1.0.0-beta.26 → 1.0.0-beta.28

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 (73) hide show
  1. package/README.md +31 -5
  2. package/dist/astrale.js +1201 -358
  3. package/package.json +3 -2
  4. package/src/commands/__tests__/domain-install-operation.test.ts +23 -0
  5. package/src/commands/__tests__/install-identity-override.test.ts +3 -3
  6. package/src/commands/domain/install.ts +19 -11
  7. package/src/commands/get.ts +1 -1
  8. package/src/commands/identity/register.ts +3 -4
  9. package/src/commands/logs.ts +2 -5
  10. package/src/commands/session/analyze.ts +26 -4
  11. package/src/commands/update.ts +61 -40
  12. package/src/lib/__tests__/skills.test.ts +809 -0
  13. package/src/lib/skills/lock.ts +117 -0
  14. package/src/lib/skills/sync.ts +814 -0
  15. package/src/lib/skills.ts +1 -52
  16. package/src/program/__tests__/program.test.ts +1 -1
  17. package/src/setup/steps/skills.ts +40 -38
  18. package/src/telemetry/__tests__/retention.test.ts +180 -0
  19. package/src/telemetry/__tests__/settings.test.ts +102 -0
  20. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  21. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  22. package/src/telemetry/recorder.ts +6 -3
  23. package/src/telemetry/retention.ts +129 -0
  24. package/src/telemetry/session.ts +18 -12
  25. package/src/telemetry/settings.ts +64 -11
  26. package/src/telemetry/store.ts +87 -9
  27. package/src/telemetry/trigger.ts +16 -28
  28. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  29. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  30. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  31. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  32. package/studio/client/dist/index.html +3 -3
  33. package/studio/server/agent/prompts/anchors.test.ts +17 -4
  34. package/studio/server/agent/prompts/anchors.ts +3 -2
  35. package/studio/server/agent/prompts/system.test.ts +2 -3
  36. package/studio/server/agent/prompts/system.ts +3 -3
  37. package/studio/server/agent/run/preparation.ts +1 -1
  38. package/studio/server/api/context.ts +1 -1
  39. package/studio/server/api/deployment.ts +1 -1
  40. package/studio/server/api/views.ts +2 -2
  41. package/studio/server/api/workspace.ts +1 -1
  42. package/studio/server/cache.test.ts +3 -8
  43. package/studio/server/cache.ts +4 -45
  44. package/studio/server/cli-consumers.test.ts +31 -2
  45. package/studio/server/handoff/copy.ts +1 -1
  46. package/studio/server/introspect/canonical-schema.test.ts +154 -128
  47. package/studio/server/introspect/canonical-schema.ts +183 -302
  48. package/studio/server/introspect/core.ts +14 -21
  49. package/studio/server/introspect/extractor.ts +11 -15
  50. package/studio/server/introspect/overlay-tsmorph.test.ts +1 -5
  51. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  52. package/studio/server/introspect/overlay.ts +3 -24
  53. package/studio/server/introspect/revision.test.ts +24 -28
  54. package/studio/server/introspect/revision.ts +10 -21
  55. package/studio/server/introspect/runtime.test.ts +14 -14
  56. package/studio/server/introspect/runtime.ts +1 -46
  57. package/studio/server/lifecycle.ts +4 -1
  58. package/studio/server/state/documents.test.ts +69 -0
  59. package/studio/server/state/documents.ts +57 -10
  60. package/studio/server/workspace/updates.ts +23 -1
  61. package/studio/shared/contracts/runtime.ts +4 -0
  62. package/studio/shared/contracts/schema.ts +10 -26
  63. package/studio/shared/contracts/surface.test.ts +2 -2
  64. package/studio/shared/contracts/workspace.ts +3 -0
  65. package/studio/shared/schema/identity.ts +0 -1
  66. package/tsconfig.json +1 -1
  67. package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
  68. package/studio/client/dist/assets/index-OeJK1TjG.js +0 -81
  69. package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
  70. package/studio/client/dist/assets/schema-studio-DRdfu_cQ.js +0 -8
  71. package/studio/server/introspect/core-extractor.ts +0 -30
  72. package/studio/server/introspect/overlay.test.ts +0 -44
  73. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -1,12 +1,12 @@
1
1
  <!doctype html>
2
- <html lang="en" class="dark">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Domain Studio</title>
7
- <script type="module" crossorigin src="/assets/index-OeJK1TjG.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-BFVFs_8x.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-Dd_uD5pT.js">
9
- <link rel="stylesheet" crossorigin href="/assets/index-C0FAnwNw.css">
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DuB9iUdu.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -41,7 +41,20 @@ const ir: SchemaIR = {
41
41
  functions: {
42
42
  inspect: {
43
43
  name: 'inspect',
44
- input: { type: 'object', properties: { cursor: { type: 'string' } }, required: [] },
44
+ input: {
45
+ type: 'object',
46
+ properties: {
47
+ cursor: {
48
+ $ref: 'https://schemas.astrale.ai/graph/1/node-path',
49
+ 'x-astrale-path': {
50
+ target: 'node',
51
+ cardinality: 'one',
52
+ accepts: [{ origin: 'example.dev', kind: 'class', name: 'Item' }],
53
+ },
54
+ },
55
+ },
56
+ required: [],
57
+ },
45
58
  output: { mode: 'stream', item: { type: 'integer' } },
46
59
  auth: 'authenticated',
47
60
  },
@@ -63,8 +76,8 @@ describe('anchor descriptions', () => {
63
76
  })
64
77
 
65
78
  test('describes standalone Functions with output mode and authentication', () => {
66
- expect(describeAnchor('function.inspect', ir, undefined)).toContain(
67
- 'stream<int> [authenticated]',
68
- )
79
+ const description = describeAnchor('function.inspect', ir, undefined)
80
+ expect(description).toContain('inspect(cursor:→node?)')
81
+ expect(description).toContain('stream<int> [authenticated]')
69
82
  })
70
83
  })
@@ -14,6 +14,8 @@ import type {
14
14
  SchemaOverlay,
15
15
  } from '../../../shared/types'
16
16
 
17
+ import { isNodePathSchema } from '../../../shared/types'
18
+
17
19
  /** Terse JSON-Schema type label (mirrors the client's format.tsx describe/typeLabel). */
18
20
  function propType(s: JsonSchema | undefined, optionalOverride?: boolean): string {
19
21
  if (!s) return 'any'
@@ -22,8 +24,7 @@ function propType(s: JsonSchema | undefined, optionalOverride?: boolean): string
22
24
  const base = Array.isArray(t) ? t.find((x) => x !== 'null') : t
23
25
  let label: string
24
26
  if (s.enum) label = `enum(${s.enum.map(String).join('|')})`
25
- else if (s.$nodeRef) label = '→node'
26
- else if (s.$dataRef) label = '→data'
27
+ else if (isNodePathSchema(s)) label = '→node'
27
28
  else if (base === 'array') label = `${propType(s.items)}[]`
28
29
  else if (base === 'object') label = `{${Object.keys(s.properties ?? {}).join(',')}}`
29
30
  else if (base === 'integer') label = 'int'
@@ -2,12 +2,11 @@ import { expect, test } from 'bun:test'
2
2
 
3
3
  import { buildSystemPrompt } from './system'
4
4
 
5
- test('embedded agents receive the current SDK layout contract', () => {
5
+ test('embedded agents receive the current SDK and frontend skill contracts', () => {
6
6
  const prompt = buildSystemPrompt({ bridge: false })
7
7
 
8
8
  expect(prompt).toContain('modular Actions and Workflows')
9
- expect(prompt).toContain('pre-Kernel-V2 APIs or layouts')
9
+ expect(prompt).toContain('astrale-frontend-design')
10
10
  expect(prompt).toContain('Runtime/Application entries')
11
- expect(prompt).toContain('do not restore defineDomain or Interface-era layouts')
12
11
  expect(prompt).not.toContain('schema/ runtime/ views/')
13
12
  })
@@ -25,12 +25,12 @@ export function buildSystemPrompt(options: { bridge: boolean }): string {
25
25
  'Operating rules (when a thread genuinely calls for a code change):',
26
26
  '- Read the skills under `.agents/skills/` FIRST and follow them: **astrale-domain**',
27
27
  ' (schema modeling, handlers, views, deploy/install) before editing schema/handlers,',
28
- ' and **astrale-cli** when you run the `astrale` CLI. Treat those current skills and',
29
- ' the project itself as authoritative; do not restore pre-Kernel-V2 APIs or layouts.',
28
+ ' **astrale-frontend-design** before editing a Domain UI, and **astrale-cli** when you',
29
+ ' run the `astrale` CLI. Treat those current skills and the project itself as authoritative.',
30
30
  '- Prefer the project’s existing semantic modules over inventing new structure. Current',
31
31
  ' SDK projects author Schema definitions, modular Actions and Workflows, a Runtime,',
32
32
  ' and an Application. Wire modules through their semantic SDK facades and explicit',
33
- ' Runtime/Application entries; do not restore defineDomain or Interface-era layouts.',
33
+ ' Runtime/Application entries.',
34
34
  '- The studio re-renders automatically as you save files — never start, build, or refresh',
35
35
  ' anything for the UI to update.',
36
36
  '- Sanity-check schema/handler edits with `pnpm typecheck` (or `tsgo --noEmit`).',
@@ -94,7 +94,7 @@ export async function prepareRun(
94
94
  bareResume && !firstTurn
95
95
  ? buildResumePrompt()
96
96
  : buildTurnPrompt({
97
- origin: bundle?.overlay.origin ?? domainId,
97
+ origin: bundle?.ir?.domain ?? handle.origin ?? domainId,
98
98
  root,
99
99
  renderFingerprint,
100
100
  schemaRevision: bundle?.schemaRevision,
@@ -43,7 +43,7 @@ export async function handleContextRoute(context: DomainRouteContext): Promise<R
43
43
  const storedContext = readContext(root)
44
44
  const openComments = readComments(root).comments.filter((comment) => comment.status === 'open')
45
45
  const markdown = buildCopyMarkdown({
46
- origin: bundle?.overlay.origin ?? id,
46
+ origin: bundle?.ir?.domain ?? handle.origin ?? id,
47
47
  root,
48
48
  renderFingerprint: bundle?.renderFingerprint ?? '',
49
49
  schemaRevision: bundle?.schemaRevision,
@@ -24,7 +24,7 @@ export async function handleDeploymentRoute(context: DomainRouteContext): Promis
24
24
  await instanceStatus(
25
25
  handle,
26
26
  target,
27
- bundle?.overlay.origin ?? null,
27
+ bundle?.ir?.domain ?? handle.origin ?? null,
28
28
  bundle?.schemaRevision ?? null,
29
29
  ),
30
30
  )
@@ -24,7 +24,7 @@ export async function handleViewRoute(context: DomainRouteContext): Promise<Resp
24
24
  const view = anatomy?.views.find((candidate) => candidate.slug === slug)
25
25
  if (!view) return notFound()
26
26
  const bundle = await getBundle(id)
27
- const origin = bundle?.overlay.origin || anatomy?.overview.origin
27
+ const origin = bundle?.ir?.domain || anatomy?.overview.origin
28
28
  if (!origin) return badRequest('domain origin is unavailable')
29
29
  return json(
30
30
  await getViewRuntime(root, origin, view, bundle, readSettings(root).viewProbeTimeoutMs),
@@ -39,7 +39,7 @@ export async function handleViewRoute(context: DomainRouteContext): Promise<Resp
39
39
  const view = anatomy?.views.find((candidate) => candidate.slug === slug)
40
40
  if (!view) return notFound()
41
41
  const bundle = await getBundle(id)
42
- const origin = bundle?.overlay.origin || anatomy?.overview.origin
42
+ const origin = bundle?.ir?.domain || anatomy?.overview.origin
43
43
  if (!origin) return badRequest('domain origin is unavailable')
44
44
  return json(
45
45
  await launchViewSession(
@@ -18,7 +18,7 @@ export async function handleWorkspaceRoute(
18
18
  const bundle = await getBundle(handle.id)
19
19
  return {
20
20
  id: handle.id,
21
- origin: bundle?.overlay.origin || handle.origin || handle.id,
21
+ origin: bundle?.ir?.domain || handle.origin || handle.id,
22
22
  path: handle.root,
23
23
  schemaDir: handle.schemaDirName,
24
24
  depsInstalled: depsInstalled(handle.root),
@@ -4,9 +4,9 @@ import { decodeBundleCacheEntry } from './cache'
4
4
 
5
5
  function entry(): Record<string, unknown> {
6
6
  return {
7
- version: 5,
7
+ version: 6,
8
8
  key: 'cache-key',
9
- futureEntryField: { version: 5 },
9
+ futureEntryField: { version: 6 },
10
10
  bundle: {
11
11
  domainId: 'notes',
12
12
  renderFingerprint: 'render-hash',
@@ -27,13 +27,8 @@ function entry(): Record<string, unknown> {
27
27
  core: {},
28
28
  },
29
29
  overlay: {
30
- origin: 'notes.example.dev',
31
- requires: [],
32
- crossDomainImports: [],
33
- mixins: [],
34
30
  handlerLinks: [],
35
31
  sourceSpans: {},
36
- annotations: [],
37
32
  },
38
33
  schemaRoot: {
39
34
  format: 'astrale.dsl',
@@ -51,7 +46,7 @@ test('bundle cache admits known structure while ignoring future fields', () => {
51
46
  const decoded = decodeBundleCacheEntry(entry())
52
47
 
53
48
  expect(decoded).toMatchObject({
54
- version: 5,
49
+ version: 6,
55
50
  key: 'cache-key',
56
51
  bundle: { domainId: 'notes', schemaMode: 'canonical-preview' },
57
52
  })
@@ -17,16 +17,15 @@ import { buildBundle } from './introspect/bundle'
17
17
  import { isCanonicalDomainSchemaV1 } from './introspect/canonical-schema'
18
18
  import { buildCore } from './introspect/core'
19
19
  import { decodeSchemaIR } from './introspect/schema-ir-json'
20
- import { asBoolean, asFiniteNumber, asJsonRecord, asString, asStringArray } from './json'
20
+ import { asBoolean, asFiniteNumber, asJsonRecord, asString } from './json'
21
21
  import { hashAnatomyFiles } from './state/baseline'
22
22
  import { readJson, writeJson } from './state/store'
23
23
 
24
24
  const bundles = new Map<string, StudioSchemaBundle>()
25
25
  const anatomies = new Map<string, Promise<DomainAnatomy>>()
26
- const cores = new Map<string, StudioCore>()
27
26
 
28
27
  const BUNDLE_CACHE_FILE = '.cache/schema-bundle.json'
29
- const BUNDLE_CACHE_VERSION = 5
28
+ const BUNDLE_CACHE_VERSION = 6
30
29
  const LOCKFILES = ['bun.lock', 'pnpm-lock.yaml', 'package-lock.json', 'yarn.lock']
31
30
  const TOOL_INPUTS = [
32
31
  'cache.ts',
@@ -37,7 +36,6 @@ const TOOL_INPUTS = [
37
36
  'introspect/canonical-schema.ts',
38
37
  'introspect/overlay.ts',
39
38
  'introspect/overlay-tsmorph.ts',
40
- 'introspect/source-overlay/annotations.ts',
41
39
  'introspect/source-overlay/handlers.ts',
42
40
  'introspect/source-overlay/kernel-calls.ts',
43
41
  'introspect/source-overlay/project.ts',
@@ -53,18 +51,6 @@ interface BundleCacheEntry {
53
51
  bundle: StudioSchemaBundle
54
52
  }
55
53
 
56
- function isCrossDomainImport(value: unknown): boolean {
57
- const record = asJsonRecord(value)
58
- const ref = asJsonRecord(record?.ref)
59
- return (
60
- typeof record?.name === 'string' &&
61
- typeof record.origin === 'string' &&
62
- ref?.kind === 'class' &&
63
- typeof ref.origin === 'string' &&
64
- typeof ref.name === 'string'
65
- )
66
- }
67
-
68
54
  function isHandlerLink(value: unknown): boolean {
69
55
  const record = asJsonRecord(value)
70
56
  return (
@@ -87,37 +73,15 @@ function isSourceSpan(value: unknown): boolean {
87
73
  )
88
74
  }
89
75
 
90
- function isAnnotation(value: unknown): boolean {
91
- const record = asJsonRecord(value)
92
- return (
93
- typeof record?.target === 'string' &&
94
- (record.severity === 'warn' || record.severity === 'info') &&
95
- (record.code === 'COMPILE_ERROR' || record.code === 'EDGE_PROP_TYPE_MISSING') &&
96
- typeof record.message === 'string'
97
- )
98
- }
99
-
100
76
  function decodeOverlay(value: unknown): SchemaOverlay | undefined {
101
77
  const record = asJsonRecord(value)
102
- const requires = asStringArray(record?.requires)
103
- const crossDomainImports = record?.crossDomainImports
104
- const mixins = record?.mixins
105
78
  const handlerLinks = record?.handlerLinks
106
79
  const sourceSpans = asJsonRecord(record?.sourceSpans)
107
- const annotations = record?.annotations
108
80
  if (
109
- typeof record?.origin !== 'string' ||
110
- !requires ||
111
- !Array.isArray(crossDomainImports) ||
112
- !crossDomainImports.every(isCrossDomainImport) ||
113
- !Array.isArray(mixins) ||
114
- !mixins.every(isCrossDomainImport) ||
115
81
  !Array.isArray(handlerLinks) ||
116
82
  !handlerLinks.every(isHandlerLink) ||
117
83
  !sourceSpans ||
118
- !Object.values(sourceSpans).every(isSourceSpan) ||
119
- !Array.isArray(annotations) ||
120
- !annotations.every(isAnnotation)
84
+ !Object.values(sourceSpans).every(isSourceSpan)
121
85
  ) {
122
86
  return undefined
123
87
  }
@@ -285,10 +249,7 @@ export async function getAnatomy(id: string, rebuild = false): Promise<DomainAna
285
249
  export async function getCore(id: string, rebuild = false): Promise<StudioCore | null> {
286
250
  const h = getDomain(id)
287
251
  if (!h) return null
288
- if (!rebuild && cores.has(id)) return cores.get(id)!
289
- const c = await buildCore(h)
290
- cores.set(id, c)
291
- return c
252
+ return buildCore(h, await getBundle(id, rebuild))
292
253
  }
293
254
 
294
255
  export function invalidate(id: string, what: 'schema' | 'anatomy' | 'all'): void {
@@ -298,6 +259,4 @@ export function invalidate(id: string, what: 'schema' | 'anatomy' | 'all'): void
298
259
  const domain = getDomain(id)
299
260
  if (domain) invalidateClientPackage(domain.root)
300
261
  }
301
- // Core is derived from the canonical Schema and belongs to the anatomy set.
302
- if (what !== 'schema') cores.delete(id)
303
262
  }
@@ -3,7 +3,7 @@ import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileS
3
3
  import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
 
6
- import type { StudioSchemaBundle, ViewInfo } from '../shared/types'
6
+ import type { StaleReport, StudioSchemaBundle, ViewInfo } from '../shared/types'
7
7
 
8
8
  import { STUDIO_CLI_DESCRIPTOR_ENV } from './cli'
9
9
  import { activeInstanceName, listInstances, setActiveInstance } from './instances/active'
@@ -209,6 +209,7 @@ describe('workspace update CLI orchestration', () => {
209
209
  latest: '0.8.0',
210
210
  channel: 'latest',
211
211
  },
212
+ skills: { status: 'update-available' },
212
213
  sdk: {
213
214
  stale: true,
214
215
  inProject: true,
@@ -235,6 +236,7 @@ describe('workspace update CLI orchestration', () => {
235
236
  latest: '0.8.0',
236
237
  channel: 'latest',
237
238
  },
239
+ skills: { status: 'update-available' },
238
240
  sdk: {
239
241
  stale: true,
240
242
  inProject: true,
@@ -251,6 +253,31 @@ describe('workspace update CLI orchestration', () => {
251
253
  ])
252
254
  })
253
255
 
256
+ test('accepts an older CLI report without a skills axis during binary replacement', async () => {
257
+ const checkArgs = ['update', '--check', '--json']
258
+ const fake = installFakeCli([
259
+ {
260
+ args: checkArgs,
261
+ responses: [
262
+ {
263
+ stdout: {
264
+ stale: false,
265
+ cli: { stale: false, managed: true },
266
+ sdk: { stale: false, inProject: false, outdated: [] },
267
+ },
268
+ },
269
+ ],
270
+ },
271
+ ])
272
+
273
+ expect(await getUpdates(fake.root)).toEqual({
274
+ stale: false,
275
+ cli: { stale: false, managed: true },
276
+ skills: { status: 'current' },
277
+ sdk: { stale: false, inProject: false, outdated: [] },
278
+ })
279
+ })
280
+
254
281
  test('hides the badge when check output is malformed or fails', async () => {
255
282
  const checkArgs = ['update', '--check', '--json']
256
283
  const fake = installFakeCli([
@@ -262,6 +289,7 @@ describe('workspace update CLI orchestration', () => {
262
289
  stdout: {
263
290
  stale: true,
264
291
  cli: { stale: true, managed: true },
292
+ skills: { status: 'current' },
265
293
  sdk: {
266
294
  stale: true,
267
295
  inProject: true,
@@ -274,9 +302,10 @@ describe('workspace update CLI orchestration', () => {
274
302
  ],
275
303
  },
276
304
  ])
277
- const expected = {
305
+ const expected: StaleReport = {
278
306
  stale: false,
279
307
  cli: { stale: false, managed: true },
308
+ skills: { status: 'current' },
280
309
  sdk: { stale: false, inProject: false, outdated: [] },
281
310
  }
282
311
 
@@ -84,7 +84,7 @@ export function buildCopyMarkdown(parts: CopyParts): string {
84
84
  lines.push(`- **Render fingerprint:** \`${renderFingerprint}\``)
85
85
  lines.push('')
86
86
  lines.push(
87
- 'Load the **astrale-domain** skill, use the context/threads below, ' +
87
+ 'Load the **astrale-domain** skill and, for UI work, **astrale-frontend-design**. Use the context/threads below, ' +
88
88
  'read any listed context documents if relevant, edit code on disk, and reply by appending one `{role:"author"}` entry per open thread ' +
89
89
  '(merge by `id`). Resolve a thread by adding a closing note plus `"status":"closed"`; ' +
90
90
  'a thread whose last entry is not yours will be resent.',