@frontera-sdk/cli 1.52.40 → 1.52.41
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/package.json +4 -4
- package/src/blueprint/ontology-edit-plan.ts +1 -1
- package/src/commands/agent/draft-changes.ts +1 -1
- package/src/commands/blueprint/declarative.ts +1 -1
- package/src/commands/blueprint/editable.ts +3 -3
- package/src/commands/local/dev.ts +1 -1
- package/src/commands/local/sync-request.ts +1 -1
- package/src/commands/local/sync.ts +1 -1
- package/src/commands/registry.ts +2 -2
- package/src/forge/claude-commands.ts +11 -6
- package/src/forge/claude-skills.ts +4 -4
- package/src/heal.ts +1 -1
- package/src/kit.ts +1 -1
- package/src/local/local-agent-grants.ts +4 -4
- package/src/local/local-applied.ts +1 -1
- package/src/local/local-apps.ts +1 -1
- package/src/local/local-dev-references.ts +2 -2
- package/src/local/local-dev-sync.ts +6 -6
- package/src/local/local-knowledge-documents.ts +1 -1
- package/src/local/local-lessons.ts +1 -1
- package/src/local/local-library.ts +1 -1
- package/src/local/local-org-settings.ts +1 -1
- package/src/local/local-portability.ts +2 -2
- package/src/local/local-secrets.ts +3 -3
- package/src/local/local-stand-ins.ts +2 -2
- package/src/local/local-sync-merge.ts +1 -1
- package/src/local/local-sync-plan.ts +8 -8
- package/src/local/local-sync-push.ts +5 -5
- package/src/local/local-sync-remote.ts +6 -6
- package/src/local/local-sync-snapshot.ts +4 -4
- package/src/scopes.ts +1 -1
- package/src/vendor/sdk-sources.json +16 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontera-sdk/cli",
|
|
3
|
-
"version": "1.52.
|
|
3
|
+
"version": "1.52.41",
|
|
4
4
|
"description": "The frontera CLI — scaffold, pull, save and deploy Frontera apps and automations.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"frontera",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/claude-agent-sdk": "^0.3.251",
|
|
42
42
|
"@faker-js/faker": "10.6.0",
|
|
43
|
-
"@frontera-sdk/core": "1.52.
|
|
44
|
-
"@frontera-sdk/functions": "1.52.
|
|
43
|
+
"@frontera-sdk/core": "1.52.41",
|
|
44
|
+
"@frontera-sdk/functions": "1.52.41",
|
|
45
45
|
"ai": "^6.0.116",
|
|
46
46
|
"axe-core": "4.12.1",
|
|
47
47
|
"gray-matter": "^4.0.3",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"yaml": "^2.9.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@frontera-sdk/forge-contracts": "1.52.
|
|
52
|
+
"@frontera-sdk/forge-contracts": "1.52.41",
|
|
53
53
|
"@types/bun": "^1.3.14",
|
|
54
54
|
"typescript": "^5.9.3"
|
|
55
55
|
}
|
|
@@ -73,7 +73,7 @@ export function ontologyEditCompatibilityFingerprint(
|
|
|
73
73
|
): string {
|
|
74
74
|
const canonical = JSON.stringify({
|
|
75
75
|
objectTypeId,
|
|
76
|
-
usedProperties: [...new Set(usedProperties)].sort(),
|
|
76
|
+
usedProperties: [...new Set(usedProperties)].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)),
|
|
77
77
|
})
|
|
78
78
|
return `sha256:${createHash('sha256').update(canonical).digest('hex')}`
|
|
79
79
|
}
|
|
@@ -79,7 +79,7 @@ export async function agentDraftChanges(api: PlatformApi, id: string): Promise<A
|
|
|
79
79
|
(k) => stableStringify(draftSections[k]) !== stableStringify(liveSnapshot[k]),
|
|
80
80
|
)
|
|
81
81
|
: []),
|
|
82
|
-
].sort()
|
|
82
|
+
].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
83
83
|
|
|
84
84
|
return { hasDraft: true, published, revision: draft.revision, changed }
|
|
85
85
|
}
|
|
@@ -1522,7 +1522,7 @@ export function orderForPrune(
|
|
|
1522
1522
|
if (blocked.length > 0) {
|
|
1523
1523
|
const named = blocked
|
|
1524
1524
|
.map((action) => `${action.apiName} → ${action.compensates}`)
|
|
1525
|
-
.sort()
|
|
1525
|
+
.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
1526
1526
|
.join(', ')
|
|
1527
1527
|
throw new CliError(
|
|
1528
1528
|
`${blocked.length === 1 ? 'An action that survives this prune still names' : 'Actions that survive this prune still name'} `
|
|
@@ -40,7 +40,7 @@ export function nextEditableSelection(
|
|
|
40
40
|
const base = selection.set ?? [...current]
|
|
41
41
|
const removed = new Set(selection.remove ?? [])
|
|
42
42
|
const next = new Set([...base, ...(selection.add ?? [])].filter((name) => !removed.has(name)))
|
|
43
|
-
return [...next].sort()
|
|
43
|
+
return [...next].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function api(ctx: CommandContext): BlueprintAuthoringApi {
|
|
@@ -93,7 +93,7 @@ export const blueprintEditable: Command = {
|
|
|
93
93
|
// the way out. Mapping them through the property ids again read every one
|
|
94
94
|
// as unknown and reported an empty set — which would have let `--add` post
|
|
95
95
|
// a set containing only the addition, silently dropping the rest.
|
|
96
|
-
const current = [...detail.editableProperties].sort()
|
|
96
|
+
const current = [...detail.editableProperties].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
97
97
|
|
|
98
98
|
if (!hasSet && add.length === 0 && remove.length === 0) {
|
|
99
99
|
return {
|
|
@@ -119,7 +119,7 @@ export const blueprintEditable: Command = {
|
|
|
119
119
|
`${unknown.join(', ')} ${unknown.length === 1 ? 'is not a property' : 'are not properties'} of "${apiName}".`,
|
|
120
120
|
{
|
|
121
121
|
code: 'USAGE',
|
|
122
|
-
hint: `Properties: ${[...known].sort().join(', ')}`,
|
|
122
|
+
hint: `Properties: ${[...known].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).join(', ')}`,
|
|
123
123
|
},
|
|
124
124
|
)
|
|
125
125
|
}
|
|
@@ -315,7 +315,7 @@ async function apply(project: LocalProject, target: SyncTarget, opts: ApplyOptio
|
|
|
315
315
|
if (!result.ok && fallback?.when.test(result.output)) {
|
|
316
316
|
// An empty fallback means there was nothing more to do.
|
|
317
317
|
result = { ok: true, output: '' }
|
|
318
|
-
for (const args of fallback.
|
|
318
|
+
for (const args of fallback.steps) {
|
|
319
319
|
result = await runCli(project, args, opts.auth)
|
|
320
320
|
if (!result.ok) break
|
|
321
321
|
}
|
|
@@ -723,7 +723,7 @@ async function show(ctx: CommandContext) {
|
|
|
723
723
|
const checks = lastChecks(view)
|
|
724
724
|
const items = view.items.map((item) => {
|
|
725
725
|
const base = bases[item.ref]!
|
|
726
|
-
const paths = [...new Set([...Object.keys(base.files), ...Object.keys(item.files)])].sort()
|
|
726
|
+
const paths = [...new Set([...Object.keys(base.files), ...Object.keys(item.files)])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
727
727
|
const files = paths.map((path) => ({ path, base: base.files[path] ?? null, local: item.files[path] ?? null })).filter((f) => f.base !== f.local || !base.known)
|
|
728
728
|
const change = item.deleted ? ('deleted' as const) : item.baseRevision ? ('changed' as const) : ('new' as const)
|
|
729
729
|
const title = titleOf(item, titles)
|
|
@@ -809,7 +809,7 @@ async function itemState(project: LocalProject, link: SyncLink, ref: string) {
|
|
|
809
809
|
const item = remote.items.find((i) => i.ref === ref) ?? null
|
|
810
810
|
const entry = lock?.workspace === link.workspace ? lock.items[ref] : undefined
|
|
811
811
|
if (!row && !item && !entry) throw new UsageError(`not in the workspace or the project: ${ref}`, 'list items with `frontera local sync pull --preview`')
|
|
812
|
-
const paths = [...new Set([...Object.keys(item?.files ?? {}), ...Object.keys(entry?.files ?? {}), ...(entry?.conflict?.files ?? [])])].sort()
|
|
812
|
+
const paths = [...new Set([...Object.keys(item?.files ?? {}), ...Object.keys(entry?.files ?? {}), ...(entry?.conflict?.files ?? [])])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
813
813
|
const base = baseFiles(entry, (p) => readBase(project.root, p))
|
|
814
814
|
const local = localFiles(project.root, paths)
|
|
815
815
|
const theirs = item?.files ?? {}
|
package/src/commands/registry.ts
CHANGED
|
@@ -476,7 +476,7 @@ export function renderCommandHelp(meta: CommandMeta): string {
|
|
|
476
476
|
// Command flags first, then the ones every command shares — what is specific
|
|
477
477
|
// to this command is what the reader came for.
|
|
478
478
|
const shared = new Set(Object.keys(GLOBAL_FLAGS))
|
|
479
|
-
const own = Object.keys(spec).filter((f) => !shared.has(f)).sort()
|
|
479
|
+
const own = Object.keys(spec).filter((f) => !shared.has(f)).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
480
480
|
const short = Object.entries(aliasesFor(meta)).reduce<Record<string, string>>((acc, [s, long]) => {
|
|
481
481
|
acc[long] = s
|
|
482
482
|
return acc
|
|
@@ -545,7 +545,7 @@ export function describeCommand(meta: CommandMeta): CommandDescriptor {
|
|
|
545
545
|
...(meta.reserved ? { unavailableReason: meta.reserved } : {}),
|
|
546
546
|
args: meta.args.map((a) => ({ ...a })),
|
|
547
547
|
flags: Object.keys(spec)
|
|
548
|
-
.sort()
|
|
548
|
+
.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
549
549
|
.map((name) => ({
|
|
550
550
|
name,
|
|
551
551
|
type: spec[name]!,
|
|
@@ -59,12 +59,17 @@ async function probe(cwd: string, claudePath: string, log?: Logger): Promise<For
|
|
|
59
59
|
const session = query({
|
|
60
60
|
// Never yields: the initialization response is all this call is for, and
|
|
61
61
|
// a prompt that resolved would send a turn to the model.
|
|
62
|
-
prompt:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
prompt: {
|
|
63
|
+
[Symbol.asyncIterator]() {
|
|
64
|
+
return {
|
|
65
|
+
next: () => new Promise<IteratorResult<never>>((resolve) => {
|
|
66
|
+
const done = () => resolve({ done: true, value: undefined })
|
|
67
|
+
if (abort.signal.aborted) done()
|
|
68
|
+
else abort.signal.addEventListener('abort', done, { once: true })
|
|
69
|
+
}),
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
68
73
|
options: {
|
|
69
74
|
cwd,
|
|
70
75
|
pathToClaudeCodeExecutable: claudePath,
|
|
@@ -58,7 +58,7 @@ function readSkillDir(dir: string): Array<{ dirName: string; path: string; conte
|
|
|
58
58
|
return []
|
|
59
59
|
}
|
|
60
60
|
const out: Array<{ dirName: string; path: string; contents: string }> = []
|
|
61
|
-
for (const entry of [...entries].sort()) {
|
|
61
|
+
for (const entry of [...entries].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
62
62
|
const path = join(dir, entry, 'SKILL.md')
|
|
63
63
|
try {
|
|
64
64
|
out.push({ dirName: entry, path, contents: readFileSync(path, 'utf8') })
|
|
@@ -92,14 +92,14 @@ function pluginSkillRoots(configDir: string): Array<{ plugin: string; dir: strin
|
|
|
92
92
|
} catch {
|
|
93
93
|
return []
|
|
94
94
|
}
|
|
95
|
-
for (const marketplace of marketplaces.sort()) {
|
|
95
|
+
for (const marketplace of marketplaces.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
96
96
|
let plugins: string[]
|
|
97
97
|
try {
|
|
98
98
|
plugins = readdirSync(join(cache, marketplace))
|
|
99
99
|
} catch {
|
|
100
100
|
continue
|
|
101
101
|
}
|
|
102
|
-
for (const plugin of plugins.sort()) {
|
|
102
|
+
for (const plugin of plugins.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
103
103
|
let versions: string[]
|
|
104
104
|
try {
|
|
105
105
|
versions = readdirSync(join(cache, marketplace, plugin))
|
|
@@ -107,7 +107,7 @@ function pluginSkillRoots(configDir: string): Array<{ plugin: string; dir: strin
|
|
|
107
107
|
continue
|
|
108
108
|
}
|
|
109
109
|
// Newest version wins when a cache holds more than one.
|
|
110
|
-
for (const version of versions.sort().reverse()) {
|
|
110
|
+
for (const version of versions.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).reverse()) {
|
|
111
111
|
const dir = join(cache, marketplace, plugin, version, 'skills')
|
|
112
112
|
try {
|
|
113
113
|
if (statSync(dir).isDirectory()) {
|
package/src/heal.ts
CHANGED
|
@@ -491,7 +491,7 @@ function vendorSdkPackage(dir: string, pkg: AppSdkPackage): void {
|
|
|
491
491
|
*/
|
|
492
492
|
export function vendoredManifest(pkg: AppSdkPackage, files: string[]): Record<string, unknown> {
|
|
493
493
|
const exports: Record<string, unknown> = { './package.json': './package.json' }
|
|
494
|
-
for (const rel of files.sort()) {
|
|
494
|
+
for (const rel of files.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
495
495
|
if (!/\.tsx?$/.test(rel)) continue
|
|
496
496
|
exports[`./${rel.replace(/\.tsx?$/, '')}`] = { types: `./${rel}`, import: `./${rel}` }
|
|
497
497
|
}
|
package/src/kit.ts
CHANGED
|
@@ -135,7 +135,7 @@ export interface RenderedProject {
|
|
|
135
135
|
/** One source, two native projections — Codex reads neither host's other tree. */
|
|
136
136
|
export function renderKit(kit: KitAssets = KIT): RenderedProject {
|
|
137
137
|
const generated: Record<string, string> = {}
|
|
138
|
-
for (const rel of Object.keys(kit.assets).sort()) {
|
|
138
|
+
for (const rel of Object.keys(kit.assets).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
139
139
|
const content = kit.assets[rel]!
|
|
140
140
|
if (kit.hosts.codex) generated[join('.agents', 'skills', rel)] = content
|
|
141
141
|
if (kit.hosts.claudeCode) generated[join('.claude', 'skills', rel)] = content
|
|
@@ -88,7 +88,7 @@ export async function applyAgentGrants(input: {
|
|
|
88
88
|
continue
|
|
89
89
|
}
|
|
90
90
|
// `read` is implied by the others (`read_sensitive`, `act`), as the runtime stores the grant.
|
|
91
|
-
wanted.set(ref.name, [...new Set(['read', ...(Array.isArray(g.permissions) ? g.permissions : [])])].sort())
|
|
91
|
+
wanted.set(ref.name, [...new Set(['read', ...(Array.isArray(g.permissions) ? g.permissions : [])])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)))
|
|
92
92
|
if (input.keepHeld || !Array.isArray(g.permissions)) keep.add(ref.name)
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -103,7 +103,7 @@ export async function applyAgentGrants(input: {
|
|
|
103
103
|
const onRuntime = new Set(held.ok ? list<{ apiName?: string }>(held.data).flatMap((t) => (t.apiName ? [t.apiName] : [])) : [])
|
|
104
104
|
const current = await call(creds.orgKey, `/v1/blueprint/grants/agent/${encodeURIComponent(agent.id)}`)
|
|
105
105
|
if (!current.ok) return { failures: [...failures, ` agent/${slug}: its Blueprint access could not be read: ${brief(current)}`], notes }
|
|
106
|
-
const has = new Map(list<{ objectTypeApiName: string; permissions?: string[] }>(current.data).map((g) => [g.objectTypeApiName, [...new Set(['read', ...(g.permissions ?? [])])].sort()]))
|
|
106
|
+
const has = new Map(list<{ objectTypeApiName: string; permissions?: string[] }>(current.data).map((g) => [g.objectTypeApiName, [...new Set(['read', ...(g.permissions ?? [])])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))]))
|
|
107
107
|
|
|
108
108
|
let granted = 0
|
|
109
109
|
const acting: string[] = []
|
|
@@ -111,7 +111,7 @@ export async function applyAgentGrants(input: {
|
|
|
111
111
|
for (const [apiName, stated] of wanted) {
|
|
112
112
|
const held = has.get(apiName) ?? []
|
|
113
113
|
const extras = keep.has(apiName) ? held.filter((p) => !stated.includes(p)) : []
|
|
114
|
-
const permissions = extras.length ? [...stated, ...extras].sort() : stated
|
|
114
|
+
const permissions = extras.length ? [...stated, ...extras].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)) : stated
|
|
115
115
|
const why = input.leftOff(apiName)
|
|
116
116
|
if (why) {
|
|
117
117
|
notes.push(` warning: Context and data: ${apiName} is left off here (${why}), so ${slug} can’t read it locally`)
|
|
@@ -141,7 +141,7 @@ export async function applyAgentGrants(input: {
|
|
|
141
141
|
const res = await call(creds.orgKey, `/v1/blueprint/grants/agent/${encodeURIComponent(agent.id)}/${encodeURIComponent(apiName)}`, 'DELETE')
|
|
142
142
|
if (!res.ok) failures.push(` objectType/${apiName}: could not be revoked: ${brief(res)}`)
|
|
143
143
|
}
|
|
144
|
-
if (granted) notes.push(` Context and data: ${granted} object type${granted === 1 ? '' : 's'}${acting.length ? `; ${slug} can submit Actions on ${acting.sort().join(', ')} (act)` : ''}`)
|
|
144
|
+
if (granted) notes.push(` Context and data: ${granted} object type${granted === 1 ? '' : 's'}${acting.length ? `; ${slug} can submit Actions on ${acting.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).join(', ')} (act)` : ''}`)
|
|
145
145
|
if (kept.length) notes.push(` Context and data: kept what ${slug}'s grants already held where its bindings.json names no permissions: ${kept.join('; ')}`)
|
|
146
146
|
return { failures, notes }
|
|
147
147
|
}
|
|
@@ -91,7 +91,7 @@ export function contentHash(project: LocalProject, target: SyncTarget): string |
|
|
|
91
91
|
// shared files it imports from outside functions/.
|
|
92
92
|
if (target.kind === 'function') {
|
|
93
93
|
hash.update(treeHash(join(project.root, 'functions')) ?? '').update(treeHash(join(project.root, 'vendor')) ?? '')
|
|
94
|
-
for (const path of importedFiles(project.root, target.file).filter((p) => !p.startsWith('functions/')).sort()) hash.update(path).update('\0').update(fileDigest(join(project.root, path)) ?? '')
|
|
94
|
+
for (const path of importedFiles(project.root, target.file).filter((p) => !p.startsWith('functions/')).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) hash.update(path).update('\0').update(fileDigest(join(project.root, path)) ?? '')
|
|
95
95
|
}
|
|
96
96
|
// Context and data the pull could not read is assumed on the project's object types: a new one changes it.
|
|
97
97
|
if (target.kind === 'bindings' && /"blueprintUnread"\s*:\s*true/.test(readFileSync(join(project.root, target.file), 'utf8'))) {
|
package/src/local/local-apps.ts
CHANGED
|
@@ -154,7 +154,7 @@ export async function applyApp(root: string, slug: string, call: Call, provides:
|
|
|
154
154
|
const gone = await call('DELETE', `/v1/functions/${encodeURIComponent(fn)}/app-bindings/${encodeURIComponent(slug)}`)
|
|
155
155
|
if (gone.status >= 400 && gone.status !== 404) lines.push(` warning: Function ${fn} could not be unbound from it (${errorOf(gone)})`)
|
|
156
156
|
}
|
|
157
|
-
lines.unshift(` saved App ${name}${named.size ? ` (may start ${[...named].sort().join(', ')})` : ''}`)
|
|
157
|
+
lines.unshift(` saved App ${name}${named.size ? ` (may start ${[...named].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).join(', ')})` : ''}`)
|
|
158
158
|
return { ok: true, output: lines.join('\n') }
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -730,7 +730,7 @@ export function editableSets(dir: string, subjects: ReadonlySet<string> | null):
|
|
|
730
730
|
try {
|
|
731
731
|
const doc = parseYaml(readFileSync(join(at, name), 'utf8')) as { apiName?: unknown; backing?: { editable?: unknown } } | null
|
|
732
732
|
const editable = doc?.backing?.editable
|
|
733
|
-
if (typeof doc?.apiName === 'string' && (!subjects || subjects.has(doc.apiName)) && Array.isArray(editable) && editable.length) out.set(doc.apiName, editable.map(String).sort())
|
|
733
|
+
if (typeof doc?.apiName === 'string' && (!subjects || subjects.has(doc.apiName)) && Array.isArray(editable) && editable.length) out.set(doc.apiName, editable.map(String).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)))
|
|
734
734
|
} catch {
|
|
735
735
|
// A file that does not parse is refused by the apply itself.
|
|
736
736
|
}
|
|
@@ -1420,7 +1420,7 @@ export async function applyBindings(project: LocalProject, target: Extract<SyncT
|
|
|
1420
1420
|
const unread = !Array.isArray(doc.blueprint) && (doc as { blueprintUnread?: unknown }).blueprintUnread === true
|
|
1421
1421
|
if (Array.isArray(doc.blueprint) || unread) {
|
|
1422
1422
|
const types = projectObjectTypes(project.root)
|
|
1423
|
-
const keptHere = [...types].filter(([, type]) => !objectTypeFit(type, () => true).leftOff).map(([apiName]) => apiName).sort()
|
|
1423
|
+
const keptHere = [...types].filter(([, type]) => !objectTypeFit(type, () => true).leftOff).map(([apiName]) => apiName).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
1424
1424
|
const granted = await applyAgentGrants({
|
|
1425
1425
|
slug: target.slug,
|
|
1426
1426
|
grants: unread ? keptHere.map((apiName) => ({ objectType: `objectType/${apiName}`, permissions: ['read'] })) : (doc.blueprint as AgentGrant[]),
|
|
@@ -299,10 +299,10 @@ export function commandFor(target: SyncTarget, profiles: SyncProfiles): string[]
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
/** When the first command fails with `when`, run `
|
|
302
|
+
/** When the first command fails with `when`, run `steps` instead. */
|
|
303
303
|
export interface Fallback {
|
|
304
304
|
when: RegExp
|
|
305
|
-
|
|
305
|
+
steps: string[][]
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
/**
|
|
@@ -310,7 +310,7 @@ export interface Fallback {
|
|
|
310
310
|
* Agent applied for the first time is created, then applied (`agent apply`
|
|
311
311
|
* stages onto an existing draft); a dataset that exists already gets its CSV
|
|
312
312
|
* as a new snapshot (`dataset create` refuses a name in use). A dataset with
|
|
313
|
-
* no CSV that exists already has nothing more to load: an empty `
|
|
313
|
+
* no CSV that exists already has nothing more to load: an empty `steps`.
|
|
314
314
|
*
|
|
315
315
|
* Only rows reach an existing dataset. A CSV with empty values a column of its
|
|
316
316
|
* revision does not allow is refused; `local dev` then cuts the dataset's next
|
|
@@ -322,14 +322,14 @@ export function fallbackFor(target: SyncTarget, profiles: SyncProfiles, root: st
|
|
|
322
322
|
if (target.kind === 'agent') {
|
|
323
323
|
// Created with its name, or every list reads the slug until it is published.
|
|
324
324
|
const named = createFlags(agentIdentity(root, target.file))
|
|
325
|
-
return { when: /NOT_FOUND/,
|
|
325
|
+
return { when: /NOT_FOUND/, steps: [['agent', 'create', target.slug, ...named, '--profile', profiles.workspace], commandFor(target, profiles)] }
|
|
326
326
|
}
|
|
327
327
|
// A workflow archived on the runtime takes no new version: restored, then pushed (the files say it is in service).
|
|
328
|
-
if (target.kind === 'workflow') return { when: /is archived, so it cannot/i,
|
|
328
|
+
if (target.kind === 'workflow') return { when: /is archived, so it cannot/i, steps: [['workflow', 'restore', target.slug, '--profile', profiles.workspace], commandFor(target, profiles)] }
|
|
329
329
|
if (target.kind === 'dataset') {
|
|
330
330
|
const doc = datasetDocument(root, target)
|
|
331
331
|
const upload = doc?.csv ? [['dataset', 'upload', doc.apiName, doc.csv, '--profile', profiles.organization]] : []
|
|
332
|
-
return { when: /already in use/i,
|
|
332
|
+
return { when: /already in use/i, steps: upload }
|
|
333
333
|
}
|
|
334
334
|
return null
|
|
335
335
|
}
|
|
@@ -179,7 +179,7 @@ export function documentPaths(root: string, kb: string): string[] {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
if (existsSync(top)) walk('', 0)
|
|
182
|
-
return out.sort()
|
|
182
|
+
return out.sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/** The documents in a knowledge base's folder and its subfolders, by path. */
|
|
@@ -96,7 +96,7 @@ export async function pullLessons(root: string, workspace: string, ws: SyncClien
|
|
|
96
96
|
const byAgent = new Map(rows<LessonsRow>(found).map((r) => [r.agent, r.rules]))
|
|
97
97
|
const record = readRecord(root, workspace)
|
|
98
98
|
const out: LessonsPulled = { written: [], removed: [], kept: [], rules: 0 }
|
|
99
|
-
for (const slug of [...new Set(agents)].sort()) {
|
|
99
|
+
for (const slug of [...new Set(agents)].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
100
100
|
const path = join(root, lessonsPath(slug))
|
|
101
101
|
const here = existsSync(path) ? readFileSync(path, 'utf8') : null
|
|
102
102
|
const editedHere = here !== null && record.files[slug] !== sha(here)
|
|
@@ -64,7 +64,7 @@ export function readLibrary(rows: LibraryRow[], directory: Directory): RemoteIte
|
|
|
64
64
|
release: { number: r.release.number, version: r.release.version },
|
|
65
65
|
...(r.state && r.state !== 'installed' ? { state: r.state } : {}),
|
|
66
66
|
...(agent ? { agent: `agent/${agent}` } : {}),
|
|
67
|
-
...(made.length ? { skills: [...new Set(made)].sort() } : {}),
|
|
67
|
+
...(made.length ? { skills: [...new Set(made)].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)) } : {}),
|
|
68
68
|
}
|
|
69
69
|
})
|
|
70
70
|
const doc: LibraryFile = { installations }
|
|
@@ -145,7 +145,7 @@ export function settingsDifferences(workspace: OrgSettings, local: OrgSettings,
|
|
|
145
145
|
if (both('voiceSpeed') && differs(workspace.voiceSpeed, local.voiceSpeed)) out.push(`Voice speed is ${workspace.voiceSpeed ?? DEFAULT} in the workspace and ${local.voiceSpeed ?? DEFAULT} here`)
|
|
146
146
|
const ws = workspace.featureFlags ?? {}
|
|
147
147
|
const here = local.featureFlags ?? {}
|
|
148
|
-
for (const key of Object.keys(ws).sort()) {
|
|
148
|
+
for (const key of Object.keys(ws).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
149
149
|
if (NOT_A_DIFFERENCE_HERE.has(key)) continue
|
|
150
150
|
if (key in here && ws[key] !== here[key]) out.push(`Feature ${key} is ${onOff(ws[key])} in the workspace and ${onOff(here[key])} here`)
|
|
151
151
|
}
|
|
@@ -427,9 +427,9 @@ export function itemNeeds(kind: string, files: Record<string, string>, connectio
|
|
|
427
427
|
}
|
|
428
428
|
needs.push({ kind: 'app-code', ...(external ? { external } : {}) })
|
|
429
429
|
}
|
|
430
|
-
for (const name of [...actions].sort()) needs.push({ kind: 'action', name })
|
|
430
|
+
for (const name of [...actions].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) needs.push({ kind: 'action', name })
|
|
431
431
|
if (unnamed) needs.push({ kind: 'action-unnamed', count: unnamed })
|
|
432
|
-
for (const name of [...plugins].sort()) needs.push({ kind: builtIns.get(name) ?? 'connection', name })
|
|
432
|
+
for (const name of [...plugins].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) needs.push({ kind: builtIns.get(name) ?? 'connection', name })
|
|
433
433
|
return needs
|
|
434
434
|
}
|
|
435
435
|
|
|
@@ -55,7 +55,7 @@ export function pluginSecretName(plugin: string, field: string): string {
|
|
|
55
55
|
|
|
56
56
|
/** The `secret:NAME` grants a Function's source names. */
|
|
57
57
|
export function functionSecretNames(source: string): string[] {
|
|
58
|
-
return [...new Set([...source.matchAll(/['"`]secret:([A-Z][A-Z0-9_]*)['"`]/g)].map((m) => m[1]!))].sort()
|
|
58
|
+
return [...new Set([...source.matchAll(/['"`]secret:([A-Z][A-Z0-9_]*)['"`]/g)].map((m) => m[1]!))].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** The secrets a workflow's HTTP steps and webhook trigger name in their `auth`, at any depth. */
|
|
@@ -70,7 +70,7 @@ export function workflowSecretNames(doc: unknown): string[] {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
visit(doc)
|
|
73
|
-
return [...out].sort()
|
|
73
|
+
return [...out].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** A Plugin setting declared empty in its file: a header or a config key, and the local secret that fills it. */
|
|
@@ -262,7 +262,7 @@ function sameBesideSecrets(text: string, base: string): boolean {
|
|
|
262
262
|
|
|
263
263
|
function sortKeys(v: unknown): unknown {
|
|
264
264
|
if (Array.isArray(v)) return v.map(sortKeys)
|
|
265
|
-
if (v && typeof v === 'object') return Object.fromEntries(Object.keys(v as Record<string, unknown>).sort().map((k) => [k, sortKeys((v as Record<string, unknown>)[k])]))
|
|
265
|
+
if (v && typeof v === 'object') return Object.fromEntries(Object.keys(v as Record<string, unknown>).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).map((k) => [k, sortKeys((v as Record<string, unknown>)[k])]))
|
|
266
266
|
return v
|
|
267
267
|
}
|
|
268
268
|
|
|
@@ -70,7 +70,7 @@ const DOCS_TOOLS: StandInFile['tools'] = [
|
|
|
70
70
|
|
|
71
71
|
/** The built-in Plugins these items need that get a stand-in (connections, the Documentation plugin), by install name. */
|
|
72
72
|
export function connectionsNeeded(items: Array<Pick<RemoteItem, 'portability'>>): string[] {
|
|
73
|
-
return [...new Set(items.flatMap((i) => (i.portability?.needs ?? []).flatMap((n) => (n.kind === 'connection' || n.kind === 'documentation' ? [n.name] : []))))].sort()
|
|
73
|
+
return [...new Set(items.flatMap((i) => (i.portability?.needs ?? []).flatMap((n) => (n.kind === 'connection' || n.kind === 'documentation' ? [n.name] : []))))].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** Which of those are the Documentation plugin. */
|
|
@@ -80,7 +80,7 @@ export function documentationNeeded(items: Array<Pick<RemoteItem, 'portability'>
|
|
|
80
80
|
|
|
81
81
|
/** The workspace's analytics Plugins these items use: platform-only. */
|
|
82
82
|
export function analyticsNeeded(items: Array<Pick<RemoteItem, 'portability'>>): string[] {
|
|
83
|
-
return [...new Set(items.flatMap((i) => (i.portability?.needs ?? []).flatMap((n) => (n.kind === 'analytics' ? [n.name] : []))))].sort()
|
|
83
|
+
return [...new Set(items.flatMap((i) => (i.portability?.needs ?? []).flatMap((n) => (n.kind === 'analytics' ? [n.name] : []))))].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// ── Platform-only Plugins ────────────────────────────────────────────────────
|
|
@@ -372,7 +372,7 @@ export function mergeItemFiles(
|
|
|
372
372
|
theirs: Record<string, string | null>,
|
|
373
373
|
opts: Omit<MergeOptions, 'offset'> = {},
|
|
374
374
|
): { files: FileMerge[]; conflicts: number } {
|
|
375
|
-
const paths = [...new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])].sort()
|
|
375
|
+
const paths = [...new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
376
376
|
const files: FileMerge[] = []
|
|
377
377
|
let offset = 0
|
|
378
378
|
for (const path of paths) {
|
|
@@ -223,7 +223,7 @@ export function hash(text: string): string {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
export function itemHash(files: Record<string, string>): string {
|
|
226
|
-
return hash(Object.keys(files).sort().map((p) => `${p}\0${files[p]}`).join('\0\0'))
|
|
226
|
+
return hash(Object.keys(files).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).map((p) => `${p}\0${files[p]}`).join('\0\0'))
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
export function readLock(root: string): SyncLock | null {
|
|
@@ -240,7 +240,7 @@ export function readLock(root: string): SyncLock | null {
|
|
|
240
240
|
|
|
241
241
|
/** Sorted keys, two spaces, a final newline: a lock diffs cleanly in review. Always written as v2. */
|
|
242
242
|
export function writeLock(root: string, lock: SyncLock): void {
|
|
243
|
-
const items = Object.fromEntries(Object.keys(lock.items).sort().map((k) => [k, sortEntry(lock.items[k]!)]))
|
|
243
|
+
const items = Object.fromEntries(Object.keys(lock.items).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).map((k) => [k, sortEntry(lock.items[k]!)]))
|
|
244
244
|
writeFileSync(join(root, LOCK_FILE), `${JSON.stringify({ version: 2, workspace: lock.workspace, ...(lock.pulledAt ? { pulledAt: lock.pulledAt } : {}), items }, null, 2)}\n`)
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -249,7 +249,7 @@ function sortEntry(entry: LockEntry): LockEntry {
|
|
|
249
249
|
...(entry.id ? { id: entry.id } : {}),
|
|
250
250
|
...(entry.revision ? { revision: entry.revision } : {}),
|
|
251
251
|
remote: entry.remote,
|
|
252
|
-
files: Object.fromEntries(Object.keys(entry.files).sort().map((k) => [k, entry.files[k]!])),
|
|
252
|
+
files: Object.fromEntries(Object.keys(entry.files).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)).map((k) => [k, entry.files[k]!])),
|
|
253
253
|
...(entry.version ? { version: entry.version } : {}),
|
|
254
254
|
...(entry.state ? { state: entry.state } : {}),
|
|
255
255
|
pulledAt: entry.pulledAt,
|
|
@@ -262,7 +262,7 @@ function sortConflict(c: LockConflict): LockConflict {
|
|
|
262
262
|
return {
|
|
263
263
|
remote: c.remote,
|
|
264
264
|
...(c.revision ? { revision: c.revision } : {}),
|
|
265
|
-
files: [...c.files].sort(),
|
|
265
|
+
files: [...c.files].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)),
|
|
266
266
|
at: c.at,
|
|
267
267
|
...(c.count !== undefined ? { count: c.count } : {}),
|
|
268
268
|
...(c.id ? { id: c.id } : {}),
|
|
@@ -400,7 +400,7 @@ export function computePlan(root: string, remote: RemoteItem[], lock: SyncLock |
|
|
|
400
400
|
for (const item of remote) {
|
|
401
401
|
seen.add(item.ref)
|
|
402
402
|
const entry = lock?.items[item.ref]
|
|
403
|
-
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(entry?.files ?? {})])].sort()
|
|
403
|
+
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(entry?.files ?? {})])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
404
404
|
const files: FileChange[] = paths.map((path) => ({ path, local: readComparable(root, path, opts.base), remote: item.files[path] ?? null }))
|
|
405
405
|
// An Agent's file that neither the base nor the workspace has but the folder does (its first tools, in a new
|
|
406
406
|
// `bindings.json`) is a change here too, as push reads it: otherwise status says nothing while push proposes it.
|
|
@@ -553,7 +553,7 @@ export function applyItems(root: string, workspace: string, items: RemoteItem[],
|
|
|
553
553
|
for (const item of items) {
|
|
554
554
|
const previous = next.items[item.ref]
|
|
555
555
|
if (opts.merge?.has(item.ref)) {
|
|
556
|
-
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(previous?.files ?? {})])].sort()
|
|
556
|
+
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(previous?.files ?? {})])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
557
557
|
const result = mergeItemFiles(baseFiles(previous, opts.base), localFiles(root, paths), item.files)
|
|
558
558
|
if (result.conflicts) {
|
|
559
559
|
const files = result.files.filter((f) => f.conflicts.length).map((f) => f.path)
|
|
@@ -633,7 +633,7 @@ export function refreshConflict(root: string, workspace: string, item: RemoteIte
|
|
|
633
633
|
const previous = next.items[item.ref]
|
|
634
634
|
const remote = itemHash(item.files)
|
|
635
635
|
if (!previous?.conflict || previous.conflict.remote === remote) return { lock: next, outcome: 'unchanged', conflicts: previous?.conflict?.count ?? 0, written: [], removed: [] }
|
|
636
|
-
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(previous.files), ...previous.conflict.files])].sort()
|
|
636
|
+
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(previous.files), ...previous.conflict.files])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
637
637
|
const result = mergeItemFiles(baseFiles(previous, base), localFiles(root, paths), item.files)
|
|
638
638
|
const now = new Date().toISOString()
|
|
639
639
|
const written: string[] = []
|
|
@@ -673,7 +673,7 @@ export type Resolution = { edited: true } | { keep?: Side; picks?: Map<number, C
|
|
|
673
673
|
export function resolveItem(root: string, workspace: string, item: RemoteItem, lock: SyncLock | null, how: Resolution, base?: (path: string) => string | null): { written: string[]; removed: string[]; lock: SyncLock } {
|
|
674
674
|
const next: SyncLock = { version: 2, workspace, items: { ...(lock?.workspace === workspace ? lock.items : {}) } }
|
|
675
675
|
const previous = next.items[item.ref]
|
|
676
|
-
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(previous?.files ?? {}), ...(previous?.conflict?.files ?? [])])].sort()
|
|
676
|
+
const paths = [...new Set([...Object.keys(item.files), ...Object.keys(previous?.files ?? {}), ...(previous?.conflict?.files ?? [])])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
677
677
|
const written: string[] = []
|
|
678
678
|
const removed: string[] = []
|
|
679
679
|
if (!('edited' in how)) {
|
|
@@ -228,7 +228,7 @@ export function itemFiles(root: string, kind: SyncKind, name: string, entry?: Lo
|
|
|
228
228
|
// What the last pull wrote for it comes along when it is still here (an agent's files are fixed; the rest may not be).
|
|
229
229
|
if (kind !== 'agent' && kind !== 'knowledge') for (const p of Object.keys(entry?.files ?? {})) if (!paths.includes(p)) paths.push(p)
|
|
230
230
|
const files: Record<string, string> = {}
|
|
231
|
-
for (const path of [...new Set(paths)].sort()) {
|
|
231
|
+
for (const path of [...new Set(paths)].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
232
232
|
if (path.endsWith('.workspace') || assets.includes(path)) continue
|
|
233
233
|
const text = readLocal(root, path)
|
|
234
234
|
if (text === null) continue
|
|
@@ -372,7 +372,7 @@ export function pushCandidates(input: CandidateInput): PushItem[] {
|
|
|
372
372
|
const here = projectRefs(root)
|
|
373
373
|
const refs = new Set<string>([...plan.map((p) => p.ref), ...here.keys()])
|
|
374
374
|
const out: PushItem[] = []
|
|
375
|
-
for (const ref of [...refs].sort()) {
|
|
375
|
+
for (const ref of [...refs].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))) {
|
|
376
376
|
const slash = ref.indexOf('/')
|
|
377
377
|
const kind = (byPlan.get(ref)?.kind ?? here.get(ref)?.kind ?? ref.slice(0, slash)) as SyncKind
|
|
378
378
|
const name = byPlan.get(ref)?.name ?? here.get(ref)?.name ?? ref.slice(slash + 1)
|
|
@@ -404,7 +404,7 @@ export function pushCandidates(input: CandidateInput): PushItem[] {
|
|
|
404
404
|
if (status === 'same' && Object.keys(files).length === 0) continue
|
|
405
405
|
const isNew = !entry && !theirs
|
|
406
406
|
const baseFiles: Record<string, string> = !isNew && theirs && entry && itemHash(theirs.files) === entry.remote ? theirs.files : {}
|
|
407
|
-
const diffPaths = [...new Set([...Object.keys(baseFiles), ...Object.keys(files)])].sort()
|
|
407
|
+
const diffPaths = [...new Set([...Object.keys(baseFiles), ...Object.keys(files)])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
408
408
|
const diff: PushFile[] = diffPaths.map((path) => ({ path, base: baseFiles[path] ?? null, local: files[path] ?? null })).filter((f) => f.base !== f.local)
|
|
409
409
|
const item: PushItem = {
|
|
410
410
|
ref,
|
|
@@ -629,7 +629,7 @@ export function recordRequest(root: string, workspace: string, view: RequestView
|
|
|
629
629
|
const file = readRequests(root, workspace)
|
|
630
630
|
const now = (opts.now ?? new Date()).toISOString()
|
|
631
631
|
const before = file.requests[view.request.id]
|
|
632
|
-
const refs = view.items.map((i) => i.ref).sort()
|
|
632
|
+
const refs = view.items.map((i) => i.ref).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
633
633
|
const titles = Object.fromEntries(refs.map((r) => [r, opts.titles?.[r] ?? before?.titles[r] ?? r]))
|
|
634
634
|
file.requests[view.request.id] = {
|
|
635
635
|
id: view.request.id,
|
|
@@ -716,7 +716,7 @@ export function sameDocument(path: string, a: string, b: string): boolean {
|
|
|
716
716
|
/** Quality checks as a set: by question, each one's lists in order of their words. */
|
|
717
717
|
function checkSet(text: string): unknown {
|
|
718
718
|
return readQualityChecks(text)
|
|
719
|
-
.map((c) => ({ ...c, documents: [...(c.documents ?? [])].sort(), keywords: [...(c.keywords ?? [])].sort() }))
|
|
719
|
+
.map((c) => ({ ...c, documents: [...(c.documents ?? [])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)), keywords: [...(c.keywords ?? [])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)) }))
|
|
720
720
|
.sort((x, y) => x.question.localeCompare(y.question))
|
|
721
721
|
}
|
|
722
722
|
|
|
@@ -375,7 +375,7 @@ function finish(it: RemoteItem, directory: Directory, docs: Array<{ path: string
|
|
|
375
375
|
}
|
|
376
376
|
it.files[path] = format === 'json' ? json(named) : stringifyYaml(named, { lineWidth: 0 })
|
|
377
377
|
}
|
|
378
|
-
it.requires = [...requires].sort()
|
|
378
|
+
it.requires = [...requires].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
379
379
|
return it
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -408,7 +408,7 @@ export type AgentContext = { grants: Grants; note?: string } | { unread: string
|
|
|
408
408
|
|
|
409
409
|
const asGrants = (granted: Array<{ objectTypeApiName?: string; permissions?: string[] }>): Grants =>
|
|
410
410
|
granted
|
|
411
|
-
.flatMap((g) => (typeof g.objectTypeApiName === 'string' ? [{ objectType: `objectType/${g.objectTypeApiName}`, permissions: [...(g.permissions?.length ? g.permissions : ['read'])].sort() }] : []))
|
|
411
|
+
.flatMap((g) => (typeof g.objectTypeApiName === 'string' ? [{ objectType: `objectType/${g.objectTypeApiName}`, permissions: [...(g.permissions?.length ? g.permissions : ['read'])].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)) }] : []))
|
|
412
412
|
.sort((a, b) => a.objectType.localeCompare(b.objectType))
|
|
413
413
|
|
|
414
414
|
const STEWARD_FIX = 'An organization key made by a Blueprint Steward with organization update reads it, and so does the workspace key on a newer platform'
|
|
@@ -866,7 +866,7 @@ async function readFunctions(source: SyncSource, lists: Lists, opts: ReadOptions
|
|
|
866
866
|
const keep = imported.filter((p) => p.startsWith('functions/'))
|
|
867
867
|
const test = `functions/${row.slug}.test.ts`
|
|
868
868
|
if (files.has(test)) keep.push(test)
|
|
869
|
-
const beyond = imported.filter((p) => !p.startsWith('functions/') && !p.startsWith('vendor/')).sort()
|
|
869
|
+
const beyond = imported.filter((p) => !p.startsWith('functions/') && !p.startsWith('vendor/')).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
870
870
|
const shared = beyond.filter(isSharedFunctionFile)
|
|
871
871
|
const outside = beyond.filter((p) => !isSharedFunctionFile(p))
|
|
872
872
|
for (const p of [...keep, ...shared]) it.files[p] = files.get(p)!
|
|
@@ -936,7 +936,7 @@ export function noteSharedFunctionFiles(items: RemoteItem[], warnings: string[])
|
|
|
936
936
|
for (const it of items) for (const path of Object.keys(it.files)) holders.set(path, [...(holders.get(path) ?? []), it])
|
|
937
937
|
for (const [path, list] of holders) {
|
|
938
938
|
if (list.length < 2 || new Set(list.map((i) => i.files[path])).size < 2) continue
|
|
939
|
-
const names = list.map((i) => i.name).sort()
|
|
939
|
+
const names = list.map((i) => i.name).sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
940
940
|
const and = (words: string[]) => (words.length < 2 ? words.join('') : `${words.slice(0, -1).join(', ')} and ${words[words.length - 1]}`)
|
|
941
941
|
for (const it of list) {
|
|
942
942
|
const others = names.filter((n) => n !== it.name)
|
|
@@ -1202,7 +1202,7 @@ export function objectSetItems(sets: ObjectSetRow[], directory: Directory, objec
|
|
|
1202
1202
|
it.unresolved.push(...unresolved.map((u) => ({ ...u, path: `blueprint/object-sets/${name}.yaml: ${u.path}` })))
|
|
1203
1203
|
it.files[`blueprint/object-sets/${name}.yaml`] = stringifyYaml(named, { lineWidth: 0 })
|
|
1204
1204
|
const others = referencesIn(named).filter((r) => r.kind === 'objectSet' && r.name !== name).map((r) => itemRef(r.kind, r.name))
|
|
1205
|
-
it.requires = [itemRef('objectType', set.objectType), ...new Set(others)].sort()
|
|
1205
|
+
it.requires = [itemRef('objectType', set.objectType), ...new Set(others)].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0))
|
|
1206
1206
|
if (set.kind === 'static') {
|
|
1207
1207
|
const n = set.staticPks?.length ?? 0
|
|
1208
1208
|
it.notes.push(`Lists ${n} ${set.objectType} record${n === 1 ? '' : 's'} by key; locally it holds those of them this runtime’s data has`)
|
|
@@ -1595,7 +1595,7 @@ async function readEvaluations(source: SyncSource, lists: Lists, directory: Dire
|
|
|
1595
1595
|
const file = `agents/${slug}/evals/${slugify(ds.name)}.yaml`
|
|
1596
1596
|
const header = `# Test cases from the platform's evaluation dataset "${ds.name}" (frontera local sync).\n`
|
|
1597
1597
|
const doc = { ...(isRegression ? { regression: true } : {}), cases }
|
|
1598
|
-
out.push(item('evaluation', `${slug}.${slugify(ds.name)}`, `${ds.name} → ${slug}`, { [file]: header + stringifyYaml(doc, { lineWidth: 0 }) }, { notes, requires: [...requires].sort() }))
|
|
1598
|
+
out.push(item('evaluation', `${slug}.${slugify(ds.name)}`, `${ds.name} → ${slug}`, { [file]: header + stringifyYaml(doc, { lineWidth: 0 }) }, { notes, requires: [...requires].sort((a, b) => (String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0)) }))
|
|
1599
1599
|
}
|
|
1600
1600
|
}
|
|
1601
1601
|
return out
|