@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.
- package/README.md +31 -5
- package/dist/astrale.js +1201 -358
- package/package.json +3 -2
- package/src/commands/__tests__/domain-install-operation.test.ts +23 -0
- package/src/commands/__tests__/install-identity-override.test.ts +3 -3
- package/src/commands/domain/install.ts +19 -11
- package/src/commands/get.ts +1 -1
- package/src/commands/identity/register.ts +3 -4
- package/src/commands/logs.ts +2 -5
- package/src/commands/session/analyze.ts +26 -4
- package/src/commands/update.ts +61 -40
- package/src/lib/__tests__/skills.test.ts +809 -0
- package/src/lib/skills/lock.ts +117 -0
- package/src/lib/skills/sync.ts +814 -0
- package/src/lib/skills.ts +1 -52
- package/src/program/__tests__/program.test.ts +1 -1
- package/src/setup/steps/skills.ts +40 -38
- package/src/telemetry/__tests__/retention.test.ts +180 -0
- package/src/telemetry/__tests__/settings.test.ts +102 -0
- package/src/telemetry/__tests__/store-scan.test.ts +128 -0
- package/src/telemetry/__tests__/trigger.test.ts +33 -4
- package/src/telemetry/recorder.ts +6 -3
- package/src/telemetry/retention.ts +129 -0
- package/src/telemetry/session.ts +18 -12
- package/src/telemetry/settings.ts +64 -11
- package/src/telemetry/store.ts +87 -9
- package/src/telemetry/trigger.ts +16 -28
- package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
- package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
- package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
- package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
- package/studio/client/dist/index.html +3 -3
- package/studio/server/agent/prompts/anchors.test.ts +17 -4
- package/studio/server/agent/prompts/anchors.ts +3 -2
- package/studio/server/agent/prompts/system.test.ts +2 -3
- package/studio/server/agent/prompts/system.ts +3 -3
- package/studio/server/agent/run/preparation.ts +1 -1
- package/studio/server/api/context.ts +1 -1
- package/studio/server/api/deployment.ts +1 -1
- package/studio/server/api/views.ts +2 -2
- package/studio/server/api/workspace.ts +1 -1
- package/studio/server/cache.test.ts +3 -8
- package/studio/server/cache.ts +4 -45
- package/studio/server/cli-consumers.test.ts +31 -2
- package/studio/server/handoff/copy.ts +1 -1
- package/studio/server/introspect/canonical-schema.test.ts +154 -128
- package/studio/server/introspect/canonical-schema.ts +183 -302
- package/studio/server/introspect/core.ts +14 -21
- package/studio/server/introspect/extractor.ts +11 -15
- package/studio/server/introspect/overlay-tsmorph.test.ts +1 -5
- package/studio/server/introspect/overlay-tsmorph.ts +0 -1
- package/studio/server/introspect/overlay.ts +3 -24
- package/studio/server/introspect/revision.test.ts +24 -28
- package/studio/server/introspect/revision.ts +10 -21
- package/studio/server/introspect/runtime.test.ts +14 -14
- package/studio/server/introspect/runtime.ts +1 -46
- package/studio/server/lifecycle.ts +4 -1
- package/studio/server/state/documents.test.ts +69 -0
- package/studio/server/state/documents.ts +57 -10
- package/studio/server/workspace/updates.ts +23 -1
- package/studio/shared/contracts/runtime.ts +4 -0
- package/studio/shared/contracts/schema.ts +10 -26
- package/studio/shared/contracts/surface.test.ts +2 -2
- package/studio/shared/contracts/workspace.ts +3 -0
- package/studio/shared/schema/identity.ts +0 -1
- package/tsconfig.json +1 -1
- package/studio/client/dist/assets/index-C0FAnwNw.css +0 -2
- package/studio/client/dist/assets/index-OeJK1TjG.js +0 -81
- package/studio/client/dist/assets/schema-studio-BilUNb6Z.css +0 -1
- package/studio/client/dist/assets/schema-studio-DRdfu_cQ.js +0 -8
- package/studio/server/introspect/core-extractor.ts +0 -30
- package/studio/server/introspect/overlay.test.ts +0 -44
- package/studio/server/introspect/source-overlay/annotations.ts +0 -14
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en"
|
|
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-
|
|
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-
|
|
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: {
|
|
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
|
-
|
|
67
|
-
|
|
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
|
|
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
|
|
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('
|
|
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
|
-
'
|
|
29
|
-
' the
|
|
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
|
|
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?.
|
|
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?.
|
|
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 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?.
|
|
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?.
|
|
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?.
|
|
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:
|
|
7
|
+
version: 6,
|
|
8
8
|
key: 'cache-key',
|
|
9
|
-
futureEntryField: { version:
|
|
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:
|
|
49
|
+
version: 6,
|
|
55
50
|
key: 'cache-key',
|
|
56
51
|
bundle: { domainId: 'notes', schemaMode: 'canonical-preview' },
|
|
57
52
|
})
|
package/studio/server/cache.ts
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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.',
|